aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/api.rs1
-rw-r--r--src/error/client.rs10
-rw-r--r--src/models.rs19
-rw-r--r--src/routers/mod.rs5
-rw-r--r--src/routers/queue.rs224
5 files changed, 255 insertions, 4 deletions
diff --git a/src/api.rs b/src/api.rs
index 23fb74b..8a6d599 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -7,6 +7,7 @@ use crate::ErrorResponse;
7 7
8pub mod tags { 8pub mod tags {
9 pub const ACCOUNT: &str = "Account"; 9 pub const ACCOUNT: &str = "Account";
10 pub const QUEUE: &str = "Queue";
10} 11}
11 12
12struct AuthModifier; 13struct AuthModifier;
diff --git a/src/error/client.rs b/src/error/client.rs
index 980e3d2..70b6001 100644
--- a/src/error/client.rs
+++ b/src/error/client.rs
@@ -4,6 +4,8 @@ pub enum ClientError {
4 UserAlreadyExists { username: String }, 4 UserAlreadyExists { username: String },
5 InvalidPassword, 5 InvalidPassword,
6 NotAuthorized, 6 NotAuthorized,
7 UserNotFound { id: i64 },
8 QueueNotFound { id: i64 },
7} 9}
8 10
9impl ClientError { 11impl ClientError {
@@ -14,6 +16,8 @@ impl ClientError {
14 Self::UserAlreadyExists { .. } => "UserAlreadyExists", 16 Self::UserAlreadyExists { .. } => "UserAlreadyExists",
15 Self::InvalidPassword => "InvalidPassword", 17 Self::InvalidPassword => "InvalidPassword",
16 Self::NotAuthorized => "NotAuthorized", 18 Self::NotAuthorized => "NotAuthorized",
19 Self::UserNotFound { .. } => "UserNotFound",
20 Self::QueueNotFound { .. } => "QueueNotFound",
17 } 21 }
18 .to_string() 22 .to_string()
19 } 23 }
@@ -25,9 +29,11 @@ impl ClientError {
25 Self::UserAlreadyExists { username } => { 29 Self::UserAlreadyExists { username } => {
26 format!("user with username `{}` already exists", username) 30 format!("user with username `{}` already exists", username)
27 } 31 }
28 Self::InvalidPassword => "password is invalid".to_string(), 32 Self::InvalidPassword => format!("password is invalid"),
29 33
30 Self::NotAuthorized => "user is not authorized".to_string(), 34 Self::NotAuthorized => format!("user is not authorized"),
35 Self::UserNotFound { id } => format!("user with id `{}` not found", id),
36 Self::QueueNotFound { id } => format!("queue with id `{}` not found", id),
31 } 37 }
32 } 38 }
33} 39}
diff --git a/src/models.rs b/src/models.rs
index b7631a4..4821ec3 100644
--- a/src/models.rs
+++ b/src/models.rs
@@ -1,4 +1,4 @@
1use entity::users; 1use entity::{queues, users};
2use serde::Serialize; 2use serde::Serialize;
3use utoipa::ToSchema; 3use utoipa::ToSchema;
4 4
@@ -25,3 +25,20 @@ impl From<users::Model> for Account {
25 } 25 }
26 } 26 }
27} 27}
28
29#[derive(Serialize, ToSchema)]
30pub struct Queue {
31 #[schema(examples(1))]
32 pub id: i64,
33 #[schema(examples("John's queue", "Очередь Ивана"))]
34 pub name: String,
35}
36
37impl From<queues::Model> for Queue {
38 fn from(value: queues::Model) -> Self {
39 Self {
40 id: value.id,
41 name: value.name,
42 }
43 }
44}
diff --git a/src/routers/mod.rs b/src/routers/mod.rs
index 80f9e74..c73e1f8 100644
--- a/src/routers/mod.rs
+++ b/src/routers/mod.rs
@@ -1,4 +1,5 @@
1mod account; 1mod account;
2mod queue;
2 3
3use utoipa::OpenApi; 4use utoipa::OpenApi;
4use utoipa_axum::router::OpenApiRouter; 5use utoipa_axum::router::OpenApiRouter;
@@ -6,5 +7,7 @@ use utoipa_axum::router::OpenApiRouter;
6use crate::{AppOpenApi, AppState}; 7use crate::{AppOpenApi, AppState};
7 8
8pub fn router() -> OpenApiRouter<AppState> { 9pub fn router() -> OpenApiRouter<AppState> {
9 OpenApiRouter::with_openapi(AppOpenApi::openapi()).nest("/account", account::router()) 10 OpenApiRouter::with_openapi(AppOpenApi::openapi())
11 .nest("/account", account::router())
12 .nest("/queue", queue::routes())
10} 13}
diff --git a/src/routers/queue.rs b/src/routers/queue.rs
new file mode 100644
index 0000000..1e93b2a
--- /dev/null
+++ b/src/routers/queue.rs
@@ -0,0 +1,224 @@
1use axum::extract::State;
2use entity::{queues, users};
3use sea_orm::{
4 ActiveModelTrait, ActiveValue::Set, ColumnTrait, DatabaseConnection, EntityTrait,
5 IntoActiveModel, ModelTrait, QueryFilter,
6};
7use serde::Deserialize;
8use utoipa::ToSchema;
9use utoipa_axum::{router::OpenApiRouter, routes};
10
11use crate::{
12 ApiError, ApiResult, AppState, ClientError, GlobalResponses, SuccessResponse,
13 extract::{ApiJson, Auth},
14 models::Queue,
15 tags::QUEUE,
16};
17
18async fn user_exists(id: i64, db: &DatabaseConnection) -> Result<bool, ApiError> {
19 Ok(users::Entity::find_by_id(id).one(db).await?.is_some())
20}
21
22async fn get_owned_queue(
23 id: i64,
24 owner_id: i64,
25 db: &DatabaseConnection,
26) -> Result<queues::Model, ApiError> {
27 Ok(queues::Entity::find_by_id(id)
28 .filter(queues::Column::OwnerId.eq(owner_id))
29 .one(db)
30 .await?
31 .ok_or(ClientError::QueueNotFound { id })?)
32}
33
34#[derive(Deserialize, ToSchema)]
35struct CreateQueueRequest {
36 #[schema(examples("John's queue", "Очередь Ивана"))]
37 name: String,
38}
39
40#[derive(Deserialize, ToSchema)]
41struct ChangeQueueNameRequest {
42 #[schema(examples(1))]
43 id: i64,
44 #[schema(examples("John's queue", "Очередь Ивана"))]
45 new_name: String,
46}
47
48#[derive(Deserialize, ToSchema)]
49struct ChangeQueueOwnerRequest {
50 #[schema(examples(1))]
51 id: i64,
52 #[schema(examples(1))]
53 new_owner_id: i64,
54}
55
56#[derive(Deserialize, ToSchema)]
57struct DeleteQueueRequest {
58 #[schema(examples(1))]
59 id: i64,
60}
61
62#[utoipa::path(
63 get,
64 path = "/owned",
65 tag = QUEUE,
66 summary = "Get owned",
67 description = "Get your own queues",
68 responses(
69 (
70 status = 200, body = SuccessResponse<Vec<Queue>>,
71 description = "Success response with your queues"
72 ),
73 GlobalResponses
74 ),
75 security(("auth" = [])),
76)]
77async fn owned(State(state): State<AppState>, Auth(user): Auth) -> ApiResult<Vec<Queue>> {
78 return Ok(SuccessResponse::ok(
79 queues::Entity::find()
80 .filter(queues::Column::OwnerId.eq(user.id))
81 .all(&state.db)
82 .await?
83 .into_iter()
84 .map(Into::into)
85 .collect(),
86 ));
87}
88
89#[utoipa::path(
90 post,
91 path = "/create",
92 tag = QUEUE,
93 summary = "Create",
94 description = "Create a new queue",
95 request_body = CreateQueueRequest,
96 responses(
97 (
98 status = 200, body = SuccessResponse<Queue>,
99 description = "Success response with created queue"
100 ),
101 GlobalResponses
102 ),
103 security(("auth" = [])),
104)]
105async fn create(
106 State(state): State<AppState>,
107 Auth(user): Auth,
108 ApiJson(req): ApiJson<CreateQueueRequest>,
109) -> ApiResult<Queue> {
110 Ok(SuccessResponse::ok(
111 queues::ActiveModel {
112 owner_id: Set(user.id),
113 name: Set(req.name),
114 ..Default::default()
115 }
116 .insert(&state.db)
117 .await?
118 .into(),
119 ))
120}
121
122#[utoipa::path(
123 put,
124 path = "/change/name",
125 tag = QUEUE,
126 summary = "Change name",
127 description = "Change queue name",
128 request_body = ChangeQueueNameRequest,
129 responses(
130 (
131 status = 200, body = SuccessResponse<Queue>,
132 description = "Success response with changed queue data"
133 ),
134 GlobalResponses
135 ),
136 security(("auth" = [])),
137)]
138async fn change_name(
139 State(state): State<AppState>,
140 Auth(user): Auth,
141 ApiJson(req): ApiJson<ChangeQueueNameRequest>,
142) -> ApiResult<Queue> {
143 let mut active_queue = get_owned_queue(req.id, user.id, &state.db)
144 .await?
145 .into_active_model();
146
147 active_queue.name = Set(req.new_name);
148
149 let queue = active_queue.update(&state.db).await?;
150 Ok(SuccessResponse::ok(queue.into()))
151}
152
153#[utoipa::path(
154 put,
155 path = "/change/owner",
156 tag = QUEUE,
157 summary = "Change owner",
158 description = "Transfer ownership of the queue",
159 request_body = ChangeQueueOwnerRequest,
160 responses(
161 (
162 status = 200, body = SuccessResponse<Queue>,
163 description = "Success response with changed queue data"
164 ),
165 GlobalResponses
166 ),
167 security(("auth" = [])),
168)]
169async fn change_owner(
170 State(state): State<AppState>,
171 Auth(user): Auth,
172 ApiJson(req): ApiJson<ChangeQueueOwnerRequest>,
173) -> ApiResult<Queue> {
174 if !user_exists(req.new_owner_id, &state.db).await? {
175 return Err(ClientError::UserNotFound {
176 id: req.new_owner_id,
177 }
178 .into());
179 }
180
181 let mut active_queue = get_owned_queue(req.id, user.id, &state.db)
182 .await?
183 .into_active_model();
184
185 active_queue.owner_id = Set(req.new_owner_id);
186
187 let queue = active_queue.update(&state.db).await?;
188 Ok(SuccessResponse::ok(queue.into()))
189}
190
191#[utoipa::path(
192 delete,
193 path = "/delete",
194 tag = QUEUE,
195 summary = "Delete",
196 description = "Delete queue",
197 request_body = DeleteQueueRequest,
198 responses(
199 (
200 status = 200, body = SuccessResponse<Queue>,
201 description = "Success response with deleted queue data"
202 ),
203 GlobalResponses
204 ),
205 security(("auth" = [])),
206)]
207async fn delete(
208 State(state): State<AppState>,
209 Auth(user): Auth,
210 ApiJson(req): ApiJson<DeleteQueueRequest>,
211) -> ApiResult<Queue> {
212 let queue = get_owned_queue(req.id, user.id, &state.db).await?;
213 queue.clone().delete(&state.db).await?;
214 Ok(SuccessResponse::ok(queue.into()))
215}
216
217pub fn routes() -> OpenApiRouter<AppState> {
218 OpenApiRouter::new()
219 .routes(routes!(owned))
220 .routes(routes!(create))
221 .routes(routes!(change_name))
222 .routes(routes!(change_owner))
223 .routes(routes!(delete))
224}