Skip to content

Commit 795c0b1

Browse files
committed
Merge branch 'main' into F24/Justin/User-Profile-Page
2 parents 011d57f + 95eeb65 commit 795c0b1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+4235
-233
lines changed

backend/typescript/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const MIN_BEHAVIOUR_LEVEL = 1;
2+
export const MAX_BEHAVIOUR_LEVEL = 4;
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { Request, Response, NextFunction } from "express";
2+
import { getApiValidationError, validateDate, validatePrimitive } from "./util";
3+
4+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
5+
/* eslint-disable-next-line import/prefer-default-export */
6+
export const activityRequestDtoValidator = async (
7+
req: Request,
8+
res: Response,
9+
next: NextFunction,
10+
) => {
11+
const { body } = req;
12+
13+
if (
14+
body.userId !== undefined &&
15+
body.userId !== null &&
16+
!validatePrimitive(body.userId, "integer")
17+
) {
18+
return res.status(400).send(getApiValidationError("userId", "integer"));
19+
}
20+
21+
if (!validatePrimitive(body.petId, "integer")) {
22+
return res.status(400).send(getApiValidationError("petId", "integer"));
23+
}
24+
25+
if (!validatePrimitive(body.activityTypeId, "integer")) {
26+
return res
27+
.status(400)
28+
.send(getApiValidationError("activityTypeId", "integer"));
29+
}
30+
31+
if (
32+
body.scheduledStartTime !== undefined &&
33+
body.scheduledStartTime !== null &&
34+
!validateDate(body.scheduledStartTime)
35+
) {
36+
return res
37+
.status(400)
38+
.send(getApiValidationError("scheduledStartTime", "Date"));
39+
}
40+
41+
if (
42+
body.startTime !== undefined &&
43+
body.startTime !== null &&
44+
!validateDate(body.startTime)
45+
) {
46+
return res.status(400).send(getApiValidationError("startTime", "Date"));
47+
}
48+
49+
if (
50+
body.endTime !== undefined &&
51+
body.endTime !== null &&
52+
!validateDate(body.endTime)
53+
) {
54+
return res.status(400).send(getApiValidationError("endTime", "Date"));
55+
}
56+
57+
if (
58+
body.notes !== undefined &&
59+
body.notes !== null &&
60+
!validatePrimitive(body.notes, "string")
61+
) {
62+
return res.status(400).send(getApiValidationError("notes", "string"));
63+
}
64+
65+
return next();
66+
};
67+
68+
export const activityUpdateDtoValidator = async (
69+
req: Request,
70+
res: Response,
71+
next: NextFunction,
72+
) => {
73+
const { body } = req;
74+
75+
if (
76+
body.userId !== undefined &&
77+
body.userId !== null &&
78+
!validatePrimitive(body.userId, "integer")
79+
) {
80+
return res.status(400).send(getApiValidationError("userId", "integer"));
81+
}
82+
83+
if (
84+
body.petId !== undefined &&
85+
body.petId !== null &&
86+
!validatePrimitive(body.petId, "integer")
87+
) {
88+
return res.status(400).send(getApiValidationError("petId", "integer"));
89+
}
90+
91+
if (
92+
body.activityTypeId !== undefined &&
93+
body.activityTypeId !== null &&
94+
!validatePrimitive(body.activityTypeId, "integer")
95+
) {
96+
return res
97+
.status(400)
98+
.send(getApiValidationError("activityTypeId", "integer"));
99+
}
100+
101+
if (
102+
body.scheduledStartTime !== undefined &&
103+
body.scheduledStartTime !== null &&
104+
!validateDate(body.scheduledStartTime)
105+
) {
106+
return res
107+
.status(400)
108+
.send(getApiValidationError("scheduledStartTime", "Date"));
109+
}
110+
111+
if (
112+
body.startTime !== undefined &&
113+
body.startTime !== null &&
114+
!validateDate(body.startTime)
115+
) {
116+
return res.status(400).send(getApiValidationError("startTime", "Date"));
117+
}
118+
119+
if (
120+
body.endTime !== undefined &&
121+
body.endTime !== null &&
122+
!validateDate(body.endTime)
123+
) {
124+
return res.status(400).send(getApiValidationError("endTime", "Date"));
125+
}
126+
127+
if (
128+
body.notes !== undefined &&
129+
body.notes !== null &&
130+
!validatePrimitive(body.notes, "string")
131+
) {
132+
return res.status(400).send(getApiValidationError("notes", "string"));
133+
}
134+
135+
return next();
136+
};

backend/typescript/middlewares/validators/authValidators.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,26 @@ export const loginRequestValidator = async (
2323
return next();
2424
};
2525

