Skip to content

Commit 253cc20

Browse files
authored
Merge branch 'backend' into feature/STR-81-login-signup-for-users
2 parents 7c735a0 + 98f82fe commit 253cc20

File tree

11 files changed

+238
-117
lines changed

11 files changed

+238
-117
lines changed

.github/workflows/backend.yml

Lines changed: 0 additions & 38 deletions
This file was deleted.

.github/workflows/cd-backend.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: CD - Deploy Backend
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
build_number:
7+
description: 'CI Build Number'
8+
required: true
9+
type: string
10+
11+
jobs:
12+
deploy:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
19+
- name: Download build artifact from CI workflow
20+
env:
21+
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
22+
run: |
23+
echo "Fetching artifact for build #${{ inputs.build_number }}"
24+
25+
CI_RUN_ID=$(gh run list --workflow "CI - Build Backend" --json databaseId,number -q ".[] | select(.number == ${{ inputs.build_number }}) | .databaseId")
26+
27+
if [ -z "$CI_RUN_ID" ]; then
28+
echo "Could not find CI run with build number ${{ inputs.build_number }}"
29+
exit 1
30+
fi
31+
32+
echo "Found CI run ID: $CI_RUN_ID"
33+
gh run download $CI_RUN_ID --name node-backend-${{ inputs.build_number }}
34+
ls -l
35+
36+
- name: Extract artifact
37+
run: |
38+
tar -xzf node-backend-${{ inputs.build_number }}.tar.gz
39+
ls -la output
40+
41+
- name: Setup Node.js
42+
uses: actions/setup-node@v4
43+
with:
44+
node-version: "20"
45+
46+
- name: Install Vercel CLI
47+
run: npm install -g vercel
48+
49+
- name: Deploy to Vercel
50+
env:
51+
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
52+
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID_BACKEND }}
53+
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_BACKEND }}
54+
run: |
55+
cd output
56+
vercel pull --yes --environment=production --token=$VERCEL_TOKEN
57+
vercel deploy --prod --token=$VERCEL_TOKEN

.github/workflows/ci-backend.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: CI - Build Backend
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'shatter-backend/**'
8+
9+
pull_request:
10+
branches: [main]
11+
paths:
12+
- 'shatter-backend/**'
13+
14+
jobs:
15+
build:
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- name: Checkout code
20+
uses: actions/checkout@v4
21+
22+
- name: Setup Node.js
23+
uses: actions/setup-node@v4
24+
with:
25+
node-version: "20"
26+
27+
- name: Install dependencies
28+
run: |
29+
cd shatter-backend
30+
npm ci
31+
32+
- name: Build project
33+
run: |
34+
cd shatter-backend
35+
npm run build
36+
37+
- name: Package build artifacts
38+
run: |
39+
cd shatter-backend
40+
mkdir output
41+
cp -r dist package.json package-lock.json vercel.json output/
42+
tar -czf node-backend-${{ github.run_number }}.tar.gz output
43+
mv node-backend-${{ github.run_number }}.tar.gz $GITHUB_WORKSPACE/
44+
45+
- name: Upload artifact
46+
uses: actions/upload-artifact@v4
47+
with:
48+
name: node-backend-${{ github.run_number }}
49+
path: node-backend-${{ github.run_number }}.tar.gz

shatter-backend/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ dist-ssr
2424
*.sw?
2525

2626
.env
27+
.vercel

