@cosmicdrift/kumiko-bundled-features 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +90 -0
- package/src/audit/__tests__/audit.integration.ts +328 -0
- package/src/audit/constants.ts +7 -0
- package/src/audit/feature.ts +23 -0
- package/src/audit/handlers/list.query.ts +98 -0
- package/src/audit/index.ts +2 -0
- package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.ts +149 -0
- package/src/auth-email-password/__tests__/account-lockout.integration.ts +308 -0
- package/src/auth-email-password/__tests__/auth-claims.integration.ts +512 -0
- package/src/auth-email-password/__tests__/auth.integration.ts +610 -0
- package/src/auth-email-password/__tests__/confirm-token-flow.test.ts +67 -0
- package/src/auth-email-password/__tests__/email-templates.test.ts +106 -0
- package/src/auth-email-password/__tests__/email-verification.integration.ts +327 -0
- package/src/auth-email-password/__tests__/identity-v3-hash.test.ts +174 -0
- package/src/auth-email-password/__tests__/identity-v3-login.integration.ts +150 -0
- package/src/auth-email-password/__tests__/invite-flow.integration.ts +458 -0
- package/src/auth-email-password/__tests__/multi-roles.integration.ts +256 -0
- package/src/auth-email-password/__tests__/password-reset.integration.ts +346 -0
- package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.ts +144 -0
- package/src/auth-email-password/__tests__/seed-admin.integration.ts +176 -0
- package/src/auth-email-password/__tests__/session-callbacks.integration.ts +310 -0
- package/src/auth-email-password/__tests__/session-strict-mode.integration.ts +101 -0
- package/src/auth-email-password/__tests__/signed-token.test.ts +78 -0
- package/src/auth-email-password/__tests__/signup-flow.integration.ts +259 -0
- package/src/auth-email-password/auth-user-row.ts +41 -0
- package/src/auth-email-password/constants.ts +101 -0
- package/src/auth-email-password/email-templates.ts +283 -0
- package/src/auth-email-password/feature.ts +140 -0
- package/src/auth-email-password/handlers/change-password.write.ts +58 -0
- package/src/auth-email-password/handlers/confirm-token-flow.ts +191 -0
- package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +203 -0
- package/src/auth-email-password/handlers/invite-accept.write.ts +189 -0
- package/src/auth-email-password/handlers/invite-create.write.ts +145 -0
- package/src/auth-email-password/handlers/invite-signup-complete.write.ts +192 -0
- package/src/auth-email-password/handlers/login.write.ts +208 -0
- package/src/auth-email-password/handlers/logout.write.ts +12 -0
- package/src/auth-email-password/handlers/request-email-verification.write.ts +29 -0
- package/src/auth-email-password/handlers/request-password-reset.write.ts +31 -0
- package/src/auth-email-password/handlers/reset-password.write.ts +61 -0
- package/src/auth-email-password/handlers/signup-confirm.write.ts +170 -0
- package/src/auth-email-password/handlers/signup-request.write.ts +104 -0
- package/src/auth-email-password/handlers/token-request-handler.ts +114 -0
- package/src/auth-email-password/handlers/verify-email.write.ts +62 -0
- package/src/auth-email-password/i18n.ts +211 -0
- package/src/auth-email-password/identity-v3-hash.ts +97 -0
- package/src/auth-email-password/index.ts +35 -0
- package/src/auth-email-password/invite-token-store.ts +92 -0
- package/src/auth-email-password/lockout-store.ts +118 -0
- package/src/auth-email-password/password-hashing.ts +43 -0
- package/src/auth-email-password/reset-token.ts +28 -0
- package/src/auth-email-password/seeding.ts +183 -0
- package/src/auth-email-password/signed-token.ts +85 -0
- package/src/auth-email-password/signup-token-store.ts +104 -0
- package/src/auth-email-password/stream-tenant.ts +31 -0
- package/src/auth-email-password/testing.ts +5 -0
- package/src/auth-email-password/token-burn-store.ts +57 -0
- package/src/auth-email-password/verification-token.ts +27 -0
- package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +51 -0
- package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +80 -0
- package/src/auth-email-password/web/__tests__/login-screen.test.tsx +94 -0
- package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +108 -0
- package/src/auth-email-password/web/__tests__/session-roles.test.ts +54 -0
- package/src/auth-email-password/web/__tests__/tenant-switcher.test.tsx +100 -0
- package/src/auth-email-password/web/__tests__/test-utils.tsx +73 -0
- package/src/auth-email-password/web/__tests__/user-menu.test.tsx +55 -0
- package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +59 -0
- package/src/auth-email-password/web/auth-client.ts +350 -0
- package/src/auth-email-password/web/auth-form-primitives.tsx +70 -0
- package/src/auth-email-password/web/auth-gate.tsx +33 -0
- package/src/auth-email-password/web/client-plugin.ts +48 -0
- package/src/auth-email-password/web/default-topbar-actions.tsx +47 -0
- package/src/auth-email-password/web/forgot-password-screen.tsx +110 -0
- package/src/auth-email-password/web/index.ts +56 -0
- package/src/auth-email-password/web/invite-accept-screen.tsx +220 -0
- package/src/auth-email-password/web/login-screen.tsx +150 -0
- package/src/auth-email-password/web/reset-password-screen.tsx +152 -0
- package/src/auth-email-password/web/session.tsx +171 -0
- package/src/auth-email-password/web/signup-complete-screen.tsx +150 -0
- package/src/auth-email-password/web/signup-screen.tsx +130 -0
- package/src/auth-email-password/web/tenant-switcher.tsx +116 -0
- package/src/auth-email-password/web/use-shell-user.ts +34 -0
- package/src/auth-email-password/web/user-menu.tsx +89 -0
- package/src/auth-email-password/web/verify-email-screen.tsx +102 -0
- package/src/billing-foundation/__tests__/billing-foundation.integration.ts +568 -0
- package/src/billing-foundation/__tests__/feature.test.ts +110 -0
- package/src/billing-foundation/__tests__/webhook-handler.test.ts +199 -0
- package/src/billing-foundation/aggregate-id.ts +21 -0
- package/src/billing-foundation/constants.ts +70 -0
- package/src/billing-foundation/entities.ts +50 -0
- package/src/billing-foundation/events.ts +71 -0
- package/src/billing-foundation/feature.ts +122 -0
- package/src/billing-foundation/get-subscription-for-tenant.ts +39 -0
- package/src/billing-foundation/handlers/create-checkout-session.write.ts +79 -0
- package/src/billing-foundation/handlers/create-portal-session.write.ts +73 -0
- package/src/billing-foundation/handlers/list-subscriptions.query.ts +20 -0
- package/src/billing-foundation/handlers/process-event.write.ts +160 -0
- package/src/billing-foundation/index.ts +42 -0
- package/src/billing-foundation/projection.ts +135 -0
- package/src/billing-foundation/types.ts +157 -0
- package/src/billing-foundation/webhook-handler.ts +184 -0
- package/src/cap-counter/__tests__/cap-counter.integration.ts +566 -0
- package/src/cap-counter/__tests__/enforce-cap.test.ts +422 -0
- package/src/cap-counter/__tests__/with-cap-enforcement.integration.ts +265 -0
- package/src/cap-counter/aggregate-id.ts +61 -0
- package/src/cap-counter/constants.ts +32 -0
- package/src/cap-counter/enforce-cap.ts +404 -0
- package/src/cap-counter/entity.ts +48 -0
- package/src/cap-counter/feature.ts +90 -0
- package/src/cap-counter/handlers/get-counter.query.ts +43 -0
- package/src/cap-counter/handlers/increment-rolling.write.ts +79 -0
- package/src/cap-counter/handlers/increment.write.ts +92 -0
- package/src/cap-counter/handlers/mark-soft-warned.write.ts +57 -0
- package/src/cap-counter/index.ts +34 -0
- package/src/cap-counter/with-cap-enforcement.ts +179 -0
- package/src/channel-email/email-channel.ts +48 -0
- package/src/channel-email/feature.ts +15 -0
- package/src/channel-email/index.ts +4 -0
- package/src/channel-email/smtp-transport.ts +65 -0
- package/src/channel-email/types.ts +34 -0
- package/src/channel-in-app/constants.ts +11 -0
- package/src/channel-in-app/feature.ts +30 -0
- package/src/channel-in-app/handlers/inbox.query.ts +28 -0
- package/src/channel-in-app/handlers/mark-all-read.write.ts +21 -0
- package/src/channel-in-app/handlers/mark-read.write.ts +32 -0
- package/src/channel-in-app/handlers/unread-count.query.ts +20 -0
- package/src/channel-in-app/in-app-channel.ts +44 -0
- package/src/channel-in-app/index.ts +4 -0
- package/src/channel-in-app/tables.ts +22 -0
- package/src/channel-push/feature.ts +15 -0
- package/src/channel-push/index.ts +3 -0
- package/src/channel-push/push-channel.ts +33 -0
- package/src/channel-push/types.ts +22 -0
- package/src/config/__tests__/app-overrides.test.ts +118 -0
- package/src/config/__tests__/config.integration.ts +1246 -0
- package/src/config/constants.ts +23 -0
- package/src/config/feature.ts +117 -0
- package/src/config/handlers/__tests__/prepare-config-write.test.ts +209 -0
- package/src/config/handlers/reset.write.ts +45 -0
- package/src/config/handlers/schema.query.ts +22 -0
- package/src/config/handlers/set.write.ts +93 -0
- package/src/config/handlers/values.query.ts +43 -0
- package/src/config/index.ts +15 -0
- package/src/config/resolver.ts +283 -0
- package/src/config/table.ts +35 -0
- package/src/config/write-helpers.ts +268 -0
- package/src/delivery/__tests__/delivery-events.integration.ts +166 -0
- package/src/delivery/__tests__/delivery.integration.ts +1405 -0
- package/src/delivery/constants.ts +33 -0
- package/src/delivery/delivery-service.ts +489 -0
- package/src/delivery/events.ts +18 -0
- package/src/delivery/feature.ts +70 -0
- package/src/delivery/handlers/log.query.ts +21 -0
- package/src/delivery/handlers/preferences.query.ts +18 -0
- package/src/delivery/handlers/set-preference.write.ts +28 -0
- package/src/delivery/index.ts +35 -0
- package/src/delivery/tables.ts +74 -0
- package/src/delivery/testing.ts +47 -0
- package/src/delivery/types.ts +71 -0
- package/src/delivery/unsubscribe.ts +99 -0
- package/src/delivery/upsert-preference.ts +145 -0
- package/src/feature-toggles/__tests__/feature-toggles.integration.ts +687 -0
- package/src/feature-toggles/constants.ts +20 -0
- package/src/feature-toggles/events.ts +18 -0
- package/src/feature-toggles/feature.ts +98 -0
- package/src/feature-toggles/global-feature-state-table.ts +28 -0
- package/src/feature-toggles/handlers/list.query.ts +26 -0
- package/src/feature-toggles/handlers/registered.query.ts +56 -0
- package/src/feature-toggles/handlers/set.write.ts +158 -0
- package/src/feature-toggles/index.ts +9 -0
- package/src/feature-toggles/toggle-runtime.ts +73 -0
- package/src/file-foundation/__tests__/feature.test.ts +35 -0
- package/src/file-foundation/__tests__/file-foundation.integration.ts +235 -0
- package/src/file-foundation/feature.ts +123 -0
- package/src/file-foundation/index.ts +7 -0
- package/src/file-provider-inmemory/__tests__/feature.test.ts +35 -0
- package/src/file-provider-inmemory/feature.ts +73 -0
- package/src/file-provider-inmemory/index.ts +3 -0
- package/src/file-provider-s3/__tests__/feature.test.ts +54 -0
- package/src/file-provider-s3/feature.ts +169 -0
- package/src/file-provider-s3/index.ts +3 -0
- package/src/files-provider-s3/__tests__/env-helper.test.ts +161 -0
- package/src/files-provider-s3/__tests__/s3-provider.integration.ts +134 -0
- package/src/files-provider-s3/__tests__/s3-provider.test.ts +36 -0
- package/src/files-provider-s3/env-helper.ts +49 -0
- package/src/files-provider-s3/index.ts +3 -0
- package/src/files-provider-s3/s3-provider.ts +114 -0
- package/src/foundation-shared/config-helpers.ts +67 -0
- package/src/foundation-shared/index.ts +4 -0
- package/src/jobs/__tests__/job-system-user.integration.ts +194 -0
- package/src/jobs/__tests__/jobs-events.integration.ts +143 -0
- package/src/jobs/__tests__/jobs-feature.integration.ts +342 -0
- package/src/jobs/constants.ts +21 -0
- package/src/jobs/events.ts +39 -0
- package/src/jobs/feature.ts +150 -0
- package/src/jobs/handlers/detail.query.ts +30 -0
- package/src/jobs/handlers/list.query.ts +36 -0
- package/src/jobs/handlers/retry.write.ts +69 -0
- package/src/jobs/handlers/trigger.write.ts +39 -0
- package/src/jobs/index.ts +5 -0
- package/src/jobs/job-run-logger.ts +213 -0
- package/src/jobs/job-run-table.ts +55 -0
- package/src/legal-pages/README.md +195 -0
- package/src/legal-pages/__tests__/legal-pages.integration.ts +361 -0
- package/src/legal-pages/constants.ts +36 -0
- package/src/legal-pages/feature.ts +187 -0
- package/src/legal-pages/index.ts +13 -0
- package/src/legal-pages/markdown.ts +69 -0
- package/src/mail-foundation/__tests__/feature.test.ts +46 -0
- package/src/mail-foundation/__tests__/mail-foundation.integration.ts +247 -0
- package/src/mail-foundation/feature.ts +160 -0
- package/src/mail-foundation/index.ts +14 -0
- package/src/mail-transport-inmemory/__tests__/feature.test.ts +37 -0
- package/src/mail-transport-inmemory/feature.ts +90 -0
- package/src/mail-transport-inmemory/index.ts +3 -0
- package/src/mail-transport-smtp/__tests__/feature.test.ts +61 -0
- package/src/mail-transport-smtp/feature.ts +182 -0
- package/src/mail-transport-smtp/index.ts +3 -0
- package/src/rate-limiting/__tests__/rate-limiting.integration.ts +84 -0
- package/src/rate-limiting/constants.ts +9 -0
- package/src/rate-limiting/feature.ts +16 -0
- package/src/rate-limiting/handlers/status.query.ts +52 -0
- package/src/rate-limiting/index.ts +2 -0
- package/src/renderer-simple/__tests__/simple-renderer.test.ts +97 -0
- package/src/renderer-simple/feature.ts +12 -0
- package/src/renderer-simple/index.ts +2 -0
- package/src/renderer-simple/simple-renderer.ts +72 -0
- package/src/secrets/__tests__/rotate.integration.ts +176 -0
- package/src/secrets/__tests__/secrets-events.integration.ts +125 -0
- package/src/secrets/__tests__/secrets.integration.ts +118 -0
- package/src/secrets/feature.ts +84 -0
- package/src/secrets/handlers/delete.write.ts +20 -0
- package/src/secrets/handlers/list.query.ts +38 -0
- package/src/secrets/handlers/rotate.job.ts +193 -0
- package/src/secrets/handlers/set.write.ts +50 -0
- package/src/secrets/index.ts +16 -0
- package/src/secrets/secrets-context.ts +296 -0
- package/src/secrets/table.ts +68 -0
- package/src/sessions/__tests__/cleanup.integration.ts +175 -0
- package/src/sessions/__tests__/password-auto-revoke.integration.ts +202 -0
- package/src/sessions/__tests__/sessions.integration.ts +472 -0
- package/src/sessions/__tests__/test-helpers.ts +66 -0
- package/src/sessions/constants.ts +43 -0
- package/src/sessions/feature.ts +84 -0
- package/src/sessions/handlers/cleanup.job.ts +109 -0
- package/src/sessions/handlers/list.query.ts +35 -0
- package/src/sessions/handlers/mine.query.ts +37 -0
- package/src/sessions/handlers/revoke-all-others.write.ts +42 -0
- package/src/sessions/handlers/revoke.write.ts +76 -0
- package/src/sessions/index.ts +17 -0
- package/src/sessions/schema/index.ts +5 -0
- package/src/sessions/schema/user-session.ts +67 -0
- package/src/sessions/session-callbacks.ts +110 -0
- package/src/sessions/testing.ts +42 -0
- package/src/subscription-mollie/__tests__/feature.test.ts +106 -0
- package/src/subscription-mollie/__tests__/mollie-foundation.integration.ts +421 -0
- package/src/subscription-mollie/__tests__/verify-webhook.test.ts +388 -0
- package/src/subscription-mollie/constants.ts +33 -0
- package/src/subscription-mollie/feature.ts +144 -0
- package/src/subscription-mollie/index.ts +13 -0
- package/src/subscription-mollie/plugin-methods.ts +79 -0
- package/src/subscription-mollie/verify-webhook.ts +244 -0
- package/src/subscription-stripe/__tests__/feature.test.ts +98 -0
- package/src/subscription-stripe/__tests__/plugin-methods.test.ts +161 -0
- package/src/subscription-stripe/__tests__/stripe-foundation.integration.ts +315 -0
- package/src/subscription-stripe/__tests__/verify-webhook.test.ts +306 -0
- package/src/subscription-stripe/constants.ts +20 -0
- package/src/subscription-stripe/feature.ts +120 -0
- package/src/subscription-stripe/index.ts +14 -0
- package/src/subscription-stripe/plugin-methods.ts +91 -0
- package/src/subscription-stripe/verify-webhook.ts +235 -0
- package/src/tenant/__tests__/multi-tenant.integration.ts +278 -0
- package/src/tenant/__tests__/seed-testing.integration.ts +229 -0
- package/src/tenant/__tests__/tenant.integration.ts +347 -0
- package/src/tenant/command-schemas.ts +37 -0
- package/src/tenant/constants.ts +37 -0
- package/src/tenant/feature.ts +109 -0
- package/src/tenant/handlers/active-tenant-ids.query.ts +19 -0
- package/src/tenant/handlers/add-member.write.ts +53 -0
- package/src/tenant/handlers/cancel-invitation.write.ts +87 -0
- package/src/tenant/handlers/create.write.ts +21 -0
- package/src/tenant/handlers/disable.write.ts +18 -0
- package/src/tenant/handlers/invitations.query.ts +31 -0
- package/src/tenant/handlers/list.query.ts +17 -0
- package/src/tenant/handlers/me.query.ts +17 -0
- package/src/tenant/handlers/members.query.ts +22 -0
- package/src/tenant/handlers/memberships.query.ts +24 -0
- package/src/tenant/handlers/remove-member.write.ts +40 -0
- package/src/tenant/handlers/resolve-user-ids.query.ts +43 -0
- package/src/tenant/handlers/update-member-roles.write.ts +54 -0
- package/src/tenant/handlers/update.write.ts +20 -0
- package/src/tenant/index.ts +12 -0
- package/src/tenant/invitation-table.ts +93 -0
- package/src/tenant/membership-table.ts +35 -0
- package/src/tenant/schema/index.ts +5 -0
- package/src/tenant/schema/tenant.ts +27 -0
- package/src/tenant/seeding.ts +155 -0
- package/src/tenant/testing.ts +8 -0
- package/src/text-content/README.md +190 -0
- package/src/text-content/__tests__/text-content.integration.ts +415 -0
- package/src/text-content/api.ts +92 -0
- package/src/text-content/constants.ts +19 -0
- package/src/text-content/feature.ts +29 -0
- package/src/text-content/handlers/by-slug.query.ts +55 -0
- package/src/text-content/handlers/set.write.ts +118 -0
- package/src/text-content/index.ts +14 -0
- package/src/text-content/seeding.ts +91 -0
- package/src/text-content/table.ts +45 -0
- package/src/tier-engine/__tests__/compose-app.test.ts +182 -0
- package/src/tier-engine/__tests__/drift.test.ts +42 -0
- package/src/tier-engine/__tests__/tier-engine.integration.ts +241 -0
- package/src/tier-engine/aggregate-id.ts +27 -0
- package/src/tier-engine/compose-app.ts +150 -0
- package/src/tier-engine/constants.ts +15 -0
- package/src/tier-engine/entity.ts +30 -0
- package/src/tier-engine/feature.ts +72 -0
- package/src/tier-engine/handlers/active-tier.query.ts +23 -0
- package/src/tier-engine/index.ts +22 -0
- package/src/user/__tests__/seed-testing.integration.ts +127 -0
- package/src/user/__tests__/user.integration.ts +198 -0
- package/src/user/command-schemas.ts +15 -0
- package/src/user/constants.ts +23 -0
- package/src/user/feature.ts +32 -0
- package/src/user/handlers/create.write.ts +54 -0
- package/src/user/handlers/detail.query.ts +9 -0
- package/src/user/handlers/find-for-auth.query.ts +38 -0
- package/src/user/handlers/list.query.ts +8 -0
- package/src/user/handlers/me.query.ts +15 -0
- package/src/user/handlers/update.write.ts +54 -0
- package/src/user/index.ts +4 -0
- package/src/user/schema/index.ts +5 -0
- package/src/user/schema/user.ts +69 -0
- package/src/user/seeding.ts +93 -0
- package/src/user/testing.ts +5 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { and, desc, eq } from "drizzle-orm";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { inAppMessagesTable } from "../tables";
|
|
5
|
+
|
|
6
|
+
export const inboxQuery = defineQueryHandler({
|
|
7
|
+
name: "inbox",
|
|
8
|
+
schema: z.object({
|
|
9
|
+
limit: z.number().min(1).max(100).default(50),
|
|
10
|
+
unreadOnly: z.boolean().default(false),
|
|
11
|
+
}),
|
|
12
|
+
access: { openToAll: true },
|
|
13
|
+
handler: async (query, ctx) => {
|
|
14
|
+
const conditions = [eq(inAppMessagesTable.userId, query.user.id)];
|
|
15
|
+
if (query.payload.unreadOnly) {
|
|
16
|
+
conditions.push(eq(inAppMessagesTable.isRead, false));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const rows = await ctx.db
|
|
20
|
+
.select()
|
|
21
|
+
.from(inAppMessagesTable)
|
|
22
|
+
.where(and(...conditions))
|
|
23
|
+
.orderBy(desc(inAppMessagesTable.createdAt))
|
|
24
|
+
.limit(query.payload.limit);
|
|
25
|
+
|
|
26
|
+
return { rows };
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { and, eq } from "drizzle-orm";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { inAppMessagesTable } from "../tables";
|
|
5
|
+
|
|
6
|
+
export const markAllReadWrite = defineWriteHandler({
|
|
7
|
+
name: "markAllRead",
|
|
8
|
+
schema: z.object({}),
|
|
9
|
+
access: { openToAll: true },
|
|
10
|
+
handler: async (event, ctx) => {
|
|
11
|
+
const rows = await ctx.db
|
|
12
|
+
.update(inAppMessagesTable)
|
|
13
|
+
.set({ isRead: true, readAt: Temporal.Now.instant() })
|
|
14
|
+
.where(
|
|
15
|
+
and(eq(inAppMessagesTable.userId, event.user.id), eq(inAppMessagesTable.isRead, false)),
|
|
16
|
+
)
|
|
17
|
+
.returning();
|
|
18
|
+
|
|
19
|
+
return { isSuccess: true, data: { marked: rows.length } };
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { NotFoundError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
+
import { and, eq } from "drizzle-orm";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { inAppMessagesTable } from "../tables";
|
|
6
|
+
|
|
7
|
+
export const markReadWrite = defineWriteHandler({
|
|
8
|
+
name: "markRead",
|
|
9
|
+
schema: z.object({
|
|
10
|
+
// inAppMessages.id is a serial integer (table is infra, not an ES aggregate).
|
|
11
|
+
id: z.number().int(),
|
|
12
|
+
}),
|
|
13
|
+
access: { openToAll: true },
|
|
14
|
+
handler: async (event, ctx) => {
|
|
15
|
+
const rows = await ctx.db
|
|
16
|
+
.update(inAppMessagesTable)
|
|
17
|
+
.set({ isRead: true, readAt: Temporal.Now.instant() })
|
|
18
|
+
.where(
|
|
19
|
+
and(
|
|
20
|
+
eq(inAppMessagesTable.id, event.payload.id),
|
|
21
|
+
eq(inAppMessagesTable.userId, event.user.id),
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
.returning();
|
|
25
|
+
|
|
26
|
+
if (rows.length === 0) {
|
|
27
|
+
return writeFailure(new NotFoundError("inAppMessage", event.payload.id));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { isSuccess: true, data: { id: rows[0]?.["id"] } };
|
|
31
|
+
},
|
|
32
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { and, count, eq } from "drizzle-orm";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { inAppMessagesTable } from "../tables";
|
|
5
|
+
|
|
6
|
+
export const unreadCountQuery = defineQueryHandler({
|
|
7
|
+
name: "unreadCount",
|
|
8
|
+
schema: z.object({}),
|
|
9
|
+
access: { openToAll: true },
|
|
10
|
+
handler: async (query, ctx) => {
|
|
11
|
+
const rows = await ctx.db
|
|
12
|
+
.select({ value: count() })
|
|
13
|
+
.from(inAppMessagesTable)
|
|
14
|
+
.where(
|
|
15
|
+
and(eq(inAppMessagesTable.userId, query.user.id), eq(inAppMessagesTable.isRead, false)),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
return { count: rows[0]?.["value"] ?? 0 };
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { tenantChannel } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import type { DeliveryChannel } from "../delivery";
|
|
3
|
+
import { inAppMessagesTable } from "./tables";
|
|
4
|
+
|
|
5
|
+
export const inAppChannel: DeliveryChannel = {
|
|
6
|
+
name: "inApp",
|
|
7
|
+
|
|
8
|
+
async resolve(userId) {
|
|
9
|
+
// InApp always resolves — the userId IS the address
|
|
10
|
+
return String(userId);
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
async send(address, message, ctx) {
|
|
14
|
+
// address is the user-id string after the ES migration — keep it as-is.
|
|
15
|
+
const userId = address;
|
|
16
|
+
|
|
17
|
+
const rows = await ctx.db
|
|
18
|
+
.insert(inAppMessagesTable)
|
|
19
|
+
.values({
|
|
20
|
+
tenantId: ctx.tenantId,
|
|
21
|
+
userId,
|
|
22
|
+
notificationType: message.notificationType,
|
|
23
|
+
title: message.title,
|
|
24
|
+
body: message.body ?? null,
|
|
25
|
+
data: message.data ? JSON.stringify(message.data) : null,
|
|
26
|
+
})
|
|
27
|
+
.returning();
|
|
28
|
+
|
|
29
|
+
if (ctx.sseBroker) {
|
|
30
|
+
ctx.sseBroker.pushToChannel(tenantChannel(ctx.tenantId), {
|
|
31
|
+
type: "channel-in-app:event:delivered",
|
|
32
|
+
data: {
|
|
33
|
+
id: rows[0]?.["id"],
|
|
34
|
+
userId,
|
|
35
|
+
notificationType: message.notificationType,
|
|
36
|
+
title: message.title,
|
|
37
|
+
body: message.body,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { status: "sent", address };
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
boolean,
|
|
3
|
+
instant,
|
|
4
|
+
table as pgTable,
|
|
5
|
+
serial,
|
|
6
|
+
text,
|
|
7
|
+
uuid,
|
|
8
|
+
} from "@cosmicdrift/kumiko-framework/db";
|
|
9
|
+
import { sql } from "drizzle-orm";
|
|
10
|
+
|
|
11
|
+
export const inAppMessagesTable = pgTable("in_app_messages", {
|
|
12
|
+
id: serial("id").primaryKey(),
|
|
13
|
+
tenantId: uuid("tenant_id").notNull(),
|
|
14
|
+
userId: text("user_id").notNull(),
|
|
15
|
+
notificationType: text("notification_type").notNull(),
|
|
16
|
+
title: text("title").notNull(),
|
|
17
|
+
body: text("body"),
|
|
18
|
+
data: text("data"), // JSON string for action, screen, etc.
|
|
19
|
+
isRead: boolean("is_read").default(false).notNull(),
|
|
20
|
+
readAt: instant("read_at"),
|
|
21
|
+
createdAt: instant("created_at").default(sql`now()`).notNull(),
|
|
22
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { createPushChannel, type PushChannelOptions } from "./push-channel";
|
|
3
|
+
|
|
4
|
+
export function createChannelPushFeature(options: PushChannelOptions): FeatureDefinition {
|
|
5
|
+
const channel = createPushChannel(options);
|
|
6
|
+
|
|
7
|
+
return defineFeature("channelPush", (r) => {
|
|
8
|
+
r.requires("delivery");
|
|
9
|
+
|
|
10
|
+
r.useExtension("deliveryChannel", "push", {
|
|
11
|
+
resolve: channel.resolve,
|
|
12
|
+
send: channel.send,
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import type { DeliveryChannel } from "../delivery";
|
|
3
|
+
import type { PushTransport } from "./types";
|
|
4
|
+
|
|
5
|
+
export type PushChannelOptions = {
|
|
6
|
+
readonly transport: PushTransport;
|
|
7
|
+
readonly resolveToken: (
|
|
8
|
+
userId: string,
|
|
9
|
+
ctx: { db: unknown; tenantId: TenantId },
|
|
10
|
+
) => Promise<string | null>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function createPushChannel(options: PushChannelOptions): DeliveryChannel {
|
|
14
|
+
const { transport, resolveToken } = options;
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
name: "push",
|
|
18
|
+
|
|
19
|
+
async resolve(userId, ctx) {
|
|
20
|
+
return resolveToken(userId, ctx);
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async send(address, message, _ctx) {
|
|
24
|
+
await transport.send({
|
|
25
|
+
token: address,
|
|
26
|
+
title: message.title,
|
|
27
|
+
body: message.body,
|
|
28
|
+
data: message.data,
|
|
29
|
+
});
|
|
30
|
+
return { status: "sent", address };
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type PushMessage = {
|
|
2
|
+
readonly token: string;
|
|
3
|
+
readonly title: string;
|
|
4
|
+
readonly body: string | undefined;
|
|
5
|
+
readonly data: Readonly<Record<string, unknown>> | undefined;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type PushTransport = {
|
|
9
|
+
send(message: PushMessage): Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function createInMemoryPushTransport(): PushTransport & {
|
|
13
|
+
readonly sent: PushMessage[];
|
|
14
|
+
} {
|
|
15
|
+
const sent: PushMessage[] = [];
|
|
16
|
+
return {
|
|
17
|
+
sent,
|
|
18
|
+
async send(message) {
|
|
19
|
+
sent.push(message);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createTenantConfig, createUserConfig } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { validateAppOverrides } from "../resolver";
|
|
4
|
+
|
|
5
|
+
// Minimal registry stub — validateAppOverrides only reads getConfigKey.
|
|
6
|
+
// Cast keeps the test isolated from the rest of the Registry surface.
|
|
7
|
+
function registryStub(keys: Record<string, unknown>) {
|
|
8
|
+
return {
|
|
9
|
+
getConfigKey: (key: string) => keys[key] as never,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("validateAppOverrides", () => {
|
|
14
|
+
test("accepts a typed override that matches an existing key", () => {
|
|
15
|
+
const reg = registryStub({
|
|
16
|
+
"files:config:max-size": createTenantConfig("number"),
|
|
17
|
+
});
|
|
18
|
+
const validated = validateAppOverrides(reg, {
|
|
19
|
+
"files:config:max-size": 50,
|
|
20
|
+
});
|
|
21
|
+
expect(validated.get("files:config:max-size")).toBe(50);
|
|
22
|
+
expect(validated.size).toBe(1);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("throws on unknown key", () => {
|
|
26
|
+
const reg = registryStub({});
|
|
27
|
+
expect(() =>
|
|
28
|
+
validateAppOverrides(reg, {
|
|
29
|
+
"missing:config:x": 1,
|
|
30
|
+
}),
|
|
31
|
+
).toThrow(/unknown config key.*missing:config:x/i);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("throws on type mismatch — number key, string value", () => {
|
|
35
|
+
const reg = registryStub({
|
|
36
|
+
"a:config:x": createTenantConfig("number"),
|
|
37
|
+
});
|
|
38
|
+
expect(() =>
|
|
39
|
+
validateAppOverrides(reg, {
|
|
40
|
+
"a:config:x": "not-a-number",
|
|
41
|
+
}),
|
|
42
|
+
).toThrow(/expected number, got string/i);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("throws on type mismatch — boolean key, number value", () => {
|
|
46
|
+
const reg = registryStub({
|
|
47
|
+
"a:config:flag": createTenantConfig("boolean"),
|
|
48
|
+
});
|
|
49
|
+
expect(() =>
|
|
50
|
+
validateAppOverrides(reg, {
|
|
51
|
+
"a:config:flag": 1,
|
|
52
|
+
}),
|
|
53
|
+
).toThrow(/expected boolean, got number/i);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("select key — accepts value in options, rejects anything else", () => {
|
|
57
|
+
const reg = registryStub({
|
|
58
|
+
"a:config:theme": createTenantConfig("select", { options: ["light", "dark", "auto"] }),
|
|
59
|
+
});
|
|
60
|
+
// ok
|
|
61
|
+
expect(() => validateAppOverrides(reg, { "a:config:theme": "dark" })).not.toThrow();
|
|
62
|
+
// not in options
|
|
63
|
+
expect(() => validateAppOverrides(reg, { "a:config:theme": "purple" })).toThrow(
|
|
64
|
+
/not in options/i,
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("throws on bounds violation — below min", () => {
|
|
69
|
+
const reg = registryStub({
|
|
70
|
+
"a:config:n": createTenantConfig("number", { bounds: { min: 1, max: 100 } }),
|
|
71
|
+
});
|
|
72
|
+
expect(() => validateAppOverrides(reg, { "a:config:n": 0 })).toThrow(/below bounds\.min/i);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("throws on bounds violation — above max", () => {
|
|
76
|
+
const reg = registryStub({
|
|
77
|
+
"a:config:n": createTenantConfig("number", { bounds: { min: 1, max: 100 } }),
|
|
78
|
+
});
|
|
79
|
+
expect(() => validateAppOverrides(reg, { "a:config:n": 101 })).toThrow(/above bounds\.max/i);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("passes multiple overrides at once and preserves all", () => {
|
|
83
|
+
const reg = registryStub({
|
|
84
|
+
"a:config:n": createTenantConfig("number"),
|
|
85
|
+
"a:config:s": createTenantConfig("text"),
|
|
86
|
+
"a:config:b": createUserConfig("boolean"),
|
|
87
|
+
});
|
|
88
|
+
const result = validateAppOverrides(reg, {
|
|
89
|
+
"a:config:n": 42,
|
|
90
|
+
"a:config:s": "hello",
|
|
91
|
+
"a:config:b": true,
|
|
92
|
+
});
|
|
93
|
+
expect(result.size).toBe(3);
|
|
94
|
+
expect(result.get("a:config:n")).toBe(42);
|
|
95
|
+
expect(result.get("a:config:s")).toBe("hello");
|
|
96
|
+
expect(result.get("a:config:b")).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("empty overrides map passes through without validation cost", () => {
|
|
100
|
+
const reg = registryStub({});
|
|
101
|
+
const result = validateAppOverrides(reg, {});
|
|
102
|
+
expect(result.size).toBe(0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("rejects override for a computed key — plan-logic may not be silently bypassed", () => {
|
|
106
|
+
const reg = registryStub({
|
|
107
|
+
"plan:config:quota": createTenantConfig("number", {
|
|
108
|
+
default: 10,
|
|
109
|
+
computed: async () => 100,
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
112
|
+
expect(() =>
|
|
113
|
+
validateAppOverrides(reg, {
|
|
114
|
+
"plan:config:quota": 999,
|
|
115
|
+
}),
|
|
116
|
+
).toThrow(/computed resolver.*app-overrides would silently bypass/i);
|
|
117
|
+
});
|
|
118
|
+
});
|