Releases: PPLEThai/pple-today
Release list
@api/backoffice@1.10.0
Minor Changes
-
#426
7b2634bThanks @PanJ! - Expose today's notification send count for the platform Console Usage tile- New
GET /platform/mini-apps/:id/notification-usagereturns{ sent }— how many audience-bound notifications the app's active key has sent in the current Asia/Bangkok quota day. - Uses the same
NotificationApiKeyUsageLogwindow as the send path's claim, so the Console tile and a 429 cannot disagree on what "today" means. - Gated by the existing platform service token. An app with no active key returns
NOTIFICATION_API_KEY_NOT_FOUND(unavailable on the platform side); zero sends today returns{ sent: 0 }.
- New
@pple-today/database@1.6.0
Minor Changes
-
#417
54f98b6Thanks @PanJ! - Beta invites for Builder Apps- New
MiniAppInvitemodel: a Builder invites a tester by phone number, and the invitation is delivered through the existing PPLE Today notification pipeline. The number is only a delivery address — accepting binds the invite to the invitee's account (userId), and listing eligibility matches on that alone, so changing a phone number never revokes access that was already consented to. GET /mini-app/invitesplusPOST /mini-app/invites/:miniAppId/acceptand/declinelet the invitee see and answer their invitations. A Beta app appears in their mini app list only after they accept; pending and declined invitations show nothing.- The
/platformservice API gainsGET/POST /platform/mini-apps/:id/invitesandDELETE .../invites/:phoneNumberfor managing an app's testers, behind the existing platform service token. The 20-tester cap is enforced here with an actionable error; removing a tester revokes their access and frees the seat, and a declined invitation does not hold one. - BETA-tier listing now resolves to the owner or an accepted invitee. DRAFT and LIVE visibility are unchanged.
- Invited numbers are validated as Thai mobile numbers after normalisation, so a malformed number is rejected outright rather than stored as an undeliverable row holding one of the app's twenty tester seats.
notifiedreflects whether the invitation actually reached a PPLE Today account, not merely that the send call succeeded — the pipeline reports unmatched numbers asfailedrather than erroring.sendNotificationToUsernow accepts sends with no notification API key, for platform-internal deliveries that have no key usage to meter. Existing keyed senders are unaffected.
- New
-
#414
c9b2a51Thanks @PanJ! - App User registration & per-app user count for Builder Apps- New
MiniAppUsermodel ((miniAppId, userId)composite key,firstOpenedAt): the registry of who has opened each mini app. It is the consent boundary for audience-bound notifications and the source of the Console's per-app user count. - Opening a mini app now registers the caller as that app's App User on the token-exchange path (
POST /auth/mini-app/:slug), for every tier. Registration is an idempotent upsert, so repeat opens are a no-op and preserve the originalfirstOpenedAt. It is best-effort: a registry write failure is logged, never surfaced, so it can't block a user from opening an app. - New service-to-service
/platformAPI, gated by a dedicatedPLATFORM_SERVICE_TOKEN(separate from admin/user auth; rejects all requests when unset). Its first endpoint,GET /platform/mini-apps/:id/user-count, returns the per-app App User count for the pple-platform provisioner.
- New
-
#416
e2da97dThanks @PanJ! - Platform service API for the Builder App lifecycleThe
/platformservice-to-service API (gated byPLATFORM_SERVICE_TOKEN, separate from admin/user auth) gains the endpoints the pple-platform provisioner uses to drive a Builder App from creation to retirement. today-v2 remains the single writer of Zitadel state — every Zitadel effect flows throughZitadelService.POST /platform/mini-apps— create a complete app registration in one call: a Zitadel OIDC app, aDRAFT/PLATFORMmini-app row owned by the Builder (ownerSub), and an app-bound notification key. Returns theclientIdand the notification key (shown once, stored hashed). The app is immediately visible to its owner as a Draft.PATCH /platform/mini-apps/:id— update name/icon/url; a URL change re-syncs the Zitadel redirect URIs before persisting.PUT /platform/mini-apps/:id/tier— set the effective tier (DRAFT/BETA/LIVE).PUT /platform/mini-apps/:id/roles— set the Live-tier visibility roles (reusesMiniAppRole; empty = everyone).DELETE /platform/mini-apps/:id— retire the app: delete its Zitadel OIDC app, soft-delete the row (retiredAt), and deactivate its notification key. Retired apps are hidden from every mini-app list; the row and its App User registry are kept for audit.
Every mutation invalidates the mini-app list cache so effects surface in PPLE Today promptly.
Schema:
MiniAppgainszitadelAppId(so retire can delete the OIDC app) andretiredAt(soft-retire);NotificationApiKeygains a nullableminiAppIdbinding it to an app (null = legacy central-team key, unchanged).
@pple-today/api-common@1.6.0
Minor Changes
-
#421
d4ca97dThanks @PanJ! - Read-only admin view of platform-managed mini apps- The
MiniAppDTO gainssource(ADMIN/PLATFORM),ownerSub, andcreatedAt, exposed to the admin backoffice API alongside the existing fields. - The admin mini-app table shows tier, source, owner, and created date for every app, with a "จัดการโดย PPLE Platform" marker on platform-provisioned apps.
- Platform-sourced apps have no edit/delete controls in the table and are rejected by the admin update/delete endpoints (
MINI_APP_PLATFORM_MANAGED) — they're owned by the Provisioner, not this admin. Manual admin app management is unchanged for ADMIN-source apps.
- The
-
#417
54f98b6Thanks @PanJ! - Beta invites for Builder Apps- New
MiniAppInvitemodel: a Builder invites a tester by phone number, and the invitation is delivered through the existing PPLE Today notification pipeline. The number is only a delivery address — accepting binds the invite to the invitee's account (userId), and listing eligibility matches on that alone, so changing a phone number never revokes access that was already consented to. GET /mini-app/invitesplusPOST /mini-app/invites/:miniAppId/acceptand/declinelet the invitee see and answer their invitations. A Beta app appears in their mini app list only after they accept; pending and declined invitations show nothing.- The
/platformservice API gainsGET/POST /platform/mini-apps/:id/invitesandDELETE .../invites/:phoneNumberfor managing an app's testers, behind the existing platform service token. The 20-tester cap is enforced here with an actionable error; removing a tester revokes their access and frees the seat, and a declined invitation does not hold one. - BETA-tier listing now resolves to the owner or an accepted invitee. DRAFT and LIVE visibility are unchanged.
- Invited numbers are validated as Thai mobile numbers after normalisation, so a malformed number is rejected outright rather than stored as an undeliverable row holding one of the app's twenty tester seats.
notifiedreflects whether the invitation actually reached a PPLE Today account, not merely that the send call succeeded — the pipeline reports unmatched numbers asfailedrather than erroring.sendNotificationToUsernow accepts sends with no notification API key, for platform-internal deliveries that have no key usage to meter. Existing keyed senders are unaffected.
- New
-
#420
2d33915Thanks @PanJ! - Full-phone identity lookup for CollaboratorsPOST /platform/users/lookupresolves a complete mobile number to{ sub, name }behind the platform service token, so the Provisioner can ask an Owner to confirm a masked name before granting Collaborator access. Exact match only — incomplete or malformed numbers are reported as not-found and never searched as prefixes.isThaiMobileE164is extracted into@pple-today/api-commonand shared with Beta invite phone validation, so "whole Thai mobile after normalisation" is one check rather than two copies of the same regex.
Patch Changes
-
#425
f8efa12Thanks @PanJ! - Allow Builder Apps to self-deep-link notifications via optionallinkPathon the audience-bound send route. Path-only values are joined to the sending app's redirect entry; absolute URLs and cross-app destinations are rejected withNOTIFICATION_INVALID_LINK_PATH. -
Updated dependencies [
54f98b6,c9b2a51,e2da97d]:- @pple-today/database@1.6.0
@client/backoffice@1.4.0
Minor Changes
-
#421
d4ca97dThanks @PanJ! - Read-only admin view of platform-managed mini apps- The
MiniAppDTO gainssource(ADMIN/PLATFORM),ownerSub, andcreatedAt, exposed to the admin backoffice API alongside the existing fields. - The admin mini-app table shows tier, source, owner, and created date for every app, with a "จัดการโดย PPLE Platform" marker on platform-provisioned apps.
- Platform-sourced apps have no edit/delete controls in the table and are rejected by the admin update/delete endpoints (
MINI_APP_PLATFORM_MANAGED) — they're owned by the Provisioner, not this admin. Manual admin app management is unchanged for ADMIN-source apps.
- The
@api/ballot-crypto@1.1.12
@api/backoffice@1.9.0
Minor Changes
-
#421
d4ca97dThanks @PanJ! - Read-only admin view of platform-managed mini apps- The
MiniAppDTO gainssource(ADMIN/PLATFORM),ownerSub, andcreatedAt, exposed to the admin backoffice API alongside the existing fields. - The admin mini-app table shows tier, source, owner, and created date for every app, with a "จัดการโดย PPLE Platform" marker on platform-provisioned apps.
- Platform-sourced apps have no edit/delete controls in the table and are rejected by the admin update/delete endpoints (
MINI_APP_PLATFORM_MANAGED) — they're owned by the Provisioner, not this admin. Manual admin app management is unchanged for ADMIN-source apps.
- The
-
#417
54f98b6Thanks @PanJ! - Beta invites for Builder Apps- New
MiniAppInvitemodel: a Builder invites a tester by phone number, and the invitation is delivered through the existing PPLE Today notification pipeline. The number is only a delivery address — accepting binds the invite to the invitee's account (userId), and listing eligibility matches on that alone, so changing a phone number never revokes access that was already consented to. GET /mini-app/invitesplusPOST /mini-app/invites/:miniAppId/acceptand/declinelet the invitee see and answer their invitations. A Beta app appears in their mini app list only after they accept; pending and declined invitations show nothing.- The
/platformservice API gainsGET/POST /platform/mini-apps/:id/invitesandDELETE .../invites/:phoneNumberfor managing an app's testers, behind the existing platform service token. The 20-tester cap is enforced here with an actionable error; removing a tester revokes their access and frees the seat, and a declined invitation does not hold one. - BETA-tier listing now resolves to the owner or an accepted invitee. DRAFT and LIVE visibility are unchanged.
- Invited numbers are validated as Thai mobile numbers after normalisation, so a malformed number is rejected outright rather than stored as an undeliverable row holding one of the app's twenty tester seats.
notifiedreflects whether the invitation actually reached a PPLE Today account, not merely that the send call succeeded — the pipeline reports unmatched numbers asfailedrather than erroring.sendNotificationToUsernow accepts sends with no notification API key, for platform-internal deliveries that have no key usage to meter. Existing keyed senders are unaffected.
- New
-
#414
c9b2a51Thanks @PanJ! - App User registration & per-app user count for Builder Apps- New
MiniAppUsermodel ((miniAppId, userId)composite key,firstOpenedAt): the registry of who has opened each mini app. It is the consent boundary for audience-bound notifications and the source of the Console's per-app user count. - Opening a mini app now registers the caller as that app's App User on the token-exchange path (
POST /auth/mini-app/:slug), for every tier. Registration is an idempotent upsert, so repeat opens are a no-op and preserve the originalfirstOpenedAt. It is best-effort: a registry write failure is logged, never surfaced, so it can't block a user from opening an app. - New service-to-service
/platformAPI, gated by a dedicatedPLATFORM_SERVICE_TOKEN(separate from admin/user auth; rejects all requests when unset). Its first endpoint,GET /platform/mini-apps/:id/user-count, returns the per-app App User count for the pple-platform provisioner.
- New
-
#416
e2da97dThanks @PanJ! - Platform service API for the Builder App lifecycleThe
/platformservice-to-service API (gated byPLATFORM_SERVICE_TOKEN, separate from admin/user auth) gains the endpoints the pple-platform provisioner uses to drive a Builder App from creation to retirement. today-v2 remains the single writer of Zitadel state — every Zitadel effect flows throughZitadelService.POST /platform/mini-apps— create a complete app registration in one call: a Zitadel OIDC app, aDRAFT/PLATFORMmini-app row owned by the Builder (ownerSub), and an app-bound notification key. Returns theclientIdand the notification key (shown once, stored hashed). The app is immediately visible to its owner as a Draft.PATCH /platform/mini-apps/:id— update name/icon/url; a URL change re-syncs the Zitadel redirect URIs before persisting.PUT /platform/mini-apps/:id/tier— set the effective tier (DRAFT/BETA/LIVE).PUT /platform/mini-apps/:id/roles— set the Live-tier visibility roles (reusesMiniAppRole; empty = everyone).DELETE /platform/mini-apps/:id— retire the app: delete its Zitadel OIDC app, soft-delete the row (retiredAt), and deactivate its notification key. Retired apps are hidden from every mini-app list; the row and its App User registry are kept for audit.
Every mutation invalidates the mini-app list cache so effects surface in PPLE Today promptly.
Schema:
MiniAppgainszitadelAppId(so retire can delete the OIDC app) andretiredAt(soft-retire);NotificationApiKeygains a nullableminiAppIdbinding it to an app (null = legacy central-team key, unchanged). -
#420
2d33915Thanks @PanJ! - Full-phone identity lookup for CollaboratorsPOST /platform/users/lookupresolves a complete mobile number to{ sub, name }behind the platform service token, so the Provisioner can ask an Owner to confirm a masked name before granting Collaborator access. Exact match only — incomplete or malformed numbers are reported as not-found and never searched as prefixes.isThaiMobileE164is extracted into@pple-today/api-commonand shared with Beta invite phone validation, so "whole Thai mobile after normalisation" is one check rather than two copies of the same regex.
Patch Changes
-
#425
f8efa12Thanks @PanJ! - Allow Builder Apps to self-deep-link notifications via optionallinkPathon the audience-bound send route. Path-only values are joined to the sending app's redirect entry; absolute URLs and cross-app destinations are rejected withNOTIFICATION_INVALID_LINK_PATH. -
#423
394226cThanks @PanJ! - Make the Beta invite seat cap atomic under concurrency- Claiming a tester seat now runs in a single DB transaction that locks the app row before counting held invites and upserting, so two concurrent creates at seat 19 cannot both insert.
- The 20-tester limit stays a service-level constant (
MINI_APP_INVITE_LIMIT); over-cap still returnsMINI_APP_INVITE_LIMIT_EXCEEDEDnaming the limit, and re-opening a declined invite still re-checks the cap.
-
#424
a89a50dThanks @PanJ! - Make the notification daily quota atomic under concurrency- Claiming a send against a key's daily quota now runs in a single DB transaction that locks the
NotificationApiKeyrow before counting usage and writing the log, so two concurrent sends at the last remaining slot cannot both insert. - The claim happens before the send; a failed send releases the claim so internal failures still do not consume budget. Empty-audience sends remain metered. 429 payload shape (
dailyQuota,remaining,resetAt) is unchanged.
- Claiming a send against a key's daily quota now runs in a single DB transaction that locks the
-
#422
c38503bThanks @PanJ! - Tier-gate mini-app token exchange for Draft/Beta appsPOST /auth/mini-app/:slugnow applies the same eligibility rules as listing (isMiniAppVisible): Draft is owner-only, Beta is owner or ACCEPTED invitee, Live keeps the existing role filter (empty = public). Ineligible callers getMINI_APP_NOT_FOUND, so App User registration still runs only after a successful exchange. -
Updated dependencies [
f8efa12,d4ca97d,54f98b6,c9b2a51,e2da97d,2d33915]:- @pple-today/api-common@1.6.0
- @pple-today/database@1.6.0
@pple-today/database@1.5.0
Minor Changes
-
#412
9172ee6Thanks @PanJ! - Tier-aware mini app listing for Builder AppsMiniAppgainstier(DRAFT/BETA/LIVE),source(ADMIN/PLATFORM), andownerSub. Existing rows default to LIVE/ADMIN with no owner, so they stay visible under the current role rules exactly as before.- The mini app listing (
GET /mini-app) is now tier-aware per requesting user: LIVE keeps its role-based visibility, while DRAFT and BETA apps are visible only to their owner (accepted BETA invitees come later). A Builder sees their own Draft in their normal list and nobody else does. - The listing response now includes
tierso the client can badge non-LIVE apps.
@pple-today/api-common@1.5.0
Minor Changes
-
#412
9172ee6Thanks @PanJ! - Tier-aware mini app listing for Builder AppsMiniAppgainstier(DRAFT/BETA/LIVE),source(ADMIN/PLATFORM), andownerSub. Existing rows default to LIVE/ADMIN with no owner, so they stay visible under the current role rules exactly as before.- The mini app listing (
GET /mini-app) is now tier-aware per requesting user: LIVE keeps its role-based visibility, while DRAFT and BETA apps are visible only to their owner (accepted BETA invitees come later). A Builder sees their own Draft in their normal list and nobody else does. - The listing response now includes
tierso the client can badge non-LIVE apps.
Patch Changes
- Updated dependencies [
9172ee6]:- @pple-today/database@1.5.0
@api/ballot-crypto@1.1.11
Patch Changes
- Updated dependencies [
9172ee6]:- @pple-today/api-common@1.5.0
@api/backoffice@1.8.0
Minor Changes
-
#412
9172ee6Thanks @PanJ! - Tier-aware mini app listing for Builder AppsMiniAppgainstier(DRAFT/BETA/LIVE),source(ADMIN/PLATFORM), andownerSub. Existing rows default to LIVE/ADMIN with no owner, so they stay visible under the current role rules exactly as before.- The mini app listing (
GET /mini-app) is now tier-aware per requesting user: LIVE keeps its role-based visibility, while DRAFT and BETA apps are visible only to their owner (accepted BETA invitees come later). A Builder sees their own Draft in their normal list and nobody else does. - The listing response now includes
tierso the client can badge non-LIVE apps.
Patch Changes
- Updated dependencies [
9172ee6]:- @pple-today/api-common@1.5.0
- @pple-today/database@1.5.0