shatter-backend/api/index.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import 'dotenv/config';
2+
import mongoose from 'mongoose';
3+
import app from '../src/app';
4+
5+
const MONGODB_URI = process.env.MONGO_URI;
6+
7+
let connectionPromise: Promise<void> | null = null;
8+
9+
async function connectDB() {
10+
if (mongoose.connection.readyState === 1) {
11+
return;
12+
}
13+
14+
if (mongoose.connection.readyState !== 0) {
15+
await mongoose.disconnect();
16+
connectionPromise = null;
17+
}
18+
19+
if (connectionPromise) {
20+
return connectionPromise;
21+
}
22+
23+
if (!MONGODB_URI) {
24+
throw new Error('MONGO_URI is not set in environment variables');
25+
}
26+
27+
connectionPromise = (async () => {
28+
try {
29+
await mongoose.connect(MONGODB_URI, {
30+
maxPoolSize: 10,
31+
minPoolSize: 2,
32+
maxIdleTimeMS: 30000,
33+
serverSelectionTimeoutMS: 5000,
34+
socketTimeoutMS: 45000,
35+
});
36+
37+
mongoose.connection.on('error', (err) => {
38+
console.error('MongoDB connection error:', err);
39+
connectionPromise = null;
40+
});
41+
42+
mongoose.connection.on('disconnected', () => {
43+
console.log('MongoDB disconnected');
44+
connectionPromise = null;
45+
});
46+
47+
} catch (error) {
48+
console.error('Failed to connect to MongoDB:', error);
49+
connectionPromise = null;
50+
throw error;
51+
}
52+
})();
53+
54+
return connectionPromise;
55+
}
56+
57+
connectDB().catch(console.error);
58+
59+
app.use(async (req, res, next) => {
60+
try {
61+
await connectDB();
62+
next();
63+
} catch (error: any) {
64+
res.status(500).json({
65+
error: 'Database connection failed',
66+
message: error.message
67+
});
68+
}
69+
});
70+
71+
export default app;

shatter-backend/src/app.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,16 @@
1-
// Express framework gives us fucnctions to creeate web server, handle routes, process HTTP req and send responses
21
import express from 'express';
3-
import userRoutes from './routes/user_route.ts'; // these routes define how to handel requests to /api/users
2+
import userRoutes from './routes/user_route'; // these routes define how to handle requests to /api/users
43
import authRoutes from './routes/auth_routes';
54

6-
// Creating express application as a single "app" object, started in server.ts
7-
// This object represents entire web server and will be used to:
8-
// register Middleware, define routes ....
95
const app = express();
106

11-
// Middleware setup
12-
// responsible for parsing incoming JSON request bodies, making req.body usable in controllers
137
app.use(express.json());
148

15-
// Defining routes
16-
// a simple route that sends back plain text "Hello" when anyone visits the app in browser
179
app.get('/', (_req, res) => {
1810
res.send('Hello');
1911
});
2012

21-
// Mounts the user routes under the path 'api/users'
22-
// any routes defined in user_route.ts will be accessible with URLs starting with /api/users
2313
app.use('/api/users', userRoutes);
2414
app.use('/api/auth', authRoutes);
2515

26-
27-
// Export the configured Express app, so that server.ts can import it
28-
// Keeping app setup separate from server startup makes the code modular and testable
2916
export default app;
Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,41 @@
1-
// import req and res types for type safety
2-
import { Request, Response } from 'express';
3-
import { User } from '../models/user_model.ts'; // imports user model created with mongoose
1+
import { Request, Response } from "express";
2+
import { User } from "../models/user_model";
43

54
// controller: GET /api/users
65
// This function handles GET reqs to /api/users
76
// It fetches all users from MongoDB and sends them as json
87

98
export const getUsers = async (_req: Request, res: Response) => {
10-
try {
11-
// retrieves all docs from "users" collection
12-
const users = await User.find().lean(); // .lean() returns plain JS objects instead of Mongoose docs, may change incase we need extra model methods later
13-
res.json(users); // sends list of users back as JSON response
14-
} catch (err) { // log the error if something goes wrong
15-
console.error('GET /api/users error:', err);
16-
res.status(500).json({ error: 'Failed to fetch users' });
17-
}
9+
try {
10+
const users = await User.find().lean();
11+
res.json(users);
12+
} catch (err: any) {
13+
console.error("GET /api/users error:", err);
14+
res.status(500).json({
15+
error: "Failed to fetch users",
16+
message: err.message,
17+
});
18+
}
1819
};
1920

