Skip to content
Open
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
2 changes: 1 addition & 1 deletion server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ const app = require('./src/app');
const PORT = process.env.PORT || 5000;

app.listen(PORT, () => {
console.log(`DIGIPIN API is running and API docs can be found at at http://localhost:${PORT}/api-docs`);
console.log(`DIGIPIN API is running and API docs can be found at http://localhost:${PORT}/api-docs`);
});
34 changes: 32 additions & 2 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,45 @@ const swaggerDocument = YAML.parse(fs.readFileSync(path.join(__dirname, '../swag

const app = express();

// Middleware
// Custom middleware: Validate JSON content-type for POST requests
const validateJsonContentType = (req, res, next) => {
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
const contentType = req.headers['content-type'];
if (!contentType || !contentType.includes('application/json')) {
return res.status(415).json({
error: 'Unsupported Media Type: Content-Type must be application/json',
code: 'INVALID_CONTENT_TYPE',
receivedContentType: contentType || 'none',
expectedContentType: 'application/json'
});
}
}
next();
};

// Middleware (order matters - validateJsonContentType before express.json())
app.use(validateJsonContentType);
app.use(cors());
app.use(express.json());
app.use(morgan('dev'));

// Error handler for JSON parsing errors (specifically for malformed JSON)
app.use((err, req, res, next) => {
// Check if it's a JSON parsing error from express.json()
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
return res.status(400).json({
error: 'Invalid JSON payload: ' + err.message,
code: 'INVALID_JSON_SYNTAX',
details: 'Ensure your JSON is properly formatted with correct syntax'
});
}
next(err);
});

// Swagger Docs Route
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));

// DIGIPIN API Routes
app.use('/api/digipin', digipinRoutes);

module.exports = app;
module.exports = app;
225 changes: 200 additions & 25 deletions src/routes/digipin.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,204 @@ const express = require('express');
const router = express.Router();
const { getDigiPin, getLatLngFromDigiPin } = require('../digipin');

router.post('/encode', (req, res) => { const { latitude, longitude } = req.body; try { const code = getDigiPin(latitude, longitude); res.json({ digipin: code }); } catch (e) { res.status(400).json({ error: e.message }); } });

router.post('/decode', (req, res) => { const { digipin } = req.body; try { const coords = getLatLngFromDigiPin(digipin); res.json(coords); } catch (e) { res.status(400).json({ error: e.message }); } });

router.get('/encode', (req, res) => {
const { latitude, longitude } = req.query;
try {
const code = getDigiPin(parseFloat(latitude), parseFloat(longitude));
res.json({ digipin: code });
} catch (e) {
res.status(400).json({ error: e.message });
}
});

router.get('/decode', (req, res) => {
const { digipin } = req.query;
try {
const coords = getLatLngFromDigiPin(digipin);
res.json(coords);
} catch (e) {
res.status(400).json({ error: e.message });
}
});


/**
* Validation helper: Check if value is a valid number
* @param {*} value - Value to validate
* @returns {boolean} - True if value is a valid number
*/
function isValidNumber(value) {
const num = typeof value === 'string' ? parseFloat(value) : value;
return typeof num === 'number' && !isNaN(num) && isFinite(num);
}

/**
* Validation helper: Check if value is a non-empty string
* @param {*} value - Value to validate
* @returns {boolean} - True if value is a non-empty string
*/
function isValidString(value) {
return typeof value === 'string' && value.trim().length > 0;
}

