Compare commits

..

No commits in common. "main" and "canvas_updated_be" have entirely different histories.

16 changed files with 463 additions and 744 deletions

View file

@ -1,37 +0,0 @@
# Build stage
FROM oven/bun:1 AS builder
# Copy package files
COPY package.json .
COPY bun.lockb .
# Install dependencies
RUN bun install --frozen-lockfile
# Copy source code
COPY . .
# Build the application
RUN bun run build
EXPOSE 5005
# Production stage
# FROM debian:bookworm-slim
# WORKDIR /app
# # Copy only the compiled binary from builder
# COPY --from=builder /app/server .
# # Expose the port your app runs on
# EXPOSE 3000
# # Copy the entrypoint script
# COPY entrypoint.sh .
# Make the entrypoint script executable
RUN chmod +x ./entrypoint.sh
# Set the entrypoint
ENTRYPOINT ["./entrypoint.sh"]

View file

@ -1,53 +0,0 @@
services:
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "5005:5005"
depends_on:
db:
condition: service_healthy
minio:
condition: service_healthy
environment:
NODE_ENV: production
DATABASE_URL: ${DATABASE_URL}
db:
image: postgres:latest
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
# ports:
# - "${DB_PORT}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
minio:
image: minio/minio:latest
# ports:
# - "9000:9000"
# - "9001:9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
command: server /data --console-address ":9001"
volumes:
- minio_data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 30s
timeout: 20s
retries: 3
volumes:
postgres_data:
minio_data:

View file

@ -1,11 +1,11 @@
import { defineConfig } from "drizzle-kit"; import { defineConfig } from 'drizzle-kit';
import { ENV } from "./src/config/env"; import { ENV } from './src/config/env';
export default defineConfig({ export default defineConfig({
out: "./drizzle", out: './drizzle',
schema: "./src/db/schema.ts", schema: './src/db/schema.ts',
dialect: "postgresql", dialect: 'postgresql',
dbCredentials: { dbCredentials: {
url: ENV.DATABASE_URL!, url: ENV.DATABASE_URL!,
}, },
}); });

View file

@ -1,9 +0,0 @@
#!/bin/sh
set -e
# Run migrations
bun run db:migrate
# Start the application
echo "Starting the application..."
./server

View file

