Compare commits

...

4 commits

Author SHA1 Message Date
Saimon8420
1ef2b41a75 all code added 2025-02-13 11:15:05 +06:00
Saimon8420
f600af78fc all code added 2025-02-13 11:11:13 +06:00
Saimon8420
32d9268dd9 all code added 2025-02-12 18:57:31 +06:00
Saimon8420
f4d4a30999 download count added, and other code fixed 2025-02-11 17:53:22 +06:00
20 changed files with 287 additions and 289 deletions

3
.env
View file

@ -12,4 +12,5 @@ CLERK_SECRET_KEY=sk_test_X4OZbPdnr9ZxlccH8eaA7Ou0oQvQ9FpQ0mq0KuwLUz
JWT_ACCESS_TOKEN_SECRET=planpostai%^$_%43%65576canvas%%$$ JWT_ACCESS_TOKEN_SECRET=planpostai%^$_%43%65576canvas%%$$
JWT_REFRESH_TOKEN_SECRET=planpostai!@43223_canvas$%^$349332$$ JWT_REFRESH_TOKEN_SECRET=planpostai!@43223_canvas$%^$349332$$

2
.gitignore vendored
View file

@ -25,7 +25,7 @@ yarn-debug.log*
yarn-error.log* yarn-error.log*
# local env files # local env files
.env .env
.env.local .env.local
.env.development.local .env.development.local
.env.test.local .env.test.local

View file

@ -1,15 +1,19 @@
# Elysia with Bun runtime # Elysia with Bun runtime
## Getting Started ## Getting Started
To get started with this template, simply paste this command into your terminal: To get started with this template, simply paste this command into your terminal:
```bash ```bash
bun create elysia ./elysia-example bun create elysia ./elysia-example
``` ```
## Development ## Development
To start the development server run: To start the development server run:
```bash ```bash
bun run dev bun run dev
``` ```
Open http://localhost:3000/ with your browser to see the result. Open <http://localhost:3000/> with your browser to see the result.

View file

@ -4,6 +4,8 @@ CREATE TABLE "projects" (
"object" json, "object" json,
"name" text, "name" text,
"description" text, "description" text,
"is_active" boolean DEFAULT false NOT NULL,
"preview_url" text,
"created_at" timestamp DEFAULT now(), "created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now() "updated_at" timestamp DEFAULT now()
); );
@ -19,9 +21,15 @@ CREATE TABLE "uploads" (
--> statement-breakpoint --> statement-breakpoint
CREATE TABLE "users" ( CREATE TABLE "users" (
"user_id" text PRIMARY KEY NOT NULL, "user_id" text PRIMARY KEY NOT NULL,
"email" text NOT NULL,
"last_name" text,
"first_name" text,
"image" text,
"paid_status" text, "paid_status" text,
"expires_in" text, "expires_in" text,
"refresh_token" text "refresh_token" text,
"download_limit" integer DEFAULT 3 NOT NULL,
"downloads_today" jsonb DEFAULT '{"date":null,"count":0}'::jsonb
); );
--> statement-breakpoint --> 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 "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

View file

@ -0,0 +1 @@
ALTER TABLE "users" ALTER COLUMN "download_limit" SET DEFAULT 10;

View file

@ -1,4 +0,0 @@
ALTER TABLE "users" ADD COLUMN "email" text NOT NULL;--> statement-breakpoint
ALTER TABLE "users" ADD COLUMN "last_name" text;--> statement-breakpoint
ALTER TABLE "users" ADD COLUMN "first_name" text;--> statement-breakpoint
ALTER TABLE "users" ADD COLUMN "image" text;

View file

@ -1,2 +0,0 @@
ALTER TABLE "projects" ADD COLUMN "is_active" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "projects" ADD COLUMN "preview_url" text;

View file

@ -1,5 +1,5 @@
{ {
"id": "a0fe5e52-63bf-4a92-adb0-ae296fb9f33e", "id": "844ebb59-bcf8-407d-8eec-e1d26325a0fa",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"version": "7", "version": "7",
"dialect": "postgresql", "dialect": "postgresql",
@ -39,6 +39,19 @@
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false
}, },
"is_active": {
"name": "is_active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"preview_url": {
"name": "preview_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": { "created_at": {
"name": "created_at", "name": "created_at",
"type": "timestamp", "type": "timestamp",
@ -152,6 +165,30 @@
"primaryKey": true, "primaryKey": true,
"notNull": true "notNull": true
}, },
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"last_name": {
"name": "last_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"first_name": {
"name": "first_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"paid_status": { "paid_status": {
"name": "paid_status", "name": "paid_status",
"type": "text", "type": "text",
@ -169,6 +206,20 @@
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false
},
"download_limit": {
"name": "download_limit",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 3
},
"downloads_today": {
"name": "downloads_today",
"type": "jsonb",
"primaryKey": false,
"notNull": false,
"default": "'{\"date\":null,\"count\":0}'::jsonb"
} }
}, },
"indexes": {}, "indexes": {},

