From 0db3d677b20ac73411ad2de1ac07d2655f8ca2bf Mon Sep 17 00:00:00 2001 From: Joseph Nelson Date: Sat, 16 May 2026 22:43:51 -0700 Subject: [PATCH] added image and location APIs (#4) Reviewed-on: https://gitea.nelson-household.com/Hard-at-Work/tcg-collectors-server/pulls/4 Co-authored-by: Joseph Nelson Co-committed-by: Joseph Nelson --- package.json | 1 + src/app.js | 4 ++ src/routes/images/index.js | 129 ++++++++++++++++++++++++++++++++++ src/routes/locations/index.js | 95 +++++++++++++++++++++++++ 4 files changed, 229 insertions(+) create mode 100644 src/routes/images/index.js create mode 100644 src/routes/locations/index.js diff --git a/package.json b/package.json index 9b0c7fa..9a590be 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "csv-parser": "^3.0.0", "dotenv": "^16.0.3", "express": "^4.22.2", + "express-validator": "^7.3.2", "jsonwebtoken": "^9.0.3", "node-pg": "^1.0.1", "p-limit": "^7.3.0", diff --git a/src/app.js b/src/app.js index c62d529..598f2a8 100644 --- a/src/app.js +++ b/src/app.js @@ -5,6 +5,8 @@ import healthCheckRoutes from './routes/healthcheck' import authRoutes from './routes/auth' import userRoutes from './routes/users' import roleRoutes from './routes/roles' +import locationRoutes from './routes/locations' +import imageRoutes from './routes/images' const app = express() const port = process.env.PORT || 3000 @@ -19,6 +21,8 @@ app.use('/healthcheck', healthCheckRoutes) app.use('/auth', authRoutes) app.use('/users', userRoutes) app.use('/roles', roleRoutes) +app.use('/locations', locationRoutes) +app.use('/images', imageRoutes) // Default Route (Catch-all for undefined routes) app.use((req, res, next) => { diff --git a/src/routes/images/index.js b/src/routes/images/index.js new file mode 100644 index 0000000..4439ba7 --- /dev/null +++ b/src/routes/images/index.js @@ -0,0 +1,129 @@ +import express from 'express' +import { Pool } from 'pg' +import { database } from '../../config' + +const { check, validationResult } = require('express-validator') + +const router = express.Router() + +// Create a connection pool to the database +const pool = new Pool(database) + +// CREATE AN IMAGE +router.post( + '/', + [check('file').isString(), check('image').isBase64()], + async (req, res) => { + try { + const errors = validationResult(req) + if (!errors.isEmpty()) + return res.status(400).json({ errors: errors.array() }) + + const { file, image } = req.body + + const client = await pool.connect() + const result = await client.query( + 'INSERT INTO images (file, image, created_at, updated_at) VALUES ($1, $2, NOW(), NOW()) RETURNING *', + [file, image] + ) + client.release() + + return res.status(201).json(result.rows[0]) + } catch (err) { + console.error(err.stack) + return res.status(500).json({ error: 'Server error' }) + } + } +) + +// READ AN IMAGE +router.get('/:id', async (req, res) => { + try { + const { id } = req.params + + const client = await pool.connect() + const result = await client.query('SELECT * FROM images WHERE id = $1', [ + id, + ]) + client.release() + + if (result.rows.length === 0) + return res.status(404).json({ error: 'Image not found' }) + + return res.status(200).json(result.rows[0]) + } catch (err) { + console.error(err.stack) + return res.status(500).json({ error: 'Server error' }) + } +}) + +// UPDATE AN IMAGE +router.put( + '/:id', + [check('file').optional().isString(), check('image').optional().isBase64()], + async (req, res) => { + try { + const errors = validationResult(req) + if (!errors.isEmpty()) + return res.status(400).json({ errors: errors.array() }) + + const { id } = req.params + const { file, image } = req.body + + const client = await pool.connect() + let query = 'UPDATE images SET' + const values = [] + + if (file) { + query += ' file = $1,' + values.push(file) + } + + if (image) { + query += ' image = $2,' + values.push(image) + } + + // Remove trailing comma and space from query string + query = query.slice(0, -1) + + query += ', updated_at = NOW() WHERE id = $3 RETURNING *' + values.push(id) + + const result = await client.query(query, values) + client.release() + + if (result.rows.length === 0) + return res.status(404).json({ error: 'Image not found' }) + + return res.status(200).json(result.rows[0]) + } catch (err) { + console.error(err.stack) + return res.status(500).json({ error: 'Server error' }) + } + } +) + +// DELETE AN IMAGE +router.delete('/:id', async (req, res) => { + try { + const { id } = req.params + + const client = await pool.connect() + const result = await client.query( + 'DELETE FROM images WHERE id = $1 RETURNING *', + [id] + ) + client.release() + + if (result.rows.length === 0) + return res.status(404).json({ error: 'Image not found' }) + + return res.status(200).json(result.rows[0]) + } catch (err) { + console.error(err.stack) + return res.status(500).json({ error: 'Server error' }) + } +}) + +export default router diff --git a/src/routes/locations/index.js b/src/routes/locations/index.js new file mode 100644 index 0000000..2fd32ac --- /dev/null +++ b/src/routes/locations/index.js @@ -0,0 +1,95 @@ +import express from 'express' +import { Pool } from 'pg' +import { database } from '../../config' + +const { check, validationResult } = require('express-validator') + +const router = express.Router() + +// Create a connection pool to the database +const pool = new Pool(database) + +// Create a new location +router.post( + '/', + [check('name', 'Name is required').not().isEmpty()], + async (req, res) => { + const errors = validationResult(req) + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }) + } + try { + const { name } = req.body + await pool.query( + 'INSERT INTO locations (name, created_at, updated_at) VALUES ($1, NOW(), NOW())', + [name] + ) + res.send({ message: 'Location created' }) + } catch (err) { + console.error(err.message) + res.status(500).send('Server error') + } + } +) + +// Get all locations +router.get('/', async (req, res) => { + try { + const result = await pool.query('SELECT * FROM locations') + res.send(result.rows) + } catch (err) { + console.error(err.message) + res.status(500).send('Server error') + } +}) + +// Get a single location by ID +router.get('/:id', async (req, res) => { + try { + const result = await pool.query('SELECT * FROM locations WHERE id = $1', [ + req.params.id, + ]) + if (result.rows.length === 0) + return res.status(404).send('Location not found') + res.send(result.rows[0]) + } catch (err) { + console.error(err.message) + res.status(500).send('Server error') + } +}) + +// Update a location +router.put( + '/:id', + [check('name', 'Name is required').not().isEmpty()], + async (req, res) => { + const errors = validationResult(req) + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }) + } + try { + const { name } = req.body + await pool.query( + 'UPDATE locations SET name = $1, updated_at = NOW() WHERE id = $2', + [name, req.params.id] + ) + res.send({ message: 'Location updated' }) + } catch (err) { + console.error(err.message) + res.status(500).send('Server error') + } + } +) + +// Delete a location +router.delete('/:id', async (req, res) => { + try { + await pool.query('DELETE FROM locations WHERE id = $1', [req.params.id]) + res.send({ message: 'Location deleted' }) + } catch (err) { + console.error(err.message) + res.status(500).send('Server error') + } +}) + +export default router