diff --git a/crates/backend/src/controller.rs b/crates/backend/src/controller.rs index 6cf5f36..7bb9b1b 100644 --- a/crates/backend/src/controller.rs +++ b/crates/backend/src/controller.rs @@ -1,11 +1,11 @@ use actix_web::web::{self, ServiceConfig}; pub mod auth; // TODO: Refactor to use re-exports instead of making module public -mod class; -mod group; -mod project; -mod template; -mod user; +pub mod class; +pub mod group; +pub mod project; +pub mod template; +pub mod user; pub fn register_controllers(cfg: &mut ServiceConfig) { cfg.service(web::scope("/project").configure(project::setup)) diff --git a/crates/backend/src/controller/class.rs b/crates/backend/src/controller/class.rs index c113fdd..3eedc5d 100644 --- a/crates/backend/src/controller/class.rs +++ b/crates/backend/src/controller/class.rs @@ -8,31 +8,82 @@ pub fn setup(cfg: &mut actix_web::web::ServiceConfig) { .service(delete_class); } -// TODO +#[utoipa::path( + get, + path = "/api/v1/class", + tag = "classes", + summary = "Get all classes (Not Implemented)", + description = "Retrieve a list of all classes - currently not implemented", + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[get("")] async fn get_classes() -> impl Responder { "" } -// TODO +#[utoipa::path( + get, + path = "/api/v1/class/{id}", + tag = "classes", + summary = "Get class by ID (Not Implemented)", + description = "Retrieve a specific class by its ID - currently not implemented", + params( + ("id" = String, Path, description = "Class ID") + ), + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[get("/{id}")] async fn get_class() -> impl Responder { "" } -// TODO +#[utoipa::path( + post, + path = "/api/v1/class", + tag = "classes", + summary = "Create class (Not Implemented)", + description = "Create a new class - currently not implemented", + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[post("")] async fn create_class() -> impl Responder { "" } -// TODO +#[utoipa::path( + put, + path = "/api/v1/class", + tag = "classes", + summary = "Update class (Not Implemented)", + description = "Update an existing class - currently not implemented", + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[put("")] async fn update_class() -> impl Responder { "" } -// TODO +#[utoipa::path( + delete, + path = "/api/v1/class/{id}", + tag = "classes", + summary = "Delete class (Not Implemented)", + description = "Delete a class by its ID - currently not implemented", + params( + ("id" = String, Path, description = "Class ID to delete") + ), + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[delete("/{id}")] async fn delete_class() -> impl Responder { "" diff --git a/crates/backend/src/controller/group.rs b/crates/backend/src/controller/group.rs index 7939664..1999bdc 100644 --- a/crates/backend/src/controller/group.rs +++ b/crates/backend/src/controller/group.rs @@ -8,31 +8,82 @@ pub fn setup(cfg: &mut actix_web::web::ServiceConfig) { .service(delete_group); } -// TODO +#[utoipa::path( + get, + path = "/api/v1/group", + tag = "groups", + summary = "Get all groups (Not Implemented)", + description = "Retrieve a list of all groups - currently not implemented", + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[get("")] async fn get_groups() -> impl Responder { "" } -// TODO +#[utoipa::path( + get, + path = "/api/v1/group/{project}", + tag = "groups", + summary = "Get groups for project (Not Implemented)", + description = "Retrieve groups for a specific project - currently not implemented", + params( + ("project" = String, Path, description = "Project ID") + ), + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[get("/{project}")] async fn get_groups_for_project() -> impl Responder { "" } -// TODO +#[utoipa::path( + post, + path = "/api/v1/group", + tag = "groups", + summary = "Create group (Not Implemented)", + description = "Create a new group - currently not implemented", + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[post("")] async fn create_group() -> impl Responder { "" } -// TODO +#[utoipa::path( + put, + path = "/api/v1/group", + tag = "groups", + summary = "Update group (Not Implemented)", + description = "Update an existing group - currently not implemented", + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[put("")] async fn update_group() -> impl Responder { "" } -// TODO +#[utoipa::path( + delete, + path = "/api/v1/group/{id}", + tag = "groups", + summary = "Delete group (Not Implemented)", + description = "Delete a group by its ID - currently not implemented", + params( + ("id" = String, Path, description = "Group ID to delete") + ), + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[delete("/{id}")] async fn delete_group() -> impl Responder { "" diff --git a/crates/backend/src/controller/project.rs b/crates/backend/src/controller/project.rs index 6f55338..6fa4f02 100644 --- a/crates/backend/src/controller/project.rs +++ b/crates/backend/src/controller/project.rs @@ -15,6 +15,17 @@ pub fn setup(cfg: &mut actix_web::web::ServiceConfig) { .service(delete_project); } +#[utoipa::path( + get, + path = "/api/v1/project", + tag = "projects", + summary = "Get all projects", + description = "Retrieve a list of all projects", + responses( + (status = 200, description = "List of projects retrieved successfully", body = Vec), + (status = 500, description = "Internal server error", body = String) + ) +)] #[get("")] async fn get_projects( db: web::Data, @@ -24,6 +35,21 @@ async fn get_projects( Ok(web::Json(projects)) } +#[utoipa::path( + get, + path = "/api/v1/project/{id}", + tag = "projects", + summary = "Get project by ID", + description = "Retrieve a specific project by its ID", + params( + ("id" = String, Path, description = "Project ID") + ), + responses( + (status = 200, description = "Project retrieved successfully", body = entity::project::Model), + (status = 404, description = "Project not found", body = String), + (status = 500, description = "Internal server error", body = String) + ) +)] #[get("/{id}")] async fn get_project( db: web::Data, @@ -36,6 +62,19 @@ async fn get_project( Ok(web::Json(project.unwrap())) } +#[utoipa::path( + post, + path = "/api/v1/project", + tag = "projects", + summary = "Create a new project", + description = "Create a new project with the provided details", + request_body = CreateProject, + responses( + (status = 200, description = "Project created successfully", body = entity::project::Model), + (status = 400, description = "Invalid request data or validation error", body = String), + (status = 500, description = "Internal server error", body = String) + ) +)] #[post("")] async fn create_project( db: web::Data, @@ -47,6 +86,23 @@ async fn create_project( Ok(web::Json(result)) } +#[utoipa::path( + put, + path = "/api/v1/project/{id}", + tag = "projects", + summary = "Update project", + description = "Update an existing project by its ID", + params( + ("id" = String, Path, description = "Project ID to update") + ), + request_body = CreateProject, + responses( + (status = 200, description = "Project updated successfully", body = entity::project::Model), + (status = 400, description = "Invalid request data or validation error", body = String), + (status = 404, description = "Project not found", body = String), + (status = 500, description = "Internal server error", body = String) + ) +)] #[put("/{id}")] async fn update_project( db: web::Data, @@ -60,6 +116,21 @@ async fn update_project( Ok(web::Json(updated_project)) } +#[utoipa::path( + delete, + path = "/api/v1/project/{id}", + tag = "projects", + summary = "Delete project", + description = "Delete a project by its ID", + params( + ("id" = String, Path, description = "Project ID to delete") + ), + responses( + (status = 200, description = "Project deleted successfully", body = String), + (status = 404, description = "Project not found", body = String), + (status = 500, description = "Internal server error", body = String) + ) +)] #[delete("/{id}")] async fn delete_project( db: web::Data, diff --git a/crates/backend/src/controller/template.rs b/crates/backend/src/controller/template.rs index 7cb97b1..ab24bc1 100644 --- a/crates/backend/src/controller/template.rs +++ b/crates/backend/src/controller/template.rs @@ -8,31 +8,82 @@ pub fn setup(cfg: &mut actix_web::web::ServiceConfig) { .service(delete_template); } -// TODO +#[utoipa::path( + get, + path = "/api/v1/template", + tag = "templates", + summary = "Get all templates (Not Implemented)", + description = "Retrieve a list of all templates - currently not implemented", + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[get("")] async fn get_templates() -> impl Responder { "" } -// TODO +#[utoipa::path( + get, + path = "/api/v1/template/{id}", + tag = "templates", + summary = "Get template by ID (Not Implemented)", + description = "Retrieve a specific template by its ID - currently not implemented", + params( + ("id" = String, Path, description = "Template ID") + ), + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[get("/{id}")] async fn get_template() -> impl Responder { "" } -// TODO +#[utoipa::path( + post, + path = "/api/v1/template", + tag = "templates", + summary = "Create template (Not Implemented)", + description = "Create a new template - currently not implemented", + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[post("")] async fn create_template() -> impl Responder { "" } -// TODO +#[utoipa::path( + put, + path = "/api/v1/template", + tag = "templates", + summary = "Update template (Not Implemented)", + description = "Update an existing template - currently not implemented", + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[put("")] async fn update_template() -> impl Responder { "" } -// TODO +#[utoipa::path( + delete, + path = "/api/v1/template/{id}", + tag = "templates", + summary = "Delete template (Not Implemented)", + description = "Delete a template by its ID - currently not implemented", + params( + ("id" = String, Path, description = "Template ID to delete") + ), + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[delete("/{id}")] async fn delete_template() -> impl Responder { "" diff --git a/crates/backend/src/controller/user.rs b/crates/backend/src/controller/user.rs index 0381c4b..6fe0775 100644 --- a/crates/backend/src/controller/user.rs +++ b/crates/backend/src/controller/user.rs @@ -2,6 +2,7 @@ use crate::{Database, entity, error::ApiError}; use actix_web::{Responder, delete, get, post, put, web}; use serde::Deserialize; use validator::Validate; +use utoipa::ToSchema; pub fn setup(cfg: &mut actix_web::web::ServiceConfig) { cfg.service(get_users) @@ -10,15 +11,29 @@ pub fn setup(cfg: &mut actix_web::web::ServiceConfig) { .service(delete_user); } -#[derive(Deserialize, Validate)] -struct CreateUser { +#[derive(Deserialize, Validate, ToSchema)] +pub struct CreateUser { #[validate(length(min = 4))] + /// Username (minimum 4 characters) username: String, + /// Full name of the user name: String, #[validate(length(min = 8))] + /// Password (minimum 8 characters) password: String, } +#[utoipa::path( + get, + path = "/api/v1/user", + tag = "users", + summary = "Get all users", + description = "Retrieve a list of all users", + responses( + (status = 200, description = "List of users retrieved successfully", body = Vec), + (status = 500, description = "Internal server error", body = String) + ) +)] #[get("")] async fn get_users( db: web::Data, @@ -27,6 +42,21 @@ async fn get_users( Ok(web::Json(users)) } +#[utoipa::path( + get, + path = "/api/v1/user/{id}", + tag = "users", + summary = "Get user by ID", + description = "Retrieve a specific user by their ID", + params( + ("id" = String, Path, description = "User ID") + ), + responses( + (status = 200, description = "User retrieved successfully", body = entity::user::Model), + (status = 404, description = "User not found", body = String), + (status = 500, description = "Internal server error", body = String) + ) +)] #[get("/{id}")] async fn get_user( db: web::Data, @@ -37,6 +67,19 @@ async fn get_user( Ok(web::Json(user.unwrap())) } +#[utoipa::path( + post, + path = "/api/v1/user", + tag = "users", + summary = "Create a new user", + description = "Create a new user with username, name, and password", + request_body = CreateUser, + responses( + (status = 200, description = "User created successfully", body = entity::user::Model), + (status = 400, description = "Invalid request data or validation error", body = String), + (status = 500, description = "Internal server error", body = String) + ) +)] #[post("")] async fn create_user( db: web::Data, @@ -50,11 +93,36 @@ async fn create_user( Ok(web::Json(result)) } +#[utoipa::path( + put, + path = "/api/v1/user", + tag = "users", + summary = "Update user (Not Implemented)", + description = "Update user information - currently not implemented", + responses( + (status = 501, description = "Not implemented", body = String) + ) +)] #[put("")] async fn update_user() -> impl Responder { "" } +#[utoipa::path( + delete, + path = "/api/v1/user/{id}", + tag = "users", + summary = "Delete user", + description = "Delete a user by their ID", + params( + ("id" = String, Path, description = "User ID to delete") + ), + responses( + (status = 200, description = "User deleted successfully", body = String), + (status = 404, description = "User not found", body = String), + (status = 500, description = "Internal server error", body = String) + ) +)] #[delete("/{id}")] async fn delete_user( db: web::Data, diff --git a/crates/backend/src/db/entity/project.rs b/crates/backend/src/db/entity/project.rs index 24e4437..53a162e 100644 --- a/crates/backend/src/db/entity/project.rs +++ b/crates/backend/src/db/entity/project.rs @@ -2,8 +2,9 @@ use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize, ToSchema)] #[sea_orm(table_name = "project")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/crates/backend/src/db/entity/user.rs b/crates/backend/src/db/entity/user.rs index 03a6b23..8d4b862 100644 --- a/crates/backend/src/db/entity/user.rs +++ b/crates/backend/src/db/entity/user.rs @@ -2,8 +2,9 @@ use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize, ToSchema)] #[sea_orm(table_name = "user")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/crates/backend/src/db/project.rs b/crates/backend/src/db/project.rs index ac6c670..c609dd7 100644 --- a/crates/backend/src/db/project.rs +++ b/crates/backend/src/db/project.rs @@ -8,11 +8,13 @@ use sea_orm::{ActiveModelTrait, DeleteResult, EntityTrait}; use serde::Deserialize; use uuid::Uuid; use validator::Validate; +use utoipa::ToSchema; -#[derive(Deserialize, Validate)] +#[derive(Deserialize, Validate, ToSchema)] pub struct CreateProject { #[validate(length(min = 3))] - name: String, + /// Project name (minimum 3 characters) + pub name: String, } impl Database { diff --git a/crates/backend/src/error.rs b/crates/backend/src/error.rs index 8ca3566..4747a25 100644 --- a/crates/backend/src/error.rs +++ b/crates/backend/src/error.rs @@ -54,6 +54,7 @@ impl From> for ApiError { #[derive(Serialize, ToSchema)] pub struct MessageResponse { + /// Response message pub message: String, } @@ -65,3 +66,11 @@ impl MessageResponse { } } } + +#[derive(Serialize, ToSchema)] +pub struct ApiErrorResponse { + /// Error message + pub error: String, + /// HTTP status code + pub status: u16, +} diff --git a/crates/backend/src/main.rs b/crates/backend/src/main.rs index fbb3dbb..cf16015 100644 --- a/crates/backend/src/main.rs +++ b/crates/backend/src/main.rs @@ -30,16 +30,48 @@ struct AppConfig { paths( controller::auth::login, controller::auth::logout, + controller::project::get_projects, + controller::project::get_project, + controller::project::create_project, + controller::project::update_project, + controller::project::delete_project, + controller::user::get_users, + controller::user::get_user, + controller::user::create_user, + controller::user::update_user, + controller::user::delete_user, + controller::group::get_groups, + controller::group::get_groups_for_project, + controller::group::create_group, + controller::group::update_group, + controller::group::delete_group, + controller::class::get_classes, + controller::class::get_class, + controller::class::create_class, + controller::class::update_class, + controller::class::delete_class, + controller::template::get_templates, + controller::template::get_template, + controller::template::create_template, + controller::template::update_template, + controller::template::delete_template, ), components(schemas( controller::auth::LoginRequest, error::MessageResponse, + error::ApiErrorResponse, + db::project::CreateProject, + controller::user::CreateUser, + entity::project::Model, + entity::user::Model, )), tags( (name = "auth", description = "Authentication endpoints"), (name = "users", description = "User management endpoints"), (name = "projects", description = "Project management endpoints"), - (name = "groups", description = "Group management endpoints"), + (name = "groups", description = "Group management endpoints (Not Implemented)"), + (name = "classes", description = "Class management endpoints (Not Implemented)"), + (name = "templates", description = "Template management endpoints (Not Implemented)"), ) )] struct ApiDoc;