add packages
This commit is contained in:
parent
708d23df22
commit
36f6657be7
@ -11,7 +11,9 @@
|
||||
"Bash(psql:*)",
|
||||
"Bash(pnpm test:*)",
|
||||
"Bash(pnpm vitest run:*)",
|
||||
"Bash(npx tsc:*)"
|
||||
"Bash(npx tsc:*)",
|
||||
"Bash(pnpm db:generate:*)",
|
||||
"Bash(DATABASE_URL=\"postgresql://fedjens@localhost:5432/eyrun_test\" JWT_SECRET=\"test-secret-that-is-at-least-32-characters-long\" pnpm db:migrate:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
11
drizzle/0003_sparkling_ares.sql
Normal file
11
drizzle/0003_sparkling_ares.sql
Normal file
@ -0,0 +1,11 @@
|
||||
CREATE TYPE "public"."membership_role" AS ENUM('owner', 'admin', 'member');--> statement-breakpoint
|
||||
CREATE TABLE "projects" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"account_id" uuid NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "memberships" ALTER COLUMN "role" SET DATA TYPE "public"."membership_role" USING "role"::"public"."membership_role";--> statement-breakpoint
|
||||
ALTER TABLE "projects" ADD CONSTRAINT "projects_account_id_accounts_id_fk" FOREIGN KEY ("account_id") REFERENCES "public"."accounts"("id") ON DELETE no action ON UPDATE no action;
|
||||
27
drizzle/0004_dear_zuras.sql
Normal file
27
drizzle/0004_dear_zuras.sql
Normal file
@ -0,0 +1,27 @@
|
||||
CREATE TABLE "packages" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"slug" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"vcpu" integer NOT NULL,
|
||||
"ram" integer NOT NULL,
|
||||
"disk" integer NOT NULL,
|
||||
"price_monthly" numeric(10, 2) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "packages_slug_unique" UNIQUE("slug")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "wms" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"project_id" uuid NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"package_id" uuid,
|
||||
"vcpu" integer NOT NULL,
|
||||
"ram" integer NOT NULL,
|
||||
"disk" integer NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "wms" ADD CONSTRAINT "wms_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "wms" ADD CONSTRAINT "wms_package_id_packages_id_fk" FOREIGN KEY ("package_id") REFERENCES "public"."packages"("id") ON DELETE no action ON UPDATE no action;
|
||||
2
drizzle/0005_amazing_supernaut.sql
Normal file
2
drizzle/0005_amazing_supernaut.sql
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE "packages" ALTER COLUMN "vcpu" SET DATA TYPE real;--> statement-breakpoint
|
||||
ALTER TABLE "wms" ALTER COLUMN "vcpu" SET DATA TYPE real;
|
||||
7
drizzle/0006_seed_packages.sql
Normal file
7
drizzle/0006_seed_packages.sql
Normal file
@ -0,0 +1,7 @@
|
||||
INSERT INTO "packages" ("slug", "name", "vcpu", "ram", "disk", "price_monthly")
|
||||
VALUES
|
||||
('sandbox', 'Sandbox', 0.25, 256, 1, '0.00'),
|
||||
('nano', 'Nano', 0.5, 512, 5, '3.00'),
|
||||
('standard', 'Standard', 1, 1024, 10, '6.00'),
|
||||
('pro', 'Pro', 4, 4096, 20, '18.00')
|
||||
ON CONFLICT ("slug") DO NOTHING;
|
||||
397
drizzle/meta/0003_snapshot.json
Normal file
397
drizzle/meta/0003_snapshot.json
Normal file
@ -0,0 +1,397 @@
|
||||
{
|
||||
"id": "68978153-b70a-48ef-bb92-88598f5319cc",
|
||||
"prevId": "aff5e3b9-ae84-4896-8198-eec50eb41dd9",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.accounts": {
|
||||
"name": "accounts",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"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": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.memberships": {
|
||||
"name": "memberships",
|
||||
"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
|
||||
},
|
||||
"account_id": {
|
||||
"name": "account_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "membership_role",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"memberships_user_id_users_id_fk": {
|
||||
"name": "memberships_user_id_users_id_fk",
|
||||
"tableFrom": "memberships",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"memberships_account_id_accounts_id_fk": {
|
||||
"name": "memberships_account_id_accounts_id_fk",
|
||||
"tableFrom": "memberships",
|
||||
"tableTo": "accounts",
|
||||
"columnsFrom": [
|
||||
"account_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"memberships_user_id_account_id_unique": {
|
||||
"name": "memberships_user_id_account_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id",
|
||||
"account_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.otp_codes": {
|
||||
"name": "otp_codes",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"code": {
|
||||
"name": "code",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"used": {
|
||||
"name": "used",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"attempts": {
|
||||
"name": "attempts",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.projects": {
|
||||
"name": "projects",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"account_id": {
|
||||
"name": "account_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"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": {
|
||||
"projects_account_id_accounts_id_fk": {
|
||||
"name": "projects_account_id_accounts_id_fk",
|
||||
"tableFrom": "projects",
|
||||
"tableTo": "accounts",
|
||||
"columnsFrom": [
|
||||
"account_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.sessions": {
|
||||
"name": "sessions",
|
||||
"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
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"revoked_at": {
|
||||
"name": "revoked_at",
|
||||
"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()"
|
||||
}
|
||||
},
|
||||
"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": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"sessions_refresh_token_unique": {
|
||||
"name": "sessions_refresh_token_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"refresh_token"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"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_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"public.membership_role": {
|
||||
"name": "membership_role",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"owner",
|
||||
"admin",
|
||||
"member"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
572
drizzle/meta/0004_snapshot.json
Normal file
572
drizzle/meta/0004_snapshot.json
Normal file
@ -0,0 +1,572 @@
|
||||
{
|
||||
"id": "3fe6ec31-b399-41bd-88d8-b1b855d5b3ad",
|
||||
"prevId": "68978153-b70a-48ef-bb92-88598f5319cc",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.accounts": {
|
||||
"name": "accounts",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"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": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.memberships": {
|
||||
"name": "memberships",
|
||||
"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
|
||||
},
|
||||
"account_id": {
|
||||
"name": "account_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "membership_role",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"memberships_user_id_users_id_fk": {
|
||||
"name": "memberships_user_id_users_id_fk",
|
||||
"tableFrom": "memberships",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"memberships_account_id_accounts_id_fk": {
|
||||
"name": "memberships_account_id_accounts_id_fk",
|
||||
"tableFrom": "memberships",
|
||||
"tableTo": "accounts",
|
||||
"columnsFrom": [
|
||||
"account_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"memberships_user_id_account_id_unique": {
|
||||
"name": "memberships_user_id_account_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id",
|
||||
"account_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.otp_codes": {
|
||||
"name": "otp_codes",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"code": {
|
||||
"name": "code",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"used": {
|
||||
"name": "used",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"attempts": {
|
||||
"name": "attempts",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.packages": {
|
||||
"name": "packages",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"vcpu": {
|
||||
"name": "vcpu",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"ram": {
|
||||
"name": "ram",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"disk": {
|
||||
"name": "disk",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"price_monthly": {
|
||||
"name": "price_monthly",
|
||||
"type": "numeric(10, 2)",
|
||||
"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": {
|
||||
"packages_slug_unique": {
|
||||
"name": "packages_slug_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"slug"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.projects": {
|
||||
"name": "projects",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"account_id": {
|
||||
"name": "account_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"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": {
|
||||
"projects_account_id_accounts_id_fk": {
|
||||
"name": "projects_account_id_accounts_id_fk",
|
||||
"tableFrom": "projects",
|
||||
"tableTo": "accounts",
|
||||
"columnsFrom": [
|
||||
"account_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.sessions": {
|
||||
"name": "sessions",
|
||||
"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
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"revoked_at": {
|
||||
"name": "revoked_at",
|
||||
"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()"
|
||||
}
|
||||
},
|
||||
"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": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"sessions_refresh_token_unique": {
|
||||
"name": "sessions_refresh_token_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"refresh_token"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"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_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.wms": {
|
||||
"name": "wms",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"project_id": {
|
||||
"name": "project_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"package_id": {
|
||||
"name": "package_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"vcpu": {
|
||||
"name": "vcpu",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"ram": {
|
||||
"name": "ram",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"disk": {
|
||||
"name": "disk",
|
||||
"type": "integer",
|
||||
"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": {
|
||||
"wms_project_id_projects_id_fk": {
|
||||
"name": "wms_project_id_projects_id_fk",
|
||||
"tableFrom": "wms",
|
||||
"tableTo": "projects",
|
||||
"columnsFrom": [
|
||||
"project_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"wms_package_id_packages_id_fk": {
|
||||
"name": "wms_package_id_packages_id_fk",
|
||||
"tableFrom": "wms",
|
||||
"tableTo": "packages",
|
||||
"columnsFrom": [
|
||||
"package_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"public.membership_role": {
|
||||
"name": "membership_role",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"owner",
|
||||
"admin",
|
||||
"member"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
572
drizzle/meta/0005_snapshot.json
Normal file
572
drizzle/meta/0005_snapshot.json
Normal file
@ -0,0 +1,572 @@
|
||||
{
|
||||
"id": "08d293e6-8a0d-4abf-bbe5-600133a8d287",
|
||||
"prevId": "3fe6ec31-b399-41bd-88d8-b1b855d5b3ad",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.accounts": {
|
||||
"name": "accounts",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"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": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.memberships": {
|
||||
"name": "memberships",
|
||||
"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
|
||||
},
|
||||
"account_id": {
|
||||
"name": "account_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "membership_role",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"memberships_user_id_users_id_fk": {
|
||||
"name": "memberships_user_id_users_id_fk",
|
||||
"tableFrom": "memberships",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"memberships_account_id_accounts_id_fk": {
|
||||
"name": "memberships_account_id_accounts_id_fk",
|
||||
"tableFrom": "memberships",
|
||||
"tableTo": "accounts",
|
||||
"columnsFrom": [
|
||||
"account_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"memberships_user_id_account_id_unique": {
|
||||
"name": "memberships_user_id_account_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id",
|
||||
"account_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.otp_codes": {
|
||||
"name": "otp_codes",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"code": {
|
||||
"name": "code",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"used": {
|
||||
"name": "used",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"attempts": {
|
||||
"name": "attempts",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.packages": {
|
||||
"name": "packages",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"vcpu": {
|
||||
"name": "vcpu",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"ram": {
|
||||
"name": "ram",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"disk": {
|
||||
"name": "disk",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"price_monthly": {
|
||||
"name": "price_monthly",
|
||||
"type": "numeric(10, 2)",
|
||||
"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": {
|
||||
"packages_slug_unique": {
|
||||
"name": "packages_slug_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"slug"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.projects": {
|
||||
"name": "projects",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"account_id": {
|
||||
"name": "account_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"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": {
|
||||
"projects_account_id_accounts_id_fk": {
|
||||
"name": "projects_account_id_accounts_id_fk",
|
||||
"tableFrom": "projects",
|
||||
"tableTo": "accounts",
|
||||
"columnsFrom": [
|
||||
"account_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.sessions": {
|
||||
"name": "sessions",
|
||||
"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
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"revoked_at": {
|
||||
"name": "revoked_at",
|
||||
"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()"
|
||||
}
|
||||
},
|
||||
"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": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"sessions_refresh_token_unique": {
|
||||
"name": "sessions_refresh_token_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"refresh_token"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"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_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.wms": {
|
||||
"name": "wms",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"project_id": {
|
||||
"name": "project_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"package_id": {
|
||||
"name": "package_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"vcpu": {
|
||||
"name": "vcpu",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"ram": {
|
||||
"name": "ram",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"disk": {
|
||||
"name": "disk",
|
||||
"type": "integer",
|
||||
"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": {
|
||||
"wms_project_id_projects_id_fk": {
|
||||
"name": "wms_project_id_projects_id_fk",
|
||||
"tableFrom": "wms",
|
||||
"tableTo": "projects",
|
||||
"columnsFrom": [
|
||||
"project_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"wms_package_id_packages_id_fk": {
|
||||
"name": "wms_package_id_packages_id_fk",
|
||||
"tableFrom": "wms",
|
||||
"tableTo": "packages",
|
||||
"columnsFrom": [
|
||||
"package_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"public.membership_role": {
|
||||
"name": "membership_role",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"owner",
|
||||
"admin",
|
||||
"member"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
572
drizzle/meta/0006_snapshot.json
Normal file
572
drizzle/meta/0006_snapshot.json
Normal file
@ -0,0 +1,572 @@
|
||||
{
|
||||
"id": "08d293e6-8a0d-4abf-bbe5-600133a8d287",
|
||||
"prevId": "3fe6ec31-b399-41bd-88d8-b1b855d5b3ad",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.accounts": {
|
||||
"name": "accounts",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"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": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.memberships": {
|
||||
"name": "memberships",
|
||||
"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
|
||||
},
|
||||
"account_id": {
|
||||
"name": "account_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "membership_role",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"memberships_user_id_users_id_fk": {
|
||||
"name": "memberships_user_id_users_id_fk",
|
||||
"tableFrom": "memberships",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"memberships_account_id_accounts_id_fk": {
|
||||
"name": "memberships_account_id_accounts_id_fk",
|
||||
"tableFrom": "memberships",
|
||||
"tableTo": "accounts",
|
||||
"columnsFrom": [
|
||||
"account_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"memberships_user_id_account_id_unique": {
|
||||
"name": "memberships_user_id_account_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id",
|
||||
"account_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.otp_codes": {
|
||||
"name": "otp_codes",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"code": {
|
||||
"name": "code",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"used": {
|
||||
"name": "used",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"attempts": {
|
||||
"name": "attempts",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.packages": {
|
||||
"name": "packages",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"vcpu": {
|
||||
"name": "vcpu",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"ram": {
|
||||
"name": "ram",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"disk": {
|
||||
"name": "disk",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"price_monthly": {
|
||||
"name": "price_monthly",
|
||||
"type": "numeric(10, 2)",
|
||||
"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": {
|
||||
"packages_slug_unique": {
|
||||
"name": "packages_slug_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"slug"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.projects": {
|
||||
"name": "projects",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"account_id": {
|
||||
"name": "account_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"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": {
|
||||
"projects_account_id_accounts_id_fk": {
|
||||
"name": "projects_account_id_accounts_id_fk",
|
||||
"tableFrom": "projects",
|
||||
"tableTo": "accounts",
|
||||
"columnsFrom": [
|
||||
"account_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.sessions": {
|
||||
"name": "sessions",
|
||||
"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
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"revoked_at": {
|
||||
"name": "revoked_at",
|
||||
"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()"
|
||||
}
|
||||
},
|
||||
"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": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"sessions_refresh_token_unique": {
|
||||
"name": "sessions_refresh_token_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"refresh_token"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"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_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.wms": {
|
||||
"name": "wms",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"project_id": {
|
||||
"name": "project_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"package_id": {
|
||||
"name": "package_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"vcpu": {
|
||||
"name": "vcpu",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"ram": {
|
||||
"name": "ram",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"disk": {
|
||||
"name": "disk",
|
||||
"type": "integer",
|
||||
"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": {
|
||||
"wms_project_id_projects_id_fk": {
|
||||
"name": "wms_project_id_projects_id_fk",
|
||||
"tableFrom": "wms",
|
||||
"tableTo": "projects",
|
||||
"columnsFrom": [
|
||||
"project_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"wms_package_id_packages_id_fk": {
|
||||
"name": "wms_package_id_packages_id_fk",
|
||||
"tableFrom": "wms",
|
||||
"tableTo": "packages",
|
||||
"columnsFrom": [
|
||||
"package_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"public.membership_role": {
|
||||
"name": "membership_role",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"owner",
|
||||
"admin",
|
||||
"member"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@ -22,6 +22,34 @@
|
||||
"when": 1770481466355,
|
||||
"tag": "0002_fast_tarot",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "7",
|
||||
"when": 1770544024156,
|
||||
"tag": "0003_sparkling_ares",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "7",
|
||||
"when": 1770544905186,
|
||||
"tag": "0004_dear_zuras",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "7",
|
||||
"when": 1770547190763,
|
||||
"tag": "0005_amazing_supernaut",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "7",
|
||||
"when": 1770547300000,
|
||||
"tag": "0006_seed_packages",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { boolean, integer, pgEnum, pgTable, text, timestamp, unique, uuid } from "drizzle-orm/pg-core";
|
||||
import { boolean, integer, numeric, pgEnum, pgTable, real, text, timestamp, unique, uuid } from "drizzle-orm/pg-core";
|
||||
|
||||
export const membershipRoleEnum = pgEnum("membership_role", ["owner", "admin", "member"]);
|
||||
|
||||
@ -43,6 +43,42 @@ export const memberships = pgTable(
|
||||
(t) => [unique().on(t.userId, t.accountId)],
|
||||
);
|
||||
|
||||
export const projects = pgTable("projects", {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
accountId: uuid("account_id")
|
||||
.notNull()
|
||||
.references(() => accounts.id),
|
||||
name: text().notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const packages = pgTable("packages", {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
slug: text().notNull().unique(),
|
||||
name: text().notNull(),
|
||||
vcpu: real().notNull(),
|
||||
ram: integer().notNull(),
|
||||
disk: integer().notNull(),
|
||||
priceMonthly: numeric("price_monthly", { precision: 10, scale: 2 }).notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const wms = pgTable("wms", {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
projectId: uuid("project_id")
|
||||
.notNull()
|
||||
.references(() => projects.id),
|
||||
name: text().notNull(),
|
||||
packageId: uuid("package_id").references(() => packages.id),
|
||||
vcpu: real().notNull(),
|
||||
ram: integer().notNull(),
|
||||
disk: integer().notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const sessions = pgTable("sessions", {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
userId: uuid("user_id")
|
||||
|
||||
@ -5,6 +5,9 @@ import { signupRoutes } from "./signup.js";
|
||||
import { sessionRoutes } from "./sessions.js";
|
||||
import { meRoutes } from "./me.js";
|
||||
import { accountRoutes } from "./accounts.js";
|
||||
import { projectRoutes } from "./projects.js";
|
||||
import { packageRoutes } from "./packages.js";
|
||||
import { wmRoutes } from "./wms.js";
|
||||
|
||||
export async function registerRoutes(app: FastifyInstance) {
|
||||
app.register(healthRoutes);
|
||||
@ -13,4 +16,7 @@ export async function registerRoutes(app: FastifyInstance) {
|
||||
app.register(sessionRoutes, { prefix: "/sessions" });
|
||||
app.register(meRoutes);
|
||||
app.register(accountRoutes);
|
||||
app.register(projectRoutes);
|
||||
app.register(packageRoutes);
|
||||
app.register(wmRoutes);
|
||||
}
|
||||
|
||||
37
src/routes/packages.ts
Normal file
37
src/routes/packages.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { db } from "../db/index.js";
|
||||
import { packages } from "../db/schema.js";
|
||||
|
||||
export async function packageRoutes(app: FastifyInstance) {
|
||||
// GET /packages — list available packages
|
||||
app.get(
|
||||
"/packages",
|
||||
{
|
||||
schema: {
|
||||
description: "List available WM packages",
|
||||
tags: ["Packages"],
|
||||
response: {
|
||||
200: {
|
||||
description: "List of packages",
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
slug: { type: "string" },
|
||||
name: { type: "string" },
|
||||
vcpu: { type: "number" },
|
||||
ram: { type: "integer" },
|
||||
disk: { type: "integer" },
|
||||
priceMonthly: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async () => {
|
||||
return db.select().from(packages);
|
||||
},
|
||||
);
|
||||
}
|
||||
187
src/routes/projects.ts
Normal file
187
src/routes/projects.ts
Normal file
@ -0,0 +1,187 @@
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { db } from "../db/index.js";
|
||||
import { projects } from "../db/schema.js";
|
||||
|
||||
export async function projectRoutes(app: FastifyInstance) {
|
||||
// GET /projects — list projects for the current account
|
||||
app.get(
|
||||
"/projects",
|
||||
{
|
||||
preHandler: [app.authenticate, app.requireAccount],
|
||||
schema: {
|
||||
description: "List all projects for the current account",
|
||||
tags: ["Projects"],
|
||||
security: [{ bearerAuth: [] }],
|
||||
response: {
|
||||
200: {
|
||||
description: "List of projects",
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
name: { type: "string" },
|
||||
createdAt: { type: "string", format: "date-time" },
|
||||
updatedAt: { type: "string", format: "date-time" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request) => {
|
||||
return db
|
||||
.select()
|
||||
.from(projects)
|
||||
.where(eq(projects.accountId, request.accountId));
|
||||
},
|
||||
);
|
||||
|
||||
// POST /projects — create a new project
|
||||
app.post<{ Body: { name: string } }>(
|
||||
"/projects",
|
||||
{
|
||||
preHandler: [app.authenticate, app.requireAccount],
|
||||
schema: {
|
||||
description: "Create a new project in the current account",
|
||||
tags: ["Projects"],
|
||||
security: [{ bearerAuth: [] }],
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["name"],
|
||||
properties: {
|
||||
name: { type: "string", minLength: 1 },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
201: {
|
||||
description: "Project created successfully",
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
name: { type: "string" },
|
||||
accountId: { type: "string" },
|
||||
createdAt: { type: "string", format: "date-time" },
|
||||
updatedAt: { type: "string", format: "date-time" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const [project] = await db
|
||||
.insert(projects)
|
||||
.values({ name: request.body.name, accountId: request.accountId })
|
||||
.returning();
|
||||
|
||||
return reply.status(201).send(project);
|
||||
},
|
||||
);
|
||||
|
||||
// PATCH /projects/:id — update a project
|
||||
app.patch<{ Params: { id: string }; Body: { name: string } }>(
|
||||
"/projects/:id",
|
||||
{
|
||||
preHandler: [app.authenticate, app.requireAccount],
|
||||
schema: {
|
||||
description: "Update a project",
|
||||
tags: ["Projects"],
|
||||
security: [{ bearerAuth: [] }],
|
||||
params: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string", format: "uuid" },
|
||||
},
|
||||
},
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["name"],
|
||||
properties: {
|
||||
name: { type: "string", minLength: 1 },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
description: "Project updated successfully",
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
name: { type: "string" },
|
||||
accountId: { type: "string" },
|
||||
createdAt: { type: "string", format: "date-time" },
|
||||
updatedAt: { type: "string", format: "date-time" },
|
||||
},
|
||||
},
|
||||
404: {
|
||||
description: "Project not found",
|
||||
type: "object",
|
||||
properties: {
|
||||
error: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const [project] = await db
|
||||
.update(projects)
|
||||
.set({ name: request.body.name, updatedAt: new Date() })
|
||||
.where(
|
||||
and(eq(projects.id, request.params.id), eq(projects.accountId, request.accountId)),
|
||||
)
|
||||
.returning();
|
||||
|
||||
if (!project) {
|
||||
return reply.status(404).send({ error: "Project not found" });
|
||||
}
|
||||
|
||||
return project;
|
||||
},
|
||||
);
|
||||
|
||||
// DELETE /projects/:id — delete a project
|
||||
app.delete<{ Params: { id: string } }>(
|
||||
"/projects/:id",
|
||||
{
|
||||
preHandler: [app.authenticate, app.requireAccount],
|
||||
schema: {
|
||||
description: "Delete a project",
|
||||
tags: ["Projects"],
|
||||
security: [{ bearerAuth: [] }],
|
||||
params: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string", format: "uuid" },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
204: {
|
||||
description: "Project deleted successfully",
|
||||
},
|
||||
404: {
|
||||
description: "Project not found",
|
||||
type: "object",
|
||||
properties: {
|
||||
error: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const [deleted] = await db
|
||||
.delete(projects)
|
||||
.where(
|
||||
and(eq(projects.id, request.params.id), eq(projects.accountId, request.accountId)),
|
||||
)
|
||||
.returning();
|
||||
|
||||
if (!deleted) {
|
||||
return reply.status(404).send({ error: "Project not found" });
|
||||
}
|
||||
|
||||
return reply.status(204).send();
|
||||
},
|
||||
);
|
||||
}
|
||||
308
src/routes/wms.ts
Normal file
308
src/routes/wms.ts
Normal file
@ -0,0 +1,308 @@
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { db } from "../db/index.js";
|
||||
import { wms, packages, projects } from "../db/schema.js";
|
||||
|
||||
export async function wmRoutes(app: FastifyInstance) {
|
||||
// GET /projects/:projectId/wms — list WMs for a project
|
||||
app.get<{ Params: { projectId: string } }>(
|
||||
"/projects/:projectId/wms",
|
||||
{
|
||||
preHandler: [app.authenticate, app.requireAccount],
|
||||
schema: {
|
||||
description: "List all WMs for a project",
|
||||
tags: ["WMs"],
|
||||
security: [{ bearerAuth: [] }],
|
||||
params: {
|
||||
type: "object",
|
||||
properties: {
|
||||
projectId: { type: "string", format: "uuid" },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
description: "List of WMs",
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
projectId: { type: "string" },
|
||||
name: { type: "string" },
|
||||
packageId: { type: "string", nullable: true },
|
||||
vcpu: { type: "number" },
|
||||
ram: { type: "integer" },
|
||||
disk: { type: "integer" },
|
||||
createdAt: { type: "string", format: "date-time" },
|
||||
updatedAt: { type: "string", format: "date-time" },
|
||||
},
|
||||
},
|
||||
},
|
||||
404: {
|
||||
description: "Project not found",
|
||||
type: "object",
|
||||
properties: { error: { type: "string" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
// Verify project belongs to the account
|
||||
const [project] = await db
|
||||
.select()
|
||||
.from(projects)
|
||||
.where(
|
||||
and(eq(projects.id, request.params.projectId), eq(projects.accountId, request.accountId)),
|
||||
);
|
||||
|
||||
if (!project) {
|
||||
return reply.status(404).send({ error: "Project not found" });
|
||||
}
|
||||
|
||||
return db.select().from(wms).where(eq(wms.projectId, request.params.projectId));
|
||||
},
|
||||
);
|
||||
|
||||
// POST /projects/:projectId/wms — create a WM
|
||||
app.post<{
|
||||
Params: { projectId: string };
|
||||
Body: { name: string; packageId?: string; vcpu?: number; ram?: number; disk?: number };
|
||||
}>(
|
||||
"/projects/:projectId/wms",
|
||||
{
|
||||
preHandler: [app.authenticate, app.requireAccount],
|
||||
schema: {
|
||||
description:
|
||||
"Create a WM from a package or with custom settings. Provide packageId to use a package, or vcpu/ram/disk for custom settings.",
|
||||
tags: ["WMs"],
|
||||
security: [{ bearerAuth: [] }],
|
||||
params: {
|
||||
type: "object",
|
||||
properties: {
|
||||
projectId: { type: "string", format: "uuid" },
|
||||
},
|
||||
},
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["name"],
|
||||
properties: {
|
||||
name: { type: "string", minLength: 1 },
|
||||
packageId: { type: "string", format: "uuid" },
|
||||
vcpu: { type: "number", minimum: 0.25 },
|
||||
ram: { type: "integer", minimum: 1 },
|
||||
disk: { type: "integer", minimum: 1 },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
201: {
|
||||
description: "WM created successfully",
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
projectId: { type: "string" },
|
||||
name: { type: "string" },
|
||||
packageId: { type: "string", nullable: true },
|
||||
vcpu: { type: "number" },
|
||||
ram: { type: "integer" },
|
||||
disk: { type: "integer" },
|
||||
createdAt: { type: "string", format: "date-time" },
|
||||
updatedAt: { type: "string", format: "date-time" },
|
||||
},
|
||||
},
|
||||
400: {
|
||||
description: "Invalid input",
|
||||
type: "object",
|
||||
properties: { error: { type: "string" } },
|
||||
},
|
||||
404: {
|
||||
description: "Project or package not found",
|
||||
type: "object",
|
||||
properties: { error: { type: "string" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const { name, packageId, vcpu, ram, disk } = request.body;
|
||||
|
||||
// Verify project belongs to the account
|
||||
const [project] = await db
|
||||
.select()
|
||||
.from(projects)
|
||||
.where(
|
||||
and(eq(projects.id, request.params.projectId), eq(projects.accountId, request.accountId)),
|
||||
);
|
||||
|
||||
if (!project) {
|
||||
return reply.status(404).send({ error: "Project not found" });
|
||||
}
|
||||
|
||||
let settings: { vcpu: number; ram: number; disk: number; packageId: string | null };
|
||||
|
||||
if (packageId) {
|
||||
const [pkg] = await db.select().from(packages).where(eq(packages.id, packageId));
|
||||
if (!pkg) {
|
||||
return reply.status(404).send({ error: "Package not found" });
|
||||
}
|
||||
settings = { vcpu: pkg.vcpu, ram: pkg.ram, disk: pkg.disk, packageId: pkg.id };
|
||||
} else if (vcpu !== undefined && ram !== undefined && disk !== undefined) {
|
||||
settings = { vcpu, ram, disk, packageId: null };
|
||||
} else {
|
||||
return reply
|
||||
.status(400)
|
||||
.send({ error: "Provide either packageId or all of vcpu, ram, and disk" });
|
||||
}
|
||||
|
||||
const [wm] = await db
|
||||
.insert(wms)
|
||||
.values({
|
||||
projectId: request.params.projectId,
|
||||
name,
|
||||
...settings,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return reply.status(201).send(wm);
|
||||
},
|
||||
);
|
||||
|
||||
// PATCH /projects/:projectId/wms/:id — update a WM
|
||||
app.patch<{
|
||||
Params: { projectId: string; id: string };
|
||||
Body: { name?: string; vcpu?: number; ram?: number; disk?: number };
|
||||
}>(
|
||||
"/projects/:projectId/wms/:id",
|
||||
{
|
||||
preHandler: [app.authenticate, app.requireAccount],
|
||||
schema: {
|
||||
description: "Update a WM's name or settings",
|
||||
tags: ["WMs"],
|
||||
security: [{ bearerAuth: [] }],
|
||||
params: {
|
||||
type: "object",
|
||||
properties: {
|
||||
projectId: { type: "string", format: "uuid" },
|
||||
id: { type: "string", format: "uuid" },
|
||||
},
|
||||
},
|
||||
body: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", minLength: 1 },
|
||||
vcpu: { type: "number", minimum: 0.25 },
|
||||
ram: { type: "integer", minimum: 1 },
|
||||
disk: { type: "integer", minimum: 1 },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
description: "WM updated successfully",
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
projectId: { type: "string" },
|
||||
name: { type: "string" },
|
||||
packageId: { type: "string", nullable: true },
|
||||
vcpu: { type: "number" },
|
||||
ram: { type: "integer" },
|
||||
disk: { type: "integer" },
|
||||
createdAt: { type: "string", format: "date-time" },
|
||||
updatedAt: { type: "string", format: "date-time" },
|
||||
},
|
||||
},
|
||||
404: {
|
||||
description: "WM not found",
|
||||
type: "object",
|
||||
properties: { error: { type: "string" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
// Verify project belongs to the account
|
||||
const [project] = await db
|
||||
.select()
|
||||
.from(projects)
|
||||
.where(
|
||||
and(eq(projects.id, request.params.projectId), eq(projects.accountId, request.accountId)),
|
||||
);
|
||||
|
||||
if (!project) {
|
||||
return reply.status(404).send({ error: "Project not found" });
|
||||
}
|
||||
|
||||
const { name, vcpu, ram, disk } = request.body;
|
||||
const updates: Record<string, unknown> = { updatedAt: new Date() };
|
||||
if (name !== undefined) updates.name = name;
|
||||
if (vcpu !== undefined) updates.vcpu = vcpu;
|
||||
if (ram !== undefined) updates.ram = ram;
|
||||
if (disk !== undefined) updates.disk = disk;
|
||||
|
||||
const [wm] = await db
|
||||
.update(wms)
|
||||
.set(updates)
|
||||
.where(and(eq(wms.id, request.params.id), eq(wms.projectId, request.params.projectId)))
|
||||
.returning();
|
||||
|
||||
if (!wm) {
|
||||
return reply.status(404).send({ error: "WM not found" });
|
||||
}
|
||||
|
||||
return wm;
|
||||
},
|
||||
);
|
||||
|
||||
// DELETE /projects/:projectId/wms/:id — delete a WM
|
||||
app.delete<{ Params: { projectId: string; id: string } }>(
|
||||
"/projects/:projectId/wms/:id",
|
||||
{
|
||||
preHandler: [app.authenticate, app.requireAccount],
|
||||
schema: {
|
||||
description: "Delete a WM",
|
||||
tags: ["WMs"],
|
||||
security: [{ bearerAuth: [] }],
|
||||
params: {
|
||||
type: "object",
|
||||
properties: {
|
||||
projectId: { type: "string", format: "uuid" },
|
||||
id: { type: "string", format: "uuid" },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
204: {
|
||||
description: "WM deleted successfully",
|
||||
},
|
||||
404: {
|
||||
description: "WM not found",
|
||||
type: "object",
|
||||
properties: { error: { type: "string" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
// Verify project belongs to the account
|
||||
const [project] = await db
|
||||
.select()
|
||||
.from(projects)
|
||||
.where(
|
||||
and(eq(projects.id, request.params.projectId), eq(projects.accountId, request.accountId)),
|
||||
);
|
||||
|
||||
if (!project) {
|
||||
return reply.status(404).send({ error: "Project not found" });
|
||||
}
|
||||
|
||||
const [deleted] = await db
|
||||
.delete(wms)
|
||||
.where(and(eq(wms.id, request.params.id), eq(wms.projectId, request.params.projectId)))
|
||||
.returning();
|
||||
|
||||
if (!deleted) {
|
||||
return reply.status(404).send({ error: "WM not found" });
|
||||
}
|
||||
|
||||
return reply.status(204).send();
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { type FastifyInstance } from "fastify";
|
||||
import { buildApp } from "../src/app.js";
|
||||
import { db } from "../src/db/index.js";
|
||||
import { users, otpCodes, sessions, accounts, memberships } from "../src/db/schema.js";
|
||||
import { users, otpCodes, sessions, accounts, memberships, projects, wms, packages } from "../src/db/schema.js";
|
||||
import { sql } from "drizzle-orm";
|
||||
|
||||
export async function createTestApp(): Promise<FastifyInstance> {
|
||||
@ -11,6 +11,9 @@ export async function createTestApp(): Promise<FastifyInstance> {
|
||||
}
|
||||
|
||||
export async function cleanDb() {
|
||||
await db.delete(wms);
|
||||
await db.delete(packages);
|
||||
await db.delete(projects);
|
||||
await db.delete(sessions);
|
||||
await db.delete(memberships);
|
||||
await db.delete(accounts);
|
||||
|
||||
47
tests/packages.test.ts
Normal file
47
tests/packages.test.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { createTestApp, cleanDb } from "./helpers.js";
|
||||
import { db } from "../src/db/index.js";
|
||||
import { packages } from "../src/db/schema.js";
|
||||
|
||||
let app: FastifyInstance;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await createTestApp();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanDb();
|
||||
});
|
||||
|
||||
describe("GET /packages", () => {
|
||||
it("returns empty list when no packages exist", async () => {
|
||||
const res = await app.inject({ method: "GET", url: "/packages" });
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.json()).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns seeded packages", async () => {
|
||||
await db.insert(packages).values([
|
||||
{ slug: "small", name: "Small", vcpu: 1, ram: 1024, disk: 20, priceMonthly: "5.00" },
|
||||
{ slug: "medium", name: "Medium", vcpu: 2, ram: 2048, disk: 40, priceMonthly: "10.00" },
|
||||
]);
|
||||
|
||||
const res = await app.inject({ method: "GET", url: "/packages" });
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const body = res.json();
|
||||
expect(body).toHaveLength(2);
|
||||
expect(body.map((p: { slug: string }) => p.slug).sort()).toEqual(["medium", "small"]);
|
||||
});
|
||||
|
||||
it("does not require authentication", async () => {
|
||||
const res = await app.inject({ method: "GET", url: "/packages" });
|
||||
expect(res.statusCode).toBe(200);
|
||||
});
|
||||
});
|
||||
279
tests/projects.test.ts
Normal file
279
tests/projects.test.ts
Normal file
@ -0,0 +1,279 @@
|
||||
import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { createTestApp, cleanDb, signupUser } from "./helpers.js";
|
||||
|
||||
let app: FastifyInstance;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await createTestApp();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanDb();
|
||||
});
|
||||
|
||||
async function setupUser() {
|
||||
const signup = await signupUser(app, "user@example.com", "Org");
|
||||
const { accessToken, accounts } = signup.json();
|
||||
return { accessToken, accountId: accounts[0].id as string };
|
||||
}
|
||||
|
||||
function headers(accessToken: string, accountId: string) {
|
||||
return { authorization: `Bearer ${accessToken}`, "x-account-id": accountId };
|
||||
}
|
||||
|
||||
describe("GET /projects", () => {
|
||||
it("returns empty list when no projects exist", async () => {
|
||||
const { accessToken, accountId } = await setupUser();
|
||||
|
||||
const res = await app.inject({
|
||||
method: "GET",
|
||||
url: "/projects",
|
||||
headers: headers(accessToken, accountId),
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.json()).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns projects for the account", async () => {
|
||||
const { accessToken, accountId } = await setupUser();
|
||||
|
||||
await app.inject({
|
||||
method: "POST",
|
||||
url: "/projects",
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "Project A" },
|
||||
});
|
||||
await app.inject({
|
||||
method: "POST",
|
||||
url: "/projects",
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "Project B" },
|
||||
});
|
||||
|
||||
const res = await app.inject({
|
||||
method: "GET",
|
||||
url: "/projects",
|
||||
headers: headers(accessToken, accountId),
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const body = res.json();
|
||||
expect(body).toHaveLength(2);
|
||||
expect(body.map((p: { name: string }) => p.name).sort()).toEqual(["Project A", "Project B"]);
|
||||
});
|
||||
|
||||
it("does not return projects from another account", async () => {
|
||||
const { accessToken, accountId } = await setupUser();
|
||||
|
||||
// Create a project in the first account
|
||||
await app.inject({
|
||||
method: "POST",
|
||||
url: "/projects",
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "Project A" },
|
||||
});
|
||||
|
||||
// Create a second account
|
||||
const accountRes = await app.inject({
|
||||
method: "POST",
|
||||
url: "/accounts",
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
payload: { name: "Org 2" },
|
||||
});
|
||||
const secondAccountId = accountRes.json().id;
|
||||
|
||||
// List projects in the second account — should be empty
|
||||
const res = await app.inject({
|
||||
method: "GET",
|
||||
url: "/projects",
|
||||
headers: headers(accessToken, secondAccountId),
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.json()).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns 401 without auth", async () => {
|
||||
const res = await app.inject({ method: "GET", url: "/projects" });
|
||||
expect(res.statusCode).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /projects", () => {
|
||||
it("creates a project", async () => {
|
||||
const { accessToken, accountId } = await setupUser();
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/projects",
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "New Project" },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(201);
|
||||
const body = res.json();
|
||||
expect(body.name).toBe("New Project");
|
||||
expect(body.accountId).toBe(accountId);
|
||||
expect(body.id).toBeDefined();
|
||||
});
|
||||
|
||||
it("returns 400 if name is missing", async () => {
|
||||
const { accessToken, accountId } = await setupUser();
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/projects",
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: {},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PATCH /projects/:id", () => {
|
||||
it("updates a project name", async () => {
|
||||
const { accessToken, accountId } = await setupUser();
|
||||
|
||||
const create = await app.inject({
|
||||
method: "POST",
|
||||
url: "/projects",
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "Old Name" },
|
||||
});
|
||||
const projectId = create.json().id;
|
||||
|
||||
const res = await app.inject({
|
||||
method: "PATCH",
|
||||
url: `/projects/${projectId}`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "New Name" },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.json().name).toBe("New Name");
|
||||
expect(res.json().id).toBe(projectId);
|
||||
});
|
||||
|
||||
it("returns 404 for non-existent project", async () => {
|
||||
const { accessToken, accountId } = await setupUser();
|
||||
|
||||
const res = await app.inject({
|
||||
method: "PATCH",
|
||||
url: "/projects/00000000-0000-0000-0000-000000000000",
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "Nope" },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
it("returns 404 when updating a project from another account", async () => {
|
||||
const { accessToken, accountId } = await setupUser();
|
||||
|
||||
const create = await app.inject({
|
||||
method: "POST",
|
||||
url: "/projects",
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "Project" },
|
||||
});
|
||||
const projectId = create.json().id;
|
||||
|
||||
// Create second account
|
||||
const accountRes = await app.inject({
|
||||
method: "POST",
|
||||
url: "/accounts",
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
payload: { name: "Org 2" },
|
||||
});
|
||||
const secondAccountId = accountRes.json().id;
|
||||
|
||||
// Try to update from second account
|
||||
const res = await app.inject({
|
||||
method: "PATCH",
|
||||
url: `/projects/${projectId}`,
|
||||
headers: headers(accessToken, secondAccountId),
|
||||
payload: { name: "Hacked" },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DELETE /projects/:id", () => {
|
||||
it("deletes a project", async () => {
|
||||
const { accessToken, accountId } = await setupUser();
|
||||
|
||||
const create = await app.inject({
|
||||
method: "POST",
|
||||
url: "/projects",
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "To Delete" },
|
||||
});
|
||||
const projectId = create.json().id;
|
||||
|
||||
const res = await app.inject({
|
||||
method: "DELETE",
|
||||
url: `/projects/${projectId}`,
|
||||
headers: headers(accessToken, accountId),
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(204);
|
||||
|
||||
// Verify it's gone
|
||||
const list = await app.inject({
|
||||
method: "GET",
|
||||
url: "/projects",
|
||||
headers: headers(accessToken, accountId),
|
||||
});
|
||||
expect(list.json()).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns 404 for non-existent project", async () => {
|
||||
const { accessToken, accountId } = await setupUser();
|
||||
|
||||
const res = await app.inject({
|
||||
method: "DELETE",
|
||||
url: "/projects/00000000-0000-0000-0000-000000000000",
|
||||
headers: headers(accessToken, accountId),
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
it("returns 404 when deleting a project from another account", async () => {
|
||||
const { accessToken, accountId } = await setupUser();
|
||||
|
||||
const create = await app.inject({
|
||||
method: "POST",
|
||||
url: "/projects",
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "Project" },
|
||||
});
|
||||
const projectId = create.json().id;
|
||||
|
||||
// Create second account
|
||||
const accountRes = await app.inject({
|
||||
method: "POST",
|
||||
url: "/accounts",
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
payload: { name: "Org 2" },
|
||||
});
|
||||
const secondAccountId = accountRes.json().id;
|
||||
|
||||
// Try to delete from second account
|
||||
const res = await app.inject({
|
||||
method: "DELETE",
|
||||
url: `/projects/${projectId}`,
|
||||
headers: headers(accessToken, secondAccountId),
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
360
tests/wms.test.ts
Normal file
360
tests/wms.test.ts
Normal file
@ -0,0 +1,360 @@
|
||||
import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { createTestApp, cleanDb, signupUser } from "./helpers.js";
|
||||
import { db } from "../src/db/index.js";
|
||||
import { packages } from "../src/db/schema.js";
|
||||
|
||||
let app: FastifyInstance;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await createTestApp();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanDb();
|
||||
});
|
||||
|
||||
async function setupUserWithProject() {
|
||||
const signup = await signupUser(app, "user@example.com", "Org");
|
||||
const { accessToken, accounts } = signup.json();
|
||||
const accountId = accounts[0].id as string;
|
||||
|
||||
const projectRes = await app.inject({
|
||||
method: "POST",
|
||||
url: "/projects",
|
||||
headers: { authorization: `Bearer ${accessToken}`, "x-account-id": accountId },
|
||||
payload: { name: "My Project" },
|
||||
});
|
||||
const projectId = projectRes.json().id as string;
|
||||
|
||||
return { accessToken, accountId, projectId };
|
||||
}
|
||||
|
||||
function headers(accessToken: string, accountId: string) {
|
||||
return { authorization: `Bearer ${accessToken}`, "x-account-id": accountId };
|
||||
}
|
||||
|
||||
async function seedSmallPackage() {
|
||||
const [pkg] = await db
|
||||
.insert(packages)
|
||||
.values({ slug: "small", name: "Small", vcpu: 1, ram: 1024, disk: 20, priceMonthly: "5.00" })
|
||||
.returning();
|
||||
return pkg;
|
||||
}
|
||||
|
||||
describe("GET /projects/:projectId/wms", () => {
|
||||
it("returns empty list when no WMs exist", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
|
||||
const res = await app.inject({
|
||||
method: "GET",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, accountId),
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.json()).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns WMs for the project", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
|
||||
await app.inject({
|
||||
method: "POST",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "WM 1", vcpu: 1, ram: 512, disk: 10 },
|
||||
});
|
||||
await app.inject({
|
||||
method: "POST",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "WM 2", vcpu: 2, ram: 1024, disk: 20 },
|
||||
});
|
||||
|
||||
const res = await app.inject({
|
||||
method: "GET",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, accountId),
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.json()).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("returns 404 for a project in another account", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
|
||||
// Create second account
|
||||
const accountRes = await app.inject({
|
||||
method: "POST",
|
||||
url: "/accounts",
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
payload: { name: "Org 2" },
|
||||
});
|
||||
const secondAccountId = accountRes.json().id;
|
||||
|
||||
const res = await app.inject({
|
||||
method: "GET",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, secondAccountId),
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /projects/:projectId/wms", () => {
|
||||
it("creates a WM from a package", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
const pkg = await seedSmallPackage();
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "My WM", packageId: pkg.id },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(201);
|
||||
const body = res.json();
|
||||
expect(body.name).toBe("My WM");
|
||||
expect(body.packageId).toBe(pkg.id);
|
||||
expect(body.vcpu).toBe(1);
|
||||
expect(body.ram).toBe(1024);
|
||||
expect(body.disk).toBe(20);
|
||||
});
|
||||
|
||||
it("creates a WM with custom settings", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "Custom WM", vcpu: 4, ram: 8192, disk: 100 },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(201);
|
||||
const body = res.json();
|
||||
expect(body.packageId).toBeNull();
|
||||
expect(body.vcpu).toBe(4);
|
||||
expect(body.ram).toBe(8192);
|
||||
expect(body.disk).toBe(100);
|
||||
});
|
||||
|
||||
it("returns 400 if neither packageId nor full custom settings provided", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "Bad WM", vcpu: 2 },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.json().error).toMatch(/packageId/);
|
||||
});
|
||||
|
||||
it("returns 404 for non-existent package", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "WM", packageId: "00000000-0000-0000-0000-000000000000" },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
expect(res.json().error).toMatch(/Package/);
|
||||
});
|
||||
|
||||
it("returns 404 for a project in another account", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
|
||||
const accountRes = await app.inject({
|
||||
method: "POST",
|
||||
url: "/accounts",
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
payload: { name: "Org 2" },
|
||||
});
|
||||
const secondAccountId = accountRes.json().id;
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, secondAccountId),
|
||||
payload: { name: "WM", vcpu: 1, ram: 512, disk: 10 },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PATCH /projects/:projectId/wms/:id", () => {
|
||||
it("updates WM name", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
|
||||
const create = await app.inject({
|
||||
method: "POST",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "Old Name", vcpu: 1, ram: 512, disk: 10 },
|
||||
});
|
||||
const wmId = create.json().id;
|
||||
|
||||
const res = await app.inject({
|
||||
method: "PATCH",
|
||||
url: `/projects/${projectId}/wms/${wmId}`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "New Name" },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.json().name).toBe("New Name");
|
||||
expect(res.json().vcpu).toBe(1);
|
||||
});
|
||||
|
||||
it("updates individual settings (e.g. only disk)", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
const pkg = await seedSmallPackage();
|
||||
|
||||
const create = await app.inject({
|
||||
method: "POST",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "WM", packageId: pkg.id },
|
||||
});
|
||||
const wmId = create.json().id;
|
||||
|
||||
const res = await app.inject({
|
||||
method: "PATCH",
|
||||
url: `/projects/${projectId}/wms/${wmId}`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { disk: 40 },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.json().disk).toBe(40);
|
||||
expect(res.json().vcpu).toBe(1);
|
||||
expect(res.json().ram).toBe(1024);
|
||||
});
|
||||
|
||||
it("returns 404 for non-existent WM", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
|
||||
const res = await app.inject({
|
||||
method: "PATCH",
|
||||
url: `/projects/${projectId}/wms/00000000-0000-0000-0000-000000000000`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "Nope" },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
it("returns 404 for project in another account", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
|
||||
const create = await app.inject({
|
||||
method: "POST",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "WM", vcpu: 1, ram: 512, disk: 10 },
|
||||
});
|
||||
const wmId = create.json().id;
|
||||
|
||||
const accountRes = await app.inject({
|
||||
method: "POST",
|
||||
url: "/accounts",
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
payload: { name: "Org 2" },
|
||||
});
|
||||
const secondAccountId = accountRes.json().id;
|
||||
|
||||
const res = await app.inject({
|
||||
method: "PATCH",
|
||||
url: `/projects/${projectId}/wms/${wmId}`,
|
||||
headers: headers(accessToken, secondAccountId),
|
||||
payload: { name: "Hacked" },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DELETE /projects/:projectId/wms/:id", () => {
|
||||
it("deletes a WM", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
|
||||
const create = await app.inject({
|
||||
method: "POST",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "To Delete", vcpu: 1, ram: 512, disk: 10 },
|
||||
});
|
||||
const wmId = create.json().id;
|
||||
|
||||
const res = await app.inject({
|
||||
method: "DELETE",
|
||||
url: `/projects/${projectId}/wms/${wmId}`,
|
||||
headers: headers(accessToken, accountId),
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(204);
|
||||
|
||||
// Verify it's gone
|
||||
const list = await app.inject({
|
||||
method: "GET",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, accountId),
|
||||
});
|
||||
expect(list.json()).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns 404 for non-existent WM", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
|
||||
const res = await app.inject({
|
||||
method: "DELETE",
|
||||
url: `/projects/${projectId}/wms/00000000-0000-0000-0000-000000000000`,
|
||||
headers: headers(accessToken, accountId),
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
it("returns 404 for project in another account", async () => {
|
||||
const { accessToken, accountId, projectId } = await setupUserWithProject();
|
||||
|
||||
const create = await app.inject({
|
||||
method: "POST",
|
||||
url: `/projects/${projectId}/wms`,
|
||||
headers: headers(accessToken, accountId),
|
||||
payload: { name: "WM", vcpu: 1, ram: 512, disk: 10 },
|
||||
});
|
||||
const wmId = create.json().id;
|
||||
|
||||
const accountRes = await app.inject({
|
||||
method: "POST",
|
||||
url: "/accounts",
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
payload: { name: "Org 2" },
|
||||
});
|
||||
const secondAccountId = accountRes.json().id;
|
||||
|
||||
const res = await app.inject({
|
||||
method: "DELETE",
|
||||
url: `/projects/${projectId}/wms/${wmId}`,
|
||||
headers: headers(accessToken, secondAccountId),
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user