Refactor license and permission management, add services

Renamed licenceRepository.js to licenseRepository.js and updated its API to use status instead of userId for licenses. Moved permission checking logic from userRepository to permissionsRepository. Added new service layers for license, permissions, and user management, implementing error handling and business logic. Removed the old register.js service and cleaned up test.js. Updated database schema to remove userId from licenses.
This commit is contained in:
Gilles Lazures 2026-01-27 05:49:50 +01:00
parent 1e78bd68cf
commit f8c1340b41
9 changed files with 243 additions and 69 deletions

View File

@ -64,16 +64,11 @@ function initializeDatabase() {
key TEXT NOT NULL UNIQUE, key TEXT NOT NULL UNIQUE,
productId INTEGER NOT NULL, productId INTEGER NOT NULL,
targetIhannel TEXT NOT NULL, targetIhannel TEXT NOT NULL,
userId INTEGER DEFAULT NULL,
usedAt TEXT, usedAt TEXT,
FOREIGN KEY(productId) FOREIGN KEY(productId)
REFERENCES products(id) REFERENCES products(id)
ON DELETE CASCADE, ON DELETE CASCADE,
FOREIGN KEY(userId)
REFERENCES accounts(id)
ON DELETE SET NULL
) )
`) `)
} }

View File

@ -5,13 +5,10 @@ function create(key, productId, targetChannel) {
INSERT INTO licenses ( INSERT INTO licenses (
key, key,
productId, productId,
targetChannel targetChannel,
) status
VALUES (
?,
?,
?
) )
VALUES (?, ?, ?, 'active')
` `
const statement = database.prepare(query) const statement = database.prepare(query)
return statement.run(key, productId, targetChannel) return statement.run(key, productId, targetChannel)
@ -19,7 +16,7 @@ function create(key, productId, targetChannel) {
function findByKey(key) { function findByKey(key) {
const query = ` const query = `
SELECT id, key, productId, targetChannel, userId, usedAt SELECT id, key, productId, targetChannel, status, createdAt
FROM licenses FROM licenses
WHERE key = ? WHERE key = ?
` `
@ -27,24 +24,14 @@ function findByKey(key) {
return statement.get(key) return statement.get(key)
} }
function activate(licenseId, userId) { function updateStatus(id, status) {
const query = ` const query = `
UPDATE licenses UPDATE licenses
SET userId = ?, usedAt = CURRENT_TIMESTAMP SET status = ?
WHERE id = ? AND userId IS NULL WHERE id = ?
` `
const statement = database.prepare(query) const statement = database.prepare(query)
return statement.run(userId, licenseId) return statement.run(status, id)
}
function deactivate(licenseId, userId) {
const query = `
UPDATE licenses
SET userId = ?, usedAt = NULL
WHERE id = ? AND userId IS NULL
`
const statement = database.prepare(query)
return statement.run(userId, licenseId)
} }
function findByProduct(productId) { function findByProduct(productId) {
@ -68,8 +55,7 @@ function remove(id) {
module.exports = { module.exports = {
create, create,
remove, remove,
activate,
findByKey, findByKey,
deactivate, updateStatus,
findByProduct, findByProduct,
} }

View File

@ -53,9 +53,26 @@ function revokeAllOnProduct(userId, productId) {
return statement.run(userId, productId) return statement.run(userId, productId)
} }
function hasPermission(userId, productName, channel, permission) {
const query = `
SELECT 1 FROM permissions p
JOIN products pr ON p.product_id = pr.id
WHERE p.user_id = ?
AND pr.name = ?
AND (p.channel = ? OR p.channel = '*')
AND p.permission_key = ?
`
const stmt = db.prepare(query)
const result = stmt.get(userId, productName, channel, permission)
return !!result
}
module.exports = { module.exports = {
grant, grant,
revoke, revoke,
hasPermission,
revokeAllOnProduct,
findByUserAndProduct, findByUserAndProduct,
revokeAllOnProduct }
};

View File

@ -59,27 +59,10 @@ function findById(identifier) {
return statement.get(identifier) return statement.get(identifier)
} }
function hasPermission(userId, productName, channel, permission) {
const query = `
SELECT 1 FROM permissions p
JOIN products pr ON p.product_id = pr.id
WHERE p.user_id = ?
AND pr.name = ?
AND (p.channel = ? OR p.channel = '*')
AND p.permission_key = ?
`
const stmt = db.prepare(query)
const result = stmt.get(userId, productName, channel, permission)
return !!result
}
module.exports = { module.exports = {
remove, remove,
register, register,
findById, findById,
hasPermission,
findByUsername, findByUsername,
changePassword, changePassword,
} }

View File

@ -0,0 +1,77 @@
const licenceRepository = require("../repositories/licenceRepository")
const logger = require("../modules/logger")
const { DefaultError } = require("../errors/errors")
async function create({ key, productId, targetChannel }) {
const existing = licenceRepository.findByKey(key)
if (existing) {
throw new DefaultError(409, "License key already exists.")
}
try {
return licenceRepository.create(key, productId, targetChannel)
} catch (error) {
logger.error(error, ["Service", "yellow", "LICENSE", "green"])
throw new DefaultError(500, "Please contact the maintainer")
}
}
function getLicenseByKey(key) {
try {
const license = licenceRepository.findByKey(key)
if (!license) {
throw new DefaultError(404, "License not found.")
}
return license
} catch (error) {
if (error instanceof DefaultError) throw error
logger.error(error, ["Service", "yellow", "LICENSE", "green"])
throw new DefaultError(500, "Please contact the maintainer")
}
}
async function updateStatus(id, status) {
try {
const result = licenceRepository.updateStatus(id, status)
if (result.changes === 0) {
throw new DefaultError(404, "License not found, status update failed.")
}
return result
} catch (error) {
if (error instanceof DefaultError) throw error
logger.error(error, ["Service", "yellow", "LICENSE", "green"])
throw new DefaultError(500, "Please contact the maintainer")
}
}
function listByProduct(productId) {
try {
const licenses = licenceRepository.findByProduct(productId)
return licenses
} catch (error) {
logger.error(error, ["Service", "yellow", "LICENSE", "green"])
throw new DefaultError(500, "Please contact the maintainer")
}
}
async function remove(id) {
try {
const result = licenceRepository.remove(id)
if (result.changes === 0) {
throw new DefaultError(404, "License not found.")
}
return result
} catch (error) {
if (error instanceof DefaultError) throw error
logger.error(error, ["Service", "yellow", "LICENSE", "green"])
throw new DefaultError(500, "Please contact the maintainer")
}
}
module.exports = {
create,
getLicenseByKey,
updateStatus,
listByProduct,
remove
}

View File

@ -0,0 +1,72 @@
const permissionsRepository = require("../repositories/permissionsRepository")
const userRepository = require("../repositories/userRepository")
const logger = require("../modules/logger")
const { DefaultError } = require("../errors/errors")
async function grant({ userId, productId, channel, permissionKey }) {
const user = userRepository.findById(userId)
if (!user) {
throw new DefaultError(404, "User not found.")
}
try {
return permissionsRepository.grant(userId, productId, channel, permissionKey)
} catch (error) {
logger.error(error, ["Service", "yellow", "PERMISSION", "green"])
throw new DefaultError(500, "Please contact the maintainer")
}
}
async function revoke({ userId, productId, channel, permissionKey }) {
try {
const result = permissionsRepository.revoke(userId, productId, channel, permissionKey)
if (result.changes === 0) {
throw new DefaultError(404, "Permission not found or already revoked.")
}
return result
} catch (error) {
if (error instanceof DefaultError) throw error
logger.error(error, ["Service", "yellow", "PERMISSION", "green"])
throw new DefaultError(500, "Please contact the maintainer")
}
}
function findByUserAndProduct(userId, productId) {
try {
const permissions = permissionsRepository.findByUserAndProduct(userId, productId)
if (!permissions || permissions.length === 0) {
throw new DefaultError(404, "No permissions found for this user and product.")
}
return permissions
} catch (error) {
if (error instanceof DefaultError) throw error
logger.error(error, ["Service", "yellow", "PERMISSION", "green"])
throw new DefaultError(500, "Please contact the maintainer")
}
}
async function revokeAllOnProduct(userId, productId) {
try {
return permissionsRepository.revokeAllOnProduct(userId, productId)
} catch (error) {
logger.error(error, ["Service", "yellow", "PERMISSION", "green"])
throw new DefaultError(500, "Please contact the maintainer")
}
}
function checkPermission(userId, productName, channel, permission) {
try {
return permissionsRepository.hasPermission(userId, productName, channel, permission)
} catch (error) {
logger.error(error, ["Service", "yellow", "PERMISSION", "green"])
throw new DefaultError(500, "Please contact the maintainer")
}
}
module.exports = {
grant,
revoke,
checkPermission,
revokeAllOnProduct,
findByUserAndProduct
}

View File

@ -1,19 +0,0 @@
const crypto = require("node:crypto")
const DefaultError = require("../errors/DefaultError")
function register({ email, username }) {
const canRegister = true
if (canRegister === true) {
return {
id: crypto.randomUUID(),
username: username,
email: email
}
} else {
throw new DefaultError(418, "I'm a teapot", "", "TeaPotExeception")
}
}
module.exports = {
register
}

66
services/userService.js Normal file
View File

@ -0,0 +1,66 @@
const userRepository = require("../repositories/userRepository")
const bcrypt = require("bcryptjs")
const logger = require("../modules/logger")
const { DefaultError } = require("../errors/errors")
async function register({ firstName = null, lastName = null, username, password }) {
const isUserExists = userRepository.findByUsername(username)
if (isUserExists) {
throw new DefaultError(409, "Username taken.")
}
try {
const hashedPassword = await bcrypt.hash(password, 4)
return userRepository.register(firstName, lastName, username, hashedPassword)
} catch (error) {
logger.error(error, ["Service", "yellow", "USER", "green"])
throw new DefaultError(500, "Please contact the maintainer")
}
}
async function remove(id) {
const user = userRepository.findById(id)
if (!user) {
throw new DefaultError(404, "User not found.")
}
try {
return userRepository.remove(id)
} catch (error) {
logger.error(error, ["Service", "yellow", "USER", "green"])
throw new DefaultError(500, "Please contact the maintainer")
}
}
async function changePassword(id, newPassword) {
const user = userRepository.findById(id)
if (!user) {
throw new DefaultError(404, "User not found.")
}
try {
const hashedPassword = await bcrypt.hash(newPassword, 4)
return userRepository.changePassword(id, hashedPassword)
} catch (error) {
logger.error(error, ["Service", "yellow", "USER", "green"])
throw new DefaultError(500, "Please contact the maintainer")
}
}
function findById(id) {
try {
const user = userRepository.findById(id)
if (!user) {
throw new DefaultError(404, "User not found.")
}
return user
} catch (error) {
if (error instanceof DefaultError) throw error
logger.error(error, ["Service", "yellow", "USER", "green"])
throw new DefaultError(500, "Please contact the maintainer")
}
}
module.exports = {
remove,
findById,
register,
changePassword,
}

View File

@ -1,3 +0,0 @@
const dg = require("./modules/databaseGlobals")
dg.initializeDatabase()