added user and role APIs and fixed some items that were missing #3
@@ -31,6 +31,12 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
docker build \
|
docker build \
|
||||||
--build-arg DATABASE_URL=${{ secrets.DATABASE_URL }} \
|
--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 }} .
|
-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 }}
|
docker push gitea.nelson-household.com/hard-at-work/tcg-collectors-server/tcg-collectors-server:${{ github.sha }}
|
||||||
|
|
||||||
|
|||||||
+12
@@ -1,8 +1,20 @@
|
|||||||
FROM node:24-alpine
|
FROM node:24-alpine
|
||||||
|
|
||||||
ARG DATABASE_URL
|
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 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
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import path from 'path'
|
|||||||
import cors from 'cors'
|
import cors from 'cors'
|
||||||
import healthCheckRoutes from './routes/healthcheck'
|
import healthCheckRoutes from './routes/healthcheck'
|
||||||
import authRoutes from './routes/auth'
|
import authRoutes from './routes/auth'
|
||||||
|
import userRoutes from './routes/users'
|
||||||
|
import roleRoutes from './routes/roles'
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
const port = process.env.PORT || 3000
|
const port = process.env.PORT || 3000
|
||||||
@@ -15,6 +17,8 @@ app.use(cors())
|
|||||||
// Mount Routes
|
// Mount Routes
|
||||||
app.use('/healthcheck', healthCheckRoutes)
|
app.use('/healthcheck', healthCheckRoutes)
|
||||||
app.use('/auth', authRoutes)
|
app.use('/auth', authRoutes)
|
||||||
|
app.use('/users', userRoutes)
|
||||||
|
app.use('/roles', roleRoutes)
|
||||||
|
|
||||||
// Default Route (Catch-all for undefined routes)
|
// Default Route (Catch-all for undefined routes)
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
|
|||||||
@@ -7,4 +7,7 @@ export default {
|
|||||||
database: process.env.DB_NAME || 'your_db_name',
|
database: process.env.DB_NAME || 'your_db_name',
|
||||||
port: process.env.DB_PORT || 5432,
|
port: process.env.DB_PORT || 5432,
|
||||||
},
|
},
|
||||||
|
jwtEnv: {
|
||||||
|
secret: process.env.JWT_SECRET || '',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
import bcrypt from 'bcryptjs'
|
import bcrypt from 'bcryptjs'
|
||||||
import { database } from '../../config'
|
import { database, jwtEnv } from '../../config'
|
||||||
|
|
||||||
const { Pool } = require('pg')
|
const { Pool } = require('pg')
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ router.post('/login', async (req, res) => {
|
|||||||
// Issue JWT token
|
// Issue JWT token
|
||||||
const token = jwt.sign(
|
const token = jwt.sign(
|
||||||
{ id: user.id, username: user.username },
|
{ id: user.id, username: user.username },
|
||||||
process.env.JWT_SECRET,
|
jwtEnv.secret,
|
||||||
{ expiresIn: '1h' }
|
{ expiresIn: '1h' }
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,12 +56,12 @@ router.post('/refresh-token', async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Verify the refresh token
|
// 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)
|
// Create a new access token (JWT)
|
||||||
const token = jwt.sign(
|
const token = jwt.sign(
|
||||||
{ id: decoded.id, username: decoded.username },
|
{ id: decoded.id, username: decoded.username },
|
||||||
process.env.JWT_SECRET,
|
jwtEnv.secret,
|
||||||
{ expiresIn: '1h' }
|
{ expiresIn: '1h' }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user