Skip to content

Commit

Permalink
feat (fonction-utilisateur): Ajout d'un champ fonction pour l'utilisa… (
Browse files Browse the repository at this point in the history
#472)

* feat (fonction-utilisateur): Ajout d'un champ fonction pour l'utilisateur

* fix: fonction utilisateur enum
  • Loading branch information
FaXaq authored Dec 17, 2024
1 parent d08fc7b commit 1685dc2
Show file tree
Hide file tree
Showing 17 changed files with 117 additions and 18 deletions.
1 change: 1 addition & 0 deletions docs/developpement/developpement.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Commandes:
- `yarn cli migrations:status`: Vérification du status des migrations
- `yarn cli migrations:up`: Execution des migrations
- `yarn cli migrations:create`: Creation d'une nouvelle migration
- `yarn cli migrations:generate-schema`: Génère le schéma de la base de données

### Lancement de l'application

Expand Down
1 change: 0 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"dev": "tsup-node --env.TSUP_WATCH true",
"build": "tsup-node --env.NODE_ENV production",
"typecheck": "NODE_OPTIONS='--max-old-space-size=8192' tsc --noEmit",
"kysely": "npx kysely-codegen --schema public --out-file=./src/db/schema.ts --dialect postgres && yarn prettier:fix",
"generate:seed": "./seed/generate-seed.sh"
},
"dependencies": {
Expand Down
11 changes: 11 additions & 0 deletions server/src/commands.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { exec } from "node:child_process";
import { setMaxListeners } from "node:events";
import { writeFileSync } from "node:fs";
import path from "node:path";
Expand Down Expand Up @@ -156,6 +157,16 @@ export const down = async (db: Kysely<unknown>) => {};
);
});

program
.command("migrations:generate-schema")
.description("Generate kysely schema")
.action(async () => {
console.log(path.join(__dirname(), "../src", "db/schema.ts"));
exec(
`DATABASE_URL="${config.psql.uri}" npx kysely-codegen --schema public --out-file=${path.join(__dirname(), "../src", "db/schema.ts")} --dialect postgres && yarn prettier:fix`
);
});

productCommands(program);

export async function startCLI() {
Expand Down
1 change: 1 addition & 0 deletions server/src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,7 @@ export interface User {
enabled: Generated<boolean>;
sub: string | null;
lastSeenAt: Timestamp | null;
fonction: string | null;
}

export interface DB {
Expand Down
2 changes: 2 additions & 0 deletions server/src/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ import * as migration_1727790113090 from "./migration_1727790113090";
import * as migration_1729693085251 from "./migration_1729693085251";
import * as migration_1730888921742 from "./migration_1730888921742";
import * as migration_1731666807035 from "./migration_1731666807035";
import * as migration_1733743141284 from "./migration_1733743141284";

type Migration = {
up: (db: Kysely<any>) => Promise<void>;
Expand Down Expand Up @@ -195,4 +196,5 @@ export const migrations: Migrations = {
migration_1730888921742,
migration_1729693085251,
migration_1731666807035,
migration_1733743141284,
};
11 changes: 11 additions & 0 deletions server/src/migrations/migration_1733743141284.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Kysely } from "kysely";

import type { DB } from "@/db/schema";

export const up = async (db: Kysely<DB>) => {
return await db.schema.alterTable("user").addColumn("fonction", "varchar").execute();
};

export const down = async (db: Kysely<unknown>) => {
return await db.schema.alterTable("user").dropColumn("fonction").execute();
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const [createUser, createUserFactory] = inject(
},
(deps) =>
async ({ body, requestUser }: { body: BodySchema; requestUser?: RequestUser }) => {
const { email, firstname, lastname, role, codeRegion } = body;
const { email, firstname, lastname, role, codeRegion, fonction } = body;

if (!email.match(emailRegex)) throw Boom.badRequest(`L'email est invalide`);

Expand All @@ -46,7 +46,9 @@ export const [createUser, createUserFactory] = inject(
role,
codeRegion,
enabled: true,
fonction,
});

const activationToken = jwt.sign({ email }, config.auth.activationJwtSecret, {
issuer: "orion",
});
Expand Down
10 changes: 9 additions & 1 deletion server/src/modules/core/usecases/getUsers/getUsers.route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createRoute } from "@http-wizard/core";
import type { UserFonction } from "shared/enum/userFonction";
import { ROUTES } from "shared/routes/routes";

import { getScopeFilterForUser } from "@/modules/core/utils/getScopeFilterForUser";
Expand Down Expand Up @@ -29,7 +30,14 @@ export const getUsersRoute = (server: Server) => {
scope,
scopeFilter,
});
response.code(200).send(users);

response.code(200).send({
count: users.count,
users: users.users.map((user) => ({
...user,
fonction: user.fonction as UserFonction,
})),
});
},
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ describe("extractUserInRequest usecase", () => {
enabled: false,
sub: undefined,
lastSeenAt: undefined,
fonction: undefined,
}),
});

