services
parent
366e3f3b23
commit
da0814bcf5
|
@ -4,6 +4,8 @@ import bodyParser from "body-parser";
|
|||
import swaggerUI from "swagger-ui-express";
|
||||
import cors from "cors";
|
||||
|
||||
import storeRoutes from "./src/routes/storeRoutes";
|
||||
import storeServicesRoutes from "./src/routes/storeServicesRoutes";
|
||||
import userRoutes from "./src/routes/userRoutes";
|
||||
import usersRoutes from "./src/routes/usersRoutes";
|
||||
|
||||
|
@ -44,6 +46,8 @@ const options = {
|
|||
// TODO: add cors
|
||||
app.use(cors());
|
||||
app.use(bodyParser.json());
|
||||
app.use("/api/v1/store", storeRoutes);
|
||||
app.use("/api/v1/store/services", storeServicesRoutes);
|
||||
app.use("/api/v1/user", userRoutes);
|
||||
app.use("/api/v1/users", usersRoutes);
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import { Request, Response } from "express";
|
||||
import Store from "../models/store";
|
||||
import { getUserSession } from "../utils/utils";
|
||||
import logger from "../logger/logger";
|
||||
|
||||
export async function GetStore(req: Request, res: Response) {
|
||||
try {
|
||||
const { storeId } = req.params;
|
||||
|
||||
if (!storeId) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// check if requester is the store owner
|
||||
|
||||
const userSession = await getUserSession(req);
|
||||
|
||||
if (!userSession) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
const store = await Store.findOne({
|
||||
where: {
|
||||
store_id: storeId,
|
||||
},
|
||||
attributes: ["owner_user_id", "name"],
|
||||
});
|
||||
|
||||
if (!store) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
if (store.owner_user_id !== userSession.user_id) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
res.status(200).send({ store: { name: store.name } });
|
||||
} catch (error) {
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,338 @@
|
|||
import { Request, Response } from "express";
|
||||
import StoreService from "../models/storeService";
|
||||
import {
|
||||
getUserSession,
|
||||
newStoreServiceActivityId,
|
||||
newStoreServiceId,
|
||||
} from "../utils/utils";
|
||||
import {
|
||||
isStoreServiceActivityDescriptionValid,
|
||||
isStoreServiceActivityDurationValid,
|
||||
isStoreServiceActivityNameValid,
|
||||
isStoreServiceActivityPriceValid,
|
||||
isStoreServiceNameValid,
|
||||
} from "../validator/validator";
|
||||
import Store from "../models/store";
|
||||
import logger from "../logger/logger";
|
||||
import StoreServiceActivity from "../models/storeServiceActivity";
|
||||
import StoreServiceActivityUsers from "../models/storeServiceActivityUsers";
|
||||
import User from "../models/user";
|
||||
|
||||
export async function GetStoreServices(req: Request, res: Response) {
|
||||
try {
|
||||
const { storeId } = req.params;
|
||||
|
||||
if (!storeId) {
|
||||
logger.debug("Invalid request");
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
const services = await StoreService.findAll({
|
||||
where: {
|
||||
store_id: storeId,
|
||||
},
|
||||
attributes: ["service_id", "name"],
|
||||
});
|
||||
|
||||
// get all users assigned to the store
|
||||
// this user list will be used in the ui to show if no users are selected for a service activity
|
||||
|
||||
const users = await User.findAll({
|
||||
where: {
|
||||
store_id: storeId,
|
||||
},
|
||||
attributes: ["user_id", "username"],
|
||||
});
|
||||
|
||||
res.status(200).send({ services, users });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function CreateStoreService(req: Request, res: Response) {
|
||||
try {
|
||||
const { storeId, name } = req.body;
|
||||
|
||||
if (!storeId || !name || !isStoreServiceNameValid(name)) {
|
||||
logger.debug("Invalid request");
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// check if requester is the store owner
|
||||
|
||||
const userSession = await getUserSession(req);
|
||||
|
||||
if (!userSession) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
const store = await Store.findOne({
|
||||
where: {
|
||||
store_id: storeId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!store) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
if (store.owner_user_id !== userSession.user_id) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
// create store service
|
||||
|
||||
const service = await StoreService.create({
|
||||
service_id: newStoreServiceId(),
|
||||
store_id: storeId,
|
||||
name: name,
|
||||
});
|
||||
|
||||
res.status(200).send({ service });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function UpdateStoreService(req: Request, res: Response) {
|
||||
try {
|
||||
const { serviceId, name } = req.body;
|
||||
|
||||
if (!serviceId || !name || !isStoreServiceNameValid(name)) {
|
||||
logger.debug("Invalid request");
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// check if requester is the store owner
|
||||
|
||||
const userSession = await getUserSession(req);
|
||||
|
||||
if (!userSession) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
const service = await StoreService.findOne({
|
||||
where: {
|
||||
service_id: serviceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!service) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
const store = await Store.findOne({
|
||||
where: {
|
||||
store_id: service.store_id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!store) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
if (store.owner_user_id !== userSession.user_id) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
// update store service
|
||||
|
||||
await StoreService.update(
|
||||
{
|
||||
name: name,
|
||||
},
|
||||
{
|
||||
where: {
|
||||
service_id: serviceId,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
res.status(200).send({ msg: "success" });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function CreateStoreServiceActivity(req: Request, res: Response) {
|
||||
try {
|
||||
const { serviceId, name, description, price, duration } = req.body;
|
||||
|
||||
if (
|
||||
!serviceId ||
|
||||
!name ||
|
||||
!isStoreServiceActivityNameValid(name) ||
|
||||
!isStoreServiceActivityDescriptionValid(description) ||
|
||||
!isStoreServiceActivityPriceValid(price) ||
|
||||
!isStoreServiceActivityDurationValid(duration)
|
||||
) {
|
||||
logger.debug("Invalid request");
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// check if requester is the store owner
|
||||
|
||||
const userSession = await getUserSession(req);
|
||||
|
||||
if (!userSession) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
const service = await StoreService.findOne({
|
||||
where: {
|
||||
service_id: serviceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!service) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
const store = await Store.findOne({
|
||||
where: {
|
||||
store_id: service.store_id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!store) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
if (store.owner_user_id !== userSession.user_id) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
// create store service activity
|
||||
|
||||
const activity = await StoreServiceActivity.create({
|
||||
activity_id: newStoreServiceActivityId(),
|
||||
service_id: serviceId,
|
||||
name: name,
|
||||
description: description,
|
||||
price: price,
|
||||
duration: duration,
|
||||
});
|
||||
|
||||
res.status(200).send({ activity });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function GetStoreServiceActivities(req: Request, res: Response) {
|
||||
try {
|
||||
const { storeId, serviceId } = req.params;
|
||||
|
||||
if (!storeId || !serviceId) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
const activities = await StoreServiceActivity.findAll({
|
||||
where: {
|
||||
service_id: serviceId,
|
||||
},
|
||||
attributes: ["activity_id", "name", "description", "price", "duration"],
|
||||
});
|
||||
|
||||
/*
|
||||
activities.forEach(async (activity) => {
|
||||
const assignedUsers = await StoreServiceActivityUsers.findAll({
|
||||
where: {
|
||||
activity_id: activity.activity_id,
|
||||
},
|
||||
attributes: ["user_id"],
|
||||
});
|
||||
}); */
|
||||
|
||||
res.status(200).send({ activities });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function UpdateStoreServiceActivity(req: Request, res: Response) {
|
||||
try {
|
||||
const { activityId, name, description, price, duration } = req.body;
|
||||
|
||||
if (
|
||||
!activityId ||
|
||||
!name ||
|
||||
!isStoreServiceActivityNameValid(name) ||
|
||||
!isStoreServiceActivityDescriptionValid(description) ||
|
||||
!isStoreServiceActivityPriceValid(price) ||
|
||||
!isStoreServiceActivityDurationValid(duration)
|
||||
) {
|
||||
logger.debug("Invalid request");
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// check if requester is the store owner
|
||||
|
||||
const userSession = await getUserSession(req);
|
||||
|
||||
if (!userSession) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
const activity = await StoreServiceActivity.findOne({
|
||||
where: {
|
||||
activity_id: activityId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!activity) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
const service = await StoreService.findOne({
|
||||
where: {
|
||||
service_id: activity.service_id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!service) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
const store = await Store.findOne({
|
||||
where: {
|
||||
store_id: service.store_id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!store) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
if (store.owner_user_id !== userSession.user_id) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
// update store service activity
|
||||
|
||||
await StoreServiceActivity.update(
|
||||
{
|
||||
name: name,
|
||||
description: description,
|
||||
price: price,
|
||||
duration: duration,
|
||||
},
|
||||
{
|
||||
where: {
|
||||
activity_id: activityId,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
res.status(200).send({ msg: "success" });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
|
@ -6,15 +6,24 @@ import {
|
|||
isPasswordValid,
|
||||
isUsernameValid,
|
||||
} from "../validator/validator";
|
||||
import { Roles } from "../utils/constants";
|
||||
import {
|
||||
CALENDAR_MAX_FUTURE_BOOKING_DAYS,
|
||||
CALENDAR_MAX_SERVICE_DURATION,
|
||||
CALENDAR_MIN_EARLIEST_BOOKING_TIME,
|
||||
CALENDAR_PRIMARY_CALENDAR_ID,
|
||||
CALENDAR_USING_PRIMARY_CALENDAR,
|
||||
Roles,
|
||||
} from "../utils/constants";
|
||||
import {
|
||||
decodeBase64,
|
||||
getUserSession,
|
||||
hashPassword,
|
||||
matchPassword,
|
||||
newStoreId,
|
||||
newUserId,
|
||||
saveSession,
|
||||
} from "../utils/utils";
|
||||
import Store from "../models/store";
|
||||
|
||||
export async function SignUp(req: Request, res: Response) {
|
||||
try {
|
||||
|
@ -65,24 +74,51 @@ export async function SignUp(req: Request, res: Response) {
|
|||
|
||||
const hashedPassword = await hashPassword(decodedPassword);
|
||||
|
||||
// create user
|
||||
// create store
|
||||
|
||||
await User.create({
|
||||
user_id: newUserId(),
|
||||
role: Roles.Master,
|
||||
account_name: accountName,
|
||||
username: username,
|
||||
password: hashedPassword,
|
||||
let userId = newUserId();
|
||||
|
||||
Store.create({
|
||||
store_id: newStoreId(),
|
||||
owner_user_id: userId,
|
||||
name: username,
|
||||
calendar_max_future_booking_days: CALENDAR_MAX_FUTURE_BOOKING_DAYS,
|
||||
calendar_min_earliest_booking_time: CALENDAR_MIN_EARLIEST_BOOKING_TIME,
|
||||
calendar_primary_calendar_id: CALENDAR_PRIMARY_CALENDAR_ID,
|
||||
calendar_using_primary_calendar: CALENDAR_USING_PRIMARY_CALENDAR,
|
||||
calendar_max_service_duration: CALENDAR_MAX_SERVICE_DURATION,
|
||||
})
|
||||
.then((user) => {
|
||||
.then(async (store) => {
|
||||
logger.debug(
|
||||
"User created with accountName: %s username: %s",
|
||||
user.account_name,
|
||||
user.username
|
||||
"Store created with storeId: %s storeName: %s",
|
||||
store.store_id,
|
||||
store.name
|
||||
);
|
||||
|
||||
// create session
|
||||
saveSession(res, user.user_id, user.username);
|
||||
// create user
|
||||
|
||||
await User.create({
|
||||
user_id: userId,
|
||||
store_id: store.store_id,
|
||||
role: Roles.Master,
|
||||
account_name: accountName,
|
||||
username: username,
|
||||
password: hashedPassword,
|
||||
})
|
||||
.then((user) => {
|
||||
logger.debug(
|
||||
"User created with accountName: %s username: %s",
|
||||
user.account_name,
|
||||
user.username
|
||||
);
|
||||
|
||||
// create session
|
||||
saveSession(res, user.user_id, user.username);
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error(err);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error(err);
|
||||
|
@ -180,13 +216,60 @@ export async function GetUser(req: Request, res: Response) {
|
|||
where: {
|
||||
user_id: session.user_id,
|
||||
},
|
||||
attributes: ["user_id", "username"],
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
res.status(200).send({ username: user.username });
|
||||
const stores = await Store.findAll({
|
||||
where: {
|
||||
owner_user_id: user.user_id,
|
||||
},
|
||||
attributes: ["store_id", "name"],
|
||||
});
|
||||
|
||||
res.status(200).send({ user, stores });
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function IsAccountNameAvailable(req: Request, res: Response) {
|
||||
try {
|
||||
let { accountName } = req.body;
|
||||
|
||||
// validate request
|
||||
|
||||
if (!accountName) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
accountName = accountName.toLowerCase();
|
||||
|
||||
if (!isAccountNameValid(accountName)) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// check if user exists
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
account_name: accountName,
|
||||
},
|
||||
});
|
||||
|
||||
if (user) {
|
||||
logger.debug(
|
||||
"User already exists with this accountName: %s",
|
||||
accountName
|
||||
);
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
res.status(200).send({ msg: "account name available" });
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
|
|
|
@ -3,6 +3,7 @@ import logger from "../logger/logger";
|
|||
import {
|
||||
isAccountNameValid,
|
||||
isPasswordValid,
|
||||
isUserIdValid,
|
||||
isUsernameValid,
|
||||
} from "../validator/validator";
|
||||
import {
|
||||
|
@ -16,11 +17,11 @@ import { Roles } from "../utils/constants";
|
|||
|
||||
export async function AddEmployee(req: Request, res: Response) {
|
||||
try {
|
||||
let { username, accountName, password } = req.body;
|
||||
let { storeId, username, accountName, password } = req.body;
|
||||
|
||||
// validate request
|
||||
|
||||
if (!username || !accountName || !password) {
|
||||
if (!storeId || !username || !accountName || !password) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
|
@ -53,11 +54,10 @@ export async function AddEmployee(req: Request, res: Response) {
|
|||
|
||||
accountName = accountName.toLowerCase();
|
||||
|
||||
if (!isUsernameValid(username)) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
if (!isAccountNameValid(accountName)) {
|
||||
if (
|
||||
!isUsernameValid(username) ||
|
||||
!(await isAccountNameValid(accountName))
|
||||
) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
|
@ -94,11 +94,11 @@ export async function AddEmployee(req: Request, res: Response) {
|
|||
|
||||
await User.create({
|
||||
user_id: newUserId(),
|
||||
store_id: storeId,
|
||||
role: Roles.Worker,
|
||||
account_name: accountName,
|
||||
username: username,
|
||||
password: hashedPassword,
|
||||
master_user_id: requester.user_id,
|
||||
})
|
||||
.then(() => {
|
||||
res.status(200).send({ msg: "success" });
|
||||
|
@ -115,6 +115,8 @@ export async function AddEmployee(req: Request, res: Response) {
|
|||
|
||||
export async function GetEmployees(req: Request, res: Response) {
|
||||
try {
|
||||
let { storeId } = req.params;
|
||||
|
||||
const requesterSession = await getUserSession(req);
|
||||
|
||||
if (!requesterSession) {
|
||||
|
@ -126,18 +128,191 @@ export async function GetEmployees(req: Request, res: Response) {
|
|||
|
||||
const employees = await User.findAll({
|
||||
where: {
|
||||
master_user_id: requesterSession.user_id,
|
||||
store_id: storeId,
|
||||
},
|
||||
attributes: ["username", "account_name"],
|
||||
attributes: ["user_id", "username", "account_name"],
|
||||
});
|
||||
|
||||
// simulate a delay
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 4000));
|
||||
|
||||
res.status(200).send({ employees: employees });
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function UpdateEmployee(req: Request, res: Response) {
|
||||
try {
|
||||
let { userId, username, accountName } = req.body;
|
||||
|
||||
// validate request
|
||||
// check if username or account name is provided
|
||||
|
||||
if (!userId || (!username && !accountName)) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// validate username and account name
|
||||
|
||||
if (!isUserIdValid(userId)) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
let update = {};
|
||||
|
||||
if (accountName) {
|
||||
accountName = accountName.toLowerCase();
|
||||
|
||||
if (!(await isAccountNameValid(accountName))) {
|
||||
res.status(400).send({ err: "invalid request" });
|
||||
return;
|
||||
}
|
||||
|
||||
update = {
|
||||
...update,
|
||||
account_name: accountName,
|
||||
};
|
||||
}
|
||||
|
||||
if (username) {
|
||||
if (!isUsernameValid(username)) {
|
||||
res.status(400).send({ err: "invalid request" });
|
||||
return;
|
||||
}
|
||||
|
||||
update = {
|
||||
...update,
|
||||
username: username,
|
||||
};
|
||||
}
|
||||
|
||||
// verify if requester is a store master
|
||||
|
||||
const requesterSession = await getUserSession(req);
|
||||
|
||||
if (!requesterSession) {
|
||||
logger.debug("Requester session not found");
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
const requester = await User.findOne({
|
||||
where: {
|
||||
user_id: requesterSession.user_id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!requester) {
|
||||
logger.debug("Requester not found");
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
if (requester.role !== Roles.Master) {
|
||||
logger.debug("Requester is not a store master");
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
// check if user exists
|
||||
|
||||
const existingUser = await User.findOne({
|
||||
where: {
|
||||
user_id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingUser) {
|
||||
logger.debug("User not found");
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// update user
|
||||
|
||||
await User.update(
|
||||
{
|
||||
username: username,
|
||||
account_name: accountName,
|
||||
},
|
||||
{
|
||||
where: {
|
||||
user_id: userId,
|
||||
},
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
res.status(200).send({ msg: "success" });
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error(err);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DeleteEmployee(req: Request, res: Response) {
|
||||
try {
|
||||
let { userId } = req.body;
|
||||
|
||||
// validate request
|
||||
|
||||
if (!userId) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// verify if requester is a store master
|
||||
|
||||
const requesterSession = await getUserSession(req);
|
||||
|
||||
if (!requesterSession) {
|
||||
logger.debug("Requester session not found");
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
const requester = await User.findOne({
|
||||
where: {
|
||||
user_id: requesterSession.user_id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!requester) {
|
||||
logger.debug("Requester not found");
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
if (requester.role !== Roles.Master) {
|
||||
logger.debug("Requester is not a store master");
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
// check if user exists
|
||||
|
||||
const existingUser = await User.findOne({
|
||||
where: {
|
||||
user_id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingUser) {
|
||||
logger.debug("User not found");
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// delete user
|
||||
|
||||
await User.destroy({
|
||||
where: {
|
||||
user_id: userId,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
res.status(200).send({ msg: "success" });
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error(err);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
import Session from "./session";
|
||||
import Store from "./store";
|
||||
import StoreService from "./storeService";
|
||||
import StoreServiceActivity from "./storeServiceActivity";
|
||||
import StoreServiceActivityUsers from "./storeServiceActivityUsers";
|
||||
|
||||
import User from "./user";
|
||||
|
||||
function syncModels() {
|
||||
User.sync({ alter: true });
|
||||
Session.sync({ alter: true });
|
||||
User.sync();
|
||||
Session.sync();
|
||||
Store.sync();
|
||||
StoreService.sync();
|
||||
StoreServiceActivity.sync();
|
||||
StoreServiceActivityUsers.sync();
|
||||
}
|
||||
|
||||
export default syncModels;
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import { DataTypes, Model } from "sequelize";
|
||||
import sequelize from "../database/database";
|
||||
|
||||
interface StoreAttributes {
|
||||
store_id: string;
|
||||
owner_user_id: string;
|
||||
name: string;
|
||||
calendar_max_future_booking_days: number;
|
||||
calendar_min_earliest_booking_time: number;
|
||||
calendar_primary_calendar_id: string;
|
||||
calendar_using_primary_calendar: boolean;
|
||||
calendar_max_service_duration: number;
|
||||
}
|
||||
|
||||
class Store extends Model<StoreAttributes> implements StoreAttributes {
|
||||
declare store_id: string;
|
||||
declare owner_user_id: string;
|
||||
declare name: string;
|
||||
declare calendar_max_future_booking_days: number;
|
||||
declare calendar_min_earliest_booking_time: number;
|
||||
declare calendar_primary_calendar_id: string;
|
||||
declare calendar_using_primary_calendar: boolean;
|
||||
declare calendar_max_service_duration: number;
|
||||
}
|
||||
|
||||
Store.init(
|
||||
{
|
||||
// Model attributes are defined here
|
||||
store_id: {
|
||||
primaryKey: true,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
owner_user_id: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
calendar_max_future_booking_days: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
calendar_min_earliest_booking_time: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
calendar_primary_calendar_id: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
calendar_using_primary_calendar: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
},
|
||||
calendar_max_service_duration: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
tableName: "stores",
|
||||
sequelize, // passing the `sequelize` instance is required
|
||||
}
|
||||
);
|
||||
|
||||
export default Store;
|
|
@ -0,0 +1,42 @@
|
|||
import { DataTypes, Model } from "sequelize";
|
||||
import sequelize from "../database/database";
|
||||
|
||||
interface StoreServiceAttributes {
|
||||
service_id: string;
|
||||
store_id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
class StoreService
|
||||
extends Model<StoreServiceAttributes>
|
||||
implements StoreServiceAttributes
|
||||
{
|
||||
declare service_id: string;
|
||||
declare store_id: string;
|
||||
declare name: string;
|
||||
}
|
||||
|
||||
StoreService.init(
|
||||
{
|
||||
// Model attributes are defined here
|
||||
service_id: {
|
||||
primaryKey: true,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
store_id: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
tableName: "store_services",
|
||||
sequelize, // passing the `sequelize` instance is required
|
||||
}
|
||||
);
|
||||
|
||||
export default StoreService;
|
|
@ -0,0 +1,60 @@
|
|||
import { DataTypes, Model } from "sequelize";
|
||||
import sequelize from "../database/database";
|
||||
|
||||
interface StoreServiceActivityAttributes {
|
||||
activity_id: string;
|
||||
service_id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
price: number;
|
||||
duration: number;
|
||||
}
|
||||
|
||||
class StoreServiceActivity
|
||||
extends Model<StoreServiceActivityAttributes>
|
||||
implements StoreServiceActivityAttributes
|
||||
{
|
||||
declare activity_id: string;
|
||||
declare service_id: string;
|
||||
declare name: string;
|
||||
declare description: string;
|
||||
declare price: number;
|
||||
declare duration: number;
|
||||
}
|
||||
|
||||
StoreServiceActivity.init(
|
||||
{
|
||||
// Model attributes are defined here
|
||||
activity_id: {
|
||||
primaryKey: true,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
service_id: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
price: {
|
||||
type: DataTypes.FLOAT,
|
||||
allowNull: false,
|
||||
},
|
||||
duration: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
tableName: "store_service_activities",
|
||||
sequelize, // passing the `sequelize` instance is required
|
||||
}
|
||||
);
|
||||
|
||||
export default StoreServiceActivity;
|
|
@ -0,0 +1,37 @@
|
|||
import { DataTypes, Model } from "sequelize";
|
||||
import sequelize from "../database/database";
|
||||
|
||||
interface StoreServiceActivityUsersAttributes {
|
||||
activity_id: string;
|
||||
user_id: string;
|
||||
}
|
||||
|
||||
class StoreServiceActivityUsers
|
||||
extends Model<StoreServiceActivityUsersAttributes>
|
||||
implements StoreServiceActivityUsersAttributes
|
||||
{
|
||||
declare activity_id: string;
|
||||
declare user_id: string;
|
||||
}
|
||||
|
||||
StoreServiceActivityUsers.init(
|
||||
{
|
||||
// Model attributes are defined here
|
||||
activity_id: {
|
||||
primaryKey: true,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
user_id: {
|
||||
primaryKey: true,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
tableName: "store_service_activity_users",
|
||||
sequelize, // passing the `sequelize` instance is required
|
||||
}
|
||||
);
|
||||
|
||||
export default StoreServiceActivityUsers;
|
|
@ -3,22 +3,29 @@ import sequelize from "../database/database";
|
|||
|
||||
interface UserAttributes {
|
||||
user_id: string;
|
||||
master_user_id?: string;
|
||||
store_id: string;
|
||||
// TODO: change to role_id
|
||||
role: string;
|
||||
account_name: string;
|
||||
username: string;
|
||||
password: string;
|
||||
calendar_settings?: string;
|
||||
calendar_max_future_booking_days?: number;
|
||||
calendar_min_earliest_booking_time?: number;
|
||||
calendar_primary_calendar_id?: string;
|
||||
calendar_using_primary_calendar?: boolean;
|
||||
}
|
||||
|
||||
class User extends Model<UserAttributes> implements UserAttributes {
|
||||
declare user_id: string;
|
||||
declare master_user_id: string;
|
||||
declare store_id: string;
|
||||
declare role: string;
|
||||
declare account_name: string;
|
||||
declare username: string;
|
||||
declare password: string;
|
||||
declare calendar_settings: string;
|
||||
declare calendar_max_future_booking_days: number;
|
||||
declare calendar_min_earliest_booking_time: number;
|
||||
declare calendar_primary_calendar_id: string;
|
||||
declare calendar_using_primary_calendar: boolean;
|
||||
}
|
||||
|
||||
User.init(
|
||||
|
@ -29,13 +36,13 @@ User.init(
|
|||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
master_user_id: {
|
||||
store_id: {
|
||||
type: DataTypes.STRING,
|
||||
// allowNull defaults to true
|
||||
allowNull: false,
|
||||
},
|
||||
role: {
|
||||
type: DataTypes.STRING,
|
||||
// allowNull defaults to true
|
||||
allowNull: false,
|
||||
},
|
||||
account_name: {
|
||||
type: DataTypes.STRING,
|
||||
|
@ -49,9 +56,17 @@ User.init(
|
|||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
calendar_settings: {
|
||||
calendar_max_future_booking_days: {
|
||||
type: DataTypes.INTEGER,
|
||||
},
|
||||
calendar_min_earliest_booking_time: {
|
||||
type: DataTypes.INTEGER,
|
||||
},
|
||||
calendar_primary_calendar_id: {
|
||||
type: DataTypes.STRING,
|
||||
// allowNull defaults to true
|
||||
},
|
||||
calendar_using_primary_calendar: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import express from "express";
|
||||
const router = express.Router();
|
||||
|
||||
import * as storeController from "../controllers/storeController";
|
||||
|
||||
router.get("/:storeId", storeController.GetStore);
|
||||
|
||||
export default router;
|
|
@ -0,0 +1,19 @@
|
|||
import express from "express";
|
||||
const router = express.Router();
|
||||
|
||||
import * as storeServicesController from "../controllers/storeServicesController";
|
||||
|
||||
router.get("/:storeId", storeServicesController.GetStoreServices);
|
||||
router.post("/", storeServicesController.CreateStoreService);
|
||||
router.post("/update", storeServicesController.UpdateStoreService);
|
||||
router.post("/activity", storeServicesController.CreateStoreServiceActivity);
|
||||
router.get(
|
||||
"/activities/:storeId/:serviceId",
|
||||
storeServicesController.GetStoreServiceActivities
|
||||
);
|
||||
router.post(
|
||||
"/activity/update",
|
||||
storeServicesController.UpdateStoreServiceActivity
|
||||
);
|
||||
|
||||
export default router;
|
|
@ -8,5 +8,6 @@ router.post("/auth/signup", userController.SignUp);
|
|||
router.post("/auth/login", userController.Login);
|
||||
router.delete("/auth/logout", sessionProtection, userController.Logout);
|
||||
router.get("/", sessionProtection, userController.GetUser);
|
||||
router.post("/auth/check/accountname", userController.IsAccountNameAvailable);
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -4,6 +4,8 @@ const router = express.Router();
|
|||
import * as usersController from "../controllers/usersController";
|
||||
|
||||
router.post("/", usersController.AddEmployee);
|
||||
router.get("/", usersController.GetEmployees);
|
||||
router.get("/:storeId", usersController.GetEmployees);
|
||||
router.post("/update", usersController.UpdateEmployee);
|
||||
router.delete("/", usersController.DeleteEmployee);
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export const DEFAULT_SESSION_EXPIRY = 365 * 24 * 60 * 60 * 1000; // 365 days
|
||||
export const USER_SESSION_LENGTH = 32;
|
||||
|
||||
export const USERNAME_MIN_LENGTH = 3;
|
||||
export const USERNAME_MAX_LENGTH = 20;
|
||||
|
@ -9,9 +10,36 @@ export const ACCOUNT_NAME_MAX_LENGTH = 20;
|
|||
export const PASSWORD_MIN_LENGTH = 8;
|
||||
export const PASSWORD_MAX_LENGTH = 64;
|
||||
|
||||
export const USER_ID_LENGTH = 36;
|
||||
|
||||
// Header name for the session ID
|
||||
export const HEADER_X_AUTHORIZATION: string = "x-authorization";
|
||||
|
||||
export const STORE_SERVICE_MIN_LENGTH = 3;
|
||||
export const STORE_SERVICE_MAX_LENGTH = 32;
|
||||
|
||||
export const STORE_SERVICE_ACTIVITY_NAME_MIN_LENGTH = 3;
|
||||
export const STORE_SERVICE_ACTIVITY_NAME_MAX_LENGTH = 32;
|
||||
|
||||
export const STORE_SERVICE_ACTIVITY_DESCRIPTION_MIN_LENGTH = 3;
|
||||
export const STORE_SERVICE_ACTIVITY_DESCRIPTION_MAX_LENGTH = 1024;
|
||||
|
||||
export const STORE_SERVICE_ACTIVITY_PRICE_MIN = 0;
|
||||
export const STORE_SERVICE_ACTIVITY_PRICE_MAX = 10000000;
|
||||
|
||||
export const STORE_SERVICE_ACTIVITY_DURATION_HOURS_MAX = 23;
|
||||
export const STORE_SERVICE_ACTIVITY_DURATION_MINUTES_MAX = 60;
|
||||
export const STORE_SERVICE_ACTIVITY_DURATION_MIN = 0;
|
||||
export const STORE_SERVICE_ACTIVITY_DURATION_MAX =
|
||||
STORE_SERVICE_ACTIVITY_DURATION_HOURS_MAX * 60 +
|
||||
STORE_SERVICE_ACTIVITY_DURATION_MINUTES_MAX;
|
||||
|
||||
export const CALENDAR_MAX_FUTURE_BOOKING_DAYS = 14;
|
||||
export const CALENDAR_MIN_EARLIEST_BOOKING_TIME = 1;
|
||||
export const CALENDAR_PRIMARY_CALENDAR_ID = "";
|
||||
export const CALENDAR_USING_PRIMARY_CALENDAR = false;
|
||||
export const CALENDAR_MAX_SERVICE_DURATION = 1440; // 24 hours in minutes
|
||||
|
||||
export const Roles = {
|
||||
// admin of the whole system independent of stores
|
||||
Admin: "admin",
|
||||
|
|
|
@ -3,7 +3,11 @@ import bcrypt from "bcrypt";
|
|||
import { v4 as uuidv4 } from "uuid";
|
||||
import { Request, Response } from "express";
|
||||
import Session from "../models/session";
|
||||
import { DEFAULT_SESSION_EXPIRY, HEADER_X_AUTHORIZATION } from "./constants";
|
||||
import {
|
||||
DEFAULT_SESSION_EXPIRY,
|
||||
HEADER_X_AUTHORIZATION,
|
||||
USER_SESSION_LENGTH,
|
||||
} from "./constants";
|
||||
|
||||
export async function matchPassword(decodedPassword: string, password: string) {
|
||||
return await bcrypt.compare(decodedPassword, password);
|
||||
|
@ -21,8 +25,20 @@ export function newUserId() {
|
|||
return uuidv4();
|
||||
}
|
||||
|
||||
export function newStoreId() {
|
||||
return uuidv4();
|
||||
}
|
||||
|
||||
export function newStoreServiceId() {
|
||||
return uuidv4();
|
||||
}
|
||||
|
||||
export function newStoreServiceActivityId() {
|
||||
return uuidv4();
|
||||
}
|
||||
|
||||
export function newUserSession() {
|
||||
return crypto.randomBytes(32).toString("hex");
|
||||
return crypto.randomBytes(USER_SESSION_LENGTH).toString("hex");
|
||||
}
|
||||
|
||||
export async function saveSession(
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import logger from "../logger/logger";
|
||||
import {
|
||||
USERNAME_MIN_LENGTH,
|
||||
USERNAME_MAX_LENGTH,
|
||||
|
@ -5,7 +6,19 @@ import {
|
|||
PASSWORD_MAX_LENGTH,
|
||||
ACCOUNT_NAME_MIN_LENGTH,
|
||||
ACCOUNT_NAME_MAX_LENGTH,
|
||||
USER_ID_LENGTH,
|
||||
STORE_SERVICE_MIN_LENGTH,
|
||||
STORE_SERVICE_MAX_LENGTH,
|
||||
STORE_SERVICE_ACTIVITY_NAME_MAX_LENGTH,
|
||||
STORE_SERVICE_ACTIVITY_NAME_MIN_LENGTH,
|
||||
STORE_SERVICE_ACTIVITY_DESCRIPTION_MAX_LENGTH,
|
||||
STORE_SERVICE_ACTIVITY_DESCRIPTION_MIN_LENGTH,
|
||||
STORE_SERVICE_ACTIVITY_PRICE_MAX,
|
||||
STORE_SERVICE_ACTIVITY_PRICE_MIN,
|
||||
STORE_SERVICE_ACTIVITY_DURATION_MAX,
|
||||
STORE_SERVICE_ACTIVITY_DURATION_MIN,
|
||||
} from "../utils/constants";
|
||||
import User from "../models/user";
|
||||
|
||||
// TODO: regex for username
|
||||
export function isUsernameValid(username: string) {
|
||||
|
@ -16,11 +29,28 @@ export function isUsernameValid(username: string) {
|
|||
}
|
||||
|
||||
// TODO: regex for account name
|
||||
export function isAccountNameValid(accountName: string) {
|
||||
return (
|
||||
accountName.length >= ACCOUNT_NAME_MIN_LENGTH &&
|
||||
accountName.length <= ACCOUNT_NAME_MAX_LENGTH
|
||||
);
|
||||
export async function isAccountNameValid(accountName: string) {
|
||||
if (
|
||||
accountName.length < ACCOUNT_NAME_MIN_LENGTH ||
|
||||
accountName.length > ACCOUNT_NAME_MAX_LENGTH
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if account name is already taken in the database
|
||||
|
||||
const existingUser = await User.findOne({
|
||||
where: {
|
||||
account_name: accountName,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
logger.debug("User already exists with this accountName: %s", accountName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: regex for password
|
||||
|
@ -30,3 +60,52 @@ export function isPasswordValid(password: string) {
|
|||
password.length <= PASSWORD_MAX_LENGTH
|
||||
);
|
||||
}
|
||||
|
||||
export function isUserIdValid(userId: string) {
|
||||
return userId.length === USER_ID_LENGTH;
|
||||
}
|
||||
|
||||
export function isStoreServiceNameValid(storeServiceName: string) {
|
||||
return (
|
||||
storeServiceName.length >= STORE_SERVICE_MIN_LENGTH &&
|
||||
storeServiceName.length <= STORE_SERVICE_MAX_LENGTH
|
||||
);
|
||||
}
|
||||
|
||||
export function isStoreServiceActivityNameValid(
|
||||
storeServiceActivityName: string
|
||||
) {
|
||||
return (
|
||||
storeServiceActivityName.length >= STORE_SERVICE_ACTIVITY_NAME_MIN_LENGTH &&
|
||||
storeServiceActivityName.length <= STORE_SERVICE_ACTIVITY_NAME_MAX_LENGTH
|
||||
);
|
||||
}
|
||||
|
||||
export function isStoreServiceActivityDescriptionValid(
|
||||
storeServiceActivityDescription: string
|
||||
) {
|
||||
return (
|
||||
storeServiceActivityDescription.length >=
|
||||
STORE_SERVICE_ACTIVITY_DESCRIPTION_MIN_LENGTH &&
|
||||
storeServiceActivityDescription.length <=
|
||||
STORE_SERVICE_ACTIVITY_DESCRIPTION_MAX_LENGTH
|
||||
);
|
||||
}
|
||||
|
||||
export function isStoreServiceActivityPriceValid(
|
||||
storeServiceActivityPrice: any
|
||||
) {
|
||||
return (
|
||||
storeServiceActivityPrice >= STORE_SERVICE_ACTIVITY_PRICE_MIN &&
|
||||
storeServiceActivityPrice <= STORE_SERVICE_ACTIVITY_PRICE_MAX
|
||||
);
|
||||
}
|
||||
|
||||
export function isStoreServiceActivityDurationValid(
|
||||
storeServiceActivityDuration: any
|
||||
) {
|
||||
return (
|
||||
storeServiceActivityDuration >= STORE_SERVICE_ACTIVITY_DURATION_MIN &&
|
||||
storeServiceActivityDuration <= STORE_SERVICE_ACTIVITY_DURATION_MAX
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue