diff --git a/server/auth.js b/server/auth.js
index 9da0213..bf0d6ad 100644
--- a/server/auth.js
+++ b/server/auth.js
@@ -16,18 +16,13 @@ const createAdmin = async (password) => {
// Verify Admin Login
const verifyAdmin = async (accessKey, password, token) => {
- // 1. Fetch admin from DB
const { rows: [admin] } = await db.query(
'SELECT * FROM admins WHERE access_key = $1',
[accessKey]
);
-
- // 2. Verify password
if (!admin || !await argon2.verify(admin.argon2_hash, password)) {
return false;
}
-
- // 3. Verify TOTP
return speakeasy.totp.verify({
secret: admin.totp_secret,
encoding: 'base32',
diff --git a/server/controllers/adminController.js b/server/controllers/adminController.js
index 8b9c0a9..d4a20ae 100644
--- a/server/controllers/adminController.js
+++ b/server/controllers/adminController.js
@@ -25,13 +25,17 @@ exports.login = async (accessKey, password) => {
exports.publishResult = async (data, authorization) => {
const token = authorization?.split(' ')[1];
const [admin] = await db.query('SELECT id FROM admins WHERE session_token = ?', [token]);
-
if (!admin) throw { status: 401, message: 'Unauthorized' };
const { team, date, result } = data;
+ // validate if the team exists
+ 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.' };
+
+ // publish result using team id
await db.query(`
INSERT INTO results (team_id, result_date, result)
- SELECT id, ?, ? FROM teams WHERE name = ?
+ VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE result = VALUES(result)
- `, [date, result, team.toUpperCase()]);
+ `, [teams[0].id, date, result]);
};
diff --git a/server/controllers/resultController.js b/server/controllers/resultController.js
new file mode 100644
index 0000000..24a44a4
--- /dev/null
+++ b/server/controllers/resultController.js
@@ -0,0 +1,43 @@
+const db = require('../db');
+
+exports.getMonthlyResults = async (req, res) => {
+ try {
+ const { team, month } = req.body;
+ if (!team || !month) {
+ return res.status(400).json({ error: 'Team and month are required.' });
+ }
+ const results = await db.query(`
+ SELECT r.result, r.result_date
+ FROM results r
+ JOIN teams t ON r.team_id = t.id
+ WHERE t.name = ? AND DATE_FORMAT(r.result_date, '%Y-%m') = ?
+ `, [team.toUpperCase(), month]);
+ if (results.length === 0) {
+ return res.status(404).json({ message: 'No results found for this team in the specified month.' });
+ }
+ res.json(results);
+ } catch (error) {
+ res.status(500).json({ error: 'Server error' });
+ }
+};
+
+exports.getDailyResults = async (req, res) => {
+ try {
+ const date = req.query.date;
+ if (!date) {
+ return res.status(400).json({ error: 'Date query parameter is required.' });
+ }
+ const results = await db.query(`
+ SELECT t.name as team, r.result, r.result_date
+ FROM results r
+ JOIN teams t ON r.team_id = t.id
+ WHERE r.result_date = ?
+ `, [date]);
+ if (results.length === 0) {
+ return res.status(404).json({ message: 'No results found for the specified date.' });
+ }
+ res.json(results);
+ } catch (error) {
+ res.status(500).json({ error: 'Server error' });
+ }
+};
diff --git a/server/controllers/teamController.js b/server/controllers/teamController.js
index 5030e5a..1d16f50 100644
--- a/server/controllers/teamController.js
+++ b/server/controllers/teamController.js
@@ -1,6 +1,5 @@
const db = require('../db');
-// Get all teams
exports.getAllTeams = async (req, res) => {
try {
const teams = await db.query('SELECT * FROM teams');
@@ -11,7 +10,6 @@ exports.getAllTeams = async (req, res) => {
}
};
-// Create a new team
exports.createTeam = async (req, res) => {
try {
const { name, announcement_time } = req.body;
@@ -32,7 +30,6 @@ exports.createTeam = async (req, res) => {
}
};
-// Update a team
exports.updateTeam = async (req, res) => {
try {
const { id } = req.params;
@@ -66,7 +63,6 @@ exports.updateTeam = async (req, res) => {
}
};
-// Delete a team
exports.deleteTeam = async (req, res) => {
try {
const { id } = req.params;
diff --git a/server/middlewares/security.js b/server/middlewares/security.js
index 4da2fef..494d807 100644
--- a/server/middlewares/security.js
+++ b/server/middlewares/security.js
@@ -1,17 +1,37 @@
const crypto = require('crypto');
+function sanitize(input) {
+ if (typeof input === 'string') {
+ return input.replace(//g, ">");
+ }
+ if (Array.isArray(input)) {
+ return input.map(sanitize);
+ }
+ if (input && typeof input === 'object') {
+ const sanitizedObj = {};
+ for (const key in input) {
+ if (Object.hasOwnProperty.call(input, key)) {
+ sanitizedObj[key] = sanitize(input[key]);
+ }
+ }
+ return sanitizedObj;
+ }
+ return input;
+}
+
module.exports = {
- // IP Anonymization
anonymizeIP: (req, res, next) => {
const ip = req.ip || '127.0.0.1';
- const salt = Math.floor(Date.now() / 3600000); // Hourly salt
+ const salt = Math.floor(Date.now() / 3600000);
req.anonymizedIP = crypto.createHash('sha3-256')
.update(ip + salt + process.env.IP_PEPPER)
.digest('hex');
next();
},
sanitizeInput: (req, res, next) => {
- // ...implement input sanitization if needed...
+ if (req.body) req.body = sanitize(req.body);
+ if (req.query) req.query = sanitize(req.query);
+ if (req.params) req.params = sanitize(req.params);
next();
}
};
\ No newline at end of file
diff --git a/server/postman_collection.json b/server/postman_collection.json
index a1fcf95..7731854 100644
--- a/server/postman_collection.json
+++ b/server/postman_collection.json
@@ -1,7 +1,7 @@
{
"info": {
"_postman_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
- "name": "Satta Backend API",
+ "name": "Kings Backend API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
@@ -156,6 +156,77 @@
"path": ["api", "health"]
}
}
+ },
+ {
+ "name": "Test Sanitization",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"name\": \"\",\n \"announcement_time\": \"02:30:00\"\n}"
+ },
+ "url": {
+ "raw": "http://localhost:3000/api/teams",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "3000",
+ "path": [
+ "api",
+ "teams"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Get Monthly Results",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"team\": \"BIKANER SUPER\",\n \"month\": \"2025-03\"\n}"
+ },
+ "url": {
+ "raw": "http://localhost:3000/api/results/monthly",
+ "protocol": "http",
+ "host": ["localhost"],
+ "port": "3000",
+ "path": ["api", "results", "monthly"]
+ }
+ }
+ },
+ {
+ "name": "Get Daily Results",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "http://localhost:3000/api/results/daily?date=2025-03-12",
+ "protocol": "http",
+ "host": ["localhost"],
+ "port": "3000",
+ "path": ["api", "results", "daily"],
+ "query": [
+ {
+ "key": "date",
+ "value": "2025-03-12"
+ }
+ ]
+ }
+ }
}
]
}
diff --git a/server/readme.md b/server/readme.md
index 7c42d44..b05a4b1 100644
--- a/server/readme.md
+++ b/server/readme.md
@@ -1,7 +1,7 @@
-# Satta Backend API
+# Kings Backend API
## Overview
-This project provides a backend API for the Satta system. It includes endpoints for managing teams, publishing results, admin authentication, and a simple caching mechanism.
+Kings Backend API is a RESTful API for managing teams, publishing match/show results, and handling admin authentication. It also includes a simple in-memory caching mechanism, input sanitization, and rate limiting for security.
## Prerequisites
- Node.js (v14 or higher)
@@ -9,12 +9,13 @@ This project provides a backend API for the Satta system. It includes endpoints
## Installation
-1. Clone the repository:
+1. **Clone the Repository**
```
git clone
cd kingproject/bazar3
```
-2. Install dependencies:
+
+2. **Install Dependencies**
```
cd server
npm install
@@ -22,7 +23,9 @@ This project provides a backend API for the Satta system. It includes endpoints
## Configuration
-1. Create a `.env` file in `/server` (or modify the existing one) with the following variables:
+1. **Environment Variables**
+
+ Create a `.env` file in the `/server` directory with the following variables:
```
DB_HOST=localhost
DB_USER=user
@@ -35,11 +38,13 @@ This project provides a backend API for the Satta system. It includes endpoints
## Database Setup
-1. Import the schema by running the SQL file `/server/schema.sql` in your MySQL client:
+1. **Import Schema**
+
+ Run the following command in your MySQL client to create the database and tables:
```
mysql -u user -p < server/schema.sql
```
-2. Ensure the database `kingdb_prod` is created with the required tables (teams, results, admins).
+ This creates the `kingdb_prod` database and the required tables: `teams`, `results`, and `admins`.
## Admin Account Setup
@@ -47,64 +52,120 @@ To create an admin account, run:
```
npm run create-admin --
```
-This command will output an `Access Key` which you'll use for admin login.
+This script will output an `Access Key` for admin login.
## Running the Server
-Start the API server with:
+Start the API server by running:
```
npm start
```
-The server listens on the port specified in the `.env` file (default 3000).
+The server will listen on the port specified in your `.env` file (default is 3000).
## API Endpoints
### Public Endpoints
-- **GET /api/results?team=&date=**
- Retrieve the result for a specified team and date.
+- **GET /api/results?team=<TEAM_NAME>&date=<YYYY-MM-DD>**
+ Retrieve the result for a specified team and date.
- **GET /api/today**
Retrieve all results for the current day.
- **GET /api/health**
- Basic health check endpoint to verify server and database connectivity.
+ Health check endpoint to verify server and database connectivity.
+- **POST /api/results/monthly**
+ Get monthly results for a team.
+ _Request Body Example:_
+ ```json
+ {
+ "team": "BIKANER SUPER",
+ "month": "2025-03"
+ }
+ ```
+- **GET /api/results/daily?date=<YYYY-MM-DD>**
+ Get daily results for all teams.
### Admin Endpoints
- **POST /admin/login**
- Login using `accessKey` and `password` to receive a session token.
+ Log in using `accessKey` and `password` to receive a session token.
+ _Request Body Example:_
+ ```json
+ {
+ "accessKey": "",
+ "password": ""
+ }
+ ```
- **POST /admin/results**
- Publish a result. Requires authorization header with the token:
- `Authorization: Bearer `
+ Publish a result. Requires an authorization header with the session token.
+ _Request Body Example:_
+ ```json
+ {
+ "team": "NEW TEAM",
+ "date": "2025-03-12",
+ "result": "45"
+ }
+ ```
### Team Endpoints
- **GET /api/teams**
Retrieve all teams.
- **POST /api/teams**
Create a new team. Requires `name` and `announcement_time` in the body.
+ _Request Body Example:_
+ ```json
+ {
+ "name": "NEW TEAM",
+ "announcement_time": "02:30:00"
+ }
+ ```
- **PUT /api/teams/:id**
Update a team.
- **DELETE /api/teams/:id**
Delete a team.
+### Testing Sanitization
+A sample endpoint (POST /api/teams) will sanitize HTML input. For example, sending:
+```json
+{
+ "name": "",
+ "announcement_time": "02:30:00"
+}
+```
+will have the `<` and `>` characters escaped to protect against XSS.
+
## Testing the API
-A Postman collection is provided in `/server/postman_collection.json`. You can import this collection into Postman to test all endpoints easily.
+1. **Using Postman**
-Additionally, a simple test script is available:
-```
-npm run test-api
-```
-This script uses `axios` to perform a sequence of API calls, including admin login, creating a team, fetching teams, updating, deleting, and publishing a result.
+ Import the Postman collection from `/server/postman_collection.json` to test all endpoints, including admin authentication, team management, result retrieval, and sanitization.
+
+2. **Using the Test Script**
+
+ A test script is available that performs a sequence of API calls:
+ ```
+ npm run test-api
+ ```
+ This script uses `axios` to:
+ - Log in as an admin.
+ - Create, fetch, update, and delete teams.
+ - Publish a result.
## Caching
-Results are cached in-memory for 5 minutes. Any write operations (POST, PUT, DELETE) clear the cache automatically.
+
+- Results are cached in memory for 5 minutes.
+- Any write operations (POST, PUT, DELETE) clear the cache automatically.
## Rate Limiting and Security
-- Rate limiting is implemented to allow 100 requests per minute per anonymized IP.
-- IP addresses are anonymized using SHA3-256 with a salt and a secret pepper before being used for rate limiting.
+- **Rate Limiting:**
+ The API allows 100 requests per minute per anonymized IP, using SHA3-256 based IP anonymization.
+- **Input Sanitization:**
+ The middleware sanitizes incoming data (body, query, params) by escaping HTML characters to prevent XSS.
+- **SQL Injection Protection:**
+ SQL queries use prepared statements with parameterized queries, ensuring inputs and queries remain separate.
## Additional Notes
-- For input validation, the project leverages Joi.
-- Changes to the project configuration or dependency versions may require updating the readme accordingly.
+- Input validation is implemented using Joi.
+- Keep your environment variables secure.
+- Modify configurations as necessary when upgrading dependency versions.
## License
Please include your project's license details here.
diff --git a/server/routes/public.js b/server/routes/public.js
index 245b5b4..244c433 100644
--- a/server/routes/public.js
+++ b/server/routes/public.js
@@ -2,8 +2,8 @@ const express = require('express');
const router = express.Router();
const db = require('../db');
const cache = require('../cache');
+const resultController = require('../controllers/resultController');
-// Get specific result
router.get('/results', async (req, res) => {
try {
const { team, date } = req.query;
@@ -29,7 +29,6 @@ router.get('/results', async (req, res) => {
}
});
-// Get all today's results
router.get('/today', async (req, res) => {
try {
const today = new Date().toISOString().split('T')[0];
@@ -63,4 +62,10 @@ router.get('/health', async (req, res) => {
}
});
+// (expects body: { team, month })
+router.post('/results/monthly', resultController.getMonthlyResults);
+
+// (expects query param: date=YYYY-MM-DD)
+router.get('/results/daily', resultController.getDailyResults);
+
module.exports = router;
\ No newline at end of file
diff --git a/server/routes/team.js b/server/routes/team.js
index 979bf5d..15871b2 100644
--- a/server/routes/team.js
+++ b/server/routes/team.js
@@ -3,16 +3,12 @@ const router = express.Router();
const teamController = require('../controllers/teamController');
const { validateTeam } = require('../middlewares/validation');
-// Get all teams
router.get('/', teamController.getAllTeams);
-// Create a new team
router.post('/', validateTeam, teamController.createTeam);
-// Update a team
router.put('/:id', validateTeam, teamController.updateTeam);
-// Delete a team
router.delete('/:id', teamController.deleteTeam);
module.exports = router;
diff --git a/server/scripts/test-api.js b/server/scripts/test-api.js
index 34f866b..157a76f 100644
--- a/server/scripts/test-api.js
+++ b/server/scripts/test-api.js
@@ -13,7 +13,6 @@ const BASE_URL = 'http://localhost:3000';
const sessionToken = loginResponse.data.token;
console.log('Login successful. Session Token:', sessionToken);
- // Create a Team
console.log('Creating a new team...');
const createTeamResponse = await axios.post(
`${BASE_URL}/api/teams`,
@@ -32,7 +31,6 @@ const BASE_URL = 'http://localhost:3000';
const teamsResponse = await axios.get(`${BASE_URL}/api/teams`);
console.log('Teams:', teamsResponse.data);
- // Update a Team
console.log('Updating a team...');
const updateTeamResponse = await axios.put(
`${BASE_URL}/api/teams/1`,
@@ -46,14 +44,12 @@ const BASE_URL = 'http://localhost:3000';
);
console.log('Team updated:', updateTeamResponse.data);
- // Delete a Team
console.log('Deleting a team...');
const deleteTeamResponse = await axios.delete(`${BASE_URL}/api/teams/1`, {
headers: { Authorization: `Bearer ${sessionToken}` }
});
console.log('Team deleted:', deleteTeamResponse.data);
- // Publish a Result
console.log('Publishing a result...');
const publishResultResponse = await axios.post(
`${BASE_URL}/admin/results`,