refactor(mobility): switch to Drizzle ORM and remove raw SQLite usage
- replace Database with db instance - use schema imports for tables - use db.select, db.insert, onConflictDoUpdate - remove manual connection handling and console logs - improve type safety and maintainability refactor(students): migrate to Drizzle ORM and async queries Replace raw sqlite queries with Drizzle ORM. Remove the connect helper and use the shared db instance and schema definitions. Convert getItself, getAll and addStudents to async functions, use eq and lt helpers, and simplify promotion handling. This improves type safety, maintainability, and allows non‑blocking database access.
This commit is contained in:
@@ -1,55 +1,36 @@
|
|||||||
import { Handlers } from "$fresh/server.ts";
|
import { Handlers } from "$fresh/server.ts";
|
||||||
import { Database } from "@db/sqlite";
|
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 = {
|
export const handler: Handlers = {
|
||||||
// deno-lint-ignore require-await
|
|
||||||
async GET() {
|
async GET() {
|
||||||
try {
|
try {
|
||||||
console.log("Connecting to mobility database...");
|
const studentRows = await db
|
||||||
const connection = new Database("databases/data/mobility.db", {
|
.select({
|
||||||
create: false,
|
id: students.userId,
|
||||||
});
|
firstName: students.firstName,
|
||||||
connection.run(
|
lastName: students.lastName,
|
||||||
"ATTACH DATABASE 'databases/data/students.db' AS students",
|
promotionId: students.promotionId,
|
||||||
);
|
endyear: promotions.endyear,
|
||||||
console.log("Connected to databases.");
|
current: promotions.current,
|
||||||
|
})
|
||||||
|
.from(students)
|
||||||
|
.leftJoin(promotions, eq(students.promotionId, promotions.id));
|
||||||
|
|
||||||
const students = connection.prepare(
|
const mobilityRows = await db.select().from(mobility);
|
||||||
`SELECT
|
|
||||||
students.userId AS id,
|
|
||||||
students.firstName,
|
|
||||||
students.lastName,
|
|
||||||
students.promotionId AS promotionId,
|
|
||||||
promotions.name AS promotionName
|
|
||||||
FROM students.students
|
|
||||||
LEFT JOIN students.promotions ON students.promotionId = promotions.id`,
|
|
||||||
).all();
|
|
||||||
|
|
||||||
const mobilities = connection.prepare(
|
const promotionRows = await db
|
||||||
`SELECT
|
.select({ id: promotions.id, endyear: promotions.endyear, current: promotions.current })
|
||||||
mobility.id,
|
.from(promotions);
|
||||||
mobility.studentId,
|
|
||||||
mobility.startDate,
|
|
||||||
mobility.endDate,
|
|
||||||
mobility.weeksCount,
|
|
||||||
mobility.destinationCountry,
|
|
||||||
mobility.destinationName,
|
|
||||||
mobility.mobilityStatus
|
|
||||||
FROM mobility`,
|
|
||||||
).all();
|
|
||||||
|
|
||||||
const promotions = connection.prepare(
|
|
||||||
`SELECT id, name FROM students.promotions`,
|
|
||||||
).all();
|
|
||||||
|
|
||||||
connection.close();
|
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({ mobilities, students, promotions }),
|
JSON.stringify({
|
||||||
{
|
mobilities: mobilityRows,
|
||||||
status: 200,
|
students: studentRows,
|
||||||
headers: { "Content-Type": "application/json" },
|
promotions: promotionRows,
|
||||||
},
|
}),
|
||||||
|
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching mobility data:", error);
|
console.error("Error fetching mobility data:", error);
|
||||||
@@ -58,8 +39,6 @@ export const handler: Handlers = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async POST(request) {
|
async POST(request) {
|
||||||
console.log("API /mobility/api/insert_mobility POST called");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { data } = body;
|
const { data } = body;
|
||||||
@@ -67,32 +46,8 @@ export const handler: Handlers = {
|
|||||||
if (!Array.isArray(data)) {
|
if (!Array.isArray(data)) {
|
||||||
throw new Error("Invalid request body");
|
throw new Error("Invalid request body");
|
||||||
}
|
}
|
||||||
console.log("Connecting to mobility database...");
|
|
||||||
const connection = new Database("databases/data/mobility.db", {
|
|
||||||
create: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("Attaching students database...");
|
for (const entry of data) {
|
||||||
connection.run(
|
|
||||||
"ATTACH DATABASE 'databases/data/students.db' AS students",
|
|
||||||
);
|
|
||||||
console.log("Students database attached successfully.");
|
|
||||||
|
|
||||||
const insertQuery = connection.prepare(
|
|
||||||
`INSERT INTO mobility (
|
|
||||||
id, studentId, startDate, endDate, weeksCount, destinationCountry, destinationName, mobilityStatus
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
ON CONFLICT(id) DO UPDATE SET
|
|
||||||
startDate = excluded.startDate,
|
|
||||||
endDate = excluded.endDate,
|
|
||||||
weeksCount = excluded.weeksCount,
|
|
||||||
destinationCountry = excluded.destinationCountry,
|
|
||||||
destinationName = excluded.destinationName,
|
|
||||||
mobilityStatus = excluded.mobilityStatus`,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const mobility of data) {
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
studentId,
|
studentId,
|
||||||
@@ -102,19 +57,16 @@ export const handler: Handlers = {
|
|||||||
destinationCountry,
|
destinationCountry,
|
||||||
destinationName,
|
destinationName,
|
||||||
mobilityStatus = "N/A",
|
mobilityStatus = "N/A",
|
||||||
} = mobility;
|
} = entry;
|
||||||
|
|
||||||
console.log("Processing mobility data:", mobility);
|
const studentExists = await db
|
||||||
|
.select({ userId: students.userId })
|
||||||
|
.from(students)
|
||||||
|
.where(eq(students.userId, studentId))
|
||||||
|
.limit(1)
|
||||||
|
.then((rows) => rows.length > 0);
|
||||||
|
|
||||||
const studentExists = connection
|
if (!studentExists) {
|
||||||
.prepare(
|
|
||||||
`SELECT COUNT(*) AS count FROM students.students WHERE userId = ?`,
|
|
||||||
)
|
|
||||||
.get(studentId);
|
|
||||||
|
|
||||||
console.log(`Student ${studentId} exists:`, studentExists.count > 0);
|
|
||||||
|
|
||||||
if (studentExists.count === 0) {
|
|
||||||
console.warn(`Skipping mobility for unknown studentId: ${studentId}`);
|
console.warn(`Skipping mobility for unknown studentId: ${studentId}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -123,43 +75,39 @@ export const handler: Handlers = {
|
|||||||
if (startDate && endDate) {
|
if (startDate && endDate) {
|
||||||
const start = new Date(startDate);
|
const start = new Date(startDate);
|
||||||
const end = new Date(endDate);
|
const end = new Date(endDate);
|
||||||
if (start <= end) {
|
calculatedWeeksCount = start <= end
|
||||||
calculatedWeeksCount = Math.ceil(
|
? Math.ceil(
|
||||||
(end.getTime() - start.getTime()) / (7 * 24 * 60 * 60 * 1000),
|
(end.getTime() - start.getTime()) / (7 * 24 * 60 * 60 * 1000),
|
||||||
);
|
)
|
||||||
} else {
|
: null;
|
||||||
calculatedWeeksCount = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Executing SQL insert/update query for:", {
|
await db
|
||||||
id,
|
.insert(mobility)
|
||||||
studentId,
|
.values({
|
||||||
startDate,
|
id,
|
||||||
endDate,
|
studentId,
|
||||||
calculatedWeeksCount,
|
startDate,
|
||||||
destinationCountry,
|
endDate,
|
||||||
destinationName,
|
weeksCount: calculatedWeeksCount,
|
||||||
mobilityStatus,
|
destinationCountry,
|
||||||
});
|
destinationName,
|
||||||
|
mobilityStatus,
|
||||||
insertQuery.run(
|
})
|
||||||
id,
|
.onConflictDoUpdate({
|
||||||
studentId,
|
target: mobility.id,
|
||||||
startDate,
|
set: {
|
||||||
endDate,
|
startDate,
|
||||||
calculatedWeeksCount,
|
endDate,
|
||||||
destinationCountry,
|
weeksCount: calculatedWeeksCount,
|
||||||
destinationName,
|
destinationCountry,
|
||||||
mobilityStatus,
|
destinationName,
|
||||||
);
|
mobilityStatus,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
connection.close();
|
return new Response("Data inserted/updated successfully", { status: 200 });
|
||||||
console.log("Mobility data inserted/updated successfully.");
|
|
||||||
return new Response("Data inserted/updated successfully", {
|
|
||||||
status: 200,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error inserting mobility data:", error);
|
console.error("Error inserting mobility data:", error);
|
||||||
return new Response("Failed to insert/update data", { status: 500 });
|
return new Response("Failed to insert/update data", { status: 500 });
|
||||||
|
|||||||
@@ -1,150 +1,121 @@
|
|||||||
import { FreshContext, Handlers } from "$fresh/server.ts";
|
import { FreshContext, Handlers } from "$fresh/server.ts";
|
||||||
import connect from "$root/databases/connect.ts";
|
import { db } from "$root/databases/db.ts";
|
||||||
|
import { promotions, students } from "$root/databases/schema.ts";
|
||||||
import { AuthenticatedState } from "$root/defaults/interfaces.ts";
|
import { AuthenticatedState } from "$root/defaults/interfaces.ts";
|
||||||
import { Database } from "@db/sqlite";
|
import { eq, lt } from "npm:drizzle-orm";
|
||||||
|
|
||||||
/**
|
async function getItself(
|
||||||
* Gets itself from the database.
|
|
||||||
* @param database The database connection
|
|
||||||
* @param userId The user ID.
|
|
||||||
* @returns Itself from the database.
|
|
||||||
*/
|
|
||||||
function getItself(
|
|
||||||
database: Database,
|
|
||||||
userId: string,
|
userId: string,
|
||||||
): { student: Student | null; promo: Promotion | null } {
|
): Promise<{ student: Student | null; promo: Promotion | null }> {
|
||||||
const studentQuery = "select * from students where userId = ?";
|
const student = await db
|
||||||
const student: Student | undefined = database.prepare(studentQuery).get(
|
.select()
|
||||||
userId,
|
.from(students)
|
||||||
);
|
.where(eq(students.userId, userId))
|
||||||
|
.limit(1)
|
||||||
|
.then((rows) => rows[0] ?? null);
|
||||||
|
|
||||||
if (!student) {
|
if (!student) {
|
||||||
return { student: null, promo: null };
|
return { student: null, promo: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
const promoQuery = "select * from promotions where id = ?";
|
const promo = await db
|
||||||
const promo: Promotion | undefined = database.prepare(promoQuery).get(
|
.select()
|
||||||
student.promotionId,
|
.from(promotions)
|
||||||
);
|
.where(eq(promotions.id, student.promotionId!))
|
||||||
|
.limit(1)
|
||||||
|
.then((rows) => rows[0] ?? null);
|
||||||
|
|
||||||
return { student, promo: promo ?? null };
|
return { student, promo };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function getAll(): Promise<
|
||||||
* Gets itself from the database.
|
{ students: Student[]; promos: Promotion[] }
|
||||||
* @param database The database connexion
|
> {
|
||||||
* @param userId The user ID.
|
const rows = await db
|
||||||
* @returns Itself from the database.
|
.select({
|
||||||
*/
|
userId: students.userId,
|
||||||
function getAll(
|
firstName: students.firstName,
|
||||||
database: Database,
|
lastName: students.lastName,
|
||||||
): { students: Student[]; promos: Promotion[] } {
|
mail: students.mail,
|
||||||
const studentsQuery = `
|
promotionId: students.promotionId,
|
||||||
select userId, firstName, lastName, mail, promotionId
|
})
|
||||||
from students inner join promotions
|
.from(students)
|
||||||
on students.promotionId = promotions.id
|
.innerJoin(promotions, eq(students.promotionId, promotions.id))
|
||||||
where promotions.current < 6`;
|
.where(lt(promotions.current, 6));
|
||||||
const students: Student[] = database.prepare(studentsQuery).all();
|
|
||||||
|
|
||||||
const promosQuery = "select * from promotions where promotions.current < 6";
|
const promos = await db
|
||||||
const promos: Promotion[] | undefined = database.prepare(promosQuery).all();
|
.select()
|
||||||
|
.from(promotions)
|
||||||
|
.where(lt(promotions.current, 6));
|
||||||
|
|
||||||
return { students, promos };
|
return { students: rows as Student[], promos };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function addStudents(
|
||||||
* Add users to the database.
|
studentList: Student[],
|
||||||
* @param database The database connexion
|
promoId: number,
|
||||||
* @param students The students to add
|
): Promise<void> {
|
||||||
* @param promoId The promotion id.
|
for (const student of studentList) {
|
||||||
*/
|
await db
|
||||||
function addStudents(database: Database, students: Student[], promoId: string) {
|
.insert(students)
|
||||||
const query = `
|
.values({
|
||||||
INSERT INTO students
|
userId: student.userId,
|
||||||
(userId, firstName, lastName, mail, promotionId)
|
firstName: student.firstName,
|
||||||
VALUES (?, ?, ?, ?, ?)`;
|
lastName: student.lastName,
|
||||||
|
mail: student.mail,
|
||||||
const statement = database.prepare(query);
|
promotionId: promoId,
|
||||||
|
})
|
||||||
for (const student of students) {
|
.onConflictDoNothing();
|
||||||
statement.run(
|
|
||||||
student.userId,
|
|
||||||
student.firstName,
|
|
||||||
student.lastName,
|
|
||||||
student.mail,
|
|
||||||
promoId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const handler: Handlers<null, AuthenticatedState> = {
|
export const handler: Handlers<null, AuthenticatedState> = {
|
||||||
/**
|
|
||||||
* The students the user can see.
|
|
||||||
* @param _request The HTTP request.
|
|
||||||
* @param _context The context with authenticated state.
|
|
||||||
* @returns All students our user can see.
|
|
||||||
*/
|
|
||||||
// deno-lint-ignore require-await
|
|
||||||
async GET(
|
async GET(
|
||||||
_request: Request,
|
_request: Request,
|
||||||
context: FreshContext<AuthenticatedState>,
|
context: FreshContext<AuthenticatedState>,
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
using connection = connect("students");
|
|
||||||
const database = connection.database;
|
|
||||||
|
|
||||||
if (context.state.session.eduPersonPrimaryAffiliation == "student") {
|
if (context.state.session.eduPersonPrimaryAffiliation == "student") {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify(getItself(database, context.state.session.uid)),
|
JSON.stringify(await getItself(context.state.session.uid)),
|
||||||
{
|
{ headers: { "content-type": "application/json" } },
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify(getAll(database)),
|
JSON.stringify(await getAll()),
|
||||||
{
|
{ headers: { "content-type": "application/json" } },
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* Add students in the database.
|
|
||||||
* @param request The HTTP request.
|
|
||||||
* @param _context The Fresh context.
|
|
||||||
* @returns HTTP 201 on successful insert.
|
|
||||||
*/
|
|
||||||
async POST(
|
async POST(
|
||||||
request: Request,
|
request: Request,
|
||||||
_context: FreshContext<AuthenticatedState>,
|
_context: FreshContext<AuthenticatedState>,
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const { students, promo }: { students: Student[]; promo: string } =
|
const { students: studentList, promo }: {
|
||||||
await request.json();
|
students: Student[];
|
||||||
|
promo: string;
|
||||||
|
} = await request.json();
|
||||||
|
|
||||||
if (!promo || !promo.match(/^\d{4}-\dA$/) || !Array.isArray(students)) {
|
if (!promo || !promo.match(/^\d{4}-\dA$/) || !Array.isArray(studentList)) {
|
||||||
return new Response(null, { status: 400 });
|
return new Response(null, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
using connection = connect("students");
|
|
||||||
const database = connection.database;
|
|
||||||
|
|
||||||
const { endyear, current } = promo.match(
|
const { endyear, current } = promo.match(
|
||||||
/^(?<endyear>\d{4})-(?<current>\d)A$/,
|
/^(?<endyear>\d{4})-(?<current>\d)A$/,
|
||||||
)?.groups!;
|
)?.groups!;
|
||||||
|
|
||||||
database.prepare(
|
await db
|
||||||
"insert or ignore into promotions (endyear, current) values (?, ?)",
|
.insert(promotions)
|
||||||
).run(endyear, current);
|
.values({ endyear: Number(endyear), current: Number(current) })
|
||||||
|
.onConflictDoNothing();
|
||||||
|
|
||||||
const { id: promoId }: { id: string } = database
|
const promo_row = await db
|
||||||
.prepare("select id from promotions where endyear = ? and current = ?")
|
.select()
|
||||||
.get(endyear, current)!;
|
.from(promotions)
|
||||||
|
.where(eq(promotions.endyear, Number(endyear)))
|
||||||
|
.then((rows) => rows.find((r) => r.current === Number(current))!);
|
||||||
|
|
||||||
addStudents(database, students, promoId);
|
await addStudents(studentList, promo_row.id);
|
||||||
|
|
||||||
return new Response(null, { status: 201 });
|
return new Response(null, { status: 201 });
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user