mirror of
https://github.com/nullishamy/ferri.git
synced 2025-08-18 10:39:14 +00:00
Compare commits
No commits in common. "a924415a74444d4fd2861b6c32f4784a9598f046" and "77fba1a082d3fb3fe6fd550587599a11fe7aa4ac" have entirely different histories.
a924415a74
...
77fba1a082
22 changed files with 61 additions and 625 deletions
52
Cargo.lock
generated
52
Cargo.lock
generated
|
@ -110,48 +110,6 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4"
|
||||
dependencies = [
|
||||
"askama_derive",
|
||||
"itoa",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama_derive"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f"
|
||||
dependencies = [
|
||||
"askama_parser",
|
||||
"basic-toml",
|
||||
"memchr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama_parser"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.6"
|
||||
|
@ -248,15 +206,6 @@ version = "1.7.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3"
|
||||
|
||||
[[package]]
|
||||
name = "basic-toml"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "binascii"
|
||||
version = "0.1.4"
|
||||
|
@ -2207,7 +2156,6 @@ dependencies = [
|
|||
name = "server"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"askama",
|
||||
"chrono",
|
||||
"main",
|
||||
"rand 0.8.5",
|
||||
|
|
|
@ -55,9 +55,9 @@ async fn main() {
|
|||
r#"
|
||||
INSERT INTO user (
|
||||
id, acct, url, remote, username,
|
||||
actor_id, display_name, created_at, icon_url
|
||||
actor_id, display_name, created_at
|
||||
)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
|
||||
"#,
|
||||
"9b9d497b-2731-435f-a929-e609ca69dac9",
|
||||
"amy",
|
||||
|
@ -66,8 +66,7 @@ async fn main() {
|
|||
"amy",
|
||||
"https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9",
|
||||
"amy",
|
||||
ts,
|
||||
"https://ferri.amy.mov/assets/pfp.png"
|
||||
ts
|
||||
)
|
||||
.execute(&mut *conn)
|
||||
.await
|
||||
|
|
|
@ -44,10 +44,10 @@ impl From<db::User> for api::Account {
|
|||
note: "".to_string(),
|
||||
url: val.url,
|
||||
|
||||
avatar: val.icon_url.clone(),
|
||||
avatar_static: val.icon_url.clone(),
|
||||
header: val.icon_url.clone(),
|
||||
header_static: val.icon_url,
|
||||
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: 0,
|
||||
following_count: 0,
|
||||
|
@ -79,42 +79,6 @@ impl From<db::User> for ap::Person {
|
|||
owner: format!("https://ferri.amy.mov/users/{}", val.id.0),
|
||||
public_key: include_str!("../../../public.pem").to_string(),
|
||||
}),
|
||||
icon: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<db::Post> for api::Status {
|
||||
fn from(value: db::Post) -> api::Status {
|
||||
api::Status {
|
||||
id: value.id,
|
||||
created_at: value.created_at.to_rfc3339(),
|
||||
in_reply_to_id: None,
|
||||
in_reply_to_account_id: None,
|
||||
sensitive: false,
|
||||
spoiler_text: String::new(),
|
||||
visibility: "Public".to_string(),
|
||||
language: "en-GB".to_string(),
|
||||
uri: value.uri.clone(),
|
||||
url: value.uri.0.to_string(),
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
favourited: false,
|
||||
reblogged: false,
|
||||
muted: false,
|
||||
bookmarked: false,
|
||||
content: value.content,
|
||||
reblog: None,
|
||||
application: None,
|
||||
account: value.user.into(),
|
||||
media_attachments: vec![],
|
||||
mentions: vec![],
|
||||
tags: vec![],
|
||||
emojis: vec![],
|
||||
card: None,
|
||||
poll: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,28 +1,14 @@
|
|||
use crate::types::{DbError, ObjectUri, ObjectUuid, db};
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use sqlx::SqliteConnection;
|
||||
use tracing::{info, error};
|
||||
use tracing::info;
|
||||
|
||||
const SQLITE_TIME_FMT: &str = "%Y-%m-%d %H:%M:%S";
|
||||
|
||||
fn parse_ts(ts: String) -> Option<DateTime<Utc>> {
|
||||
// Depending on how the TS is queried it may be naive (so get it back to utc)
|
||||
// or it may have a timezone associated with it
|
||||
let dt = NaiveDateTime::parse_from_str(&ts, SQLITE_TIME_FMT)
|
||||
.map(|ndt| {
|
||||
ndt.and_utc()
|
||||
})
|
||||
.or_else(|_| {
|
||||
DateTime::parse_from_rfc3339(&ts)
|
||||
.map(|dt| dt.to_utc())
|
||||
});
|
||||
|
||||
if let Err(err) = dt {
|
||||
error!("could not parse datetime {} ({}), db weirdness", ts, err);
|
||||
return None
|
||||
}
|
||||
|
||||
Some(dt.unwrap())
|
||||
NaiveDateTime::parse_from_str(&ts, SQLITE_TIME_FMT)
|
||||
.ok()
|
||||
.map(|nt| nt.and_utc())
|
||||
}
|
||||
|
||||
pub async fn user_by_id(id: ObjectUuid, conn: &mut SqliteConnection) -> Result<db::User, DbError> {
|
||||
|
@ -40,8 +26,7 @@ pub async fn user_by_id(id: ObjectUuid, conn: &mut SqliteConnection) -> Result<d
|
|||
u.url,
|
||||
u.acct,
|
||||
u.remote,
|
||||
u.created_at,
|
||||
u.icon_url
|
||||
u.created_at
|
||||
FROM "user" u
|
||||
INNER JOIN "actor" a ON u.actor_id = a.id
|
||||
WHERE u.id = ?1
|
||||
|
@ -74,10 +59,9 @@ pub async fn user_by_id(id: ObjectUuid, conn: &mut SqliteConnection) -> Result<d
|
|||
"#,
|
||||
record.user_id
|
||||
)
|
||||
.fetch_optional(&mut *conn)
|
||||
.fetch_one(&mut *conn)
|
||||
.await
|
||||
.map_err(|e| DbError::FetchError(e.to_string()))?
|
||||
.flatten()
|
||||
.and_then(|ts| {
|
||||
info!("parsing timestamp {}", ts);
|
||||
parse_ts(ts)
|
||||
|
@ -102,142 +86,5 @@ pub async fn user_by_id(id: ObjectUuid, conn: &mut SqliteConnection) -> Result<d
|
|||
created_at: user_created,
|
||||
url: record.url,
|
||||
posts: db::UserPosts { last_post_at },
|
||||
icon_url: record.icon_url
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn user_by_actor_uri(uri: ObjectUri, conn: &mut SqliteConnection) -> Result<db::User, DbError> {
|
||||
info!("fetching user by actor_uri '{:?}' from the database", uri);
|
||||
|
||||
let record = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
u.id as "user_id",
|
||||
u.username,
|
||||
u.actor_id,
|
||||
u.display_name,
|
||||
a.inbox,
|
||||
a.outbox,
|
||||
u.url,
|
||||
u.acct,
|
||||
u.remote,
|
||||
u.created_at,
|
||||
u.icon_url
|
||||
FROM "user" u
|
||||
INNER JOIN "actor" a ON u.actor_id = a.id
|
||||
WHERE u.actor_id = ?1
|
||||
"#,
|
||||
uri.0
|
||||
)
|
||||
.fetch_one(&mut *conn)
|
||||
.await
|
||||
.map_err(|e| DbError::FetchError(e.to_string()))?;
|
||||
|
||||
let follower_count = sqlx::query_scalar!(
|
||||
r#"
|
||||
SELECT COUNT(follower_id)
|
||||
FROM "follow"
|
||||
WHERE followed_id = ?1
|
||||
"#,
|
||||
record.actor_id
|
||||
)
|
||||
.fetch_one(&mut *conn)
|
||||
.await
|
||||
.map_err(|e| DbError::FetchError(e.to_string()))?;
|
||||
|
||||
let last_post_at = sqlx::query_scalar!(
|
||||
r#"
|
||||
SELECT datetime(p.created_at)
|
||||
FROM post p
|
||||
WHERE p.user_id = ?1
|
||||
ORDER BY datetime(p.created_at) DESC
|
||||
LIMIT 1
|
||||
"#,
|
||||
record.user_id
|
||||
)
|
||||
.fetch_optional(&mut *conn)
|
||||
.await
|
||||
.map_err(|e| DbError::FetchError(e.to_string()))?
|
||||
.flatten()
|
||||
.and_then(|ts| {
|
||||
info!("parsing timestamp {}", ts);
|
||||
parse_ts(ts)
|
||||
});
|
||||
|
||||
let user_created = parse_ts(record.created_at).expect("no db corruption");
|
||||
|
||||
info!("user {:?} has {} followers", record.user_id, follower_count);
|
||||
info!("user {:?} last posted {:?}", record.user_id, last_post_at);
|
||||
|
||||
Ok(db::User {
|
||||
id: ObjectUuid(record.user_id),
|
||||
actor: db::Actor {
|
||||
id: ObjectUri(record.actor_id),
|
||||
inbox: record.inbox,
|
||||
outbox: record.outbox,
|
||||
},
|
||||
acct: record.acct,
|
||||
remote: record.remote,
|
||||
username: record.username,
|
||||
display_name: record.display_name,
|
||||
created_at: user_created,
|
||||
url: record.url,
|
||||
posts: db::UserPosts { last_post_at },
|
||||
icon_url: record.icon_url
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn posts_for_user_id(
|
||||
id: ObjectUuid,
|
||||
conn: &mut SqliteConnection
|
||||
) -> Result<Vec<db::Post>, DbError> {
|
||||
let mut out = vec![];
|
||||
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 as "post_created",
|
||||
p.boosted_post_id, a.inbox, a.outbox, u.created_at as "user_created",
|
||||
u.acct, u.remote, u.url as "user_url", u.icon_url
|
||||
FROM post p
|
||||
INNER JOIN user u on p.user_id = u.id
|
||||
INNER JOIN actor a ON u.actor_id = a.id
|
||||
WHERE p.user_id = ?
|
||||
"#, id.0)
|
||||
.fetch_all(&mut *conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for record in posts {
|
||||
let user_created = parse_ts(record.user_created)
|
||||
.expect("no db corruption");
|
||||
|
||||
out.push(db::Post {
|
||||
id: ObjectUuid(record.post_id),
|
||||
uri: ObjectUri(record.post_uri),
|
||||
user: db::User {
|
||||
id: ObjectUuid(record.user_id),
|
||||
actor: db::Actor {
|
||||
id: ObjectUri(record.actor_id),
|
||||
inbox: record.inbox,
|
||||
outbox: record.outbox,
|
||||
},
|
||||
acct: record.acct,
|
||||
remote: record.remote,
|
||||
username: record.username,
|
||||
display_name: record.display_name,
|
||||
created_at: user_created,
|
||||
url: record.user_url,
|
||||
icon_url: record.icon_url,
|
||||
posts: db::UserPosts {
|
||||
last_post_at: None
|
||||
}
|
||||
},
|
||||
content: record.content,
|
||||
created_at: parse_ts(record.post_created).unwrap(),
|
||||
boosted_post: None
|
||||
})
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
|
|
@ -5,9 +5,8 @@ pub async fn new_user(user: db::User, conn: &mut SqliteConnection) -> Result<db:
|
|||
let ts = user.created_at.to_rfc3339();
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO user (id, acct, url, created_at, remote,
|
||||
username, actor_id, display_name, icon_url)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
|
||||
INSERT INTO user (id, acct, url, created_at, remote, username, actor_id, display_name)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
|
||||
"#,
|
||||
user.id.0,
|
||||
user.acct,
|
||||
|
@ -16,8 +15,7 @@ pub async fn new_user(user: db::User, conn: &mut SqliteConnection) -> Result<db:
|
|||
user.remote,
|
||||
user.username,
|
||||
user.actor.id.0,
|
||||
user.display_name,
|
||||
user.icon_url
|
||||
user.display_name
|
||||
)
|
||||
.execute(conn)
|
||||
.await
|
||||
|
|
|
@ -27,12 +27,6 @@ pub enum ObjectContext {
|
|||
Vec(Vec<serde_json::Value>),
|
||||
}
|
||||
|
||||
impl Default for ObjectContext {
|
||||
fn default() -> Self {
|
||||
ObjectContext::Str(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
|
||||
pub struct ObjectUri(pub String);
|
||||
|
||||
|
@ -54,7 +48,6 @@ impl ObjectUuid {
|
|||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
pub struct Object {
|
||||
#[serde(rename = "@context")]
|
||||
#[serde(default)]
|
||||
pub context: ObjectContext,
|
||||
pub id: ObjectUri,
|
||||
}
|
||||
|
@ -86,20 +79,9 @@ pub mod db {
|
|||
pub remote: bool,
|
||||
pub url: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub icon_url: String,
|
||||
|
||||
pub posts: UserPosts,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub struct Post {
|
||||
pub id: ObjectUuid,
|
||||
pub uri: ObjectUri,
|
||||
pub user: User,
|
||||
pub content: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub boosted_post: Option<ObjectUuid>
|
||||
}
|
||||
}
|
||||
|
||||
pub mod ap {
|
||||
|
@ -121,8 +103,6 @@ pub mod ap {
|
|||
pub struct MinimalActivity {
|
||||
#[serde(flatten)]
|
||||
pub obj: Object,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub ty: ActivityType,
|
||||
}
|
||||
|
||||
|
@ -143,7 +123,6 @@ pub mod ap {
|
|||
#[serde(flatten)]
|
||||
pub obj: Object,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub ty: ActivityType,
|
||||
|
||||
pub object: Post,
|
||||
|
@ -160,7 +139,6 @@ pub mod ap {
|
|||
#[serde(flatten)]
|
||||
pub obj: Object,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub ty: ActivityType,
|
||||
|
||||
pub object: String,
|
||||
|
@ -172,7 +150,6 @@ pub mod ap {
|
|||
#[serde(flatten)]
|
||||
pub obj: Object,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub ty: ActivityType,
|
||||
|
||||
pub object: String,
|
||||
|
@ -184,7 +161,6 @@ pub mod ap {
|
|||
#[serde(flatten)]
|
||||
pub obj: Object,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub ty: ActivityType,
|
||||
|
||||
pub actor: String,
|
||||
|
@ -199,7 +175,6 @@ pub mod ap {
|
|||
#[serde(flatten)]
|
||||
pub obj: Object,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub ty: ActivityType,
|
||||
|
||||
#[serde(rename = "published")]
|
||||
|
@ -221,25 +196,6 @@ pub mod ap {
|
|||
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 {
|
||||
|
@ -257,8 +213,6 @@ pub mod ap {
|
|||
pub name: String,
|
||||
|
||||
pub public_key: Option<UserKey>,
|
||||
|
||||
pub icon: Option<PersonIcon>
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
|
@ -303,37 +257,6 @@ pub mod api {
|
|||
pub links: Vec<WebfingerLink>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
pub struct Status {
|
||||
pub id: ObjectUuid,
|
||||
pub created_at: String,
|
||||
pub in_reply_to_id: Option<ObjectUri>,
|
||||
pub in_reply_to_account_id: Option<ObjectUri>,
|
||||
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<Box<Status>>,
|
||||
pub application: Option<()>,
|
||||
pub account: Account,
|
||||
pub media_attachments: Vec<Option<()>>,
|
||||
pub mentions: Vec<Option<()>>,
|
||||
pub tags: Vec<Option<()>>,
|
||||
pub emojis: Vec<Option<()>>,
|
||||
pub card: Option<()>,
|
||||
pub poll: Option<()>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
pub struct Account {
|
||||
pub id: ObjectUuid,
|
||||
|
|
|
@ -19,4 +19,3 @@ tracing-subscriber = { workspace = true }
|
|||
thiserror = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
askama = "0.14.0"
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
use rocket::{get, post, response::content::RawHtml};
|
||||
use askama::Template;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
struct IndexTemplate {
|
||||
val: String
|
||||
}
|
||||
|
||||
#[post("/clicked")]
|
||||
pub async fn button_clicked() -> RawHtml<String> {
|
||||
let tmpl = IndexTemplate { val: "clicked".to_string() };
|
||||
RawHtml(tmpl.render().unwrap())
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub async fn index() -> RawHtml<String> {
|
||||
let tmpl = IndexTemplate { val: "test".to_string() };
|
||||
RawHtml(tmpl.render().unwrap())
|
||||
}
|
|
@ -27,8 +27,8 @@ pub async fn new_app(
|
|||
VALUES (?1, ?2, ?3)
|
||||
"#,
|
||||
app.client_name,
|
||||
secret,
|
||||
app.scopes
|
||||
app.scopes,
|
||||
secret
|
||||
)
|
||||
.execute(&mut **db)
|
||||
.await
|
||||
|
|
|
@ -4,4 +4,3 @@ pub mod preferences;
|
|||
pub mod status;
|
||||
pub mod timeline;
|
||||
pub mod user;
|
||||
pub mod search;
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
use rocket::{
|
||||
get, serde::json::Json, FromFormField, State,
|
||||
};
|
||||
use main::types::{api, get};
|
||||
use rocket_db_pools::Connection;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{info, error};
|
||||
|
||||
use crate::{http_wrapper::HttpWrapper, AuthenticatedUser, Db};
|
||||
|
||||
#[derive(Serialize, Deserialize, FromFormField, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum SearchType {
|
||||
Accounts,
|
||||
Hashtags,
|
||||
Statuses,
|
||||
All
|
||||
}
|
||||
|
||||
impl Default for SearchType {
|
||||
fn default() -> Self {
|
||||
Self::All
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SearchResults {
|
||||
statuses: Vec<api::Status>,
|
||||
accounts: Vec<api::Account>,
|
||||
hashtags: Vec<()>
|
||||
}
|
||||
|
||||
#[get("/search?<q>&<type>")]
|
||||
pub async fn search(
|
||||
q: &str,
|
||||
r#type: SearchType,
|
||||
helpers: &State<crate::Helpers>,
|
||||
mut db: Connection<Db>,
|
||||
user: AuthenticatedUser
|
||||
) -> Json<SearchResults> {
|
||||
let ty = r#type;
|
||||
info!("search for {} (ty: {:?})", q, ty);
|
||||
|
||||
let key_id = "https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9#main-key";
|
||||
let http = HttpWrapper::new(&helpers.http, key_id);
|
||||
|
||||
let mut accounts = vec![];
|
||||
let mut statuses = vec![];
|
||||
|
||||
match ty {
|
||||
SearchType::Accounts => {
|
||||
let person = {
|
||||
let res = http.get_person(q).await;
|
||||
if let Err(e) = res {
|
||||
error!("could not load user {}: {}", q, e.to_string());
|
||||
None
|
||||
} else {
|
||||
Some(res.unwrap())
|
||||
}
|
||||
};
|
||||
|
||||
let user = get::user_by_actor_uri(person.unwrap().obj.id, &mut **db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
accounts.push(user.into())
|
||||
},
|
||||
SearchType::Statuses => {
|
||||
if q == "me" {
|
||||
let st = get::posts_for_user_id(user.id, &mut **db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for status in st.into_iter() {
|
||||
statuses.push(status.into());
|
||||
}
|
||||
}
|
||||
},
|
||||
SearchType::Hashtags => todo!(),
|
||||
SearchType::All => todo!(),
|
||||
}
|
||||
|
||||
Json(SearchResults {
|
||||
statuses,
|
||||
accounts,
|
||||
hashtags: vec![],
|
||||
})
|
||||
}
|
|
@ -43,7 +43,7 @@ async fn create_status(
|
|||
http: &HttpClient,
|
||||
status: &Status,
|
||||
) -> TimelineStatus {
|
||||
let user = ap::User::from_id(&user.id.0, &mut **db).await.unwrap();
|
||||
let user = ap::User::from_id(&user.id, &mut **db).await.unwrap();
|
||||
let outbox = ap::Outbox::for_user(user.clone(), http);
|
||||
|
||||
let post_id = ap::new_id();
|
||||
|
|
|
@ -35,8 +35,11 @@ pub struct TimelineStatus {
|
|||
#[get("/timelines/home")]
|
||||
pub async fn home(
|
||||
mut db: Connection<Db>,
|
||||
helpers: &State<crate::Helpers>,
|
||||
_user: AuthenticatedUser,
|
||||
) -> Json<Vec<TimelineStatus>> {
|
||||
let config = &helpers.config;
|
||||
|
||||
#[derive(sqlx::FromRow, Debug)]
|
||||
struct Post {
|
||||
is_boost_source: bool,
|
||||
|
@ -48,8 +51,6 @@ pub async fn home(
|
|||
boosted_post_id: Option<String>,
|
||||
display_name: String,
|
||||
username: String,
|
||||
icon_url: String,
|
||||
user_url: String
|
||||
}
|
||||
|
||||
// FIXME: query! can't cope with this. returns a type error
|
||||
|
@ -74,7 +75,7 @@ pub async fn home(
|
|||
)
|
||||
SELECT is_boost_source, 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, p.boosted_post_id, u.icon_url, u.url as "user_url"
|
||||
u.actor_id, p.created_at, p.boosted_post_id
|
||||
FROM get_home_timeline_with_boosts
|
||||
JOIN post p ON p.id = get_home_timeline_with_boosts.id
|
||||
JOIN user u ON u.id = p.user_id;
|
||||
|
@ -89,6 +90,7 @@ pub async fn home(
|
|||
for record in posts.iter() {
|
||||
let mut boost: Option<Box<TimelineStatus>> = None;
|
||||
if let Some(ref boosted_id) = record.boosted_post_id {
|
||||
let user_uri = config.user_url(&record.user_id);
|
||||
let record = posts.iter().find(|p| &p.post_id == boosted_id).unwrap();
|
||||
|
||||
boost = Some(Box::new(TimelineStatus {
|
||||
|
@ -121,11 +123,11 @@ pub async fn home(
|
|||
created_at: "2025-04-10T22:12:09Z".to_string(),
|
||||
attribution_domains: vec![],
|
||||
note: "".to_string(),
|
||||
url: record.user_url.clone(),
|
||||
avatar: record.icon_url.clone(),
|
||||
avatar_static: record.icon_url.clone(),
|
||||
header: record.icon_url.clone(),
|
||||
header_static: record.icon_url.clone(),
|
||||
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,
|
||||
|
@ -135,6 +137,7 @@ pub async fn home(
|
|||
}
|
||||
|
||||
if !record.is_boost_source {
|
||||
let user_uri = config.user_web_url(&record.username);
|
||||
out.push(TimelineStatus {
|
||||
id: record.post_id.clone(),
|
||||
created_at: record.created_at.clone(),
|
||||
|
@ -165,11 +168,11 @@ pub async fn home(
|
|||
created_at: "2025-04-10T22:12:09Z".to_string(),
|
||||
attribution_domains: vec![],
|
||||
note: "".to_string(),
|
||||
url: record.user_url.clone(),
|
||||
avatar: record.icon_url.clone(),
|
||||
avatar_static: record.icon_url.clone(),
|
||||
header: record.icon_url.clone(),
|
||||
header_static: record.icon_url.clone(),
|
||||
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,
|
||||
|
|
|
@ -66,7 +66,7 @@ pub async fn new_follow(
|
|||
) -> Result<(), NotFound<String>> {
|
||||
let http = &helpers.http;
|
||||
|
||||
let follower = ap::User::from_actor_id(&user.actor_id.0, &mut **db).await;
|
||||
let follower = ap::User::from_actor_id(&user.actor_id, &mut **db).await;
|
||||
|
||||
let followed = ap::User::from_id(uuid, &mut **db)
|
||||
.await
|
||||
|
|
|
@ -57,9 +57,6 @@ async fn create_user(
|
|||
};
|
||||
|
||||
let url = format!("https://ferri.amy.mov/{}", acct);
|
||||
let icon_url = user.icon.as_ref().map(|ic| ic.url.clone()).unwrap_or(
|
||||
"https://ferri.amy.mov/assets/pfp.png".to_string()
|
||||
);
|
||||
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
// FIXME: Pull from user
|
||||
|
@ -68,10 +65,10 @@ async fn create_user(
|
|||
r#"
|
||||
INSERT INTO user (
|
||||
id, acct, url, remote, username,
|
||||
actor_id, display_name, created_at, icon_url
|
||||
actor_id, display_name, created_at
|
||||
)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
|
||||
ON CONFLICT(actor_id) DO NOTHING;
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
|
||||
ON CONFLICT(actor_id) DO NOTHING;
|
||||
"#,
|
||||
uuid,
|
||||
acct,
|
||||
|
@ -80,8 +77,7 @@ async fn create_user(
|
|||
user.preferred_username,
|
||||
actor,
|
||||
user.name,
|
||||
ts,
|
||||
icon_url
|
||||
ts
|
||||
)
|
||||
.execute(conn)
|
||||
.await
|
||||
|
@ -174,9 +170,6 @@ async fn resolve_actor<'a>(
|
|||
remote: remote_info.is_remote,
|
||||
url: remote_info.web_url,
|
||||
created_at: main::ap::now(),
|
||||
icon_url: person.icon.map(|ic| ic.url).unwrap_or(
|
||||
"https://ferri.amy.mov/assets/pfp.png".to_string()
|
||||
),
|
||||
|
||||
posts: db::UserPosts { last_post_at: None },
|
||||
};
|
||||
|
@ -334,8 +327,6 @@ async fn handle_boost_activity<'a>(
|
|||
);
|
||||
let user_id = actor_user.id();
|
||||
|
||||
info!("inserting post with id {} uri {}", base_id, uri);
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO post (id, uri, user_id, content, created_at, boosted_post_id)
|
||||
|
|
|
@ -4,8 +4,6 @@ pub mod oauth;
|
|||
pub mod user;
|
||||
|
||||
pub mod api;
|
||||
pub mod admin;
|
||||
|
||||
pub mod custom;
|
||||
pub mod inbox;
|
||||
pub mod well_known;
|
||||
|
|
|
@ -1,40 +1,23 @@
|
|||
use crate::Db;
|
||||
use askama::Template;
|
||||
use tracing::error;
|
||||
|
||||
use rocket::{
|
||||
FromForm,
|
||||
form::Form,
|
||||
get, post,
|
||||
response::status::BadRequest,
|
||||
response::content::RawHtml,
|
||||
response::Redirect,
|
||||
serde::{Deserialize, Serialize, json::Json},
|
||||
};
|
||||
|
||||
use rocket_db_pools::Connection;
|
||||
|
||||
struct AuthorizeClient {
|
||||
id: String
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "authorize.html")]
|
||||
struct AuthorizeTemplate {
|
||||
client: AuthorizeClient,
|
||||
scopes: Vec<String>,
|
||||
scope_raw: String,
|
||||
redirect_uri: String,
|
||||
user_id: String
|
||||
}
|
||||
|
||||
#[post("/oauth/accept?<id>&<client_id>&<scope>")]
|
||||
pub async fn accept(
|
||||
mut db: Connection<Db>,
|
||||
id: &str,
|
||||
#[get("/oauth/authorize?<client_id>&<scope>&<redirect_uri>&<response_type>")]
|
||||
pub async fn authorize(
|
||||
client_id: &str,
|
||||
scope: &str
|
||||
) -> RawHtml<String> {
|
||||
let user_id = id;
|
||||
scope: &str,
|
||||
redirect_uri: &str,
|
||||
response_type: &str,
|
||||
mut db: Connection<Db>,
|
||||
) -> Redirect {
|
||||
// For now, we will always authorize the request and assign it to an admin user
|
||||
let user_id = "9b9d497b-2731-435f-a929-e609ca69dac9";
|
||||
let code = main::gen_token(15);
|
||||
|
||||
// This will act as a token for the user, but we will in future say that it expires very shortly
|
||||
|
@ -69,37 +52,7 @@ pub async fn accept(
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
// HACK: Until we are storing oauth stuff more properly we will hardcode phanpy
|
||||
RawHtml(format!(r#"
|
||||
<script>window.location.href="{}{}"</script>
|
||||
"#, "https://phanpy.social?code=", code))
|
||||
}
|
||||
|
||||
#[get("/oauth/authorize?<client_id>&<scope>&<redirect_uri>&<response_type>")]
|
||||
pub async fn authorize(
|
||||
client_id: &str,
|
||||
scope: &str,
|
||||
redirect_uri: &str,
|
||||
response_type: &str
|
||||
) -> Result<RawHtml<String>, BadRequest<String>> {
|
||||
if response_type != "code" {
|
||||
error!("unknown response type {}", response_type);
|
||||
return Err(
|
||||
BadRequest(format!("unknown response type {}", response_type))
|
||||
)
|
||||
}
|
||||
|
||||
let tmpl = AuthorizeTemplate {
|
||||
client: AuthorizeClient {
|
||||
id: client_id.to_string()
|
||||
},
|
||||
scope_raw: scope.to_string(),
|
||||
scopes: scope.split(" ").map(|s| s.to_string()).collect(),
|
||||
redirect_uri: redirect_uri.to_string(),
|
||||
user_id: "9b9d497b-2731-435f-a929-e609ca69dac9".to_string()
|
||||
};
|
||||
|
||||
Ok(RawHtml(tmpl.render().unwrap()))
|
||||
Redirect::temporary(format!("{}?code={}", redirect_uri, code))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::http::HttpClient;
|
|||
use main::types::ap;
|
||||
use std::fmt::Debug;
|
||||
use thiserror::Error;
|
||||
use tracing::{Level, error, event, info};
|
||||
use tracing::{Level, error, event};
|
||||
|
||||
pub struct HttpWrapper<'a> {
|
||||
client: &'a HttpClient,
|
||||
|
@ -54,7 +54,6 @@ impl<'a> HttpWrapper<'a> {
|
|||
}
|
||||
|
||||
let raw_body = raw_body.unwrap();
|
||||
info!("raw body {}", raw_body);
|
||||
let decoded = serde_json::from_str::<T>(&raw_body);
|
||||
|
||||
if let Err(e) = decoded {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use endpoints::{
|
||||
api::{self, timeline},
|
||||
admin, custom, inbox, oauth, user, well_known,
|
||||
custom, inbox, oauth, user, well_known,
|
||||
};
|
||||
|
||||
use tracing_subscriber::fmt;
|
||||
|
||||
use main::{ap, types::{ObjectUri, ObjectUuid}};
|
||||
use main::ap;
|
||||
|
||||
use main::ap::http;
|
||||
use main::config::Config;
|
||||
|
@ -36,9 +36,9 @@ async fn activity_endpoint(_activity: String) {}
|
|||
#[derive(Debug)]
|
||||
pub struct AuthenticatedUser {
|
||||
pub username: String,
|
||||
pub id: ObjectUuid,
|
||||
pub id: String,
|
||||
pub token: String,
|
||||
pub actor_id: ObjectUri,
|
||||
pub actor_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -72,9 +72,9 @@ impl<'a> FromRequest<'a> for AuthenticatedUser {
|
|||
if let Ok(auth) = auth {
|
||||
return Outcome::Success(AuthenticatedUser {
|
||||
token: auth.token,
|
||||
id: ObjectUuid(auth.id),
|
||||
id: auth.id,
|
||||
username: auth.display_name,
|
||||
actor_id: ObjectUri(auth.actor_id),
|
||||
actor_id: auth.actor_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -122,13 +122,6 @@ pub fn launch(cfg: Config) -> Rocket<Build> {
|
|||
.attach(Db::init())
|
||||
.attach(cors::CORS)
|
||||
.mount("/assets", rocket::fs::FileServer::from("./assets"))
|
||||
.mount(
|
||||
"/admin",
|
||||
routes![
|
||||
admin::index,
|
||||
admin::button_clicked
|
||||
]
|
||||
)
|
||||
.mount(
|
||||
"/",
|
||||
routes![
|
||||
|
@ -140,7 +133,6 @@ pub fn launch(cfg: Config) -> Rocket<Build> {
|
|||
user::following,
|
||||
user::post,
|
||||
oauth::authorize,
|
||||
oauth::accept,
|
||||
oauth::new_token,
|
||||
cors::options_req,
|
||||
activity_endpoint,
|
||||
|
@ -150,10 +142,7 @@ pub fn launch(cfg: Config) -> Rocket<Build> {
|
|||
user_profile,
|
||||
],
|
||||
)
|
||||
.mount("/api/v2", routes![
|
||||
api::instance::instance,
|
||||
api::search::search,
|
||||
])
|
||||
.mount("/api/v2", routes![api::instance::instance])
|
||||
.mount(
|
||||
"/api/v1",
|
||||
routes![
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="https://ferri.amy.mov/admin/">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Ferri Test</title>
|
||||
|
||||
<link rel="stylesheet" href="https://raw.githubusercontent.com/tailwindlabs/tailwindcss/dbc8023a08964f513c20796e170cb91ce891df3f/packages/tailwindcss/preflight.css">
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
|
||||
<style>
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Authorization request</h1>
|
||||
|
||||
<span>
|
||||
App '{{ client.id }}' would like to access the following scopes in your account
|
||||
</span>
|
||||
|
||||
<ul>
|
||||
{% for scope in scopes %}
|
||||
<li>{{ scope }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<button
|
||||
hx-post="/oauth/accept?id={{ user_id }}&client_id={{ client.id }}&scope={{ scope_raw }}"
|
||||
>
|
||||
Accept and return to {{ redirect_uri }}?
|
||||
</button>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="https://ferri.amy.mov/admin/">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Ferri Test</title>
|
||||
|
||||
<link rel="stylesheet" href="https://raw.githubusercontent.com/tailwindlabs/tailwindcss/dbc8023a08964f513c20796e170cb91ce891df3f/packages/tailwindcss/preflight.css">
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
</head>
|
||||
<body>
|
||||
<button hx-post="clicked" hx-swap="outerHTML">
|
||||
Click Me {{ val }}
|
||||
</button>
|
||||
</body>
|
||||
</html>
|
|
@ -9,7 +9,6 @@ CREATE TABLE IF NOT EXISTS user
|
|||
username TEXT NOT NULL,
|
||||
actor_id TEXT NOT NULL UNIQUE,
|
||||
display_name TEXT NOT NULL,
|
||||
icon_url TEXT NOT NULL,
|
||||
|
||||
FOREIGN KEY(actor_id) REFERENCES actor(id)
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue