nexusbert commited on
Commit
ee0bba4
·
1 Parent(s): 0ca18f8

milestone2 commit

Browse files
.dockerignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ dist
3
+ .env
4
+ .env.local
5
+ .git
6
+ .gitignore
7
+ *.md
8
+ .vscode
9
+ .idea
10
+ *.log
11
+ npm-debug.log*
12
+ yarn-debug.log*
13
+ yarn-error.log*
14
+
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ node_modules/
2
+ dist/
3
+ .env
4
+ *.log
5
+ .DS_Store
6
+
Dockerfile ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Base Image
2
+ FROM node:20-slim
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Set NODE_ENV for production
8
+ ENV NODE_ENV=production
9
+
10
+ # Install system dependencies (needed for some npm packages)
11
+ RUN apt-get update && apt-get install -y \
12
+ python3 \
13
+ make \
14
+ g++ \
15
+ && rm -rf /var/lib/apt/lists/*
16
+
17
+ # Copy package files
18
+ COPY package.json package-lock.json ./
19
+
20
+ # Install all dependencies (needed for build)
21
+ RUN npm ci && npm cache clean --force
22
+
23
+ # Copy source code
24
+ COPY . .
25
+
26
+ # Build TypeScript (migrations will be compiled to dist/migrations/*.js)
27
+ RUN npm run build
28
+
29
+ # Remove dev dependencies after build
30
+ RUN npm prune --production
31
+
32
+ # Copy and setup startup script
33
+ COPY start.sh ./start.sh
34
+ RUN chmod +x ./start.sh
35
+
36
+ # Expose port (Hugging Face Spaces uses 7860)
37
+ EXPOSE 7860
38
+
39
+ # Use the startup script
40
+ CMD ["./start.sh"]
41
+
ormconfig.ts ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { DataSourceOptions } from "typeorm";
2
+ import dotenv from "dotenv";
3
+ dotenv.config();
4
+
5
+ export const config: DataSourceOptions = {
6
+ type: "postgres",
7
+ url: process.env.DATABASE_URL,
8
+ synchronize: false,
9
+ logging: true,
10
+ entities: ["src/entity/**/*.ts"],
11
+ migrations: ["src/migrations/*.ts"],
12
+ subscribers: [],
13
+ extra: {
14
+ ssl: process.env.DATABASE_URL?.includes("render.com") ? {
15
+ rejectUnauthorized: false,
16
+ } : false,
17
+ },
18
+ };
19
+
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "stylegpt-milestone2",
3
+ "version": "1.0.0",
4
+ "description": "--- title: StyleGPT Milestone2 emoji: 🌍 colorFrom: purple colorTo: red sdk: docker pinned: false ---",
5
+ "main": "index.ts",
6
+ "scripts": {
7
+ "dev": "nodemon --exec ts-node src/index.ts",
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "migration:run": "ts-node src/scripts/run-migrations.ts",
11
+ "migration:generate": "typeorm-ts-node-commonjs migration:generate -d src/utils/dataSource.ts",
12
+ "migration:revert": "typeorm-ts-node-commonjs migration:revert -d src/utils/dataSource.ts"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://huggingface.co/spaces/nexusbert/StyleGPT-milestone2"
17
+ },
18
+ "author": "",
19
+ "license": "ISC",
20
+ "dependencies": {
21
+ "axios": "^1.13.1",
22
+ "bcrypt": "^5.1.1",
23
+ "cloudinary": "^2.8.0",
24
+ "cors": "^2.8.5",
25
+ "dotenv": "^17.2.3",
26
+ "express": "^5.1.0",
27
+ "jsonwebtoken": "^9.0.2",
28
+ "pg": "^8.16.3",
29
+ "reflect-metadata": "^0.2.2",
30
+ "typeorm": "^0.3.27"
31
+ },
32
+ "devDependencies": {
33
+ "@types/bcrypt": "^5.0.2",
34
+ "@types/cors": "^2.8.19",
35
+ "@types/express": "^5.0.5",
36
+ "@types/jsonwebtoken": "^9.0.10",
37
+ "@types/node": "^24.9.2",
38
+ "nodemon": "^3.1.10",
39
+ "ts-node": "^10.9.2",
40
+ "typescript": "^5.9.3"
41
+ }
42
+ }
src/entity/User.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, OneToMany } from "typeorm";
2
+ import { WardrobeItem } from "./WardrobeItem";
3
+
4
+ @Entity()
5
+ export class User {
6
+ @PrimaryGeneratedColumn()
7
+ id!: number;
8
+
9
+ @Column({ unique: true })
10
+ email!: string;
11
+
12
+ @Column()
13
+ name!: string;
14
+
15
+ @Column()
16
+ password!: string;
17
+
18
+ @OneToMany(() => WardrobeItem, (wardrobeItem) => wardrobeItem.user)
19
+ wardrobeItems!: WardrobeItem[];
20
+
21
+ @CreateDateColumn()
22
+ createdAt!: Date;
23
+ }
24
+
src/entity/WardrobeItem.ts ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn } from "typeorm";
2
+ import { User } from "./User";
3
+
4
+ @Entity()
5
+ export class WardrobeItem {
6
+ @PrimaryGeneratedColumn()
7
+ id!: number;
8
+
9
+ @Column()
10
+ imageUrl!: string;
11
+
12
+ @Column()
13
+ category!: string;
14
+
15
+ @Column()
16
+ style!: string;
17
+
18
+ @ManyToOne(() => User, (user) => user.wardrobeItems)
19
+ @JoinColumn({ name: "userId" })
20
+ user!: User;
21
+
22
+ @Column()
23
+ userId!: number;
24
+
25
+ @CreateDateColumn()
26
+ createdAt!: Date;
27
+ }
28
+
src/index.ts ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import "reflect-metadata";
2
+ import express from "express";
3
+ import cors from "cors";
4
+ import dotenv from "dotenv";
5
+ import { AppDataSource } from "./utils/dataSource";
6
+ import authRoute from "./routes/auth";
7
+ import uploadRoute from "./routes/upload";
8
+ import suggestRoute from "./routes/suggest";
9
+ import profileRoute from "./routes/profile";
10
+
11
+ dotenv.config();
12
+
13
+ const app = express();
14
+ app.use(cors());
15
+ app.use(express.json({ limit: "10mb" }));
16
+
17
+ app.use("/api/auth", authRoute);
18
+ app.use("/api/profile", profileRoute);
19
+ app.use("/api/upload", uploadRoute);
20
+ app.use("/api/suggest", suggestRoute);
21
+
22
+ app.get("/health", (req, res) => {
23
+ res.json({ status: "healthy", database: AppDataSource.isInitialized });
24
+ });
25
+
26
+ const PORT = process.env.PORT || 7860;
27
+
28
+ AppDataSource.initialize()
29
+ .then(() => {
30
+ console.log("✅ Database connected");
31
+ app.listen(PORT, () => console.log(`🧥 StyleGPT running on port ${PORT}`));
32
+ })
33
+ .catch((error) => console.error("❌ DB connection failed:", error));
34
+
src/middleware/auth.ts ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response, NextFunction } from "express";
2
+ import jwt from "jsonwebtoken";
3
+ import dotenv from "dotenv";
4
+ dotenv.config();
5
+
6
+ export interface AuthRequest extends Request {
7
+ userId?: number;
8
+ userEmail?: string;
9
+ }
10
+
11
+ export const authenticateToken = (
12
+ req: AuthRequest,
13
+ res: Response,
14
+ next: NextFunction
15
+ ) => {
16
+ const authHeader = req.headers["authorization"];
17
+ const token = authHeader && authHeader.split(" ")[1];
18
+
19
+ if (!token) {
20
+ return res.status(401).json({ success: false, error: "No token provided" });
21
+ }
22
+
23
+ const jwtSecret = process.env.JWT_SECRET || "your-secret-key-change-in-production";
24
+
25
+ jwt.verify(token, jwtSecret, (err: any, decoded: any) => {
26
+ if (err) {
27
+ return res.status(403).json({ success: false, error: "Invalid or expired token" });
28
+ }
29
+
30
+ req.userId = decoded.userId;
31
+ req.userEmail = decoded.email;
32
+ next();
33
+ });
34
+ };
35
+
src/migrations/1700000000000-InitWardrobe.ts ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class InitWardrobe1700000000000 implements MigrationInterface {
4
+ public async up(queryRunner: QueryRunner): Promise<void> {
5
+ await queryRunner.query(`
6
+ CREATE TABLE IF NOT EXISTS "user" (
7
+ "id" SERIAL NOT NULL PRIMARY KEY,
8
+ "email" VARCHAR NOT NULL UNIQUE,
9
+ "name" VARCHAR NOT NULL,
10
+ "password" VARCHAR NOT NULL,
11
+ "createdAt" TIMESTAMP NOT NULL DEFAULT now()
12
+ )
13
+ `);
14
+
15
+ await queryRunner.query(`
16
+ CREATE TABLE IF NOT EXISTS "wardrobe_item" (
17
+ "id" SERIAL NOT NULL PRIMARY KEY,
18
+ "imageUrl" VARCHAR NOT NULL,
19
+ "category" VARCHAR NOT NULL,
20
+ "style" VARCHAR NOT NULL,
21
+ "userId" INTEGER NOT NULL,
22
+ "createdAt" TIMESTAMP NOT NULL DEFAULT now(),
23
+ CONSTRAINT "FK_wardrobe_item_user" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE
24
+ )
25
+ `);
26
+
27
+ await queryRunner.query(`
28
+ CREATE INDEX IF NOT EXISTS "IDX_wardrobe_item_userId" ON "wardrobe_item" ("userId")
29
+ `);
30
+ }
31
+
32
+ public async down(queryRunner: QueryRunner): Promise<void> {
33
+ await queryRunner.query(`DROP TABLE IF EXISTS "wardrobe_item"`);
34
+ await queryRunner.query(`DROP TABLE IF EXISTS "user"`);
35
+ }
36
+ }
37
+
src/routes/auth.ts ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from "express";
2
+ import bcrypt from "bcrypt";
3
+ import jwt from "jsonwebtoken";
4
+ import { AppDataSource } from "../utils/dataSource";
5
+ import { User } from "../entity/User";
6
+ import dotenv from "dotenv";
7
+ dotenv.config();
8
+
9
+ const router = express.Router();
10
+
11
+ router.post("/register", async (req, res) => {
12
+ try {
13
+ const { name, email, password } = req.body;
14
+
15
+ if (!name || !email || !password) {
16
+ return res.status(400).json({
17
+ success: false,
18
+ error: "Name, email, and password are required",
19
+ });
20
+ }
21
+
22
+ if (password.length < 6) {
23
+ return res.status(400).json({
24
+ success: false,
25
+ error: "Password must be at least 6 characters",
26
+ });
27
+ }
28
+
29
+ const userRepo = AppDataSource.getRepository(User);
30
+
31
+ const existingUser = await userRepo.findOne({ where: { email } });
32
+ if (existingUser) {
33
+ return res.status(400).json({
34
+ success: false,
35
+ error: "Email already registered",
36
+ });
37
+ }
38
+
39
+ const hashedPassword = await bcrypt.hash(password, 10);
40
+
41
+ const newUser = userRepo.create({
42
+ name,
43
+ email,
44
+ password: hashedPassword,
45
+ });
46
+
47
+ await userRepo.save(newUser);
48
+
49
+ const jwtSecret = process.env.JWT_SECRET || "your-secret-key-change-in-production";
50
+ const token = jwt.sign(
51
+ { userId: newUser.id, email: newUser.email },
52
+ jwtSecret,
53
+ { expiresIn: "7d" }
54
+ );
55
+
56
+ res.status(201).json({
57
+ success: true,
58
+ user: {
59
+ id: newUser.id,
60
+ name: newUser.name,
61
+ email: newUser.email,
62
+ },
63
+ token,
64
+ });
65
+ } catch (error: any) {
66
+ console.error("Registration error:", error);
67
+ res.status(500).json({
68
+ success: false,
69
+ error: error.message || "Registration failed",
70
+ });
71
+ }
72
+ });
73
+
74
+ router.post("/login", async (req, res) => {
75
+ try {
76
+ const { email, password } = req.body;
77
+
78
+ if (!email || !password) {
79
+ return res.status(400).json({
80
+ success: false,
81
+ error: "Email and password are required",
82
+ });
83
+ }
84
+
85
+ const userRepo = AppDataSource.getRepository(User);
86
+ const user = await userRepo.findOne({ where: { email } });
87
+
88
+ if (!user) {
89
+ return res.status(401).json({
90
+ success: false,
91
+ error: "Invalid email or password",
92
+ });
93
+ }
94
+
95
+ const isPasswordValid = await bcrypt.compare(password, user.password);
96
+ if (!isPasswordValid) {
97
+ return res.status(401).json({
98
+ success: false,
99
+ error: "Invalid email or password",
100
+ });
101
+ }
102
+
103
+ const jwtSecret = process.env.JWT_SECRET || "your-secret-key-change-in-production";
104
+ const token = jwt.sign(
105
+ { userId: user.id, email: user.email },
106
+ jwtSecret,
107
+ { expiresIn: "7d" }
108
+ );
109
+
110
+ res.json({
111
+ success: true,
112
+ user: {
113
+ id: user.id,
114
+ name: user.name,
115
+ email: user.email,
116
+ },
117
+ token,
118
+ });
119
+ } catch (error: any) {
120
+ console.error("Login error:", error);
121
+ res.status(500).json({
122
+ success: false,
123
+ error: error.message || "Login failed",
124
+ });
125
+ }
126
+ });
127
+
128
+ export default router;
129
+
src/routes/profile.ts ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from "express";
2
+ import { AppDataSource } from "../utils/dataSource";
3
+ import { User } from "../entity/User";
4
+ import { authenticateToken, AuthRequest } from "../middleware/auth";
5
+
6
+ const router = express.Router();
7
+
8
+ router.get("/", authenticateToken, async (req: AuthRequest, res) => {
9
+ try {
10
+ const userId = req.userId!;
11
+
12
+ const userRepo = AppDataSource.getRepository(User);
13
+ const user = await userRepo.findOne({
14
+ where: { id: userId },
15
+ select: ["id", "name", "email", "createdAt"],
16
+ });
17
+
18
+ if (!user) {
19
+ return res.status(404).json({
20
+ success: false,
21
+ error: "User not found",
22
+ });
23
+ }
24
+
25
+ res.json({
26
+ success: true,
27
+ user: {
28
+ id: user.id,
29
+ name: user.name,
30
+ email: user.email,
31
+ createdAt: user.createdAt,
32
+ },
33
+ });
34
+ } catch (error: any) {
35
+ console.error("Profile error:", error);
36
+ res.status(500).json({
37
+ success: false,
38
+ error: error.message || "Failed to fetch profile",
39
+ });
40
+ }
41
+ });
42
+
43
+ export default router;
44
+
src/routes/suggest.ts ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from "express";
2
+ import axios from "axios";
3
+ import { AppDataSource } from "../utils/dataSource";
4
+ import { WardrobeItem } from "../entity/WardrobeItem";
5
+ import { authenticateToken, AuthRequest } from "../middleware/auth";
6
+ import dotenv from "dotenv";
7
+ dotenv.config();
8
+
9
+ const router = express.Router();
10
+
11
+ router.post("/", authenticateToken, async (req: AuthRequest, res) => {
12
+ try {
13
+ const { message, session_id } = req.body;
14
+ const userId = req.userId!;
15
+
16
+ if (!message) {
17
+ return res.status(400).json({ success: false, error: "message is required" });
18
+ }
19
+
20
+ // Get only the authenticated user's wardrobe
21
+ const itemRepo = AppDataSource.getRepository(WardrobeItem);
22
+ const wardrobe = await itemRepo.find({ where: { userId } });
23
+
24
+ const prompt = `
25
+ User Wardrobe: ${wardrobe.length > 0 ? wardrobe.map(w => `${w.category} (${w.style})`).join(", ") : "No items in wardrobe yet"}
26
+ User request: ${message}
27
+ Suggest a stylish outfit combination from the wardrobe.
28
+ `;
29
+
30
+ const response = await axios.post(
31
+ process.env.CHAT_ENDPOINT!,
32
+ { message: prompt, session_id: session_id || `user_${userId}` },
33
+ { headers: { "Content-Type": "application/json" } }
34
+ );
35
+
36
+ res.json({ success: true, suggestion: response.data });
37
+ } catch (error: any) {
38
+ console.error("Suggest error:", error);
39
+ res.status(500).json({ success: false, error: error.message || "Suggestion failed" });
40
+ }
41
+ });
42
+
43
+ export default router;
44
+
src/routes/upload.ts ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from "express";
2
+ import cloudinary from "../utils/cloudinary";
3
+ import { classifyFashionImage } from "../utils/hfClient";
4
+ import { AppDataSource } from "../utils/dataSource";
5
+ import { WardrobeItem } from "../entity/WardrobeItem";
6
+ import { authenticateToken, AuthRequest } from "../middleware/auth";
7
+
8
+ const router = express.Router();
9
+
10
+ router.post("/", authenticateToken, async (req: AuthRequest, res) => {
11
+ try {
12
+ const { imageUrl } = req.body;
13
+ const userId = req.userId!;
14
+
15
+ if (!imageUrl) {
16
+ return res.status(400).json({ success: false, error: "imageUrl is required" });
17
+ }
18
+
19
+ // Upload image to Cloudinary
20
+ const uploadResult = await cloudinary.uploader.upload(imageUrl, {
21
+ folder: "wardrobe",
22
+ });
23
+
24
+ // Classify using Fashion-CLIP
25
+ const fashionResult = await classifyFashionImage(uploadResult.secure_url);
26
+ const bestLabel = fashionResult[0]?.label || "unknown";
27
+
28
+ // Save in DB with user association
29
+ const itemRepo = AppDataSource.getRepository(WardrobeItem);
30
+ const newItem = itemRepo.create({
31
+ imageUrl: uploadResult.secure_url,
32
+ category: bestLabel,
33
+ style: bestLabel.includes("formal") ? "formal" : "casual",
34
+ userId: userId,
35
+ });
36
+
37
+ await itemRepo.save(newItem);
38
+ res.json({ success: true, item: newItem });
39
+ } catch (error: any) {
40
+ console.error("Upload error:", error);
41
+ res.status(500).json({ success: false, error: error.message || "Upload failed" });
42
+ }
43
+ });
44
+
45
+ export default router;
46
+
src/scripts/run-migrations.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import "reflect-metadata";
2
+ import { AppDataSource } from "../utils/dataSource";
3
+
4
+ async function runMigrations() {
5
+ try {
6
+ console.log("Connecting to database...");
7
+ await AppDataSource.initialize();
8
+ console.log("✅ Database connected");
9
+
10
+ console.log("Running migrations...");
11
+ await AppDataSource.runMigrations();
12
+ console.log("✅ Migrations completed successfully");
13
+
14
+ await AppDataSource.destroy();
15
+ process.exit(0);
16
+ } catch (error) {
17
+ console.error("❌ Migration failed:", error);
18
+ await AppDataSource.destroy();
19
+ process.exit(1);
20
+ }
21
+ }
22
+
23
+ runMigrations();
24
+
src/types/index.d.ts ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface FashionClassification {
2
+ label: string;
3
+ score: number;
4
+ }
5
+
6
+ export interface UploadResponse {
7
+ success: boolean;
8
+ item?: WardrobeItem;
9
+ error?: string;
10
+ }
11
+
12
+ export interface WardrobeItem {
13
+ id: number;
14
+ imageUrl: string;
15
+ category: string;
16
+ style: string;
17
+ userId: number;
18
+ createdAt: Date;
19
+ }
20
+
21
+ export interface User {
22
+ id: number;
23
+ name: string;
24
+ email: string;
25
+ createdAt: Date;
26
+ }
27
+
28
+ export interface AuthResponse {
29
+ success: boolean;
30
+ user?: User;
31
+ token?: string;
32
+ error?: string;
33
+ }
34
+
35
+ export interface SuggestResponse {
36
+ success: boolean;
37
+ suggestion?: any;
38
+ error?: string;
39
+ }
40
+
src/utils/cloudinary.ts ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { v2 as cloudinary } from "cloudinary";
2
+ import dotenv from "dotenv";
3
+ dotenv.config();
4
+
5
+ cloudinary.config({
6
+ cloud_name: process.env.CLOUDINARY_CLOUD_NAME!,
7
+ api_key: process.env.CLOUDINARY_API_KEY!,
8
+ api_secret: process.env.CLOUDINARY_API_SECRET!,
9
+ });
10
+
11
+ export default cloudinary;
12
+
src/utils/dataSource.ts ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import "reflect-metadata";
2
+ import { DataSource } from "typeorm";
3
+ import { WardrobeItem } from "../entity/WardrobeItem";
4
+ import { User } from "../entity/User";
5
+ import dotenv from "dotenv";
6
+ dotenv.config();
7
+
8
+ export const AppDataSource = new DataSource({
9
+ type: "postgres",
10
+ url: process.env.DATABASE_URL,
11
+ synchronize: false,
12
+ logging: true,
13
+ entities: [User, WardrobeItem],
14
+ migrations: [
15
+ process.env.NODE_ENV === "production"
16
+ ? "dist/migrations/*.js"
17
+ : "src/migrations/*.ts"
18
+ ],
19
+ subscribers: [],
20
+ extra: {
21
+ ssl: process.env.DATABASE_URL?.includes("render.com") ? {
22
+ rejectUnauthorized: false,
23
+ } : false,
24
+ },
25
+ });
26
+
src/utils/hfClient.ts ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios from "axios";
2
+ import dotenv from "dotenv";
3
+ dotenv.config();
4
+
5
+ const HF_API_KEY = process.env.HF_API_KEY;
6
+
7
+ export async function classifyFashionImage(imageUrl: string) {
8
+ const endpoint = "https://api-inference.huggingface.co/models/patrickjohncyh/fashion-clip";
9
+
10
+ const payload = {
11
+ inputs: imageUrl,
12
+ parameters: {
13
+ candidate_labels: ["shirt", "pants", "dress", "jacket", "formal", "casual", "streetwear"],
14
+ },
15
+ };
16
+
17
+ const headers = {
18
+ Authorization: `Bearer ${HF_API_KEY}`,
19
+ "Content-Type": "application/json",
20
+ };
21
+
22
+ const response = await axios.post(endpoint, payload, { headers });
23
+ return response.data;
24
+ }
25
+
start.sh ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ echo "🚀 Starting StyleGPT Milestone 2..."
5
+
6
+ # Run migrations using compiled JS
7
+ if [ -f "dist/scripts/run-migrations.js" ]; then
8
+ echo "📦 Running database migrations..."
9
+ node dist/scripts/run-migrations.js || echo "⚠️ Migrations completed or skipped"
10
+ else
11
+ echo "⚠️ Migration script not found, skipping..."
12
+ fi
13
+
14
+ # Start the application
15
+ echo "✅ Starting server on port ${PORT:-7860}..."
16
+ exec node dist/index.js
tsconfig.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "moduleResolution": "node",
14
+ "experimentalDecorators": true,
15
+ "emitDecoratorMetadata": true,
16
+ "strictPropertyInitialization": false
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
21
+