Skip to content

Commit e99daa7

Browse files
authored
Merge pull request #128 from ItKlubBozoLagan/dev
Prod sync
2 parents b010931 + 0d7e77e commit e99daa7

39 files changed

Lines changed: 1581 additions & 333 deletions

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
# Kontestis
44

55
The code contest and evaluation platform developed for the [2023 InfoKup Competition](https://informatika.azoo.hr/kategorija/3/Razvoj-softvera)
6+
7+
## Evaluator
8+
[ItKlubBozoLagan/evaluator-v2/](https://github.com/ItKlubBozoLagan/evaluator-v2/)

apps/backend/src/database/Database.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
Cluster,
33
ContestAnnouncement,
4+
ContestChatMessage,
45
ContestMember,
56
ContestQuestion,
67
EduUser,
@@ -13,6 +14,7 @@ import {
1314
OrganisationMember,
1415
Problem,
1516
SiteNotification,
17+
TemporaryUser,
1618
} from "@kontestis/models";
1719
import { ClusterSubmission } from "@kontestis/models";
1820
import { Contest } from "@kontestis/models";
@@ -76,6 +78,8 @@ import { migration_contest_show_leaderboard } from "./migrations/0049_contest_sh
7678
import { migration_improve_generators } from "./migrations/0050_improve_generators";
7779
import { migration_add_sample_clusters } from "./migrations/0051_add_sample_clusters";
7880
import { migration_generator_id_index } from "./migrations/0052_generator_id_index";
81+
import { migration_contest_chat_messages } from "./migrations/0053_contest_chat_messages";
82+
import { migration_add_temporary_users } from "./migrations/0054_add_temporary_users";
7983

8084
export const Database = new ScylloClient<{
8185
users: User;
@@ -88,6 +92,7 @@ export const Database = new ScylloClient<{
8892
testcase_submissions: TestcaseSubmission;
8993
contest_members: ContestMember;
9094
contest_questions: ContestQuestion;
95+
contest_chat_messages: ContestChatMessage;
9196
contest_announcements: ContestAnnouncement;
9297
organisations: Organisation;
9398
organisation_members: OrganisationMember;
@@ -97,6 +102,7 @@ export const Database = new ScylloClient<{
97102
mail_preferences: MailPreference;
98103
edu_users: EduUser;
99104
managed_users: ManagedUser;
105+
temporary_users: TemporaryUser;
100106
generators: Generator;
101107
}>({
102108
client: {
@@ -163,6 +169,8 @@ const migrations: Migration<any>[] = [
163169
migration_improve_generators,
164170
migration_add_sample_clusters,
165171
migration_generator_id_index,
172+
migration_contest_chat_messages,
173+
migration_add_temporary_users,
166174
];
167175

168176
export const initDatabase = async () => {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { ContestChatMessageV1, ContestQuestionV2 } from "@kontestis/models";
2+
import { Migration } from "scyllo";
3+
4+
import { generateSnowflake, getSnowflakeTime } from "../../lib/snowflake";
5+
6+
type MigrationType = {
7+
contest_chat_messages: ContestChatMessageV1;
8+
contest_questions: ContestQuestionV2;
9+
};
10+
11+
export const migration_contest_chat_messages: Migration<MigrationType> = async (database, log) => {
12+
// Create chat messages table
13+
await database.createTable(
14+
"contest_chat_messages",
15+
true,
16+
{
17+
id: { type: "bigint" },
18+
thread_id: { type: "bigint" },
19+
contest_id: { type: "bigint" },
20+
author_member_id: { type: "bigint" },
21+
content: { type: "text" },
22+
created_at: { type: "timestamp" },
23+
},
24+
"id"
25+
);
26+
27+
await database.createIndex(
28+
"contest_chat_messages",
29+
"contest_chat_messages_by_thread_id",
30+
"thread_id"
31+
);
32+
33+
await database.createIndex(
34+
"contest_chat_messages",
35+
"contest_chat_messages_by_contest_id",
36+
"contest_id"
37+
);
38+
39+
// Add new columns to contest_questions
40+
await database.raw("ALTER TABLE contest_questions ADD last_message_at timestamp");
41+
await database.raw("ALTER TABLE contest_questions ADD last_message_member_id bigint");
42+
43+
// Migrate existing questions into chat messages
44+
const questions = await database.selectFrom("contest_questions", "*", {});
45+
46+
for (const question of questions) {
47+
const questionTime = getSnowflakeTime(question.id);
48+
49+
// Insert the question text as the first message
50+
await database.insertInto("contest_chat_messages", {
51+
id: question.id,
52+
thread_id: question.id,
53+
contest_id: question.contest_id,
54+
author_member_id: question.contest_member_id,
55+
content: question.question,
56+
created_at: questionTime,
57+
});
58+
59+
let lastMessageAt = questionTime;
60+
let lastMessageMemberId = question.contest_member_id;
61+
62+
// Insert the response as a second message if it exists
63+
if (question.response && question.response_author_id) {
64+
const responseTime = new Date(questionTime.getTime() + 1000);
65+
66+
await database.insertInto("contest_chat_messages", {
67+
id: generateSnowflake(),
68+
thread_id: question.id,
69+
contest_id: question.contest_id,
70+
author_member_id: question.response_author_id,
71+
content: question.response,
72+
created_at: responseTime,
73+
});
74+
75+
lastMessageAt = responseTime;
76+
lastMessageMemberId = question.response_author_id;
77+
}
78+
79+
await database.update(
80+
"contest_questions",
81+
{ last_message_at: lastMessageAt, last_message_member_id: lastMessageMemberId },
82+
{ id: question.id }
83+
);
84+
}
85+
86+
log(`Migrated ${questions.length} existing questions to chat messages`);
87+
log("Done");
88+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { TemporaryUserV1 } from "@kontestis/models";
2+
import { Migration } from "scyllo";
3+
4+
type MigrationType = {
5+
temporary_users: TemporaryUserV1;
6+
};
7+
8+
export const migration_add_temporary_users: Migration<MigrationType> = async (database, log) => {
9+
await database.createTable(
10+
"temporary_users",
11+
true,
12+
{
13+
id: {
14+
type: "bigint",
15+
},
16+
username: {
17+
type: "text",
18+
},
19+
password: {
20+
type: "text",
21+
},
22+
organisation_id: {
23+
type: "bigint",
24+
},
25+
created_at: { type: "timestamp" },
26+
},
27+
"id"
28+
);
29+
30+
await database.createIndex("temporary_users", "temporary_users_by_username", "username");
31+
32+
log("Done");
33+
};

apps/backend/src/extractors/extractUser.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,24 @@ export const extractUser = async (req: Request): Promise<FullUser> => {
2020

2121
const { user: tokenData, authSource } = jwtData;
2222

23+
if (authSource === "temporary") {
24+
const temporaryUser = await Database.selectOneFrom("temporary_users", "*", {
25+
id: tokenData.id,
26+
});
27+
28+
if (!temporaryUser) {
29+
throw new SafeError(StatusCodes.UNAUTHORIZED);
30+
}
31+
32+
return {
33+
...tokenData,
34+
is_edu: false as const,
35+
auth_source: authSource,
36+
is_temporary: true,
37+
temporary_data: { organisation_id: temporaryUser.organisation_id },
38+
};
39+
}
40+
2341
const eduUser = await Database.selectOneFrom("edu_users", "*", {
2442
id: tokenData.id,
2543
});

apps/backend/src/lib/auth.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const AuthSourceType = Type.Union([
2424
Type.Literal("google"),
2525
Type.Literal("aai-edu"),
2626
Type.Literal("managed"),
27+
Type.Literal("temporary"),
2728
]);
2829

2930
export const generateJwt = <

apps/backend/src/routes/auth/AuthHandler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ import { extractIdFromParameters } from "../../utils/extractorUtils";
1313
import { respond } from "../../utils/response";
1414
import { AaiEduHandler } from "./AaiEduHandler";
1515
import ManagedHandler from "./ManagedHandler";
16+
import TemporaryHandler from "./TemporaryHandler";
1617

1718
const AuthHandler = Router();
1819

1920
AuthHandler.use("/aai-edu", AaiEduHandler);
2021
AuthHandler.use("/managed", ManagedHandler);
22+
AuthHandler.use("/temporary", TemporaryHandler);
2123

2224
const OAuthSchema = Type.Object({
2325
credential: Type.String(),

0 commit comments

Comments
 (0)