View file

@ -1,6 +1,6 @@
{ {
"id": "b6897b47-e0f0-48c5-8917-696944c8524b", "id": "93d3a5a6-cb89-49b5-87aa-116132d77a7c",
"prevId": "a0fe5e52-63bf-4a92-adb0-ae296fb9f33e", "prevId": "844ebb59-bcf8-407d-8eec-e1d26325a0fa",
"version": "7", "version": "7",
"dialect": "postgresql", "dialect": "postgresql",
"tables": { "tables": {
@ -39,6 +39,19 @@
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false
}, },
"is_active": {
"name": "is_active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"preview_url": {
"name": "preview_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": { "created_at": {
"name": "created_at", "name": "created_at",
"type": "timestamp", "type": "timestamp",
@ -193,6 +206,20 @@
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false
},
"download_limit": {
"name": "download_limit",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 10
},
"downloads_today": {
"name": "downloads_today",
"type": "jsonb",
"primaryKey": false,
"notNull": false,
"default": "'{\"date\":null,\"count\":0}'::jsonb"
} }
}, },
"indexes": {}, "indexes": {},

View file

@ -1,231 +0,0 @@
{
"id": "97be3edd-38c0-499d-accc-13797e7318aa",
"prevId": "b6897b47-e0f0-48c5-8917-696944c8524b",
"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
},
"is_active": {
"name": "is_active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"preview_url": {
"name": "preview_url",
"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
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"last_name": {
"name": "last_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"first_name": {
"name": "first_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"paid_status": {
"name": "paid_status",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_in": {
"name": "expires_in",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View file

@ -5,22 +5,15 @@
{ {
"idx": 0, "idx": 0,
"version": "7", "version": "7",
"when": 1737876637906, "when": 1739164927235,
"tag": "0000_tidy_echo", "tag": "0000_cultured_groot",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 1, "idx": 1,
"version": "7", "version": "7",
"when": 1737876981144, "when": 1739422984444,
"tag": "0001_shallow_umar", "tag": "0001_groovy_randall_flagg",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1738213216210,
"tag": "0002_serious_green_goblin",
"breakpoints": true "breakpoints": true
} }
] ]

View file

@ -104,7 +104,7 @@ export const generateToken = async (context: any) => {
maxAge: 7 * 24 * 60 * 60, // 7 days in seconds maxAge: 7 * 24 * 60 * 60, // 7 days in seconds
}); });
return { status: 201, message: "Token generated successfully", token: accessToken, userId: user?.id }; return { status: 201, message: "Token generated successfully", token: accessToken, user: user.user };
} }
return { status: 500, message: "An error occurred while storing the refresh token" }; return { status: 500, message: "An error occurred while storing the refresh token" };
} }

View file