20-
2121
// controller: POST /api/users
2222
// reads data from req body, vailidates it and creates a new user
2323
export const createUser = async (req: Request, res: Response) => {
24-
try {
25-
// Destructure the req body sent by the client
26-
// The ?? {} ensures we don't get error if req.body is undefined
27-
const { name, email } = req.body ?? {};
24+
try {
25+
const { name, email } = req.body ?? {};
2826

29-
// Basic validation to ensure both name and email are provided
30-
// if not respond with bad request and stop further processes
31-
if (!name || !email) {
32-
return res.status(400).json({ error: 'name and email required' });
33-
}
27+
if (!name || !email) {
28+
return res.status(400).json({ error: "name and email required" });
29+
}
3430

35-
// create a new user doc in DB using Mongoose's .create()
36-
const user = await User.create({ name, email });
37-
// respond with "created" and send back created user as JSON
38-
res.status(201).json(user);
39-
} catch(err: any) {
40-
// Handle duplicate email error
41-
// Mongo DB rejects duplicat value since we have email marked as 'unique'
42-
if (err?.code === 11000) {
43-
return res.status(409).json({ error: 'email already exists' });
44-
}
45-
46-
// for all other errors, log them and return generic 500 response
47-
console.error('POST /api/users error:', err);
48-
res.status(500).json({error: 'Failed to create user' });
31+
const user = await User.create({ name, email });
32+
res.status(201).json(user);
33+
} catch (err: any) {
34+
if (err?.code === 11000) {
35+
return res.status(409).json({ error: "email already exists" });
4936
}
37+
38+
console.error("POST /api/users error:", err);
39+
res.status(500).json({ error: "Failed to create user" });
40+
}
5041
};
Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
// router is like a mini-express app that allows grouping of related routes together
21
import { Router } from 'express';
3-
import { getUsers, createUser } from '../controllers/user_controller.ts';
4-
// Importing controller functions that handle logic for each route
5-
// These function define what happens when a req is received
2+
import { getUsers, createUser } from '../controllers/user_controller';
63

7-
// creating new router instance
84
const router = Router();
95

10-
// Defining routes for the /api/users path
11-
router.get('/', getUsers); // when GET req is made, run getUsers func
12-
router.post('/', createUser); // when POST req is made, run creatUser func
6+
router.get('/', getUsers);
7+
router.post('/', createUser);
138

14-
// Export the router so it can be used in app.ts
15-
// app.ts imports router and mounts it under '/api/users'
169
export default router;

shatter-backend/src/server.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,26 @@
1-
// This is the main entry point that starts the application
2-
3-
import 'dotenv/config'; // loads .env file and populates process.env (the node.js object)
4-
import mongoose from 'mongoose'; // mongoose is an Object Data Modelling (ODM) livrary for MongoDB
5-
import app from './app.ts'; // Import the express app
6-
1+
import 'dotenv/config';
2+
import mongoose from 'mongoose';
3+
import app from './app';
74

85
// config
6+
const PORT = process.env.PORT ? Number(process.env.PORT) : 4000;
7+
const MONGODB_URI = process.env.MONGO_URI;
98

10-
const PORT = process.env.PORT ? Number(process.env.PORT) : 4000; // use defined PORT in .env if present , otherwise default to 400
11-
const MONGODB_URI = process.env.MONGO_URI; // read the mongoDB connection from env variables
12-
13-
// Start up function
149
async function start() {
1510
try {
16-
if (!MONGODB_URI) { // check that MOGO URI is provided in .env
11+
if (!MONGODB_URI) {
1712
throw new Error('MONGODB_URI is not set');
1813
}
19-
await mongoose.connect(MONGODB_URI); // this returns a promise, so we 'await' until it's successfully connected
14+
await mongoose.connect(MONGODB_URI);
2015
console.log('Successfully connected to MongoDB');
2116

2217
// start listening for incoming HTTP requests on chosen port
2318
app.listen(PORT, () => {
2419
console.log('Server running on http://localhost:${PORT}');
2520
});
26-
} catch (err) { // if connection goes wrong, log the error
21+
} catch (err) {
2722
console.error('Failed to start server:', err);
28-
process.exit(1); // code 1 indicates failure
23+
process.exit(1);
2924
}
3025
}
3126

0 commit comments

Comments
 (0)