added user and role APIs and fixed some items that were missing #3

Merged
joseph.nelson4456 merged 1 commits from create-users-route into main 2026-05-15 23:34:29 -07:00
7 changed files with 280 additions and 4 deletions
+6
View File
@@ -31,6 +31,12 @@ jobs:
run: |
docker build \
--build-arg DATABASE_URL=${{ secrets.DATABASE_URL }} \
--build-arg DB_HOST=${{ secrets.DB_HOST }} \
--build-arg DB_USERNAME=${{ secrets.DB_USERNAME }} \
--build-arg DB_PASSWORD=${{ secrets.DB_PASSWORD }} \
--build-arg DB_PORT=${{ secrets.DB_PORT }} \
--build-arg DB_DATABASE=${{ secrets.DB_DATABASE }} \
--build-arg JWT_SECRET=${{ secrets.JWT_SECRET }} \
-t gitea.nelson-household.com/hard-at-work/tcg-collectors-server/tcg-collectors-server:${{ github.sha }} .
docker push gitea.nelson-household.com/hard-at-work/tcg-collectors-server/tcg-collectors-server:${{ github.sha }}
+12
View File
@@ -1,8 +1,20 @@
FROM node:24-alpine
ARG DATABASE_URL
ARG DB_HOST
ARG DB_USERNAME
ARG DB_PASSWORD
ARG DB_PORT
ARG DB_DATABASE
ARG JWT_SECRET
ENV DATABASE_URL=${DATABASE_URL}
ENV DB_HOST=${DB_HOST}
ENV DB_USERNAME=${DB_USERNAME}
ENV DB_PASSWORD=${DB_PASSWORD}
ENV DB_PORT=${DB_PORT}
ENV DB_DATABASE=${DB_DATABASE}
ENV JWT_SECRET=${JWT_SECRET}
WORKDIR /app
COPY . .
+4
View File
@@ -3,6 +3,8 @@ import path from 'path'
import cors from 'cors'
import healthCheckRoutes from './routes/healthcheck'
import authRoutes from './routes/auth'
import userRoutes from './routes/users'
import roleRoutes from './routes/roles'
const app = express()
const port = process.env.PORT || 3000
@@ -15,6 +17,8 @@ app.use(cors())
// Mount Routes
app.use('/healthcheck', healthCheckRoutes)
app.use('/auth', authRoutes)
app.use('/users', userRoutes)
app.use('/roles', roleRoutes)
// Default Route (Catch-all for undefined routes)
app.use((req, res, next) => {
+3
View File
@@ -7,4 +7,7 @@ export default {
database: process.env.DB_NAME || 'your_db_name',
port: process.env.DB_PORT || 5432,
},
jwtEnv: {
secret: process.env.JWT_SECRET || '',
},
}
+4 -4
View File
@@ -1,7 +1,7 @@
import express from 'express'
import jwt from 'jsonwebtoken'
import bcrypt from 'bcryptjs'
import { database } from '../../config'
import { database, jwtEnv } from '../../config'
const { Pool } = require('pg')
@@ -35,7 +35,7 @@ router.post('/login', async (req, res) => {
// Issue JWT token
const token = jwt.sign(
{ id: user.id, username: user.username },
process.env.JWT_SECRET,
jwtEnv.secret,
{ expiresIn: '1h' }
)
@@ -56,12 +56,12 @@ router.post('/refresh-token', async (req, res) => {
try {
// Verify the refresh token
const decoded = jwt.verify(refreshToken, process.env.JWT_SECRET)
const decoded = jwt.verify(refreshToken, jwtEnv.secret)
// Create a new access token (JWT)
const token = jwt.sign(
{ id: decoded.id, username: decoded.username },
process.env.JWT_SECRET,
jwtEnv.secret,
{ expiresIn: '1h' }
)
+93
View File
@@ -0,0 +1,93 @@
import express from 'express'
import { database } from '../../config'
const { Pool } = require('pg')
const router = express.Router()
// Create a connection pool to the database
const pool = new Pool(database)
// Create a new role
router.post('/', async (req, res) => {
const { name } = req.body
const client = await pool.connect()
try {
await client.query('BEGIN')
const result = await client.query(
'INSERT INTO roles (name, created_at, updated_at) VALUES ($1, NOW(), NOW()) RETURNING *',
[name]
)
await client.query('COMMIT')
res.status(201).json(result.rows[0])
} catch (err) {
await client.query('ROLLBACK')
res.status(500).json({ error: err.message })
}
})
// Get all roles
router.get('/', async (req, res) => {
try {
const result = await pool.query('SELECT * FROM roles')
res.json(result.rows)
} catch (err) {
res.status(500).json({ error: err.message })
}
})
// Get a single role by id
router.get('/:id', async (req, res) => {
const { id } = req.params
try {
const result = await pool.query('SELECT * FROM roles WHERE id = $1', [id])
if (result.rows.length === 0)
return res.status(404).json({ error: 'Role not found' })
res.json(result.rows[0])
} catch (err) {
res.status(500).json({ error: err.message })
}
})
// Update a role by id
router.put('/:id', async (req, res) => {
const { id } = req.params
const { name } = req.body
const client = await pool.connect()
try {
await client.query('BEGIN')
const result = await client.query(
'UPDATE roles SET name = $1, updated_at = NOW() WHERE id = $2 RETURNING *',
[name, id]
)
if (result.rows.length === 0)
return res.status(404).json({ error: 'Role not found' })
await client.query('COMMIT')
res.json(result.rows[0])
} catch (err) {
await client.query('ROLLBACK')
res.status(500).json({ error: err.message })
}
})
// Delete a role by id
router.delete('/:id', async (req, res) => {
const { id } = req.params
const client = await pool.connect()
try {
await client.query('BEGIN')
const result = await client.query('DELETE FROM roles WHERE id = $1', [id])
if (result.rows.length === 0)
return res.status(404).json({ error: 'Role not found' })
await client.query('COMMIT')
res.json(result.rows[0])
} catch (err) {
await client.query('ROLLBACK')
res.status(500).json({ error: err.message })
}
})
export default router
+158
View File
@@ -0,0 +1,158 @@
import express from 'express'
import { database } from '../../config'
const { Pool } = require('pg')
const router = express.Router()
// Create a connection pool to the database
const pool = new Pool(database)
/**
* Get all users.
*/
router.get('/', async (req, res) => {
const { rows } = await pool.query('SELECT * FROM users')
res.json(rows)
})
/**
* Create a new user.
*
* @param {string} name - The user's name
* @param {string} email - The user's email address
* @param {string} password - The user's password (must be hashed)
* @param {string} role_name - The name of the role to assign to the user
*/
router.post('/', async (req, res) => {
const { name, email, password, role_name } = req.body
const client = await pool.connect()
try {
await client.query('BEGIN')
// Query for the UUID of the role by name
const roleResult = await client.query(
'SELECT id FROM roles WHERE name = $1',
[role_name]
)
if (roleResult.rows.length === 0) {
throw new Error(`Role ${role_name} not found`)
}
const roleId = roleResult.rows[0].id
// Insert the new user into the database
const result = await client.query(
'INSERT INTO users (name, email, password, role_id, created_at, updated_at) VALUES ($1, $2, $3, $4, NOW(), NOW()) RETURNING *',
[name, email, password, roleId]
)
// Return the newly created user
res.status(201).json(result.rows[0])
await client.query('COMMIT')
} catch (err) {
await client.query('ROLLBACK')
res.status(500).send(err)
} finally {
client.release()
}
})
/**
* Update an existing user.
*
* @param {number} id - The ID of the user to update
* @param {string} name - The new name for the user
* @param {string} email - The new email address for the user (optional)
* @param {string} password - The new password for the user (optional, must be hashed)
* @param {string} role_name - The name of the new role to assign to the user (optional)
*/
router.put('/:id', async (req, res) => {
const { id } = req.params
const { name, email, password, role_name } = req.body
const client = await pool.connect()
try {
await client.query('BEGIN')
// Query for the UUID of the role by name if provided
let roleId = null
if (role_name) {
const roleResult = await client.query(
'SELECT id FROM roles WHERE name = $1',
[role_name]
)
if (roleResult.rows.length === 0) {
throw new Error(`Role ${role_name} not found`)
}
roleId = roleResult.rows[0].id
}
// Update the user in the database
let queryText = 'UPDATE users SET name = $1'
const values = [name]
if (email) {
queryText += ', email = $2'
values.push(email)
}
if (password) {
queryText += ', password = $3'
values.push(password)
}
if (roleId !== null) {
queryText += ', role_id = $4'
values.push(roleId)
}
queryText += ' , updated_at = NOW() WHERE id = $5 RETURNING *'
values.push(id)
const result = await client.query(queryText, values)
// Return the updated user
res.json(result.rows[0])
await client.query('COMMIT')
} catch (err) {
await client.query('ROLLBACK')
res.status(500).send(err)
} finally {
client.release()
}
})
/**
* Delete an existing user.
*
* @param {number} id - The ID of the user to delete
*/
router.delete('/:id', async (req, res) => {
const { id } = req.params
const client = await pool.connect()
try {
await client.query('BEGIN')
// Delete the user from the database
const result = await client.query(
'DELETE FROM users WHERE id = $1 RETURNING *',
[id]
)
// Return the deleted user
res.json(result.rows[0])
await client.query('COMMIT')
} catch (err) {
await client.query('ROLLBACK')
res.status(500).send(err)
} finally {
client.release()
}
})
export default router