aboutsummaryrefslogtreecommitdiffhomepage
path: root/pkgs/server
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/server')
-rw-r--r--pkgs/server/drizzle.config.ts15
-rw-r--r--pkgs/server/drizzle/0000_cynical_zeigeist.sql58
-rw-r--r--pkgs/server/drizzle/0001_spotty_jane_foster.sql9
-rw-r--r--pkgs/server/drizzle/meta/0000_snapshot.json403
-rw-r--r--pkgs/server/drizzle/meta/0001_snapshot.json462
-rw-r--r--pkgs/server/drizzle/meta/_journal.json20
-rw-r--r--pkgs/server/package.json35
-rw-r--r--pkgs/server/src/db/index.ts12
-rw-r--r--pkgs/server/src/db/schema.ts116
-rw-r--r--pkgs/server/src/index.test.ts12
-rw-r--r--pkgs/server/src/index.ts30
-rw-r--r--pkgs/server/src/middleware/auth.test.ts190
-rw-r--r--pkgs/server/src/middleware/auth.ts65
-rw-r--r--pkgs/server/src/middleware/error-handler.test.ts173
-rw-r--r--pkgs/server/src/middleware/error-handler.ts95
-rw-r--r--pkgs/server/src/middleware/index.ts2
-rw-r--r--pkgs/server/src/repositories/index.ts3
-rw-r--r--pkgs/server/src/repositories/refresh-token.ts35
-rw-r--r--pkgs/server/src/repositories/types.ts46
-rw-r--r--pkgs/server/src/repositories/user.ts55
-rw-r--r--pkgs/server/src/routes/auth.test.ts428
-rw-r--r--pkgs/server/src/routes/auth.ts199
-rw-r--r--pkgs/server/src/routes/index.ts1
-rw-r--r--pkgs/server/tsconfig.json9
-rw-r--r--pkgs/server/vitest.config.ts10
25 files changed, 0 insertions, 2483 deletions
diff --git a/pkgs/server/drizzle.config.ts b/pkgs/server/drizzle.config.ts
deleted file mode 100644
index 0e4596b..0000000
--- a/pkgs/server/drizzle.config.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { defineConfig } from "drizzle-kit";
-
-const databaseUrl = process.env.DATABASE_URL;
-if (!databaseUrl) {
- throw new Error("DATABASE_URL environment variable is not set");
-}
-
-export default defineConfig({
- out: "./drizzle",
- schema: "./src/db/schema.ts",
- dialect: "postgresql",
- dbCredentials: {
- url: databaseUrl,
- },
-});
diff --git a/pkgs/server/drizzle/0000_cynical_zeigeist.sql b/pkgs/server/drizzle/0000_cynical_zeigeist.sql
deleted file mode 100644
index 3034102..0000000
--- a/pkgs/server/drizzle/0000_cynical_zeigeist.sql
+++ /dev/null
@@ -1,58 +0,0 @@
-CREATE TABLE "cards" (
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
- "deck_id" uuid NOT NULL,
- "front" text NOT NULL,
- "back" text NOT NULL,
- "state" smallint DEFAULT 0 NOT NULL,
- "due" timestamp with time zone DEFAULT now() NOT NULL,
- "stability" real DEFAULT 0 NOT NULL,
- "difficulty" real DEFAULT 0 NOT NULL,
- "elapsed_days" integer DEFAULT 0 NOT NULL,
- "scheduled_days" integer DEFAULT 0 NOT NULL,
- "reps" integer DEFAULT 0 NOT NULL,
- "lapses" integer DEFAULT 0 NOT NULL,
- "last_review" timestamp with time zone,
- "created_at" timestamp with time zone DEFAULT now() NOT NULL,
- "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
- "deleted_at" timestamp with time zone,
- "sync_version" integer DEFAULT 0 NOT NULL
-);
---> statement-breakpoint
-CREATE TABLE "decks" (
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
- "user_id" uuid NOT NULL,
- "name" varchar(255) NOT NULL,
- "description" text,
- "new_cards_per_day" integer DEFAULT 20 NOT NULL,
- "created_at" timestamp with time zone DEFAULT now() NOT NULL,
- "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
- "deleted_at" timestamp with time zone,
- "sync_version" integer DEFAULT 0 NOT NULL
-);
---> statement-breakpoint
-CREATE TABLE "review_logs" (
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
- "card_id" uuid NOT NULL,
- "user_id" uuid NOT NULL,
- "rating" smallint NOT NULL,
- "state" smallint NOT NULL,
- "scheduled_days" integer NOT NULL,
- "elapsed_days" integer NOT NULL,
- "reviewed_at" timestamp with time zone DEFAULT now() NOT NULL,
- "duration_ms" integer,
- "sync_version" integer DEFAULT 0 NOT NULL
-);
---> statement-breakpoint
-CREATE TABLE "users" (
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
- "username" varchar(255) NOT NULL,
- "password_hash" varchar(255) NOT NULL,
- "created_at" timestamp with time zone DEFAULT now() NOT NULL,
- "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
- CONSTRAINT "users_username_unique" UNIQUE("username")
-);
---> statement-breakpoint
-ALTER TABLE "cards" ADD CONSTRAINT "cards_deck_id_decks_id_fk" FOREIGN KEY ("deck_id") REFERENCES "public"."decks"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
-ALTER TABLE "decks" ADD CONSTRAINT "decks_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
-ALTER TABLE "review_logs" ADD CONSTRAINT "review_logs_card_id_cards_id_fk" FOREIGN KEY ("card_id") REFERENCES "public"."cards"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
-ALTER TABLE "review_logs" ADD CONSTRAINT "review_logs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; \ No newline at end of file
diff --git a/pkgs/server/drizzle/0001_spotty_jane_foster.sql b/pkgs/server/drizzle/0001_spotty_jane_foster.sql
deleted file mode 100644
index 417408f..0000000
--- a/pkgs/server/drizzle/0001_spotty_jane_foster.sql
+++ /dev/null
@@ -1,9 +0,0 @@
-CREATE TABLE "refresh_tokens" (
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
- "user_id" uuid NOT NULL,
- "token_hash" varchar(255) NOT NULL,
- "expires_at" timestamp with time zone NOT NULL,
- "created_at" timestamp with time zone DEFAULT now() NOT NULL
-);
---> statement-breakpoint
-ALTER TABLE "refresh_tokens" ADD CONSTRAINT "refresh_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file
diff --git a/pkgs/server/drizzle/meta/0000_snapshot.json b/pkgs/server/drizzle/meta/0000_snapshot.json
deleted file mode 100644
index 16a3f68..0000000
--- a/pkgs/server/drizzle/meta/0000_snapshot.json
+++ /dev/null
@@ -1,403 +0,0 @@
-{
- "id": "d2f779a2-d302-4fe3-91bb-a541025dbe4a",
- "prevId": "00000000-0000-0000-0000-000000000000",
- "version": "7",
- "dialect": "postgresql",
- "tables": {
- "public.cards": {
- "name": "cards",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "deck_id": {
- "name": "deck_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "front": {
- "name": "front",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "back": {
- "name": "back",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "state": {
- "name": "state",
- "type": "smallint",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "due": {
- "name": "due",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "stability": {
- "name": "stability",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "difficulty": {
- "name": "difficulty",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "elapsed_days": {
- "name": "elapsed_days",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "scheduled_days": {
- "name": "scheduled_days",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "reps": {
- "name": "reps",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "lapses": {
- "name": "lapses",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "last_review": {
- "name": "last_review",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "deleted_at": {
- "name": "deleted_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "sync_version": {
- "name": "sync_version",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- }
- },
- "indexes": {},
- "foreignKeys": {
- "cards_deck_id_decks_id_fk": {
- "name": "cards_deck_id_decks_id_fk",
- "tableFrom": "cards",
- "tableTo": "decks",
- "columnsFrom": [
- "deck_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.decks": {
- "name": "decks",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "name": {
- "name": "name",
- "type": "varchar(255)",
- "primaryKey": false,
- "notNull": true
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "new_cards_per_day": {
- "name": "new_cards_per_day",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 20
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "deleted_at": {
- "name": "deleted_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "sync_version": {
- "name": "sync_version",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- }
- },
- "indexes": {},
- "foreignKeys": {
- "decks_user_id_users_id_fk": {
- "name": "decks_user_id_users_id_fk",
- "tableFrom": "decks",
- "tableTo": "users",
- "columnsFrom": [
- "user_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.review_logs": {
- "name": "review_logs",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "card_id": {
- "name": "card_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "rating": {
- "name": "rating",
- "type": "smallint",
- "primaryKey": false,
- "notNull": true
- },
- "state": {
- "name": "state",
- "type": "smallint",
- "primaryKey": false,
- "notNull": true
- },
- "scheduled_days": {
- "name": "scheduled_days",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "elapsed_days": {
- "name": "elapsed_days",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "reviewed_at": {
- "name": "reviewed_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "duration_ms": {
- "name": "duration_ms",
- "type": "integer",
- "primaryKey": false,
- "notNull": false
- },
- "sync_version": {
- "name": "sync_version",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- }
- },
- "indexes": {},
- "foreignKeys": {
- "review_logs_card_id_cards_id_fk": {
- "name": "review_logs_card_id_cards_id_fk",
- "tableFrom": "review_logs",
- "tableTo": "cards",
- "columnsFrom": [
- "card_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "review_logs_user_id_users_id_fk": {
- "name": "review_logs_user_id_users_id_fk",
- "tableFrom": "review_logs",
- "tableTo": "users",
- "columnsFrom": [
- "user_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.users": {
- "name": "users",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "username": {
- "name": "username",
- "type": "varchar(255)",
- "primaryKey": false,
- "notNull": true
- },
- "password_hash": {
- "name": "password_hash",
- "type": "varchar(255)",
- "primaryKey": false,
- "notNull": true
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "users_username_unique": {
- "name": "users_username_unique",
- "nullsNotDistinct": false,
- "columns": [
- "username"
- ]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- }
- },
- "enums": {},
- "schemas": {},
- "sequences": {},
- "roles": {},
- "policies": {},
- "views": {},
- "_meta": {
- "columns": {},
- "schemas": {},
- "tables": {}
- }
-} \ No newline at end of file
diff --git a/pkgs/server/drizzle/meta/0001_snapshot.json b/pkgs/server/drizzle/meta/0001_snapshot.json
deleted file mode 100644
index 55c3999..0000000
--- a/pkgs/server/drizzle/meta/0001_snapshot.json
+++ /dev/null
@@ -1,462 +0,0 @@
-{
- "id": "d3d0fb16-7e44-4217-916e-a5edc9ab7a16",
- "prevId": "d2f779a2-d302-4fe3-91bb-a541025dbe4a",
- "version": "7",
- "dialect": "postgresql",
- "tables": {
- "public.cards": {
- "name": "cards",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "deck_id": {
- "name": "deck_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "front": {
- "name": "front",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "back": {
- "name": "back",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "state": {
- "name": "state",
- "type": "smallint",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "due": {
- "name": "due",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "stability": {
- "name": "stability",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "difficulty": {
- "name": "difficulty",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "elapsed_days": {
- "name": "elapsed_days",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "scheduled_days": {
- "name": "scheduled_days",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "reps": {
- "name": "reps",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "lapses": {
- "name": "lapses",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "last_review": {
- "name": "last_review",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "deleted_at": {
- "name": "deleted_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "sync_version": {
- "name": "sync_version",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- }
- },
- "indexes": {},
- "foreignKeys": {
- "cards_deck_id_decks_id_fk": {
- "name": "cards_deck_id_decks_id_fk",
- "tableFrom": "cards",
- "tableTo": "decks",
- "columnsFrom": [
- "deck_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.decks": {
- "name": "decks",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "name": {
- "name": "name",
- "type": "varchar(255)",
- "primaryKey": false,
- "notNull": true
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "new_cards_per_day": {
- "name": "new_cards_per_day",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 20
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "deleted_at": {
- "name": "deleted_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "sync_version": {
- "name": "sync_version",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- }
- },
- "indexes": {},
- "foreignKeys": {
- "decks_user_id_users_id_fk": {
- "name": "decks_user_id_users_id_fk",
- "tableFrom": "decks",
- "tableTo": "users",
- "columnsFrom": [
- "user_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.refresh_tokens": {
- "name": "refresh_tokens",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "token_hash": {
- "name": "token_hash",
- "type": "varchar(255)",
- "primaryKey": false,
- "notNull": true
- },
- "expires_at": {
- "name": "expires_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {
- "refresh_tokens_user_id_users_id_fk": {
- "name": "refresh_tokens_user_id_users_id_fk",
- "tableFrom": "refresh_tokens",
- "tableTo": "users",
- "columnsFrom": [
- "user_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.review_logs": {
- "name": "review_logs",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "card_id": {
- "name": "card_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "rating": {
- "name": "rating",
- "type": "smallint",
- "primaryKey": false,
- "notNull": true
- },
- "state": {
- "name": "state",
- "type": "smallint",
- "primaryKey": false,
- "notNull": true
- },
- "scheduled_days": {
- "name": "scheduled_days",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "elapsed_days": {
- "name": "elapsed_days",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "reviewed_at": {
- "name": "reviewed_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "duration_ms": {
- "name": "duration_ms",
- "type": "integer",
- "primaryKey": false,
- "notNull": false
- },
- "sync_version": {
- "name": "sync_version",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- }
- },
- "indexes": {},
- "foreignKeys": {
- "review_logs_card_id_cards_id_fk": {
- "name": "review_logs_card_id_cards_id_fk",
- "tableFrom": "review_logs",
- "tableTo": "cards",
- "columnsFrom": [
- "card_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "review_logs_user_id_users_id_fk": {
- "name": "review_logs_user_id_users_id_fk",
- "tableFrom": "review_logs",
- "tableTo": "users",
- "columnsFrom": [
- "user_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.users": {
- "name": "users",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "username": {
- "name": "username",
- "type": "varchar(255)",
- "primaryKey": false,
- "notNull": true
- },
- "password_hash": {
- "name": "password_hash",
- "type": "varchar(255)",
- "primaryKey": false,
- "notNull": true
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "users_username_unique": {
- "name": "users_username_unique",
- "nullsNotDistinct": false,
- "columns": [
- "username"
- ]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- }
- },
- "enums": {},
- "schemas": {},
- "sequences": {},
- "roles": {},
- "policies": {},
- "views": {},
- "_meta": {
- "columns": {},
- "schemas": {},
- "tables": {}
- }
-} \ No newline at end of file
diff --git a/pkgs/server/drizzle/meta/_journal.json b/pkgs/server/drizzle/meta/_journal.json
deleted file mode 100644
index f245fa6..0000000
--- a/pkgs/server/drizzle/meta/_journal.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "version": "7",
- "dialect": "postgresql",
- "entries": [
- {
- "idx": 0,
- "version": "7",
- "when": 1764706245996,
- "tag": "0000_cynical_zeigeist",
- "breakpoints": true
- },
- {
- "idx": 1,
- "version": "7",
- "when": 1764708169736,
- "tag": "0001_spotty_jane_foster",
- "breakpoints": true
- }
- ]
-} \ No newline at end of file
diff --git a/pkgs/server/package.json b/pkgs/server/package.json
deleted file mode 100644
index 4159084..0000000
--- a/pkgs/server/package.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "name": "@kioku/server",
- "version": "0.1.0",
- "private": true,
- "main": "dist/index.js",
- "scripts": {
- "dev": "node --watch src/index.ts",
- "build": "tsc",
- "start": "node dist/index.js",
- "test": "vitest run",
- "test:watch": "vitest",
- "db:generate": "drizzle-kit generate",
- "db:migrate": "drizzle-kit migrate",
- "db:push": "drizzle-kit push",
- "db:studio": "drizzle-kit studio"
- },
- "author": "nsfisis",
- "license": "MIT",
- "packageManager": "pnpm@10.23.0",
- "type": "module",
- "dependencies": {
- "@hono/node-server": "^1.19.6",
- "@kioku/shared": "workspace:*",
- "argon2": "^0.44.0",
- "drizzle-orm": "^0.44.7",
- "hono": "^4.10.7",
- "pg": "^8.16.3"
- },
- "devDependencies": {
- "@types/node": "^24.10.1",
- "@types/pg": "^8.15.6",
- "drizzle-kit": "^0.31.7",
- "vitest": "^4.0.14"
- }
-}
diff --git a/pkgs/server/src/db/index.ts b/pkgs/server/src/db/index.ts
deleted file mode 100644
index 6730947..0000000
--- a/pkgs/server/src/db/index.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { drizzle } from "drizzle-orm/node-postgres";
-import * as schema from "./schema";
-
-const databaseUrl = process.env.DATABASE_URL;
-
-if (!databaseUrl) {
- throw new Error("DATABASE_URL environment variable is not set");
-}
-
-export const db = drizzle(databaseUrl, { schema });
-
-export * from "./schema";
diff --git a/pkgs/server/src/db/schema.ts b/pkgs/server/src/db/schema.ts
deleted file mode 100644
index 4b9631f..0000000
--- a/pkgs/server/src/db/schema.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-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),
-});
diff --git a/pkgs/server/src/index.test.ts b/pkgs/server/src/index.test.ts
deleted file mode 100644
index 216e965..0000000
--- a/pkgs/server/src/index.test.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { describe, expect, it } from "vitest";
-import { app } from "./index";
-
-describe("Hono app", () => {
- describe("GET /api/health", () => {
- it("returns ok status", async () => {
- const res = await app.request("/api/health");
- expect(res.status).toBe(200);
- expect(await res.json()).toEqual({ status: "ok" });
- });
- });
-});
diff --git a/pkgs/server/src/index.ts b/pkgs/server/src/index.ts
deleted file mode 100644
index a0ae0a4..0000000
--- a/pkgs/server/src/index.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { serve } from "@hono/node-server";
-import { Hono } from "hono";
-import { logger } from "hono/logger";
-import { errorHandler } from "./middleware";
-import { auth } from "./routes";
-
-const app = new Hono();
-
-app.use("*", logger());
-app.onError(errorHandler);
-
-app.get("/", (c) => {
- return c.json({ message: "Kioku API" });
-});
-
-app.get("/api/health", (c) => {
- return c.json({ status: "ok" });
-});
-
-app.route("/api/auth", auth);
-
-const port = Number(process.env.PORT) || 3000;
-console.log(`Server is running on port ${port}`);
-
-serve({
- fetch: app.fetch,
- port,
-});
-
-export { app };
diff --git a/pkgs/server/src/middleware/auth.test.ts b/pkgs/server/src/middleware/auth.test.ts
deleted file mode 100644
index 8c4286b..0000000
--- a/pkgs/server/src/middleware/auth.test.ts
+++ /dev/null
@@ -1,190 +0,0 @@
-import { Hono } from "hono";
-import { sign } from "hono/jwt";
-import { beforeEach, describe, expect, it } from "vitest";
-import { authMiddleware, getAuthUser } from "./auth";
-import { errorHandler } from "./error-handler";
-
-const JWT_SECRET = process.env.JWT_SECRET || "test-secret";
-
-interface ErrorResponse {
- error: {
- code: string;
- message: string;
- };
-}
-
-interface SuccessResponse {
- userId: string;
-}
-
-describe("authMiddleware", () => {
- let app: Hono;
-
- beforeEach(() => {
- app = new Hono();
- app.onError(errorHandler);
- app.use("/protected/*", authMiddleware);
- app.get("/protected/resource", (c) => {
- const user = getAuthUser(c);
- return c.json({ userId: user.id });
- });
- });
-
- it("allows access with valid token", async () => {
- const now = Math.floor(Date.now() / 1000);
- const token = await sign(
- {
- sub: "user-123",
- iat: now,
- exp: now + 3600,
- },
- JWT_SECRET,
- );
-
- const res = await app.request("/protected/resource", {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- });
-
- expect(res.status).toBe(200);
- const body = (await res.json()) as SuccessResponse;
- expect(body.userId).toBe("user-123");
- });
-
- it("returns 401 when Authorization header is missing", async () => {
- const res = await app.request("/protected/resource");
-
- expect(res.status).toBe(401);
- const body = (await res.json()) as ErrorResponse;
- expect(body.error.code).toBe("MISSING_AUTH");
- });
-
- it("returns 401 for invalid Authorization format", async () => {
- const res = await app.request("/protected/resource", {
- headers: {
- Authorization: "Basic sometoken",
- },
- });
-
- expect(res.status).toBe(401);
- const body = (await res.json()) as ErrorResponse;
- expect(body.error.code).toBe("INVALID_AUTH_FORMAT");
- });
-
- it("returns 401 for empty Bearer token", async () => {
- const res = await app.request("/protected/resource", {
- headers: {
- Authorization: "Bearer",
- },
- });
-
- expect(res.status).toBe(401);
- const body = (await res.json()) as ErrorResponse;
- expect(body.error.code).toBe("INVALID_AUTH_FORMAT");
- });
-
- it("returns 401 for expired token", async () => {
- const now = Math.floor(Date.now() / 1000);
- const token = await sign(
- {
- sub: "user-123",
- iat: now - 7200,
- exp: now - 3600, // expired 1 hour ago
- },
- JWT_SECRET,
- );
-
- const res = await app.request("/protected/resource", {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- });
-
- expect(res.status).toBe(401);
- const body = (await res.json()) as ErrorResponse;
- expect(body.error.code).toBe("INVALID_TOKEN");
- });
-
- it("returns 401 for invalid token", async () => {
- const res = await app.request("/protected/resource", {
- headers: {
- Authorization: "Bearer invalid.token.here",
- },
- });
-
- expect(res.status).toBe(401);
- const body = (await res.json()) as ErrorResponse;
- expect(body.error.code).toBe("INVALID_TOKEN");
- });
-
- it("returns 401 for token signed with wrong secret", async () => {
- const now = Math.floor(Date.now() / 1000);
- const token = await sign(
- {
- sub: "user-123",
- iat: now,
- exp: now + 3600,
- },
- "wrong-secret",
- );
-
- const res = await app.request("/protected/resource", {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- });
-
- expect(res.status).toBe(401);
- const body = (await res.json()) as ErrorResponse;
- expect(body.error.code).toBe("INVALID_TOKEN");
- });
-});
-
-describe("getAuthUser", () => {
- it("returns user from context when authenticated", async () => {
- const app = new Hono();
- app.onError(errorHandler);
- app.use("/test", authMiddleware);
- app.get("/test", (c) => {
- const user = getAuthUser(c);
- return c.json({ id: user.id });
- });
-
- const now = Math.floor(Date.now() / 1000);
- const token = await sign(
- {
- sub: "test-user-456",
- iat: now,
- exp: now + 3600,
- },
- JWT_SECRET,
- );
-
- const res = await app.request("/test", {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- });
-
- expect(res.status).toBe(200);
- const body = (await res.json()) as { id: string };
- expect(body.id).toBe("test-user-456");
- });
-
- it("throws when user is not in context", async () => {
- const app = new Hono();
- app.onError(errorHandler);
- // Note: no authMiddleware applied
- app.get("/unprotected", (c) => {
- const user = getAuthUser(c);
- return c.json({ id: user.id });
- });
-
- const res = await app.request("/unprotected");
-
- expect(res.status).toBe(401);
- const body = (await res.json()) as ErrorResponse;
- expect(body.error.code).toBe("NOT_AUTHENTICATED");
- });
-});
diff --git a/pkgs/server/src/middleware/auth.ts b/pkgs/server/src/middleware/auth.ts
deleted file mode 100644
index c295834..0000000
--- a/pkgs/server/src/middleware/auth.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import type { Context, Next } from "hono";
-import { verify } from "hono/jwt";
-import { Errors } from "./error-handler";
-
-const JWT_SECRET = process.env.JWT_SECRET;
-if (!JWT_SECRET) {
- throw new Error("JWT_SECRET environment variable is required");
-}
-
-export interface AuthUser {
- id: string;
-}
-
-interface JWTPayload {
- sub: string;
- iat: number;
- exp: number;
-}
-
-/**
- * Auth middleware that validates JWT tokens from Authorization header
- * Sets the authenticated user in context variables
- */
-export async function authMiddleware(c: Context, next: Next) {
- const authHeader = c.req.header("Authorization");
-
- if (!authHeader) {
- throw Errors.unauthorized("Missing Authorization header", "MISSING_AUTH");
- }
-
- if (!authHeader.startsWith("Bearer ")) {
- throw Errors.unauthorized(
- "Invalid Authorization header format",
- "INVALID_AUTH_FORMAT",
- );
- }
-
- const token = authHeader.slice(7);
-
- try {
- const payload = (await verify(token, JWT_SECRET)) as unknown as JWTPayload;
-
- const user: AuthUser = {
- id: payload.sub,
- };
-
- c.set("user", user);
-
- await next();
- } catch {
- throw Errors.unauthorized("Invalid or expired token", "INVALID_TOKEN");
- }
-}
-
-/**
- * Helper function to get the authenticated user from context
- * Throws if user is not authenticated
- */
-export function getAuthUser(c: Context): AuthUser {
- const user = c.get("user") as AuthUser | undefined;
- if (!user) {
- throw Errors.unauthorized("Not authenticated", "NOT_AUTHENTICATED");
- }
- return user;
-}
diff --git a/pkgs/server/src/middleware/error-handler.test.ts b/pkgs/server/src/middleware/error-handler.test.ts
deleted file mode 100644
index 21d6fc1..0000000
--- a/pkgs/server/src/middleware/error-handler.test.ts
+++ /dev/null
@@ -1,173 +0,0 @@
-import { Hono } from "hono";
-import { describe, expect, it } from "vitest";
-import { AppError, Errors, errorHandler } from "./error-handler";
-
-function createTestApp() {
- const app = new Hono();
- app.onError(errorHandler);
- return app;
-}
-
-describe("errorHandler", () => {
- describe("AppError handling", () => {
- it("returns correct status and message for AppError", async () => {
- const app = createTestApp();
- app.get("/test", () => {
- throw new AppError("Custom error", 400, "CUSTOM_ERROR");
- });
-
- const res = await app.request("/test");
- expect(res.status).toBe(400);
- expect(await res.json()).toEqual({
- error: {
- message: "Custom error",
- code: "CUSTOM_ERROR",
- },
- });
- });
-
- it("uses default values for AppError", async () => {
- const app = createTestApp();
- app.get("/test", () => {
- throw new AppError("Something went wrong");
- });
-
- const res = await app.request("/test");
- expect(res.status).toBe(500);
- expect(await res.json()).toEqual({
- error: {
- message: "Something went wrong",
- code: "INTERNAL_ERROR",
- },
- });
- });
- });
-
- describe("Errors factory functions", () => {
- it("handles badRequest error", async () => {
- const app = createTestApp();
- app.get("/test", () => {
- throw Errors.badRequest("Invalid input");
- });
-
- const res = await app.request("/test");
- expect(res.status).toBe(400);
- expect(await res.json()).toEqual({
- error: {
- message: "Invalid input",
- code: "BAD_REQUEST",
- },
- });
- });
-
- it("handles unauthorized error", async () => {
- const app = createTestApp();
- app.get("/test", () => {
- throw Errors.unauthorized();
- });
-
- const res = await app.request("/test");
- expect(res.status).toBe(401);
- expect(await res.json()).toEqual({
- error: {
- message: "Unauthorized",
- code: "UNAUTHORIZED",
- },
- });
- });
-
- it("handles forbidden error", async () => {
- const app = createTestApp();
- app.get("/test", () => {
- throw Errors.forbidden("Access denied");
- });
-
- const res = await app.request("/test");
- expect(res.status).toBe(403);
- expect(await res.json()).toEqual({
- error: {
- message: "Access denied",
- code: "FORBIDDEN",
- },
- });
- });
-
- it("handles notFound error", async () => {
- const app = createTestApp();
- app.get("/test", () => {
- throw Errors.notFound("Resource not found");
- });
-
- const res = await app.request("/test");
- expect(res.status).toBe(404);
- expect(await res.json()).toEqual({
- error: {
- message: "Resource not found",
- code: "NOT_FOUND",
- },
- });
- });
-
- it("handles conflict error", async () => {
- const app = createTestApp();
- app.get("/test", () => {
- throw Errors.conflict("Already exists");
- });
-
- const res = await app.request("/test");
- expect(res.status).toBe(409);
- expect(await res.json()).toEqual({
- error: {
- message: "Already exists",
- code: "CONFLICT",
- },
- });
- });
-
- it("handles validationError", async () => {
- const app = createTestApp();
- app.get("/test", () => {
- throw Errors.validationError("Invalid data");
- });
-
- const res = await app.request("/test");
- expect(res.status).toBe(422);
- expect(await res.json()).toEqual({
- error: {
- message: "Invalid data",
- code: "VALIDATION_ERROR",
- },
- });
- });
-
- it("handles internal error", async () => {
- const app = createTestApp();
- app.get("/test", () => {
- throw Errors.internal("Database connection failed");
- });
-
- const res = await app.request("/test");
- expect(res.status).toBe(500);
- expect(await res.json()).toEqual({
- error: {
- message: "Database connection failed",
- code: "INTERNAL_ERROR",
- },
- });
- });
- });
-
- describe("unknown error handling", () => {
- it("handles generic Error with 500 status", async () => {
- const app = createTestApp();
- app.get("/test", () => {
- throw new Error("Unexpected error");
- });
-
- const res = await app.request("/test");
- expect(res.status).toBe(500);
- const body = (await res.json()) as { error: { code: string } };
- expect(body.error.code).toBe("INTERNAL_ERROR");
- });
- });
-});
diff --git a/pkgs/server/src/middleware/error-handler.ts b/pkgs/server/src/middleware/error-handler.ts
deleted file mode 100644
index 7b92940..0000000
--- a/pkgs/server/src/middleware/error-handler.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import type { Context } from "hono";
-import { HTTPException } from "hono/http-exception";
-import type { ContentfulStatusCode } from "hono/utils/http-status";
-
-/**
- * Application-specific error with status code and optional details
- */
-export class AppError extends Error {
- readonly statusCode: ContentfulStatusCode;
- readonly code: string;
-
- constructor(
- message: string,
- statusCode: ContentfulStatusCode = 500,
- code = "INTERNAL_ERROR",
- ) {
- super(message);
- this.name = "AppError";
- this.statusCode = statusCode;
- this.code = code;
- }
-}
-
-/**
- * Common error factory functions
- */
-export const Errors = {
- badRequest: (message = "Bad request", code = "BAD_REQUEST") =>
- new AppError(message, 400, code),
-
- unauthorized: (message = "Unauthorized", code = "UNAUTHORIZED") =>
- new AppError(message, 401, code),
-
- forbidden: (message = "Forbidden", code = "FORBIDDEN") =>
- new AppError(message, 403, code),
-
- notFound: (message = "Not found", code = "NOT_FOUND") =>
- new AppError(message, 404, code),
-
- conflict: (message = "Conflict", code = "CONFLICT") =>
- new AppError(message, 409, code),
-
- validationError: (message = "Validation failed", code = "VALIDATION_ERROR") =>
- new AppError(message, 422, code),
-
- internal: (message = "Internal server error", code = "INTERNAL_ERROR") =>
- new AppError(message, 500, code),
-};
-
-interface ErrorResponse {
- error: {
- message: string;
- code: string;
- };
-}
-
-/**
- * Global error handler middleware for Hono
- */
-export function errorHandler(err: Error, c: Context): Response {
- // Handle AppError
- if (err instanceof AppError) {
- const response: ErrorResponse = {
- error: {
- message: err.message,
- code: err.code,
- },
- };
- return c.json(response, err.statusCode);
- }
-
- // Handle Hono's HTTPException
- if (err instanceof HTTPException) {
- const response: ErrorResponse = {
- error: {
- message: err.message,
- code: "HTTP_ERROR",
- },
- };
- return c.json(response, err.status as ContentfulStatusCode);
- }
-
- // Handle unknown errors
- console.error("Unhandled error:", err);
- const response: ErrorResponse = {
- error: {
- message:
- process.env.NODE_ENV === "production"
- ? "Internal server error"
- : err.message,
- code: "INTERNAL_ERROR",
- },
- };
- return c.json(response, 500);
-}
diff --git a/pkgs/server/src/middleware/index.ts b/pkgs/server/src/middleware/index.ts
deleted file mode 100644
index 57de4dd..0000000
--- a/pkgs/server/src/middleware/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { type AuthUser, authMiddleware, getAuthUser } from "./auth";
-export { AppError, Errors, errorHandler } from "./error-handler";
diff --git a/pkgs/server/src/repositories/index.ts b/pkgs/server/src/repositories/index.ts
deleted file mode 100644
index f1bcfb1..0000000
--- a/pkgs/server/src/repositories/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export { refreshTokenRepository } from "./refresh-token";
-export * from "./types";
-export { userRepository } from "./user";
diff --git a/pkgs/server/src/repositories/refresh-token.ts b/pkgs/server/src/repositories/refresh-token.ts
deleted file mode 100644
index 82302df..0000000
--- a/pkgs/server/src/repositories/refresh-token.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { and, eq, gt } from "drizzle-orm";
-import { db, refreshTokens } from "../db";
-import type { RefreshTokenRepository } from "./types";
-
-export const refreshTokenRepository: RefreshTokenRepository = {
- async findValidToken(tokenHash) {
- const [token] = await db
- .select({
- id: refreshTokens.id,
- userId: refreshTokens.userId,
- expiresAt: refreshTokens.expiresAt,
- })
- .from(refreshTokens)
- .where(
- and(
- eq(refreshTokens.tokenHash, tokenHash),
- gt(refreshTokens.expiresAt, new Date()),
- ),
- )
- .limit(1);
- return token;
- },
-
- async create(data) {
- await db.insert(refreshTokens).values({
- userId: data.userId,
- tokenHash: data.tokenHash,
- expiresAt: data.expiresAt,
- });
- },
-
- async deleteById(id) {
- await db.delete(refreshTokens).where(eq(refreshTokens.id, id));
- },
-};
diff --git a/pkgs/server/src/repositories/types.ts b/pkgs/server/src/repositories/types.ts
deleted file mode 100644
index 1ab4bdc..0000000
--- a/pkgs/server/src/repositories/types.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * Repository types for abstracting database operations
- */
-
-export interface User {
- id: string;
- username: string;
- passwordHash: string;
- createdAt: Date;
- updatedAt: Date;
-}
-
-export interface UserPublic {
- id: string;
- username: string;
- createdAt: Date;
-}
-
-export interface RefreshToken {
- id: string;
- userId: string;
- tokenHash: string;
- expiresAt: Date;
- createdAt: Date;
-}
-
-export interface UserRepository {
- findByUsername(
- username: string,
- ): Promise<Pick<User, "id" | "username" | "passwordHash"> | undefined>;
- existsByUsername(username: string): Promise<boolean>;
- create(data: { username: string; passwordHash: string }): Promise<UserPublic>;
- findById(id: string): Promise<Pick<User, "id" | "username"> | undefined>;
-}
-
-export interface RefreshTokenRepository {
- findValidToken(
- tokenHash: string,
- ): Promise<Pick<RefreshToken, "id" | "userId" | "expiresAt"> | undefined>;
- create(data: {
- userId: string;
- tokenHash: string;
- expiresAt: Date;
- }): Promise<void>;
- deleteById(id: string): Promise<void>;
-}
diff --git a/pkgs/server/src/repositories/user.ts b/pkgs/server/src/repositories/user.ts
deleted file mode 100644
index 7917632..0000000
--- a/pkgs/server/src/repositories/user.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { eq } from "drizzle-orm";
-import { db, users } from "../db";
-import type { UserPublic, UserRepository } from "./types";
-
-export const userRepository: UserRepository = {
- async findByUsername(username) {
- const [user] = await db
- .select({
- id: users.id,
- username: users.username,
- passwordHash: users.passwordHash,
- })
- .from(users)
- .where(eq(users.username, username))
- .limit(1);
- return user;
- },
-
- async existsByUsername(username) {
- const [user] = await db
- .select({ id: users.id })
- .from(users)
- .where(eq(users.username, username))
- .limit(1);
- return user !== undefined;
- },
-
- async create(data): Promise<UserPublic> {
- const [newUser] = await db
- .insert(users)
- .values({
- username: data.username,
- passwordHash: data.passwordHash,
- })
- .returning({
- id: users.id,
- username: users.username,
- createdAt: users.createdAt,
- });
- // Insert with returning should always return the created row
- return newUser!;
- },
-
- async findById(id) {
- const [user] = await db
- .select({
- id: users.id,
- username: users.username,
- })
- .from(users)
- .where(eq(users.id, id))
- .limit(1);
- return user;
- },
-};
diff --git a/pkgs/server/src/routes/auth.test.ts b/pkgs/server/src/routes/auth.test.ts
deleted file mode 100644
index 34eb2b6..0000000
--- a/pkgs/server/src/routes/auth.test.ts
+++ /dev/null
@@ -1,428 +0,0 @@
-import { Hono } from "hono";
-import { beforeEach, describe, expect, it, vi } from "vitest";
-import { errorHandler } from "../middleware";
-import type {
- RefreshTokenRepository,
- UserPublic,
- UserRepository,
-} from "../repositories";
-import { createAuthRouter } from "./auth";
-
-vi.mock("argon2", () => ({
- hash: vi.fn((password: string) => Promise.resolve(`hashed_${password}`)),
- verify: vi.fn((hash: string, password: string) =>
- Promise.resolve(hash === `hashed_${password}`),
- ),
-}));
-
-function createMockUserRepo(): UserRepository {
- return {
- findByUsername: vi.fn(),
- existsByUsername: vi.fn(),
- create: vi.fn(),
- findById: vi.fn(),
- };
-}
-
-function createMockRefreshTokenRepo(): RefreshTokenRepository {
- return {
- findValidToken: vi.fn(),
- create: vi.fn(),
- deleteById: vi.fn(),
- };
-}
-
-interface RegisterResponse {
- user?: {
- id: string;
- username: string;
- createdAt: string;
- };
- error?: {
- code: string;
- message: string;
- };
-}
-
-interface LoginResponse {
- accessToken?: string;
- refreshToken?: string;
- user?: {
- id: string;
- username: string;
- };
- error?: {
- code: string;
- message: string;
- };
-}
-
-describe("POST /register", () => {
- let app: Hono;
- let mockUserRepo: ReturnType<typeof createMockUserRepo>;
- let mockRefreshTokenRepo: ReturnType<typeof createMockRefreshTokenRepo>;
-
- beforeEach(() => {
- vi.clearAllMocks();
- mockUserRepo = createMockUserRepo();
- mockRefreshTokenRepo = createMockRefreshTokenRepo();
- const auth = createAuthRouter({
- userRepo: mockUserRepo,
- refreshTokenRepo: mockRefreshTokenRepo,
- });
- app = new Hono();
- app.onError(errorHandler);
- app.route("/api/auth", auth);
- });
-
- it("creates a new user with valid credentials", async () => {
- vi.mocked(mockUserRepo.existsByUsername).mockResolvedValue(false);
- vi.mocked(mockUserRepo.create).mockResolvedValue({
- id: "test-uuid-123",
- username: "testuser",
- createdAt: new Date("2024-01-01T00:00:00Z"),
- } as UserPublic);
-
- const res = await app.request("/api/auth/register", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- username: "testuser",
- password: "securepassword12345",
- }),
- });
-
- expect(res.status).toBe(201);
- const body = (await res.json()) as RegisterResponse;
- expect(body.user).toEqual({
- id: "test-uuid-123",
- username: "testuser",
- createdAt: "2024-01-01T00:00:00.000Z",
- });
- expect(mockUserRepo.existsByUsername).toHaveBeenCalledWith("testuser");
- expect(mockUserRepo.create).toHaveBeenCalledWith({
- username: "testuser",
- passwordHash: "hashed_securepassword12345",
- });
- });
-
- it("returns 422 for invalid username", async () => {
- const res = await app.request("/api/auth/register", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- username: "",
- password: "securepassword12345",
- }),
- });
-
- expect(res.status).toBe(422);
- const body = (await res.json()) as RegisterResponse;
- expect(body.error?.code).toBe("VALIDATION_ERROR");
- });
-
- it("returns 422 for password too short", async () => {
- const res = await app.request("/api/auth/register", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- username: "testuser",
- password: "tooshort123456",
- }),
- });
-
- expect(res.status).toBe(422);
- const body = (await res.json()) as RegisterResponse;
- expect(body.error?.code).toBe("VALIDATION_ERROR");
- });
-
- it("returns 409 for existing username", async () => {
- vi.mocked(mockUserRepo.existsByUsername).mockResolvedValue(true);
-
- const res = await app.request("/api/auth/register", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- username: "existinguser",
- password: "securepassword12345",
- }),
- });
-
- expect(res.status).toBe(409);
- const body = (await res.json()) as RegisterResponse;
- expect(body.error?.code).toBe("USERNAME_EXISTS");
- });
-});
-
-describe("POST /login", () => {
- let app: Hono;
- let mockUserRepo: ReturnType<typeof createMockUserRepo>;
- let mockRefreshTokenRepo: ReturnType<typeof createMockRefreshTokenRepo>;
-
- beforeEach(() => {
- vi.clearAllMocks();
- mockUserRepo = createMockUserRepo();
- mockRefreshTokenRepo = createMockRefreshTokenRepo();
- const auth = createAuthRouter({
- userRepo: mockUserRepo,
- refreshTokenRepo: mockRefreshTokenRepo,
- });
- app = new Hono();
- app.onError(errorHandler);
- app.route("/api/auth", auth);
- });
-
- it("returns access token for valid credentials", async () => {
- vi.mocked(mockUserRepo.findByUsername).mockResolvedValue({
- id: "user-uuid-123",
- username: "testuser",
- passwordHash: "hashed_correctpassword",
- });
- vi.mocked(mockRefreshTokenRepo.create).mockResolvedValue(undefined);
-
- const res = await app.request("/api/auth/login", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- username: "testuser",
- password: "correctpassword",
- }),
- });
-
- expect(res.status).toBe(200);
- const body = (await res.json()) as LoginResponse;
- expect(body.accessToken).toBeDefined();
- expect(typeof body.accessToken).toBe("string");
- expect(body.refreshToken).toBeDefined();
- expect(typeof body.refreshToken).toBe("string");
- expect(body.user).toEqual({
- id: "user-uuid-123",
- username: "testuser",
- });
- expect(mockRefreshTokenRepo.create).toHaveBeenCalledWith({
- userId: "user-uuid-123",
- tokenHash: expect.any(String),
- expiresAt: expect.any(Date),
- });
- });
-
- it("returns 401 for non-existent user", async () => {
- vi.mocked(mockUserRepo.findByUsername).mockResolvedValue(undefined);
-
- const res = await app.request("/api/auth/login", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- username: "nonexistent",
- password: "anypassword",
- }),
- });
-
- expect(res.status).toBe(401);
- const body = (await res.json()) as LoginResponse;
- expect(body.error?.code).toBe("INVALID_CREDENTIALS");
- });
-
- it("returns 401 for incorrect password", async () => {
- vi.mocked(mockUserRepo.findByUsername).mockResolvedValue({
- id: "user-uuid-123",
- username: "testuser",
- passwordHash: "hashed_correctpassword",
- });
-
- const res = await app.request("/api/auth/login", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- username: "testuser",
- password: "wrongpassword",
- }),
- });
-
- expect(res.status).toBe(401);
- const body = (await res.json()) as LoginResponse;
- expect(body.error?.code).toBe("INVALID_CREDENTIALS");
- });
-
- it("returns 422 for missing username", async () => {
- const res = await app.request("/api/auth/login", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- username: "",
- password: "somepassword",
- }),
- });
-
- expect(res.status).toBe(422);
- const body = (await res.json()) as LoginResponse;
- expect(body.error?.code).toBe("VALIDATION_ERROR");
- });
-
- it("returns 422 for missing password", async () => {
- const res = await app.request("/api/auth/login", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- username: "testuser",
- password: "",
- }),
- });
-
- expect(res.status).toBe(422);
- const body = (await res.json()) as LoginResponse;
- expect(body.error?.code).toBe("VALIDATION_ERROR");
- });
-});
-
-interface RefreshResponse {
- accessToken?: string;
- refreshToken?: string;
- user?: {
- id: string;
- username: string;
- };
- error?: {
- code: string;
- message: string;
- };
-}
-
-describe("POST /refresh", () => {
- let app: Hono;
- let mockUserRepo: ReturnType<typeof createMockUserRepo>;
- let mockRefreshTokenRepo: ReturnType<typeof createMockRefreshTokenRepo>;
-
- beforeEach(() => {
- vi.clearAllMocks();
- mockUserRepo = createMockUserRepo();
- mockRefreshTokenRepo = createMockRefreshTokenRepo();
- const auth = createAuthRouter({
- userRepo: mockUserRepo,
- refreshTokenRepo: mockRefreshTokenRepo,
- });
- app = new Hono();
- app.onError(errorHandler);
- app.route("/api/auth", auth);
- });
-
- it("returns new tokens for valid refresh token", async () => {
- vi.mocked(mockRefreshTokenRepo.findValidToken).mockResolvedValue({
- id: "token-id-123",
- userId: "user-uuid-123",
- expiresAt: new Date(Date.now() + 86400000),
- });
- vi.mocked(mockUserRepo.findById).mockResolvedValue({
- id: "user-uuid-123",
- username: "testuser",
- });
- vi.mocked(mockRefreshTokenRepo.deleteById).mockResolvedValue(undefined);
- vi.mocked(mockRefreshTokenRepo.create).mockResolvedValue(undefined);
-
- const res = await app.request("/api/auth/refresh", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- refreshToken: "valid-refresh-token-hex",
- }),
- });
-
- expect(res.status).toBe(200);
- const body = (await res.json()) as RefreshResponse;
- expect(body.accessToken).toBeDefined();
- expect(typeof body.accessToken).toBe("string");
- expect(body.refreshToken).toBeDefined();
- expect(typeof body.refreshToken).toBe("string");
- expect(body.user).toEqual({
- id: "user-uuid-123",
- username: "testuser",
- });
- expect(mockRefreshTokenRepo.deleteById).toHaveBeenCalledWith(
- "token-id-123",
- );
- expect(mockRefreshTokenRepo.create).toHaveBeenCalledWith({
- userId: "user-uuid-123",
- tokenHash: expect.any(String),
- expiresAt: expect.any(Date),
- });
- });
-
- it("returns 401 for invalid refresh token", async () => {
- vi.mocked(mockRefreshTokenRepo.findValidToken).mockResolvedValue(undefined);
-
- const res = await app.request("/api/auth/refresh", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- refreshToken: "invalid-refresh-token",
- }),
- });
-
- expect(res.status).toBe(401);
- const body = (await res.json()) as RefreshResponse;
- expect(body.error?.code).toBe("INVALID_REFRESH_TOKEN");
- });
-
- it("returns 401 for expired refresh token", async () => {
- vi.mocked(mockRefreshTokenRepo.findValidToken).mockResolvedValue(undefined);
-
- const res = await app.request("/api/auth/refresh", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- refreshToken: "expired-refresh-token",
- }),
- });
-
- expect(res.status).toBe(401);
- const body = (await res.json()) as RefreshResponse;
- expect(body.error?.code).toBe("INVALID_REFRESH_TOKEN");
- });
-
- it("returns 401 when user not found", async () => {
- vi.mocked(mockRefreshTokenRepo.findValidToken).mockResolvedValue({
- id: "token-id-123",
- userId: "deleted-user-id",
- expiresAt: new Date(Date.now() + 86400000),
- });
- vi.mocked(mockUserRepo.findById).mockResolvedValue(undefined);
-
- const res = await app.request("/api/auth/refresh", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- refreshToken: "valid-refresh-token",
- }),
- });
-
- expect(res.status).toBe(401);
- const body = (await res.json()) as RefreshResponse;
- expect(body.error?.code).toBe("USER_NOT_FOUND");
- });
-
- it("returns 422 for missing refresh token", async () => {
- const res = await app.request("/api/auth/refresh", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({}),
- });
-
- expect(res.status).toBe(422);
- const body = (await res.json()) as RefreshResponse;
- expect(body.error?.code).toBe("VALIDATION_ERROR");
- });
-
- it("returns 422 for empty refresh token", async () => {
- const res = await app.request("/api/auth/refresh", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- refreshToken: "",
- }),
- });
-
- expect(res.status).toBe(422);
- const body = (await res.json()) as RefreshResponse;
- expect(body.error?.code).toBe("VALIDATION_ERROR");
- });
-});
diff --git a/pkgs/server/src/routes/auth.ts b/pkgs/server/src/routes/auth.ts
deleted file mode 100644
index e1f7ebb..0000000
--- a/pkgs/server/src/routes/auth.ts
+++ /dev/null
@@ -1,199 +0,0 @@
-import { createHash, randomBytes } from "node:crypto";
-import {
- createUserSchema,
- loginSchema,
- refreshTokenSchema,
-} from "@kioku/shared";
-import * as argon2 from "argon2";
-import { Hono } from "hono";
-import { sign } from "hono/jwt";
-import { Errors } from "../middleware";
-import {
- type RefreshTokenRepository,
- refreshTokenRepository,
- type UserRepository,
- userRepository,
-} from "../repositories";
-
-const JWT_SECRET = process.env.JWT_SECRET;
-if (!JWT_SECRET) {
- throw new Error("JWT_SECRET environment variable is required");
-}
-const ACCESS_TOKEN_EXPIRES_IN = 60 * 15; // 15 minutes
-const REFRESH_TOKEN_EXPIRES_IN = 60 * 60 * 24 * 7; // 7 days
-
-function generateRefreshToken(): string {
- return randomBytes(32).toString("hex");
-}
-
-function hashToken(token: string): string {
- return createHash("sha256").update(token).digest("hex");
-}
-
-export interface AuthDependencies {
- userRepo: UserRepository;
- refreshTokenRepo: RefreshTokenRepository;
-}
-
-export function createAuthRouter(deps: AuthDependencies) {
- const { userRepo, refreshTokenRepo } = deps;
- const auth = new Hono();
-
- auth.post("/register", async (c) => {
- const body = await c.req.json();
-
- const parsed = createUserSchema.safeParse(body);
- if (!parsed.success) {
- throw Errors.validationError(parsed.error.issues[0]?.message);
- }
-
- const { username, password } = parsed.data;
-
- // Check if username already exists
- const exists = await userRepo.existsByUsername(username);
- if (exists) {
- throw Errors.conflict("Username already exists", "USERNAME_EXISTS");
- }
-
- // Hash password with Argon2
- const passwordHash = await argon2.hash(password);
-
- // Create user
- const newUser = await userRepo.create({ username, passwordHash });
-
- return c.json({ user: newUser }, 201);
- });
-
- auth.post("/login", async (c) => {
- const body = await c.req.json();
-
- const parsed = loginSchema.safeParse(body);
- if (!parsed.success) {
- throw Errors.validationError(parsed.error.issues[0]?.message);
- }
-
- const { username, password } = parsed.data;
-
- // Find user by username
- const user = await userRepo.findByUsername(username);
-
- if (!user) {
- throw Errors.unauthorized(
- "Invalid username or password",
- "INVALID_CREDENTIALS",
- );
- }
-
- // Verify password
- const isPasswordValid = await argon2.verify(user.passwordHash, password);
- if (!isPasswordValid) {
- throw Errors.unauthorized(
- "Invalid username or password",
- "INVALID_CREDENTIALS",
- );
- }
-
- // Generate JWT access token
- const now = Math.floor(Date.now() / 1000);
- const accessToken = await sign(
- {
- sub: user.id,
- iat: now,
- exp: now + ACCESS_TOKEN_EXPIRES_IN,
- },
- JWT_SECRET,
- );
-
- // Generate refresh token
- const refreshToken = generateRefreshToken();
- const tokenHash = hashToken(refreshToken);
- const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRES_IN * 1000);
-
- // Store refresh token in database
- await refreshTokenRepo.create({
- userId: user.id,
- tokenHash,
- expiresAt,
- });
-
- return c.json({
- accessToken,
- refreshToken,
- user: {
- id: user.id,
- username: user.username,
- },
- });
- });
-
- auth.post("/refresh", async (c) => {
- const body = await c.req.json();
-
- const parsed = refreshTokenSchema.safeParse(body);
- if (!parsed.success) {
- throw Errors.validationError(parsed.error.issues[0]?.message);
- }
-
- const { refreshToken } = parsed.data;
- const tokenHash = hashToken(refreshToken);
-
- // Find valid refresh token
- const storedToken = await refreshTokenRepo.findValidToken(tokenHash);
-
- if (!storedToken) {
- throw Errors.unauthorized(
- "Invalid or expired refresh token",
- "INVALID_REFRESH_TOKEN",
- );
- }
-
- // Get user info
- const user = await userRepo.findById(storedToken.userId);
-
- if (!user) {
- throw Errors.unauthorized("User not found", "USER_NOT_FOUND");
- }
-
- // Delete old refresh token (rotation)
- await refreshTokenRepo.deleteById(storedToken.id);
-
- // Generate new access token
- const now = Math.floor(Date.now() / 1000);
- const accessToken = await sign(
- {
- sub: user.id,
- iat: now,
- exp: now + ACCESS_TOKEN_EXPIRES_IN,
- },
- JWT_SECRET,
- );
-
- // Generate new refresh token (rotation)
- const newRefreshToken = generateRefreshToken();
- const newTokenHash = hashToken(newRefreshToken);
- const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRES_IN * 1000);
-
- await refreshTokenRepo.create({
- userId: user.id,
- tokenHash: newTokenHash,
- expiresAt,
- });
-
- return c.json({
- accessToken,
- refreshToken: newRefreshToken,
- user: {
- id: user.id,
- username: user.username,
- },
- });
- });
-
- return auth;
-}
-
-// Default auth router with real repositories for production use
-export const auth = createAuthRouter({
- userRepo: userRepository,
- refreshTokenRepo: refreshTokenRepository,
-});
diff --git a/pkgs/server/src/routes/index.ts b/pkgs/server/src/routes/index.ts
deleted file mode 100644
index 2925e6d..0000000
--- a/pkgs/server/src/routes/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { auth } from "./auth";
diff --git a/pkgs/server/tsconfig.json b/pkgs/server/tsconfig.json
deleted file mode 100644
index 038af8a..0000000
--- a/pkgs/server/tsconfig.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "outDir": "./dist",
- "rootDir": "./src"
- },
- "include": ["src/**/*"],
- "exclude": ["node_modules", "dist"]
-}
diff --git a/pkgs/server/vitest.config.ts b/pkgs/server/vitest.config.ts
deleted file mode 100644
index af9649f..0000000
--- a/pkgs/server/vitest.config.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { defineConfig } from "vitest/config";
-
-export default defineConfig({
- test: {
- globals: true,
- env: {
- JWT_SECRET: "test-secret-key",
- },
- },
-});