This commit is contained in:
shivam 2025-03-18 21:38:09 +05:30
commit a501ddf0d6
12 changed files with 99 additions and 56 deletions

30
nginx.conf Normal file
View File

@ -0,0 +1,30 @@
user nginx;
worker_processes auto;
http {
# ...existing code...
server {
server_name kings.com;
root /var/www/kings-frontend; # Path to your built frontend files
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# ...existing code...
}
server {
server_name backend.kings.com;
location / {
proxy_pass http://localhost:3000; // Forward to your Node.js backend
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
# ...existing code...
}
}
# ...existing code...
}

View File

@ -27,15 +27,17 @@ exports.publishResult = async (data, authorization) => {
const [admin] = await db.query('SELECT id FROM admins WHERE session_token = ?', [token]); const [admin] = await db.query('SELECT id FROM admins WHERE session_token = ?', [token]);
if (!admin) throw { status: 401, message: 'Unauthorized' }; if (!admin) throw { status: 401, message: 'Unauthorized' };
const { team, date, result } = data; const { team, date, result, announcement_time } = data; // renamed draw_time
// validate if the team exists // validate if the team exists
const teams = await db.query('SELECT id FROM teams WHERE name = ?', [team.toUpperCase()]); const teams = await db.query('SELECT id FROM teams WHERE name = ?', [team.toUpperCase()]);
if (!teams.length) throw { status: 400, message: 'Team does not exist. Create team first.' }; if (!teams.length) throw { status: 400, message: 'Team does not exist. Create team first.' };
// publish result using team id // publish result using team id
await db.query(` await db.query(`
INSERT INTO results (team_id, result_date, result) INSERT INTO results (team_id, result_date, result, announcement_time)
VALUES (?, ?, ?) VALUES (?, ?, ?, COALESCE(?, '00:00:00'))
ON DUPLICATE KEY UPDATE result = VALUES(result) ON DUPLICATE KEY UPDATE
`, [teams[0].id, date, result]); result = VALUES(result),
announcement_time = VALUES(announcement_time)
`, [teams[0].id, date, result, announcement_time]);
}; };

View File

