Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: CI

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

jobs:
test:
runs-on: ubuntu-latest

services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: visa_fit_test
MYSQL_USER: visa_fit
MYSQL_PASSWORD: visa_fit_pwd
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8

- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

- name: Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
run: pnpm install --no-frozen-lockfile

- name: Run linting
run: pnpm lint

- name: Run tests
run: pnpm test
env:
NODE_ENV: test
DB_HOST: localhost
DB_PORT: 3306
DB_USER: visa_fit
DB_PASSWORD: visa_fit_pwd
DB_NAME: visa_fit_test
JWT_SECRET: test_jwt_secret

- name: Build backend
run: pnpm backend:build

- name: Build frontend
run: pnpm --filter frontend build
49 changes: 41 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,23 +1,56 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
node_modules/
/.pnp
.pnp.js
.pnpm-store/

# testing
/coverage
coverage/
playwright-report/
test-results/

# production
# production builds
/build
frontend/build/
backend/dist/
backend/build/

# environment variables & secrets
.env
.env.*
!.env.example

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

Thumbs.db
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# IDE & editors
.vscode/
.idea/
*.swp
*.swo
*~

# Docker & database
docker-compose.override.yml
mysql_data/
postgres_data/

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Cursor & other tools (optional - remove if you want to keep these)
.specstory/
88 changes: 44 additions & 44 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -1,45 +1,45 @@
{
"name": "backend",
"version": "1.0.0",
"type": "module",
"description": "Visa Fit Backend API",
"main": "src/index.js",
"scripts": {
"dev": "nodemon src/index.js",
"start": "node src/index.js",
"build": "echo 'No build step needed for ESM'",
"test": "vitest",
"test:coverage": "vitest --coverage",
"lint": "eslint src --ext .js,.ts",
"clean": "rm -rf node_modules dist"
},
"dependencies": {
"express": "^5.0.0-beta.1",
"cors": "^2.8.5",
"helmet": "^7.0.0",
"morgan": "^1.10.0",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.0",
"multer": "^1.4.5-lts.1",
"prisma": "^5.0.0",
"@prisma/client": "^5.0.0",
"zod": "^3.21.0",
"winston": "^3.10.0",
"dotenv": "^16.3.0"
},
"devDependencies": {
"nodemon": "^3.0.0",
"vitest": "^0.34.0",
"supertest": "^6.3.0",
"@types/express": "^4.17.17",
"@types/cors": "^2.8.13",
"@types/bcryptjs": "^2.4.2",
"@types/jsonwebtoken": "^9.0.2",
"@types/multer": "^1.4.7",
"@types/morgan": "^1.9.4",
"@types/supertest": "^2.0.12"
},
"engines": {
"node": ">=18.0.0"
}
}
"name": "backend",
"version": "1.0.0",
"type": "module",
"description": "Visa Fit Backend API",
"main": "src/index.js",
"scripts": {
"dev": "nodemon src/index.js",
"start": "node src/index.js",
"build": "echo 'No build step needed for ESM'",
"test": "vitest",
"test:coverage": "vitest --coverage",
"lint": "eslint src --ext .js,.ts",
"clean": "rm -rf node_modules dist"
},
"dependencies": {
"express": "^4.19.2",
"cors": "^2.8.5",
"helmet": "^7.0.0",
"morgan": "^1.10.0",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.0",
"multer": "^1.4.5-lts.1",
"prisma": "^5.0.0",
"@prisma/client": "^5.0.0",
"zod": "^3.21.0",
"winston": "^3.10.0",
"dotenv": "^16.3.0"
},
"devDependencies": {
"nodemon": "^3.0.0",
"vitest": "^0.34.0",
"supertest": "^6.3.0",
"@types/express": "^4.17.17",
"@types/cors": "^2.8.13",
"@types/bcryptjs": "^2.4.2",
"@types/jsonwebtoken": "^9.0.2",
"@types/multer": "^1.4.7",
"@types/morgan": "^1.9.4",
"@types/supertest": "^2.0.12"
},
"engines": {
"node": ">=18.0.0"
}
}
31 changes: 31 additions & 0 deletions backend/prisma/migrations/20250621195618_init/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-- CreateTable
CREATE TABLE "users" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"email" TEXT NOT NULL,
"pass_hash" TEXT NOT NULL,
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- CreateTable
CREATE TABLE "submissions" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"user_id" INTEGER NOT NULL,
"answers" TEXT NOT NULL,
"verdict" TEXT,
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "submissions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateTable
CREATE TABLE "resumes" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"user_id" INTEGER NOT NULL,
"file_name" TEXT NOT NULL,
"mime_type" TEXT NOT NULL,
"data" BLOB NOT NULL,
"uploaded_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "resumes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateIndex
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
3 changes: 3 additions & 0 deletions backend/prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "sqlite"
51 changes: 51 additions & 0 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "sqlite"
url = "file:./dev.db"
}

model User {
id Int @id @default(autoincrement())
email String @unique
passHash String @map("pass_hash")
createdAt DateTime @default(now()) @map("created_at")

// Relations
submissions Submission[]
resumes Resume[]

@@map("users")
}

model Submission {
id Int @id @default(autoincrement())
userId Int @map("user_id")
answers String // JSON string for SQLite compatibility
verdict String? // "eligible" | "not_eligible" | "pending"
createdAt DateTime @default(now()) @map("created_at")

// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)

@@map("submissions")
}

model Resume {
id Int @id @default(autoincrement())
userId Int @map("user_id")
fileName String @map("file_name")
mimeType String @map("mime_type")
data Bytes // LONGBLOB for file storage
uploadedAt DateTime @default(now()) @map("uploaded_at")

// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)

@@map("resumes")
}
63 changes: 63 additions & 0 deletions backend/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import express from "express";
import cors from "cors";
import helmet from "helmet";
import morgan from "morgan";
import dotenv from "dotenv";
import authRoutes from "./routes/auth.js";

// Load environment variables
dotenv.config();

const app = express();
const PORT = process.env.PORT || 4000;

// Middleware
app.use(helmet());
app.use(
cors({
origin: process.env.FRONTEND_URL || "http://localhost:3000",
credentials: true,
})
);
app.use(morgan("combined"));
app.use(express.json({ limit: "10mb" }));
app.use(express.urlencoded({ extended: true, limit: "10mb" }));

// Health check
app.get("/health", (req, res) => {
res.json({
status: "OK",
timestamp: new Date().toISOString(),
environment: process.env.NODE_ENV || "development",
});
});

// API routes
app.get("/api", (req, res) => {
res.json({ message: "Visa Fit API Server" });
});

// Auth routes
app.use("/api/auth", authRoutes);

// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: "Something went wrong!",
message:
process.env.NODE_ENV === "development"
? err.message
: "Internal server error",
});
});

// 404 handler
app.use("*", (req, res) => {
res.status(404).json({ error: "Route not found" });
});

app.listen(PORT, () => {
console.log(`πŸš€ Server running on port ${PORT}`);
console.log(`πŸ“Š Health check: http://localhost:${PORT}/health`);
});
Loading
Loading