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 { 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 = {
|
||||
// deno-lint-ignore require-await
|
||||
async GET() {
|
||||
try {
|
||||
console.log("Connecting to mobility database...");
|
||||
const connection = new Database("databases/data/mobility.db", {
|
||||
create: false,
|
||||
});
|
||||
connection.run(
|
||||
"ATTACH DATABASE 'databases/data/students.db' AS students",
|
||||
);
|
||||
console.log("Connected to databases.");
|
||||
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 students = connection.prepare(
|
||||
`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 mobilityRows = await db.select().from(mobility);
|
||||
|
||||
const mobilities = connection.prepare(
|
||||
`SELECT
|
||||
mobility.id,
|
||||
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();
|
||||
const promotionRows = await db
|
||||
.select({ id: promotions.id, endyear: promotions.endyear, current: promotions.current })
|
||||
.from(promotions);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({ mobilities, students, promotions }),
|
||||
{
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
JSON.stringify({
|
||||
mobilities: mobilityRows,
|
||||
students: studentRows,
|
||||
promotions: promotionRows,
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error fetching mobility data:", error);
|
||||
@@ -58,8 +39,6 @@ export const handler: Handlers = {
|
||||
},
|
||||
|
||||
async POST(request) {
|
||||
console.log("API /mobility/api/insert_mobility POST called");
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { data } = body;
|
||||
@@ -67,32 +46,8 @@ export const handler: Handlers = {
|
||||
if (!Array.isArray(data)) {
|
||||
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...");
|
||||
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) {
|
||||
for (const entry of data) {
|
||||
const {
|
||||
id,
|
||||
studentId,
|
||||
@@ -102,19 +57,16 @@ export const handler: Handlers = {
|
||||
destinationCountry,
|
||||
destinationName,
|
||||
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
|
||||
.prepare(
|
||||
`SELECT COUNT(*) AS count FROM students.students WHERE userId = ?`,
|
||||
)
|
||||
.get(studentId);
|
||||
|
||||
console.log(`Student ${studentId} exists:`, studentExists.count > 0);
|
||||
|
||||
if (studentExists.count === 0) {
|
||||
if (!studentExists) {
|
||||
console.warn(`Skipping mobility for unknown studentId: ${studentId}`);
|
||||
continue;
|
||||
}
|
||||
@@ -123,43 +75,39 @@ export const handler: Handlers = {
|
||||
if (startDate && endDate) {
|
||||
const start = new Date(startDate);
|
||||
const end = new Date(endDate);
|
||||
if (start <= end) {
|
||||
calculatedWeeksCount = Math.ceil(
|
||||
calculatedWeeksCount = start <= end
|
||||
? Math.ceil(
|
||||
(end.getTime() - start.getTime()) / (7 * 24 * 60 * 60 * 1000),
|
||||
);
|
||||
} else {
|
||||
calculatedWeeksCount = null;
|
||||
}
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
console.log("Executing SQL insert/update query for:", {
|
||||
await db
|
||||
.insert(mobility)
|
||||
.values({
|
||||
id,
|
||||
studentId,
|
||||
startDate,
|
||||
endDate,
|
||||
calculatedWeeksCount,
|
||||
weeksCount: calculatedWeeksCount,
|
||||
destinationCountry,
|
||||
destinationName,
|
||||
mobilityStatus,
|
||||
});
|
||||
|
||||
insertQuery.run(
|
||||
id,
|
||||
studentId,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: mobility.id,
|
||||
set: {
|
||||
startDate,
|
||||
endDate,
|
||||
calculatedWeeksCount,
|
||||
weeksCount: calculatedWeeksCount,
|
||||
destinationCountry,
|
||||
destinationName,
|
||||
mobilityStatus,
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
connection.close();
|
||||
console.log("Mobility data inserted/updated successfully.");
|
||||
return new Response("Data inserted/updated successfully", {
|
||||
status: 200,
|
||||
});
|
||||
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,150 +1,121 @@
|
||||
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 { Database } from "@db/sqlite";
|
||||
import { eq, lt } from "npm:drizzle-orm";
|
||||
|
||||
/**
|
||||
* Gets itself from the database.
|
||||
* @param database The database connection
|
||||
* @param userId The user ID.
|
||||
* @returns Itself from the database.
|
||||
*/
|
||||
function getItself(
|
||||
database: Database,
|
||||
async function getItself(
|
||||
userId: string,
|
||||
): { student: Student | null; promo: Promotion | null } {
|
||||
const studentQuery = "select * from students where userId = ?";
|
||||
const student: Student | undefined = database.prepare(studentQuery).get(
|
||||
userId,
|
||||
);
|
||||
): 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 promoQuery = "select * from promotions where id = ?";
|
||||
const promo: Promotion | undefined = database.prepare(promoQuery).get(
|
||||
student.promotionId,
|
||||
);
|
||||
const promo = await db
|
||||
.select()
|
||||
.from(promotions)
|
||||
.where(eq(promotions.id, student.promotionId!))
|
||||
.limit(1)
|
||||
.then((rows) => rows[0] ?? null);
|
||||
|
||||
return { student, promo: promo ?? null };
|
||||
return { student, promo };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets itself from the database.
|
||||
* @param database The database connexion
|
||||
* @param userId The user ID.
|
||||
* @returns Itself from the database.
|
||||
*/
|
||||
function getAll(
|
||||
database: Database,
|
||||
): { students: Student[]; promos: Promotion[] } {
|
||||
const studentsQuery = `
|
||||
select userId, firstName, lastName, mail, promotionId
|
||||
from students inner join promotions
|
||||
on students.promotionId = promotions.id
|
||||
where promotions.current < 6`;
|
||||
const students: Student[] = database.prepare(studentsQuery).all();
|
||||
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 promosQuery = "select * from promotions where promotions.current < 6";
|
||||
const promos: Promotion[] | undefined = database.prepare(promosQuery).all();
|
||||
const promos = await db
|
||||
.select()
|
||||
.from(promotions)
|
||||
.where(lt(promotions.current, 6));
|
||||
|
||||
return { students, promos };
|
||||
return { students: rows as Student[], promos };
|
||||
}
|
||||
|
||||
/**
|
||||
* Add users to the database.
|
||||
* @param database The database connexion
|
||||
* @param students The students to add
|
||||
* @param promoId The promotion id.
|
||||
*/
|
||||
function addStudents(database: Database, students: Student[], promoId: string) {
|
||||
const query = `
|
||||
INSERT INTO students
|
||||
(userId, firstName, lastName, mail, promotionId)
|
||||
VALUES (?, ?, ?, ?, ?)`;
|
||||
|
||||
const statement = database.prepare(query);
|
||||
|
||||
for (const student of students) {
|
||||
statement.run(
|
||||
student.userId,
|
||||
student.firstName,
|
||||
student.lastName,
|
||||
student.mail,
|
||||
promoId,
|
||||
);
|
||||
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> = {
|
||||
/**
|
||||
* 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(
|
||||
_request: Request,
|
||||
context: FreshContext<AuthenticatedState>,
|
||||
): Promise<Response> {
|
||||
using connection = connect("students");
|
||||
const database = connection.database;
|
||||
|
||||
if (context.state.session.eduPersonPrimaryAffiliation == "student") {
|
||||
return new Response(
|
||||
JSON.stringify(getItself(database, context.state.session.uid)),
|
||||
{
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
},
|
||||
JSON.stringify(await getItself(context.state.session.uid)),
|
||||
{ headers: { "content-type": "application/json" } },
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(
|
||||
JSON.stringify(getAll(database)),
|
||||
{
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
},
|
||||
JSON.stringify(await getAll()),
|
||||
{ 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(
|
||||
request: Request,
|
||||
_context: FreshContext<AuthenticatedState>,
|
||||
): Promise<Response> {
|
||||
const { students, promo }: { students: Student[]; promo: string } =
|
||||
await request.json();
|
||||
const { students: studentList, promo }: {
|
||||
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 });
|
||||
}
|
||||
|
||||
using connection = connect("students");
|
||||
const database = connection.database;
|
||||
|
||||
const { endyear, current } = promo.match(
|
||||
/^(?<endyear>\d{4})-(?<current>\d)A$/,
|
||||
)?.groups!;
|
||||
|
||||
database.prepare(
|
||||
"insert or ignore into promotions (endyear, current) values (?, ?)",
|
||||
).run(endyear, current);
|
||||
await db
|
||||
.insert(promotions)
|
||||
.values({ endyear: Number(endyear), current: Number(current) })
|
||||
.onConflictDoNothing();
|
||||
|
||||
const { id: promoId }: { id: string } = database
|
||||
.prepare("select id from promotions where endyear = ? and current = ?")
|
||||
.get(endyear, current)!;
|
||||
const promo_row = await db
|
||||
.select()
|
||||
.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 });
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user