@ -3,11 +3,10 @@
"version": "1.0.50", "version": "1.0.50",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"db:studio": "drizzle-kit studio", "db:studio": "drizzle-kit studio --port=3000",
"db:generate": "drizzle-kit generate", "db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate", "db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push:pg", "db:push": "drizzle-kit push:pg",
"build": "bun build --compile --minify-whitespace --minify-syntax --target bun --outfile server ./src/app.ts",
"dev": "bun run --watch src/app.ts" "dev": "bun run --watch src/app.ts"
}, },
"dependencies": { "dependencies": {
@ -15,7 +14,6 @@
"@elysiajs/cookie": "^0.8.0", "@elysiajs/cookie": "^0.8.0",
"@elysiajs/cors": "^1.2.0", "@elysiajs/cors": "^1.2.0",
"@elysiajs/swagger": "^1.2.0", "@elysiajs/swagger": "^1.2.0",
"drizzle-kit": "^0.30.2",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"drizzle-orm": "^0.38.4", "drizzle-orm": "^0.38.4",
"elysia": "latest", "elysia": "latest",
@ -28,6 +26,7 @@
"devDependencies": { "devDependencies": {
"@types/pg": "^8.11.10", "@types/pg": "^8.11.10",
"bun-types": "latest", "bun-types": "latest",
"drizzle-kit": "^0.30.2",
"tsx": "^4.19.2" "tsx": "^4.19.2"
}, },
"module": "src/app.js" "module": "src/app.js"

View file

@ -1,5 +1,5 @@
import { createClerkClient } from "@clerk/backend"; import { createClerkClient } from "@clerk/backend";
import { ENV } from "../../config/env"; import { ENV } from "../../config/env"
import { users } from "../../db/schema"; import { users } from "../../db/schema";
import { db } from "../../db"; import { db } from "../../db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
@ -7,160 +7,119 @@ import { eq } from "drizzle-orm";
// @ts-ignore // @ts-ignore
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { import { checkUserInDB, createUser, storeRefreshToken } from "../../helper/auth/auth.helper";
checkUserInDB,
createUser,
storeRefreshToken,
} from "../../helper/auth/auth.helper";
import { verifyAuth } from "../../middlewares/auth.middlewares"; import { verifyAuth } from "../../middlewares/auth.middlewares";
// Initialize Clerk with your API key // Initialize Clerk with your API key
const clerk = createClerkClient({ secretKey: ENV.CLERK_SECRET_KEY }); const clerk = createClerkClient({ secretKey: ENV.CLERK_SECRET_KEY });
export const getUserData = async (userId: string) => { export const getUserData = async (userId: string) => {
try { try {
const [user, checkInDB] = await Promise.all([ const [user, checkInDB] = await Promise.all([
clerk.users.getUser(userId), clerk.users.getUser(userId),
checkUserInDB(userId), checkUserInDB(userId)
]); ]);
if (user && !checkInDB.found) { if (user && !checkInDB.found) {
// Validate and transform user data
const userDBData = {
id: user.id,
email: user.emailAddresses[0].emailAddress, // Assuming the first email address
firstName: user.firstName || "N/A", // Provide a default value if needed
lastName: user.lastName || "N/A",
image: user.imageUrl,
};
const userData = await createUser(userDBData); // Validate and transform user data
const userDBData = {
id: user.id,
email: user.emailAddresses[0].emailAddress, // Assuming the first email address
firstName: user.firstName || "N/A", // Provide a default value if needed
lastName: user.lastName || "N/A",
image: user.imageUrl,
};
return { const userData = await createUser(userDBData);
status: 200,
message: "User retrieved successfully", return { status: 200, message: "User retrieved successfully", data: userData };
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` };
} }
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 updateUser = async ( export const updateUser = async (id: string, body: {
id: string, paid_status: string,
body: { package_expire_date: string,
paid_status: string; }) => {
package_expire_date: string; 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 });
) => {
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 { return { status: 200, message: "User updated successfully", updateUserData };
status: 200,
message: "User updated successfully", } catch (error: any) {
updateUserData, console.error("Error in updateUser:", error.message || error.toString());
}; return { status: 500, message: `An error occurred while updating the user` };
} catch (error: any) { }
console.error("Error in updateUser:", error.message || error.toString()); }
return {
status: 500,
message: `An error occurred while updating the user`,
};
}
};
export const generateToken = async (context: any) => { export const generateToken = async (context: any) => {
try { try {
const userId = context?.params?.userId; const userId = context?.params?.userId;
const access_cookie = context?.cookie?.access_token?.value; const access_cookie = context?.cookie?.access_token?.value;
const refresh_cookie = context?.cookie?.refresh_token?.value; const refresh_cookie = context?.cookie?.refresh_token?.value;
if (access_cookie !== undefined || refresh_cookie !== undefined) { if (access_cookie !== undefined || refresh_cookie !== undefined) {
const verify = await verifyAuth(context?.cookie); const verify = await verifyAuth(context?.cookie);
return verify; return verify;
} else if (
access_cookie === undefined &&
refresh_cookie === undefined &&
userId !== undefined
) {
const user = await checkUserInDB(userId);
if (user?.found === true) {
// generate access token
const accessToken = jwt.sign({ userId }, ENV.JWT_ACCESS_TOKEN_SECRET, {
expiresIn: "3h",
});
// generate refresh token
const refreshToken = jwt.sign(
{ userId },
ENV.JWT_REFRESH_TOKEN_SECRET,
{ expiresIn: "7d" }
);
// store refresh token in db
const storeRToken = await storeRefreshToken(userId, refreshToken);
if (storeRToken.status === 200) {
context.cookie.access_token.set({
value: accessToken,
httpOnly: true,
secure: true, // Set to true in production
sameSite: "none", // Adjust based on your needs
path: "/",
maxAge: 3 * 60 * 60, // 3 hours in seconds
});
context.cookie.refresh_token.set({
value: refreshToken,
httpOnly: true,
secure: true, // Set to true in production
sameSite: "none", // Adjust based on your needs
path: "/",
maxAge: 7 * 24 * 60 * 60, // 7 days in seconds
});
return {
status: 201,
message: "Token generated successfully",
token: accessToken,
user: user.user,
};
} }
return { else if (access_cookie === undefined && refresh_cookie === undefined && userId !== undefined) {
status: 500, const user = await checkUserInDB(userId);
message: "An error occurred while storing the refresh token", if (user?.found === true) {
};
} else { // generate access token
return { status: 404, message: "User not found" }; const accessToken = jwt.sign({ userId }, ENV.JWT_ACCESS_TOKEN_SECRET, { expiresIn: '3h' });
}
} else { // generate refresh token
return { status: 404, message: "Unauthorized!!!" }; const refreshToken = jwt.sign({ userId }, ENV.JWT_REFRESH_TOKEN_SECRET, { expiresIn: '7d' });
// store refresh token in db
const storeRToken = await storeRefreshToken(userId, refreshToken);
if (storeRToken.status === 200) {
context.cookie.access_token.set({
value: accessToken,
httpOnly: true,
secure: true, // Set to true in production
sameSite: 'none', // Adjust based on your needs
path: "/",
maxAge: 3 * 60 * 60, // 3 hours in seconds
});
context.cookie.refresh_token.set({
value: refreshToken,
httpOnly: true,
secure: true, // Set to true in production
sameSite: 'none', // Adjust based on your needs
path: "/",
maxAge: 7 * 24 * 60 * 60, // 7 days in seconds
});
return { status: 201, message: "Token generated successfully", token: accessToken, user: user.user };
}
return { status: 500, message: "An error occurred while storing the refresh token" };
}
else {
return { status: 404, message: "User not found" };
}
}
else {
return { status: 404, message: "Unauthorized!!!" };
}
} catch (error: any) {
console.error("Error in generateToken:", error.message || error.toString());
return { status: 500, message: `An error occurred while generating the token` };
} }
} catch (error: any) { }
console.error("Error in generateToken:", error.message || error.toString());
return {
status: 500,
message: `An error occurred while generating the token`,
};
}
};

View file

@ -1,80 +1,47 @@
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"; import { verifyAuth } from "../../middlewares/auth.middlewares";
export const authRoute = new Elysia({ prefix: "/auth" }); export const authRoute = new Elysia({
prefix: "/auth",
authRoute.get( tags: ["Auth"],
"/user/:userId",
async ({ params: { userId } }) => await getUserData(userId),
{
detail: { detail: {
tags: ["Auth"], description: "Routes for managing users",
summary: "Get user data", }
}, })
params: t.Object({
userId: t.String(),
}),
}
);
authRoute.post( authRoute.get("/user/:userId", async ({ params: { userId } }) => await getUserData(userId), {
"/user/update/:userId",
async ({ params: { userId }, body }) => await updateUser(userId, body),
{
detail: {
tags: ["Auth"],
summary: "Update user",
},
params: t.Object({ params: t.Object({
userId: t.String(), userId: t.String()
})
});
authRoute.post("/user/update/:userId", async ({ params: { userId }, body }) => await updateUser(userId, body), {
params: t.Object({
userId: t.String()
}), }),
body: t.Object({ body: t.Object({
paid_status: t.String(), paid_status: t.String(),
package_expire_date: t.String(), package_expire_date: t.String(),
}), })
} });
);
authRoute.get( authRoute.get("/generate-token/:userId", async (context) => await generateToken(context));
"/generate-token/:userId",
async (context) => await generateToken(context),
{
detail: {
tags: ["Auth"],
summary: "Generate token",
},
params: t.Object({
userId: t.String(),
}),
}
);
authRoute.get( authRoute.get("/user/me", async ({ cookie }) => {
"/user/me",
async ({ cookie }) => {
const authData = await verifyAuth(cookie); const authData = await verifyAuth(cookie);
if (authData.status !== 200) { if (authData.status !== 200) {
return authData; return authData;
} else {
const userId = 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;
}
} }
}, else {
{ const userId: string | any = authData.userId;
detail: { const response = await getUserData(userId);
tags: ["Auth"], if (response?.status === 200) {
summary: "Get current user", return { ...response.data, token: authData.token, status: 200, message: "User data fetched successfully" };
}, }
} else {
); return response;
}
}
})

View file

@ -1,19 +1,18 @@
import { ENV } from "../../config/env"; import { ENV } from "../../config/env";
export const getAllDesign = async (token: string) => { export const getAllDesign = async (token: string) => {
try { try {
const response = await fetch(`${ENV.CANVAS_SERVER_URL_DEV}/design`, { const response = await fetch(`${ENV.CANVAS_SERVER_URL_DEV}/design`, {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, }
}); });
const data = await response.json(); const data = await response.json();
console.log(response); return data;
return data; } catch (error: any) {
} catch (error: any) { console.log(error);
console.log(error); return { status: 500, message: error.message, token };
return { status: 500, message: error.message, token }; }
} }
};

View file

@ -1,4 +1,4 @@
import { Elysia } from "elysia"; 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";
@ -6,22 +6,17 @@ import { downloadRoute } from "./downloadCount/download.count.route";
import { photoLibraryRoutes } from "./photoLibrary/photo.library.route"; import { photoLibraryRoutes } from "./photoLibrary/photo.library.route";
import { designRoute } from "./design/design.route"; import { designRoute } from "./design/design.route";
export const api = new Elysia({ prefix: "" }) export const api = new Elysia({
.get("/", () => { prefix: "/api",
console.log("Root endpoint accessed"); });
api.get("/", () => {
return "Hello from PlanPostAI Canvas API"; return "Hello from PlanPostAI Canvas API";
}) });
.use(authRoute)
.use(projectRoutes) api.use(authRoute);
.use(uploadRoutes) api.use(projectRoutes);
.use(downloadRoute) api.use(uploadRoutes);
.use(photoLibraryRoutes) api.use(downloadRoute);
.use(designRoute) api.use(photoLibraryRoutes);
.onError(({ code, error, set }) => { api.use(designRoute);
console.error(`API Error: ${code}`, error);
if (code === "NOT_FOUND") {
set.status = 404;
return "API Endpoint Not Found";
}
return "API Error Occurred";
});

View file

@ -6,225 +6,156 @@ 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) => { export const getAllProjects = async (userId: string, token: string) => {
try { try {
// Fetch all projects for the given user // Fetch all projects for the given user
const allProjects = await db const allProjects = await db.select({
.select({ id: projects.id,
id: projects.id, name: projects.name,
name: projects.name, description: projects.description,
description: projects.description, preview_url: projects.preview_url,
preview_url: projects.preview_url, object: projects.object,
object: projects.object, }).from(projects).where(eq(projects.userId, userId));
})
.from(projects)
.where(eq(projects.userId, userId));
// Identify projects where 'object' is empty or 'object.objects' is empty // Identify projects where 'object' is empty or 'object.objects' is empty
const projectsToDelete = allProjects.filter( const projectsToDelete = allProjects.filter(proj =>
(proj) => (proj.object && typeof proj.object === "object" && Object.keys(proj.object).length === 0) ||
(proj.object && (proj.object?.objects && Array.isArray(proj.object.objects) && proj.object.objects.length === 0)
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' // Delete projects with empty 'object' or empty 'object.objects'
await Promise.all( await Promise.all(
projectsToDelete.map(async (proj) => { projectsToDelete.map(async (proj) => {
// Step 1: Delete associated uploads first // Step 1: Delete associated uploads first
await db.delete(uploads).where(eq(uploads.projectId, proj.id)); await db.delete(uploads).where(eq(uploads.projectId, proj.id));
// Step 2: Delete the project itself // Step 2: Delete the project itself
await db.delete(projects).where(eq(projects.id, proj.id)); await db.delete(projects).where(eq(projects.id, proj.id));
// Step 3: Delete the associated bucket // Step 3: Delete the associated bucket
await removeBucket(proj.id); await removeBucket(proj.id);
}) })
); );
// Get remaining projects // Get remaining projects
const remainingProjects = allProjects.filter( const remainingProjects = allProjects.filter(proj =>
(proj) => !(
!( (proj.object && typeof proj.object === "object" && Object.keys(proj.object).length === 0) ||
(proj.object && (proj.object?.objects && Array.isArray(proj.object.objects) && proj.object.objects.length === 0)
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) { if (remainingProjects.length === 0) {
return { status: 404, message: "No projects found", token }; 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 };
} }
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 const project = await db.select({
.select({ id: projects.id,
id: projects.id, name: projects.name,
name: projects.name, description: projects.description,
description: projects.description, preview_url: projects.preview_url,
preview_url: projects.preview_url, object: projects.object,
object: projects.object, }).from(projects).where(eq(projects.id, id)).limit(1);
}) if (project.length === 0) {
.from(projects) return { status: 404, message: "Project not found", token };
.where(eq(projects.id, id)) }
.limit(1); return { status: 200, message: "Project fetched successfully", data: project[0], token };
if (project.length === 0) { } catch (error: any) {
return { status: 404, message: "Project not found", token }; console.log(error.message);
return { status: 500, message: "An error occurred while fetching projects", token };
} }
return {
status: 200,
message: "Project fetched successfully",
data: project[0],
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);
const bucket = await createBucket(id); const bucket = await createBucket(id);
return { return { status: 200, message: "New project created successfully", data: { id, bucketName: bucket }, token };
status: 200,
message: "New project created successfully", } catch (error: any) {
data: { id, bucketName: bucket }, console.log(error.message);
token, return { status: 500, message: "An error occurred while creating projects", token }
}; }
} catch (error: any) {
console.log(error.message);
return {
status: 500,
message: "An error occurred while creating projects",
token,
};
}
}; };
export const updateProject = async ( export const updateProject = async (id: string, body: any, token: string, user_id: string) => {
id: string, try {
body: any, // 1. Validate if project exists
token: string, const existingProject = await db.select().from(projects).where(eq(projects.id, id));
user_id: string if (existingProject.length === 0) {
) => { return { status: 404, message: "Project not found", token };
try { }
// 1. Validate if project exists
const existingProject = await db const { object, name, description, preview_url } = body;
.select() // The preview_url will come from client-side as well, where before updating the project a project capture will be taken and uploaded to the bucket. than the url will be sent to the server.And rest of them are normal process
.from(projects)
.where(eq(projects.id, id)); const updatedProject = await db.update(projects).set({
if (existingProject.length === 0) { object,
return { status: 404, message: "Project not found", token }; name,
description,
preview_url,
userId: user_id,
}).where(eq(projects.id, id)).returning({
id: projects.id,
object: projects.object,
name: projects.name,
description: projects.description,
preview_url: projects.preview_url
});
if (updatedProject.length === 0) {
return { status: 500, message: "Failed to update the project", token };
}
return { status: 200, message: "Project updated successfully", data: updatedProject[0], token };
} catch (error: any) {
console.log("Error updating project:", error.message || error.toString());
return { status: 500, message: "An error occurred while updating the project", token };
} }
const { object, name, description, preview_url } = body;
// The preview_url will come from client-side as well, where before updating the project a project capture will be taken and uploaded to the bucket. than the url will be sent to the server.And rest of them are normal process
const updatedProject = await db
.update(projects)
.set({
object,
name,
description,
preview_url,
userId: user_id,
})
.where(eq(projects.id, id))
.returning({
id: projects.id,
object: projects.object,
name: projects.name,
description: projects.description,
preview_url: projects.preview_url,
});
if (updatedProject.length === 0) {
return { status: 500, message: "Failed to update the project", token };
}
return {
status: 200,
message: "Project updated successfully",
data: updatedProject[0],
token,
};
} catch (error: any) {
console.log("Error updating project:", error.message || error.toString());
return {
status: 500,
message: "An error occurred while updating the project",
token,
};
}
}; };
export const deleteProject = async (id: string, token: string) => { export const deleteProject = async (id: string, token: string) => {
try { try {
const deletedUploads = await db const deletedUploads = await db
.delete(uploads) .delete(uploads)
.where(eq(uploads.projectId, id)) .where(eq(uploads.projectId, id))
.returning({ id: uploads.id }); .returning({ id: uploads.id });
if (deletedUploads.length >= 0) { if (deletedUploads.length >= 0) {
// Step 4: Delete the project // Step 4: Delete the project
const deletedProject = await db const deletedProject = await db
.delete(projects) .delete(projects)
.where(eq(projects.id, id)) .where(eq(projects.id, id))
.returning({ id: projects.id }); .returning({ id: projects.id });
if (deletedProject.length === 0) { if (deletedProject.length === 0) {
return { status: 404, message: "Project not found", token }; return { status: 404, message: "Project not found", token };
} }
// Step 5: Delete the associated bucket // Step 5: Delete the associated bucket
const bucketDeletionResult = await removeBucket(id); const bucketDeletionResult = await removeBucket(id);
if (bucketDeletionResult.status !== 200) { if (bucketDeletionResult.status !== 200) {
return { return {
status: bucketDeletionResult.status, status: bucketDeletionResult.status,
message: `Error deleting bucket: ${bucketDeletionResult.message}`, message: `Error deleting bucket: ${bucketDeletionResult.message}`,
token, token
}; };
} }
return { return { status: 200, message: "Project and associated bucket deleted successfully", token };
status: 200, }
message: "Project and associated bucket deleted successfully", } catch (error: any) {
token, console.log("Error in deleteProject:", error.message || error.toString());
}; return { status: 500, message: "An error occurred while deleting the project", token };
} }
} catch (error: any) {
console.log("Error in deleteProject:", error.message || error.toString());
return {
status: 500,
message: "An error occurred while deleting the project",
token,
};
}
}; };

View file

@ -1,104 +1,87 @@
import { Elysia, t } from "elysia"; import { Elysia, t } from "elysia";
import { import { createProject, deleteProject, getAllProjects, getEachProjects, updateProject } from "./project.controller";
createProject,
deleteProject,
getAllProjects,
getEachProjects,
updateProject,
} from "./project.controller";
import { verifyAuth } from "../../middlewares/auth.middlewares"; import { verifyAuth } from "../../middlewares/auth.middlewares";
export const projectRoutes = new Elysia({ export const projectRoutes = new Elysia({
prefix: "/projects", prefix: "/projects",
tags: ["Projects"], tags: ["Projects"],
detail: { detail: {
description: "Routes for managing projects", description: "Routes for managing projects",
}, }
}).derive(async ({ cookie }) => { }).derive(async ({ cookie }) => {
const authData = await verifyAuth(cookie); const authData = await verifyAuth(cookie);
return { authData }; // Inject into context return { authData }; // Inject into context
}); });
projectRoutes.get( projectRoutes.get("/each/:project_id", async ({ params: { project_id }, authData }) => {
"/each/:project_id", if (authData.status !== 200)
async ({ params: { project_id }, authData }) => { return authData;
if (authData.status !== 200) return authData;
else { else {
const token = authData.token; const token = authData.token;
const response = await getEachProjects(project_id, token); const response = await getEachProjects(project_id, token);
return response; return response;
} }
}, }, {
{
params: t.Object({ params: t.Object({
project_id: t.String(), project_id: t.String()
}), })
} });
);
projectRoutes.get("/", async ({ authData }: any) => { projectRoutes.get("/", async ({ authData }: any) => {
if (authData.status !== 200) return authData; if (authData.status !== 200)
else { return authData;
const userId = authData.userId; else {
const token = authData.token; const userId = authData.userId;
const response = await getAllProjects(userId, token); const token = authData.token;
return response; const response = await getAllProjects(userId, token);
} return response;
}
}); });
projectRoutes.post("/create", async ({ authData }: any) => { projectRoutes.post("/create", async ({ authData }: any) => {
if (authData.status !== 200) return authData; if (authData.status !== 200)
else { return authData;
const userId = authData.userId; else {
const token = authData.token; const userId = authData.userId;
const response = await createProject(userId, token); const token = authData.token;
return response; const response = await createProject(userId, token);
} return response;
}
}); });
projectRoutes.put( projectRoutes.put("/update/:project_id", async ({ body, params: { project_id }, authData }) => {
"/update/:project_id", if (authData.status !== 200)
async ({ body, params: { project_id }, authData }) => { return authData;
if (authData.status !== 200) return authData;
else { else {
const token = authData.token; const token = authData.token;
const user_id = authData?.userId; const user_id = authData?.userId;
// sending user_id to the controller to update the project with the user_id, when user tried to design a existing project from the design project panel // sending user_id to the controller to update the project with the user_id, when user tried to design a existing project from the design project panel
const response = await updateProject( const response = await updateProject(project_id, body, token, user_id as string);
project_id, return response;
body,
token,
user_id as string
);
return response;
} }
}, }, {
{
params: t.Object({ params: t.Object({
project_id: t.String(), project_id: t.String()
}), }),
body: t.Object({ body: t.Object({
object: t.Record(t.String(), t.Any()), // Allows any JSON object object: t.Record(t.String(), t.Any()), // Allows any JSON object
name: t.String(), name: t.String(),
description: t.String(), description: t.String(),
preview_url: t.String(), preview_url: t.String(),
}), })
} });
);
projectRoutes.delete( projectRoutes.delete("/delete/:project_id", async ({ params: { project_id }, authData }) => {
"/delete/:project_id", if (authData.status !== 200)
async ({ params: { project_id }, authData }) => { return authData;
if (authData.status !== 200) return authData;
else { else {
const token = authData.token; const token = authData.token;
const response = await deleteProject(project_id, token); const response = await deleteProject(project_id, token);
return response; return response;
} }
}, }, {
{
params: t.Object({ params: t.Object({
project_id: t.String(), project_id: t.String()
}), })
} });
);

View file

@ -1,52 +1,60 @@
import { Elysia, t } from "elysia"; import { Elysia } from "elysia";
import swagger from "@elysiajs/swagger"; import swagger from '@elysiajs/swagger';
import cors from "@elysiajs/cors";
import { ENV } from "./config/env"; import { ENV } from "./config/env";
import cors from "@elysiajs/cors";
import { api } from "./api"; import { api } from "./api";
const app = new Elysia() const allowedOrigins = [
.use( "http://localhost:5175",
cors({ "http://localhost:5174",
origin: [ "http://localhost:5173",
"http://localhost:5175", "https://dashboard.planpostai.com",
"http://localhost:5174", "https://canvas.planpostai.com",
"https://dashboard.planpostai.com", ];
"https://dev.dashboard.planpostai.com",
"https://canvas.planpostai.com", const app = new Elysia({
"https://canvasdev.planpostai.com", prefix: "",
], tags: ["Default"],
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"], })
allowedHeaders: [ .use(cors({
"Content-Type", origin: allowedOrigins,
"Authorization", methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
"X-Requested-With", allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "Accept", "Origin", "Access-Control-Allow-Origin"],
"Accept", credentials: true,
"Origin", }))
"Access-Control-Allow-Origin", .use(swagger({
], path: "/api/docs",
credentials: true, documentation: {
}) info: {
) title: "Canvas API",
.get("/test", () => "Hello World", {}) version: "1.0.0",
.use(api) description: "Canvas API Documentation",
.use(
swagger({
path: "/swagger",
documentation: {
openapi: "3.1.0",
info: {
title: "Canvas API",
version: "1.0.0",
description: "Canvas API Documentation",
},
}, },
}) tags: [
) {
.listen(ENV.SERVER_PORT); 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.log("hello from app.ts under error");
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}`)
})
app.routes.forEach((route) => {
console.log(`Route: ${route.method} ${route.path}`);
});
console.log(`🦊 Elysia is running at ${ENV.SERVER_URL}`);
console.log(`Swagger docs available at ${ENV.SERVER_URL}/swagger`);

View file

@ -1,17 +1,17 @@
import "dotenv/config"; import 'dotenv/config'
export const ENV = { export const ENV = {
SERVER_URL: process.env.SERVER_URL, SERVER_URL: process.env.SERVER_URL,
SERVER_PORT: process.env.SERVER_PORT || 5000, SERVER_PORT: process.env.SERVER_PORT || 5000,
CANVAS_SERVER_URL_DEV: process.env.CANVAS_SERVER_URL_DEV, CANVAS_SERVER_URL_DEV: process.env.CANVAS_SERVER_URL_DEV,
DATABASE_URL: process.env.DATABASE_URL, DATABASE_URL: process.env.DATABASE_URL,
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
MINIO_ENDPOINT: process.env.MINIO_URL, MINIO_ENDPOINT: process.env.MINIO_ENDPOINT,
MINIO_PORT: process.env.MINIO_PORT, MINIO_PORT: process.env.MINIO_PORT,
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY, CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
JWT_ACCESS_TOKEN_SECRET: process.env.JWT_ACCESS_TOKEN_SECRET, JWT_ACCESS_TOKEN_SECRET: process.env.JWT_ACCESS_TOKEN_SECRET,
JWT_REFRESH_TOKEN_SECRET: process.env.JWT_REFRESH_TOKEN_SECRET, JWT_REFRESH_TOKEN_SECRET: process.env.JWT_REFRESH_TOKEN_SECRET,
PEXELS_URL: process.env.PEXELS_URL, PEXELS_URL: process.env.PEXELS_URL,
PEXELS_ACCESS_KEY: process.env.PEXELS_ACCESS_KEY, PEXELS_ACCESS_KEY: process.env.PEXELS_ACCESS_KEY,
}; }

View file

@ -2,8 +2,9 @@ import { Client } from "minio";
import { ENV } from "../config/env"; import { ENV } from "../config/env";
export const minioClient = new Client({ export const minioClient = new Client({
endPoint: ENV.MINIO_ENDPOINT!.replace("http://", "").replace("https://", ""), endPoint: ENV.MINIO_ENDPOINT!,
useSSL: ENV.MINIO_ENDPOINT!.startsWith("https"), port: ENV.MINIO_PORT,
accessKey: ENV.MINIO_ACCESS_KEY, useSSL: false,
secretKey: ENV.MINIO_SECRET_KEY, accessKey: ENV.MINIO_ACCESS_KEY,
}); secretKey: ENV.MINIO_SECRET_KEY,
})

View file

@ -1,59 +1,35 @@
import { import { boolean, integer, json, pgTable, text, timestamp, uuid, jsonb } from "drizzle-orm/pg-core";
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(),
email: text("email").notNull(), email: text("email").notNull(),
lastName: text("last_name"), lastName: text("last_name"),
firstName: text("first_name"), firstName: text("first_name"),
image: text("image"), image: text("image"),
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), download_limit: integer("download_limit").notNull().default(10),
downloads_today: jsonb("downloads_today").default({ date: null, count: 0 }), downloads_today: jsonb("downloads_today").default({ date: null, count: 0 }),
}); });
export const projects = pgTable("projects", { export const projects = pgTable("projects", {
id: uuid("project_id").defaultRandom().primaryKey(), id: uuid("project_id").defaultRandom().primaryKey(),
userId: text("user_id").references(() => users.id), userId: text("user_id").references(() => users.id),
object: json(), object: json(),
name: text("name"), name: text("name"),
description: text("description"), description: text("description"),
is_public: boolean("is_active").notNull().default(false), is_public: boolean("is_active").notNull().default(false),
preview_url: text("preview_url"), preview_url: text("preview_url"),
created_at: timestamp("created_at").defaultNow(), created_at: timestamp("created_at").defaultNow(),
updated_at: timestamp("updated_at").defaultNow(), updated_at: timestamp("updated_at").defaultNow(),
}); });
export const uploads = pgTable("uploads", { export const uploads = pgTable("uploads", {
id: uuid().defaultRandom().primaryKey(), id: uuid().defaultRandom().primaryKey(),
filename: text("filename").notNull(), filename: text("filename").notNull(),
url: text("url").notNull(), url: text("url").notNull(),
projectId: uuid().references(() => projects.id), projectId: uuid().references(() => projects.id),
created_at: timestamp("created_at").defaultNow(), created_at: timestamp("created_at").defaultNow(),
updated_at: timestamp("updated_at").defaultNow(), updated_at: timestamp("updated_at").defaultNow(),
});
export const shapes = pgTable("shapes", {
id: uuid("shape_id").defaultRandom().primaryKey(),
shapes: text("shapes").notNull(),
created_at: timestamp("created_at").defaultNow(),
updated_at: timestamp("updated_at").defaultNow(),
});
export const category = pgTable("project_category", {
id: uuid("category_id").defaultRandom().primaryKey(),
user_id: uuid().references(() => users.id),
category: text("category").notNull(),
created_at: timestamp("created_at").defaultNow(),
updated_at: timestamp("updated_at").defaultNow(),
}); });