26+
export const loginWithSignInLinkRequestValidator = async (
27+
req: Request,
28+
res: Response,
29+
next: NextFunction,
30+
) => {
31+
if (!validatePrimitive(req.body.accessToken, "string")) {
32+
return res.status(400).send(getApiValidationError("accessToken", "string"));
33+
}
34+
if (!validatePrimitive(req.body.refreshToken, "string")) {
35+
return res
36+
.status(400)
37+
.send(getApiValidationError("refreshToken", "string"));
38+
}
39+
if (!validatePrimitive(req.body.email, "string")) {
40+
return res.status(400).send(getApiValidationError("email", "string"));
41+
}
42+
43+
return next();
44+
};
45+
2646
export const registerRequestValidator = async (
2747
req: Request,
2848
res: Response,
@@ -43,3 +63,15 @@ export const registerRequestValidator = async (
4363

4464
return next();
4565
};
66+
67+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
68+
export const inviteUserDtoValidator = async (
69+
req: Request,
70+
res: Response,
71+
next: NextFunction,
72+
) => {
73+
if (!validatePrimitive(req.body.email, "string")) {
74+
return res.status(400).send(getApiValidationError("email", "string"));
75+
}
76+
return next();
77+
};

backend/typescript/middlewares/validators/util.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
type Type = "string" | "integer" | "boolean" | "decimal" | "PetStatus" | "Sex";
1+
type Type =
2+
| "string"
3+
| "integer"
4+
| "boolean"
5+
| "decimal"
6+
| "PetStatus"
7+
| "Sex"
8+
| "Date";
29

310
const allowableContentTypes = new Set([
411
"text/plain",
@@ -50,6 +57,10 @@ export const validateFileType = (mimetype: string): boolean => {
5057
return allowableContentTypes.has(mimetype);
5158
};
5259

60+
export const validateDate = (value: any): boolean => {
61+
return !Number.isNaN(Date.parse(value));
62+
};
63+
5364
export const getApiValidationError = (
5465
fieldName: string,
5566
type: Type,

backend/typescript/migrations/2024.10.22T19.38.07.create-behaviour-level-details-table.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ export const up: Migration = async ({ context: sequelize }) => {
2323
},
2424
level: {
2525
type: DataType.INTEGER,
26-
validate: {
27-
min: 1,
28-
max: 4,
29-
},
3026
allowNull: false,
3127
},
3228
description: {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { DataType } from "sequelize-typescript";
2+
3+
import { Migration } from "../umzug";
4+
import { MAX_BEHAVIOUR_LEVEL, MIN_BEHAVIOUR_LEVEL } from "../constants";
5+
6+
const TABLE_NAME = "user_behaviours";
7+
const CONSTRAINT_NAME = "unique_user_behaviour_skill";
8+
const CONSTRAINT_NAME_2 = "max_level_interval";
9+
10+
export const up: Migration = async ({ context: sequelize }) => {
11+
await sequelize.getQueryInterface().createTable(TABLE_NAME, {
12+
id: {
13+
type: DataType.INTEGER,
14+
allowNull: false,
15+
primaryKey: true,
16+
autoIncrement: true,
17+
},
18+
user_id: {
19+
type: DataType.INTEGER,
20+
allowNull: false,
21+
references: {
22+
model: "users",
23+
key: "id",
24+
},
25+
},
26+
behaviour_id: {
27+
type: DataType.INTEGER,
28+
allowNull: false,
29+
references: {
30+
model: "behaviours",
31+
key: "id",
32+
},
33+
},
34+
max_level: {
35+
type: DataType.INTEGER,
36+
allowNull: false,
37+
},
38+
});
39+
40+
await sequelize.getQueryInterface().addConstraint(TABLE_NAME, {
41+
fields: ["behaviour_id", "user_id"],
42+
type: "unique",
43+
name: CONSTRAINT_NAME,
44+
});
45+
46+
await sequelize.query(
47+
`ALTER TABLE ${TABLE_NAME} ADD CONSTRAINT ${CONSTRAINT_NAME_2}
48+
CHECK (max_level BETWEEN ${MIN_BEHAVIOUR_LEVEL} AND ${MAX_BEHAVIOUR_LEVEL});`,
49+
);
50+
};
51+
52+
export const down: Migration = async ({ context: sequelize }) => {
53+
await sequelize
54+
.getQueryInterface()
55+
.removeConstraint(TABLE_NAME, CONSTRAINT_NAME);
56+
57+
await sequelize.query(
58+
`ALTER TABLE ${TABLE_NAME} DROP CONSTRAINT ${CONSTRAINT_NAME_2};`,
59+
);
60+
61+
await sequelize.getQueryInterface().dropTable(TABLE_NAME);
62+
};
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { DataType } from "sequelize-typescript";
2+
import { Migration } from "../umzug";
3+
import { MAX_BEHAVIOUR_LEVEL, MIN_BEHAVIOUR_LEVEL } from "../constants";
4+
5+
const TABLE_NAME = "pet_behaviours";
6+
const CONSTRAINT_NAME = "unique_pet_behaviour";
7+
const CONSTRAINT_NAME_2 = "skill_level_interval";
8+
9+
export const up: Migration = async ({ context: sequelize }) => {
10+
await sequelize.getQueryInterface().addColumn(TABLE_NAME, "is_highlighted", {
11+
type: DataType.BOOLEAN,
12+
allowNull: true,
13+
defaultValue: false,
14+
});
15+
16+
await sequelize.getQueryInterface().addConstraint(TABLE_NAME, {
17+
fields: ["behaviour_id", "pet_id"],
18+
type: "unique",
19+
name: CONSTRAINT_NAME,
20+
});
21+
22+
await sequelize.query(
23+
`ALTER TABLE ${TABLE_NAME} ADD CONSTRAINT ${CONSTRAINT_NAME_2}
24+
CHECK (skill_level BETWEEN ${MIN_BEHAVIOUR_LEVEL} AND ${MAX_BEHAVIOUR_LEVEL});`,
25+
);
26+
};
27+
28+
export const down: Migration = async ({ context: sequelize }) => {
29+
await sequelize
30+
.getQueryInterface()
31+
.removeConstraint(TABLE_NAME, CONSTRAINT_NAME);
32+
33+
await sequelize
34+
.getQueryInterface()
35+
.removeColumn(TABLE_NAME, "is_highlighted");
36+
37+
await sequelize.query(
38+
`ALTER TABLE ${TABLE_NAME} DROP CONSTRAINT ${CONSTRAINT_NAME_2};`,
39+
);
40+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { DataType } from "sequelize-typescript";
2+
import { Migration } from "../umzug";
3+
import { MAX_BEHAVIOUR_LEVEL, MIN_BEHAVIOUR_LEVEL } from "../constants";
4+
5+
const TABLE_NAME = "behaviour_level_details";
6+
const CONSTRAINT_NAME = "level_interval";
7+
8+
export const up: Migration = async ({ context: sequelize }) => {
9+
await sequelize.query(
10+
`ALTER TABLE ${TABLE_NAME} ADD CONSTRAINT ${CONSTRAINT_NAME}
11+
CHECK (level BETWEEN ${MIN_BEHAVIOUR_LEVEL} AND ${MAX_BEHAVIOUR_LEVEL});`,
12+
);
13+
};
14+
15+
export const down: Migration = async ({ context: sequelize }) => {
16+
await sequelize.query(
17+
`ALTER TABLE ${TABLE_NAME} DROP CONSTRAINT ${CONSTRAINT_NAME};`,
18+
);
19+
};

backend/typescript/models/activity.model.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import User from "./user.model";
1010
import Pet from "./pet.model";
1111
import ActivityType from "./activityType.model";
1212

13-
@Table({ timestamps: false, tableName: "activities" })
13+
@Table({
14+
tableName: "activities",
15+
timestamps: true,
16+
createdAt: "created_at",
17+
updatedAt: "updated_at",
18+
})
1419
export default class Activity extends Model {
15-
@Column({})
16-
activity_id!: number;
17-
1820
@ForeignKey(() => User) // in case of null, task has not been assigned
1921
@Column({})
2022
user_id?: number;

backend/typescript/models/petBehaviour.ts renamed to backend/typescript/models/petBehaviour.model.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,30 @@ import {
44
Table,
55
ForeignKey,
66
BelongsTo,
7+
DataType,
78
} from "sequelize-typescript";
89
import Behaviour from "./behaviour.model";
910
import Pet from "./pet.model";
1011

1112
@Table({ timestamps: false, tableName: "pet_behaviours" })
1213
export default class PetBehaviour extends Model {
1314
@ForeignKey(() => Pet)
14-
@Column({})
15+
@Column({ type: DataType.INTEGER, allowNull: false })
1516
pet_id!: number;
1617

1718
@BelongsTo(() => Pet)
1819
pet!: Pet;
1920

2021
@ForeignKey(() => Behaviour)
21-
@Column({})
22+
@Column({ type: DataType.INTEGER, allowNull: false })
2223
behaviour_id!: number;
2324

2425
@BelongsTo(() => Behaviour)
2526
behaviour!: Behaviour;
2627

27-
@Column({})
28+
@Column({ type: DataType.INTEGER, allowNull: false })
2829
skill_level!: number;
30+
31+
@Column({ type: DataType.BOOLEAN, allowNull: true })
32+
is_highlighted!: boolean;
2933
}

0 commit comments

Comments
 (0)