@ -7,7 +7,7 @@ exports.getMonthlyResults = async (req, res) => {
return res.status(400).json({ error: 'Team and month are required.' }); return res.status(400).json({ error: 'Team and month are required.' });
} }
const results = await db.query(` const results = await db.query(`
SELECT r.result, r.result_date SELECT r.result, r.result_date, r.announcement_time
FROM results r FROM results r
JOIN teams t ON r.team_id = t.id JOIN teams t ON r.team_id = t.id
WHERE t.name = ? AND DATE_FORMAT(r.result_date, '%Y-%m') = ? WHERE t.name = ? AND DATE_FORMAT(r.result_date, '%Y-%m') = ?
@ -28,7 +28,7 @@ exports.getDailyResults = async (req, res) => {
return res.status(400).json({ error: 'Date query parameter is required.' }); return res.status(400).json({ error: 'Date query parameter is required.' });
} }
const results = await db.query(` const results = await db.query(`
SELECT t.name as team, r.result, r.result_date SELECT t.name as team, r.result, r.result_date, r.announcement_time
FROM results r FROM results r
JOIN teams t ON r.team_id = t.id JOIN teams t ON r.team_id = t.id
WHERE r.result_date = ? WHERE r.result_date = ?

View File

@ -12,15 +12,15 @@ exports.getAllTeams = async (req, res) => {
exports.createTeam = async (req, res) => { exports.createTeam = async (req, res) => {
try { try {
const { name, announcement_time } = req.body; const { name } = req.body;
if (!name || !announcement_time) { if (!name) {
return res.status(400).json({ error: 'Name and announcement time are required' }); return res.status(400).json({ error: 'Name is required' });
} }
await db.query( await db.query(
'INSERT INTO teams (name, announcement_time) VALUES (?, ?)', 'INSERT INTO teams (name) VALUES (?)',
[name.toUpperCase(), announcement_time] [name.toUpperCase()]
); );
res.status(201).json({ success: true, message: 'Team created successfully' }); res.status(201).json({ success: true, message: 'Team created successfully' });
@ -33,26 +33,14 @@ exports.createTeam = async (req, res) => {
exports.updateTeam = async (req, res) => { exports.updateTeam = async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const { name, announcement_time } = req.body; const { name } = req.body;
if (!name && !announcement_time) { if (!name) {
return res.status(400).json({ error: 'At least one field (name or announcement time) is required' }); return res.status(400).json({ error: 'At least name is required' });
} }
const fields = []; const fields = ['name = ?'];
const values = []; const values = [name.toUpperCase(), id];
if (name) {
fields.push('name = ?');
values.push(name.toUpperCase());
}
if (announcement_time) {
fields.push('announcement_time = ?');
values.push(announcement_time);
}
values.push(id);
await db.query(`UPDATE teams SET ${fields.join(', ')} WHERE id = ?`, values); await db.query(`UPDATE teams SET ${fields.join(', ')} WHERE id = ?`, values);

View File

@ -2,8 +2,7 @@ const Joi = require('joi');
exports.validateTeam = (req, res, next) => { exports.validateTeam = (req, res, next) => {
const schema = Joi.object({ const schema = Joi.object({
name: Joi.string().max(100).required(), name: Joi.string().max(100).required()
announcement_time: Joi.string().pattern(/^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/).required()
}); });
const { error } = schema.validate(req.body); const { error } = schema.validate(req.body);

View File

@ -44,7 +44,7 @@
], ],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{\n \"team\": \"BIKANER SUPER\",\n \"date\": \"2025-03-12\",\n \"result\": \"45\"\n}" "raw": "{\n \"team\": \"BIKANER SUPER\",\n \"date\": \"2025-03-12\",\n \"result\": \"45\",\n \"announcement_time\": \"02:30:00\"\n}"
}, },
"url": { "url": {
"raw": "http://localhost:3000/admin/results", "raw": "http://localhost:3000/admin/results",
@ -77,11 +77,15 @@
{ {
"key": "Content-Type", "key": "Content-Type",
"value": "application/json" "value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer <SESSION_TOKEN>"
} }
], ],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{\n \"name\": \"NEW TEAM\",\n \"announcement_time\": \"02:30:00\"\n}" "raw": "{\n \"name\": \"NEW TEAM\"\n}"
}, },
"url": { "url": {
"raw": "http://localhost:3000/api/teams", "raw": "http://localhost:3000/api/teams",
@ -100,11 +104,15 @@
{ {
"key": "Content-Type", "key": "Content-Type",
"value": "application/json" "value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer <SESSION_TOKEN>"
} }
], ],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{\n \"name\": \"UPDATED TEAM\",\n \"announcement_time\": \"03:00:00\"\n}" "raw": "{\n \"name\": \"UPDATED TEAM\"\n}"
}, },
"url": { "url": {
"raw": "http://localhost:3000/api/teams/1", "raw": "http://localhost:3000/api/teams/1",
@ -119,7 +127,12 @@
"name": "Delete Team", "name": "Delete Team",
"request": { "request": {
"method": "DELETE", "method": "DELETE",
"header": [], "header": [
{
"key": "Authorization",
"value": "Bearer <SESSION_TOKEN>"
}
],
"url": { "url": {
"raw": "http://localhost:3000/api/teams/1", "raw": "http://localhost:3000/api/teams/1",
"protocol": "http", "protocol": "http",
@ -169,7 +182,7 @@
], ],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{\n \"name\": \"<script>alert('xss');</script>\",\n \"announcement_time\": \"02:30:00\"\n}" "raw": "{\n \"name\": \"<script>alert('xss');</script>\"\n}"
}, },
"url": { "url": {
"raw": "http://localhost:3000/api/teams", "raw": "http://localhost:3000/api/teams",

View File

@ -106,27 +106,27 @@ The server will listen on the port specified in your `.env` file (default is 300
### Team Endpoints ### Team Endpoints
- **GET /api/teams** - **GET /api/teams**
Retrieve all teams. Retrieve all teams (public).
- **POST /api/teams** - **POST /api/teams**
Create a new team. Requires `name` and `announcement_time` in the body. Create a new team (admin only).
_Request Body Example:_
```json ```json
{ {
"name": "NEW TEAM", "name": "NEW TEAM"
"announcement_time": "02:30:00"
} }
``` ```
- **PUT /api/teams/:id** - **PUT /api/teams/:id**
Update a team. Update a team (admin only, requires Bearer token).
- **DELETE /api/teams/:id** - **DELETE /api/teams/:id**
Delete a team. Delete a team (admin only, requires Bearer token).
### Testing Sanitization ### Testing Sanitization
A sample endpoint (POST /api/teams) will sanitize HTML input. For example, sending: A sample endpoint (POST /api/teams) will sanitize HTML input. For example, sending:
```json ```json
{ {
"name": "<script>alert('xss');</script>", "name": "<script>alert('xss');</script>"
"announcement_time": "02:30:00"
} }
``` ```
will have the `<` and `>` characters escaped to protect against XSS. will have the `<` and `>` characters escaped to protect against XSS.

View File

@ -14,7 +14,7 @@ router.get('/results', async (req, res) => {
} }
const [result] = await db.query(` const [result] = await db.query(`
SELECT r.result, r.result_date, t.name AS team SELECT r.result, r.result_date, r.announcement_time, t.name AS team
FROM results r FROM results r
JOIN teams t ON r.team_id = t.id JOIN teams t ON r.team_id = t.id
WHERE t.name = ? AND r.result_date = ? WHERE t.name = ? AND r.result_date = ?
@ -39,7 +39,7 @@ router.get('/today', async (req, res) => {
} }
const results = await db.query(` const results = await db.query(`
SELECT t.name AS team, r.result, r.result_date SELECT t.name AS team, r.result, r.result_date, r.announcement_time
FROM results r FROM results r
JOIN teams t ON r.team_id = t.id JOIN teams t ON r.team_id = t.id
WHERE r.result_date = ? WHERE r.result_date = ?

View File

@ -2,13 +2,26 @@ const express = require('express');
const router = express.Router(); const router = express.Router();
const teamController = require('../controllers/teamController'); const teamController = require('../controllers/teamController');
const { validateTeam } = require('../middlewares/validation'); const { validateTeam } = require('../middlewares/validation');
const db = require('../db');
async function requireAdmin(req, res, next) {
try {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'Unauthorized' });
const [admin] = await db.query('SELECT id FROM admins WHERE session_token = ?', [token]);
if (!admin) return res.status(401).json({ error: 'Unauthorized' });
next();
} catch {
res.status(401).json({ error: 'Unauthorized' });
}
}
router.get('/', teamController.getAllTeams); router.get('/', teamController.getAllTeams);
router.post('/', validateTeam, teamController.createTeam); router.post('/', requireAdmin, validateTeam, teamController.createTeam);
router.put('/:id', validateTeam, teamController.updateTeam); router.put('/:id', requireAdmin, validateTeam, teamController.updateTeam);
router.delete('/:id', teamController.deleteTeam); router.delete('/:id', requireAdmin, teamController.deleteTeam);
module.exports = router; module.exports = router;

View File

@ -4,7 +4,6 @@ USE kingdb_prod;
CREATE TABLE teams ( CREATE TABLE teams (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL UNIQUE,
announcement_time TIME NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); );
@ -12,6 +11,7 @@ CREATE TABLE results (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
team_id INT NOT NULL, team_id INT NOT NULL,
result_date DATE NOT NULL, result_date DATE NOT NULL,
announcement_time TIME NOT NULL,
result VARCHAR(10) NOT NULL, result VARCHAR(10) NOT NULL,
FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE, FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE,
UNIQUE KEY uniq_team_date (team_id, result_date) UNIQUE KEY uniq_team_date (team_id, result_date)

View File

@ -17,8 +17,7 @@ const BASE_URL = 'http://localhost:3000';
const createTeamResponse = await axios.post( const createTeamResponse = await axios.post(
`${BASE_URL}/api/teams`, `${BASE_URL}/api/teams`,
{ {
name: 'NEW TEAM', name: 'NEW TEAM'
announcement_time: '02:30:00'
}, },
{ {
headers: { Authorization: `Bearer ${sessionToken}` } headers: { Authorization: `Bearer ${sessionToken}` }
@ -35,8 +34,7 @@ const BASE_URL = 'http://localhost:3000';
const updateTeamResponse = await axios.put( const updateTeamResponse = await axios.put(
`${BASE_URL}/api/teams/1`, `${BASE_URL}/api/teams/1`,
{ {
name: 'UPDATED TEAM', name: 'UPDATED TEAM'
announcement_time: '03:00:00'
}, },
{ {
headers: { Authorization: `Bearer ${sessionToken}` } headers: { Authorization: `Bearer ${sessionToken}` }

View File

@ -10,7 +10,7 @@ const teamRoutes = require('./routes/team');
const app = express(); const app = express();
app.use(cors({ origin: ['http://localhost:3000', 'https://your-production-domain.com'] })); app.use(cors({ origin: ['http://localhost:3000', '*', 'https://your-production-domain.com'] }));
app.use(express.json({ limit: '10kb' })); app.use(express.json({ limit: '10kb' }));
app.use(security.anonymizeIP); app.use(security.anonymizeIP);
app.use(security.sanitizeInput); app.use(security.sanitizeInput);