Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin' into F24/julia/activity-managem…
Browse files Browse the repository at this point in the history
…ent-endpoints-I
  • Loading branch information
liya-zhu committed Nov 28, 2024
2 parents df42fef + befb6ec commit a2c9170
Show file tree
Hide file tree
Showing 21 changed files with 647 additions and 13 deletions.
2 changes: 2 additions & 0 deletions backend/typescript/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const MIN_BEHAVIOUR_LEVEL = 1;
export const MAX_BEHAVIOUR_LEVEL = 4;
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { DataType } from "sequelize-typescript";

import { Migration } from "../umzug";

const TABLE_NAME = "user_animal_types";

export const up: Migration = async ({ context: sequelize }) => {
await sequelize.getQueryInterface().createTable(TABLE_NAME, {
user_id: {
type: DataType.INTEGER,
allowNull: false,
primaryKey: true,
references: {
model: "users",
key: "id",
},
onUpdate: "CASCADE",
onDelete: "CASCADE",
},
animal_type_id: {
type: DataType.INTEGER,
allowNull: false,
primaryKey: true,
references: {
model: "animal_types",
key: "id",
},
onUpdate: "CASCADE",
onDelete: "CASCADE",
},
});
};

export const down: Migration = async ({ context: sequelize }) => {
await sequelize.getQueryInterface().dropTable(TABLE_NAME);
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ export const up: Migration = async ({ context: sequelize }) => {
},
level: {
type: DataType.INTEGER,
validate: {
min: 1,
max: 4,
},
allowNull: false,
},
description: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { DataType } from "sequelize-typescript";

import { Migration } from "../umzug";
import { MAX_BEHAVIOUR_LEVEL, MIN_BEHAVIOUR_LEVEL } from "../constants";

const TABLE_NAME = "user_behaviours";
const CONSTRAINT_NAME = "unique_user_behaviour_skill";
const CONSTRAINT_NAME_2 = "max_level_interval";

export const up: Migration = async ({ context: sequelize }) => {
await sequelize.getQueryInterface().createTable(TABLE_NAME, {
id: {
type: DataType.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
user_id: {
type: DataType.INTEGER,
allowNull: false,
references: {
model: "users",
key: "id",
},
},
behaviour_id: {
type: DataType.INTEGER,
allowNull: false,
references: {
model: "behaviours",
key: "id",
},
},
max_level: {
type: DataType.INTEGER,
allowNull: false,
},
});

await sequelize.getQueryInterface().addConstraint(TABLE_NAME, {
fields: ["behaviour_id", "user_id"],
type: "unique",
name: CONSTRAINT_NAME,
});

await sequelize.query(
`ALTER TABLE ${TABLE_NAME} ADD CONSTRAINT ${CONSTRAINT_NAME_2}
CHECK (max_level BETWEEN ${MIN_BEHAVIOUR_LEVEL} AND ${MAX_BEHAVIOUR_LEVEL});`,
);
};

export const down: Migration = async ({ context: sequelize }) => {
await sequelize
.getQueryInterface()
.removeConstraint(TABLE_NAME, CONSTRAINT_NAME);

await sequelize.query(
`ALTER TABLE ${TABLE_NAME} DROP CONSTRAINT ${CONSTRAINT_NAME_2};`,
);

await sequelize.getQueryInterface().dropTable(TABLE_NAME);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { DataType } from "sequelize-typescript";
import { Migration } from "../umzug";
import { MAX_BEHAVIOUR_LEVEL, MIN_BEHAVIOUR_LEVEL } from "../constants";

const TABLE_NAME = "pet_behaviours";
const CONSTRAINT_NAME = "unique_pet_behaviour";
const CONSTRAINT_NAME_2 = "skill_level_interval";

export const up: Migration = async ({ context: sequelize }) => {
await sequelize.getQueryInterface().addColumn(TABLE_NAME, "is_highlighted", {
type: DataType.BOOLEAN,
allowNull: true,
defaultValue: false,
});

await sequelize.getQueryInterface().addConstraint(TABLE_NAME, {
fields: ["behaviour_id", "pet_id"],
type: "unique",
name: CONSTRAINT_NAME,
});

await sequelize.query(
`ALTER TABLE ${TABLE_NAME} ADD CONSTRAINT ${CONSTRAINT_NAME_2}
CHECK (skill_level BETWEEN ${MIN_BEHAVIOUR_LEVEL} AND ${MAX_BEHAVIOUR_LEVEL});`,
);
};

export const down: Migration = async ({ context: sequelize }) => {
await sequelize
.getQueryInterface()
.removeConstraint(TABLE_NAME, CONSTRAINT_NAME);

await sequelize
.getQueryInterface()
.removeColumn(TABLE_NAME, "is_highlighted");

await sequelize.query(
`ALTER TABLE ${TABLE_NAME} DROP CONSTRAINT ${CONSTRAINT_NAME_2};`,
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DataType } from "sequelize-typescript";
import { Migration } from "../umzug";
import { MAX_BEHAVIOUR_LEVEL, MIN_BEHAVIOUR_LEVEL } from "../constants";

const TABLE_NAME = "behaviour_level_details";
const CONSTRAINT_NAME = "level_interval";

export const up: Migration = async ({ context: sequelize }) => {
await sequelize.query(
`ALTER TABLE ${TABLE_NAME} ADD CONSTRAINT ${CONSTRAINT_NAME}
CHECK (level BETWEEN ${MIN_BEHAVIOUR_LEVEL} AND ${MAX_BEHAVIOUR_LEVEL});`,
);
};

export const down: Migration = async ({ context: sequelize }) => {
await sequelize.query(
`ALTER TABLE ${TABLE_NAME} DROP CONSTRAINT ${CONSTRAINT_NAME};`,
);
};
12 changes: 11 additions & 1 deletion backend/typescript/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import * as path from "path";
import { Sequelize } from "sequelize-typescript";
import User from "./user.model";
import AnimalType from "./animalType.model";
import UserAnimalType from "./userAnimalType.model";
import defineRelationships from "./modelRelationships";

const DATABASE_URL =
process.env.NODE_ENV === "production"
Expand All @@ -8,6 +12,12 @@ const DATABASE_URL =
: `postgres://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@${process.env.DB_HOST}:5432/${process.env.POSTGRES_DB_DEV}`;

/* eslint-disable-next-line import/prefer-default-export */
export const sequelize = new Sequelize(DATABASE_URL, {
const sequelize = new Sequelize(DATABASE_URL, {
models: [path.join(__dirname, "/*.model.ts")],
});

sequelize.addModels([User, AnimalType, UserAnimalType]);

defineRelationships();

export { sequelize, User, AnimalType, UserAnimalType };
7 changes: 7 additions & 0 deletions backend/typescript/models/modelRelationships.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import User from "./user.model";
import AnimalType from "./animalType.model";
import UserAnimalType from "./userAnimalType.model";

export default function defineRelationships(): void {
User.belongsToMany(AnimalType, { through: UserAnimalType });
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,30 @@ import {
Table,
ForeignKey,
BelongsTo,
DataType,
} from "sequelize-typescript";
import Behaviour from "./behaviour.model";
import Pet from "./pet.model";

@Table({ timestamps: false, tableName: "pet_behaviours" })
export default class PetBehaviour extends Model {
@ForeignKey(() => Pet)
@Column({})
@Column({ type: DataType.INTEGER, allowNull: false })
pet_id!: number;

@BelongsTo(() => Pet)
pet!: Pet;

@ForeignKey(() => Behaviour)
@Column({})
@Column({ type: DataType.INTEGER, allowNull: false })
behaviour_id!: number;

@BelongsTo(() => Behaviour)
behaviour!: Behaviour;

@Column({})
@Column({ type: DataType.INTEGER, allowNull: false })
skill_level!: number;

@Column({ type: DataType.BOOLEAN, allowNull: true })
is_highlighted!: boolean;
}
26 changes: 26 additions & 0 deletions backend/typescript/models/userAnimalType.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
Column,
DataType,
Model,
Table,
ForeignKey,
PrimaryKey,
} from "sequelize-typescript";
import User from "./user.model";
import AnimalType from "./animalType.model";

@Table({
tableName: "User_AnimalTypes",
timestamps: true,
})
export default class UserAnimalType extends Model {
@ForeignKey(() => User)
@PrimaryKey
@Column({ type: DataType.INTEGER, allowNull: false })
user_id!: number;

@ForeignKey(() => AnimalType)
@PrimaryKey
@Column({ type: DataType.INTEGER, allowNull: false })
animal_type_id!: number;
}
30 changes: 30 additions & 0 deletions backend/typescript/models/userBehaviour.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
Column,
Model,
Table,
ForeignKey,
BelongsTo,
DataType,
} from "sequelize-typescript";
import Behaviour from "./behaviour.model";
import User from "./user.model";

@Table({ timestamps: false, tableName: "user_behaviours" })
export default class UserBehaviour extends Model {
@ForeignKey(() => User)
@Column({ type: DataType.INTEGER, allowNull: false })
user_id!: number;

@BelongsTo(() => User)
user!: User;

@ForeignKey(() => Behaviour)
@Column({ type: DataType.INTEGER, allowNull: false })
behaviour_id!: number;

@BelongsTo(() => Behaviour)
behaviour!: Behaviour;

@Column({ type: DataType.INTEGER, allowNull: false })
max_level!: number;
}
19 changes: 17 additions & 2 deletions frontend/src/APIClients/UserAPIClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { User } from "../types/UserTypes";
import { User, CreateUserDTO } from "../types/UserTypes";
import AUTHENTICATED_USER_KEY from "../constants/AuthConstants";
import baseAPIClient from "./BaseAPIClient";
import { getLocalStorageObjProperty } from "../utils/LocalStorageUtils";
Expand All @@ -18,4 +18,19 @@ const get = async (): Promise<User[]> => {
}
};

export default { get };
const create = async (formData: CreateUserDTO): Promise<CreateUserDTO> => {
const bearerToken = `Bearer ${getLocalStorageObjProperty(
AUTHENTICATED_USER_KEY,
"accessToken",
)}`;
try {
const { data } = await baseAPIClient.post("/users", formData, {
headers: { Authorization: bearerToken },
});
return data;
} catch (error) {
throw new Error(`Failed to create user: ${error}`);
}
};

export default { get, create };
6 changes: 6 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Default from "./components/pages/Default";
import Login from "./components/auth/Login";
import Signup from "./components/auth/Signup";
import ForgotPasswordPage from "./components/pages/ForgotPassword";
import PrivateRoute from "./components/auth/PrivateRoute";
import CreatePage from "./components/pages/CreatePage";
import PetListPage from "./components/pages/PetListPage";
Expand Down Expand Up @@ -61,6 +62,11 @@ const App = (): React.ReactElement => {
<Switch>
<Route exact path={Routes.LOGIN_PAGE} component={Login} />
<Route exact path={Routes.SIGNUP_PAGE} component={Signup} />
<Route
exact
path={Routes.FORGOT_PASSWORD_PAGE}
component={ForgotPasswordPage}
/>
<PrivateRoute
exact
path={Routes.HOME_PAGE}
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/components/common/StatusMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react";
import { Text } from "@chakra-ui/react";

interface StatusMessageProps {
message: string;
color?: string;
}

const StatusMessage = ({
message,
color = "blue.700",
}: StatusMessageProps): React.ReactElement => {
return (
<Text color={color} textAlign="center" lineHeight="120%" marginTop="16px">
{message}
</Text>
);
};

export default StatusMessage;
Loading

0 comments on commit a2c9170

Please sign in to comment.