diff --git a/.gitignore b/.gitignore index b590c5e..d032443 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,5 @@ coverage *.tsbuildinfo -*.sql \ No newline at end of file +*.sql +.env \ No newline at end of file diff --git a/api/db.js b/api/db.js new file mode 100644 index 0000000..9612d9d --- /dev/null +++ b/api/db.js @@ -0,0 +1,18 @@ +const mariadb = require('mariadb') +const dotenv = require('dotenv') +dotenv.config(); + + +const pool = mariadb.createPool({ + host: process.env.DB_HOST, + port: process.env.DB_PORT, + user: process.env.DB_USERNAME, + password: process.env.DB_PASSWORD, + connectionLimit: 5, + connectTimeout: 10000, // give it more breathing room + acquireTimeout: 15000, + database: 'ranger_unit_tracker', + ssl: false, +}); + +module.exports = pool; \ No newline at end of file diff --git a/api/index.js b/api/index.js index 9abb8b1..91318fe 100644 --- a/api/index.js +++ b/api/index.js @@ -1,11 +1,17 @@ +const dotenv = require('dotenv') +dotenv.config(); + const express = require('express') const cors = require('cors') + const app = express() app.use(cors()) app.use(express.json()) -const port = 3000 + +const port = 3000; +const pool = require('./db') let applicationData = { app: null, @@ -13,19 +19,72 @@ let applicationData = { status: null, }; -app.post('/application', (req, res) => { - const data = req.body; - applicationData.app = data.App; - applicationData.status = "Pending"; - console.log(applicationData); - res.send('Application received'); +app.post('/application', async (req, res) => { + try { + const App = req.body?.App || {}; + if (!app) return res.status(400).json({ error: 'Missing App payload' }); + + // TODO: replace with current user ID + const memberId = 2; + + const sql = `INSERT INTO applications (member_id, app_version, app_data) VALUES (?, ?, ?);`; + const appVersion = 1; + + const params = [memberId, appVersion, JSON.stringify(App)] + + console.log(params) + + await pool.query(sql, params); + + res.sendStatus(201); + } catch (err) { + console.error('Insert failed:', err); + res.status(500).json({ error: 'Failed to save application' }); + } }); -app.get('/application/me', (req, res) => { - if (applicationData.app) { - res.send(applicationData); - } else { - res.status(204).send(); + +app.get('/application/me', async (req, res) => { + try { + // TODO: replace with current user ID + const applicationId = 1; + + const rows = await pool.query( + 'SELECT * FROM applications WHERE id = ?', + [applicationId] + ); + + if (!Array.isArray(rows) || rows.length === 0) { + return res.sendStatus(204); + } + + return res.status(200).json(rows[0]); + } catch (err) { + console.error('Query failed:', err); + return res.status(500).json({ error: 'Failed to load application' }); + } +}); + + +app.get('/application/all', async (req, res) => { + try { + + const sql = `SELECT + member.name AS member_name, + app.id, + app.member_id, + app.submitted_at, + app.app_status + FROM applications AS app + LEFT JOIN members AS member + ON member.id = app.member_id;` + + const rows = await pool.query(sql); + + res.status(200).json(rows); + } catch { + console.error(err); + res.status(500); } }); @@ -35,6 +94,64 @@ app.post('/application/message', (req, res) => { res.status(200).send(); }); +app.post('/application/approve/:id', async (req, res) => { + const appID = req.params.id; + + const sql = ` + UPDATE applications + SET approved_at = NOW() + WHERE id = ? + AND approved_at IS NULL + AND denied_at IS NULL + `; + try { + const result = await pool.execute(sql, appID); + + console.log(result); + + if (result.affectedRows === 0) { + res.status(400).json('Something went wrong approving the application'); + } + + if (result.affectedRows == 1) { + res.sendStatus(200); + } + + } catch (err) { + console.error('Approve failed:', err); + res.status(500).json({ error: 'Failed to approve application' }); + } +}); + +app.post('/application/deny/:id', async (req, res) => { + const appID = req.params.id; + + const sql = ` + UPDATE applications + SET denied_at = NOW() + WHERE id = ? + AND approved_at IS NULL + AND denied_at IS NULL + `; + try { + const result = await pool.execute(sql, appID); + + console.log(result); + + if (result.affectedRows === 0) { + res.status(400).json('Something went wrong denying the application'); + } + + if (result.affectedRows == 1) { + res.sendStatus(200); + } + + } catch (err) { + console.error('Approve failed:', err); + res.status(500).json({ error: 'Failed to deny application' }); + } +}); + app.listen(port, () => { console.log(`Example app listening on port ${port}`) }) \ No newline at end of file diff --git a/api/package-lock.json b/api/package-lock.json index dbb9ee4..ed6a60b 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -10,7 +10,25 @@ "license": "ISC", "dependencies": { "cors": "^2.8.5", - "express": "^5.1.0" + "dotenv": "^17.2.1", + "express": "^5.1.0", + "mariadb": "^3.4.5", + "mysql2": "^3.14.3" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", + "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.10.0" } }, "node_modules/accepts": { @@ -26,6 +44,15 @@ "node": ">= 0.6" } }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", @@ -153,6 +180,15 @@ } } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -162,6 +198,18 @@ "node": ">= 0.8" } }, + "node_modules/dotenv": { + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", + "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -322,6 +370,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -453,6 +510,55 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/lru.min": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", + "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/mariadb": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.5.tgz", + "integrity": "sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==", + "license": "LGPL-2.1-or-later", + "dependencies": { + "@types/geojson": "^7946.0.16", + "@types/node": "^24.0.13", + "denque": "^2.1.0", + "iconv-lite": "^0.6.3", + "lru-cache": "^10.4.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -510,6 +616,47 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mysql2": { + "version": "3.14.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.3.tgz", + "integrity": "sha512-fD6MLV8XJ1KiNFIF0bS7Msl8eZyhlTDCDl75ajU5SJtpdx9ZPEACulJcqJWr1Y8OYyxsFc4j3+nflpmhxCU5aQ==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", @@ -695,6 +842,11 @@ "node": ">= 18" } }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "node_modules/serve-static": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", @@ -788,6 +940,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -820,6 +981,12 @@ "node": ">= 0.6" } }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/api/package.json b/api/package.json index 80710c7..64bbbab 100644 --- a/api/package.json +++ b/api/package.json @@ -11,6 +11,9 @@ }, "dependencies": { "cors": "^2.8.5", - "express": "^5.1.0" + "dotenv": "^17.2.1", + "express": "^5.1.0", + "mariadb": "^3.4.5", + "mysql2": "^3.14.3" } }