@ -1,5 +1,6 @@
import Elysia, { t } from "elysia"; import Elysia, { t } from "elysia";
import { generateToken, getUserData, updateUser } from "./auth.controller"; import { generateToken, getUserData, updateUser } from "./auth.controller";
import { verifyAuth } from "../../middlewares/auth.middlewares";
export const authRoute = new Elysia({ export const authRoute = new Elysia({
prefix: "/auth", prefix: "/auth",
@ -27,3 +28,20 @@ authRoute.post("/user/update/:userId", async ({ params: { userId }, body }) => a
authRoute.get("/generate-token/:userId", async (context) => await generateToken(context)); authRoute.get("/generate-token/:userId", async (context) => await generateToken(context));
authRoute.get("/user/me", async ({ cookie }) => {
const authData = await verifyAuth(cookie);
if (authData.status !== 200) {
return authData;
}
else {
const userId: string | any = authData.userId;
const response = await getUserData(userId);
if (response?.status === 200) {
return { ...response.data, token: authData.token, status: 200, message: "User data fetched successfully" };
}
else {
return response;
}
}
})

View file

@ -0,0 +1,50 @@
import { eq } from "drizzle-orm";
import { db } from "../../db";
import { users } from "../../db/schema";
export const downloadCount = async (userId: string, token: string, date: string) => {
try {
// Fetch user data
const [user] = await db.select().from(users).where(eq(users.id, userId));
if (!user) {
return { success: false, status: 404, message: "User not found" };
}
// Define the expected structure for downloads_today
type DownloadsToday = { date: string; count: number };
let downloads_today: DownloadsToday = user.downloads_today as DownloadsToday || { date: "", count: 0 };
let { download_limit } = user;
// Initialize downloads_today if not present
if (!downloads_today.date) {
downloads_today = { date, count: 1 };
}
else if (downloads_today.date !== date) {
downloads_today = { date, count: 1 }; // Reset count for a new date
}
else if ((downloads_today.count || 0) >= download_limit) {
return { success: false, status: 400, message: "Download limit reached for today", token };
}
else {
downloads_today.count += 1;
}
// Update the database
await db.update(users).set({ downloads_today }).where(eq(users.id, userId));
return {
status: 200,
success: true,
message: `Download allowed. Remaining downloads: ${download_limit - downloads_today.count}`,
downloads_today,
token,
};
} catch (error) {
console.error("Error updating download count:", error);
return { status: 500, message: "An error occurred while updating the download count", token };
}
};

View file

@ -0,0 +1,32 @@
import Elysia, { t } from "elysia";
import { verifyAuth } from "../../middlewares/auth.middlewares";
import { downloadCount } from "./download.count.controller";
export const downloadRoute = new Elysia({
prefix: "/download-count",
tags: ["Download Count"],
detail: {
description: "Routes for download count",
}
}).derive(async ({ cookie }) => {
const authData = await verifyAuth(cookie);
return { authData }; // Inject into context
})
downloadRoute.post("/", async ({ authData, body }) => {
if (authData.status !== 200)
return authData;
else {
const userId: String | any = authData.userId;
const token = authData.token;
const { date } = body;
const response = await downloadCount(userId, token, date);
return response;
}
}, {
body: t.Object({
date: t.String(),
})
});

View file

@ -2,6 +2,7 @@ import Elysia from "elysia";
import { projectRoutes } from "./project/project.route"; import { projectRoutes } from "./project/project.route";
import { uploadRoutes } from "./upload/upload.route"; import { uploadRoutes } from "./upload/upload.route";
import { authRoute } from "./auth/auth.route"; import { authRoute } from "./auth/auth.route";
import { downloadRoute } from "./downloadCount/download.count.route";
export const api = new Elysia({ export const api = new Elysia({
prefix: "/api", prefix: "/api",
@ -9,4 +10,5 @@ export const api = new Elysia({
api.use(authRoute); api.use(authRoute);
api.use(projectRoutes); api.use(projectRoutes);
api.use(uploadRoutes); api.use(uploadRoutes);
api.use(downloadRoute);

View file

@ -5,6 +5,56 @@ import { createEmptyProject } from "../../helper/projects/createProject";
import { createBucket } from "../../helper/upload/createBucket"; import { createBucket } from "../../helper/upload/createBucket";
import { removeBucket } from "../../helper/upload/removeBucket"; import { removeBucket } from "../../helper/upload/removeBucket";
export const getAllProjects = async (userId: string, token: string) => {
try {
// Fetch all projects for the given user
const allProjects = await db.select({
id: projects.id,
name: projects.name,
description: projects.description,
preview_url: projects.preview_url,
object: projects.object,
}).from(projects).where(eq(projects.userId, userId));
// Identify projects where 'object' is empty or 'object.objects' is empty
const projectsToDelete = allProjects.filter(proj =>
(proj.object && typeof proj.object === "object" && Object.keys(proj.object).length === 0) ||
(proj.object?.objects && Array.isArray(proj.object.objects) && proj.object.objects.length === 0)
);
// Delete projects with empty 'object' or empty 'object.objects'
await Promise.all(
projectsToDelete.map(async (proj) => {
// Step 1: Delete associated uploads first
await db.delete(uploads).where(eq(uploads.projectId, proj.id));
// Step 2: Delete the project itself
await db.delete(projects).where(eq(projects.id, proj.id));
// Step 3: Delete the associated bucket
await removeBucket(proj.id);
})
);
// Get remaining projects
const remainingProjects = allProjects.filter(proj =>
!(
(proj.object && typeof proj.object === "object" && Object.keys(proj.object).length === 0) ||
(proj.object?.objects && Array.isArray(proj.object.objects) && proj.object.objects.length === 0)
)
);
if (remainingProjects.length === 0) {
return { status: 404, message: "No projects found", token };
}
return { status: 200, message: "Projects fetched successfully", data: remainingProjects, token };
} catch (error: any) {
console.log(error.message);
return { status: 500, message: "An error occurred while fetching projects", token };
}
};
export const getEachProjects = async (id: string, token: string) => { export const getEachProjects = async (id: string, token: string) => {
try { try {
const project = await db.select({ const project = await db.select({
@ -24,28 +74,6 @@ export const getEachProjects = async (id: string, token: string) => {
} }
}; };
export const getAllProjects = async (userId: string, token: string) => {
try {
// Fetch all projects for the given user
const allProjects = await db.select({
id: projects.id,
name: projects.name,
description: projects.description,
preview_url: projects.preview_url,
object: projects.object,
}).from(projects).where(eq(projects.userId, userId));
if (allProjects.length === 0) {
return { status: 404, message: "No projects found", token };
}
return { status: 200, message: "Projects fetched successfully", data: allProjects, token };
} catch (error: any) {
console.log(error.message);
return { status: 500, message: "An error occurred while fetching projects", token };
}
};
export const createProject = async (userId: string, token: string) => { export const createProject = async (userId: string, token: string) => {
try { try {
const { id } = await createEmptyProject(userId); const { id } = await createEmptyProject(userId);

View file

@ -11,7 +11,10 @@ const allowedOrigins = [
"https://your-production-site.com", "https://your-production-site.com",
]; ];
const app = new Elysia() const app = new Elysia({
prefix: "",
tags: ["Default"],
})
.use(cors({ .use(cors({
origin: allowedOrigins, origin: allowedOrigins,
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"], methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
@ -46,6 +49,10 @@ const app = new Elysia()
}); });
app.get("/", () => {
return "Hello from PlanPostAI Canvas API";
});
// all routes here // all routes here
app.use(api); app.use(api);

View file

@ -1,4 +1,4 @@
import { boolean, json, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; import { boolean, integer, json, pgTable, text, timestamp, uuid, jsonb } from "drizzle-orm/pg-core";
export const users = pgTable("users", { export const users = pgTable("users", {
id: text("user_id").primaryKey().notNull(), id: text("user_id").primaryKey().notNull(),
@ -9,6 +9,8 @@ export const users = pgTable("users", {
paid_status: text("paid_status"), paid_status: text("paid_status"),
expires_in: text("expires_in"), expires_in: text("expires_in"),
refresh_token: text("refresh_token"), refresh_token: text("refresh_token"),
download_limit: integer("download_limit").notNull().default(10),
downloads_today: jsonb("downloads_today").default({ date: null, count: 0 }),
}); });
export const projects = pgTable("projects", { export const projects = pgTable("projects", {

View file

@ -13,16 +13,27 @@ type User = {
// this will check the user into our local canvas database // this will check the user into our local canvas database
export const checkUserInDB = async (id: string) => { export const checkUserInDB = async (id: string) => {
try { try {
const user = await db.select().from(users).where(eq(users.id, id)); const user = await db
if (user.length > 0) { .select({
return { found: true, id: user[0]?.id }; email: users.email,
} lastName: users.lastName,
else { firstName: users.firstName,
return { found: false }; image: users.image,
} paidStatus: users.paid_status,
expiresIn: users.expires_in,
download_limit: users.download_limit,
download_today: users.downloads_today
})
.from(users)
.where(eq(users.id, id));
return user.length > 0
? { found: true, user: user[0] }
: { found: false };
} catch (error: any) { } catch (error: any) {
console.error("Error in checkUserInDB:", error.message || error.toString()); console.error("Error in checkUserInDB:", error.message || error.toString());
return { status: 500, message: `An error occurred while checking the user in DB` }; return { status: 500, message: "An error occurred while checking the user in DB" };
} }
}; };