From fafaf243c54700a54450638e6abfb113ab9ebff6 Mon Sep 17 00:00:00 2001 From: nullishamy Date: Tue, 6 May 2025 18:36:31 +0100 Subject: [PATCH] fix: misc cleanup --- ferri-cli/src/main.rs | 61 +- ferri-main/src/types/ap.rs | 223 ++++++++ ferri-main/src/types/api.rs | 231 ++++++++ ferri-main/src/types/db.rs | 59 ++ ferri-main/src/types/mod.rs | 541 +----------------- ferri-server/src/endpoints/admin/mod.rs | 10 +- ferri-server/src/endpoints/api/preferences.rs | 22 +- ferri-server/src/endpoints/api/timeline.rs | 50 +- ferri-server/src/endpoints/api/user.rs | 105 +--- ferri-server/src/http_wrapper.rs | 4 - ferri-server/src/lib.rs | 1 - ferri-server/templates/_layout.html | 19 + ferri-server/templates/index.html | 64 ++- 13 files changed, 640 insertions(+), 750 deletions(-) create mode 100644 ferri-main/src/types/ap.rs create mode 100644 ferri-main/src/types/api.rs create mode 100644 ferri-main/src/types/db.rs create mode 100644 ferri-server/templates/_layout.html diff --git a/ferri-cli/src/main.rs b/ferri-cli/src/main.rs index b67ad9e..afbe020 100644 --- a/ferri-cli/src/main.rs +++ b/ferri-cli/src/main.rs @@ -1,3 +1,4 @@ +use main::types::{db, make, ObjectUri, ObjectUuid}; use server::launch; extern crate rocket; @@ -24,6 +25,10 @@ pub fn read_config(path: impl AsRef) -> config::Config { toml::from_str(&content).unwrap() } +fn s(st: &'static str) -> String { + st.to_string() +} + #[rocket::main] async fn main() { let cli = Cli::parse(); @@ -36,42 +41,30 @@ async fn main() { .unwrap(); let mut conn = pool.acquire().await.unwrap(); - sqlx::query!( - r#" - INSERT INTO actor (id, inbox, outbox) - VALUES (?1, ?2, ?3) - "#, - "https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9", - "https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9/inbox", - "https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9/outbox" - ) - .execute(&mut *conn) - .await - .unwrap(); + let actor = db::Actor { + id: ObjectUri(s("https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9")), + inbox: s("https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9/inbox"), + outbox: s("https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9/outbox") + }; - let ts = main::ap::new_ts(); + make::new_actor(actor.clone(), &mut *conn).await.unwrap(); - sqlx::query!( - r#" - INSERT INTO user ( - id, acct, url, remote, username, - actor_id, display_name, created_at, icon_url - ) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9) - "#, - "9b9d497b-2731-435f-a929-e609ca69dac9", - "amy", - "https://ferri.amy.mov/@amy", - false, - "amy", - "https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9", - "amy", - ts, - "https://ferri.amy.mov/assets/pfp.png" - ) - .execute(&mut *conn) - .await - .unwrap(); + let user = db::User { + id: ObjectUuid(s("9b9d497b-2731-435f-a929-e609ca69dac9")), + actor, + username: s("amy"), + display_name: s("amy (display)"), + acct: s("amy"), + remote: false, + url: s("https://ferri.amy.mov/@amy"), + created_at: main::ap::now(), + icon_url: s("https://ferri.amy.mov/assets/pfp.png"), + posts: db::UserPosts { + last_post_at: None + } + }; + + make::new_user(user, &mut *conn).await.unwrap(); } else { let _ = launch(config).launch().await; } diff --git a/ferri-main/src/types/ap.rs b/ferri-main/src/types/ap.rs new file mode 100644 index 0000000..a7a4ef2 --- /dev/null +++ b/ferri-main/src/types/ap.rs @@ -0,0 +1,223 @@ +use super::*; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub enum ActivityType { + Reject, + Create, + Note, + Delete, + Undo, + Accept, + Announce, + Person, + Like, + Follow, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct MinimalActivity { + #[serde(flatten)] + pub obj: Object, + + #[serde(rename = "type")] + pub ty: ActivityType, +} + +pub type DeleteActivity = BasicActivity; +pub type LikeActivity = BasicActivity; + +#[derive(Serialize, Deserialize, Debug)] +pub struct BasicActivity { + #[serde(flatten)] + pub obj: Object, + + pub object: String, + pub actor: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct CreateActivity { + #[serde(flatten)] + pub obj: Object, + + #[serde(rename = "type")] + pub ty: ActivityType, + + pub object: Post, + pub actor: String, + pub to: Vec, + pub cc: Vec, + + #[serde(rename = "published")] + pub ts: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct FollowActivity { + #[serde(flatten)] + pub obj: Object, + + #[serde(rename = "type")] + pub ty: ActivityType, + + pub object: String, + pub actor: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct AcceptActivity { + #[serde(flatten)] + pub obj: Object, + + #[serde(rename = "type")] + pub ty: ActivityType, + + pub object: String, + pub actor: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BoostActivity { + #[serde(flatten)] + pub obj: Object, + + #[serde(rename = "type")] + pub ty: ActivityType, + + pub actor: String, + pub published: String, + pub to: Vec, + pub cc: Vec, + pub object: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum PostAttachmentType { + Document +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct PostAttachment { + #[serde(rename = "type")] + pub ty: PostAttachmentType, + + pub media_type: String, + pub url: String, + #[serde(deserialize_with = "deserialize_null_default")] + pub name: String, + + pub summary: Option, + #[serde(default)] + pub sensitive: bool +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Post { + #[serde(flatten)] + pub obj: Object, + + #[serde(rename = "type")] + pub ty: ActivityType, + + #[serde(rename = "published")] + pub ts: String, + pub content: String, + pub to: Vec, + pub cc: Vec, + + pub attachment: Vec, + + #[serde(rename = "attributedTo")] + pub attributed_to: Option, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct Actor { + #[serde(flatten)] + pub obj: Object, + + pub inbox: String, + pub outbox: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub enum IconType { + Image +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct PersonIcon { + #[serde(rename = "type")] + pub ty: IconType, + pub url: String, + + #[serde(default)] + pub summary: String, + #[serde(default)] + pub width: i64, + #[serde(default)] + pub height: i64 +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Person { + #[serde(flatten)] + pub obj: Object, + + #[serde(rename = "type")] + pub ty: ActivityType, + + pub following: String, + pub followers: String, + + pub summary: String, + pub inbox: String, + pub outbox: String, + + pub preferred_username: String, + pub name: String, + + pub public_key: Option, + + pub icon: Option +} + +pub struct RemoteInfo { + pub is_remote: bool, + pub web_url: String, + pub acct: String +} + +impl Person { + pub fn remote_info(&self) -> RemoteInfo { + let url = Url::parse(&self.obj.id.0).unwrap(); + let host = url.host_str().unwrap(); + + let (acct, remote) = if host != "ferri.amy.mov" { + (format!("{}@{}", self.preferred_username, host), true) + } else { + (self.preferred_username.clone(), false) + }; + + let url = format!("https://ferri.amy.mov/{}", acct); + + RemoteInfo { + acct: acct.to_string(), + web_url: url, + is_remote: remote, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct UserKey { + pub id: String, + pub owner: String, + + #[serde(rename = "publicKeyPem")] + pub public_key: String, +} diff --git a/ferri-main/src/types/api.rs b/ferri-main/src/types/api.rs new file mode 100644 index 0000000..81594a5 --- /dev/null +++ b/ferri-main/src/types/api.rs @@ -0,0 +1,231 @@ +use super::*; +use serde::{Deserialize, Serialize}; + +// API will not really use actors so treat them as DB actors +// until we require specificity +pub type Actor = db::Actor; + +#[derive(Serialize, Deserialize, Debug)] +pub struct CredentialApplication { + pub name: String, + pub scopes: String, + pub redirect_uris: Vec, + pub client_id: String, + pub client_secret: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct WebfingerLink { + pub rel: String, + #[serde(rename = "type")] + pub ty: Option, + pub href: Option, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct WebfingerHit { + pub subject: String, + pub aliases: Vec, + pub links: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct StatusAttachment { + pub id: ObjectUuid, + #[serde(rename = "type")] + pub ty: String, + + pub url: String, + pub description: String +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct Status { + pub id: ObjectUuid, + pub created_at: String, + pub in_reply_to_id: Option, + pub in_reply_to_account_id: Option, + pub sensitive: bool, + pub spoiler_text: String, + pub visibility: String, + pub language: String, + pub uri: ObjectUri, + pub url: String, + pub replies_count: i64, + pub reblogs_count: i64, + pub favourites_count: i64, + pub favourited: bool, + pub reblogged: bool, + pub muted: bool, + pub bookmarked: bool, + pub content: String, + pub reblog: Option>, + pub application: Option<()>, + pub account: Account, + pub media_attachments: Vec, + pub mentions: Vec>, + pub tags: Vec>, + pub emojis: Vec>, + pub card: Option<()>, + pub poll: Option<()>, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct Account { + pub id: ObjectUuid, + pub username: String, + pub acct: String, + pub display_name: String, + + pub locked: bool, + pub bot: bool, + + pub created_at: String, + pub attribution_domains: Vec, + + pub note: String, + pub url: String, + + pub avatar: String, + pub avatar_static: String, + pub header: String, + pub header_static: String, + + pub followers_count: i64, + pub following_count: i64, + pub statuses_count: i64, + pub last_status_at: Option, + + pub emojis: Vec, + pub fields: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct Emoji { + pub shortcode: String, + pub url: String, + pub static_url: String, + pub visible_in_picker: bool, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct CustomField { + pub name: String, + pub value: String, + pub verified_at: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Instance { + pub domain: String, + pub title: String, + pub version: String, + pub source_url: String, + pub description: String, + pub thumbnail: Thumbnail, + pub icon: Vec, + pub languages: Vec, + pub configuration: Configuration, + pub registrations: Registrations, + pub contact: Contact, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Configuration { + pub urls: Urls, + pub accounts: Accounts, + pub statuses: Statuses, + pub media_attachments: MediaAttachments, + pub polls: Polls, + pub translation: Translation, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Accounts { + pub max_featured_tags: i64, + pub max_pinned_statuses: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct MediaAttachments { + pub supported_mime_types: Vec, + pub description_limit: i64, + pub image_size_limit: i64, + pub image_matrix_limit: i64, + pub video_size_limit: i64, + pub video_frame_rate_limit: i64, + pub video_matrix_limit: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Polls { + pub max_options: i64, + pub max_characters_per_option: i64, + pub min_expiration: i64, + pub max_expiration: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Statuses { + pub max_characters: i64, + pub max_media_attachments: i64, + pub characters_reserved_per_url: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Translation { + pub enabled: bool, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Urls { + pub streaming: String, + pub about: String, + pub privacy_policy: String, + pub terms_of_service: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Contact { + pub email: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Field { + pub name: String, + pub value: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Icon { + pub src: String, + pub size: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Registrations { + pub enabled: bool, + pub approval_required: bool, + pub reason_required: bool, + pub message: Option, + pub min_age: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Thumbnail { + pub url: String, +} + +#[derive(Serialize, Deserialize)] +pub struct Preferences { + #[serde(rename = "posting:default:visibility")] + pub posting_default_visibility: String, + #[serde(rename = "posting:default:sensitive")] + pub posting_default_sensitive: bool, + #[serde(rename = "posting:default:language")] + pub posting_default_language: Option, + #[serde(rename = "reading:expand:media")] + pub reading_expand_media: String, + #[serde(rename = "reading:expand:spoilers")] + pub reading_expand_spoilers: bool, +} diff --git a/ferri-main/src/types/db.rs b/ferri-main/src/types/db.rs new file mode 100644 index 0000000..2c8ce0e --- /dev/null +++ b/ferri-main/src/types/db.rs @@ -0,0 +1,59 @@ +use super::*; +use chrono::{DateTime, Utc}; + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct Follow { + pub id: ObjectUri, + pub follower: ObjectUri, + pub followed: ObjectUri, +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct Actor { + pub id: ObjectUri, + pub inbox: String, + pub outbox: String, +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct UserPosts { + // User may have no posts + pub last_post_at: Option>, +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct User { + pub id: ObjectUuid, + pub actor: Actor, + pub username: String, + pub display_name: String, + pub acct: String, + pub remote: bool, + pub url: String, + pub created_at: DateTime, + pub icon_url: String, + + pub posts: UserPosts, +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct Attachment { + pub id: ObjectUuid, + pub post_id: ObjectUuid, + pub url: String, + pub media_type: Option, + pub sensitive: bool, + pub alt: Option +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct Post { + pub id: ObjectUuid, + pub uri: ObjectUri, + pub user: User, + pub content: String, + pub created_at: DateTime, + pub boosted_post: Option>, + pub attachments: Vec +} + diff --git a/ferri-main/src/types/mod.rs b/ferri-main/src/types/mod.rs index 35c4a43..61487dd 100644 --- a/ferri-main/src/types/mod.rs +++ b/ferri-main/src/types/mod.rs @@ -7,6 +7,10 @@ pub mod convert; pub mod get; pub mod make; +pub mod db; +pub mod ap; +pub mod api; + fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result where T: Default + Deserialize<'de>, @@ -67,540 +71,3 @@ pub struct Object { pub context: ObjectContext, pub id: ObjectUri, } - -pub mod db { - use super::*; - use chrono::{DateTime, Utc}; - - #[derive(Debug, Eq, PartialEq, Clone)] - pub struct Follow { - pub id: ObjectUri, - pub follower: ObjectUri, - pub followed: ObjectUri, - } - - #[derive(Debug, Eq, PartialEq, Clone)] - pub struct Actor { - pub id: ObjectUri, - pub inbox: String, - pub outbox: String, - } - - #[derive(Debug, Eq, PartialEq, Clone)] - pub struct UserPosts { - // User may have no posts - pub last_post_at: Option>, - } - - #[derive(Debug, Eq, PartialEq, Clone)] - pub struct User { - pub id: ObjectUuid, - pub actor: Actor, - pub username: String, - pub display_name: String, - pub acct: String, - pub remote: bool, - pub url: String, - pub created_at: DateTime, - pub icon_url: String, - - pub posts: UserPosts, - } - - #[derive(Debug, Eq, PartialEq, Clone)] - pub struct Attachment { - pub id: ObjectUuid, - pub post_id: ObjectUuid, - pub url: String, - pub media_type: Option, - pub sensitive: bool, - pub alt: Option - } - - #[derive(Debug, Eq, PartialEq, Clone)] - pub struct Post { - pub id: ObjectUuid, - pub uri: ObjectUri, - pub user: User, - pub content: String, - pub created_at: DateTime, - pub boosted_post: Option>, - pub attachments: Vec - } -} - -pub mod ap { - use super::*; - use serde::{Deserialize, Serialize}; - use url::Url; - - #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] - pub enum ActivityType { - Reject, - Create, - Note, - Delete, - Undo, - Accept, - Announce, - Person, - Like, - Follow, - } - - #[derive(Serialize, Deserialize, Debug)] - pub struct MinimalActivity { - #[serde(flatten)] - pub obj: Object, - - #[serde(rename = "type")] - pub ty: ActivityType, - } - - pub type DeleteActivity = BasicActivity; - pub type LikeActivity = BasicActivity; - - #[derive(Serialize, Deserialize, Debug)] - pub struct BasicActivity { - #[serde(flatten)] - pub obj: Object, - - pub object: String, - pub actor: String, - } - - #[derive(Serialize, Deserialize, Debug)] - pub struct CreateActivity { - #[serde(flatten)] - pub obj: Object, - - #[serde(rename = "type")] - pub ty: ActivityType, - - pub object: Post, - pub actor: String, - pub to: Vec, - pub cc: Vec, - - #[serde(rename = "published")] - pub ts: String, - } - - #[derive(Serialize, Deserialize, Debug)] - pub struct FollowActivity { - #[serde(flatten)] - pub obj: Object, - - #[serde(rename = "type")] - pub ty: ActivityType, - - pub object: String, - pub actor: String, - } - - #[derive(Serialize, Deserialize, Debug)] - pub struct AcceptActivity { - #[serde(flatten)] - pub obj: Object, - - #[serde(rename = "type")] - pub ty: ActivityType, - - pub object: String, - pub actor: String, - } - - #[derive(Serialize, Deserialize, Debug)] - pub struct BoostActivity { - #[serde(flatten)] - pub obj: Object, - - #[serde(rename = "type")] - pub ty: ActivityType, - - pub actor: String, - pub published: String, - pub to: Vec, - pub cc: Vec, - pub object: String, - } - - #[derive(Serialize, Deserialize, Debug)] - pub enum PostAttachmentType { - Document - } - - #[derive(Serialize, Deserialize, Debug)] - #[serde(rename_all = "camelCase")] - pub struct PostAttachment { - #[serde(rename = "type")] - pub ty: PostAttachmentType, - - pub media_type: String, - pub url: String, - #[serde(deserialize_with = "deserialize_null_default")] - pub name: String, - - pub summary: Option, - #[serde(default)] - pub sensitive: bool - } - - #[derive(Serialize, Deserialize, Debug)] - pub struct Post { - #[serde(flatten)] - pub obj: Object, - - #[serde(rename = "type")] - pub ty: ActivityType, - - #[serde(rename = "published")] - pub ts: String, - pub content: String, - pub to: Vec, - pub cc: Vec, - - pub attachment: Vec, - - #[serde(rename = "attributedTo")] - pub attributed_to: Option, - } - - #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] - pub struct Actor { - #[serde(flatten)] - pub obj: Object, - - pub inbox: String, - pub outbox: String, - } - - #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] - pub enum IconType { - Image - } - - #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] - pub struct PersonIcon { - #[serde(rename = "type")] - pub ty: IconType, - pub url: String, - - #[serde(default)] - pub summary: String, - #[serde(default)] - pub width: i64, - #[serde(default)] - pub height: i64 - } - - #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] - #[serde(rename_all = "camelCase")] - pub struct Person { - #[serde(flatten)] - pub obj: Object, - - #[serde(rename = "type")] - pub ty: ActivityType, - - pub following: String, - pub followers: String, - - pub summary: String, - pub inbox: String, - pub outbox: String, - - pub preferred_username: String, - pub name: String, - - pub public_key: Option, - - pub icon: Option - } - - pub struct RemoteInfo { - pub is_remote: bool, - pub web_url: String, - pub acct: String - } - - impl Person { - pub fn remote_info(&self) -> RemoteInfo { - let url = Url::parse(&self.obj.id.0).unwrap(); - let host = url.host_str().unwrap(); - - let (acct, remote) = if host != "ferri.amy.mov" { - (format!("{}@{}", self.preferred_username, host), true) - } else { - (self.preferred_username.clone(), false) - }; - - let url = format!("https://ferri.amy.mov/{}", acct); - - RemoteInfo { - acct: acct.to_string(), - web_url: url, - is_remote: remote, - } - } - } - - #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] - pub struct UserKey { - pub id: String, - pub owner: String, - - #[serde(rename = "publicKeyPem")] - pub public_key: String, - } -} - -pub mod api { - use super::*; - use serde::{Deserialize, Serialize}; - - // API will not really use actors so treat them as DB actors - // until we require specificity - pub type Actor = db::Actor; - - #[derive(Serialize, Deserialize, Debug)] - pub struct CredentialApplication { - pub name: String, - pub scopes: String, - pub redirect_uris: Vec, - pub client_id: String, - pub client_secret: String, - } - - #[derive(Deserialize, Serialize, Debug)] - pub struct WebfingerLink { - pub rel: String, - #[serde(rename = "type")] - pub ty: Option, - pub href: Option, - } - - #[derive(Deserialize, Serialize, Debug)] - pub struct WebfingerHit { - pub subject: String, - pub aliases: Vec, - pub links: Vec, - } - - #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] - pub struct StatusAttachment { - pub id: ObjectUuid, - #[serde(rename = "type")] - pub ty: String, - - pub url: String, - pub description: String - } - - #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] - pub struct Status { - pub id: ObjectUuid, - pub created_at: String, - pub in_reply_to_id: Option, - pub in_reply_to_account_id: Option, - pub sensitive: bool, - pub spoiler_text: String, - pub visibility: String, - pub language: String, - pub uri: ObjectUri, - pub url: String, - pub replies_count: i64, - pub reblogs_count: i64, - pub favourites_count: i64, - pub favourited: bool, - pub reblogged: bool, - pub muted: bool, - pub bookmarked: bool, - pub content: String, - pub reblog: Option>, - pub application: Option<()>, - pub account: Account, - pub media_attachments: Vec, - pub mentions: Vec>, - pub tags: Vec>, - pub emojis: Vec>, - pub card: Option<()>, - pub poll: Option<()>, - } - - #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] - pub struct Account { - pub id: ObjectUuid, - pub username: String, - pub acct: String, - pub display_name: String, - - pub locked: bool, - pub bot: bool, - - pub created_at: String, - pub attribution_domains: Vec, - - pub note: String, - pub url: String, - - pub avatar: String, - pub avatar_static: String, - pub header: String, - pub header_static: String, - - pub followers_count: i64, - pub following_count: i64, - pub statuses_count: i64, - pub last_status_at: Option, - - pub emojis: Vec, - pub fields: Vec, - } - - #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] - pub struct Emoji { - pub shortcode: String, - pub url: String, - pub static_url: String, - pub visible_in_picker: bool, - } - - #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] - pub struct CustomField { - pub name: String, - pub value: String, - pub verified_at: Option, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Instance { - pub domain: String, - pub title: String, - pub version: String, - pub source_url: String, - pub description: String, - pub thumbnail: Thumbnail, - pub icon: Vec, - pub languages: Vec, - pub configuration: Configuration, - pub registrations: Registrations, - pub contact: Contact, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Configuration { - pub urls: Urls, - pub accounts: Accounts, - pub statuses: Statuses, - pub media_attachments: MediaAttachments, - pub polls: Polls, - pub translation: Translation, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Accounts { - pub max_featured_tags: i64, - pub max_pinned_statuses: i64, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct MediaAttachments { - pub supported_mime_types: Vec, - pub description_limit: i64, - pub image_size_limit: i64, - pub image_matrix_limit: i64, - pub video_size_limit: i64, - pub video_frame_rate_limit: i64, - pub video_matrix_limit: i64, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Polls { - pub max_options: i64, - pub max_characters_per_option: i64, - pub min_expiration: i64, - pub max_expiration: i64, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Statuses { - pub max_characters: i64, - pub max_media_attachments: i64, - pub characters_reserved_per_url: i64, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Translation { - pub enabled: bool, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Urls { - pub streaming: String, - pub about: String, - pub privacy_policy: String, - pub terms_of_service: String, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Contact { - pub email: String, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Field { - pub name: String, - pub value: String, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Icon { - pub src: String, - pub size: String, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Registrations { - pub enabled: bool, - pub approval_required: bool, - pub reason_required: bool, - pub message: Option, - pub min_age: i64, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Thumbnail { - pub url: String, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn ap_actor_to_db() { - let domain = "https://example.com"; - - let ap = ap::Actor { - obj: Object { - context: as_context(), - id: ObjectUri(format!("{}/users/sample", domain)), - }, - inbox: format!("{}/users/sample/inbox", domain), - outbox: format!("{}/users/sample/outbox", domain), - }; - - let db: db::Actor = ap.into(); - - assert_eq!( - db, - db::Actor { - id: ObjectUri("https://example.com/users/sample".to_string()), - inbox: "https://example.com/users/sample/inbox".to_string(), - outbox: "https://example.com/users/sample/outbox".to_string(), - } - ); - } -} diff --git a/ferri-server/src/endpoints/admin/mod.rs b/ferri-server/src/endpoints/admin/mod.rs index 51e6ec7..174b968 100644 --- a/ferri-server/src/endpoints/admin/mod.rs +++ b/ferri-server/src/endpoints/admin/mod.rs @@ -1,20 +1,14 @@ -use rocket::{get, post, response::content::RawHtml}; +use rocket::{get, response::content::RawHtml}; use askama::Template; #[derive(Template)] #[template(path = "index.html")] struct IndexTemplate { - val: String -} -#[post("/clicked")] -pub async fn button_clicked() -> RawHtml { - let tmpl = IndexTemplate { val: "clicked".to_string() }; - RawHtml(tmpl.render().unwrap()) } #[get("/")] pub async fn index() -> RawHtml { - let tmpl = IndexTemplate { val: "test".to_string() }; + let tmpl = IndexTemplate { }; RawHtml(tmpl.render().unwrap()) } diff --git a/ferri-server/src/endpoints/api/preferences.rs b/ferri-server/src/endpoints/api/preferences.rs index a06db49..afe4e56 100644 --- a/ferri-server/src/endpoints/api/preferences.rs +++ b/ferri-server/src/endpoints/api/preferences.rs @@ -1,26 +1,12 @@ +use main::types::api; use rocket::{ get, - serde::{Deserialize, Serialize, json::Json}, + serde::json::Json, }; -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct Preferences { - #[serde(rename = "posting:default:visibility")] - pub posting_default_visibility: String, - #[serde(rename = "posting:default:sensitive")] - pub posting_default_sensitive: bool, - #[serde(rename = "posting:default:language")] - pub posting_default_language: Option, - #[serde(rename = "reading:expand:media")] - pub reading_expand_media: String, - #[serde(rename = "reading:expand:spoilers")] - pub reading_expand_spoilers: bool, -} - #[get("/preferences")] -pub async fn preferences() -> Json { - Json(Preferences { +pub async fn preferences() -> Json { + Json(api::Preferences { posting_default_visibility: "public".to_string(), posting_default_sensitive: false, posting_default_language: None, diff --git a/ferri-server/src/endpoints/api/timeline.rs b/ferri-server/src/endpoints/api/timeline.rs index 582b397..f6c82d0 100644 --- a/ferri-server/src/endpoints/api/timeline.rs +++ b/ferri-server/src/endpoints/api/timeline.rs @@ -1,48 +1,11 @@ -use crate::{AuthenticatedUser, Db, endpoints::api::user::CredentialAcount}; -use main::types::{api, get, ObjectUuid}; +use crate::{AuthenticatedUser, Db}; +use main::types::{api, get}; use rocket::{ get, - serde::{Deserialize, Serialize, json::Json}, + serde::json::Json, }; use rocket_db_pools::Connection; -pub type TimelineAccount = CredentialAcount; - -#[derive(Debug, Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct TimelineStatusAttachment { - id: ObjectUuid, - #[serde(rename = "type")] - ty: String, - url: String, - description: String -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct TimelineStatus { - pub id: String, - pub created_at: String, - pub in_reply_to_id: Option, - pub in_reply_to_account_id: Option, - pub content: String, - pub visibility: String, - pub spoiler_text: String, - pub sensitive: bool, - pub uri: String, - pub url: String, - pub replies_count: i64, - pub reblogs_count: i64, - pub favourites_count: i64, - pub favourited: bool, - pub reblogged: bool, - pub muted: bool, - pub bookmarked: bool, - pub reblog: Option>, - pub media_attachments: Vec, - pub account: TimelineAccount, -} - #[get("/timelines/home")] pub async fn home( mut db: Connection, @@ -50,7 +13,10 @@ pub async fn home( ) -> Json> { let posts = get::home_timeline(user.actor_id, &mut **db) .await - .unwrap(); + .unwrap() + .into_iter() + .map(|p| p.into()) + .collect(); - Json(posts.into_iter().map(Into::into).collect()) + Json(posts) } diff --git a/ferri-server/src/endpoints/api/user.rs b/ferri-server/src/endpoints/api/user.rs index 3f60f86..6cb86a0 100644 --- a/ferri-server/src/endpoints/api/user.rs +++ b/ferri-server/src/endpoints/api/user.rs @@ -1,4 +1,5 @@ use main::ap; +use main::types::{api, get, ObjectUuid}; use rocket::response::status::NotFound; use rocket::{ State, get, post, @@ -6,8 +7,8 @@ use rocket::{ }; use rocket_db_pools::Connection; use uuid::Uuid; +use tracing::info; -use crate::timeline::{TimelineAccount, TimelineStatus}; use crate::{AuthenticatedUser, Db}; #[derive(Debug, Serialize, Deserialize)] @@ -34,7 +35,8 @@ pub struct CredentialAcount { } #[get("/accounts/verify_credentials")] -pub async fn verify_credentials() -> Json { +pub async fn verify_credentials(user: AuthenticatedUser) -> Json { + info!("verifying creds for {:#?}", user); Json(CredentialAcount { id: "9b9d497b-2731-435f-a929-e609ca69dac9".to_string(), username: "amy".to_string(), @@ -98,31 +100,12 @@ pub async fn account( mut db: Connection, uuid: &str, _user: AuthenticatedUser, -) -> Result, NotFound> { - let user = ap::User::from_id(uuid, &mut **db) +) -> Result, NotFound> { + let user = get::user_by_id(ObjectUuid(uuid.to_string()), &mut **db) .await .map_err(|e| NotFound(e.to_string()))?; - let user_uri = format!("https://ferri.amy.mov/users/{}", user.username()); - Ok(Json(CredentialAcount { - id: user.id().to_string(), - username: user.username().to_string(), - acct: user.username().to_string(), - display_name: user.display_name().to_string(), - locked: false, - bot: false, - created_at: "2025-04-10T22:12:09Z".to_string(), - attribution_domains: vec![], - note: "".to_string(), - url: user_uri, - avatar: "https://ferri.amy.mov/assets/pfp.png".to_string(), - avatar_static: "https://ferri.amy.mov/assets/pfp.png".to_string(), - header: "https://ferri.amy.mov/assets/pfp.png".to_string(), - header_static: "https://ferri.amy.mov/assets/pfp.png".to_string(), - followers_count: 1, - following_count: 1, - statuses_count: 1, - last_status_at: "2025-04-10T22:14:34Z".to_string(), - })) + + Ok(Json(user.into())) } #[get("/accounts//statuses?<_limit>")] @@ -131,69 +114,17 @@ pub async fn statuses( uuid: &str, _limit: Option, _user: AuthenticatedUser, -) -> Result>, NotFound> { - let user = ap::User::from_id(uuid, &mut **db) +) -> Result>, NotFound> { + let user = get::user_by_id(ObjectUuid(uuid.to_string()), &mut **db) .await .map_err(|e| NotFound(e.to_string()))?; - let uid = user.id(); - let posts = sqlx::query!( - r#" - SELECT p.id as "post_id", u.id as "user_id", p.content, p.uri as "post_uri", u.username, u.display_name, u.actor_id, p.created_at - FROM post p - INNER JOIN user u on p.user_id = u.id - WHERE u.id = ?1 - ORDER BY p.created_at DESC - "#, uid) - .fetch_all(&mut **db) - .await - .unwrap(); - - let mut out = Vec::::new(); - for record in posts { - let user_uri = format!("https://ferri.amy.mov/users/{}", record.username); - out.push(TimelineStatus { - id: record.post_id.clone(), - created_at: record.created_at.clone(), - in_reply_to_id: None, - in_reply_to_account_id: None, - content: record.content.clone(), - visibility: "public".to_string(), - spoiler_text: "".to_string(), - sensitive: false, - uri: record.post_uri.clone(), - url: record.post_uri.clone(), - replies_count: 0, - reblogs_count: 0, - favourites_count: 0, - favourited: false, - reblogged: false, - reblog: None, - muted: false, - bookmarked: false, - media_attachments: vec![], - account: CredentialAcount { - id: record.user_id.clone(), - username: record.username.clone(), - acct: record.username.clone(), - display_name: record.display_name.clone(), - locked: false, - bot: false, - created_at: "2025-04-10T22:12:09Z".to_string(), - attribution_domains: vec![], - note: "".to_string(), - url: user_uri, - avatar: "https://ferri.amy.mov/assets/pfp.png".to_string(), - avatar_static: "https://ferri.amy.mov/assets/pfp.png".to_string(), - header: "https://ferri.amy.mov/assets/pfp.png".to_string(), - header_static: "https://ferri.amy.mov/assets/pfp.png".to_string(), - followers_count: 1, - following_count: 1, - statuses_count: 1, - last_status_at: "2025-04-10T22:14:34Z".to_string(), - }, - }); - } - - Ok(Json(out)) + let posts = get::posts_for_user_id(user.id, &mut **db) + .await + .unwrap() + .into_iter() + .map(|p| p.into()) + .collect(); + + Ok(Json(posts)) } diff --git a/ferri-server/src/http_wrapper.rs b/ferri-server/src/http_wrapper.rs index 38c51be..e065010 100644 --- a/ferri-server/src/http_wrapper.rs +++ b/ferri-server/src/http_wrapper.rs @@ -22,10 +22,6 @@ impl<'a> HttpWrapper<'a> { Self { client, key_id } } - pub fn client(&self) -> &'a HttpClient { - self.client - } - async fn get( &self, ty: &str, diff --git a/ferri-server/src/lib.rs b/ferri-server/src/lib.rs index 5b8b59e..0154ded 100644 --- a/ferri-server/src/lib.rs +++ b/ferri-server/src/lib.rs @@ -134,7 +134,6 @@ pub fn launch(cfg: Config) -> Rocket { "/admin", routes![ admin::index, - admin::button_clicked ] ) .mount( diff --git a/ferri-server/templates/_layout.html b/ferri-server/templates/_layout.html new file mode 100644 index 0000000..9cc9117 --- /dev/null +++ b/ferri-server/templates/_layout.html @@ -0,0 +1,19 @@ + + + + + + + + + {% block title %}{% endblock %} + + {%~ block styles ~%} {% endblock ~%} + + + + + + {%~ block content %}{% endblock ~%} + + diff --git a/ferri-server/templates/index.html b/ferri-server/templates/index.html index f912bcf..50db608 100644 --- a/ferri-server/templates/index.html +++ b/ferri-server/templates/index.html @@ -1,19 +1,45 @@ - - - - - - - - - Ferri Test - - - - - - - - +{% extends "_layout.html" %} + +{%- block title -%} +Control panel +{%- endblock -%} + +{%- block styles -%} + +{%- endblock -%} + +{%- block content -%} +
+
+

Test

+
+ +
+

Test

+
+ +
+

Test

+
+ +
+

Test

+
+
+{%- endblock -%}