Skip to content

Commit eb5bf52

Browse files
nakomochinaka-12
andauthored
Backend/role count (#34)
* tmp * 配属人数指定のUI * 複数役職のDB処理 * 複数役職のminの締め切り制限・希望役職数表示 * 超過時に警告(暫定的) * check * fix --------- Co-authored-by: naka-12 <[email protected]>
1 parent 4757303 commit eb5bf52

File tree

11 files changed

+557
-61
lines changed

11 files changed

+557
-61
lines changed

biome.jsonc

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"noDefaultExport": "off",
3838
"noNamespaceImport": "off",
3939
"useBlockStatements": "off",
40+
"noParameterProperties": "off",
4041
},
4142
"complexity": {
4243
"noExcessiveCognitiveComplexity": "off",

service/db/schema.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const participants = sqliteTable("participants", {
88
.references(() => projects.id, { onDelete: "cascade" })
99
.notNull(),
1010
is_admin: integer("is_admin").notNull(),
11+
roles_count: integer("roles_count"),
1112
});
1213

1314
export const accounts = sqliteTable("accounts", {
@@ -21,6 +22,7 @@ export const projects = sqliteTable("projects", {
2122
name: text().notNull(),
2223
description: text(),
2324
closed_at: text(),
25+
multiple_roles: integer().notNull(),
2426
});
2527

2628
export const roles = sqliteTable("roles", {

service/routes/projects/index.ts

+131-44
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { json, param } from "service/validator/hono.ts";
1515
import { assignRoles } from "share/logic/min-flow.ts";
1616
import { ProjectSchema } from "share/schema.ts";
1717
import * as v from "valibot";
18+
import { multipleMatch } from "share/logic/multiple.ts";
1819

1920
import { at } from "share/lib.ts";
2021
import preferenceRoutes from "./preferences.ts";
@@ -68,6 +69,7 @@ const route = new Hono<HonoOptions>()
6869
.select({
6970
id: participants.id,
7071
name: participants.name,
72+
roles_count: participants.roles_count,
7173
})
7274
.from(participants)
7375
.where(
@@ -115,6 +117,7 @@ const route = new Hono<HonoOptions>()
115117
id: project_id,
116118
name: body.name,
117119
description: body.description,
120+
multiple_roles: body.multipleRoles,
118121
},
119122
])
120123
.returning()
@@ -194,59 +197,143 @@ const route = new Hono<HonoOptions>()
194197
return c.json({ message: "Unauthorized" }, 401);
195198
}
196199

197-
await db(c)
198-
.update(projects)
199-
.set({
200-
closed_at: new Date().toISOString(),
201-
})
202-
.where(eq(projects.id, projectId));
200+
const projectData = (
201+
await db(c).select().from(projects).where(eq(projects.id, projectId))
202+
)[0];
203203

204-
const participantsData = await db(c)
205-
.select()
206-
.from(ratings)
207-
.where(eq(ratings.project_id, projectId))
208-
.orderBy(ratings.participant_id, ratings.role_id);
204+
if (!projectData) {
205+
throw new HTTPException(404, { message: "Project not found" });
206+
}
209207

210-
const ratingsByParticipant = Map.groupBy(
211-
participantsData,
212-
(item) => item.participant_id,
213-
);
208+
if (projectData.closed_at) {
209+
throw new HTTPException(409, { message: "Project already finalized" });
210+
}
214211

215-
const ratingsArray: number[][] = []; // TODO: 型付けをマシにする
216-
const participantIndexIdMap: string[] = [];
212+
if (projectData.multiple_roles) {
213+
// multiple mode
214+
const participantsWithPreferences = await db(c)
215+
.select({
216+
id: participants.id,
217+
rolesCount: participants.roles_count,
218+
score: ratings.score,
219+
roleId: ratings.role_id,
220+
})
221+
.from(participants)
222+
.innerJoin(
223+
ratings,
224+
and(
225+
eq(participants.id, ratings.participant_id),
226+
eq(participants.project_id, ratings.project_id),
227+
),
228+
)
229+
.where(eq(participants.project_id, projectId));
217230

218-
ratingsByParticipant.forEach((r) => {
219-
ratingsArray.push(r.map((item) => item.score));
220-
participantIndexIdMap.push(r[0]?.participant_id ?? "-");
221-
});
231+
const preferencesByParticipant = Map.groupBy(
232+
participantsWithPreferences,
233+
(p) => p.id,
234+
);
222235

223-
const roleConstraints = await db(c)
224-
.select()
225-
.from(roles)
226-
.where(eq(roles.project_id, projectId))
227-
.orderBy(roles.id);
228-
const minMaxConstraints = roleConstraints.map((role) => ({
229-
min: role.min,
230-
max: role.max,
231-
}));
236+
const participantInput = Array.from(
237+
preferencesByParticipant.entries(),
238+
).map(([participantId, preferences]) => ({
239+
id: participantId,
240+
rolesCount: preferences[0]?.rolesCount ?? 0,
241+
preferences: preferences.map((p) => ({
242+
roleId: p.roleId,
243+
score: p.score,
244+
})),
245+
}));
232246

233-
const result = assignRoles(
234-
ratingsArray,
235-
at(ratingsArray, 0).length,
236-
minMaxConstraints,
237-
);
247+
const roleConstraints = await db(c)
248+
.select()
249+
.from(roles)
250+
.where(eq(roles.project_id, projectId))
251+
.orderBy(roles.id);
238252

239-
await db(c)
240-
.insert(matches)
241-
.values(
242-
result.map((r) => ({
243-
id: crypto.randomUUID(),
244-
project_id: projectId,
245-
role_id: roleConstraints[r.role]?.id ?? "-",
246-
participant_id: participantIndexIdMap[r.participant] ?? "-",
247-
})),
253+
const roleInput = roleConstraints.map((role) => ({
254+
id: role.id,
255+
capacity: role.max, // multiple mode の場合、max と min は同一
256+
}));
257+
258+
console.log(participantInput);
259+
console.log(roleInput);
260+
261+
const matching = multipleMatch(participantInput, roleInput);
262+
console.log(matching);
263+
264+
const result: {
265+
id: string;
266+
project_id: string;
267+
role_id: string;
268+
participant_id: string;
269+
}[] = [];
270+
matching.forEach((m) => {
271+
m.roleIds.forEach((roleId) => {
272+
result.push({
273+
id: crypto.randomUUID(),
274+
project_id: projectId,
275+
role_id: roleId,
276+
participant_id: m.participantId,
277+
});
278+
});
279+
});
280+
281+
await db(c).insert(matches).values(result);
282+
} else {
283+
// default mode
284+
const participantsData = await db(c)
285+
.select()
286+
.from(ratings)
287+
.where(eq(ratings.project_id, projectId))
288+
.orderBy(ratings.participant_id, ratings.role_id);
289+
290+
const ratingsByParticipant = Map.groupBy(
291+
participantsData,
292+
(item) => item.participant_id,
248293
);
249294

295+
const ratingsArray: number[][] = []; // TODO: 型付けをマシにする
296+
const participantIndexIdMap: string[] = [];
297+
298+
ratingsByParticipant.forEach((r) => {
299+
ratingsArray.push(r.map((item) => item.score));
300+
participantIndexIdMap.push(r[0]?.participant_id ?? "-");
301+
});
302+
303+
const roleConstraints = await db(c)
304+
.select()
305+
.from(roles)
306+
.where(eq(roles.project_id, projectId))
307+
.orderBy(roles.id);
308+
const minMaxConstraints = roleConstraints.map((role) => ({
309+
min: role.min,
310+
max: role.max,
311+
}));
312+
313+
const result = assignRoles(
314+
ratingsArray,
315+
at(ratingsArray, 0).length,
316+
minMaxConstraints,
317+
);
318+
319+
await db(c)
320+
.insert(matches)
321+
.values(
322+
result.map((r) => ({
323+
id: crypto.randomUUID(),
324+
project_id: projectId,
325+
role_id: roleConstraints[r.role]?.id ?? "-",
326+
participant_id: participantIndexIdMap[r.participant] ?? "-",
327+
})),
328+
);
329+
}
330+
await db(c)
331+
.update(projects)
332+
.set({
333+
closed_at: new Date().toISOString(),
334+
})
335+
.where(eq(projects.id, projectId));
336+
250337
return c.json({}, 200);
251338
})
252339
.get("/:projectId/result", param({ projectId: v.string() }), async (c) => {

service/routes/projects/participants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const route = new Hono<HonoOptions>().get(
1717
id: participants.id,
1818
name: participants.name,
1919
is_admin: participants.is_admin,
20+
roles_count: participants.roles_count,
2021
})
2122
.from(participants)
2223
.where(

service/routes/projects/preferences.ts

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const route = new Hono<HonoOptions>()
4545
browser_id: browser_id,
4646
project_id: projectId,
4747
is_admin: 0,
48+
roles_count: body.rolesCount,
4849
})
4950
.returning()
5051
)[0];
@@ -83,6 +84,7 @@ const route = new Hono<HonoOptions>()
8384
.update(participants)
8485
.set({
8586
name: body.participantName,
87+
roles_count: body.rolesCount || null,
8688
})
8789
.where(and(eq(participants.browser_id, browser_id)))
8890
.returning({ id: participants.id })

0 commit comments

Comments
 (0)