From 20c8987b4d94651faf1e6c78d7f7fc839fd8933e Mon Sep 17 00:00:00 2001 From: MarcoSelvatici Date: Sun, 29 Aug 2021 12:36:33 +0200 Subject: [PATCH 1/8] [Contest service] Fix bug in auth user. The issue was that mapping.rs used different names for fullname (name, long_name, fullName), so the auth_user query could not retrieve the right field from the db, thus panicking. --- contest_service/db/init.js | 4 ++-- contest_service/src/mappings.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contest_service/db/init.js b/contest_service/db/init.js index 2ed11fb..4b52381 100644 --- a/contest_service/db/init.js +++ b/contest_service/db/init.js @@ -45,9 +45,9 @@ db.createCollection("users", { $jsonSchema: { bsonType: "object", _id: { bsonType: "string" }, // username - required: ["fullName", "password"], + required: ["fullname", "password"], properties: { - fullName: { bsonType: "string" }, + fullname: { bsonType: "string" }, password: { bsonType: "string" }, } } diff --git a/contest_service/src/mappings.rs b/contest_service/src/mappings.rs index 5a8cf2e..c26fdf4 100644 --- a/contest_service/src/mappings.rs +++ b/contest_service/src/mappings.rs @@ -287,7 +287,7 @@ pub mod user { #[derive(Clone)] pub struct User { username: String, - name: String, + fullname: String, password: Password, } @@ -325,7 +325,7 @@ pub mod user { fn from(record: Document) -> Self { Self { username: record.get_str("_id").unwrap().to_owned(), - name: record.get_str("fullName").unwrap().to_owned(), + fullname: record.get_str("fullname").unwrap().to_owned(), // Here I assume that the password stored in the DB are hashed, since I hash them before insertion password: Password::Hashed(record.get_str("password").unwrap().to_owned()), } @@ -335,7 +335,7 @@ pub mod user { fn from(pb: protos::service::contest::SetUserRequest) -> Self { Self { username: pb.username, - name: pb.fullname, + fullname: pb.fullname, password: Password::Clear(pb.password), } } @@ -346,7 +346,7 @@ pub mod user { u.hash_password().expect("Could not hash password"); let mut result = Document::new(); result.insert("_id", u.username); - result.insert("longName", u.name); + result.insert("fullname", u.fullname); result.insert( "password", { @@ -364,7 +364,7 @@ pub mod user { fn from(u: User) -> Self { Self::Success(protos::service::contest::auth_user_response::Success { username: u.username, - fullname: u.name, + fullname: u.fullname, }) } } From 0e761e4364648f09793db6dd9383179f314a7fe1 Mon Sep 17 00:00:00 2001 From: MarcoSelvatici Date: Sun, 29 Aug 2021 16:04:53 +0200 Subject: [PATCH 2/8] [Contest service] Properly setup validator. --- contest_service/db/init.js | 89 ------------------------- contest_service/src/main.rs | 129 +++++++++++++++++++++++++++++++++++- docker-compose.yaml | 11 ++- 3 files changed, 131 insertions(+), 98 deletions(-) delete mode 100644 contest_service/db/init.js diff --git a/contest_service/db/init.js b/contest_service/db/init.js deleted file mode 100644 index 4b52381..0000000 --- a/contest_service/db/init.js +++ /dev/null @@ -1,89 +0,0 @@ -db.createCollection("contest_metadata", { - max: 1, // caps the number of documents in this collection to 1 - validator: { - $jsonSchema: { - bsonType: "object", - required: ["name", "description"], - properties: { - name: { bsonType: "string" }, - description: { bsonType: "string" }, - startTime: { bsonType: "timestamp" }, // missing means there is no start time - endTime: { bsonType: "timestamp" }, // missing means there is no end time - } - } - } -}) - -db.createCollection("problems", { - validator: { - $jsonSchema: { - bsonType: "object", - _id: { bsonType: "int" }, // problem id - required: ["name", "longName", "statement"], - properties: { - name: { bsonType: "string" }, - longName: { bsonType: "string" }, - statement: { bsonType: "binData" } - // testcasesPerSubtask: { - // bsonType: "array", - // items: { - // bsonType: "int" - // } - // }, - // timeLimitNs: { bsonType: "int" }, add when needed - // memoryLimitB: { bsonType: "int" }, add when needed - // sourceSizeLimitB: { bsonType: "int" }, add when needed - // score: { bsonType: "..." } add when needed - // problemType: { bsonType: "string" } add when needed - } - } - } -}) - -db.createCollection("users", { - validator: { - $jsonSchema: { - bsonType: "object", - _id: { bsonType: "string" }, // username - required: ["fullname", "password"], - properties: { - fullname: { bsonType: "string" }, - password: { bsonType: "string" }, - } - } - } -}) - -db.createCollection("announcements", { - validator: { - $jsonSchema: { - bsonType: "object", - _id: { bsonType: "int" }, // announcement id - required: ["subject", "text", "created"], - properties: { - subject: { bsonType: "string" }, - problemId: { bsonType: "int" }, - text: { bsonType: "string" }, - to: { bsonType: "string" }, - created: { bsonType: "timestamp" } - } - } - } -}) - -db.createCollection("questions", { - validator: { - $jsonSchema: { - bsonType: "object", - _id: { bsonType: "int" }, // question id - required: ["subject", "text", "created"], - properties: { - subject: { bsonType: "string" }, - problemId: { bsonType: "int" }, - text: { bsonType: "string" }, - from: { bsonType: "string" }, - created: { bsonType: "timestamp" } - } - } - } -}) diff --git a/contest_service/src/main.rs b/contest_service/src/main.rs index 58ef017..c855843 100644 --- a/contest_service/src/main.rs +++ b/contest_service/src/main.rs @@ -4,8 +4,10 @@ use std::convert::TryFrom; use mappings::chat::Message; use mongodb::{ bson::{doc, Document}, - options::{ClientOptions, UpdateOptions}, - Client, + options::{ + ClientOptions, CreateCollectionOptions, UpdateOptions, ValidationAction, ValidationLevel, + }, + Client, Database, }; use protos::service::contest::{contest_server::*, *}; use protos::utils::*; @@ -26,6 +28,125 @@ where Status::internal(format!("{:?}", e)) } +async fn init_contest_service_db(db: Database) -> Result<(), Box> { + // TODO: consider using this validator syntax (might be slightly nicer): + // https://docs.mongodb.com/v5.0/core/schema-validation/#other-query-expressions + db.create_collection( + "contest_metadata", + CreateCollectionOptions::builder() + .validator(doc! { + "$jsonSchema": { + "bsonType": "object", + "required": ["name", "description"], + "properties": { + "name": { "bsonType": "string", "description": "description for name"}, + "description": { "bsonType": "string", "description": "description for description" }, + "startTime": { "bsonType": "timestamp", "description": "description for sT" }, // missing means there is no start time + "endTime": { "bsonType": "timestamp", "description": "description for eT" } // missing means there is no end time + } + } + }) + .validation_action(ValidationAction::Error) + .validation_level(ValidationLevel::Strict) + .capped(true) + .max(1) + // Size must be set if we want to used capped. SWe set it to an unreachable value (1 MB) so + // it will never be reached by our singleton document (enforced by max(1)). + .size(2_u64.pow(20)) + .build(), + ) + .await?; + + db.create_collection( + "problems", + CreateCollectionOptions::builder() + .validator(doc! { + "$jsonSchema": { + "bsonType": "object", + "required": ["_id", "name", "longName", "statement"], + "properties": { + "_id": { "bsonType": "int" }, // problem id + "name": { "bsonType": "string" }, + "longName": { "bsonType": "string" }, + "statement": { "bsonType": "binData" } + } + } + }) + .validation_action(ValidationAction::Error) + .validation_level(ValidationLevel::Strict) + .build(), + ) + .await?; + + db.create_collection( + "users", + CreateCollectionOptions::builder() + .validator(doc! { + "$jsonSchema": { + "bsonType": "object", + "required": ["_id", "fullname", "password"], + "properties": { + "_id": { "bsonType": "string" }, // username + "fullname": { "bsonType": "string" }, + "password": { "bsonType": "string" } + } + } + }) + .validation_action(ValidationAction::Error) + .validation_level(ValidationLevel::Strict) + .build(), + ) + .await?; + + db.create_collection( + "announcements", + CreateCollectionOptions::builder() + .validator(doc! { + "$jsonSchema": { + "bsonType": "object", + "required": ["_id", "subject", "text", "created"], + "properties": { + "_id": { "bsonType": "int" }, // announcement id + "subject": { "bsonType": "string" }, + "problemId": { "bsonType": "int" }, + "text": { "bsonType": "string" }, + "to": { "bsonType": "string" }, + "created": { "bsonType": "timestamp" } + } + } + }) + .validation_action(ValidationAction::Error) + .validation_level(ValidationLevel::Strict) + .build(), + ) + .await?; + + db.create_collection( + "questions", + CreateCollectionOptions::builder() + .validator(doc! { + "$jsonSchema": { + "bsonType": "object", + "required": ["_id", "subject", "text", "created"], + "properties": { + "_id": { "bsonType": "int" }, // question id + "subject": { "bsonType": "string" }, + "problemId": { "bsonType": "int" }, + "text": { "bsonType": "string" }, + "from": { "bsonType": "string" }, + "created": { "bsonType": "timestamp" } + } + } + }) + .validation_action(ValidationAction::Error) + .validation_level(ValidationLevel::Strict) + .build(), + ) + .await?; + + Ok(()) +} + #[derive(Debug)] pub struct ContestService { db_client: Client, @@ -33,8 +154,10 @@ pub struct ContestService { impl ContestService { async fn new() -> Result> { + let db_client = Client::with_options(ClientOptions::parse(CONNECTION_STRING).await?)?; + init_contest_service_db(db_client.database("contestdb")).await?; Ok(Self { - db_client: Client::with_options(ClientOptions::parse(CONNECTION_STRING).await?)?, + db_client: db_client, }) } diff --git a/docker-compose.yaml b/docker-compose.yaml index 868bed4..0f8a276 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -2,22 +2,21 @@ version: "3.3" services: contest_service_db: - image: mongo:4.4.8 + image: mongo:5.0.0 restart: always environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example MONGO_INITDB_DATABASE: contestdb - volumes: - - ./contest_service/db/init.js:/docker-entrypoint-initdb.d/init.js - # - contest_service_db_volume:/data/db - ports: + # volumes: + # - contest_service_db_volume:/data/db + ports: - 27017:27017 contest_service: build: context: . dockerfile: ./contest_service/Dockerfile - ports: + ports: - 50051:50051 depends_on: - contest_service_db From 6b46978b62b3b96c1e3685480f71e1088a6d2698 Mon Sep 17 00:00:00 2001 From: MarcoSelvatici Date: Sun, 29 Aug 2021 16:10:54 +0200 Subject: [PATCH 3/8] [Contest service] remove debugging errors. --- contest_service/src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contest_service/src/main.rs b/contest_service/src/main.rs index c855843..047d997 100644 --- a/contest_service/src/main.rs +++ b/contest_service/src/main.rs @@ -39,10 +39,10 @@ async fn init_contest_service_db(db: Database) -> Result<(), Box Date: Fri, 10 Sep 2021 20:13:47 +0200 Subject: [PATCH 4/8] Set required attributes --- protos/protos/service/submission.proto | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/protos/protos/service/submission.proto b/protos/protos/service/submission.proto index fbea26b..e76c3d6 100644 --- a/protos/protos/service/submission.proto +++ b/protos/protos/service/submission.proto @@ -7,12 +7,12 @@ import "evaluation.proto"; // Submission service message EvaluateSubmissionRequest { - evaluation.Submission sub = 1; + required evaluation.Submission sub = 1; } message EvaluateSubmissionResponse { - evaluation.EvaluationResult res = 1; - common.Id submission_id = 2; - google.protobuf.Timestamp timestamp = 3; + required evaluation.EvaluationResult res = 1; + required common.Id submission_id = 2; + required google.protobuf.Timestamp timestamp = 3; } message GetSubmissionListRequest { optional uint32 limit = 1; @@ -21,20 +21,20 @@ message GetSubmissionListRequest { } message GetSubmissionListResponse { message Item { - common.Id sub_id = 1; - string user = 2; - uint32 problem_id = 3; - google.protobuf.Timestamp timestamp = 5; - optional double score = 6; + required common.Id sub_id = 1; + required string user = 2; + required uint32 problem_id = 3; + required google.protobuf.Timestamp timestamp = 5; + required optional double score = 6; } repeated Item list = 1; } message GetSubmissionDetailsRequest { - common.Id submission_id = 1; + required common.Id submission_id = 1; } message GetSubmissionDetailsResponse { - evaluation.Submission sub = 1; - google.protobuf.Timestamp timestamp = 2; + required evaluation.Submission sub = 1; + required google.protobuf.Timestamp timestamp = 2; } service Submission { rpc evaluate_submission(EvaluateSubmissionRequest) returns (EvaluateSubmissionResponse); From 02ae11a930ac0a26c3ec0e1ac8cf879f818ea5bb Mon Sep 17 00:00:00 2001 From: Eugenio Tampieri Date: Fri, 10 Sep 2021 20:19:36 +0200 Subject: [PATCH 5/8] Revert "Set required attributes" This reverts commit 5f5089e29c278cb077fb2ea6ba1a1669b5883f5d. --- protos/protos/service/submission.proto | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/protos/protos/service/submission.proto b/protos/protos/service/submission.proto index e76c3d6..fbea26b 100644 --- a/protos/protos/service/submission.proto +++ b/protos/protos/service/submission.proto @@ -7,12 +7,12 @@ import "evaluation.proto"; // Submission service message EvaluateSubmissionRequest { - required evaluation.Submission sub = 1; + evaluation.Submission sub = 1; } message EvaluateSubmissionResponse { - required evaluation.EvaluationResult res = 1; - required common.Id submission_id = 2; - required google.protobuf.Timestamp timestamp = 3; + evaluation.EvaluationResult res = 1; + common.Id submission_id = 2; + google.protobuf.Timestamp timestamp = 3; } message GetSubmissionListRequest { optional uint32 limit = 1; @@ -21,20 +21,20 @@ message GetSubmissionListRequest { } message GetSubmissionListResponse { message Item { - required common.Id sub_id = 1; - required string user = 2; - required uint32 problem_id = 3; - required google.protobuf.Timestamp timestamp = 5; - required optional double score = 6; + common.Id sub_id = 1; + string user = 2; + uint32 problem_id = 3; + google.protobuf.Timestamp timestamp = 5; + optional double score = 6; } repeated Item list = 1; } message GetSubmissionDetailsRequest { - required common.Id submission_id = 1; + common.Id submission_id = 1; } message GetSubmissionDetailsResponse { - required evaluation.Submission sub = 1; - required google.protobuf.Timestamp timestamp = 2; + evaluation.Submission sub = 1; + google.protobuf.Timestamp timestamp = 2; } service Submission { rpc evaluate_submission(EvaluateSubmissionRequest) returns (EvaluateSubmissionResponse); From 608bd497fed4dab52778fcecba927a185cc73ee4 Mon Sep 17 00:00:00 2001 From: Eugenio Tampieri Date: Fri, 10 Sep 2021 20:19:46 +0200 Subject: [PATCH 6/8] Fixed protos --- protos/protos/service/contest.proto | 40 ++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/protos/protos/service/contest.proto b/protos/protos/service/contest.proto index 5e191e9..28e1f01 100644 --- a/protos/protos/service/contest.proto +++ b/protos/protos/service/contest.proto @@ -5,8 +5,8 @@ package service.contest; import "google/protobuf/timestamp.proto"; message AuthUserRequest { - string username = 1; - string password = 2; + required string username = 1; + required string password = 2; } message AuthUserResponse { message Success { @@ -23,37 +23,37 @@ message AuthUserResponse { } message ContestMetadata { - string name = 1; - string description = 2; + required string name = 1; + required string description = 2; optional google.protobuf.Timestamp start_time = 3; optional google.protobuf.Timestamp end_time = 4; } message GetContestMetadataRequest {} message GetContestMetadataResponse { - ContestMetadata metadata = 1; + required ContestMetadata metadata = 1; } message Problem { - uint32 id = 1; - string name = 2; - string long_name = 3; + required uint32 id = 1; + required string name = 2; + required string long_name = 3; } message GetProblemRequest { - uint32 problem_id = 1; + required uint32 problem_id = 1; } message GetProblemResponse { - Problem info = 1; - bytes statement = 2; + required Problem info = 1; + required bytes statement = 2; } message Message { // questions and announcements are the same - uint32 id = 1; - string subject = 2; + required uint32 id = 1; + required string subject = 2; optional uint32 problem_id = 3; - string text = 4; + required string text = 4; optional string from = 5; optional string to = 6; - google.protobuf.Timestamp sent_at = 7; + required google.protobuf.Timestamp sent_at = 7; } message GetAnnouncementListRequest { optional uint32 problem_id = 1; @@ -72,9 +72,9 @@ message GetQuestionListResponse { message SetUserRequest { - string username = 1; - string fullname = 2; - string password = 3; + required string username = 1; + required string fullname = 2; + required string password = 3; } message SetUserResponse { enum Code { @@ -85,7 +85,7 @@ message SetUserResponse { } message SetContestMetadataRequest { - ContestMetadata metadata = 1; + required ContestMetadata metadata = 1; } message SetContestMetadataResponse {} @@ -96,7 +96,7 @@ message SetProblemRequest { message SetProblemResponse {} message AddMessageRequest { - Message message = 1; + required Message message = 1; } message AddMessageResponse {} From 2f4ecd3d3634c1bf684da754a32221161b2915ec Mon Sep 17 00:00:00 2001 From: Michael Chelli Date: Wed, 13 Oct 2021 13:14:31 +0200 Subject: [PATCH 7/8] downgraded mongo version --- docker-compose.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 4de6b1c..8736a54 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -2,7 +2,7 @@ version: "3.3" services: contest_service_db: - image: mongo:5.0.0 + image: mongo:4.4.8 #mongo:5.0.0 restart: always environment: MONGO_INITDB_ROOT_USERNAME: root @@ -18,14 +18,14 @@ services: dockerfile: ./contest_service/Dockerfile ports: - 50051:50051 - depends_on: + depends_on: - contest_service_db - + evaluation_service: build: context: . dockerfile: ./evaluation_service/Dockerfile - ports: + ports: - 50052:50051 submission_service_db: @@ -38,18 +38,18 @@ services: # volumes: # - ./submission_service/db/init.js:/docker-entrypoint-initdb.d/init.js # - submission_service_db_volume:/data/db - ports: + ports: - 27020:27017 submission_service: build: context: . dockerfile: ./submission_service/Dockerfile - ports: + ports: - 50055:50051 - depends_on: + depends_on: - submission_service_db volumes: contest_service_db_volume: - submission_service_db_volume: # what for? \ No newline at end of file + submission_service_db_volume: # what for? From 30ba04c25548fd7398288a61ab3044956814fe28 Mon Sep 17 00:00:00 2001 From: Michael Chelli Date: Wed, 13 Oct 2021 13:19:48 +0200 Subject: [PATCH 8/8] moved mongo schema to new file --- contest_service/src/main.rs | 128 +--------------------------- contest_service/src/mongo_schema.rs | 124 +++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 124 deletions(-) create mode 100644 contest_service/src/mongo_schema.rs diff --git a/contest_service/src/main.rs b/contest_service/src/main.rs index 4b92225..197dc3c 100644 --- a/contest_service/src/main.rs +++ b/contest_service/src/main.rs @@ -5,10 +5,8 @@ use std::convert::TryFrom; use mappings::chat::Message; use mongodb::{ bson::{doc, Document}, - options::{ - ClientOptions, CreateCollectionOptions, UpdateOptions, ValidationAction, ValidationLevel, - }, - Client, Database, + options::{ClientOptions, UpdateOptions}, + Client, }; use protos::service::contest::{contest_server::*, *}; use protos::utils::*; @@ -16,6 +14,7 @@ use tonic::{transport::*, Request, Response, Status}; mod mappings; +mod mongo_schema; #[cfg(test)] mod tests; @@ -29,125 +28,6 @@ where Status::internal(format!("{:?}", e)) } -async fn init_contest_service_db(db: Database) -> Result<(), Box> { - // TODO: consider using this validator syntax (might be slightly nicer): - // https://docs.mongodb.com/v5.0/core/schema-validation/#other-query-expressions - db.create_collection( - "contest_metadata", - CreateCollectionOptions::builder() - .validator(doc! { - "$jsonSchema": { - "bsonType": "object", - "required": ["name", "description"], - "properties": { - "name": { "bsonType": "string" }, - "description": { "bsonType": "string" }, - "startTime": { "bsonType": "timestamp" }, // missing means there is no start time - "endTime": { "bsonType": "timestamp" } // missing means there is no end time - } - } - }) - .validation_action(ValidationAction::Error) - .validation_level(ValidationLevel::Strict) - .capped(true) - .max(1) - // Size must be set if we want to used capped. SWe set it to an unreachable value (1 MB) so - // it will never be reached by our singleton document (enforced by max(1)). - .size(2_u64.pow(20)) - .build(), - ) - .await?; - - db.create_collection( - "problems", - CreateCollectionOptions::builder() - .validator(doc! { - "$jsonSchema": { - "bsonType": "object", - "required": ["_id", "name", "longName", "statement"], - "properties": { - "_id": { "bsonType": "int" }, // problem id - "name": { "bsonType": "string" }, - "longName": { "bsonType": "string" }, - "statement": { "bsonType": "binData" } - } - } - }) - .validation_action(ValidationAction::Error) - .validation_level(ValidationLevel::Strict) - .build(), - ) - .await?; - - db.create_collection( - "users", - CreateCollectionOptions::builder() - .validator(doc! { - "$jsonSchema": { - "bsonType": "object", - "required": ["_id", "fullname", "password"], - "properties": { - "_id": { "bsonType": "string" }, // username - "fullname": { "bsonType": "string" }, - "password": { "bsonType": "string" } - } - } - }) - .validation_action(ValidationAction::Error) - .validation_level(ValidationLevel::Strict) - .build(), - ) - .await?; - - db.create_collection( - "announcements", - CreateCollectionOptions::builder() - .validator(doc! { - "$jsonSchema": { - "bsonType": "object", - "required": ["_id", "subject", "text", "created"], - "properties": { - "_id": { "bsonType": "int" }, // announcement id - "subject": { "bsonType": "string" }, - "problemId": { "bsonType": "int" }, - "text": { "bsonType": "string" }, - "to": { "bsonType": "string" }, - "created": { "bsonType": "timestamp" } - } - } - }) - .validation_action(ValidationAction::Error) - .validation_level(ValidationLevel::Strict) - .build(), - ) - .await?; - - db.create_collection( - "questions", - CreateCollectionOptions::builder() - .validator(doc! { - "$jsonSchema": { - "bsonType": "object", - "required": ["_id", "subject", "text", "created"], - "properties": { - "_id": { "bsonType": "int" }, // question id - "subject": { "bsonType": "string" }, - "problemId": { "bsonType": "int" }, - "text": { "bsonType": "string" }, - "from": { "bsonType": "string" }, - "created": { "bsonType": "timestamp" } - } - } - }) - .validation_action(ValidationAction::Error) - .validation_level(ValidationLevel::Strict) - .build(), - ) - .await?; - - Ok(()) -} - #[derive(Debug)] pub struct ContestService { db_client: Client, @@ -156,7 +36,7 @@ pub struct ContestService { impl ContestService { async fn new() -> Result> { let db_client = Client::with_options(ClientOptions::parse(CONNECTION_STRING).await?)?; - init_contest_service_db(db_client.database("contestdb")).await?; + mongo_schema::init_contest_service_db(db_client.database("contestdb")).await?; Ok(Self { db_client }) } diff --git a/contest_service/src/mongo_schema.rs b/contest_service/src/mongo_schema.rs new file mode 100644 index 0000000..72d233f --- /dev/null +++ b/contest_service/src/mongo_schema.rs @@ -0,0 +1,124 @@ +use mongodb::{ + bson::doc, + options::{CreateCollectionOptions, ValidationAction, ValidationLevel}, + Database, +}; + +pub async fn init_contest_service_db(db: Database) -> Result<(), Box> { + // TODO: consider using this validator syntax (might be slightly nicer): + // https://docs.mongodb.com/v5.0/core/schema-validation/#other-query-expressions + db.create_collection( + "contest_metadata", + CreateCollectionOptions::builder() + .validator(doc! { + "$jsonSchema": { + "bsonType": "object", + "required": ["name", "description"], + "properties": { + "name": { "bsonType": "string" }, + "description": { "bsonType": "string" }, + "startTime": { "bsonType": "timestamp" }, // missing means there is no start time + "endTime": { "bsonType": "timestamp" } // missing means there is no end time + } + } + }) + .validation_action(ValidationAction::Error) + .validation_level(ValidationLevel::Strict) + .capped(true) + .max(1) + // Size must be set if we want to used capped. SWe set it to an unreachable value (1 MB) so + // it will never be reached by our singleton document (enforced by max(1)). + .size(2_u64.pow(20)) + .build(), + ) + .await?; + + db.create_collection( + "problems", + CreateCollectionOptions::builder() + .validator(doc! { + "$jsonSchema": { + "bsonType": "object", + "required": ["_id", "name", "longName", "statement"], + "properties": { + "_id": { "bsonType": "int" }, // problem id + "name": { "bsonType": "string" }, + "longName": { "bsonType": "string" }, + "statement": { "bsonType": "binData" } + } + } + }) + .validation_action(ValidationAction::Error) + .validation_level(ValidationLevel::Strict) + .build(), + ) + .await?; + + db.create_collection( + "users", + CreateCollectionOptions::builder() + .validator(doc! { + "$jsonSchema": { + "bsonType": "object", + "required": ["_id", "fullname", "password"], + "properties": { + "_id": { "bsonType": "string" }, // username + "fullname": { "bsonType": "string" }, + "password": { "bsonType": "string" } + } + } + }) + .validation_action(ValidationAction::Error) + .validation_level(ValidationLevel::Strict) + .build(), + ) + .await?; + + db.create_collection( + "announcements", + CreateCollectionOptions::builder() + .validator(doc! { + "$jsonSchema": { + "bsonType": "object", + "required": ["_id", "subject", "text", "created"], + "properties": { + "_id": { "bsonType": "int" }, // announcement id + "subject": { "bsonType": "string" }, + "problemId": { "bsonType": "int" }, + "text": { "bsonType": "string" }, + "to": { "bsonType": "string" }, + "created": { "bsonType": "timestamp" } + } + } + }) + .validation_action(ValidationAction::Error) + .validation_level(ValidationLevel::Strict) + .build(), + ) + .await?; + + db.create_collection( + "questions", + CreateCollectionOptions::builder() + .validator(doc! { + "$jsonSchema": { + "bsonType": "object", + "required": ["_id", "subject", "text", "created"], + "properties": { + "_id": { "bsonType": "int" }, // question id + "subject": { "bsonType": "string" }, + "problemId": { "bsonType": "int" }, + "text": { "bsonType": "string" }, + "from": { "bsonType": "string" }, + "created": { "bsonType": "timestamp" } + } + } + }) + .validation_action(ValidationAction::Error) + .validation_level(ValidationLevel::Strict) + .build(), + ) + .await?; + + Ok(()) +}