feat: v2 rewrite — hexagonal arch, ActivityPub federation, NATS, deployment-ready (#1)
This commit was merged in pull request #1.
This commit is contained in:
10
crates/api-types/Cargo.toml
Normal file
10
crates/api-types/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "api-types"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
utoipa = { version = "5.5.0", features = ["uuid", "chrono"] }
|
||||
2
crates/api-types/src/lib.rs
Normal file
2
crates/api-types/src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod requests;
|
||||
pub mod responses;
|
||||
92
crates/api-types/src/requests.rs
Normal file
92
crates/api-types/src/requests.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use serde::Deserialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub const DEFAULT_PAGE: u64 = 1;
|
||||
pub const DEFAULT_PER_PAGE: u64 = 20;
|
||||
pub const MAX_PER_PAGE: u64 = 100;
|
||||
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RegisterRequest {
|
||||
/// Username (1-32 chars, alphanumeric + underscore)
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LoginRequest {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateThoughtRequest {
|
||||
/// Up to 128 characters
|
||||
pub content: String,
|
||||
pub in_reply_to_id: Option<Uuid>,
|
||||
/// One of: "public", "followers", "unlisted", "direct"
|
||||
pub visibility: Option<String>,
|
||||
pub content_warning: Option<String>,
|
||||
pub sensitive: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditThoughtRequest {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateProfileRequest {
|
||||
pub display_name: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub header_url: Option<String>,
|
||||
pub custom_css: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetTopFriendsRequest {
|
||||
/// Ordered list of user UUIDs, max 8
|
||||
pub friend_ids: Vec<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateApiKeyRequest {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, utoipa::IntoParams)]
|
||||
pub struct PaginationQuery {
|
||||
pub page: Option<u64>,
|
||||
pub per_page: Option<u64>,
|
||||
}
|
||||
|
||||
impl PaginationQuery {
|
||||
pub fn page(&self) -> u64 {
|
||||
self.page.unwrap_or(DEFAULT_PAGE).max(DEFAULT_PAGE)
|
||||
}
|
||||
|
||||
pub fn per_page(&self) -> u64 {
|
||||
self.per_page.unwrap_or(DEFAULT_PER_PAGE).min(MAX_PER_PAGE)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, utoipa::IntoParams)]
|
||||
pub struct SearchQuery {
|
||||
pub q: String,
|
||||
pub page: Option<u64>,
|
||||
pub per_page: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NotificationUpdateRequest {
|
||||
pub read: bool,
|
||||
}
|
||||
137
crates/api-types/src/responses.rs
Normal file
137
crates/api-types/src/responses.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthResponse {
|
||||
pub token: String,
|
||||
pub user: UserResponse,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserResponse {
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
pub display_name: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub header_url: Option<String>,
|
||||
pub custom_css: Option<String>,
|
||||
pub local: bool,
|
||||
pub is_followed_by_viewer: bool,
|
||||
#[serde(rename = "joinedAt")]
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ThoughtResponse {
|
||||
pub id: Uuid,
|
||||
pub content: String,
|
||||
pub author: UserResponse,
|
||||
#[serde(rename = "replyToId")]
|
||||
pub in_reply_to_id: Option<Uuid>,
|
||||
#[serde(rename = "replyToUrl", skip_serializing_if = "Option::is_none")]
|
||||
pub in_reply_to_url: Option<String>,
|
||||
pub visibility: String,
|
||||
pub content_warning: Option<String>,
|
||||
pub sensitive: bool,
|
||||
pub like_count: i64,
|
||||
pub boost_count: i64,
|
||||
pub reply_count: i64,
|
||||
pub liked_by_viewer: bool,
|
||||
pub boosted_by_viewer: bool,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PagedResponse<T: Serialize + utoipa::ToSchema> {
|
||||
pub items: Vec<T>,
|
||||
pub total: i64,
|
||||
pub page: u64,
|
||||
pub per_page: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ApiKeyResponse {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NotificationResponse {
|
||||
pub id: Uuid,
|
||||
pub notification_type: String,
|
||||
pub from_user: Option<UserResponse>,
|
||||
pub thought_id: Option<Uuid>,
|
||||
pub read: bool,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TopFriendsResponse {
|
||||
pub top_friends: Vec<UserResponse>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ErrorResponse {
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreatedApiKeyResponse {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
/// Raw API key — shown only once at creation
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileField {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoteActorResponse {
|
||||
pub handle: String,
|
||||
pub display_name: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub url: String,
|
||||
pub bio: Option<String>,
|
||||
pub banner_url: Option<String>,
|
||||
pub also_known_as: Option<String>,
|
||||
pub outbox_url: Option<String>,
|
||||
pub followers_url: Option<String>,
|
||||
pub following_url: Option<String>,
|
||||
pub attachment: Vec<ProfileField>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ActorConnectionResponse {
|
||||
pub handle: String,
|
||||
pub display_name: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ActorConnectionPageResponse {
|
||||
pub items: Vec<ActorConnectionResponse>,
|
||||
pub page: u32,
|
||||
pub has_more: bool,
|
||||
}
|
||||
Reference in New Issue
Block a user