Skip to content
Clone

refactor(api): phase4d avatar-in-images, suspension merge, memes split, read-state verb unification, WS MarkRead removed, invite accept payload unified

ZhenYi committed
parent b5cb919 commit 6e15d2f Browse files

31 files changed

+171 −291

File tree 31
Deleted lib/auth/src/handlers/active_suspension.rs
@@ -1,28 +0,0 @@
1use axum::extract::State;
2use export::rest::AppResponseData;
3use session::Session;
4
5use crate::errors::ApiError;
6use crate::services::AuthService;
7use crate::services::suspensions::SuspensionDto;
8use session::share::session::require_uid;
9
10#[utoipa::path(
11 get,
12 path = "/me/suspension",
13 tag = "suspension",
14 responses(
15 (status = 200, description = "active suspension for the current user, none when absent", body = AppResponseData<Option<SuspensionDto>>)
16 )
17)]
18pub async fn active_suspension(
19 State(service): State<AuthService>,
20 session: Session,
21) -> Result<AppResponseData<Option<SuspensionDto>>, ApiError> {
22 let uid = require_uid(&session)?;
23 let resp = service
24 .active_suspension(uid)
25 .await
26 .map_err(ApiError::from)?;
27 Ok(AppResponseData::ok(resp))
28}
Deleted lib/auth/src/handlers/mark_all_notifications_read.rs
@@ -1,27 +0,0 @@
1use axum::extract::State;
2use export::rest::{AppResponseData, NoData};
3use session::Session;
4
5use crate::errors::ApiError;
6use crate::services::AuthService;
7use session::share::session::require_uid;
8
9#[utoipa::path(
10 put,
11 path = "/me/notifications/read-state",
12 tag = "notifications",
13 responses(
14 (status = 200, description = "all notifications marked as read", body = AppResponseData<NoData>)
15 )
16)]
17pub async fn mark_all_notifications_read(
18 State(service): State<AuthService>,
19 session: Session,
20) -> Result<AppResponseData<NoData>, ApiError> {
21 let uid = require_uid(&session)?;
22 service
23 .mark_all_notifications_read(uid)
24 .await
25 .map_err(ApiError::from)?;
26 Ok(AppResponseData::empty())
27}
Deleted lib/auth/src/handlers/mark_notification_read.rs
@@ -1,32 +0,0 @@
1use axum::extract::{Path, State};
2use export::rest::{AppResponseData, NoData};
3use session::Session;
4use uuid::Uuid;
5
6use crate::errors::ApiError;
7use crate::services::AuthService;
8use session::share::session::require_uid;
9
10#[utoipa::path(
11 put,
12 path = "/me/notifications/{notification_id}/read",
13 tag = "notifications",
14 params(
15 ("notification_id" = Uuid, Path, description = "notification id")
16 ),
17 responses(
18 (status = 200, description = "notification marked as read", body = AppResponseData<NoData>)
19 )
20)]
21pub async fn mark_notification_read(
22 State(service): State<AuthService>,
23 session: Session,
24 Path(notification_id): Path<Uuid>,
25) -> Result<AppResponseData<NoData>, ApiError> {
26 let uid = require_uid(&session)?;
27 service
28 .mark_notification_read(uid, notification_id)
29 .await
30 .map_err(ApiError::from)?;
31 Ok(AppResponseData::empty())
32}
Modified lib/auth/src/handlers/mod.rs View lib/auth/src/handlers/mod.rs
@@ -1,5 +1,4 @@
11pub mod accept_agreement;
2pub mod active_suspension;
32pub mod add_ssh_key;
43pub mod agreement_page;
54pub mod cancel_deletion;
@@ -23,11 +22,9 @@
2322pub mod login_2fa;
2423pub mod logout;
2524pub mod devices;
26pub mod mark_all_notifications_read;
2725pub mod mark_notifications_read;
2826pub mod rotate_access_key;
2927pub mod update_access_key;
30pub mod mark_notification_read;
3128pub mod me;
3229pub mod pow_challenge;
3330pub mod reclaim;
Modified lib/auth/src/openapi.rs View lib/auth/src/openapi.rs
@@ -45,7 +45,6 @@
4545 info(title = "gitdata auth api", version = "1.0"),
4646 paths(
4747 handlers::accept_agreement::accept_agreement,
48 handlers::active_suspension::active_suspension,
4948 handlers::add_ssh_key::add_ssh_key,
5049 handlers::cancel_deletion::cancel_deletion,
5150 handlers::cancel_email_change::cancel_email_change,
@@ -75,8 +74,6 @@
7574 handlers::login::login,
7675 handlers::login_2fa::login_2fa,
7776 handlers::logout::logout,
78 handlers::mark_all_notifications_read::mark_all_notifications_read,
79 handlers::mark_notification_read::mark_notification_read,
8077 handlers::mark_notifications_read::mark_notifications_read,
8178 handlers::me::me,
8279 handlers::pow_challenge::pow_challenge,
Modified lib/auth/src/routes.rs View lib/auth/src/routes.rs
@@ -3,7 +3,6 @@
33use axum::routing::{delete, get, patch, post, put};
44
55use crate::handlers::accept_agreement::accept_agreement;
6use crate::handlers::active_suspension::active_suspension;
76use crate::handlers::add_ssh_key::add_ssh_key;
87use crate::handlers::agreement_page::agreement_page;
98use crate::handlers::cancel_deletion::cancel_deletion;
@@ -26,8 +25,6 @@
2625use crate::handlers::login::login;
2726use crate::handlers::login_2fa::login_2fa;
2827use crate::handlers::logout::logout;
29use crate::handlers::mark_all_notifications_read::mark_all_notifications_read;
30use crate::handlers::mark_notification_read::mark_notification_read;
3128use crate::handlers::me::me;
3229use crate::handlers::pow_challenge::pow_challenge;
3330use crate::handlers::reclaim::reclaim;
@@ -113,18 +110,10 @@
113110 get(unread_notifications_count),
114111 )
115112 .route(
116 "/me/notifications/read-state",
117 put(mark_all_notifications_read),
118 )
119 .route(
120113 "/me/notifications/{notification_id}",
121114 delete(delete_notification),
122115 )
123116 .route(
124 "/me/notifications/{notification_id}/read",
125 put(mark_notification_read),
126 )
127 .route(
128117 "/me/notifications/read",
129118 put(crate::handlers::mark_notifications_read::mark_notifications_read),
130119 )
@@ -144,7 +133,6 @@
144133 .post(request_deletion)
145134 .delete(cancel_deletion),
146135 )
147 .route("/me/suspension", get(active_suspension))
148136 .route_layer(middleware::from_fn_with_state(service, auth_guard));
149137
150138 public.merge(authed)
Modified lib/chat/src/handlers/read_state.rs View lib/chat/src/handlers/read_state.rs
@@ -70,8 +70,9 @@
7070 Ok(AppResponseData::ok(page))
7171}
7272
73#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams, utoipa::ToSchema)]
74pub struct MarkThreadReadQuery {
73#[derive(Debug, Clone, serde::Deserialize, utoipa::ToSchema)]
74pub struct MarkThreadReadCmd {
75 #[serde(default)]
7576 pub message_id: Option<Uuid>,
7677}
7778
@@ -79,7 +80,8 @@
7980 put,
8081 path = "/chat/threads/{thread_id}/read-state",
8182 tag = "chat",
82 params(("thread_id" = Uuid, Path, description = "thread id"), MarkThreadReadQuery),
83 params(("thread_id" = Uuid, Path, description = "thread id")),
84 request_body = MarkThreadReadCmd,
8385 responses(
8486 (status = 200, description = "thread marked read", body = AppResponseData<NoData>),
8587 (status = 404, description = "not found", body = AppResponseData<NoData>)
@@ -89,7 +91,7 @@
8991 State(service): State<ChatService>,
9092 session: Session,
9193 Path(thread_id): Path<Uuid>,
92 Query(q): Query<MarkThreadReadQuery>,
94 Json(q): Json<MarkThreadReadCmd>,
9395) -> Result<AppResponseData<NoData>, ApiError> {
9496 let uid = require_uid(&session)?;
9597 service
Modified lib/chat/src/ws/frames.rs View lib/chat/src/ws/frames.rs
@@ -18,13 +18,6 @@
1818 room_id: Uuid,
1919 after_seq: i64,
2020 },
21 MarkRead {
22 room_id: Uuid,
23 #[serde(default)]
24 message_id: Option<Uuid>,
25 #[serde(default)]
26 seq: Option<i64>,
27 },
2821 Typing {
2922 room_id: Uuid,
3023 #[serde(default)]
Modified lib/chat/src/ws/mod.rs View lib/chat/src/ws/mod.rs
@@ -15,7 +15,6 @@
1515
1616use crate::errors::ChatError;
1717use crate::services::messages::ListMessageQuery;
18use crate::services::read_state::MarkReadCmd;
1918use crate::services::service::ChatService;
2019use crate::share::realtime::RoomEvent;
2120use session::SessionGuard;
@@ -529,15 +528,6 @@
529528 };
530529 let _ = send_frame(sink, &frame).await;
531530 }
532 }
533 ClientFrame::MarkRead {
534 room_id,
535 message_id,
536 seq,
537 } => {
538 let _ = service
539 .mark_read(uid, room_id, MarkReadCmd { message_id, seq })
540 .await;
541531 }
542532 ClientFrame::Typing { room_id, thread_id } => {
543533 // Coarse throttle: at most one typing broadcast per 3s per socket.
Modified lib/chat/src/openapi.rs View lib/chat/src/openapi.rs
@@ -135,7 +135,7 @@
135135 crate::services::read_state::ReadStateDto,
136136 crate::services::read_state::SpaceReadStatePage,
137137 crate::services::read_state::MarkReadCmd,
138 crate::handlers::read_state::MarkThreadReadQuery,
138 crate::handlers::read_state::MarkThreadReadCmd,
139139 crate::services::attachments::AttachmentDto,
140140 crate::services::attachments::CreateAttachmentCmd,
141141 crate::services::attachments::UploadIntentDto,
Added lib/issues/src/handlers/issue_users/mark_issue_read.rs View lib/issues/src/handlers/issue_users/mark_issue_read.rs
@@ -0,0 +1,51 @@
1use axum::extract::{Json, Path, State};
2use export::rest::{AppResponseData, NoData};
3use session::Session;
4
5use crate::errors::ApiError;
6use crate::services::issue_users::{IssueUserDto, UpdateIssueUserCmd};
7use crate::services::issues::IssuePath;
8use crate::services::service::IssueService;
9use session::share::session::require_uid;
10
11#[derive(Debug, Clone, serde::Deserialize, utoipa::ToSchema)]
12pub struct MarkIssueReadCmd {
13 pub read: bool,
14}
15
16#[utoipa::path(
17 put,
18 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/read-state",
19 tag = "issues",
20 params(IssuePath),
21 request_body = MarkIssueReadCmd,
22 responses(
23 (status = 200, description = "mark issue read/unread for caller", body = AppResponseData<IssueUserDto>),
24 (status = 403, description = "forbidden", body = AppResponseData<NoData>),
25 (status = 404, description = "not found", body = AppResponseData<NoData>)
26 )
27)]
28pub async fn mark_issue_read(
29 State(service): State<IssueService>,
30 session: Session,
31 Path(path): Path<IssuePath>,
32 Json(body): Json<MarkIssueReadCmd>,
33) -> Result<AppResponseData<IssueUserDto>, ApiError> {
34 let caller = require_uid(&session)?;
35 let issue_id = service
36 .resolve_issue_ref(caller, &path.space_name, path.board_id, &path.issue_id)
37 .await?;
38 let dto = service
39 .update_issue_user(
40 caller,
41 &path.space_name,
42 path.board_id,
43 issue_id,
44 caller,
45 UpdateIssueUserCmd {
46 is_read: Some(body.read),
47 },
48 )
49 .await?;
50 Ok(AppResponseData::ok(dto))
51}
Modified lib/issues/src/handlers/issue_users/mod.rs View lib/issues/src/handlers/issue_users/mod.rs
@@ -1,2 +1,2 @@
11pub mod list_issue_users;
2pub mod update_issue_user;
2pub mod mark_issue_read;
Deleted lib/issues/src/handlers/issue_users/update_issue_user.rs
@@ -1,41 +0,0 @@
1use axum::extract::{Json, Path, State};
2use export::rest::{AppResponseData, NoData};
3use session::Session;
4
5use crate::errors::ApiError;
6use crate::services::issue_users::{IssueUserDto, IssueUserPath, UpdateIssueUserCmd};
7use crate::services::service::IssueService;
8use session::share::session::require_uid;
9
10#[utoipa::path(
11 patch,
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/users/{user_id}",
13 tag = "issues",
14 params(IssueUserPath),
15 request_body = UpdateIssueUserCmd,
16 responses(
17 (status = 200, description = "update issue user state", body = AppResponseData<IssueUserDto>),
18 (status = 403, description = "forbidden", body = AppResponseData<NoData>),
19 (status = 404, description = "not found", body = AppResponseData<NoData>)
20 )
21)]
22pub async fn update_issue_user(
23 State(service): State<IssueService>,
24 session: Session,
25 Path(path): Path<IssueUserPath>,
26 Json(cmd): Json<UpdateIssueUserCmd>,
27) -> Result<AppResponseData<IssueUserDto>, ApiError> {
28 let caller = require_uid(&session)?;
29 let issue_id = service.resolve_issue_ref(caller, &path.space_name, path.board_id, &path.issue_id).await?;
30 let dto = service
31 .update_issue_user(
32 caller,
33 &path.space_name,
34 path.board_id,
35 issue_id,
36 path.user_id,
37 cmd,
38 )
39 .await?;
40 Ok(AppResponseData::ok(dto))
41}
Modified lib/issues/src/openapi.rs View lib/issues/src/openapi.rs
@@ -64,7 +64,7 @@
6464 crate::handlers::tracked_time::update_tracked_time::update_tracked_time,
6565 crate::handlers::tracked_time::delete_tracked_time::delete_tracked_time,
6666 crate::handlers::issue_users::list_issue_users::list_issue_users,
67 crate::handlers::issue_users::update_issue_user::update_issue_user,
67 crate::handlers::issue_users::mark_issue_read::mark_issue_read,
6868 crate::handlers::repo_relations::list_repo_relations::list_repo_relations,
6969 crate::handlers::repo_relations::create_repo_relation::create_repo_relation,
7070 crate::handlers::repo_relations::delete_repo_relation::delete_repo_relation,
Modified lib/issues/src/routes.rs View lib/issues/src/routes.rs
@@ -17,7 +17,7 @@
1717use crate::handlers::dependencies::list_dependencies::list_dependencies;
1818use crate::handlers::events::list_events::list_events;
1919use crate::handlers::issue_users::list_issue_users::list_issue_users;
20use crate::handlers::issue_users::update_issue_user::update_issue_user;
20use crate::handlers::issue_users::mark_issue_read::mark_issue_read;
2121use crate::handlers::issues::close_issue::close_issue;
2222use crate::handlers::issues::create_issue::create_issue;
2323use crate::handlers::issues::delete_issue::delete_issue;
@@ -213,8 +213,8 @@
213213 get(list_issue_users),
214214 )
215215 .route(
216 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/users/{user_id}",
217 patch(update_issue_user),
216 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/read-state",
217 put(mark_issue_read),
218218 )
219219 .route(
220220 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/repos",
Deleted lib/space/src/handlers/delete_space_avatar.rs
@@ -1,29 +0,0 @@
1use axum::extract::{Path, State};
2use export::rest::{AppResponseData, NoData};
3use session::Session;
4
5use crate::errors::ApiError;
6use crate::services::service::SpaceService;
7use crate::services::spaces::SpaceNamePath;
8use session::share::session::require_uid;
9
10#[utoipa::path(
11 delete,
12 path = "/spaces/{space_name}/avatar",
13 tag = "space",
14 params(SpaceNamePath),
15 responses(
16 (status = 200, description = "space avatar deleted", body = AppResponseData<NoData>),
17 (status = 403, description = "forbidden", body = AppResponseData<NoData>),
18 (status = 404, description = "space not found", body = AppResponseData<NoData>)
19 )
20)]
21pub async fn delete_space_avatar(
22 State(service): State<SpaceService>,
23 session: Session,
24 Path(path): Path<SpaceNamePath>,
25) -> Result<AppResponseData<NoData>, ApiError> {
26 let uid = require_uid(&session)?;
27 service.delete_space_avatar(uid, &path.space_name).await?;
28 Ok(AppResponseData::empty())
29}
Deleted lib/space/src/handlers/get_space_avatar.rs
@@ -1,28 +0,0 @@
1use axum::extract::{Path, State};
2use export::rest::{AppResponseData, NoData};
3use session::{Session, SessionUser};
4
5use crate::errors::ApiError;
6use crate::services::avatar::SpaceAvatarDto;
7use crate::services::service::SpaceService;
8use crate::services::spaces::SpaceNamePath;
9
10#[utoipa::path(
11 get,
12 path = "/spaces/{space_name}/avatar",
13 tag = "space",
14 params(SpaceNamePath),
15 responses(
16 (status = 200, description = "get space avatar", body = AppResponseData<Option<SpaceAvatarDto>>),
17 (status = 404, description = "space not found", body = AppResponseData<NoData>)
18 )
19)]
20pub async fn get_space_avatar(
21 State(service): State<SpaceService>,
22 session: Session,
23 Path(path): Path<SpaceNamePath>,
24) -> Result<AppResponseData<Option<SpaceAvatarDto>>, ApiError> {
25 let caller = session.uid();
26 let avatar = service.get_space_avatar(caller, &path.space_name).await?;
27 Ok(AppResponseData::ok(avatar))
28}
Modified lib/space/src/handlers/mod.rs View lib/space/src/handlers/mod.rs
@@ -12,13 +12,11 @@
1212pub mod delete_join_rule;
1313pub mod delete_room;
1414pub mod delete_space;
15pub mod delete_space_avatar;
1615pub mod delete_space_image;
1716pub mod get_ban;
1817pub mod get_group;
1918pub mod get_member;
2019pub mod get_space;
21pub mod get_space_avatar;
2220pub mod get_space_dir_tree;
2321pub mod get_space_image;
2422pub mod join_group_self;
@@ -47,6 +45,5 @@
4745pub mod update_join_rule;
4846pub mod update_member;
4947pub mod update_space;
50pub mod upload_space_avatar;
5148pub mod upload_space_image;
5249pub mod self_member;
Deleted lib/space/src/handlers/upload_space_avatar.rs
@@ -1,35 +0,0 @@
1use axum::extract::{Json, Path, State};
2use export::rest::{AppResponseData, NoData};
3use session::Session;
4
5use crate::errors::ApiError;
6use crate::services::avatar::{SpaceAvatarDto, UploadSpaceAvatarCmd};
7use crate::services::service::SpaceService;
8use crate::services::spaces::SpaceNamePath;
9use session::share::session::require_uid;
10
11#[utoipa::path(
12 put,
13 path = "/spaces/{space_name}/avatar",
14 tag = "space",
15 params(SpaceNamePath),
16 request_body = UploadSpaceAvatarCmd,
17 responses(
18 (status = 200, description = "space avatar uploaded", body = AppResponseData<SpaceAvatarDto>),
19 (status = 400, description = "bad request", body = AppResponseData<NoData>),
20 (status = 403, description = "forbidden", body = AppResponseData<NoData>),
21 (status = 404, description = "space not found", body = AppResponseData<NoData>)
22 )
23)]
24pub async fn upload_space_avatar(
25 State(service): State<SpaceService>,
26 session: Session,
27 Path(path): Path<SpaceNamePath>,
28 Json(cmd): Json<UploadSpaceAvatarCmd>,
29) -> Result<AppResponseData<SpaceAvatarDto>, ApiError> {
30 let uid = require_uid(&session)?;
31 let avatar = service
32 .upload_space_avatar(uid, &path.space_name, cmd)
33 .await?;
34 Ok(AppResponseData::ok(avatar))
35}
Modified lib/space/src/services/images.rs View lib/space/src/services/images.rs
@@ -19,6 +19,7 @@
1919#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
2020#[serde(rename_all = "snake_case")]
2121pub enum SpaceImageKind {
22 Avatar,
2223 Banner,
2324 InviteBanner,
2425 EmbedSplash,
@@ -27,6 +28,7 @@
2728impl SpaceImageKind {
2829 pub fn as_str(&self) -> &'static str {
2930 match self {
31 Self::Avatar => "avatar",
3032 Self::Banner => "banner",
3133 Self::InviteBanner => "invite_banner",
3234 Self::EmbedSplash => "embed_splash",
@@ -119,6 +121,9 @@
119121
120122 let mut am: spaces::ActiveModel = space.into();
121123 match kind {
124 SpaceImageKind::Avatar => {
125 am.avatar_url = Set(Some(url.clone()));
126 }
122127 SpaceImageKind::Banner => {
123128 am.banner_url = Set(Some(url.clone()));
124129 am.banner_width = Set(cmd.width);
@@ -184,6 +189,7 @@
184189
185190 let space_id = space.id;
186191 let old_url = match kind {
192 SpaceImageKind::Avatar => space.avatar_url.clone(),
187193 SpaceImageKind::Banner => space.banner_url.clone(),
188194 SpaceImageKind::InviteBanner => space.invite_banner_url.clone(),
189195 SpaceImageKind::EmbedSplash => space.embed_splash_url.clone(),
@@ -192,6 +198,9 @@
192198 let now = OffsetDateTime::now_utc();
193199 let mut am: spaces::ActiveModel = space.into();
194200 match kind {
201 SpaceImageKind::Avatar => {
202 am.avatar_url = Set(None);
203 }
195204 SpaceImageKind::Banner => {
196205 am.banner_url = Set(None);
197206 am.banner_width = Set(None);
@@ -252,6 +261,7 @@
252261 }
253262
254263 let (url_opt, width, height) = match kind {
264 SpaceImageKind::Avatar => (space.avatar_url.clone(), None, None),
255265 SpaceImageKind::Banner => (space.banner_url, space.banner_width, space.banner_height),
256266 SpaceImageKind::InviteBanner => (
257267 space.invite_banner_url,
Modified lib/space/src/services/invites.rs View lib/space/src/services/invites.rs
@@ -52,7 +52,7 @@
5252
5353#[derive(Debug, Clone, Deserialize, ToSchema)]
5454pub struct AcceptInviteCmd {
55 pub accept_code: String,
55 pub code: String,
5656}
5757
5858#[derive(Debug, Clone, Serialize, ToSchema)]
@@ -360,7 +360,7 @@
360360 ));
361361 }
362362 let invite = space_invite::Entity::find()
363 .filter(space_invite::Column::AcceptCode.eq(&cmd.accept_code))
363 .filter(space_invite::Column::AcceptCode.eq(&cmd.code))
364364 .one(self.db().read())
365365 .await?
366366 .ok_or(SpaceError::NotFound)?;
Modified lib/space/src/services/spaces.rs View lib/space/src/services/spaces.rs
@@ -98,8 +98,6 @@
9898 #[serde(default)]
9999 pub description: Option<Option<String>>,
100100 #[serde(default)]
101 pub avatar_url: Option<Option<String>>,
102 #[serde(default)]
103101 pub is_private: Option<bool>,
104102 #[serde(default)]
105103 pub allow_join: Option<bool>,
@@ -398,9 +396,7 @@
398396 am.description = Set(desc);
399397 }
400398
401 if let Some(avatar) = cmd.avatar_url {
402 am.avatar_url = Set(avatar);
403 }
399
404400 if let Some(is_private) = cmd.is_private {
405401 am.is_private = Set(is_private);
406402 }
Modified lib/space/src/openapi.rs View lib/space/src/openapi.rs
@@ -6,14 +6,11 @@
66 paths(
77 crate::handlers::get_space::get_space,
88 crate::handlers::get_group::get_group,
9 crate::handlers::get_space_avatar::get_space_avatar,
109 crate::handlers::get_space_image::get_space_image,
1110 crate::handlers::create_space::create_space,
1211 crate::handlers::list_spaces::list_spaces,
1312 crate::handlers::update_space::update_space,
1413 crate::handlers::delete_space::delete_space,
15 crate::handlers::upload_space_avatar::upload_space_avatar,
16 crate::handlers::delete_space_avatar::delete_space_avatar,
1714 crate::handlers::upload_space_image::upload_space_image,
1815 crate::handlers::delete_space_image::delete_space_image,
1916 crate::handlers::create_dir_node::create_dir_node,
Modified lib/space/src/routes.rs View lib/space/src/routes.rs
@@ -13,13 +13,11 @@
1313use crate::handlers::delete_group::delete_group;
1414use crate::handlers::delete_join_rule::delete_join_rule;
1515use crate::handlers::delete_space::delete_space;
16use crate::handlers::delete_space_avatar::delete_space_avatar;
1716use crate::handlers::delete_space_image::delete_space_image;
1817use crate::handlers::get_ban::get_ban;
1918use crate::handlers::get_group::get_group;
2019use crate::handlers::get_member::get_member;
2120use crate::handlers::get_space::get_space;
22use crate::handlers::get_space_avatar::get_space_avatar;
2321use crate::handlers::get_space_dir_tree::get_space_dir_tree;
2422use crate::handlers::get_space_image::get_space_image;
2523use crate::handlers::join_group_self::join_group_self;
@@ -48,7 +46,6 @@
4846use crate::handlers::update_join_rule::update_join_rule;
4947use crate::handlers::update_member::update_member;
5048use crate::handlers::update_space::update_space;
51use crate::handlers::upload_space_avatar::upload_space_avatar;
5249use crate::handlers::upload_space_image::upload_space_image;
5350use crate::services::service::SpaceService;
5451use crate::share::guard::space_guard;
@@ -57,7 +54,6 @@
5754 let public: Router<SpaceService> = Router::new()
5855 .route("/spaces/{space_name}", get(get_space))
5956 .route("/spaces/{space_name}/groups/{group_id}", get(get_group))
60 .route("/spaces/{space_name}/avatar", get(get_space_avatar))
6157 .route("/spaces/{space_name}/images/{kind}", get(get_space_image));
6258
6359 let protected = Router::new()
@@ -66,10 +62,6 @@
6662 .route(
6763 "/spaces/{space_name}",
6864 patch(update_space).delete(delete_space),
69 )
70 .route(
71 "/spaces/{space_name}/avatar",
72 put(upload_space_avatar).delete(delete_space_avatar),
7365 )
7466 .route(
7567 "/spaces/{space_name}/images/{kind}",
Modified lib/user/src/handlers/create_meme.rs View lib/user/src/handlers/create_meme.rs
@@ -14,7 +14,7 @@
1414 tag = "user",
1515 request_body = UpsertMemeCmd,
1616 responses(
17 (status = 200, description = "meme created or updated", body = AppResponseData<MemeDto>)
17 (status = 200, description = "meme created", body = AppResponseData<MemeDto>)
1818 )
1919)]
2020pub async fn create_meme(
@@ -23,6 +23,6 @@
2323 Json(cmd): Json<UpsertMemeCmd>,
2424) -> Result<AppResponseData<MemeDto>, ApiError> {
2525 let uid = require_uid(&session)?;
26 let meme = service.upsert_meme(uid, cmd).await?;
26 let meme = service.create_meme(uid, cmd).await?;
2727 Ok(AppResponseData::ok(meme))
2828}
Modified lib/user/src/handlers/mod.rs View lib/user/src/handlers/mod.rs
@@ -22,6 +22,7 @@
2222pub mod save_message;
2323pub mod set_primary_email;
2424pub mod unsave_message;
25pub mod update_meme;
2526pub mod upload_avatar;
2627pub mod upsert_push_subscription;
2728pub mod verify_add_email;
Added lib/user/src/handlers/update_meme.rs View lib/user/src/handlers/update_meme.rs
@@ -0,0 +1,32 @@
1use axum::Json;
2use axum::extract::{Path, State};
3use export::rest::{AppResponseData, NoData};
4use session::Session;
5use uuid::Uuid;
6
7use crate::errors::ApiError;
8use crate::services::profile::{MemeDto, UpsertMemeCmd};
9use crate::services::service::UserService;
10use session::share::session::require_uid;
11
12#[utoipa::path(
13 put,
14 path = "/me/memes/{meme_id}",
15 tag = "user",
16 params(("meme_id" = Uuid, Path)),
17 request_body = UpsertMemeCmd,
18 responses(
19 (status = 200, description = "meme updated", body = AppResponseData<MemeDto>),
20 (status = 404, description = "not found", body = AppResponseData<NoData>)
21 )
22)]
23pub async fn update_meme(
24 State(service): State<UserService>,
25 session: Session,
26 Path(meme_id): Path<Uuid>,
27 Json(cmd): Json<UpsertMemeCmd>,
28) -> Result<AppResponseData<MemeDto>, ApiError> {
29 let uid = require_uid(&session)?;
30 let meme = service.update_meme(uid, meme_id, cmd).await?;
31 Ok(AppResponseData::ok(meme))
32}
Modified lib/user/src/services/profile.rs View lib/user/src/services/profile.rs
@@ -198,7 +198,7 @@
198198 Ok(rows.into_iter().map(MemeDto::from).collect())
199199 }
200200
201 pub async fn upsert_meme(&self, uid: Uuid, cmd: UpsertMemeCmd) -> Result<MemeDto, UserError> {
201 fn validate_meme_cmd(cmd: &UpsertMemeCmd) -> Result<(), UserError> {
202202 if cmd.name.is_empty() || cmd.name.len() > MAX_MEME_NAME {
203203 return Err(UserError::BadRequest(format!(
204204 "meme name length must be 1..={MAX_MEME_NAME}"
@@ -214,7 +214,53 @@
214214 "url length must be <={MAX_MEME_URL}"
215215 )));
216216 }
217 Ok(())
218 }
219
220 pub async fn create_meme(&self, uid: Uuid, cmd: UpsertMemeCmd) -> Result<MemeDto, UserError> {
221 Self::validate_meme_cmd(&cmd)?;
222 let existing = user_favorite_memes::Entity::find()
223 .filter(user_favorite_memes::Column::UserId.eq(uid))
224 .filter(user_favorite_memes::Column::Name.eq(&cmd.name))
225 .one(self.db().read())
226 .await?;
227 if existing.is_some() {
228 return Err(UserError::Conflict("meme name already exists".to_string()));
229 }
230 let model = self.upsert_meme_row(uid, cmd).await?;
231 Ok(MemeDto::from(model))
232 }
233
234 pub async fn update_meme(
235 &self,
236 uid: Uuid,
237 meme_id: Uuid,
238 cmd: UpsertMemeCmd,
239 ) -> Result<MemeDto, UserError> {
240 Self::validate_meme_cmd(&cmd)?;
241 let row = user_favorite_memes::Entity::find_by_id(meme_id)
242 .one(self.db().read())
243 .await?
244 .ok_or(UserError::NotFound)?;
245 if row.user_id != uid {
246 return Err(UserError::Forbidden);
247 }
248 let mut am: user_favorite_memes::ActiveModel = row.into();
249 am.name = Set(cmd.name);
250 am.content_type = Set(cmd.content_type);
251 am.url = Set(cmd.url);
252 am.size = Set(cmd.size);
253 am.width = Set(cmd.width);
254 am.height = Set(cmd.height);
255 let model = am.update(self.db().write()).await?;
256 Ok(MemeDto::from(model))
257 }
217258
259 async fn upsert_meme_row(
260 &self,
261 uid: Uuid,
262 cmd: UpsertMemeCmd,
263 ) -> Result<user_favorite_memes::Model, UserError> {
218264 let existing = user_favorite_memes::Entity::find()
219265 .filter(user_favorite_memes::Column::UserId.eq(uid))
220266 .filter(user_favorite_memes::Column::Name.eq(&cmd.name))
@@ -248,7 +294,7 @@
248294 }
249295 };
250296
251 Ok(MemeDto::from(model))
297 Ok(model)
252298 }
253299
254300 pub async fn delete_meme(&self, uid: Uuid, meme_id: Uuid) -> Result<(), UserError> {
Modified lib/user/src/services/suspensions.rs View lib/user/src/services/suspensions.rs
@@ -36,6 +36,7 @@
3636#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
3737pub struct IncludeRevokedQuery {
3838 pub include_revoked: Option<bool>,
39 pub active: Option<bool>,
3940}
4041
4142impl UserService {
@@ -51,6 +52,14 @@
5152 if q.include_revoked != Some(true) {
5253 sel = sel.filter(user_suspensions::Column::RevokedAt.is_null());
5354 }
55 if q.active == Some(true) {
56 let now = time::OffsetDateTime::now_utc();
57 sel = sel.filter(
58 sea_orm::Condition::any()
59 .add(user_suspensions::Column::EndsAt.gt(now))
60 .add(user_suspensions::Column::EndsAt.is_null()),
61 );
62 }
5463
5564 let rows = sel.all(self.db().read()).await?;
5665 Ok(rows.into_iter().map(SuspensionDto::from).collect())
Modified lib/user/src/openapi.rs View lib/user/src/openapi.rs
@@ -30,6 +30,7 @@
3030 handlers::my_spaces::my_spaces,
3131 handlers::list_memes::list_memes,
3232 handlers::create_meme::create_meme,
33 handlers::update_meme::update_meme,
3334 handlers::delete_meme::delete_meme,
3435 handlers::get_profile_detail::get_profile_detail,
3536 handlers::list_emails::list_emails,
Modified lib/user/src/routes.rs View lib/user/src/routes.rs
@@ -1,5 +1,5 @@
11use axum::Router;
2use axum::routing::{delete, get, patch, post};
2use axum::routing::{delete, get, patch, post, put};
33
44use crate::handlers::add_email::add_email;
55use crate::handlers::create_meme::create_meme;
@@ -43,6 +43,7 @@
4343 )
4444 .route("/me/spaces", get(my_spaces))
4545 .route("/me/memes", get(list_memes).post(create_meme))
46 .route("/me/memes/{meme_id}", put(crate::handlers::update_meme::update_meme))
4647 .route("/me/memes/{meme_id}", delete(delete_meme))
4748 .route("/me/emails", get(list_emails).post(add_email))
4849 .route("/me/emails/verify", post(verify_add_email))