feat: add users, sessions, and api_keys tables

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 13:18:07 +02:00
parent b3a13fa974
commit 32c7b41ce5
5 changed files with 739 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
CREATE TABLE `api_keys` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` text NOT NULL,
`key_hash` text NOT NULL,
`key_prefix` text NOT NULL,
`created_at` integer NOT NULL
);
--> statement-breakpoint
CREATE TABLE `sessions` (
`id` text PRIMARY KEY NOT NULL,
`user_id` integer NOT NULL,
`expires_at` integer NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `users` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`username` text NOT NULL,
`password_hash` text NOT NULL,
`created_at` integer NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX `users_username_unique` ON `users` (`username`);

View File

@@ -0,0 +1,655 @@
{
"version": "6",
"dialect": "sqlite",
"id": "738e67c5-ebad-46c1-9261-6ab60ec4bdb1",
"prevId": "e26d6e96-f2cd-4b76-ad4e-58da0af6f3c0",
"tables": {
"api_keys": {
"name": "api_keys",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"key_hash": {
"name": "key_hash",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"key_prefix": {
"name": "key_prefix",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"categories": {
"name": "categories",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"icon": {
"name": "icon",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'package'"
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"categories_name_unique": {
"name": "categories_name_unique",
"columns": [
"name"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"items": {
"name": "items",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"weight_grams": {
"name": "weight_grams",
"type": "real",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"price_cents": {
"name": "price_cents",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"category_id": {
"name": "category_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"notes": {
"name": "notes",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"product_url": {
"name": "product_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"image_filename": {
"name": "image_filename",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"image_source_url": {
"name": "image_source_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"items_category_id_categories_id_fk": {
"name": "items_category_id_categories_id_fk",
"tableFrom": "items",
"tableTo": "categories",
"columnsFrom": [
"category_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"sessions": {
"name": "sessions",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"sessions_user_id_users_id_fk": {
"name": "sessions_user_id_users_id_fk",
"tableFrom": "sessions",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"settings": {
"name": "settings",
"columns": {
"key": {
"name": "key",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"setup_items": {
"name": "setup_items",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"setup_id": {
"name": "setup_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"item_id": {
"name": "item_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"classification": {
"name": "classification",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'base'"
}
},
"indexes": {},
"foreignKeys": {
"setup_items_setup_id_setups_id_fk": {
"name": "setup_items_setup_id_setups_id_fk",
"tableFrom": "setup_items",
"tableTo": "setups",
"columnsFrom": [
"setup_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"setup_items_item_id_items_id_fk": {
"name": "setup_items_item_id_items_id_fk",
"tableFrom": "setup_items",
"tableTo": "items",
"columnsFrom": [
"item_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"setups": {
"name": "setups",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"thread_candidates": {
"name": "thread_candidates",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"thread_id": {
"name": "thread_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"weight_grams": {
"name": "weight_grams",
"type": "real",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"price_cents": {
"name": "price_cents",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"category_id": {
"name": "category_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"notes": {
"name": "notes",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"product_url": {
"name": "product_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"image_filename": {
"name": "image_filename",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"image_source_url": {
"name": "image_source_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'researching'"
},
"pros": {
"name": "pros",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cons": {
"name": "cons",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"sort_order": {
"name": "sort_order",
"type": "real",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"thread_candidates_thread_id_threads_id_fk": {
"name": "thread_candidates_thread_id_threads_id_fk",
"tableFrom": "thread_candidates",
"tableTo": "threads",
"columnsFrom": [
"thread_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"thread_candidates_category_id_categories_id_fk": {
"name": "thread_candidates_category_id_categories_id_fk",
"tableFrom": "thread_candidates",
"tableTo": "categories",
"columnsFrom": [
"category_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"threads": {
"name": "threads",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'active'"
},
"resolved_candidate_id": {
"name": "resolved_candidate_id",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"category_id": {
"name": "category_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"threads_category_id_categories_id_fk": {
"name": "threads_category_id_categories_id_fk",
"tableFrom": "threads",
"tableTo": "categories",
"columnsFrom": [
"category_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"users": {
"name": "users",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"password_hash": {
"name": "password_hash",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"users_username_unique": {
"name": "users_username_unique",
"columns": [
"username"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}

View File

@@ -50,6 +50,13 @@
"when": 1775214741464,
"tag": "0006_hard_the_professor",
"breakpoints": true
},
{
"idx": 7,
"version": "6",
"when": 1775215076284,
"tag": "0007_icy_prodigy",
"breakpoints": true
}
]
}

View File

@@ -98,3 +98,30 @@ export const settings = sqliteTable("settings", {
key: text("key").primaryKey(),
value: text("value").notNull(),
});
export const users = sqliteTable("users", {
id: integer("id").primaryKey({ autoIncrement: true }),
username: text("username").notNull().unique(),
passwordHash: text("password_hash").notNull(),
createdAt: integer("created_at", { mode: "timestamp" })
.notNull()
.$defaultFn(() => new Date()),
});
export const sessions = sqliteTable("sessions", {
id: text("id").primaryKey(),
userId: integer("user_id")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
});
export const apiKeys = sqliteTable("api_keys", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
keyHash: text("key_hash").notNull(),
keyPrefix: text("key_prefix").notNull(),
createdAt: integer("created_at", { mode: "timestamp" })
.notNull()
.$defaultFn(() => new Date()),
});

View File

@@ -90,6 +90,33 @@ export function createTestDb() {
)
`);
sqlite.run(`
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
created_at INTEGER NOT NULL DEFAULT (unixepoch())
)
`);
sqlite.run(`
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
expires_at INTEGER NOT NULL
)
`);
sqlite.run(`
CREATE TABLE api_keys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
key_hash TEXT NOT NULL,
key_prefix TEXT NOT NULL,
created_at INTEGER NOT NULL DEFAULT (unixepoch())
)
`);
const db = drizzle(sqlite, { schema });
// Seed default Uncategorized category