diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-12-06 17:05:21 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-12-06 17:37:04 +0900 |
| commit | 811458427593a4172a2cd535cc768db375350dca (patch) | |
| tree | 6c4f46c96b6f29392dc19d591e39e03c187033a1 /src/server/db/schema.ts | |
| parent | 9736a8981fbd6c6defbd67517ca23904fc844629 (diff) | |
| download | kioku-811458427593a4172a2cd535cc768db375350dca.tar.gz kioku-811458427593a4172a2cd535cc768db375350dca.tar.zst kioku-811458427593a4172a2cd535cc768db375350dca.zip | |
feat(dev): change architecture and directory structure
Diffstat (limited to 'src/server/db/schema.ts')
| -rw-r--r-- | src/server/db/schema.ts | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts new file mode 100644 index 0000000..4b9631f --- /dev/null +++ b/src/server/db/schema.ts @@ -0,0 +1,116 @@ +import { + integer, + pgTable, + real, + smallint, + text, + timestamp, + uuid, + varchar, +} from "drizzle-orm/pg-core"; + +// Card states for FSRS algorithm +export const CardState = { + New: 0, + Learning: 1, + Review: 2, + Relearning: 3, +} as const; + +// Rating values for reviews +export const Rating = { + Again: 1, + Hard: 2, + Good: 3, + Easy: 4, +} as const; + +export const users = pgTable("users", { + id: uuid("id").primaryKey().defaultRandom(), + username: varchar("username", { length: 255 }).notNull().unique(), + passwordHash: varchar("password_hash", { length: 255 }).notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .notNull() + .defaultNow(), +}); + +export const refreshTokens = pgTable("refresh_tokens", { + id: uuid("id").primaryKey().defaultRandom(), + userId: uuid("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + tokenHash: varchar("token_hash", { length: 255 }).notNull(), + expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), +}); + +export const decks = pgTable("decks", { + id: uuid("id").primaryKey().defaultRandom(), + userId: uuid("user_id") + .notNull() + .references(() => users.id), + name: varchar("name", { length: 255 }).notNull(), + description: text("description"), + newCardsPerDay: integer("new_cards_per_day").notNull().default(20), + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .notNull() + .defaultNow(), + deletedAt: timestamp("deleted_at", { withTimezone: true }), + syncVersion: integer("sync_version").notNull().default(0), +}); + +export const cards = pgTable("cards", { + id: uuid("id").primaryKey().defaultRandom(), + deckId: uuid("deck_id") + .notNull() + .references(() => decks.id), + front: text("front").notNull(), + back: text("back").notNull(), + + // FSRS fields + state: smallint("state").notNull().default(CardState.New), + due: timestamp("due", { withTimezone: true }).notNull().defaultNow(), + stability: real("stability").notNull().default(0), + difficulty: real("difficulty").notNull().default(0), + elapsedDays: integer("elapsed_days").notNull().default(0), + scheduledDays: integer("scheduled_days").notNull().default(0), + reps: integer("reps").notNull().default(0), + lapses: integer("lapses").notNull().default(0), + lastReview: timestamp("last_review", { withTimezone: true }), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .notNull() + .defaultNow(), + deletedAt: timestamp("deleted_at", { withTimezone: true }), + syncVersion: integer("sync_version").notNull().default(0), +}); + +export const reviewLogs = pgTable("review_logs", { + id: uuid("id").primaryKey().defaultRandom(), + cardId: uuid("card_id") + .notNull() + .references(() => cards.id), + userId: uuid("user_id") + .notNull() + .references(() => users.id), + rating: smallint("rating").notNull(), + state: smallint("state").notNull(), + scheduledDays: integer("scheduled_days").notNull(), + elapsedDays: integer("elapsed_days").notNull(), + reviewedAt: timestamp("reviewed_at", { withTimezone: true }) + .notNull() + .defaultNow(), + durationMs: integer("duration_ms"), + syncVersion: integer("sync_version").notNull().default(0), +}); |
