mirror of
https://github.com/jnsahaj/tweakcn.git
synced 2026-08-30 18:10:28 +08:00
6bc66ba071
- Add like_count column to community_theme table with backfill migration - Update toggleLikeTheme to atomically increment/decrement instead of COUNT(*) - Remove expensive GROUP BY subquery from getCommunityThemes and getCommunityDataForTheme - Wrap read queries with unstable_cache (60s themes, 300s tag counts) - Add revalidateTag calls to all write operations for cache invalidation - Increase React Query staleTime (themes 2m→5m, tags 5m→15m, published 5m→10m) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
212 lines
7.0 KiB
TypeScript
212 lines
7.0 KiB
TypeScript
import { ThemeStyles } from "@/types/theme";
|
|
import {
|
|
pgTable,
|
|
json,
|
|
timestamp,
|
|
boolean,
|
|
text,
|
|
integer,
|
|
primaryKey,
|
|
index,
|
|
} from "drizzle-orm/pg-core";
|
|
|
|
export const user = pgTable("user", {
|
|
id: text("id").primaryKey(),
|
|
name: text("name").notNull(),
|
|
email: text("email").notNull().unique(),
|
|
emailVerified: boolean("email_verified").notNull(),
|
|
image: text("image"),
|
|
createdAt: timestamp("created_at").notNull(),
|
|
updatedAt: timestamp("updated_at").notNull(),
|
|
});
|
|
|
|
export const session = pgTable("session", {
|
|
id: text("id").primaryKey(),
|
|
expiresAt: timestamp("expires_at").notNull(),
|
|
token: text("token").notNull().unique(),
|
|
createdAt: timestamp("created_at").notNull(),
|
|
updatedAt: timestamp("updated_at").notNull(),
|
|
ipAddress: text("ip_address"),
|
|
userAgent: text("user_agent"),
|
|
userId: text("user_id")
|
|
.notNull()
|
|
.references(() => user.id, { onDelete: "cascade" }),
|
|
});
|
|
|
|
export const account = pgTable("account", {
|
|
id: text("id").primaryKey(),
|
|
accountId: text("account_id").notNull(),
|
|
providerId: text("provider_id").notNull(),
|
|
userId: text("user_id")
|
|
.notNull()
|
|
.references(() => user.id, { onDelete: "cascade" }),
|
|
accessToken: text("access_token"),
|
|
refreshToken: text("refresh_token"),
|
|
idToken: text("id_token"),
|
|
accessTokenExpiresAt: timestamp("access_token_expires_at"),
|
|
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
|
scope: text("scope"),
|
|
password: text("password"),
|
|
createdAt: timestamp("created_at").notNull(),
|
|
updatedAt: timestamp("updated_at").notNull(),
|
|
});
|
|
|
|
export const verification = pgTable("verification", {
|
|
id: text("id").primaryKey(),
|
|
identifier: text("identifier").notNull(),
|
|
value: text("value").notNull(),
|
|
expiresAt: timestamp("expires_at").notNull(),
|
|
createdAt: timestamp("created_at"),
|
|
updatedAt: timestamp("updated_at"),
|
|
});
|
|
|
|
export const theme = pgTable("theme", {
|
|
id: text("id").primaryKey(),
|
|
userId: text("user_id")
|
|
.notNull()
|
|
.references(() => user.id, { onDelete: "cascade" }),
|
|
name: text("name").notNull(),
|
|
styles: json("styles").$type<ThemeStyles>().notNull(),
|
|
createdAt: timestamp("created_at").notNull(),
|
|
updatedAt: timestamp("updated_at").notNull(),
|
|
});
|
|
|
|
export const aiUsage = pgTable("ai_usage", {
|
|
id: text("id").primaryKey(),
|
|
userId: text("user_id")
|
|
.notNull()
|
|
.references(() => user.id, { onDelete: "cascade" }),
|
|
modelId: text("model_id").notNull(),
|
|
promptTokens: text("prompt_tokens").notNull().default("0"),
|
|
completionTokens: text("completion_tokens").notNull().default("0"),
|
|
daysSinceEpoch: text("days_since_epoch").notNull(),
|
|
createdAt: timestamp("created_at").notNull(),
|
|
});
|
|
|
|
export const subscription = pgTable("subscription", {
|
|
id: text("id").primaryKey(),
|
|
createdAt: timestamp("createdAt").notNull(),
|
|
modifiedAt: timestamp("modifiedAt"),
|
|
amount: integer("amount").notNull(),
|
|
currency: text("currency").notNull(),
|
|
recurringInterval: text("recurringInterval").notNull(),
|
|
status: text("status").notNull(),
|
|
currentPeriodStart: timestamp("currentPeriodStart").notNull(),
|
|
currentPeriodEnd: timestamp("currentPeriodEnd").notNull(),
|
|
cancelAtPeriodEnd: boolean("cancelAtPeriodEnd").notNull().default(false),
|
|
canceledAt: timestamp("canceledAt"),
|
|
startedAt: timestamp("startedAt").notNull(),
|
|
endsAt: timestamp("endsAt"),
|
|
endedAt: timestamp("endedAt"),
|
|
customerId: text("customerId").notNull(),
|
|
productId: text("productId").notNull(),
|
|
discountId: text("discountId"),
|
|
checkoutId: text("checkoutId").notNull(),
|
|
customerCancellationReason: text("customerCancellationReason"),
|
|
customerCancellationComment: text("customerCancellationComment"),
|
|
metadata: text("metadata"), // JSON string
|
|
customFieldData: text("customFieldData"), // JSON string
|
|
userId: text("userId").references(() => user.id),
|
|
});
|
|
|
|
// OAuth 2.0 tables
|
|
|
|
export const oauthApp = pgTable("oauth_app", {
|
|
id: text("id").primaryKey(),
|
|
name: text("name").notNull(),
|
|
description: text("description"),
|
|
clientId: text("client_id").notNull().unique(),
|
|
clientSecretHash: text("client_secret_hash").notNull(),
|
|
redirectUris: json("redirect_uris").$type<string[]>().notNull(),
|
|
scopes: json("scopes").$type<string[]>().notNull(),
|
|
isActive: boolean("is_active").notNull().default(true),
|
|
createdAt: timestamp("created_at").notNull(),
|
|
updatedAt: timestamp("updated_at").notNull(),
|
|
});
|
|
|
|
export const oauthAuthorizationCode = pgTable("oauth_authorization_code", {
|
|
id: text("id").primaryKey(),
|
|
code: text("code").notNull().unique(),
|
|
appId: text("app_id")
|
|
.notNull()
|
|
.references(() => oauthApp.id, { onDelete: "cascade" }),
|
|
userId: text("user_id")
|
|
.notNull()
|
|
.references(() => user.id, { onDelete: "cascade" }),
|
|
scopes: json("scopes").$type<string[]>().notNull(),
|
|
redirectUri: text("redirect_uri").notNull(),
|
|
codeChallenge: text("code_challenge"),
|
|
codeChallengeMethod: text("code_challenge_method"),
|
|
expiresAt: timestamp("expires_at").notNull(),
|
|
usedAt: timestamp("used_at"),
|
|
createdAt: timestamp("created_at").notNull(),
|
|
});
|
|
|
|
export const oauthToken = pgTable("oauth_token", {
|
|
id: text("id").primaryKey(),
|
|
accessTokenHash: text("access_token_hash").notNull().unique(),
|
|
refreshTokenHash: text("refresh_token_hash").unique(),
|
|
appId: text("app_id")
|
|
.notNull()
|
|
.references(() => oauthApp.id, { onDelete: "cascade" }),
|
|
userId: text("user_id")
|
|
.notNull()
|
|
.references(() => user.id, { onDelete: "cascade" }),
|
|
scopes: json("scopes").$type<string[]>().notNull(),
|
|
accessTokenExpiresAt: timestamp("access_token_expires_at").notNull(),
|
|
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
|
revokedAt: timestamp("revoked_at"),
|
|
createdAt: timestamp("created_at").notNull(),
|
|
updatedAt: timestamp("updated_at").notNull(),
|
|
});
|
|
|
|
// Community themes
|
|
|
|
export const communityTheme = pgTable(
|
|
"community_theme",
|
|
{
|
|
id: text("id").primaryKey(),
|
|
themeId: text("theme_id")
|
|
.notNull()
|
|
.unique()
|
|
.references(() => theme.id, { onDelete: "cascade" }),
|
|
userId: text("user_id")
|
|
.notNull()
|
|
.references(() => user.id, { onDelete: "cascade" }),
|
|
publishedAt: timestamp("published_at").notNull(),
|
|
likeCount: integer("like_count").notNull().default(0),
|
|
},
|
|
(table) => [
|
|
index("community_theme_published_at_idx").on(table.publishedAt),
|
|
index("community_theme_like_count_idx").on(table.likeCount),
|
|
]
|
|
);
|
|
|
|
export const communityThemeTag = pgTable(
|
|
"community_theme_tag",
|
|
{
|
|
communityThemeId: text("community_theme_id")
|
|
.notNull()
|
|
.references(() => communityTheme.id, { onDelete: "cascade" }),
|
|
tag: text("tag").notNull(),
|
|
},
|
|
(table) => [
|
|
primaryKey({ columns: [table.communityThemeId, table.tag] }),
|
|
index("community_theme_tag_tag_idx").on(table.tag),
|
|
]
|
|
);
|
|
|
|
export const themeLike = pgTable(
|
|
"theme_like",
|
|
{
|
|
userId: text("user_id")
|
|
.notNull()
|
|
.references(() => user.id, { onDelete: "cascade" }),
|
|
themeId: text("theme_id")
|
|
.notNull()
|
|
.references(() => communityTheme.id, { onDelete: "cascade" }),
|
|
createdAt: timestamp("created_at").notNull(),
|
|
},
|
|
(table) => [primaryKey({ columns: [table.userId, table.themeId] })]
|
|
);
|