mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 20:46:42 +02:00
style: auto-format codebase with Prettier
Applied formatting to 1487+ files using pnpm format:write - TypeScript/JavaScript files - Svelte components - Astro pages - JSON configs - Markdown docs 13 files still need manual review (Astro JSX comments)
This commit is contained in:
parent
0241f5554c
commit
d36b321d9d
3952 changed files with 661498 additions and 739751 deletions
|
|
@ -6,28 +6,28 @@ let connection: ReturnType<typeof postgres> | null = null;
|
|||
let db: ReturnType<typeof drizzle> | null = null;
|
||||
|
||||
export function getConnection(databaseUrl: string) {
|
||||
if (!connection) {
|
||||
connection = postgres(databaseUrl, {
|
||||
max: 10,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 10,
|
||||
});
|
||||
}
|
||||
return connection;
|
||||
if (!connection) {
|
||||
connection = postgres(databaseUrl, {
|
||||
max: 10,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 10,
|
||||
});
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
export function getDb(databaseUrl: string) {
|
||||
if (!db) {
|
||||
const conn = getConnection(databaseUrl);
|
||||
db = drizzle(conn, { schema });
|
||||
}
|
||||
return db;
|
||||
if (!db) {
|
||||
const conn = getConnection(databaseUrl);
|
||||
db = drizzle(conn, { schema });
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
export async function closeConnection() {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
connection = null;
|
||||
db = null;
|
||||
}
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
connection = null;
|
||||
db = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,24 +6,24 @@ import { getDb, closeConnection } from './connection';
|
|||
config();
|
||||
|
||||
async function runMigrations() {
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
|
||||
if (!databaseUrl) {
|
||||
throw new Error('DATABASE_URL environment variable is not set');
|
||||
}
|
||||
if (!databaseUrl) {
|
||||
throw new Error('DATABASE_URL environment variable is not set');
|
||||
}
|
||||
|
||||
console.log('Running migrations...');
|
||||
console.log('Running migrations...');
|
||||
|
||||
try {
|
||||
const db = getDb(databaseUrl);
|
||||
await migrate(db, { migrationsFolder: './src/db/migrations' });
|
||||
console.log('Migrations completed successfully');
|
||||
} catch (error) {
|
||||
console.error('Migration failed:', error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await closeConnection();
|
||||
}
|
||||
try {
|
||||
const db = getDb(databaseUrl);
|
||||
await migrate(db, { migrationsFolder: './src/db/migrations' });
|
||||
console.log('Migrations completed successfully');
|
||||
} catch (error) {
|
||||
console.error('Migration failed:', error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await closeConnection();
|
||||
}
|
||||
}
|
||||
|
||||
runMigrations();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1764089133415,
|
||||
"tag": "0000_lush_ironclad",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1764089133415,
|
||||
"tag": "0000_lush_ironclad",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,87 +8,97 @@ export const userRoleEnum = pgEnum('user_role', ['user', 'admin', 'service']);
|
|||
|
||||
// Users table
|
||||
export const users = authSchema.table('users', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
email: text('email').unique().notNull(),
|
||||
emailVerified: boolean('email_verified').default(false).notNull(),
|
||||
name: text('name'),
|
||||
avatarUrl: text('avatar_url'),
|
||||
role: userRoleEnum('role').default('user').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
deletedAt: timestamp('deleted_at', { withTimezone: true }),
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
email: text('email').unique().notNull(),
|
||||
emailVerified: boolean('email_verified').default(false).notNull(),
|
||||
name: text('name'),
|
||||
avatarUrl: text('avatar_url'),
|
||||
role: userRoleEnum('role').default('user').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
deletedAt: timestamp('deleted_at', { withTimezone: true }),
|
||||
});
|
||||
|
||||
// Sessions table
|
||||
export const sessions = authSchema.table('sessions', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||
token: text('token').unique().notNull(),
|
||||
refreshToken: text('refresh_token').unique().notNull(),
|
||||
refreshTokenExpiresAt: timestamp('refresh_token_expires_at', { withTimezone: true }).notNull(),
|
||||
ipAddress: text('ip_address'),
|
||||
userAgent: text('user_agent'),
|
||||
deviceId: text('device_id'),
|
||||
deviceName: text('device_name'),
|
||||
lastActivityAt: timestamp('last_activity_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
||||
revokedAt: timestamp('revoked_at', { withTimezone: true }),
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id')
|
||||
.references(() => users.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
token: text('token').unique().notNull(),
|
||||
refreshToken: text('refresh_token').unique().notNull(),
|
||||
refreshTokenExpiresAt: timestamp('refresh_token_expires_at', { withTimezone: true }).notNull(),
|
||||
ipAddress: text('ip_address'),
|
||||
userAgent: text('user_agent'),
|
||||
deviceId: text('device_id'),
|
||||
deviceName: text('device_name'),
|
||||
lastActivityAt: timestamp('last_activity_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
||||
revokedAt: timestamp('revoked_at', { withTimezone: true }),
|
||||
});
|
||||
|
||||
// Accounts table (for OAuth providers)
|
||||
export const accounts = authSchema.table('accounts', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||
provider: text('provider').notNull(), // 'google', 'github', 'apple', etc.
|
||||
providerAccountId: text('provider_account_id').notNull(),
|
||||
accessToken: text('access_token'),
|
||||
refreshToken: text('refresh_token'),
|
||||
expiresAt: timestamp('expires_at', { withTimezone: true }),
|
||||
tokenType: text('token_type'),
|
||||
scope: text('scope'),
|
||||
idToken: text('id_token'),
|
||||
metadata: jsonb('metadata'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id')
|
||||
.references(() => users.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
provider: text('provider').notNull(), // 'google', 'github', 'apple', etc.
|
||||
providerAccountId: text('provider_account_id').notNull(),
|
||||
accessToken: text('access_token'),
|
||||
refreshToken: text('refresh_token'),
|
||||
expiresAt: timestamp('expires_at', { withTimezone: true }),
|
||||
tokenType: text('token_type'),
|
||||
scope: text('scope'),
|
||||
idToken: text('id_token'),
|
||||
metadata: jsonb('metadata'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// Verification tokens (for email verification, password reset)
|
||||
export const verificationTokens = authSchema.table('verification_tokens', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||
token: text('token').unique().notNull(),
|
||||
type: text('type').notNull(), // 'email_verification', 'password_reset'
|
||||
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
usedAt: timestamp('used_at', { withTimezone: true }),
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id')
|
||||
.references(() => users.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
token: text('token').unique().notNull(),
|
||||
type: text('type').notNull(), // 'email_verification', 'password_reset'
|
||||
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
usedAt: timestamp('used_at', { withTimezone: true }),
|
||||
});
|
||||
|
||||
// Password table (separate for security)
|
||||
export const passwords = authSchema.table('passwords', {
|
||||
userId: uuid('user_id').primaryKey().references(() => users.id, { onDelete: 'cascade' }),
|
||||
hashedPassword: text('hashed_password').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
userId: uuid('user_id')
|
||||
.primaryKey()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
hashedPassword: text('hashed_password').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// Two-factor authentication
|
||||
export const twoFactorAuth = authSchema.table('two_factor_auth', {
|
||||
userId: uuid('user_id').primaryKey().references(() => users.id, { onDelete: 'cascade' }),
|
||||
secret: text('secret').notNull(),
|
||||
enabled: boolean('enabled').default(false).notNull(),
|
||||
backupCodes: jsonb('backup_codes'), // Array of hashed backup codes
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
enabledAt: timestamp('enabled_at', { withTimezone: true }),
|
||||
userId: uuid('user_id')
|
||||
.primaryKey()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
secret: text('secret').notNull(),
|
||||
enabled: boolean('enabled').default(false).notNull(),
|
||||
backupCodes: jsonb('backup_codes'), // Array of hashed backup codes
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
enabledAt: timestamp('enabled_at', { withTimezone: true }),
|
||||
});
|
||||
|
||||
// Security events log
|
||||
export const securityEvents = authSchema.table('security_events', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }),
|
||||
eventType: text('event_type').notNull(), // 'login', 'logout', 'password_reset', 'suspicious_activity'
|
||||
ipAddress: text('ip_address'),
|
||||
userAgent: text('user_agent'),
|
||||
metadata: jsonb('metadata'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }),
|
||||
eventType: text('event_type').notNull(), // 'login', 'logout', 'password_reset', 'suspicious_activity'
|
||||
ipAddress: text('ip_address'),
|
||||
userAgent: text('user_agent'),
|
||||
metadata: jsonb('metadata'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,104 +1,136 @@
|
|||
import { pgSchema, uuid, integer, text, timestamp, jsonb, index, pgEnum, boolean } from 'drizzle-orm/pg-core';
|
||||
import {
|
||||
pgSchema,
|
||||
uuid,
|
||||
integer,
|
||||
text,
|
||||
timestamp,
|
||||
jsonb,
|
||||
index,
|
||||
pgEnum,
|
||||
boolean,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { users } from './auth.schema';
|
||||
|
||||
export const creditsSchema = pgSchema('credits');
|
||||
|
||||
// Transaction types enum
|
||||
export const transactionTypeEnum = pgEnum('transaction_type', [
|
||||
'purchase',
|
||||
'usage',
|
||||
'refund',
|
||||
'bonus',
|
||||
'expiry',
|
||||
'adjustment',
|
||||
'purchase',
|
||||
'usage',
|
||||
'refund',
|
||||
'bonus',
|
||||
'expiry',
|
||||
'adjustment',
|
||||
]);
|
||||
|
||||
// Transaction status enum
|
||||
export const transactionStatusEnum = pgEnum('transaction_status', [
|
||||
'pending',
|
||||
'completed',
|
||||
'failed',
|
||||
'cancelled',
|
||||
'pending',
|
||||
'completed',
|
||||
'failed',
|
||||
'cancelled',
|
||||
]);
|
||||
|
||||
// Credit balances (one per user)
|
||||
export const balances = creditsSchema.table('balances', {
|
||||
userId: uuid('user_id').primaryKey().references(() => users.id, { onDelete: 'cascade' }),
|
||||
balance: integer('balance').default(0).notNull(),
|
||||
freeCreditsRemaining: integer('free_credits_remaining').default(150).notNull(),
|
||||
dailyFreeCredits: integer('daily_free_credits').default(5).notNull(),
|
||||
lastDailyResetAt: timestamp('last_daily_reset_at', { withTimezone: true }).defaultNow(),
|
||||
totalEarned: integer('total_earned').default(0).notNull(),
|
||||
totalSpent: integer('total_spent').default(0).notNull(),
|
||||
version: integer('version').default(0).notNull(), // For optimistic locking
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
userId: uuid('user_id')
|
||||
.primaryKey()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
balance: integer('balance').default(0).notNull(),
|
||||
freeCreditsRemaining: integer('free_credits_remaining').default(150).notNull(),
|
||||
dailyFreeCredits: integer('daily_free_credits').default(5).notNull(),
|
||||
lastDailyResetAt: timestamp('last_daily_reset_at', { withTimezone: true }).defaultNow(),
|
||||
totalEarned: integer('total_earned').default(0).notNull(),
|
||||
totalSpent: integer('total_spent').default(0).notNull(),
|
||||
version: integer('version').default(0).notNull(), // For optimistic locking
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// Transaction ledger
|
||||
export const transactions = creditsSchema.table('transactions', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||
type: transactionTypeEnum('type').notNull(),
|
||||
status: transactionStatusEnum('status').default('pending').notNull(),
|
||||
amount: integer('amount').notNull(),
|
||||
balanceBefore: integer('balance_before').notNull(),
|
||||
balanceAfter: integer('balance_after').notNull(),
|
||||
appId: text('app_id').notNull(), // 'memoro', 'chat', 'picture', etc.
|
||||
description: text('description').notNull(),
|
||||
metadata: jsonb('metadata'), // Additional context
|
||||
idempotencyKey: text('idempotency_key').unique(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
completedAt: timestamp('completed_at', { withTimezone: true }),
|
||||
}, (table) => ({
|
||||
userIdIdx: index('transactions_user_id_idx').on(table.userId),
|
||||
appIdIdx: index('transactions_app_id_idx').on(table.appId),
|
||||
createdAtIdx: index('transactions_created_at_idx').on(table.createdAt),
|
||||
idempotencyKeyIdx: index('transactions_idempotency_key_idx').on(table.idempotencyKey),
|
||||
}));
|
||||
export const transactions = creditsSchema.table(
|
||||
'transactions',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id')
|
||||
.references(() => users.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
type: transactionTypeEnum('type').notNull(),
|
||||
status: transactionStatusEnum('status').default('pending').notNull(),
|
||||
amount: integer('amount').notNull(),
|
||||
balanceBefore: integer('balance_before').notNull(),
|
||||
balanceAfter: integer('balance_after').notNull(),
|
||||
appId: text('app_id').notNull(), // 'memoro', 'chat', 'picture', etc.
|
||||
description: text('description').notNull(),
|
||||
metadata: jsonb('metadata'), // Additional context
|
||||
idempotencyKey: text('idempotency_key').unique(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
completedAt: timestamp('completed_at', { withTimezone: true }),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index('transactions_user_id_idx').on(table.userId),
|
||||
appIdIdx: index('transactions_app_id_idx').on(table.appId),
|
||||
createdAtIdx: index('transactions_created_at_idx').on(table.createdAt),
|
||||
idempotencyKeyIdx: index('transactions_idempotency_key_idx').on(table.idempotencyKey),
|
||||
})
|
||||
);
|
||||
|
||||
// Credit packages (pricing tiers)
|
||||
export const packages = creditsSchema.table('packages', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
credits: integer('credits').notNull(), // Number of credits
|
||||
priceEuroCents: integer('price_euro_cents').notNull(), // Price in euro cents
|
||||
stripePriceId: text('stripe_price_id').unique(),
|
||||
active: boolean('active').default(true).notNull(),
|
||||
sortOrder: integer('sort_order').default(0).notNull(),
|
||||
metadata: jsonb('metadata'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
credits: integer('credits').notNull(), // Number of credits
|
||||
priceEuroCents: integer('price_euro_cents').notNull(), // Price in euro cents
|
||||
stripePriceId: text('stripe_price_id').unique(),
|
||||
active: boolean('active').default(true).notNull(),
|
||||
sortOrder: integer('sort_order').default(0).notNull(),
|
||||
metadata: jsonb('metadata'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// Purchase history
|
||||
export const purchases = creditsSchema.table('purchases', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||
packageId: uuid('package_id').references(() => packages.id),
|
||||
credits: integer('credits').notNull(),
|
||||
priceEuroCents: integer('price_euro_cents').notNull(),
|
||||
stripePaymentIntentId: text('stripe_payment_intent_id').unique(),
|
||||
stripeCustomerId: text('stripe_customer_id'),
|
||||
status: transactionStatusEnum('status').default('pending').notNull(),
|
||||
metadata: jsonb('metadata'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
completedAt: timestamp('completed_at', { withTimezone: true }),
|
||||
}, (table) => ({
|
||||
userIdIdx: index('purchases_user_id_idx').on(table.userId),
|
||||
stripePaymentIntentIdIdx: index('purchases_stripe_payment_intent_id_idx').on(table.stripePaymentIntentId),
|
||||
}));
|
||||
export const purchases = creditsSchema.table(
|
||||
'purchases',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id')
|
||||
.references(() => users.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
packageId: uuid('package_id').references(() => packages.id),
|
||||
credits: integer('credits').notNull(),
|
||||
priceEuroCents: integer('price_euro_cents').notNull(),
|
||||
stripePaymentIntentId: text('stripe_payment_intent_id').unique(),
|
||||
stripeCustomerId: text('stripe_customer_id'),
|
||||
status: transactionStatusEnum('status').default('pending').notNull(),
|
||||
metadata: jsonb('metadata'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
completedAt: timestamp('completed_at', { withTimezone: true }),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index('purchases_user_id_idx').on(table.userId),
|
||||
stripePaymentIntentIdIdx: index('purchases_stripe_payment_intent_id_idx').on(
|
||||
table.stripePaymentIntentId
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
// Usage tracking (for analytics)
|
||||
export const usageStats = creditsSchema.table('usage_stats', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||
appId: text('app_id').notNull(),
|
||||
creditsUsed: integer('credits_used').notNull(),
|
||||
date: timestamp('date', { withTimezone: true }).notNull(),
|
||||
metadata: jsonb('metadata'),
|
||||
}, (table) => ({
|
||||
userIdDateIdx: index('usage_stats_user_id_date_idx').on(table.userId, table.date),
|
||||
appIdDateIdx: index('usage_stats_app_id_date_idx').on(table.appId, table.date),
|
||||
}));
|
||||
export const usageStats = creditsSchema.table(
|
||||
'usage_stats',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id')
|
||||
.references(() => users.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
appId: text('app_id').notNull(),
|
||||
creditsUsed: integer('credits_used').notNull(),
|
||||
date: timestamp('date', { withTimezone: true }).notNull(),
|
||||
metadata: jsonb('metadata'),
|
||||
},
|
||||
(table) => ({
|
||||
userIdDateIdx: index('usage_stats_user_id_date_idx').on(table.userId, table.date),
|
||||
appIdDateIdx: index('usage_stats_app_id_date_idx').on(table.appId, table.date),
|
||||
})
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue