a lot of fixes, implementing abstract product api endpoints
This commit is contained in:
		
							
								
								
									
										14
									
								
								src/controllers/abstractproduct.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/controllers/abstractproduct.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					import AbstractProductService from '../services/abstractproduct.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TAG = "/controllers/abstractproduct.js"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AbstractProductController {
 | 
				
			||||||
 | 
					    async create(req, res) {
 | 
				
			||||||
 | 
					        const { groupId, barcode, name, net_weight, image_filename, category, unit } = req.body;
 | 
				
			||||||
 | 
					        // console.log(groupId, barcode, name, net_weight, image_filename, category, unit)
 | 
				
			||||||
 | 
					        await AbstractProductService.create(groupId, barcode, name, net_weight, image_filename, category, unit);
 | 
				
			||||||
 | 
					        return res.status(200).send("Successfull");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default new AbstractProductController();
 | 
				
			||||||
@@ -10,12 +10,12 @@ const TAG = "/controllers/group.js"
 | 
				
			|||||||
class GroupController {
 | 
					class GroupController {
 | 
				
			||||||
    async create(req, res) {
 | 
					    async create(req, res) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            let { name } = req.params;
 | 
					            let { groupName } = req.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let user = jwt.decode(req.headers.authorization.split(' ')[1], config.secret);
 | 
					            let user = jwt.decode(req.headers.authorization.split(' ')[1], config.secret);
 | 
				
			||||||
            let status = await GroupService.create(name, user.login.id);
 | 
					            let status = await GroupService.create(groupName, user.login.id);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
            log.info(`New group with name ${name} was just created by user ${user.login.username}`);
 | 
					            log.info(`New group with name ${groupName} was just created by user ${user.login.username}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            UserService.joinGroup(user.login.id, status.id);
 | 
					            UserService.joinGroup(user.login.id, status.id);
 | 
				
			||||||
            return res.status(200).send("Successfull");
 | 
					            return res.status(200).send("Successfull");
 | 
				
			||||||
@@ -23,27 +23,25 @@ class GroupController {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    async join(req, res) {
 | 
					    async join(req, res) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            let { id } = req.params;
 | 
					            let { groupId } = req.params;
 | 
				
			||||||
 | 
					 | 
				
			||||||
            await GroupService.getById(id);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let user = jwt.decode(req.headers.authorization.split(' ')[1], config.secret);
 | 
					            let user = jwt.decode(req.headers.authorization.split(' ')[1], config.secret);
 | 
				
			||||||
            let status = await UserService.joinGroup(user.login.id, id);
 | 
					            let status = await UserService.joinGroup(user.login.id, groupId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (status == statuses.duplicate) return res.status(400).send("Already in group");
 | 
					            if (status == statuses.duplicate) return res.status(400).send("Already in group");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            log.info(`User ${user.login.username} has just joined group with ID ${id}`);
 | 
					            log.info(`User ${user.login.username} has just joined group with ID ${groupId}`);
 | 
				
			||||||
            return res.status(200).send("Successfull");
 | 
					            return res.status(200).send("Successfull");
 | 
				
			||||||
        } catch (e) { return res.status(500).send(log.unknownError(`${TAG}/join: ${e}`)); }
 | 
					        } catch (e) { return res.status(500).send(log.unknownError(`${TAG}/join: ${e}`)); }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async updatePassword(req, res) {
 | 
					    async updatePassword(req, res) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            let { id } = req.params;
 | 
					            let { groupId } = req.params;
 | 
				
			||||||
            let { password } = req.body;
 | 
					            let { password } = req.body;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await GroupService.updatePassword(id, password);
 | 
					            await GroupService.updatePassword(groupId, password);
 | 
				
			||||||
            log.info(`Password for group with ID ${id} was updated`);
 | 
					            log.info(`Password for group with ID ${groupId} was updated`);
 | 
				
			||||||
            return res.status(200).send("Successfull");
 | 
					            return res.status(200).send("Successfull");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        } catch (e) { return res.status(500).send(log.unknownError(`${TAG}/updatePassword ${e}`)); }
 | 
					        } catch (e) { return res.status(500).send(log.unknownError(`${TAG}/updatePassword ${e}`)); }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import express from 'express';
 | 
					import express from 'express';
 | 
				
			||||||
import UserRouter from './routers/user.js';
 | 
					import UserRouter from './routers/user.js';
 | 
				
			||||||
import GroupRouter from './routers/group.js';
 | 
					import GroupRouter from './routers/group.js';
 | 
				
			||||||
 | 
					import AbstractProductRouter from './routers/abstractproduct.js';
 | 
				
			||||||
// import AdminRouter from './routers/admin.js';
 | 
					// import AdminRouter from './routers/admin.js';
 | 
				
			||||||
import log from './utils/log.js'
 | 
					import log from './utils/log.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -12,6 +13,7 @@ app.use(express.urlencoded({extended: true}));
 | 
				
			|||||||
app.use(express.json());
 | 
					app.use(express.json());
 | 
				
			||||||
app.use('/api/user/', UserRouter);
 | 
					app.use('/api/user/', UserRouter);
 | 
				
			||||||
app.use('/api/group/', GroupRouter);
 | 
					app.use('/api/group/', GroupRouter);
 | 
				
			||||||
 | 
					app.use('/api/abstractproduct', AbstractProductRouter);
 | 
				
			||||||
// app.use('/api/admin/', AdminRouter);
 | 
					// app.use('/api/admin/', AdminRouter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.listen(config.port, () => {
 | 
					app.listen(config.port, () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,26 +2,37 @@ import log from '../utils/log.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 GroupService from '../services/group.js';
 | 
					import GroupService from '../services/group.js';
 | 
				
			||||||
 | 
					import UserService from '../services/user.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TAG = "/middlewares/auth.js"
 | 
					const TAG = "/middlewares/auth.js"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const requireUsernameAndPassword = async (req, res, next) => {
 | 
					const requireUsername = async (req, res, next) => {
 | 
				
			||||||
    if (req.method == "OPTIONS") next();
 | 
					    if (req.method == "OPTIONS") next();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        const {username, password} = req.body;
 | 
					        const {username} = req.body;
 | 
				
			||||||
            if (!username) return res.status(400).send("Username is required");
 | 
					            if (!username) return res.status(400).send("Username is required");
 | 
				
			||||||
 | 
					            next();
 | 
				
			||||||
 | 
					    } catch (e) { return res.status(500).send(unknownError(`${TAG}/requireUsername: ${e}`)); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const requirePassword = async (req, res, next) => {
 | 
				
			||||||
 | 
					    if (req.method == "OPTIONS") next();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        const {password} = req.body;
 | 
				
			||||||
            if (!password) return res.status(400).send("Password is required");
 | 
					            if (!password) return res.status(400).send("Password is required");
 | 
				
			||||||
            next();
 | 
					            next();
 | 
				
			||||||
    } catch (e) { return res.status(500).send(unknownError(`${TAG}/requireUsernameAndPassword: ${e}`)); }
 | 
					    } catch (e) { return res.status(500).send(unknownError(`${TAG}/requirePassword: ${e}`)); }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const authenticate = async (req, res, next) => {
 | 
					const authenticate = async (req, res, next) => {
 | 
				
			||||||
    if (req.method == "OPTIONS") next();
 | 
					    if (req.method == "OPTIONS") next();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					        if (!req.headers.authorization) return res.status(403).send("No authorization header supplied");
 | 
				
			||||||
        const token = req.headers.authorization.split(' ')[1]
 | 
					        const token = req.headers.authorization.split(' ')[1]
 | 
				
			||||||
        if (!token) return res.status(401).send("No authorization token supplied");
 | 
					        if (!token) return res.status(403).send("No authorization token supplied");
 | 
				
			||||||
        if (!jwt.verify(token, config.secret)) return res.status(403).send("Authorization token is incorrect");
 | 
					        if (!jwt.verify(token, config.secret)) return res.status(403).send("Authorization token is incorrect");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        next();
 | 
					        next();
 | 
				
			||||||
@@ -33,13 +44,12 @@ const authorizeGroupOwner = async (req, res, next) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        const token = req.headers.authorization.split(' ')[1]
 | 
					        const token = req.headers.authorization.split(' ')[1]
 | 
				
			||||||
        if (!token) return res.status(401).send("No authorization token supplied");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const { id } = req.params;
 | 
					        const { groupId } = req.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let user = jwt.decode(token, config.secret)
 | 
					        let user = jwt.decode(token, config.secret)
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        let adminId = await GroupService.getAdminId(id);
 | 
					        let adminId = await GroupService.getAdminId(groupId);
 | 
				
			||||||
        if (user.login.id != adminId) return res.status(403).send("Not your group");
 | 
					        if (user.login.id != adminId) return res.status(403).send("Not your group");
 | 
				
			||||||
        next();
 | 
					        next();
 | 
				
			||||||
    } catch (e) { return res.status(500).send(log.unknownError(`${TAG}/authorizeGroupOwner: ${e}`)); }
 | 
					    } catch (e) { return res.status(500).send(log.unknownError(`${TAG}/authorizeGroupOwner: ${e}`)); }
 | 
				
			||||||
@@ -49,10 +59,10 @@ const checkGroupPassword = async (req, res, next) => {
 | 
				
			|||||||
    if (req.method == "OPTIONS") next();
 | 
					    if (req.method == "OPTIONS") next();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        const { id } = req.params;
 | 
					        const { groupId } = req.params;
 | 
				
			||||||
        const { password } = req.body;
 | 
					        const { password } = req.body;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const groupPassword = await GroupService.getPassword(id);
 | 
					        const groupPassword = await GroupService.getPassword(groupId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (groupPassword != password) return res.status(403).send("Wrong password");
 | 
					        if (groupPassword != password) return res.status(403).send("Wrong password");
 | 
				
			||||||
        next();
 | 
					        next();
 | 
				
			||||||
@@ -60,4 +70,15 @@ const checkGroupPassword = async (req, res, next) => {
 | 
				
			|||||||
    } catch (e) {return res.status(500).send(log.unknownError(`${TAG}/checkGroupPassword: ${e}`));}
 | 
					    } catch (e) {return res.status(500).send(log.unknownError(`${TAG}/checkGroupPassword: ${e}`));}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default { requireUsernameAndPassword, authenticate, authorizeGroupOwner, checkGroupPassword }
 | 
					const userIsInGroup = async (req, res, next) => {
 | 
				
			||||||
 | 
					    if (req.method == "OPTIONS") next();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const groupId = req.body.groupId || req.params.groupId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const token = req.headers.authorization.split(' ')[1]
 | 
				
			||||||
 | 
					    let user = jwt.decode(token, config.secret)
 | 
				
			||||||
 | 
					    if (!await UserService.isInGroup(user.login.id, groupId)) return res.status(403).send("You are not a member of this group");
 | 
				
			||||||
 | 
					    next();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default { requireUsername, requirePassword, authenticate, authorizeGroupOwner, checkGroupPassword, userIsInGroup }
 | 
				
			||||||
@@ -27,10 +27,9 @@ const usernameDoesntExist = async (req, res, next) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const groupExists = async (req, res, next) => {
 | 
					const groupExists = async (req, res, next) => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					        let groupId = req.params.groupId || req.body.groupId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const { id } = req.params;
 | 
					        let group = await GroupService.getById(groupId);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let group = await GroupService.getById(id);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!group || group == statuses.not_found) return res.status(404).send("Group not found");
 | 
					        if (!group || group == statuses.not_found) return res.status(404).send("Group not found");
 | 
				
			||||||
        next();
 | 
					        next();
 | 
				
			||||||
@@ -39,13 +38,23 @@ const groupExists = async (req, res, next) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const groupDoesntExist = async (req, res, next) => {
 | 
					const groupDoesntExist = async (req, res, next) => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					        let groupId = req.params.groupId || req.body.groupId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const { id } = req.params;
 | 
					        let group = await GroupService.getById(groupId);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let group = await GroupService.getById(id);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (group || group != statuses.not_found) return res.status(400).send("Such group already exists");
 | 
					        if (group || group != statuses.not_found) return res.status(400).send("Such group already exists");
 | 
				
			||||||
        next();
 | 
					        next();
 | 
				
			||||||
    } catch (e) { return res.status(500).send(log.unknownError(`${TAG}/groupDoesntExist: ${e}`)) }
 | 
					    } catch (e) { return res.status(500).send(log.unknownError(`${TAG}/groupDoesntExist: ${e}`)) }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
export default { usernameExists, usernameDoesntExist, groupExists, groupDoesntExist }
 | 
					
 | 
				
			||||||
 | 
					const groupNameDoesntExist = async (req, res, next) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        const { groupName } = req.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let group = await GroupService.getByName(groupName);
 | 
				
			||||||
 | 
					        if (group) return res.status(400).send("Such group name already exists");
 | 
				
			||||||
 | 
					        next();
 | 
				
			||||||
 | 
					    } catch (e) { return res.status(500).send(log.unknownError(`${TAG}/groupNameDoesntExist: ${e}`)) }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default { usernameExists, usernameDoesntExist, groupExists, groupDoesntExist, groupNameDoesntExist }
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/routers/abstractproduct.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/routers/abstractproduct.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					import { Router } from 'express';
 | 
				
			||||||
 | 
					import auth from '../middlewares/auth.js';
 | 
				
			||||||
 | 
					import AbstractProductController from '../controllers/abstractproduct.js'
 | 
				
			||||||
 | 
					import existance from '../middlewares/existance.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AbstractProductRouter = new Router();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AbstractProductRouter.post('/create', auth.authenticate, existance.groupExists, auth.userIsInGroup, AbstractProductController.create);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default AbstractProductRouter;
 | 
				
			||||||
@@ -5,8 +5,8 @@ import existance from '../middlewares/existance.js';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const GroupRouter = new Router();
 | 
					const GroupRouter = new Router();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
GroupRouter.post('/create/:name', auth.authenticate, existance.groupDoesntExist, GroupController.create);
 | 
					GroupRouter.post('/create/:groupName', auth.authenticate, existance.groupNameDoesntExist, GroupController.create);
 | 
				
			||||||
GroupRouter.post('/join/:id', auth.authenticate, existance.groupExists, auth.checkGroupPassword, GroupController.join);
 | 
					GroupRouter.post('/join/:groupId', auth.authenticate, existance.groupExists, auth.requirePassword, auth.checkGroupPassword, GroupController.join);
 | 
				
			||||||
GroupRouter.post('/password/:id', auth.authenticate, existance.groupExists, auth.authorizeGroupOwner, GroupController.updatePassword)
 | 
					GroupRouter.post('/password/:groupId', auth.authenticate, existance.groupExists, auth.authorizeGroupOwner, auth.requirePassword, GroupController.updatePassword)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default GroupRouter;
 | 
					export default GroupRouter;
 | 
				
			||||||
@@ -5,8 +5,7 @@ import UserController from '../controllers/user.js'
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const UserRouter = new Router();
 | 
					const UserRouter = new Router();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
UserRouter.post('/register', auth.requireUsernameAndPassword, existance.usernameDoesntExist, UserController.register);
 | 
					UserRouter.post('/register', auth.requireUsername, auth.requirePassword, existance.usernameDoesntExist, UserController.register);
 | 
				
			||||||
UserRouter.post('/login', auth.requireUsernameAndPassword, existance.usernameExists, UserController.login);
 | 
					UserRouter.post('/login', auth.requireUsername, auth.requirePassword, existance.usernameExists, UserController.login);
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default UserRouter;
 | 
					export default UserRouter;
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/services/abstractproduct.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/services/abstractproduct.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					import db from '../db.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AbstractProductService {
 | 
				
			||||||
 | 
					    async create (groupid, barcode, name, net_weight, image_filename, category, unit) {
 | 
				
			||||||
 | 
					        await db.query("INSERT INTO abstract_products (group_id, barcode, name, net_weight, image_filename, category, unit) VALUES ($1, $2, $3, $4, $5, $6, $7)", [groupid, barcode, name, net_weight, image_filename, category, unit]);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default new AbstractProductService();
 | 
				
			||||||
@@ -29,6 +29,10 @@ class GroupService {
 | 
				
			|||||||
    async getPassword(id) {
 | 
					    async getPassword(id) {
 | 
				
			||||||
        return (await db.query("SELECT password FROM groups WHERE id = $1", [id])).rows[0].password;
 | 
					        return (await db.query("SELECT password FROM groups WHERE id = $1", [id])).rows[0].password;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async getByName(name) {
 | 
				
			||||||
 | 
					        return (await db.query("SELECT * FROM groups WHERE name = $1", [name])).rows[0]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default new GroupService();
 | 
					export default new GroupService();
 | 
				
			||||||
							
								
								
									
										11
									
								
								src/utils/hash.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/utils/hash.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import { createHash } from 'node:crypto'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const hashAbstractProduct = (barcode, name, net_weight, image_filename, category, unit) =>  {
 | 
				
			||||||
 | 
					    return createHash('md5').update(`${barcode}${name}${net_weight}${image_filename}${category}${unit}`).digest('hex');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const hashProduct = (abstract_product_id, amount, date_of_production, expiry_date) => {
 | 
				
			||||||
 | 
					    return createHash('md5').update(`${abstract_product_id}${amount}${date_of_production}${expiry_date}`).digest('hex');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default { hashAbstractProduct, hashProduct };
 | 
				
			||||||
		Reference in New Issue
	
	Block a user