Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 93aa2769dd |
+9
-76
@@ -1,95 +1,28 @@
|
|||||||
import {
|
import {
|
||||||
date,
|
date,
|
||||||
doublePrecision,
|
|
||||||
integer,
|
integer,
|
||||||
pgTable,
|
pgTable,
|
||||||
primaryKey,
|
|
||||||
serial,
|
serial,
|
||||||
text,
|
text,
|
||||||
} from "npm:drizzle-orm/pg-core";
|
} from "npm:drizzle-orm/pg-core";
|
||||||
|
|
||||||
export const roles = pgTable("roles", {
|
|
||||||
id: serial("id").primaryKey(),
|
|
||||||
nom: text("nom").notNull(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const permissions = pgTable("permissions", {
|
|
||||||
id: text("id").primaryKey(),
|
|
||||||
nom: text("nom").notNull(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const rolePermissions = pgTable("role_permissions", {
|
|
||||||
idRole: integer("idRole").notNull().references(() => roles.id),
|
|
||||||
idPermission: text("idPermission").notNull().references(() => permissions.id),
|
|
||||||
}, (t) => ({
|
|
||||||
pk: primaryKey({ columns: [t.idRole, t.idPermission] }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const users = pgTable("users", {
|
|
||||||
id: text("id").primaryKey(),
|
|
||||||
nom: text("nom").notNull(),
|
|
||||||
prenom: text("prenom").notNull(),
|
|
||||||
idRole: integer("idRole").references(() => roles.id),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const promotions = pgTable("promotions", {
|
export const promotions = pgTable("promotions", {
|
||||||
id: text("idPromo").primaryKey(),
|
id: serial("id").primaryKey(),
|
||||||
annee: text("annee"),
|
endyear: integer("endyear"),
|
||||||
|
current: integer("current"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const students = pgTable("students", {
|
export const students = pgTable("students", {
|
||||||
numEtud: serial("numEtud").primaryKey(),
|
userId: text("userId").primaryKey(),
|
||||||
nom: text("nom").notNull(),
|
firstName: text("firstName"),
|
||||||
prenom: text("prenom").notNull(),
|
lastName: text("lastName"),
|
||||||
idPromo: text("idPromo").references(() => promotions.id),
|
mail: text("mail"),
|
||||||
|
promotionId: integer("promotionId").references(() => promotions.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const modules = pgTable("modules", {
|
|
||||||
id: text("id").primaryKey(),
|
|
||||||
nom: text("nom").notNull(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const enseignements = pgTable("enseignements", {
|
|
||||||
idProf: text("idProf").notNull().references(() => users.id),
|
|
||||||
idModule: text("idModule").notNull().references(() => modules.id),
|
|
||||||
idPromo: text("idPromo").notNull().references(() => promotions.id),
|
|
||||||
}, (t) => ({
|
|
||||||
pk: primaryKey({ columns: [t.idProf, t.idModule, t.idPromo] }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const ues = pgTable("ues", {
|
|
||||||
id: serial("id").primaryKey(),
|
|
||||||
nom: text("nom").notNull(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ueModules = pgTable("ue_modules", {
|
|
||||||
idModule: text("idModule").notNull().references(() => modules.id),
|
|
||||||
idUE: integer("idUE").notNull().references(() => ues.id),
|
|
||||||
idPromo: text("idPromo").notNull().references(() => promotions.id),
|
|
||||||
coeff: doublePrecision("coeff").notNull(),
|
|
||||||
}, (t) => ({
|
|
||||||
pk: primaryKey({ columns: [t.idModule, t.idUE, t.idPromo] }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const notes = pgTable("notes", {
|
|
||||||
numEtud: integer("numEtud").notNull().references(() => students.numEtud),
|
|
||||||
idModule: text("idModule").notNull().references(() => modules.id),
|
|
||||||
note: doublePrecision("note").notNull(),
|
|
||||||
}, (t) => ({
|
|
||||||
pk: primaryKey({ columns: [t.numEtud, t.idModule] }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const ajustements = pgTable("ajustements", {
|
|
||||||
numEtud: integer("numEtud").notNull().references(() => students.numEtud),
|
|
||||||
idUE: integer("idUE").notNull().references(() => ues.id),
|
|
||||||
valeur: doublePrecision("valeur").notNull(),
|
|
||||||
}, (t) => ({
|
|
||||||
pk: primaryKey({ columns: [t.numEtud, t.idUE] }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const mobility = pgTable("mobility", {
|
export const mobility = pgTable("mobility", {
|
||||||
id: serial("id").primaryKey(),
|
id: serial("id").primaryKey(),
|
||||||
studentId: integer("studentId").references(() => students.numEtud),
|
studentId: text("studentId").references(() => students.userId),
|
||||||
startDate: date("startDate"),
|
startDate: date("startDate"),
|
||||||
endDate: date("endDate"),
|
endDate: date("endDate"),
|
||||||
weeksCount: integer("weeksCount"),
|
weeksCount: integer("weeksCount"),
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
interface Promotion {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Student {
|
||||||
|
id: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
promotionId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Mobility {
|
||||||
|
id: number;
|
||||||
|
studentId: string;
|
||||||
|
startDate: string | null;
|
||||||
|
endDate: string | null;
|
||||||
|
weeksCount: number | null;
|
||||||
|
destinationCountry: string | null;
|
||||||
|
destinationName: string | null;
|
||||||
|
mobilityStatus: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ConsultMobility() {
|
||||||
|
const [data, setData] = useState<
|
||||||
|
| {
|
||||||
|
promotions?: Promotion[];
|
||||||
|
students?: Student[];
|
||||||
|
mobilities?: Mobility[];
|
||||||
|
}
|
||||||
|
| null
|
||||||
|
>(null);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
console.log("ConsultMobility: Fetching data from API...");
|
||||||
|
try {
|
||||||
|
const response = await fetch("/mobility/api/insert_mobility");
|
||||||
|
console.log("ConsultMobility: API response status:", response.status);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error fetching data: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
console.log("ConsultMobility: Data fetched successfully:", result);
|
||||||
|
setData(result);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("ConsultMobility: Error fetching data:", err);
|
||||||
|
setError("Failed to load mobility data. Please try again later.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <p className="error">{error}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data?.promotions) {
|
||||||
|
return <p>No promotions found.</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<h2>Consult Mobility</h2>
|
||||||
|
{data.promotions.map((promo) => (
|
||||||
|
<div key={promo.id}>
|
||||||
|
<h3>Promotion: {promo.name}</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>First Name</th>
|
||||||
|
<th>Last Name</th>
|
||||||
|
<th>Start Date</th>
|
||||||
|
<th>End Date</th>
|
||||||
|
<th>Weeks Count</th>
|
||||||
|
<th>Destination Country</th>
|
||||||
|
<th>Destination Name</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data.students
|
||||||
|
?.filter((student) => student.promotionId === promo.id)
|
||||||
|
.map((student) => {
|
||||||
|
const mobility = data.mobilities?.find((mob) =>
|
||||||
|
mob.studentId === student.id
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<tr key={student.id}>
|
||||||
|
<td>{student.id}</td>
|
||||||
|
<td>{student.firstName}</td>
|
||||||
|
<td>{student.lastName}</td>
|
||||||
|
<td>{mobility?.startDate || "N/A"}</td>
|
||||||
|
<td>{mobility?.endDate || "N/A"}</td>
|
||||||
|
<td>{mobility?.weeksCount ?? "N/A"}</td>
|
||||||
|
<td>{mobility?.destinationCountry || "N/A"}</td>
|
||||||
|
<td>{mobility?.destinationName || "N/A"}</td>
|
||||||
|
<td>{mobility?.mobilityStatus || "N/A"}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
interface Promotion {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Student {
|
||||||
|
id: number;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
mail: string;
|
||||||
|
promotionId: number;
|
||||||
|
promotionName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ConsultStudents_test() {
|
||||||
|
const [data, setData] = useState<
|
||||||
|
{ promotions: Promotion[]; students: Student[] } | null
|
||||||
|
>(null);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/students/api/insert_students");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error fetching data: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
setData(result);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching data:", err);
|
||||||
|
setError("Failed to load data. Please try again later.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<h2>Consult Students</h2>
|
||||||
|
{error && <p className="error">{error}</p>}
|
||||||
|
{data?.promotions.map((promo) => (
|
||||||
|
<div key={promo.id}>
|
||||||
|
<h3>Promotion: {promo.id}</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>First Name</th>
|
||||||
|
<th>Last Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data.students
|
||||||
|
.filter((student) => student.promotionId === promo.id)
|
||||||
|
.map((student) => (
|
||||||
|
<tr key={student.id}>
|
||||||
|
<td>{student.id}</td>
|
||||||
|
<td>{student.firstName}</td>
|
||||||
|
<td>{student.lastName}</td>
|
||||||
|
<td>{student.mail}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
interface Student {
|
||||||
|
id: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
promotionId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Promotion {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Mobility {
|
||||||
|
id: number | null;
|
||||||
|
studentId: string;
|
||||||
|
startDate: string | null;
|
||||||
|
endDate: string | null;
|
||||||
|
weeksCount: number | null;
|
||||||
|
destinationCountry: string | null;
|
||||||
|
destinationName: string | null;
|
||||||
|
mobilityStatus: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EditMobility() {
|
||||||
|
const [data, setData] = useState<
|
||||||
|
| {
|
||||||
|
promotions?: Promotion[];
|
||||||
|
students?: Student[];
|
||||||
|
mobilities?: Mobility[];
|
||||||
|
}
|
||||||
|
| null
|
||||||
|
>(null);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
console.log("EditMobility: Fetching data from API...");
|
||||||
|
try {
|
||||||
|
const response = await fetch("/mobility/api/insert_mobility");
|
||||||
|
console.log("EditMobility: API response status:", response.status);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error fetching data: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
console.log("EditMobility: Data fetched successfully:", result);
|
||||||
|
setData(result);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("EditMobility: Error fetching data:", err);
|
||||||
|
setError("Failed to load mobility data. Please try again later.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleChange = (
|
||||||
|
studentId: string,
|
||||||
|
field: keyof Mobility,
|
||||||
|
value: string | number | null,
|
||||||
|
) => {
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
setData((prevData) => {
|
||||||
|
if (!prevData) return null;
|
||||||
|
|
||||||
|
const updatedMobilities = prevData.mobilities?.map((mobility) => {
|
||||||
|
if (mobility.studentId === studentId) {
|
||||||
|
const updatedMobility = { ...mobility, [field]: value };
|
||||||
|
|
||||||
|
if (field === "startDate" || field === "endDate") {
|
||||||
|
const startDate = new Date(updatedMobility.startDate || "");
|
||||||
|
const endDate = new Date(updatedMobility.endDate || "");
|
||||||
|
if (startDate && endDate && startDate <= endDate) {
|
||||||
|
const weeks = Math.ceil(
|
||||||
|
(endDate.getTime() - startDate.getTime()) /
|
||||||
|
(7 * 24 * 60 * 60 * 1000),
|
||||||
|
);
|
||||||
|
updatedMobility.weeksCount = weeks;
|
||||||
|
} else {
|
||||||
|
updatedMobility.weeksCount = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedMobility;
|
||||||
|
}
|
||||||
|
return mobility;
|
||||||
|
}) || [];
|
||||||
|
|
||||||
|
return { ...prevData, mobilities: updatedMobilities };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
setIsSaving(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/mobility/api/insert_mobility", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ data: data?.mobilities }),
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("EditMobility: Save response status:", response.status);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
alert("Data saved successfully!");
|
||||||
|
globalThis.location.reload();
|
||||||
|
} else {
|
||||||
|
throw new Error(`Failed to save data: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("EditMobility: Error saving data:", error);
|
||||||
|
alert("An error occurred while saving data.");
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <p className="error">{error}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data?.promotions) {
|
||||||
|
return <p>Loading data...</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<h2>Edit Mobility</h2>
|
||||||
|
{data.promotions.map((promo) => (
|
||||||
|
<div key={promo.id}>
|
||||||
|
<h3>Promotion: {promo.name}</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>First Name</th>
|
||||||
|
<th>Last Name</th>
|
||||||
|
<th>Start Date</th>
|
||||||
|
<th>End Date</th>
|
||||||
|
<th>Weeks Count</th>
|
||||||
|
<th>Destination Country</th>
|
||||||
|
<th>Destination Name</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data.students
|
||||||
|
?.filter((student) => student.promotionId === promo.id)
|
||||||
|
.map((student) => {
|
||||||
|
const mobility = data.mobilities?.find((mob) =>
|
||||||
|
mob.studentId === student.id
|
||||||
|
) || {
|
||||||
|
id: null,
|
||||||
|
studentId: student.id,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
weeksCount: null,
|
||||||
|
destinationCountry: null,
|
||||||
|
destinationName: null,
|
||||||
|
mobilityStatus: "N/A",
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={student.id}>
|
||||||
|
<td>{student.id}</td>
|
||||||
|
<td>{student.firstName}</td>
|
||||||
|
<td>{student.lastName}</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={mobility.startDate || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange(
|
||||||
|
student.id,
|
||||||
|
"startDate",
|
||||||
|
e.target.value,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={mobility.endDate || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange(student.id, "endDate", e.target.value)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{mobility.weeksCount ?? "N/A"}</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={mobility.destinationCountry || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange(
|
||||||
|
student.id,
|
||||||
|
"destinationCountry",
|
||||||
|
e.target.value,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={mobility.destinationName || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange(
|
||||||
|
student.id,
|
||||||
|
"destinationName",
|
||||||
|
e.target.value,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select
|
||||||
|
value={mobility.mobilityStatus}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleChange(
|
||||||
|
student.id,
|
||||||
|
"mobilityStatus",
|
||||||
|
e.target.value,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<option value="N/A">N/A</option>
|
||||||
|
<option value="Planned">Planned</option>
|
||||||
|
<option value="In Progress">In Progress</option>
|
||||||
|
<option value="Completed">Completed</option>
|
||||||
|
<option value="Validated">Validated</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<button type="button" onClick={handleSave} disabled={isSaving}>
|
||||||
|
{isSaving ? "Saving..." : "Confirm"}
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
import { AppProperties } from "$root/defaults/interfaces.ts";
|
import { AppProperties } from "$root/defaults/interfaces.ts";
|
||||||
|
|
||||||
const properties: AppProperties = {
|
const properties: AppProperties = {
|
||||||
name: "Admin",
|
name: "PolyMobility",
|
||||||
icon: "school",
|
icon: "flight_takeoff",
|
||||||
|
hint: "Student mobility management",
|
||||||
pages: {
|
pages: {
|
||||||
index: "Homepage",
|
index: "Homepage",
|
||||||
|
overview: "Mobility overview",
|
||||||
|
edit_mobility: "Mobility management",
|
||||||
|
consult_students_test: "Test consult students",
|
||||||
},
|
},
|
||||||
adminOnly: [],
|
adminOnly: ["edit_mobility", "consult_students_test"],
|
||||||
hint: "PolyMPR module",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default properties;
|
export default properties;
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Handlers } from "$fresh/server.ts";
|
||||||
|
import { db } from "$root/databases/db.ts";
|
||||||
|
import { mobility, promotions, students,ens } from "$root/databases/schema.ts";
|
||||||
|
import { eq } from "npm:drizzle-orm";
|
||||||
|
|
||||||
|
export const handler: Handlers = {
|
||||||
|
async GET() {
|
||||||
|
try {
|
||||||
|
// recup les enseigne
|
||||||
|
|
||||||
|
const rows = await
|
||||||
|
db.select().from(students)
|
||||||
|
|
||||||
|
// en faire des json
|
||||||
|
// les retouner
|
||||||
|
return { students: rows as Student[], promos };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Handlers } from "$fresh/server.ts";
|
|
||||||
|
|
||||||
export const handler: Handlers = {
|
|
||||||
async POST(request, context) {
|
|
||||||
if (request.headers.get("content-type") != "application/json") {
|
|
||||||
return new Response(null, {
|
|
||||||
status: 400,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseBody = {
|
|
||||||
requestBody: await request.json(),
|
|
||||||
context,
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(responseBody), {
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import { Handlers } from "$fresh/server.ts";
|
||||||
|
import { db } from "$root/databases/db.ts";
|
||||||
|
import { mobility, promotions, students } from "$root/databases/schema.ts";
|
||||||
|
import { eq } from "npm:drizzle-orm";
|
||||||
|
|
||||||
|
export const handler: Handlers = {
|
||||||
|
async GET() {
|
||||||
|
try {
|
||||||
|
const studentRows = await db
|
||||||
|
.select({
|
||||||
|
id: students.userId,
|
||||||
|
firstName: students.firstName,
|
||||||
|
lastName: students.lastName,
|
||||||
|
promotionId: students.promotionId,
|
||||||
|
endyear: promotions.endyear,
|
||||||
|
current: promotions.current,
|
||||||
|
})
|
||||||
|
.from(students)
|
||||||
|
.leftJoin(promotions, eq(students.promotionId, promotions.id));
|
||||||
|
|
||||||
|
const mobilityRows = await db.select().from(mobility);
|
||||||
|
|
||||||
|
const promotionRows = await db
|
||||||
|
.select({ id: promotions.id, endyear: promotions.endyear, current: promotions.current })
|
||||||
|
.from(promotions);
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
mobilities: mobilityRows,
|
||||||
|
students: studentRows,
|
||||||
|
promotions: promotionRows,
|
||||||
|
}),
|
||||||
|
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching mobility data:", error);
|
||||||
|
return new Response("Failed to fetch data", { status: 500 });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async POST(request) {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const { data } = body;
|
||||||
|
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
throw new Error("Invalid request body");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of data) {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
studentId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
weeksCount,
|
||||||
|
destinationCountry,
|
||||||
|
destinationName,
|
||||||
|
mobilityStatus = "N/A",
|
||||||
|
} = entry;
|
||||||
|
|
||||||
|
const studentExists = await db
|
||||||
|
.select({ userId: students.userId })
|
||||||
|
.from(students)
|
||||||
|
.where(eq(students.userId, studentId))
|
||||||
|
.limit(1)
|
||||||
|
.then((rows) => rows.length > 0);
|
||||||
|
|
||||||
|
if (!studentExists) {
|
||||||
|
console.warn(`Skipping mobility for unknown studentId: ${studentId}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let calculatedWeeksCount = weeksCount;
|
||||||
|
if (startDate && endDate) {
|
||||||
|
const start = new Date(startDate);
|
||||||
|
const end = new Date(endDate);
|
||||||
|
calculatedWeeksCount = start <= end
|
||||||
|
? Math.ceil(
|
||||||
|
(end.getTime() - start.getTime()) / (7 * 24 * 60 * 60 * 1000),
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await db
|
||||||
|
.insert(mobility)
|
||||||
|
.values({
|
||||||
|
id,
|
||||||
|
studentId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
weeksCount: calculatedWeeksCount,
|
||||||
|
destinationCountry,
|
||||||
|
destinationName,
|
||||||
|
mobilityStatus,
|
||||||
|
})
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: mobility.id,
|
||||||
|
set: {
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
weeksCount: calculatedWeeksCount,
|
||||||
|
destinationCountry,
|
||||||
|
destinationName,
|
||||||
|
mobilityStatus,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response("Data inserted/updated successfully", { status: 200 });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error inserting mobility data:", error);
|
||||||
|
return new Response("Failed to insert/update data", { status: 500 });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Handlers } from "$fresh/server.ts";
|
|
||||||
import { AuthenticatedState } from "$root/defaults/interfaces.ts";
|
|
||||||
|
|
||||||
const PERMISSIONS = [
|
|
||||||
{ id: "student_read", nom: "Consulter les élèves" },
|
|
||||||
{ id: "student_write", nom: "Gérer les élèves" },
|
|
||||||
{ id: "note_read", nom: "Consulter les notes" },
|
|
||||||
{ id: "note_write", nom: "Gérer les notes" },
|
|
||||||
{ id: "module_read", nom: "Consulter les modules" },
|
|
||||||
{ id: "module_write", nom: "Gérer les modules" },
|
|
||||||
{ id: "user_read", nom: "Consulter les utilisateurs" },
|
|
||||||
{ id: "user_write", nom: "Gérer les utilisateurs" },
|
|
||||||
{ id: "role_write", nom: "Gérer les rôles" },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const handler: Handlers<null, AuthenticatedState> = {
|
|
||||||
GET(_request, _context): Response {
|
|
||||||
return new Response(JSON.stringify(PERMISSIONS), {
|
|
||||||
headers: { "content-type": "application/json" },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import { FreshContext, Handlers } from "$fresh/server.ts";
|
|
||||||
import { db } from "$root/databases/db.ts";
|
|
||||||
import { rolePermissions, roles } from "$root/databases/schema.ts";
|
|
||||||
import { AuthenticatedState } from "$root/defaults/interfaces.ts";
|
|
||||||
import { eq } from "npm:drizzle-orm";
|
|
||||||
|
|
||||||
async function getRoleWithPermissions(
|
|
||||||
id: number,
|
|
||||||
): Promise<{ id: number; nom: string; permissions: string[] } | null> {
|
|
||||||
const role = await db
|
|
||||||
.select()
|
|
||||||
.from(roles)
|
|
||||||
.where(eq(roles.id, id))
|
|
||||||
.then((rows) => rows[0] ?? null);
|
|
||||||
|
|
||||||
if (!role) return null;
|
|
||||||
|
|
||||||
const perms = await db
|
|
||||||
.select({ idPermission: rolePermissions.idPermission })
|
|
||||||
.from(rolePermissions)
|
|
||||||
.where(eq(rolePermissions.idRole, id));
|
|
||||||
|
|
||||||
return { id: role.id, nom: role.nom, permissions: perms.map((p) => p.idPermission) };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const handler: Handlers<null, AuthenticatedState> = {
|
|
||||||
// #65 GET /roles
|
|
||||||
async GET(
|
|
||||||
_request: Request,
|
|
||||||
_context: FreshContext<AuthenticatedState>,
|
|
||||||
): Promise<Response> {
|
|
||||||
const allRoles = await db.select().from(roles);
|
|
||||||
|
|
||||||
const result = await Promise.all(
|
|
||||||
allRoles.map((r) => getRoleWithPermissions(r.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(result), {
|
|
||||||
headers: { "content-type": "application/json" },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// #66 POST /roles
|
|
||||||
async POST(
|
|
||||||
request: Request,
|
|
||||||
_context: FreshContext<AuthenticatedState>,
|
|
||||||
): Promise<Response> {
|
|
||||||
const body: { nom: string } = await request.json();
|
|
||||||
|
|
||||||
if (!body.nom) {
|
|
||||||
return new Response(null, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const [created] = await db
|
|
||||||
.insert(roles)
|
|
||||||
.values({ nom: body.nom })
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ id: created.id, nom: created.nom, permissions: [] }),
|
|
||||||
{ status: 201, headers: { "content-type": "application/json" } },
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
import { FreshContext, Handlers } from "$fresh/server.ts";
|
|
||||||
import { db } from "$root/databases/db.ts";
|
|
||||||
import { rolePermissions, roles } from "$root/databases/schema.ts";
|
|
||||||
import { AuthenticatedState } from "$root/defaults/interfaces.ts";
|
|
||||||
import { eq } from "npm:drizzle-orm";
|
|
||||||
|
|
||||||
const NOT_FOUND = new Response(
|
|
||||||
JSON.stringify({ error: "Ressource introuvable" }),
|
|
||||||
{ status: 404, headers: { "content-type": "application/json" } },
|
|
||||||
);
|
|
||||||
|
|
||||||
async function getRoleWithPermissions(
|
|
||||||
id: number,
|
|
||||||
): Promise<{ id: number; nom: string; permissions: string[] } | null> {
|
|
||||||
const role = await db
|
|
||||||
.select()
|
|
||||||
.from(roles)
|
|
||||||
.where(eq(roles.id, id))
|
|
||||||
.then((rows) => rows[0] ?? null);
|
|
||||||
|
|
||||||
if (!role) return null;
|
|
||||||
|
|
||||||
const perms = await db
|
|
||||||
.select({ idPermission: rolePermissions.idPermission })
|
|
||||||
.from(rolePermissions)
|
|
||||||
.where(eq(rolePermissions.idRole, id));
|
|
||||||
|
|
||||||
return { id: role.id, nom: role.nom, permissions: perms.map((p) => p.idPermission) };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const handler: Handlers<null, AuthenticatedState> = {
|
|
||||||
// #67 GET /roles/{idRole}
|
|
||||||
async GET(
|
|
||||||
_request: Request,
|
|
||||||
context: FreshContext<AuthenticatedState>,
|
|
||||||
): Promise<Response> {
|
|
||||||
const id = Number(context.params.idRole);
|
|
||||||
const role = await getRoleWithPermissions(id);
|
|
||||||
|
|
||||||
if (!role) return NOT_FOUND;
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(role), {
|
|
||||||
headers: { "content-type": "application/json" },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// #68 PUT /roles/{idRole}
|
|
||||||
async PUT(
|
|
||||||
request: Request,
|
|
||||||
context: FreshContext<AuthenticatedState>,
|
|
||||||
): Promise<Response> {
|
|
||||||
const id = Number(context.params.idRole);
|
|
||||||
const body: { nom: string; permissions: string[] } = await request.json();
|
|
||||||
|
|
||||||
const [updated] = await db
|
|
||||||
.update(roles)
|
|
||||||
.set({ nom: body.nom })
|
|
||||||
.where(eq(roles.id, id))
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
if (!updated) return NOT_FOUND;
|
|
||||||
|
|
||||||
// Reset permissions
|
|
||||||
await db.delete(rolePermissions).where(eq(rolePermissions.idRole, id));
|
|
||||||
|
|
||||||
if (body.permissions?.length) {
|
|
||||||
await db.insert(rolePermissions).values(
|
|
||||||
body.permissions.map((p) => ({ idRole: id, idPermission: p })),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const role = await getRoleWithPermissions(id);
|
|
||||||
return new Response(JSON.stringify(role), {
|
|
||||||
headers: { "content-type": "application/json" },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// #69 DELETE /roles/{idRole}
|
|
||||||
async DELETE(
|
|
||||||
_request: Request,
|
|
||||||
context: FreshContext<AuthenticatedState>,
|
|
||||||
): Promise<Response> {
|
|
||||||
const id = Number(context.params.idRole);
|
|
||||||
|
|
||||||
// Cascade delete role_permissions first
|
|
||||||
await db.delete(rolePermissions).where(eq(rolePermissions.idRole, id));
|
|
||||||
|
|
||||||
const [deleted] = await db
|
|
||||||
.delete(roles)
|
|
||||||
.where(eq(roles.id, id))
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
if (!deleted) return NOT_FOUND;
|
|
||||||
|
|
||||||
return new Response(null, { status: 204 });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import { FreshContext, Handlers } from "$fresh/server.ts";
|
|
||||||
import { db } from "$root/databases/db.ts";
|
|
||||||
import { users } from "$root/databases/schema.ts";
|
|
||||||
import { AuthenticatedState } from "$root/defaults/interfaces.ts";
|
|
||||||
import { eq } from "npm:drizzle-orm";
|
|
||||||
|
|
||||||
export const handler: Handlers<null, AuthenticatedState> = {
|
|
||||||
// #60 GET /users
|
|
||||||
async GET(
|
|
||||||
request: Request,
|
|
||||||
_context: FreshContext<AuthenticatedState>,
|
|
||||||
): Promise<Response> {
|
|
||||||
const url = new URL(request.url);
|
|
||||||
const idRole = url.searchParams.get("idRole");
|
|
||||||
|
|
||||||
const rows = idRole
|
|
||||||
? await db.select().from(users).where(eq(users.idRole, Number(idRole)))
|
|
||||||
: await db.select().from(users);
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(rows), {
|
|
||||||
headers: { "content-type": "application/json" },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// #61 POST /users
|
|
||||||
async POST(
|
|
||||||
request: Request,
|
|
||||||
_context: FreshContext<AuthenticatedState>,
|
|
||||||
): Promise<Response> {
|
|
||||||
const body: { id: string; nom: string; prenom: string; idRole: number } =
|
|
||||||
await request.json();
|
|
||||||
|
|
||||||
if (!body.id || !body.nom || !body.prenom) {
|
|
||||||
return new Response(null, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = await db
|
|
||||||
.select()
|
|
||||||
.from(users)
|
|
||||||
.where(eq(users.id, body.id))
|
|
||||||
.then((rows) => rows[0] ?? null);
|
|
||||||
|
|
||||||
if (existing) {
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ error: "Un utilisateur avec cet identifiant existe déjà" }),
|
|
||||||
{ status: 409, headers: { "content-type": "application/json" } },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [created] = await db
|
|
||||||
.insert(users)
|
|
||||||
.values({ id: body.id, nom: body.nom, prenom: body.prenom, idRole: body.idRole })
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(created), {
|
|
||||||
status: 201,
|
|
||||||
headers: { "content-type": "application/json" },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { FreshContext, Handlers } from "$fresh/server.ts";
|
|
||||||
import { db } from "$root/databases/db.ts";
|
|
||||||
import { users } from "$root/databases/schema.ts";
|
|
||||||
import { AuthenticatedState } from "$root/defaults/interfaces.ts";
|
|
||||||
import { eq } from "npm:drizzle-orm";
|
|
||||||
|
|
||||||
const NOT_FOUND = new Response(
|
|
||||||
JSON.stringify({ error: "Ressource introuvable" }),
|
|
||||||
{ status: 404, headers: { "content-type": "application/json" } },
|
|
||||||
);
|
|
||||||
|
|
||||||
export const handler: Handlers<null, AuthenticatedState> = {
|
|
||||||
// #62 GET /users/{id}
|
|
||||||
async GET(
|
|
||||||
_request: Request,
|
|
||||||
context: FreshContext<AuthenticatedState>,
|
|
||||||
): Promise<Response> {
|
|
||||||
const user = await db
|
|
||||||
.select()
|
|
||||||
.from(users)
|
|
||||||
.where(eq(users.id, context.params.id))
|
|
||||||
.then((rows) => rows[0] ?? null);
|
|
||||||
|
|
||||||
if (!user) return NOT_FOUND;
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(user), {
|
|
||||||
headers: { "content-type": "application/json" },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// #63 PUT /users/{id}
|
|
||||||
async PUT(
|
|
||||||
request: Request,
|
|
||||||
context: FreshContext<AuthenticatedState>,
|
|
||||||
): Promise<Response> {
|
|
||||||
const body: { nom: string; prenom: string; idRole: number } =
|
|
||||||
await request.json();
|
|
||||||
|
|
||||||
const [updated] = await db
|
|
||||||
.update(users)
|
|
||||||
.set({ nom: body.nom, prenom: body.prenom, idRole: body.idRole })
|
|
||||||
.where(eq(users.id, context.params.id))
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
if (!updated) return NOT_FOUND;
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(updated), {
|
|
||||||
headers: { "content-type": "application/json" },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// #64 DELETE /users/{id}
|
|
||||||
async DELETE(
|
|
||||||
_request: Request,
|
|
||||||
context: FreshContext<AuthenticatedState>,
|
|
||||||
): Promise<Response> {
|
|
||||||
const [deleted] = await db
|
|
||||||
.delete(users)
|
|
||||||
.where(eq(users.id, context.params.id))
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
if (!deleted) return NOT_FOUND;
|
|
||||||
|
|
||||||
return new Response(null, { status: 204 });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import ConsultStudents_test from "$root/routes/(apps)/mobility/(_islands)/ConsultStudents_test.tsx";
|
||||||
|
import {
|
||||||
|
getPartialsConfig,
|
||||||
|
makePartials,
|
||||||
|
} from "$root/defaults/makePartials.tsx";
|
||||||
|
import { FreshContext } from "$fresh/server.ts";
|
||||||
|
import { State } from "$root/routes/_middleware.ts";
|
||||||
|
//import EditStudents from "../(_islands)/EditStudents.tsx";
|
||||||
|
|
||||||
|
// deno-lint-ignore require-await
|
||||||
|
async function Mobility(_request: Request, _context: FreshContext<State>) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Test consult students</h1>
|
||||||
|
<ConsultStudents_test />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = getPartialsConfig();
|
||||||
|
export default makePartials(Mobility);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import EditMobility from "$root/routes/(apps)/mobility/(_islands)/EditMobility.tsx";
|
||||||
|
import {
|
||||||
|
getPartialsConfig,
|
||||||
|
makePartials,
|
||||||
|
} from "$root/defaults/makePartials.tsx";
|
||||||
|
import { FreshContext } from "$fresh/server.ts";
|
||||||
|
import { State } from "$root/routes/_middleware.ts";
|
||||||
|
|
||||||
|
// deno-lint-ignore require-await
|
||||||
|
async function Mobility(_request: Request, _context: FreshContext<State>) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Edit mobility</h1>
|
||||||
|
<EditMobility />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = getPartialsConfig();
|
||||||
|
export default makePartials(Mobility);
|
||||||
@@ -5,8 +5,9 @@ import {
|
|||||||
import { FreshContext } from "$fresh/server.ts";
|
import { FreshContext } from "$fresh/server.ts";
|
||||||
import { State } from "$root/routes/_middleware.ts";
|
import { State } from "$root/routes/_middleware.ts";
|
||||||
|
|
||||||
export async function Index(request: Request, context: FreshContext<State>) {
|
// deno-lint-ignore require-await
|
||||||
return <h2>Welcome to Admin.</h2>;
|
export async function Index(_request: Request, context: FreshContext<State>) {
|
||||||
|
return <h2>Welcome to {context.state.session?.displayName}.</h2>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = getPartialsConfig();
|
export const config = getPartialsConfig();
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import ConsultMobility from "$root/routes/(apps)/mobility/(_islands)/ConsultMobility.tsx";
|
||||||
|
import {
|
||||||
|
getPartialsConfig,
|
||||||
|
makePartials,
|
||||||
|
} from "$root/defaults/makePartials.tsx";
|
||||||
|
import { FreshContext } from "$fresh/server.ts";
|
||||||
|
import { State } from "$root/routes/_middleware.ts";
|
||||||
|
|
||||||
|
// deno-lint-ignore require-await
|
||||||
|
async function Mobility(_request: Request, _context: FreshContext<State>) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Edit mobility</h1>
|
||||||
|
<ConsultMobility />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = getPartialsConfig();
|
||||||
|
export default makePartials(Mobility);
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Handlers } from "$fresh/server.ts";
|
|
||||||
import { db } from "../../../../databases/db.ts";
|
|
||||||
import { ues } from "../../../../databases/schema.ts";
|
|
||||||
|
|
||||||
export const handler: Handlers = {
|
|
||||||
async GET() {
|
|
||||||
try {
|
|
||||||
const result = await db.select().from(ues);
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(result), {
|
|
||||||
status: 200,
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching UEs:", error);
|
|
||||||
return new Response("Failed to fetch data", { status: 500 });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,57 +1,122 @@
|
|||||||
import { FreshContext, Handlers } from "$fresh/server.ts";
|
import { FreshContext, Handlers } from "$fresh/server.ts";
|
||||||
import { db } from "$root/databases/db.ts";
|
import { db } from "$root/databases/db.ts";
|
||||||
import { students } from "$root/databases/schema.ts";
|
import { promotions, students } from "$root/databases/schema.ts";
|
||||||
import { AuthenticatedState } from "$root/defaults/interfaces.ts";
|
import { AuthenticatedState } from "$root/defaults/interfaces.ts";
|
||||||
import { eq } from "npm:drizzle-orm";
|
import { eq, lt } from "npm:drizzle-orm";
|
||||||
|
|
||||||
|
async function getItself(
|
||||||
|
userId: string,
|
||||||
|
): Promise<{ student: Student | null; promo: Promotion | null }> {
|
||||||
|
const student = await db
|
||||||
|
.select()
|
||||||
|
.from(students)
|
||||||
|
.where(eq(students.userId, userId))
|
||||||
|
.limit(1)
|
||||||
|
.then((rows) => rows[0] ?? null);
|
||||||
|
|
||||||
|
if (!student) {
|
||||||
|
return { student: null, promo: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const promo = await db
|
||||||
|
.select()
|
||||||
|
.from(promotions)
|
||||||
|
.where(eq(promotions.id, student.promotionId!))
|
||||||
|
.limit(1)
|
||||||
|
.then((rows) => rows[0] ?? null);
|
||||||
|
|
||||||
|
return { student, promo };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAll(): Promise<
|
||||||
|
{ students: Student[]; promos: Promotion[] }
|
||||||
|
> {
|
||||||
|
const rows = await db
|
||||||
|
.select({
|
||||||
|
userId: students.userId,
|
||||||
|
firstName: students.firstName,
|
||||||
|
lastName: students.lastName,
|
||||||
|
mail: students.mail,
|
||||||
|
promotionId: students.promotionId,
|
||||||
|
})
|
||||||
|
.from(students)
|
||||||
|
.innerJoin(promotions, eq(students.promotionId, promotions.id))
|
||||||
|
.where(lt(promotions.current, 6));
|
||||||
|
|
||||||
|
const promos = await db
|
||||||
|
.select()
|
||||||
|
.from(promotions)
|
||||||
|
.where(lt(promotions.current, 6));
|
||||||
|
|
||||||
|
return { students: rows as Student[], promos };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addStudents(
|
||||||
|
studentList: Student[],
|
||||||
|
promoId: number,
|
||||||
|
): Promise<void> {
|
||||||
|
for (const student of studentList) {
|
||||||
|
await db
|
||||||
|
.insert(students)
|
||||||
|
.values({
|
||||||
|
userId: student.userId,
|
||||||
|
firstName: student.firstName,
|
||||||
|
lastName: student.lastName,
|
||||||
|
mail: student.mail,
|
||||||
|
promotionId: promoId,
|
||||||
|
})
|
||||||
|
.onConflictDoNothing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const handler: Handlers<null, AuthenticatedState> = {
|
export const handler: Handlers<null, AuthenticatedState> = {
|
||||||
// #7 GET /students
|
|
||||||
async GET(
|
async GET(
|
||||||
request: Request,
|
_request: Request,
|
||||||
context: FreshContext<AuthenticatedState>,
|
context: FreshContext<AuthenticatedState>,
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
if (context.state.session.eduPersonPrimaryAffiliation !== "employee") {
|
if (context.state.session.eduPersonPrimaryAffiliation == "student") {
|
||||||
return new Response(JSON.stringify([]), {
|
return new Response(
|
||||||
headers: { "content-type": "application/json" },
|
JSON.stringify(await getItself(context.state.session.uid)),
|
||||||
});
|
{ headers: { "content-type": "application/json" } },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(request.url);
|
return new Response(
|
||||||
const idPromo = url.searchParams.get("idPromo");
|
JSON.stringify(await getAll()),
|
||||||
|
{ headers: { "content-type": "application/json" } },
|
||||||
const rows = idPromo
|
);
|
||||||
? await db.select().from(students).where(eq(students.idPromo, idPromo))
|
|
||||||
: await db.select().from(students);
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(rows), {
|
|
||||||
headers: { "content-type": "application/json" },
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// #8 POST /students
|
|
||||||
async POST(
|
async POST(
|
||||||
request: Request,
|
request: Request,
|
||||||
context: FreshContext<AuthenticatedState>,
|
_context: FreshContext<AuthenticatedState>,
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
if (context.state.session.eduPersonPrimaryAffiliation !== "employee") {
|
const { students: studentList, promo }: {
|
||||||
return new Response(null, { status: 403 });
|
students: Student[];
|
||||||
}
|
promo: string;
|
||||||
|
} = await request.json();
|
||||||
|
|
||||||
const body: { numEtud: number; nom: string; prenom: string; idPromo: string } =
|
if (!promo || !promo.match(/^\d{4}-\dA$/) || !Array.isArray(studentList)) {
|
||||||
await request.json();
|
|
||||||
|
|
||||||
if (!body.nom || !body.prenom || !body.idPromo) {
|
|
||||||
return new Response(null, { status: 400 });
|
return new Response(null, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const [created] = await db
|
const { endyear, current } = promo.match(
|
||||||
.insert(students)
|
/^(?<endyear>\d{4})-(?<current>\d)A$/,
|
||||||
.values({ nom: body.nom, prenom: body.prenom, idPromo: body.idPromo })
|
)?.groups!;
|
||||||
.returning();
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(created), {
|
await db
|
||||||
status: 201,
|
.insert(promotions)
|
||||||
headers: { "content-type": "application/json" },
|
.values({ endyear: Number(endyear), current: Number(current) })
|
||||||
});
|
.onConflictDoNothing();
|
||||||
|
|
||||||
|
const promo_row = await db
|
||||||
|
.select()
|
||||||
|
.from(promotions)
|
||||||
|
.where(eq(promotions.endyear, Number(endyear)))
|
||||||
|
.then((rows) => rows.find((r) => r.current === Number(current))!);
|
||||||
|
|
||||||
|
await addStudents(studentList, promo_row.id);
|
||||||
|
|
||||||
|
return new Response(null, { status: 201 });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
import { FreshContext, Handlers } from "$fresh/server.ts";
|
|
||||||
import { db } from "$root/databases/db.ts";
|
|
||||||
import { students } from "$root/databases/schema.ts";
|
|
||||||
import { AuthenticatedState } from "$root/defaults/interfaces.ts";
|
|
||||||
|
|
||||||
// #9 POST /students/import-csv
|
|
||||||
export const handler: Handlers<null, AuthenticatedState> = {
|
|
||||||
async POST(
|
|
||||||
request: Request,
|
|
||||||
context: FreshContext<AuthenticatedState>,
|
|
||||||
): Promise<Response> {
|
|
||||||
if (context.state.session.eduPersonPrimaryAffiliation !== "employee") {
|
|
||||||
return new Response(null, { status: 403 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = await request.formData();
|
|
||||||
const file = formData.get("file") as File | null;
|
|
||||||
const idPromo = formData.get("idPromo") as string | null;
|
|
||||||
|
|
||||||
if (!file || !idPromo) {
|
|
||||||
return new Response(null, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = await file.text();
|
|
||||||
const lines = text.trim().split("\n");
|
|
||||||
|
|
||||||
let imported = 0;
|
|
||||||
const errors: { line: number; message: string }[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
const lineNum = i + 1;
|
|
||||||
const cols = lines[i].split(",").map((c) => c.trim());
|
|
||||||
|
|
||||||
const [numEtudStr, nom, prenom] = cols;
|
|
||||||
|
|
||||||
if (!numEtudStr) {
|
|
||||||
errors.push({ line: lineNum, message: "Numéro étudiant manquant" });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const numEtud = Number(numEtudStr);
|
|
||||||
if (isNaN(numEtud)) {
|
|
||||||
errors.push({ line: lineNum, message: "Numéro étudiant invalide" });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!nom || !prenom) {
|
|
||||||
errors.push({ line: lineNum, message: "Nom ou prénom manquant" });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await db
|
|
||||||
.insert(students)
|
|
||||||
.values({ nom, prenom, idPromo })
|
|
||||||
.onConflictDoNothing();
|
|
||||||
|
|
||||||
imported++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response(JSON.stringify({ imported, errors }), {
|
|
||||||
headers: { "content-type": "application/json" },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user