Compare commits

...

11 Commits

Author SHA1 Message Date
010475bca2 added endpoint for group leaving 2024-11-18 13:54:05 +03:00
63aaa30d92 added endpoint for group ownership transfer 2024-11-18 13:34:39 +03:00
947dc720a5 added rename endpoint for groups 2024-11-18 12:32:11 +03:00
7d98ba89c5 fixed wrong action sequence 2024-11-14 03:41:34 +03:00
9988cedbc4 forgot to import 2024-11-13 23:25:58 +03:00
4f5cd78eb0 debug info 2024-11-13 23:09:50 +03:00
0e4cc6507b fixes 2024-11-13 23:08:50 +03:00
1e6975fa13 stupid me 2024-11-13 20:07:59 +03:00
05652f1cfb added persistent volumes for postgresql db 2024-11-13 19:41:30 +03:00
3c7e88d389 deleting old image of an abstract product on its update 2024-11-13 19:38:46 +03:00
eb0c8ac99d deleting abstract product image on its deletion 2024-11-13 19:36:55 +03:00
12 changed files with 108 additions and 26 deletions

View File

@@ -13,10 +13,8 @@ services:
- ./data/uploads:/uploads - ./data/uploads:/uploads
database: database:
image: 'postgres:15' image: 'postgres:15'
volumes:
# ports: - ./data/db:/var/lib/postgresql/data
# - 5432:5432
environment: environment:
POSTGRES_USER: bsfe POSTGRES_USER: bsfe
POSTGRES_PASSWORD: Ch@NgEME! POSTGRES_PASSWORD: Ch@NgEME!

View File

@@ -5,6 +5,7 @@
"user invalid syntax": "Invalid syntax in one of user's parameters!", "user invalid syntax": "Invalid syntax in one of user's parameters!",
"user already in group": "User is already in group!", "user already in group": "User is already in group!",
"user not found": "User does not exists!", "user not found": "User does not exists!",
"admin leave": "You are an admin of that group. You must transfer ownership of that group first!",
"username taken": "Username is taken!", "username taken": "Username is taken!",
"username not found": "Such username not found!", "username not found": "Such username not found!",
"username required": "Username is required!", "username required": "Username is required!",
@@ -15,6 +16,8 @@
"group id not found": "Group with such ID not found!", "group id not found": "Group with such ID not found!",
"group not an owner": "You are not an owner of this group!", "group not an owner": "You are not an owner of this group!",
"group not a member": "You are not a member of this group!", "group not a member": "You are not a member of this group!",
"name not specified": "New name of a group is not specified!",
"new owner not specified": "ID of a new group owner is not specified!",
"abstract product not found": "Such abstract product is not found!", "abstract product not found": "Such abstract product is not found!",
"abstract product invalid syntax": "Invalid syntax in one of user's parameters!", "abstract product invalid syntax": "Invalid syntax in one of user's parameters!",
"barcode not found": "Such barcode not found!", "barcode not found": "Such barcode not found!",

View File

@@ -5,6 +5,7 @@
"user invalid syntax": "Неправильный синткасис в одном из параметров пользователя!", "user invalid syntax": "Неправильный синткасис в одном из параметров пользователя!",
"user already in group": "Пользователь уже в группе!", "user already in group": "Пользователь уже в группе!",
"user not found": "Пользователь не существует!", "user not found": "Пользователь не существует!",
"admin leave": "Вы являетесь администратором группы. Перед выходом передайте владение группой!",
"username taken": "Имя пользователя занято!", "username taken": "Имя пользователя занято!",
"username not found": "Такое имя пользователя не найдено!", "username not found": "Такое имя пользователя не найдено!",
"username required": "Требуется имя пользователя!", "username required": "Требуется имя пользователя!",
@@ -15,6 +16,8 @@
"group id not found": "Группы с таким ID не существует!", "group id not found": "Группы с таким ID не существует!",
"group not an owner": "Вы не владелец этой группы!", "group not an owner": "Вы не владелец этой группы!",
"group not a member": "Вы не участник этой группы!", "group not a member": "Вы не участник этой группы!",
"name not specified": "Не указано новое имя группы!",
"new owner not specified": "Не указан ID нового владельца группы!",
"abstract product not found": "Такой абстрактный продукт не найден!", "abstract product not found": "Такой абстрактный продукт не найден!",
"abstract product invalid syntax": "Неправильный синткасис в одном из параметров абстрактного продукта!", "abstract product invalid syntax": "Неправильный синткасис в одном из параметров абстрактного продукта!",
"barcode not found": "Такой штрихкод не найден!", "barcode not found": "Такой штрихкод не найден!",