/**
* POST /encode - Encode latitude and longitude into DIGIPIN
* @body {number} latitude - Latitude coordinate
* @body {number} longitude - Longitude coordinate
* @returns {object} {digipin: string} - Encoded DIGIPIN code
*/
router.post('/encode', (req, res) => {
const { latitude, longitude } = req.body;

// Validate inputs
if (latitude === undefined || latitude === null) {
return res.status(400).json({
error: 'Missing required field: latitude',
code: 'MISSING_LATITUDE'
});
}

if (longitude === undefined || longitude === null) {
return res.status(400).json({
error: 'Missing required field: longitude',
code: 'MISSING_LONGITUDE'
});
}

if (!isValidNumber(latitude)) {
return res.status(400).json({
error: 'Invalid latitude: must be a valid number',
code: 'INVALID_LATITUDE'
});
}

if (!isValidNumber(longitude)) {
return res.status(400).json({
error: 'Invalid longitude: must be a valid number',
code: 'INVALID_LONGITUDE'
});
}

try {
const latNum = parseFloat(latitude);
const lonNum = parseFloat(longitude);
const code = getDigiPin(latNum, lonNum);
res.status(200).json({ digipin: code });
} catch (error) {
res.status(400).json({
error: error.message,
code: 'ENCODING_FAILED'
});
}
});

/**
* POST /decode - Decode DIGIPIN to coordinates
* @body {string} digipin - DIGIPIN code to decode
* @returns {object} {latitude: string, longitude: string} - Decoded coordinates
*/
router.post('/decode', (req, res) => {
const { digipin } = req.body;

// Validate input
if (digipin === undefined || digipin === null) {
return res.status(400).json({
error: 'Missing required field: digipin',
code: 'MISSING_DIGIPIN'
});
}

// Validate digipin is a string type
if (typeof digipin !== 'string') {
return res.status(400).json({
error: `Invalid digipin type: expected string, received ${typeof digipin}`,
code: 'INVALID_DIGIPIN_TYPE',
receivedType: typeof digipin
});
}

if (!isValidString(digipin)) {
return res.status(400).json({
error: 'Invalid digipin: must be a non-empty string',
code: 'INVALID_DIGIPIN'
});
}

try {
const coords = getLatLngFromDigiPin(digipin);
res.status(200).json(coords);
} catch (error) {
res.status(400).json({
error: error.message,
code: 'DECODING_FAILED'
});
}
});

/**
* GET /encode - Encode latitude and longitude into DIGIPIN
* @query {number} latitude - Latitude coordinate
* @query {number} longitude - Longitude coordinate
* @returns {object} {digipin: string} - Encoded DIGIPIN code
*/
router.get('/encode', (req, res) => {
const { latitude, longitude } = req.query;

// Validate inputs
if (!latitude || !longitude) {
return res.status(400).json({
error: 'Missing required query parameters: latitude and longitude',
code: 'MISSING_PARAMETERS'
});
}

if (!isValidNumber(latitude)) {
return res.status(400).json({
error: 'Invalid latitude: must be a valid number',
code: 'INVALID_LATITUDE'
});
}

if (!isValidNumber(longitude)) {
return res.status(400).json({
error: 'Invalid longitude: must be a valid number',
code: 'INVALID_LONGITUDE'
});
}

try {
const latNum = parseFloat(latitude);
const lonNum = parseFloat(longitude);
const code = getDigiPin(latNum, lonNum);
res.status(200).json({ digipin: code });
} catch (error) {
res.status(400).json({
error: error.message,
code: 'ENCODING_FAILED'
});
}
});

/**
* GET /decode - Decode DIGIPIN to coordinates
* @query {string} digipin - DIGIPIN code to decode
* @returns {object} {latitude: string, longitude: string} - Decoded coordinates
*/
router.get('/decode', (req, res) => {
const { digipin } = req.query;

// Validate input
if (!digipin) {
return res.status(400).json({
error: 'Missing required query parameter: digipin',
code: 'MISSING_DIGIPIN'
});
}

// Validate digipin is a string type (query params are always strings, but for safety)
if (typeof digipin !== 'string') {
return res.status(400).json({
error: `Invalid digipin type: expected string, received ${typeof digipin}`,
code: 'INVALID_DIGIPIN_TYPE',
receivedType: typeof digipin
});
}

if (!isValidString(digipin)) {
return res.status(400).json({
error: 'Invalid digipin: must be a non-empty string',
code: 'INVALID_DIGIPIN'
});
}

try {
const coords = getLatLngFromDigiPin(digipin);
res.status(200).json(coords);
} catch (error) {
res.status(400).json({
error: error.message,
code: 'DECODING_FAILED'
});
}
});

module.exports = router;