Expand Down Expand Up @@ -79,6 +80,7 @@ describe("extractUserInRequest usecase", () => {
enabled: true,
sub: undefined,
lastSeenAt: undefined,
fonction: undefined,
}),
});

Expand Down
21 changes: 21 additions & 0 deletions shared/enum/userFonction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { z } from "zod";

export const userFonction = z.enum([
"Région",
"Région académique",
"Inspecteur",
"DO CMQ",
"Conseiller en formation professionnelle",
"Coordonnateur de CFA-A",
"DRAIO",
"Services DOS",
"DASEN",
"DRAFPIC",
"SGRA",
"CSA",
"Recteur",
]);

export const UserFonctionEnum = userFonction.Enum;

export type UserFonction = z.infer<typeof userFonction>;
4 changes: 3 additions & 1 deletion shared/routes/schemas/get.users.schema.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { z } from "zod";

import { userFonction } from "../../enum/userFonction";
import type { Role } from "../../security/permissions";
import { PERMISSIONS } from "../../security/permissions";

const UserSchema = z.object({
export const UserSchema = z.object({
id: z.string(),
firstname: z.string().optional(),
lastname: z.string().optional(),
Expand All @@ -14,6 +15,7 @@ const UserSchema = z.object({
createdAt: z.string().optional(),
uais: z.array(z.string()).optional(),
enabled: z.boolean(),
fonction: userFonction.optional(),
});

export const getUsersSchema = {
Expand Down
2 changes: 2 additions & 0 deletions shared/routes/schemas/post.users.userId.schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z } from "zod";

import { userFonction } from "../../enum/userFonction";
import type { Role } from "../../security/permissions";
import { PERMISSIONS } from "../../security/permissions";

Expand All @@ -9,6 +10,7 @@ const BodySchema = z.object({
email: z.string().email().toLowerCase(),
role: z.enum(Object.keys(PERMISSIONS) as [Role]),
codeRegion: z.string().min(1).optional(),
fonction: userFonction.optional(),
});

export type BodySchema = z.infer<typeof BodySchema>;
Expand Down
2 changes: 2 additions & 0 deletions shared/routes/schemas/put.users.userId.schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z } from "zod";

import { userFonction } from "../../enum/userFonction";
import type { Role } from "../../security/permissions";
import { PERMISSIONS } from "../../security/permissions";

Expand All @@ -10,6 +11,7 @@ const BodySchema = z.object({
role: z.enum(Object.keys(PERMISSIONS) as [Role]),
codeRegion: z.string().min(1).nullable(),
enabled: z.boolean(),
fonction: userFonction.optional(),
});

export type BodySchema = z.infer<typeof BodySchema>;
Expand Down
14 changes: 14 additions & 0 deletions ui/app/(wrapped)/admin/users/CreateUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useEffect } from "react";
import { useForm } from "react-hook-form";
import type { Role } from "shared";
import { getHierarchy } from "shared";
import { UserFonctionEnum } from "shared/enum/userFonction";
import { z } from "zod";

import { client } from "@/api.client";
Expand Down Expand Up @@ -155,6 +156,19 @@ export const CreateUser = ({ isOpen, onClose }: { isOpen: boolean; onClose: () =
<AlertDescription>{getErrorMessage(error)}</AlertDescription>
</Alert>
)}

<FormControl mb="4" isInvalid={!!errors.fonction}>
<FormLabel>Fonction de l'utilisateur</FormLabel>
<Select {...register("fonction")}>
{<option value="">Aucune</option>}
{Object.keys(UserFonctionEnum)?.map((userFonction) => (
<option key={userFonction} value={userFonction}>
{userFonction}
</option>
))}
</Select>
{!!errors.fonction && <FormErrorMessage>{errors.fonction.message}</FormErrorMessage>}
</FormControl>
</ModalBody>

<ModalFooter>
Expand Down
24 changes: 19 additions & 5 deletions ui/app/(wrapped)/admin/users/EditUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useEffect } from "react";
import { useForm } from "react-hook-form";
import type { Role } from "shared";
import { getHierarchy } from "shared";
import { UserFonctionEnum } from "shared/enum/userFonction";
import { z } from "zod";

import { client } from "@/api.client";
Expand Down Expand Up @@ -154,11 +155,24 @@ export const EditUser = ({
</Select>
{!!errors.codeRegion && <FormErrorMessage>{errors.codeRegion.message}</FormErrorMessage>}
</FormControl>
<FormControl mb="4" isInvalid={!!errors.enabled}>
<Checkbox {...register("enabled")} isRequired={false}>
Compte actif
</Checkbox>
{!!errors.enabled && <FormErrorMessage>{errors.enabled.message}</FormErrorMessage>}
<FormControl mb="4" isInvalid={!!errors.fonction}>
<FormLabel>Fonction de l'utilisateur</FormLabel>
<Select {...register("fonction")}>
{<option value="">Aucune</option>}
{Object.keys(UserFonctionEnum)?.map((userFonction) => (
<option key={userFonction} value={userFonction}>
{userFonction}
</option>
))}
</Select>

<FormControl my="4" isInvalid={!!errors.enabled}>
<Checkbox {...register("enabled")} isRequired={false}>
Compte actif
</Checkbox>
{!!errors.enabled && <FormErrorMessage>{errors.enabled.message}</FormErrorMessage>}
</FormControl>
{!!errors.fonction && <FormErrorMessage>{errors.fonction.message}</FormErrorMessage>}
</FormControl>
{isError && (
<Alert status="error">
Expand Down
6 changes: 6 additions & 0 deletions ui/app/(wrapped)/admin/users/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const Columns = {
libelleRegion: "Région",
uais: "Uais",
createdAt: "Ajouté le",
fonction: "Fonction",
} satisfies ExportColumns<(typeof client.infer)["[GET]/users"]["users"][number]>;

// eslint-disable-next-line import/no-anonymous-default-export, react/display-name
Expand Down Expand Up @@ -170,6 +171,10 @@ export default () => {
<OrderIcon {...order} column="role" />
{Columns.role}
</Th>
<Th cursor="pointer" onClick={() => handleOrder("fonction")}>
<OrderIcon {...order} column="role" />
{Columns.fonction}
</Th>
<Th cursor="pointer" onClick={() => handleOrder("enabled")}>
<OrderIcon {...order} column="enabled" />
{Columns.enabled}
Expand All @@ -196,6 +201,7 @@ export default () => {
<Td>{user.firstname}</Td>
<Td>{user.lastname}</Td>
<Td>{user.role}</Td>
<Td>{user.fonction ?? "-"}</Td>
<Td>
{user.enabled ? <Badge variant="success">Actif</Badge> : <Badge variant="error">Désactivé</Badge>}
</Td>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import type { AvisTypeType } from "shared/enum/avisTypeEnum";
import type { UserFonction } from "shared/enum/userFonction";

export const FONCTIONS = {
préalable: ["région", "région académique"],
préalable: ["Région", "Région académique"],
consultatif: [
"inspecteur",
"Inspecteur",
"DO CMQ",
"conseiller en formation professionnelle",
"coordonnateur de CFA-A",
"Conseiller en formation professionnelle",
"Coordonnateur de CFA-A",
"DRAIO",
"services DOS",
"Services DOS",
"DASEN",
"région",
"Région",
"DRAFPIC",
"SGRA",
"recteur",
"Recteur",
],
final: ["région", "CSA", "recteur"],
} as Record<AvisTypeType, string[]>;
final: ["Région", "CSA", "Recteur"],
} as Record<AvisTypeType, UserFonction[]>;

0 comments on commit 1685dc2

Please sign in to comment.