@primstack/cli 0.0.1
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/dist/generators/crud/templates/drizzle-table.ts.template +12 -0
- package/dist/generators/crud/templates/handlers.ts.template +136 -0
- package/dist/generators/crud/templates/routes.ts.template +21 -0
- package/dist/generators/crud/templates/schema.ts.template +20 -0
- package/dist/index.js +9618 -0
- package/dist/integrations/analytics/providers/amplitude/templates/src/analytics/index.ts.template +79 -0
- package/dist/integrations/analytics/providers/amplitude/templates/src/analytics/types.ts.template +12 -0
- package/dist/integrations/analytics/providers/mixpanel/templates/src/analytics/index.ts.template +62 -0
- package/dist/integrations/analytics/providers/mixpanel/templates/src/analytics/types.ts.template +12 -0
- package/dist/integrations/analytics/providers/posthog/templates/src/analytics/index.ts.template +67 -0
- package/dist/integrations/analytics/providers/posthog/templates/src/analytics/types.ts.template +12 -0
- package/dist/integrations/auth/providers/authjoy/templates/api/src/middleware/auth.ts.template +89 -0
- package/dist/integrations/auth/providers/authjoy/templates/api/src/routes/auth.ts.template +27 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/components/AuthProvider.tsx.template +40 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/hooks/use-auth.ts.template +71 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/lib/auth.ts.template +59 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/pages/account.tsx.template +84 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/pages/login.tsx.template +73 -0
- package/dist/integrations/cache/providers/memory/templates/src/cache/index.ts.template +43 -0
- package/dist/integrations/cache/providers/memory/templates/src/cache/types.ts.template +3 -0
- package/dist/integrations/cache/providers/redis/templates/src/cache/index.ts.template +37 -0
- package/dist/integrations/cache/providers/redis/templates/src/cache/types.ts.template +3 -0
- package/dist/integrations/cache/providers/valkey/templates/src/cache/index.ts.template +38 -0
- package/dist/integrations/cache/providers/valkey/templates/src/cache/types.ts.template +3 -0
- package/dist/integrations/db/providers/postgres/templates/drizzle.config.ts.template +10 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/index.ts.template +13 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/migrate.ts.template +19 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/schema/index.ts.template +1 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/schema/users.ts.template +12 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/seed.ts.template +28 -0
- package/dist/integrations/db/providers/sqlite/templates/drizzle.config.ts.template +10 -0
- package/dist/integrations/db/providers/sqlite/templates/src/db/index.ts.template +10 -0
- package/dist/integrations/db/providers/sqlite/templates/src/db/schema/index.ts.template +1 -0
- package/dist/integrations/db/providers/sqlite/templates/src/db/schema/users.ts.template +12 -0
- package/dist/integrations/db/providers/sqlite/templates/src/db/seed.ts.template +28 -0
- package/dist/integrations/db/providers/supabase/templates/drizzle.config.ts.template +10 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/index.ts.template +13 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/migrate.ts.template +19 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/schema/index.ts.template +1 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/schema/users.ts.template +12 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/seed.ts.template +28 -0
- package/dist/integrations/db/providers/turso/templates/drizzle.config.ts.template +11 -0
- package/dist/integrations/db/providers/turso/templates/src/db/index.ts.template +14 -0
- package/dist/integrations/db/providers/turso/templates/src/db/schema/index.ts.template +1 -0
- package/dist/integrations/db/providers/turso/templates/src/db/schema/users.ts.template +12 -0
- package/dist/integrations/db/providers/turso/templates/src/db/seed.ts.template +28 -0
- package/dist/integrations/email/providers/nodemailer/templates/src/email/index.ts.template +24 -0
- package/dist/integrations/email/providers/nodemailer/templates/src/email/templates/index.ts.template +1 -0
- package/dist/integrations/email/providers/nodemailer/templates/src/email/templates/welcome.ts.template +7 -0
- package/dist/integrations/email/providers/nodemailer/templates/src/email/types.ts.template +7 -0
- package/dist/integrations/email/providers/resend/templates/src/email/index.ts.template +18 -0
- package/dist/integrations/email/providers/resend/templates/src/email/templates/index.ts.template +1 -0
- package/dist/integrations/email/providers/resend/templates/src/email/templates/welcome.ts.template +7 -0
- package/dist/integrations/email/providers/resend/templates/src/email/types.ts.template +7 -0
- package/dist/integrations/email/providers/sendgrid/templates/src/email/index.ts.template +16 -0
- package/dist/integrations/email/providers/sendgrid/templates/src/email/templates/index.ts.template +1 -0
- package/dist/integrations/email/providers/sendgrid/templates/src/email/templates/welcome.ts.template +7 -0
- package/dist/integrations/email/providers/sendgrid/templates/src/email/types.ts.template +7 -0
- package/dist/integrations/flags/providers/local/templates/api/src/lib/flags.ts.template +97 -0
- package/dist/integrations/flags/providers/local/templates/api/src/routes/flags.ts.template +36 -0
- package/dist/integrations/flags/providers/local/templates/flags.json.template +8 -0
- package/dist/integrations/flags/providers/local/templates/web/src/hooks/use-flag.ts.template +60 -0
- package/dist/integrations/logging/providers/axiom/templates/src/logging/index.ts.template +56 -0
- package/dist/integrations/logging/providers/axiom/templates/src/logging/types.ts.template +5 -0
- package/dist/integrations/logging/providers/pino/templates/src/logging/index.ts.template +21 -0
- package/dist/integrations/logging/providers/pino/templates/src/logging/types.ts.template +5 -0
- package/dist/integrations/logging/providers/winston/templates/src/logging/index.ts.template +30 -0
- package/dist/integrations/logging/providers/winston/templates/src/logging/types.ts.template +5 -0
- package/dist/integrations/monitor/providers/datadog/templates/src/monitor/index.ts.template +78 -0
- package/dist/integrations/monitor/providers/datadog/templates/src/monitor/types.ts.template +12 -0
- package/dist/integrations/monitor/providers/newrelic/templates/src/monitor/index.ts.template +60 -0
- package/dist/integrations/monitor/providers/newrelic/templates/src/monitor/types.ts.template +12 -0
- package/dist/integrations/monitor/providers/sentry/templates/src/monitor/index.ts.template +70 -0
- package/dist/integrations/monitor/providers/sentry/templates/src/monitor/types.ts.template +12 -0
- package/dist/integrations/queue/providers/bullmq/templates/src/queue/index.ts.template +56 -0
- package/dist/integrations/queue/providers/bullmq/templates/src/queue/types.ts.template +10 -0
- package/dist/integrations/queue/providers/memory/templates/src/queue/index.ts.template +73 -0
- package/dist/integrations/queue/providers/memory/templates/src/queue/types.ts.template +10 -0
- package/dist/integrations/queue/providers/pgboss/templates/src/queue/index.ts.template +34 -0
- package/dist/integrations/queue/providers/pgboss/templates/src/queue/types.ts.template +10 -0
- package/dist/integrations/ratelimit/providers/memory/templates/src/ratelimit/index.ts.template +95 -0
- package/dist/integrations/ratelimit/providers/memory/templates/src/ratelimit/types.ts.template +12 -0
- package/dist/integrations/ratelimit/providers/rate-limiter-flexible/templates/src/ratelimit/index.ts.template +80 -0
- package/dist/integrations/ratelimit/providers/rate-limiter-flexible/templates/src/ratelimit/types.ts.template +12 -0
- package/dist/integrations/ratelimit/providers/upstash/templates/src/ratelimit/index.ts.template +67 -0
- package/dist/integrations/ratelimit/providers/upstash/templates/src/ratelimit/types.ts.template +12 -0
- package/dist/integrations/schedule/providers/bullmq/templates/src/schedule/index.ts.template +81 -0
- package/dist/integrations/schedule/providers/bullmq/templates/src/schedule/types.ts.template +10 -0
- package/dist/integrations/schedule/providers/croner/templates/src/schedule/index.ts.template +47 -0
- package/dist/integrations/schedule/providers/croner/templates/src/schedule/types.ts.template +10 -0
- package/dist/integrations/schedule/providers/node-cron/templates/src/schedule/index.ts.template +45 -0
- package/dist/integrations/schedule/providers/node-cron/templates/src/schedule/types.ts.template +10 -0
- package/dist/integrations/search/providers/algolia/templates/src/search/index.ts.template +52 -0
- package/dist/integrations/search/providers/algolia/templates/src/search/types.ts.template +18 -0
- package/dist/integrations/search/providers/meilisearch/templates/src/search/index.ts.template +49 -0
- package/dist/integrations/search/providers/meilisearch/templates/src/search/types.ts.template +18 -0
- package/dist/integrations/search/providers/typesense/templates/src/search/index.ts.template +71 -0
- package/dist/integrations/search/providers/typesense/templates/src/search/types.ts.template +35 -0
- package/dist/integrations/storage/providers/local/templates/src/storage/index.ts.template +69 -0
- package/dist/integrations/storage/providers/local/templates/src/storage/types.ts.template +12 -0
- package/dist/integrations/storage/providers/r2/templates/src/storage/index.ts.template +80 -0
- package/dist/integrations/storage/providers/r2/templates/src/storage/types.ts.template +12 -0
- package/dist/integrations/storage/providers/s3/templates/src/storage/index.ts.template +78 -0
- package/dist/integrations/storage/providers/s3/templates/src/storage/types.ts.template +12 -0
- package/dist/integrations/stripe/templates/api/src/lib/stripe.ts.template +259 -0
- package/dist/integrations/stripe/templates/api/src/routes/stripe-webhooks.ts.template +284 -0
- package/dist/integrations/stripe/templates/api/stripe.config.ts.template +178 -0
- package/dist/integrations/stripe/templates/shared/src/pricing.ts.template +117 -0
- package/dist/integrations/stripe/templates/shared/src/stripe-types.ts.template +133 -0
- package/dist/integrations/stripe/templates/web/src/components/billing-settings.tsx.template +123 -0
- package/dist/integrations/stripe/templates/web/src/components/pricing-cards.tsx.template +115 -0
- package/dist/integrations/stripe/templates/web/src/pages/pricing.tsx.template +95 -0
- package/dist/templates/api/fastify/.env.example.template +7 -0
- package/dist/templates/api/fastify/.gitignore.template +24 -0
- package/dist/templates/api/fastify/package.json.template +23 -0
- package/dist/templates/api/fastify/src/index.ts.template +52 -0
- package/dist/templates/api/fastify/src/lib/env.ts.template +20 -0
- package/dist/templates/api/fastify/src/routes/health.ts.template +12 -0
- package/dist/templates/api/fastify/tsconfig.json.template +18 -0
- package/dist/templates/api/fastify-postgres/.env.example.template +10 -0
- package/dist/templates/api/fastify-postgres/drizzle.config.ts.template +10 -0
- package/dist/templates/api/fastify-postgres/package.json.template +16 -0
- package/dist/templates/api/fastify-postgres/src/db/index.ts.template +9 -0
- package/dist/templates/api/fastify-postgres/src/db/schema/users.ts.template +12 -0
- package/dist/templates/api/fastify-sqlite/.env.example.template +10 -0
- package/dist/templates/api/fastify-sqlite/drizzle.config.ts.template +10 -0
- package/dist/templates/api/fastify-sqlite/package.json.template +16 -0
- package/dist/templates/api/fastify-sqlite/src/db/index.ts.template +6 -0
- package/dist/templates/api/fastify-sqlite/src/db/schema/users.ts.template +12 -0
- package/dist/templates/api/fastify-supabase/.env.example.template +10 -0
- package/dist/templates/api/fastify-supabase/drizzle.config.ts.template +10 -0
- package/dist/templates/api/fastify-supabase/package.json.template +15 -0
- package/dist/templates/api/fastify-supabase/src/db/index.ts.template +9 -0
- package/dist/templates/api/fastify-supabase/src/db/schema/users.ts.template +12 -0
- package/dist/templates/api/fastify-turso/.env.example.template +11 -0
- package/dist/templates/api/fastify-turso/drizzle.config.ts.template +11 -0
- package/dist/templates/api/fastify-turso/package.json.template +15 -0
- package/dist/templates/api/fastify-turso/src/db/index.ts.template +10 -0
- package/dist/templates/api/fastify-turso/src/db/schema/users.ts.template +12 -0
- package/dist/templates/fullstack/api/.env.example.template +10 -0
- package/dist/templates/fullstack/api/.gitignore.template +4 -0
- package/dist/templates/fullstack/api/drizzle.config.ts.template +14 -0
- package/dist/templates/fullstack/api/package.json.template +33 -0
- package/dist/templates/fullstack/api/src/db/index.ts.template +13 -0
- package/dist/templates/fullstack/api/src/db/schema/api-keys.ts.template +19 -0
- package/dist/templates/fullstack/api/src/db/schema/audit-logs.ts.template +23 -0
- package/dist/templates/fullstack/api/src/db/schema/index.ts.template +8 -0
- package/dist/templates/fullstack/api/src/db/schema/invites.ts.template +19 -0
- package/dist/templates/fullstack/api/src/db/schema/memberships.ts.template +16 -0
- package/dist/templates/fullstack/api/src/db/schema/organizations.ts.template +13 -0
- package/dist/templates/fullstack/api/src/db/schema/plans.ts.template +29 -0
- package/dist/templates/fullstack/api/src/db/schema/subscriptions.ts.template +38 -0
- package/dist/templates/fullstack/api/src/db/schema/users.ts.template +14 -0
- package/dist/templates/fullstack/api/src/index.ts.template +54 -0
- package/dist/templates/fullstack/api/src/lib/env.ts.template +22 -0
- package/dist/templates/fullstack/api/src/routes/health.ts.template +14 -0
- package/dist/templates/fullstack/api/tsconfig.json.template +15 -0
- package/dist/templates/fullstack/root/.gitignore.template +26 -0
- package/dist/templates/fullstack/root/package.json.template +15 -0
- package/dist/templates/fullstack/root/pnpm-workspace.yaml.template +3 -0
- package/dist/templates/fullstack/root/turbo.json.template +17 -0
- package/dist/templates/fullstack/shared/package.json.template +36 -0
- package/dist/templates/fullstack/shared/src/index.ts.template +8 -0
- package/dist/templates/fullstack/shared/src/schemas/api-key.ts.template +28 -0
- package/dist/templates/fullstack/shared/src/schemas/audit-log.ts.template +41 -0
- package/dist/templates/fullstack/shared/src/schemas/index.ts.template +8 -0
- package/dist/templates/fullstack/shared/src/schemas/invite.ts.template +25 -0
- package/dist/templates/fullstack/shared/src/schemas/membership.ts.template +20 -0
- package/dist/templates/fullstack/shared/src/schemas/organization.ts.template +18 -0
- package/dist/templates/fullstack/shared/src/schemas/plan.ts.template +38 -0
- package/dist/templates/fullstack/shared/src/schemas/subscription.ts.template +56 -0
- package/dist/templates/fullstack/shared/src/schemas/user.ts.template +21 -0
- package/dist/templates/fullstack/shared/src/types/index.ts.template +75 -0
- package/dist/templates/fullstack/shared/src/validators/index.ts.template +53 -0
- package/dist/templates/fullstack/shared/tsconfig.json.template +17 -0
- package/dist/templates/fullstack/web/.gitignore.template +3 -0
- package/dist/templates/fullstack/web/index.html.template +13 -0
- package/dist/templates/fullstack/web/package.json.template +23 -0
- package/dist/templates/fullstack/web/src/App.tsx.template +47 -0
- package/dist/templates/fullstack/web/src/index.css.template +54 -0
- package/dist/templates/fullstack/web/src/main.tsx.template +10 -0
- package/dist/templates/fullstack/web/src/vite-env.d.ts.template +1 -0
- package/dist/templates/fullstack/web/tsconfig.json.template +21 -0
- package/dist/templates/fullstack/web/tsconfig.node.json.template +11 -0
- package/dist/templates/fullstack/web/vite.config.ts.template +15 -0
- package/dist/templates/hosted/root/.env.local.template +13 -0
- package/dist/templates/hosted/root/.gitignore.template +32 -0
- package/dist/templates/hosted/root/CLAUDE.md.template +139 -0
- package/dist/templates/hosted/root/drizzle.config.ts.template +10 -0
- package/dist/templates/hosted/root/next.config.ts.template +15 -0
- package/dist/templates/hosted/root/package.json.template +40 -0
- package/dist/templates/hosted/root/postcss.config.mjs.template +9 -0
- package/dist/templates/hosted/root/primstack.config.json.template +5 -0
- package/dist/templates/hosted/root/tailwind.config.ts.template +14 -0
- package/dist/templates/hosted/root/tsconfig.json.template +25 -0
- package/dist/templates/hosted/root/wrangler.toml.template +9 -0
- package/dist/templates/hosted/src/app/actions/example.ts.template +50 -0
- package/dist/templates/hosted/src/app/api/health/route.ts.template +5 -0
- package/dist/templates/hosted/src/app/auth/login/page.tsx.template +32 -0
- package/dist/templates/hosted/src/app/globals.css.template +59 -0
- package/dist/templates/hosted/src/app/layout.tsx.template +24 -0
- package/dist/templates/hosted/src/app/page.tsx.template +34 -0
- package/dist/templates/hosted/src/db/migrations/0000_initial.sql.template +43 -0
- package/dist/templates/hosted/src/db/schema.ts.template +52 -0
- package/dist/templates/hosted/src/env.d.ts.template +10 -0
- package/dist/templates/hosted/src/instrumentation.ts.template +6 -0
- package/dist/templates/hosted/src/lib/auth.ts.template +35 -0
- package/dist/templates/hosted/src/lib/db.ts.template +17 -0
- package/dist/templates/hosted/src/middleware.ts.template +6 -0
- package/package.json +46 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { pgTable, uuid, varchar, timestamp } from 'drizzle-orm/pg-core';
|
|
2
|
+
|
|
3
|
+
export const users = pgTable('users', {
|
|
4
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
5
|
+
email: varchar('email', { length: 255 }).notNull().unique(),
|
|
6
|
+
name: varchar('name', { length: 255 }),
|
|
7
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
8
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export type User = typeof users.$inferSelect;
|
|
12
|
+
export type NewUser = typeof users.$inferInsert;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Server configuration
|
|
2
|
+
PORT={{PORT}}
|
|
3
|
+
HOST=0.0.0.0
|
|
4
|
+
NODE_ENV=development
|
|
5
|
+
|
|
6
|
+
# CORS (optional, defaults to allow all in development)
|
|
7
|
+
# CORS_ORIGIN=https://your-frontend.com
|
|
8
|
+
|
|
9
|
+
# Turso Database
|
|
10
|
+
TURSO_DATABASE_URL=libsql://your-database.turso.io
|
|
11
|
+
TURSO_AUTH_TOKEN=your-auth-token
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineConfig } from 'drizzle-kit';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
schema: './src/db/schema',
|
|
5
|
+
out: './src/db/migrations',
|
|
6
|
+
dialect: 'turso',
|
|
7
|
+
dbCredentials: {
|
|
8
|
+
url: process.env.TURSO_DATABASE_URL!,
|
|
9
|
+
authToken: process.env.TURSO_AUTH_TOKEN,
|
|
10
|
+
},
|
|
11
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scripts": {
|
|
3
|
+
"db:generate": "drizzle-kit generate",
|
|
4
|
+
"db:migrate": "drizzle-kit migrate",
|
|
5
|
+
"db:push": "drizzle-kit push",
|
|
6
|
+
"db:studio": "drizzle-kit studio"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"drizzle-orm": "^0.36.0",
|
|
10
|
+
"@libsql/client": "^0.14.0"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"drizzle-kit": "^0.28.0"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { drizzle } from 'drizzle-orm/libsql';
|
|
2
|
+
import { createClient } from '@libsql/client';
|
|
3
|
+
import * as schema from './schema/users.js';
|
|
4
|
+
|
|
5
|
+
const client = createClient({
|
|
6
|
+
url: process.env.TURSO_DATABASE_URL!,
|
|
7
|
+
authToken: process.env.TURSO_AUTH_TOKEN!,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const db = drizzle(client, { schema });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
|
|
3
|
+
export const users = sqliteTable('users', {
|
|
4
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
5
|
+
email: text('email').notNull().unique(),
|
|
6
|
+
name: text('name'),
|
|
7
|
+
createdAt: text('created_at').notNull().default('CURRENT_TIMESTAMP'),
|
|
8
|
+
updatedAt: text('updated_at').notNull().default('CURRENT_TIMESTAMP'),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export type User = typeof users.$inferSelect;
|
|
12
|
+
export type NewUser = typeof users.$inferInsert;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Server configuration
|
|
2
|
+
PORT={{PORT}}
|
|
3
|
+
HOST=0.0.0.0
|
|
4
|
+
NODE_ENV=development
|
|
5
|
+
|
|
6
|
+
# CORS (optional, defaults to allow all in development)
|
|
7
|
+
# CORS_ORIGIN=https://your-frontend.com
|
|
8
|
+
|
|
9
|
+
# Database (update with your credentials if needed)
|
|
10
|
+
DATABASE_URL=postgresql://localhost:5432/{{PROJECT_NAME}}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from 'drizzle-kit';
|
|
2
|
+
|
|
3
|
+
if (!process.env.DATABASE_URL) {
|
|
4
|
+
throw new Error('DATABASE_URL environment variable is required');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
schema: './src/db/schema',
|
|
9
|
+
out: './src/db/migrations',
|
|
10
|
+
dialect: 'postgresql',
|
|
11
|
+
dbCredentials: {
|
|
12
|
+
url: process.env.DATABASE_URL,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{PROJECT_NAME}}/api",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "tsx watch src/index.ts",
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"start": "node dist/index.js",
|
|
9
|
+
"typecheck": "tsc --noEmit",
|
|
10
|
+
"db:generate": "drizzle-kit generate",
|
|
11
|
+
"db:migrate": "drizzle-kit migrate",
|
|
12
|
+
"db:push": "drizzle-kit push",
|
|
13
|
+
"db:studio": "drizzle-kit studio"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@{{PROJECT_NAME}}/shared": "workspace:*",
|
|
17
|
+
"@fastify/cors": "^10.0.0",
|
|
18
|
+
"@fastify/helmet": "^13.0.0",
|
|
19
|
+
"@fastify/sensible": "^6.0.0",
|
|
20
|
+
"dotenv": "^16.4.0",
|
|
21
|
+
"fastify": "^5.0.0",
|
|
22
|
+
"drizzle-orm": "^0.36.0",
|
|
23
|
+
"pg": "^8.13.0",
|
|
24
|
+
"zod": "^3.23.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.0.0",
|
|
28
|
+
"@types/pg": "^8.11.0",
|
|
29
|
+
"drizzle-kit": "^0.28.0",
|
|
30
|
+
"tsx": "^4.0.0",
|
|
31
|
+
"typescript": "^5.7.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
2
|
+
import pg from 'pg';
|
|
3
|
+
import * as schema from './schema/index.js';
|
|
4
|
+
|
|
5
|
+
export const pool = new pg.Pool({
|
|
6
|
+
connectionString: process.env.DATABASE_URL,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const db = drizzle(pool, { schema });
|
|
10
|
+
|
|
11
|
+
export async function closeDatabase(): Promise<void> {
|
|
12
|
+
await pool.end();
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { pgTable, uuid, varchar, timestamp, index, uniqueIndex } from 'drizzle-orm/pg-core';
|
|
2
|
+
import { organizations } from './organizations.js';
|
|
3
|
+
|
|
4
|
+
export const apiKeys = pgTable('api_keys', {
|
|
5
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
6
|
+
organizationId: uuid('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),
|
|
7
|
+
name: varchar('name', { length: 255 }).notNull(),
|
|
8
|
+
keyHash: varchar('key_hash', { length: 255 }).notNull(),
|
|
9
|
+
keyPrefix: varchar('key_prefix', { length: 8 }).notNull(),
|
|
10
|
+
lastUsedAt: timestamp('last_used_at'),
|
|
11
|
+
expiresAt: timestamp('expires_at'),
|
|
12
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
13
|
+
}, (table) => [
|
|
14
|
+
uniqueIndex('api_keys_key_hash_idx').on(table.keyHash),
|
|
15
|
+
index('api_keys_org_idx').on(table.organizationId),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export type ApiKey = typeof apiKeys.$inferSelect;
|
|
19
|
+
export type NewApiKey = typeof apiKeys.$inferInsert;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { pgTable, uuid, varchar, timestamp, json, index } from 'drizzle-orm/pg-core';
|
|
2
|
+
import { organizations } from './organizations.js';
|
|
3
|
+
import { users } from './users.js';
|
|
4
|
+
|
|
5
|
+
export const auditLogs = pgTable('audit_logs', {
|
|
6
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
7
|
+
organizationId: uuid('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),
|
|
8
|
+
userId: uuid('user_id').references(() => users.id, { onDelete: 'set null' }),
|
|
9
|
+
action: varchar('action', { length: 255 }).notNull(),
|
|
10
|
+
resourceType: varchar('resource_type', { length: 100 }).notNull(),
|
|
11
|
+
resourceId: varchar('resource_id', { length: 255 }),
|
|
12
|
+
metadata: json('metadata').$type<Record<string, unknown>>().notNull().default({}),
|
|
13
|
+
ipAddress: varchar('ip_address', { length: 45 }),
|
|
14
|
+
userAgent: varchar('user_agent', { length: 500 }),
|
|
15
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
16
|
+
}, (table) => [
|
|
17
|
+
index('audit_logs_org_idx').on(table.organizationId),
|
|
18
|
+
index('audit_logs_action_idx').on(table.action),
|
|
19
|
+
index('audit_logs_created_idx').on(table.createdAt),
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export type AuditLog = typeof auditLogs.$inferSelect;
|
|
23
|
+
export type NewAuditLog = typeof auditLogs.$inferInsert;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './users.js';
|
|
2
|
+
export * from './organizations.js';
|
|
3
|
+
export * from './memberships.js';
|
|
4
|
+
export * from './plans.js';
|
|
5
|
+
export * from './subscriptions.js';
|
|
6
|
+
export * from './invites.js';
|
|
7
|
+
export * from './api-keys.js';
|
|
8
|
+
export * from './audit-logs.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { pgTable, uuid, varchar, timestamp, index } from 'drizzle-orm/pg-core';
|
|
2
|
+
import { organizations } from './organizations.js';
|
|
3
|
+
|
|
4
|
+
export const invites = pgTable('invites', {
|
|
5
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
6
|
+
email: varchar('email', { length: 255 }).notNull(),
|
|
7
|
+
organizationId: uuid('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),
|
|
8
|
+
role: varchar('role', { length: 50 }).notNull().$type<'admin' | 'member'>(),
|
|
9
|
+
token: varchar('token', { length: 255 }).notNull().unique(),
|
|
10
|
+
expiresAt: timestamp('expires_at').notNull(),
|
|
11
|
+
acceptedAt: timestamp('accepted_at'),
|
|
12
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
13
|
+
}, (table) => [
|
|
14
|
+
index('invites_email_idx').on(table.email),
|
|
15
|
+
index('invites_token_idx').on(table.token),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export type Invite = typeof invites.$inferSelect;
|
|
19
|
+
export type NewInvite = typeof invites.$inferInsert;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { pgTable, uuid, varchar, timestamp, uniqueIndex } from 'drizzle-orm/pg-core';
|
|
2
|
+
import { users } from './users.js';
|
|
3
|
+
import { organizations } from './organizations.js';
|
|
4
|
+
|
|
5
|
+
export const memberships = pgTable('memberships', {
|
|
6
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
7
|
+
userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
|
8
|
+
organizationId: uuid('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),
|
|
9
|
+
role: varchar('role', { length: 50 }).notNull().$type<'owner' | 'admin' | 'member'>(),
|
|
10
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
11
|
+
}, (table) => [
|
|
12
|
+
uniqueIndex('memberships_user_org_idx').on(table.userId, table.organizationId),
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export type Membership = typeof memberships.$inferSelect;
|
|
16
|
+
export type NewMembership = typeof memberships.$inferInsert;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { pgTable, uuid, varchar, timestamp } from 'drizzle-orm/pg-core';
|
|
2
|
+
|
|
3
|
+
export const organizations = pgTable('organizations', {
|
|
4
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
5
|
+
name: varchar('name', { length: 255 }).notNull(),
|
|
6
|
+
slug: varchar('slug', { length: 255 }).notNull().unique(),
|
|
7
|
+
logoUrl: varchar('logo_url', { length: 500 }),
|
|
8
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
9
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type Organization = typeof organizations.$inferSelect;
|
|
13
|
+
export type NewOrganization = typeof organizations.$inferInsert;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { pgTable, uuid, varchar, integer, boolean, timestamp, json } from 'drizzle-orm/pg-core';
|
|
2
|
+
|
|
3
|
+
export const plans = pgTable('plans', {
|
|
4
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
5
|
+
name: varchar('name', { length: 255 }).notNull(),
|
|
6
|
+
slug: varchar('slug', { length: 255 }).notNull().unique(),
|
|
7
|
+
description: varchar('description', { length: 1000 }),
|
|
8
|
+
priceMonthly: integer('price_monthly').notNull(), // cents
|
|
9
|
+
priceYearly: integer('price_yearly').notNull(), // cents
|
|
10
|
+
features: json('features').$type<string[]>().notNull().default([]),
|
|
11
|
+
limits: json('limits').$type<{
|
|
12
|
+
seats: number | null;
|
|
13
|
+
storage: number | null;
|
|
14
|
+
apiCalls: number | null;
|
|
15
|
+
}>().notNull().default({ seats: null, storage: null, apiCalls: null }),
|
|
16
|
+
isActive: boolean('is_active').default(true).notNull(),
|
|
17
|
+
|
|
18
|
+
// Stripe integration fields
|
|
19
|
+
stripeProductIdTest: varchar('stripe_product_id_test', { length: 255 }),
|
|
20
|
+
stripeProductIdLive: varchar('stripe_product_id_live', { length: 255 }),
|
|
21
|
+
currentPriceLookupKeyMonthly: varchar('current_price_lookup_key_monthly', { length: 255 }), // e.g., 'pro_monthly_v2'
|
|
22
|
+
currentPriceLookupKeyYearly: varchar('current_price_lookup_key_yearly', { length: 255 }),
|
|
23
|
+
|
|
24
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
25
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export type Plan = typeof plans.$inferSelect;
|
|
29
|
+
export type NewPlan = typeof plans.$inferInsert;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { pgTable, uuid, varchar, boolean, timestamp, integer, index } from 'drizzle-orm/pg-core';
|
|
2
|
+
import { organizations } from './organizations.js';
|
|
3
|
+
import { plans } from './plans.js';
|
|
4
|
+
|
|
5
|
+
export const subscriptions = pgTable('subscriptions', {
|
|
6
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
7
|
+
organizationId: uuid('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),
|
|
8
|
+
planId: uuid('plan_id').notNull().references(() => plans.id),
|
|
9
|
+
status: varchar('status', { length: 50 }).notNull().$type<'active' | 'canceled' | 'past_due' | 'trialing' | 'incomplete'>(),
|
|
10
|
+
currentPeriodStart: timestamp('current_period_start').notNull(),
|
|
11
|
+
currentPeriodEnd: timestamp('current_period_end').notNull(),
|
|
12
|
+
cancelAtPeriodEnd: boolean('cancel_at_period_end').default(false).notNull(),
|
|
13
|
+
|
|
14
|
+
// Stripe integration fields
|
|
15
|
+
stripeSubscriptionId: varchar('stripe_subscription_id', { length: 255 }),
|
|
16
|
+
stripeCustomerId: varchar('stripe_customer_id', { length: 255 }),
|
|
17
|
+
|
|
18
|
+
// Price versioning (locks in the price at subscription time)
|
|
19
|
+
billingInterval: varchar('billing_interval', { length: 20 }).$type<'monthly' | 'yearly'>(),
|
|
20
|
+
priceLookupKey: varchar('price_lookup_key', { length: 255 }), // e.g., 'pro_monthly_v1'
|
|
21
|
+
priceVersion: integer('price_version'),
|
|
22
|
+
priceAmountCents: integer('price_amount_cents'), // Locked-in price
|
|
23
|
+
|
|
24
|
+
// Trial support
|
|
25
|
+
trialStart: timestamp('trial_start'),
|
|
26
|
+
trialEnd: timestamp('trial_end'),
|
|
27
|
+
|
|
28
|
+
canceledAt: timestamp('canceled_at'),
|
|
29
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
30
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
31
|
+
}, (table) => [
|
|
32
|
+
index('subscriptions_org_idx').on(table.organizationId),
|
|
33
|
+
index('subscriptions_stripe_id_idx').on(table.stripeSubscriptionId),
|
|
34
|
+
index('subscriptions_customer_idx').on(table.stripeCustomerId),
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
export type Subscription = typeof subscriptions.$inferSelect;
|
|
38
|
+
export type NewSubscription = typeof subscriptions.$inferInsert;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { pgTable, uuid, varchar, boolean, timestamp } from 'drizzle-orm/pg-core';
|
|
2
|
+
|
|
3
|
+
export const users = pgTable('users', {
|
|
4
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
5
|
+
email: varchar('email', { length: 255 }).notNull().unique(),
|
|
6
|
+
name: varchar('name', { length: 255 }),
|
|
7
|
+
avatarUrl: varchar('avatar_url', { length: 500 }),
|
|
8
|
+
emailVerified: boolean('email_verified').default(false).notNull(),
|
|
9
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
10
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export type User = typeof users.$inferSelect;
|
|
14
|
+
export type NewUser = typeof users.$inferInsert;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import Fastify from 'fastify';
|
|
2
|
+
import cors from '@fastify/cors';
|
|
3
|
+
import helmet from '@fastify/helmet';
|
|
4
|
+
import sensible from '@fastify/sensible';
|
|
5
|
+
import { env } from './lib/env.js';
|
|
6
|
+
import { closeDatabase } from './db/index.js';
|
|
7
|
+
import healthRoutes from './routes/health.js';
|
|
8
|
+
|
|
9
|
+
const app = Fastify({
|
|
10
|
+
logger: {
|
|
11
|
+
level: env.NODE_ENV === 'development' ? 'info' : 'warn',
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Plugins
|
|
16
|
+
app.register(helmet);
|
|
17
|
+
app.register(cors, {
|
|
18
|
+
origin: env.NODE_ENV === 'development'
|
|
19
|
+
? true
|
|
20
|
+
: (env.CORS_ORIGIN?.split(',').map((o) => o.trim()).filter(Boolean) ?? false),
|
|
21
|
+
});
|
|
22
|
+
app.register(sensible);
|
|
23
|
+
|
|
24
|
+
// Routes
|
|
25
|
+
app.register(healthRoutes, { prefix: '/health' });
|
|
26
|
+
|
|
27
|
+
// Graceful shutdown
|
|
28
|
+
const shutdown = async (signal: string) => {
|
|
29
|
+
app.log.info(`Received ${signal}, shutting down gracefully...`);
|
|
30
|
+
try {
|
|
31
|
+
await app.close();
|
|
32
|
+
await closeDatabase();
|
|
33
|
+
process.exit(0);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
app.log.error('Error during shutdown:', err);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
41
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
42
|
+
|
|
43
|
+
// Start server
|
|
44
|
+
const start = async () => {
|
|
45
|
+
try {
|
|
46
|
+
await app.listen({ port: env.PORT, host: env.HOST });
|
|
47
|
+
app.log.info(`Server running at http://${env.HOST}:${env.PORT}`);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
app.log.error(err);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
start();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const envSchema = z.object({
|
|
5
|
+
PORT: z.coerce.number().default({{PORT}}),
|
|
6
|
+
HOST: z.string().default('0.0.0.0'),
|
|
7
|
+
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
|
8
|
+
CORS_ORIGIN: z.string().min(1).optional(),
|
|
9
|
+
DATABASE_URL: z.string().min(1),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const env = envSchema
|
|
13
|
+
.superRefine((val, ctx) => {
|
|
14
|
+
if (val.NODE_ENV === 'production' && !val.CORS_ORIGIN) {
|
|
15
|
+
ctx.addIssue({
|
|
16
|
+
code: z.ZodIssueCode.custom,
|
|
17
|
+
message: 'CORS_ORIGIN is required when NODE_ENV=production',
|
|
18
|
+
path: ['CORS_ORIGIN'],
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
.parse(process.env);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { FastifyPluginAsync } from 'fastify';
|
|
2
|
+
|
|
3
|
+
const healthRoutes: FastifyPluginAsync = async (fastify) => {
|
|
4
|
+
fastify.get('/', async () => {
|
|
5
|
+
return { status: 'ok', timestamp: new Date().toISOString() };
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
fastify.get('/ready', async () => {
|
|
9
|
+
// Add database connectivity check here if needed
|
|
10
|
+
return { status: 'ready', timestamp: new Date().toISOString() };
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default healthRoutes;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": "./src"
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"],
|
|
14
|
+
"exclude": ["node_modules", "dist"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build outputs
|
|
5
|
+
dist/
|
|
6
|
+
.turbo/
|
|
7
|
+
|
|
8
|
+
# Environment
|
|
9
|
+
.env
|
|
10
|
+
.env.local
|
|
11
|
+
.env.*.local
|
|
12
|
+
|
|
13
|
+
# IDE
|
|
14
|
+
.idea/
|
|
15
|
+
.vscode/
|
|
16
|
+
*.swp
|
|
17
|
+
*.swo
|
|
18
|
+
|
|
19
|
+
# OS
|
|
20
|
+
.DS_Store
|
|
21
|
+
Thumbs.db
|
|
22
|
+
|
|
23
|
+
# Logs
|
|
24
|
+
*.log
|
|
25
|
+
npm-debug.log*
|
|
26
|
+
pnpm-debug.log*
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "turbo dev",
|
|
7
|
+
"build": "turbo build",
|
|
8
|
+
"typecheck": "turbo typecheck",
|
|
9
|
+
"lint": "turbo lint"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"turbo": "^2.0.0"
|
|
13
|
+
},
|
|
14
|
+
"packageManager": "pnpm@9.0.0"
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://turbo.build/schema.json",
|
|
3
|
+
"tasks": {
|
|
4
|
+
"build": {
|
|
5
|
+
"dependsOn": ["^build"],
|
|
6
|
+
"outputs": ["dist/**"]
|
|
7
|
+
},
|
|
8
|
+
"dev": {
|
|
9
|
+
"cache": false,
|
|
10
|
+
"persistent": true
|
|
11
|
+
},
|
|
12
|
+
"typecheck": {
|
|
13
|
+
"dependsOn": ["^build"]
|
|
14
|
+
},
|
|
15
|
+
"lint": {}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{PROJECT_NAME}}/shared",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./schemas": {
|
|
13
|
+
"types": "./dist/schemas/index.d.ts",
|
|
14
|
+
"import": "./dist/schemas/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./types": {
|
|
17
|
+
"types": "./dist/types/index.d.ts",
|
|
18
|
+
"import": "./dist/types/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./validators": {
|
|
21
|
+
"types": "./dist/validators/index.d.ts",
|
|
22
|
+
"import": "./dist/validators/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"dev": "tsc --watch",
|
|
28
|
+
"typecheck": "tsc --noEmit"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"zod": "^3.23.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "^5.7.0"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const apiKeySchema = z.object({
|
|
4
|
+
id: z.string().uuid(),
|
|
5
|
+
organizationId: z.string().uuid(),
|
|
6
|
+
name: z.string().min(1).max(255),
|
|
7
|
+
keyHash: z.string(), // hashed, never store plain
|
|
8
|
+
keyPrefix: z.string().length(8), // first 8 chars for identification
|
|
9
|
+
lastUsedAt: z.coerce.date().nullable(),
|
|
10
|
+
expiresAt: z.coerce.date().nullable(),
|
|
11
|
+
createdAt: z.coerce.date(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const createApiKeySchema = z.object({
|
|
15
|
+
organizationId: z.string().uuid(),
|
|
16
|
+
name: z.string().min(1).max(255),
|
|
17
|
+
expiresAt: z.coerce.date().nullable().optional(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// What's returned when creating a new key (includes unhashed key once)
|
|
21
|
+
export const apiKeyWithSecretSchema = apiKeySchema.extend({
|
|
22
|
+
key: z.string(), // full key, only shown once at creation
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const updateApiKeySchema = z.object({
|
|
26
|
+
name: z.string().min(1).max(255).optional(),
|
|
27
|
+
expiresAt: z.coerce.date().nullable().optional(),
|
|
28
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const auditLogSchema = z.object({
|
|
4
|
+
id: z.string().uuid(),
|
|
5
|
+
organizationId: z.string().uuid(),
|
|
6
|
+
userId: z.string().uuid().nullable(),
|
|
7
|
+
action: z.string(), // e.g., 'user.created', 'subscription.upgraded'
|
|
8
|
+
resourceType: z.string(), // e.g., 'user', 'subscription'
|
|
9
|
+
resourceId: z.string().nullable(),
|
|
10
|
+
metadata: z.record(z.unknown()),
|
|
11
|
+
ipAddress: z.string().nullable(),
|
|
12
|
+
userAgent: z.string().nullable(),
|
|
13
|
+
createdAt: z.coerce.date(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const createAuditLogSchema = auditLogSchema.omit({
|
|
17
|
+
id: true,
|
|
18
|
+
createdAt: true,
|
|
19
|
+
}).extend({
|
|
20
|
+
metadata: z.record(z.unknown()).optional(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Common audit actions
|
|
24
|
+
export const AuditAction = {
|
|
25
|
+
USER_CREATED: 'user.created',
|
|
26
|
+
USER_UPDATED: 'user.updated',
|
|
27
|
+
USER_DELETED: 'user.deleted',
|
|
28
|
+
ORG_CREATED: 'organization.created',
|
|
29
|
+
ORG_UPDATED: 'organization.updated',
|
|
30
|
+
MEMBER_ADDED: 'member.added',
|
|
31
|
+
MEMBER_REMOVED: 'member.removed',
|
|
32
|
+
MEMBER_ROLE_CHANGED: 'member.role_changed',
|
|
33
|
+
SUBSCRIPTION_CREATED: 'subscription.created',
|
|
34
|
+
SUBSCRIPTION_UPGRADED: 'subscription.upgraded',
|
|
35
|
+
SUBSCRIPTION_DOWNGRADED: 'subscription.downgraded',
|
|
36
|
+
SUBSCRIPTION_CANCELED: 'subscription.canceled',
|
|
37
|
+
API_KEY_CREATED: 'api_key.created',
|
|
38
|
+
API_KEY_REVOKED: 'api_key.revoked',
|
|
39
|
+
INVITE_SENT: 'invite.sent',
|
|
40
|
+
INVITE_ACCEPTED: 'invite.accepted',
|
|
41
|
+
} as const;
|