View File

@@ -45,11 +45,18 @@ class AbstractProductController {
if (req.file) { if (req.file) {
tempPath = req.file.path; tempPath = req.file.path;
let previousImageHash = (await AbstractProductService.getByLocalId(groupId, localId)).image_filename
let previousImagePath = path.join(path.resolve(path.dirname('')), `/uploads/${previousImageHash}.png`);
fs.unlinkSync(previousImagePath)
image_buffer = fs.readFileSync(tempPath); image_buffer = fs.readFileSync(tempPath);
image_filename = createHash('md5').update(image_buffer).digest('hex'); image_filename = createHash('md5').update(image_buffer).digest('hex');
targetPath = path.join(path.resolve(path.dirname('')) + `/uploads/${image_filename}.png`); targetPath = path.join(path.resolve(path.dirname('')) + `/uploads/${image_filename}.png`);
fs.copyFileSync(tempPath, targetPath); fs.copyFileSync(tempPath, targetPath);
fs.rmSync(tempPath); fs.rmSync(tempPath);
} }
if (barcode) await AbstractProductService.updateBarcode(groupId, localId, barcode); if (barcode) await AbstractProductService.updateBarcode(groupId, localId, barcode);
@@ -93,6 +100,10 @@ class AbstractProductController {
async delete(req, res) { async delete(req, res) {
let { localId, groupId } = req.params; let { localId, groupId } = req.params;
let imageFilename = (await AbstractProductService.getByLocalId(groupId, localId)).image_filename
let imagePath = path.join(path.resolve(path.dirname('')), `/uploads/${imageFilename}.png`);
fs.unlinkSync(imagePath)
await AbstractProductService.delete(groupId, localId) await AbstractProductService.delete(groupId, localId)
notify(req.headers.authorization.split(' ')[1], groupId, 'delete', 'abstractproduct', { local_id: localId }); notify(req.headers.authorization.split(' ')[1], groupId, 'delete', 'abstractproduct', { local_id: localId });

View File

@@ -5,6 +5,7 @@ import config from '../../config.json' with {type: "json"};
import log from '../utils/log.js'; import log from '../utils/log.js';
import translate from '../utils/translate.js'; import translate from '../utils/translate.js';
import responseCodes from '../response/responseCodes.js'; import responseCodes from '../response/responseCodes.js';
import customError from '../response/customError.js';
const TAG = "/controllers/group.js"; const TAG = "/controllers/group.js";
@@ -15,10 +16,7 @@ class GroupController {
let user = jwt.decode(req.headers.authorization.split(' ')[1], config.secret); let user = jwt.decode(req.headers.authorization.split(' ')[1], config.secret);
let group = await GroupService.create(groupName, user.login.id); let group = await GroupService.create(groupName, user.login.id);
log.info(`New group with name ${groupName} was just created by user ${user.login.username}`);
await UserService.joinGroup(user.login.id, group.id); await UserService.joinGroup(user.login.id, group.id);
// return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
return res.status(200).send(group.id.toString()) return res.status(200).send(group.id.toString())
} }
async join(req, res) { async join(req, res) {
@@ -27,7 +25,21 @@ class GroupController {
let user = jwt.decode(req.headers.authorization.split(' ')[1], config.secret); let user = jwt.decode(req.headers.authorization.split(' ')[1], config.secret);
await UserService.joinGroup(user.login.id, groupId); await UserService.joinGroup(user.login.id, groupId);
log.info(`User ${user.login.username} has just joined group with ID ${groupId}`); return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
async leave(req, res) {
let groupId = req.params.groupId;
let user = jwt.decode(req.headers.authorization.split(' ')[1], config.secret);
if (await GroupService.getAdminId(groupId) == user.login.id) {
let code = responseCodes.responses.user.admin_leave
return res.status(responseCodes.getHTTPCode(code)).send(translate(req.headers["accept-language"], code))
}
await UserService.leaveGroup(user.login.id, groupId);
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok)); return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
} }
@@ -75,6 +87,28 @@ class GroupController {
return res.status(200).send(result.toString()) return res.status(200).send(result.toString())
} }
async rename(req, res) {
const groupId = req.params.groupId;
const name = req.body.name;
if (!name) throw new customError(`New group name is not specified`, responseCodes.responses.groups.name_not_specified);
await GroupService.rename(groupId, name);
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
async transferOwnership(req, res) {
const groupId = req.params.groupId;
const userId = req.body.userId;
if (!userId) throw new customError(`New owner id is not specified`, responseCodes.responses.groups.new_owner_not_specified);
await GroupService.transferOwnership(groupId, userId);
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
} }
export default new GroupController(); export default new GroupController();

View File

@@ -1,7 +1,7 @@
import ProductService from '../services/product.js'; import ProductService from '../services/product.js';
import translate from '../utils/translate.js'; import translate from '../utils/translate.js';
import responseCodes from '../response/responseCodes.js'; import responseCodes from '../response/responseCodes.js';
import notify from '../utils/notify.js';
const TAG = "/controllers/product.js" const TAG = "/controllers/product.js"
class AbstractProductController { class AbstractProductController {

View File

@@ -32,17 +32,24 @@ app.get('/status', (req, res) => {
wss.on('connection', (client) => { wss.on('connection', (client) => {
client.on('message', async (message) => { client.on('message', async (message) => {
if (message == "keepalive") return;
let parsed = JSON.parse(message); let parsed = JSON.parse(message);
let token = parsed.token; let token = parsed.token;
let currentGroup = parsed.currentGroup; let currentGroup = parsed.currentGroup;
try {
if (!jwt.verify(token, config.secret)) {
client.send("Invalid token");
return;
}
if (!jwt.verify(token, config.secret)) { if (!await UserService.isInGroup(jwt.decode(token, config.secret).login.id, currentGroup)) {
client.send("Invalid token"); client.send("Not a member of specified group");
return; return;
} }
} catch (e) {
if (!await UserService.isInGroup(jwt.decode(token, config.secret).login.id, currentGroup)) { log.error("Error during connection through websocket.")
client.send("Not a member of specified group"); client.send("Error")
return; return;
} }
@@ -56,6 +63,7 @@ wss.on('connection', (client) => {
client.on('close', () => { client.on('close', () => {
for (let i = 0; i < clients.length; i++) { for (let i = 0; i < clients.length; i++) {
if (clients[i].socket == client) { if (clients[i].socket == client) {
log.debug(`Client with token ${clients[i].token} has disconnected`)
clients.splice(i, 1); clients.splice(i, 1);
break; break;
} }
@@ -69,7 +77,7 @@ const server = app.listen(config.port, () => {
server.on('upgrade', (req, res, head) => { server.on('upgrade', (req, res, head) => {
wss.handleUpgrade(req, res, head, socket => { wss.handleUpgrade(req, res, head, socket => {
wss.emit('connection', socket, request); wss.emit('connection', socket, req);
}); });
}); });

View File

@@ -7,7 +7,8 @@ const responses = {
duplicate: "user duplicate", duplicate: "user duplicate",
invalid_syntax: "user invalid syntax", invalid_syntax: "user invalid syntax",
already_in_group: "user already in group", already_in_group: "user already in group",
not_found: "user not found" not_found: "user not found",
admin_leave: "admin leave"
}, },
usernames: { usernames: {
duplicate: "username taken", duplicate: "username taken",
@@ -23,7 +24,9 @@ const responses = {
name_not_found: "group name not found", name_not_found: "group name not found",
id_not_found: "group id not found", id_not_found: "group id not found",
not_an_owner: "group not an owner", not_an_owner: "group not an owner",
not_a_member: "group not a member" not_a_member: "group not a member",
name_not_specified: "name not specified",
new_owner_not_specified: "new owner not specified"
}, },
abstractproducts: { abstractproducts: {
not_found: "abstract product not found", not_found: "abstract product not found",
@@ -117,6 +120,8 @@ const getHTTPCode = (type) => {
return 400 return 400
case responses.user.not_found: case responses.user.not_found:
return 404 return 404
case responses.user.admin_leave:
return 409
case responses.usernames.duplicate: case responses.usernames.duplicate:
@@ -141,6 +146,10 @@ const getHTTPCode = (type) => {
return 403 return 403
case responses.groups.not_a_member: case responses.groups.not_a_member:
return 403 return 403
case responses.groups.name_not_specified:
return 400
case responses.new_owner_not_specified:
return 400
case responses.abstractproducts.not_found: case responses.abstractproducts.not_found:
return 404 return 404

View File

@@ -8,10 +8,12 @@ const GroupRouter = new Router();
GroupRouter.post('/create/:groupName', tryHandler(auth.authenticate), tryHandler(GroupController.create)); GroupRouter.post('/create/:groupName', tryHandler(auth.authenticate), tryHandler(GroupController.create));
GroupRouter.post('/join/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.requirePassword), tryHandler(auth.checkGroupPassword), tryHandler(GroupController.join)); GroupRouter.post('/join/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.requirePassword), tryHandler(auth.checkGroupPassword), tryHandler(GroupController.join));
GroupRouter.get('/leave/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(GroupController.leave));
GroupRouter.post('/password/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.authorizeGroupOwner), tryHandler(auth.requirePassword), tryHandler(GroupController.updatePassword)); GroupRouter.post('/password/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.authorizeGroupOwner), tryHandler(auth.requirePassword), tryHandler(GroupController.updatePassword));
GroupRouter.get('/byName/:groupName', tryHandler(auth.authenticate), tryHandler(existance.groupNameExists), tryHandler(GroupController.getByName)) GroupRouter.get('/byName/:groupName', tryHandler(auth.authenticate), tryHandler(existance.groupNameExists), tryHandler(GroupController.getByName));
GroupRouter.get('/byId/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(GroupController.getById)) GroupRouter.get('/byId/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(GroupController.getById));
GroupRouter.get('/getUsers/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(GroupController.getUsersInGroup)) GroupRouter.get('/getUsers/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(GroupController.getUsersInGroup));
GroupRouter.get('/adminId/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(GroupController.getAdminId)) GroupRouter.get('/adminId/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(GroupController.getAdminId));
GroupRouter.post('/rename/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.authorizeGroupOwner), tryHandler(GroupController.rename));
GroupRouter.post('/transferOwnership/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.authorizeGroupOwner), tryHandler(GroupController.transferOwnership));
export default GroupRouter; export default GroupRouter;

View File

@@ -37,6 +37,14 @@ class GroupService {
async getUsersInGroup(groupId) { async getUsersInGroup(groupId) {
return (await db.query("SELECT id FROM users WHERE $1 = ANY(groups)", [groupId])).rows.map((group) => group.id) return (await db.query("SELECT id FROM users WHERE $1 = ANY(groups)", [groupId])).rows.map((group) => group.id)
} }
async rename(groupId, newName) {
await db.query("UPDATE groups SET name = $1 WHERE id = $2", [newName, groupId]).catch((e) => errorHandler(e, "groups"));;
}
async transferOwnership(groupId, userId) {
await db.query("UPDATE groups SET admin_id = $1 WHERE id = $2", [userId, groupId])
}
}; };
export default new GroupService(); export default new GroupService();

View File

@@ -31,7 +31,11 @@ class UserService {
async joinGroup(userId, groupId) { async joinGroup(userId, groupId) {
if (await (this.isInGroup(userId, groupId))) throw new customError(`joinGroup user already in group`, responseCodes.responses.user.already_in_group); if (await (this.isInGroup(userId, groupId))) throw new customError(`joinGroup user already in group`, responseCodes.responses.user.already_in_group);
await db.query("UPDATE Users SET groups = array_append(groups, $1::integer) WHERE ID = $2", [groupId, userId]); await db.query("UPDATE users SET groups = array_append(groups, $1::integer) WHERE ID = $2", [groupId, userId]);
}
async leaveGroup(userId, groupId) {
await db.query("UPDATE users SET groups = array_remove(groups, $1::integer) WHERE ID = $2", [groupId, userId]);
} }
async changeUsername(userId, username) { async changeUsername(userId, username) {

View File

@@ -2,6 +2,7 @@ import clients from '../index.js';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import config from '../../config.json' with {type: "json"}; import config from '../../config.json' with {type: "json"};
import log from './log.js';
const notify = (token, groupId, action, item, data) => { const notify = (token, groupId, action, item, data) => {
let login = jwt.decode(token, config.secret).login let login = jwt.decode(token, config.secret).login
@@ -18,7 +19,8 @@ const notify = (token, groupId, action, item, data) => {
if (client.currentGroup == groupId) { if (client.currentGroup == groupId) {
let userIdFromToken = jwt.decode(client.token, config.secret).login.id let userIdFromToken = jwt.decode(client.token, config.secret).login.id
if (userIdCurrent == userIdFromToken) return; if (userIdCurrent == userIdFromToken) return;
client.socket.send(payload) log.debug(`Sending user with ID ${userIdCurrent} packet "${JSON.stringify(payload)}"`)
client.socket.send(JSON.stringify(payload))
} }
}); });
} }