init commit
This commit is contained in:
parent
5ab79d2c42
commit
278aa030cb
29 changed files with 1124 additions and 11 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -25,6 +25,7 @@ yarn-debug.log*
|
|||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
|
|
|
|||
BIN
bun.lockb
Normal file
BIN
bun.lockb
Normal file
Binary file not shown.
11
drizzle.config.ts
Normal file
11
drizzle.config.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { defineConfig } from 'drizzle-kit';
|
||||
import { ENV } from './src/config/env';
|
||||
|
||||
export default defineConfig({
|
||||
out: './drizzle',
|
||||
schema: './src/db/schema.ts',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: ENV.DATABASE_URL!,
|
||||
},
|
||||
});
|
||||
27
drizzle/0000_eager_marvel_zombies.sql
Normal file
27
drizzle/0000_eager_marvel_zombies.sql
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
CREATE TABLE "projects" (
|
||||
"project_id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" text,
|
||||
"object" json,
|
||||
"name" text,
|
||||
"description" text,
|
||||
"created_at" timestamp DEFAULT now(),
|
||||
"updated_at" timestamp DEFAULT now()
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "uploads" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"filename" text NOT NULL,
|
||||
"url" text NOT NULL,
|
||||
"projectId" uuid,
|
||||
"created_at" timestamp DEFAULT now(),
|
||||
"updated_at" timestamp DEFAULT now()
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
"user_id" text PRIMARY KEY NOT NULL,
|
||||
"paid_status" text NOT NULL,
|
||||
"expires_in" timestamp NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "projects" ADD CONSTRAINT "projects_user_id_users_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("user_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "uploads" ADD CONSTRAINT "uploads_projectId_projects_project_id_fk" FOREIGN KEY ("projectId") REFERENCES "public"."projects"("project_id") ON DELETE no action ON UPDATE no action;
|
||||
2
drizzle/0001_useful_nighthawk.sql
Normal file
2
drizzle/0001_useful_nighthawk.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "users" ALTER COLUMN "paid_status" DROP NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "users" ALTER COLUMN "expires_in" DROP NOT NULL;
|
||||
1
drizzle/0002_broad_eternity.sql
Normal file
1
drizzle/0002_broad_eternity.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "users" ALTER COLUMN "expires_in" SET DATA TYPE text;
|
||||
188
drizzle/meta/0000_snapshot.json
Normal file
188
drizzle/meta/0000_snapshot.json
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
{
|
||||
"id": "7a9f9e79-63fc-4d2b-8b25-616f96161308",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.projects": {
|
||||
"name": "projects",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"project_id": {
|
||||
"name": "project_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"object": {
|
||||
"name": "object",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"projects_user_id_users_user_id_fk": {
|
||||
"name": "projects_user_id_users_user_id_fk",
|
||||
"tableFrom": "projects",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"user_id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.uploads": {
|
||||
"name": "uploads",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"filename": {
|
||||
"name": "filename",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"url": {
|
||||
"name": "url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"projectId": {
|
||||
"name": "projectId",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"uploads_projectId_projects_project_id_fk": {
|
||||
"name": "uploads_projectId_projects_project_id_fk",
|
||||
"tableFrom": "uploads",
|
||||
"tableTo": "projects",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"project_id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"paid_status": {
|
||||
"name": "paid_status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_in": {
|
||||
"name": "expires_in",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
188
drizzle/meta/0001_snapshot.json
Normal file
188
drizzle/meta/0001_snapshot.json
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
{
|
||||
"id": "5443181c-129a-488a-9001-d65b03b0119f",
|
||||
"prevId": "7a9f9e79-63fc-4d2b-8b25-616f96161308",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.projects": {
|
||||
"name": "projects",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"project_id": {
|
||||
"name": "project_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"object": {
|
||||
"name": "object",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"projects_user_id_users_user_id_fk": {
|
||||
"name": "projects_user_id_users_user_id_fk",
|
||||
"tableFrom": "projects",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"user_id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.uploads": {
|
||||
"name": "uploads",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"filename": {
|
||||
"name": "filename",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"url": {
|
||||
"name": "url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"projectId": {
|
||||
"name": "projectId",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"uploads_projectId_projects_project_id_fk": {
|
||||
"name": "uploads_projectId_projects_project_id_fk",
|
||||
"tableFrom": "uploads",
|
||||
"tableTo": "projects",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"project_id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"paid_status": {
|
||||
"name": "paid_status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_in": {
|
||||
"name": "expires_in",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
188
drizzle/meta/0002_snapshot.json
Normal file
188
drizzle/meta/0002_snapshot.json
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
{
|
||||
"id": "88a4eebc-30cb-457f-9fc1-c6bbff735742",
|
||||
"prevId": "5443181c-129a-488a-9001-d65b03b0119f",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.projects": {
|
||||
"name": "projects",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"project_id": {
|
||||
"name": "project_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"object": {
|
||||
"name": "object",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"projects_user_id_users_user_id_fk": {
|
||||
"name": "projects_user_id_users_user_id_fk",
|
||||
"tableFrom": "projects",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"user_id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.uploads": {
|
||||
"name": "uploads",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"filename": {
|
||||
"name": "filename",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"url": {
|
||||
"name": "url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"projectId": {
|
||||
"name": "projectId",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"uploads_projectId_projects_project_id_fk": {
|
||||
"name": "uploads_projectId_projects_project_id_fk",
|
||||
"tableFrom": "uploads",
|
||||
"tableTo": "projects",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"project_id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"paid_status": {
|
||||
"name": "paid_status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_in": {
|
||||
"name": "expires_in",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
27
drizzle/meta/_journal.json
Normal file
27
drizzle/meta/_journal.json
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1737800628545,
|
||||
"tag": "0000_eager_marvel_zombies",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1737804031716,
|
||||
"tag": "0001_useful_nighthawk",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "7",
|
||||
"when": 1737806743289,
|
||||
"tag": "0002_broad_eternity",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
24
package.json
24
package.json
|
|
@ -3,13 +3,29 @@
|
|||
"version": "1.0.50",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "bun run --watch src/index.ts"
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:push": "drizzle-kit push:pg",
|
||||
"dev": "bun run --watch src/app.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"elysia": "latest"
|
||||
"@clerk/backend": "^1.23.7",
|
||||
"@elysiajs/cors": "^1.2.0",
|
||||
"@elysiajs/swagger": "^1.2.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"drizzle-orm": "^0.38.4",
|
||||
"elysia": "latest",
|
||||
"jose": "^5.9.6",
|
||||
"minio": "^8.0.3",
|
||||
"pg": "^8.13.1",
|
||||
"postgres": "^3.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "latest"
|
||||
"@types/pg": "^8.11.10",
|
||||
"bun-types": "latest",
|
||||
"drizzle-kit": "^0.30.2",
|
||||
"tsx": "^4.19.2"
|
||||
},
|
||||
"module": "src/index.js"
|
||||
"module": "src/app.js"
|
||||
}
|
||||
68
src/api/auth/auth.controller.ts
Normal file
68
src/api/auth/auth.controller.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import { createClerkClient } from "@clerk/backend";
|
||||
import { ENV } from "../../config/env"
|
||||
import { users } from "../../db/schema";
|
||||
import { db } from "../../db";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
// Initialize Clerk with your API key
|
||||
const clerk = createClerkClient({ secretKey: ENV.CLERK_SECRET_KEY });
|
||||
|
||||
export const getUserData = async (userId: string) => {
|
||||
try {
|
||||
const [user, checkInDB] = await Promise.all([
|
||||
clerk.users.getUser(userId),
|
||||
checkUserInDB(userId)
|
||||
]);
|
||||
|
||||
if (user && !checkInDB.found) {
|
||||
const userData = await createUser(user.id);
|
||||
return { status: 200, message: "User retrieved successfully", data: userData };
|
||||
}
|
||||
if (user && checkInDB.found) {
|
||||
return { status: 200, message: "User retrieved successfully", data: checkInDB };
|
||||
}
|
||||
if (!user) {
|
||||
return { status: 404, message: "User not found" };
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Error in getUserData:", error.message || error.toString());
|
||||
return { status: 500, message: `An error occurred while getting the user` };
|
||||
}
|
||||
};
|
||||
|
||||
export const checkUserInDB = async (id: string) => {
|
||||
try {
|
||||
const user = await db.select().from(users).where(eq(users.id, id));
|
||||
return { status: 200, found: user?.length > 0 };
|
||||
} catch (error: any) {
|
||||
console.error("Error in checkUserInDB:", error.message || error.toString());
|
||||
return { status: 500, message: `An error occurred while checking the user in DB` };
|
||||
}
|
||||
};
|
||||
|
||||
export const createUser = async (id: string) => {
|
||||
try {
|
||||
const [saveUser] = await db.insert(users).values({ id }).returning({ insertedId: users.id });
|
||||
|
||||
if (!saveUser || !saveUser.insertedId) {
|
||||
throw new Error("Failed to create user or missing insertedId");
|
||||
}
|
||||
|
||||
return { status: 200, message: "User created successfully", data: saveUser.insertedId };
|
||||
} catch (error: any) {
|
||||
console.error("Error in createUser:", error.message || error.toString());
|
||||
return { status: 500, message: `An error occurred while creating the user` };
|
||||
}
|
||||
};
|
||||
|
||||
export const updateUser = async (id: string, body) => {
|
||||
try {
|
||||
const updateUserData = await db.update(users).set({ paid_status: body?.paid_status, expires_in: body?.package_expire_date }).where(eq(users.id, id)).returning({ updatedId: users.id });
|
||||
|
||||
return { status: 200, message: "User updated successfully", updateUserData };
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("Error in updateUser:", error.message || error.toString());
|
||||
return { status: 500, message: `An error occurred while updating the user` };
|
||||
}
|
||||
}
|
||||
14
src/api/auth/auth.route.ts
Normal file
14
src/api/auth/auth.route.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import Elysia from "elysia";
|
||||
import { getUserData, updateUser } from "./auth.controller";
|
||||
|
||||
export const authRoute = new Elysia({
|
||||
prefix: "/auth",
|
||||
tags: ["Auth"],
|
||||
detail: {
|
||||
description: "Routes for managing users",
|
||||
}
|
||||
})
|
||||
|
||||
authRoute.get("/user/:userId", ({ params: { userId } }) => getUserData(userId));
|
||||
|
||||
authRoute.post("/user/update/:userId", ({ params: { userId }, body }) => updateUser(userId, body));
|
||||
15
src/api/index.ts
Normal file
15
src/api/index.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import Elysia from "elysia";
|
||||
import { projectRoutes } from "./project/project.route";
|
||||
import { uploadRoutes } from "./upload/upload.route";
|
||||
import { verifyAuth } from "../middlewares/auth.middlewares";
|
||||
import { authRoute } from "./auth/auth.route";
|
||||
|
||||
export const api = new Elysia({
|
||||
prefix: "/api",
|
||||
});
|
||||
|
||||
// api.derive(verifyAuth);
|
||||
|
||||
api.use(authRoute);
|
||||
api.use(projectRoutes);
|
||||
api.use(uploadRoutes);
|
||||
37
src/api/project/project.controller.ts
Normal file
37
src/api/project/project.controller.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
export const getEachProjects = async (id: string) => {
|
||||
try {
|
||||
console.log(id);
|
||||
return { id: id }
|
||||
} catch (error) {
|
||||
console.log(error.msg)
|
||||
return { status: 500, message: "An error occurred while fetching projects" }
|
||||
}
|
||||
}
|
||||
|
||||
export const getAllProjects = async () => {
|
||||
try {
|
||||
// this will return all the project associated with the user
|
||||
} catch (error) {
|
||||
console.log(error.msg);
|
||||
return { status: 500, message: "An error occurred while fetching projects" }
|
||||
}
|
||||
}
|
||||
|
||||
export const updateProject = async (id: string, data: any) => {
|
||||
try {
|
||||
|
||||
} catch (error) {
|
||||
console.log(error.msg);
|
||||
return { status: 500, message: "An error occurred while updating projects" }
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteProject = async (id: string) => {
|
||||
try {
|
||||
|
||||
} catch (error) {
|
||||
console.log(error.msg);
|
||||
return { status: 500, message: "An error occurred while deleting projects" }
|
||||
}
|
||||
}
|
||||
|
||||
19
src/api/project/project.route.ts
Normal file
19
src/api/project/project.route.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { Elysia } from "elysia";
|
||||
import { deleteProject, getAllProjects, getEachProjects, updateProject } from "./project.controller";
|
||||
|
||||
export const projectRoutes = new Elysia({
|
||||
prefix: "/projects",
|
||||
tags: ["Projects"],
|
||||
detail: {
|
||||
description: "Routes for managing projects",
|
||||
}
|
||||
})
|
||||
|
||||
projectRoutes.get("/:id", ({ params }) => getEachProjects(params.id));
|
||||
|
||||
projectRoutes.get("/", () => getAllProjects());
|
||||
|
||||
projectRoutes.put("/update/:id", ({ request, params }) => updateProject(params.id, request.body));
|
||||
|
||||
projectRoutes.delete("/delete/:id", ({ params }) => deleteProject(params.id));
|
||||
|
||||
104
src/api/upload/upload.controller.ts
Normal file
104
src/api/upload/upload.controller.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../../db";
|
||||
import { uploads } from "../../db/schema";
|
||||
import { createEmptyProject } from "../../helper/projects/createProject";
|
||||
import { createBucket } from "../../helper/upload/createBucket";
|
||||
import { uploadToMinio } from "../../helper/upload/uploadToMinio";
|
||||
import { removeFromMinio } from "../../helper/upload/removeFromMinio";
|
||||
|
||||
export const uploadPhoto = async (req: Request) => {
|
||||
try {
|
||||
// Use the formData API to extract the data from the request
|
||||
const formData = await req.formData();
|
||||
|
||||
const projectId = formData.get("id");
|
||||
const userId = formData.get("userId");
|
||||
const file = formData.get("file");
|
||||
|
||||
// Validate the file input
|
||||
if (!file || !(file instanceof File)) {
|
||||
throw new Error("Invalid or missing file in form data");
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
if (projectId) {
|
||||
const urlLink = await uploadToMinio(file, projectId, file.name);
|
||||
|
||||
const saveFile = await db
|
||||
.insert(uploads)
|
||||
.values({ filename: file.name, url: urlLink?.url, projectId })
|
||||
.returning();
|
||||
|
||||
return { status: 200, data: { msg: "File uploaded successfully", data: saveFile } };
|
||||
} else {
|
||||
|
||||
const newProjectId = await createEmptyProject(userId.toString());
|
||||
const bucket = await createBucket(newProjectId?.id);
|
||||
const urlLink = await uploadToMinio(file, bucket, file.name);
|
||||
|
||||
const saveFile = await db
|
||||
.insert(uploads)
|
||||
.values({ filename: file.name, url: urlLink?.url, projectId: newProjectId?.id })
|
||||
.returning();
|
||||
|
||||
return { status: 200, data: { msg: "New project created and file uploaded successfully", data: saveFile } };
|
||||
}
|
||||
|
||||
} else {
|
||||
return { status: 404, message: "User not found" };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error processing file:", error);
|
||||
return { status: 500, message: "An error occurred while uploading the photo" };
|
||||
}
|
||||
};
|
||||
|
||||
export const deletePhoto = async (id: string) => {
|
||||
try {
|
||||
if (!id) {
|
||||
throw new Error("Invalid or missing file ID");
|
||||
}
|
||||
|
||||
const deleteFile = await db
|
||||
.delete(uploads)
|
||||
.where(eq(uploads.id, id))
|
||||
.returning();
|
||||
|
||||
// Ensure there's a file to delete
|
||||
if (!deleteFile || deleteFile.length === 0) {
|
||||
throw new Error("File not found or already deleted");
|
||||
}
|
||||
|
||||
const { projectId, filename } = deleteFile[0];
|
||||
|
||||
// Ensure projectId and filename are valid
|
||||
if (!projectId || !filename) {
|
||||
throw new Error("Project ID or filename is missing");
|
||||
}
|
||||
|
||||
const minioRemove = await removeFromMinio(projectId, filename);
|
||||
|
||||
return { status: 200, message: minioRemove.msg };
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error processing file:", error);
|
||||
return { status: 500, message: `An error occurred while deleting the photo: ${error.message}` };
|
||||
}
|
||||
};
|
||||
|
||||
export const getAllPhoto = async (id: string) => {
|
||||
try {
|
||||
// project id
|
||||
if (!id) {
|
||||
throw new Error("Invalid or missing project ID");
|
||||
}
|
||||
const getAllPhoto = await db.select().from(uploads).where(eq(uploads.projectId, id));
|
||||
if (getAllPhoto.length === 0) {
|
||||
return { status: 200, data: { msg: "No photos found for the given project ID", data: [] } }
|
||||
}
|
||||
return { status: 200, data: { msg: "Photos retrieved successfully", data: getAllPhoto } };
|
||||
} catch (error) {
|
||||
console.log(`Error getting photos: ${error.message}`);
|
||||
return { status: 500, message: "An error occurred while getting the photos" }
|
||||
}
|
||||
}
|
||||
16
src/api/upload/upload.route.ts
Normal file
16
src/api/upload/upload.route.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { Elysia } from "elysia";
|
||||
import { deletePhoto, getAllPhoto, uploadPhoto } from "./upload.controller";
|
||||
|
||||
export const uploadRoutes = new Elysia({
|
||||
prefix: "/uploads",
|
||||
tags: ["Uploads"],
|
||||
detail: {
|
||||
description: "Routes for uploading and managing photos",
|
||||
}
|
||||
});
|
||||
|
||||
uploadRoutes.post("/add", async ({ request }) => uploadPhoto(request));
|
||||
|
||||
uploadRoutes.delete("/delete/:id", async ({ params }) => deletePhoto(params.id));
|
||||
|
||||
uploadRoutes.get("/get/:id", async ({ params }) => getAllPhoto(params.id));
|
||||
44
src/app.ts
Normal file
44
src/app.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { Elysia } from "elysia";
|
||||
import swagger from '@elysiajs/swagger';
|
||||
|
||||
import { ENV } from "./config/env";
|
||||
import cors from "@elysiajs/cors";
|
||||
import { api } from "./api";
|
||||
|
||||
const app = new Elysia()
|
||||
.use(cors())
|
||||
.use(swagger({
|
||||
path: "/docs",
|
||||
documentation: {
|
||||
info: {
|
||||
title: "Canvas API",
|
||||
version: "1.0.0",
|
||||
description: "Canvas API Documentation",
|
||||
},
|
||||
tags: [
|
||||
{
|
||||
name: "Projects",
|
||||
description: "All APIs related to Projects",
|
||||
},
|
||||
{
|
||||
name: "Uploads",
|
||||
description: "All APIs related to Uploads"
|
||||
}
|
||||
],
|
||||
}
|
||||
}))
|
||||
.onError(({ code, error }) => {
|
||||
if (code === 'NOT_FOUND')
|
||||
return 'Not Found :(';
|
||||
console.error(error)
|
||||
});
|
||||
|
||||
|
||||
// all routes here
|
||||
app.use(api);
|
||||
|
||||
app.listen(ENV.SERVER_PORT, () => {
|
||||
console.log(`🦊 Elysia is running at ${ENV.SERVER_URL}:${ENV.SERVER_PORT}`)
|
||||
})
|
||||
|
||||
|
||||
12
src/config/env.ts
Normal file
12
src/config/env.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import 'dotenv/config'
|
||||
|
||||
export const ENV = {
|
||||
SERVER_URL: process.env.SERVER_URL,
|
||||
SERVER_PORT: process.env.SERVER_PORT || 5000,
|
||||
DATABASE_URL: process.env.DATABASE_URL,
|
||||
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
|
||||
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
|
||||
MINIO_ENDPOINT: process.env.MINIO_ENDPOINT,
|
||||
MINIO_PORT: process.env.MINIO_PORT,
|
||||
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
|
||||
}
|
||||
10
src/config/minioClient.ts
Normal file
10
src/config/minioClient.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { Client } from "minio";
|
||||
import { ENV } from "../config/env";
|
||||
|
||||
export const minioClient = new Client({
|
||||
endPoint: ENV.MINIO_ENDPOINT!,
|
||||
port: ENV.MINIO_PORT,
|
||||
useSSL: false,
|
||||
accessKey: ENV.MINIO_ACCESS_KEY,
|
||||
secretKey: ENV.MINIO_SECRET_KEY,
|
||||
})
|
||||
5
src/db/index.ts
Normal file
5
src/db/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
|
||||
import { ENV } from "../config/env";
|
||||
|
||||
export const db = drizzle(ENV.DATABASE_URL!);
|
||||
26
src/db/schema.ts
Normal file
26
src/db/schema.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { json, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||
|
||||
export const users = pgTable("users", {
|
||||
id: text("user_id").primaryKey().notNull(),
|
||||
paid_status: text("paid_status"),
|
||||
expires_in: text("expires_in"),
|
||||
});
|
||||
|
||||
export const projects = pgTable("projects", {
|
||||
id: uuid("project_id").defaultRandom().primaryKey(),
|
||||
userId: text("user_id").references(() => users.id),
|
||||
object: json(),
|
||||
name: text("name"),
|
||||
description: text("description"),
|
||||
created_at: timestamp("created_at").defaultNow(),
|
||||
updated_at: timestamp("updated_at").defaultNow(),
|
||||
});
|
||||
|
||||
export const uploads = pgTable("uploads", {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
filename: text("filename").notNull(),
|
||||
url: text("url").notNull(),
|
||||
projectId: uuid().references(() => projects.id),
|
||||
created_at: timestamp("created_at").defaultNow(),
|
||||
updated_at: timestamp("updated_at").defaultNow(),
|
||||
});
|
||||
22
src/helper/projects/createProject.ts
Normal file
22
src/helper/projects/createProject.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { db } from "../../db";
|
||||
import { projects } from "../../db/schema";
|
||||
|
||||
export const createEmptyProject = async (userId: string): Promise<{ id: string }> => {
|
||||
try {
|
||||
// Insert a new row with default values
|
||||
const [newProject] = await db
|
||||
.insert(projects)
|
||||
.values({
|
||||
userId: userId,
|
||||
object: {}, // Empty object as default
|
||||
name: "", // Empty name
|
||||
description: "", // Empty description
|
||||
})
|
||||
.returning({ id: projects.id }); // Returning the ID of the created project
|
||||
// Return the newly created project's ID
|
||||
return { id: newProject.id };
|
||||
} catch (error) {
|
||||
console.error("Error creating an empty project:", error);
|
||||
throw new Error("Failed to create an empty project");
|
||||
}
|
||||
};
|
||||
35
src/helper/upload/createBucket.ts
Normal file
35
src/helper/upload/createBucket.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { minioClient } from "../../config/minioClient";
|
||||
|
||||
export const createBucket = async (bucketName: string) => {
|
||||
try {
|
||||
const bucketExists = await minioClient.bucketExists(bucketName);
|
||||
if (!bucketExists) {
|
||||
// Create the bucket
|
||||
await minioClient.makeBucket(bucketName, "us-east-1");
|
||||
|
||||
// Set the bucket policy to make it public
|
||||
const bucketPolicy = JSON.stringify({
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Effect: "Allow",
|
||||
Principal: "*",
|
||||
Action: ["s3:GetObject"],
|
||||
Resource: [`arn:aws:s3:::${bucketName}/*`],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await minioClient.setBucketPolicy(bucketName, bucketPolicy);
|
||||
|
||||
return bucketName; // Return the bucket name if created successfully
|
||||
} else {
|
||||
return bucketName; // Return the bucket name if it already exists
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error creating or configuring bucket:", error);
|
||||
|
||||
// Optionally rethrow the error with additional context
|
||||
throw new Error(`Error creating bucket "${bucketName}": ${error.message}`);
|
||||
}
|
||||
};
|
||||
16
src/helper/upload/removeFromMinio.ts
Normal file
16
src/helper/upload/removeFromMinio.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { minioClient } from "../../config/minioClient";
|
||||
|
||||
interface RemoveFromMinioResponse {
|
||||
msg: string;
|
||||
}
|
||||
|
||||
export const removeFromMinio = async (bucketName: string, objectName: string): Promise<RemoveFromMinioResponse> => {
|
||||
try {
|
||||
// Remove the object from MinIO
|
||||
await minioClient.removeObject(bucketName, objectName);
|
||||
return { msg: `Successfully removed ${objectName}` };
|
||||
} catch (error) {
|
||||
console.error("Error removing object from MinIO:", error);
|
||||
throw new Error(`Failed to remove ${objectName} from bucket ${bucketName}: ${error.message}`);
|
||||
}
|
||||
};
|
||||
19
src/helper/upload/uploadToMinio.ts
Normal file
19
src/helper/upload/uploadToMinio.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { minioClient } from "../../config/minioClient";
|
||||
|
||||
export const uploadToMinio = async (file: File, bucketName: string, objectName: string) => {
|
||||
const buffer = Buffer.from(await file.arrayBuffer()); // Convert file to buffer
|
||||
try {
|
||||
// Ensure the file is uploaded to MinIO
|
||||
await minioClient.putObject(bucketName, objectName, buffer);
|
||||
|
||||
// Construct the public URL to access the uploaded file
|
||||
const publicUrl = `${minioClient.protocol}//${minioClient.host}:${minioClient.port}/${bucketName}/${objectName}`;
|
||||
|
||||
return { url: publicUrl };
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error uploading file to MinIO:", error);
|
||||
|
||||
throw new Error(`Error uploading file: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { Elysia } from "elysia";
|
||||
|
||||
const app = new Elysia().get("/", () => "Hello Elysia").listen(3000);
|
||||
|
||||
console.log(
|
||||
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
|
||||
);
|
||||
9
src/middlewares/auth.middlewares.ts
Normal file
9
src/middlewares/auth.middlewares.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
export const verifyAuth = (request: Request) => {
|
||||
const authHeader = request.headers.get('Authorization');
|
||||
if (!authHeader) {
|
||||
return new Response('Unauthorized', { status: 401 });
|
||||
}
|
||||
const token = authHeader.split(' ')[1];
|
||||
// Verify the token here (e.g., using a library like `jsonwebtoken` or `jose`)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue