Skip to content

Commit f4fa29e

Browse files
authored
Merge pull request #5 from kavinda-100/develop
Develop
2 parents d8c31fe + 0f79206 commit f4fa29e

6 files changed

Lines changed: 283 additions & 4 deletions

File tree

src/controllers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod auth_controller;
22
pub mod root_controller;
3+
pub mod workspace_controller;
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
use axum::{
2+
Json,
3+
extract::{Path, State},
4+
http::StatusCode,
5+
};
6+
use uuid::Uuid;
7+
use validator::Validate;
8+
9+
use crate::{
10+
config::AppState,
11+
dtos::workspace_dto::{CreateWorkspacePayload, UpdateWorkspacePayload, WorkspaceResponse},
12+
middleware::auth::AuthUser,
13+
models::Workspace,
14+
utils::{api_error::ApiError, format_validation_errors, response::ApiResponse},
15+
};
16+
17+
/**
18+
* Creates a new workspace with the provided name for the authenticated user.
19+
* Validates the input, stores the workspace in the database, and returns a JSON response with the workspace details.
20+
* Route: POST /api/v1/workspaces
21+
*/
22+
pub async fn create_workspace(
23+
State(state): State<AppState>,
24+
AuthUser(user): AuthUser,
25+
Json(payload): Json<CreateWorkspacePayload>,
26+
) -> Result<Json<ApiResponse<WorkspaceResponse>>, ApiError> {
27+
// logging the workspace creation attempt with the email (but not the password)
28+
tracing::info!("Attempting to create workspace with name: {}", payload.name);
29+
30+
// Validate the input payload
31+
payload.validate().map_err(|e| {
32+
let error_messages = format_validation_errors(&e);
33+
tracing::error!("Validation errors: {}", error_messages);
34+
ApiError::BadRequest(error_messages)
35+
})?;
36+
37+
// create the workspace in the database
38+
let new_workspace = sqlx::query_as::<_, Workspace>(
39+
"INSERT INTO workspaces (name, owner_id) VALUES ($1, $2) RETURNING *",
40+
)
41+
.bind(&payload.name)
42+
.bind(user.id)
43+
.fetch_one(&state.db)
44+
.await
45+
.map_err(|_| ApiError::InternalServerError("Failed to create workspace".into()))?;
46+
47+
// Placeholder implementation
48+
Ok(Json(ApiResponse::new(
49+
true,
50+
StatusCode::CREATED,
51+
"Workspace created successfully",
52+
Some(WorkspaceResponse {
53+
id: new_workspace.id,
54+
name: new_workspace.name,
55+
owner_id: new_workspace.owner_id,
56+
created_at: new_workspace.created_at,
57+
updated_at: new_workspace.updated_at,
58+
}),
59+
)))
60+
}
61+
62+
/**
63+
* Retrieves a list of workspaces that the authenticated user has access to.
64+
* This is a placeholder implementation that currently returns an empty list.
65+
* Route: GET /api/v1/workspaces
66+
*/
67+
pub async fn get_workspaces(
68+
State(state): State<AppState>,
69+
AuthUser(user): AuthUser,
70+
) -> Result<Json<ApiResponse<Vec<WorkspaceResponse>>>, ApiError> {
71+
// logging the workspace retrieval attempt
72+
tracing::info!("User {} is attempting to retrieve workspaces", user.email);
73+
74+
// get the workspaces from the database that the user has access to (owned)
75+
let _workspaces = sqlx::query_as::<_, Workspace>(
76+
"SELECT * FROM workspaces WHERE owner_id = $1 ORDER BY created_at DESC",
77+
)
78+
.bind(user.id)
79+
.fetch_all(&state.db)
80+
.await
81+
.map_err(|_| ApiError::InternalServerError("Failed to retrieve workspaces".into()))?;
82+
83+
// map the workspaces to the response DTOs
84+
let _workspace_responses: Vec<WorkspaceResponse> = _workspaces
85+
.into_iter()
86+
.map(|ws| WorkspaceResponse {
87+
id: ws.id,
88+
name: ws.name,
89+
owner_id: ws.owner_id,
90+
created_at: ws.created_at,
91+
updated_at: ws.updated_at,
92+
})
93+
.collect();
94+
95+
// Placeholder implementation
96+
Ok(Json(ApiResponse::new(
97+
true,
98+
StatusCode::OK,
99+
"Workspaces retrieved successfully",
100+
Some(_workspace_responses), // Return the list of workspaces
101+
)))
102+
}
103+
104+
/**
105+
* Updates the name of an existing workspace that belongs to the authenticated user.
106+
* Validates the input, checks if the workspace exists and belongs to the user, updates it in the database, and returns a JSON response with the updated workspace details.
107+
* Route: PUT /api/v1/workspaces/{id}
108+
*/
109+
pub async fn update_workspace(
110+
State(state): State<AppState>,
111+
AuthUser(user): AuthUser,
112+
Path(id): Path<Uuid>,
113+
Json(payload): Json<UpdateWorkspacePayload>,
114+
) -> Result<Json<ApiResponse<WorkspaceResponse>>, ApiError> {
115+
// logging the workspace update attempt with the email (but not the password)
116+
tracing::info!(
117+
"User {} is attempting to update workspace with id: {}",
118+
user.email,
119+
id
120+
);
121+
122+
// Validate the input payload
123+
payload.validate().map_err(|e| {
124+
let error_messages = format_validation_errors(&e);
125+
tracing::error!("Validation errors: {}", error_messages);
126+
ApiError::BadRequest(error_messages)
127+
})?;
128+
129+
// check if the workspace exists and belongs to the user
130+
let existing_workspace =
131+
sqlx::query_as::<_, Workspace>("SELECT * FROM workspaces WHERE id = $1 AND owner_id = $2")
132+
.bind(id)
133+
.bind(user.id)
134+
.fetch_optional(&state.db)
135+
.await
136+
.map_err(|_| ApiError::InternalServerError("Failed to retrieve workspace".into()))?;
137+
138+
// if the workspace doesn't exist or doesn't belong to the user, return a 404 error
139+
if existing_workspace.is_none() {
140+
tracing::warn!("Workspace with id {} not found for user {}", id, user.email);
141+
return Err(ApiError::NotFound("Workspace not found".into()));
142+
}
143+
144+
// update the workspace in the database
145+
let updated_workspace = sqlx::query_as::<_, Workspace>(
146+
"UPDATE workspaces SET name = $1, updated_at = NOW() WHERE id = $2 AND owner_id = $3 RETURNING *",
147+
)
148+
.bind(&payload.name)
149+
.bind(id)
150+
.bind(user.id)
151+
.fetch_one(&state.db)
152+
.await
153+
.map_err(|_| ApiError::InternalServerError("Failed to update workspace".into()))?;
154+
155+
// Placeholder implementation
156+
Ok(Json(ApiResponse::new(
157+
true,
158+
StatusCode::OK,
159+
"Workspace updated successfully",
160+
Some(WorkspaceResponse {
161+
id: updated_workspace.id,
162+
name: updated_workspace.name,
163+
owner_id: updated_workspace.owner_id,
164+
created_at: updated_workspace.created_at,
165+
updated_at: updated_workspace.updated_at,
166+
}),
167+
)))
168+
}
169+
170+
/**
171+
* Deletes an existing workspace that belongs to the authenticated user.
172+
* Validates the input, checks if the workspace exists and belongs to the user, deletes it from the database, and returns a JSON response confirming the deletion.
173+
* Route: DELETE /api/v1/workspaces/{id}
174+
*/
175+
pub async fn delete_workspace(
176+
State(state): State<AppState>,
177+
AuthUser(user): AuthUser,
178+
Path(id): Path<Uuid>,
179+
) -> Result<Json<ApiResponse<()>>, ApiError> {
180+
// logging the workspace update attempt with the email (but not the password)
181+
tracing::info!(
182+
"User {} is attempting to delete workspace with id: {}",
183+
user.email,
184+
id
185+
);
186+
187+
// check if the workspace exists and belongs to the user
188+
let existing_workspace =
189+
sqlx::query_as::<_, Workspace>("SELECT * FROM workspaces WHERE id = $1 AND owner_id = $2")
190+
.bind(id)
191+
.bind(user.id)
192+
.fetch_optional(&state.db)
193+
.await
194+
.map_err(|_| ApiError::InternalServerError("Failed to retrieve workspace".into()))?;
195+
196+
// if the workspace doesn't exist or doesn't belong to the user, return a 404 error
197+
let existing_workspace = existing_workspace.ok_or_else(|| {
198+
tracing::warn!("Workspace with id {} not found for user {}", id, user.email);
199+
ApiError::NotFound("Workspace not found".into())
200+
})?;
201+
202+
// delete the workspace from the database
203+
sqlx::query("DELETE FROM workspaces WHERE id = $1 AND owner_id = $2")
204+
.bind(id)
205+
.bind(user.id)
206+
.execute(&state.db)
207+
.await
208+
.map_err(|_| ApiError::InternalServerError("Failed to delete workspace".into()))?;
209+
210+
// Return a success response with no data
211+
Ok(Json(ApiResponse::new(
212+
true,
213+
StatusCode::OK,
214+
format!(
215+
"Workspace named: {} deleted successfully",
216+
existing_workspace.name
217+
),
218+
None,
219+
)))
220+
}

src/dtos/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod auth_dto;
2+
pub mod workspace_dto;

src/dtos/workspace_dto.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use chrono::NaiveDateTime;
2+
use serde::{Deserialize, Serialize};
3+
use uuid::Uuid;
4+
use validator::Validate;
5+
6+
#[derive(Debug, Deserialize, Validate)]
7+
pub struct CreateWorkspacePayload {
8+
#[validate(length(
9+
min = 2,
10+
max = 100,
11+
message = "Workspace name must be between 2 and 100 characters"
12+
))]
13+
pub name: String,
14+
}
15+
16+
#[derive(Debug, Deserialize, Validate)]
17+
pub struct UpdateWorkspacePayload {
18+
#[validate(length(
19+
min = 2,
20+
max = 100,
21+
message = "Workspace name must be between 2 and 100 characters"
22+
))]
23+
pub name: String,
24+
}
25+
26+
#[derive(Debug, Serialize)]
27+
pub struct WorkspaceResponse {
28+
pub id: Uuid,
29+
pub name: String,
30+
pub owner_id: Uuid,
31+
pub created_at: NaiveDateTime,
32+
pub updated_at: NaiveDateTime,
33+
}

src/routes/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ use axum::{Router, routing::get};
22
use tower_http::services::fs::ServeDir;
33

44
pub mod auth_routes;
5+
pub mod workspace_routes;
56

67
use crate::{
78
config::AppState, controllers::root_controller::health_check,
8-
routes::auth_routes::create_auth_routes,
9+
routes::auth_routes::create_auth_routes, routes::workspace_routes::create_workspace_routes,
910
};
1011

1112
/// Creates the main API router with all route groups
@@ -27,7 +28,7 @@ pub fn create_routes() -> Router<AppState> {
2728

2829
// Helper function to create API v1 routes
2930
fn api_v1_routes() -> Router<AppState> {
30-
Router::new().merge(create_auth_routes())
31-
// other routes
32-
// .merge(create_user_routes())
31+
Router::new()
32+
.merge(create_auth_routes())
33+
.merge(create_workspace_routes())
3334
}

src/routes/workspace_routes.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use axum::{
2+
Router,
3+
routing::{post, put},
4+
};
5+
6+
use crate::{
7+
config::AppState,
8+
controllers::workspace_controller::{
9+
create_workspace, delete_workspace, get_workspaces, update_workspace,
10+
},
11+
};
12+
13+
// Workspace routes module
14+
// This module defines all routes related to workspaces
15+
// Route path: base_url/api/v1/workspaces/*
16+
pub fn create_workspace_routes() -> Router<AppState> {
17+
Router::new()
18+
.route("/workspaces", post(create_workspace).get(get_workspaces))
19+
.route(
20+
"/workspaces/{id}",
21+
put(update_workspace).delete(delete_workspace),
22+
)
23+
}

0 commit comments

Comments
 (0)