mirror of
https://github.com/nullishamy/ferri.git
synced 2025-04-30 20:59:21 +00:00
chore: finish types refactor
This commit is contained in:
parent
5f346922f5
commit
77fba1a082
29 changed files with 611 additions and 608 deletions
|
@ -3,7 +3,7 @@ use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::Sqlite;
|
use sqlx::Sqlite;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use tracing::{event, Level};
|
use tracing::{Level, event};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ActivityType {
|
pub enum ActivityType {
|
||||||
|
@ -127,8 +127,10 @@ impl<'a> Outbox<'a> {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
event!(Level::DEBUG,
|
event!(
|
||||||
outbox_res, activity = activity.req.id,
|
Level::DEBUG,
|
||||||
|
outbox_res,
|
||||||
|
activity = activity.req.id,
|
||||||
"got response for outbox dispatch"
|
"got response for outbox dispatch"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use rsa::{
|
||||||
|
|
||||||
use base64::prelude::*;
|
use base64::prelude::*;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use tracing::{event, Level};
|
use tracing::{Level, event};
|
||||||
|
|
||||||
pub struct HttpClient {
|
pub struct HttpClient {
|
||||||
client: reqwest::Client,
|
client: reqwest::Client,
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use tracing::{info, span, Level};
|
use tracing::{Level, info, span};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum QueueMessage {
|
pub enum QueueMessage {
|
||||||
Heartbeat
|
Heartbeat,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RequestQueue {
|
pub struct RequestQueue {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
send: mpsc::Sender<QueueMessage>,
|
send: mpsc::Sender<QueueMessage>,
|
||||||
recv: mpsc::Receiver<QueueMessage>
|
recv: mpsc::Receiver<QueueMessage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct QueueHandle {
|
pub struct QueueHandle {
|
||||||
send: mpsc::Sender<QueueMessage>
|
send: mpsc::Sender<QueueMessage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QueueHandle {
|
impl QueueHandle {
|
||||||
|
@ -27,11 +27,7 @@ impl QueueHandle {
|
||||||
impl RequestQueue {
|
impl RequestQueue {
|
||||||
pub fn new(name: &'static str) -> Self {
|
pub fn new(name: &'static str) -> Self {
|
||||||
let (send, recv) = mpsc::channel();
|
let (send, recv) = mpsc::channel();
|
||||||
Self {
|
Self { name, send, recv }
|
||||||
name,
|
|
||||||
send,
|
|
||||||
recv
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn(self) -> QueueHandle {
|
pub fn spawn(self) -> QueueHandle {
|
||||||
|
|
|
@ -35,7 +35,6 @@ pub struct User {
|
||||||
display_name: String,
|
display_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum UserError {
|
pub enum UserError {
|
||||||
#[error("user `{0}` not found")]
|
#[error("user `{0}` not found")]
|
||||||
|
@ -67,7 +66,10 @@ impl User {
|
||||||
format!("https://ferri.amy.mov/users/{}", self.id())
|
format!("https://ferri.amy.mov/users/{}", self.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn from_id(uuid: &str, conn: impl sqlx::Executor<'_, Database = Sqlite>) -> Result<User, UserError> {
|
pub async fn from_id(
|
||||||
|
uuid: &str,
|
||||||
|
conn: impl sqlx::Executor<'_, Database = Sqlite>,
|
||||||
|
) -> Result<User, UserError> {
|
||||||
let user = sqlx::query!(
|
let user = sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
SELECT u.*, a.id as "actor_own_id", a.inbox, a.outbox
|
SELECT u.*, a.id as "actor_own_id", a.inbox, a.outbox
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
pub mod ap;
|
pub mod ap;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod types_rewrite;
|
pub mod types;
|
||||||
|
|
||||||
use rand::{Rng, distributions::Alphanumeric};
|
use rand::{Rng, distributions::Alphanumeric};
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
use crate::types_rewrite::api;
|
use crate::types::ap;
|
||||||
use crate::types_rewrite::ap;
|
use crate::types::api;
|
||||||
use crate::types_rewrite::db;
|
use crate::types::db;
|
||||||
|
|
||||||
use crate::types_rewrite::{Object, as_context};
|
use crate::types::{Object, ObjectUri, as_context};
|
||||||
|
|
||||||
impl From<db::Actor> for ap::Actor {
|
impl From<db::Actor> for ap::Actor {
|
||||||
fn from(val: db::Actor) -> ap::Actor {
|
fn from(val: db::Actor) -> ap::Actor {
|
||||||
ap::Actor {
|
ap::Actor {
|
||||||
obj: Object {
|
obj: Object {
|
||||||
context: as_context(),
|
context: as_context(),
|
||||||
id: val.id
|
id: val.id,
|
||||||
},
|
},
|
||||||
inbox: val.inbox,
|
inbox: val.inbox,
|
||||||
outbox: val.outbox
|
outbox: val.outbox,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ impl From<ap::Actor> for db::Actor {
|
||||||
db::Actor {
|
db::Actor {
|
||||||
id: val.obj.id,
|
id: val.obj.id,
|
||||||
inbox: val.inbox,
|
inbox: val.inbox,
|
||||||
outbox: val.outbox
|
outbox: val.outbox,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,3 +59,26 @@ impl From<db::User> for api::Account {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<db::User> for ap::Person {
|
||||||
|
fn from(val: db::User) -> ap::Person {
|
||||||
|
ap::Person {
|
||||||
|
obj: Object {
|
||||||
|
context: as_context(),
|
||||||
|
id: ObjectUri(format!("https://ferri.amy.mov/users/{}", val.id.0)),
|
||||||
|
},
|
||||||
|
following: format!("https://ferri.amy.mov/users/{}/following", val.id.0),
|
||||||
|
followers: format!("https://ferri.amy.mov/users/{}/followers", val.id.0),
|
||||||
|
summary: format!("ferri {}", val.username),
|
||||||
|
inbox: format!("https://ferri.amy.mov/users/{}/inbox", val.id.0),
|
||||||
|
outbox: format!("https://ferri.amy.mov/users/{}/outbox", val.id.0),
|
||||||
|
preferred_username: val.display_name,
|
||||||
|
name: val.username,
|
||||||
|
public_key: Some(ap::UserKey {
|
||||||
|
id: format!("https://ferri.amy.mov/users/{}#main-key", val.id.0),
|
||||||
|
owner: format!("https://ferri.amy.mov/users/{}", val.id.0),
|
||||||
|
public_key: include_str!("../../../public.pem").to_string(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
use crate::types_rewrite::{ObjectUuid, ObjectUri, db};
|
use crate::types::{DbError, ObjectUri, ObjectUuid, db};
|
||||||
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use sqlx::SqliteConnection;
|
use sqlx::SqliteConnection;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use chrono::{NaiveDateTime, DateTime, Utc};
|
|
||||||
use crate::types_rewrite::DbError;
|
|
||||||
|
|
||||||
const SQLITE_TIME_FMT: &'static str = "%Y-%m-%d %H:%M:%S";
|
const SQLITE_TIME_FMT: &str = "%Y-%m-%d %H:%M:%S";
|
||||||
|
|
||||||
fn parse_ts(ts: String) -> Option<DateTime<Utc>> {
|
fn parse_ts(ts: String) -> Option<DateTime<Utc>> {
|
||||||
NaiveDateTime::parse_from_str(&ts, SQLITE_TIME_FMT)
|
NaiveDateTime::parse_from_str(&ts, SQLITE_TIME_FMT)
|
||||||
|
@ -15,7 +14,8 @@ fn parse_ts(ts: String) -> Option<DateTime<Utc>> {
|
||||||
pub async fn user_by_id(id: ObjectUuid, conn: &mut SqliteConnection) -> Result<db::User, DbError> {
|
pub async fn user_by_id(id: ObjectUuid, conn: &mut SqliteConnection) -> Result<db::User, DbError> {
|
||||||
info!("fetching user by uuid '{:?}' from the database", id);
|
info!("fetching user by uuid '{:?}' from the database", id);
|
||||||
|
|
||||||
let record = sqlx::query!(r#"
|
let record = sqlx::query!(
|
||||||
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
u.id as "user_id",
|
u.id as "user_id",
|
||||||
u.username,
|
u.username,
|
||||||
|
@ -30,27 +30,35 @@ pub async fn user_by_id(id: ObjectUuid, conn: &mut SqliteConnection) -> Result<d
|
||||||
FROM "user" u
|
FROM "user" u
|
||||||
INNER JOIN "actor" a ON u.actor_id = a.id
|
INNER JOIN "actor" a ON u.actor_id = a.id
|
||||||
WHERE u.id = ?1
|
WHERE u.id = ?1
|
||||||
"#, id.0)
|
"#,
|
||||||
|
id.0
|
||||||
|
)
|
||||||
.fetch_one(&mut *conn)
|
.fetch_one(&mut *conn)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| DbError::FetchError(e.to_string()))?;
|
.map_err(|e| DbError::FetchError(e.to_string()))?;
|
||||||
|
|
||||||
let follower_count = sqlx::query_scalar!(r#"
|
let follower_count = sqlx::query_scalar!(
|
||||||
|
r#"
|
||||||
SELECT COUNT(follower_id)
|
SELECT COUNT(follower_id)
|
||||||
FROM "follow"
|
FROM "follow"
|
||||||
WHERE followed_id = ?1
|
WHERE followed_id = ?1
|
||||||
"#, record.actor_id)
|
"#,
|
||||||
|
record.actor_id
|
||||||
|
)
|
||||||
.fetch_one(&mut *conn)
|
.fetch_one(&mut *conn)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| DbError::FetchError(e.to_string()))?;
|
.map_err(|e| DbError::FetchError(e.to_string()))?;
|
||||||
|
|
||||||
let last_post_at = sqlx::query_scalar!(r#"
|
let last_post_at = sqlx::query_scalar!(
|
||||||
|
r#"
|
||||||
SELECT datetime(p.created_at)
|
SELECT datetime(p.created_at)
|
||||||
FROM post p
|
FROM post p
|
||||||
WHERE p.user_id = ?1
|
WHERE p.user_id = ?1
|
||||||
ORDER BY datetime(p.created_at) DESC
|
ORDER BY datetime(p.created_at) DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
"#, record.user_id)
|
"#,
|
||||||
|
record.user_id
|
||||||
|
)
|
||||||
.fetch_one(&mut *conn)
|
.fetch_one(&mut *conn)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| DbError::FetchError(e.to_string()))?
|
.map_err(|e| DbError::FetchError(e.to_string()))?
|
||||||
|
@ -69,7 +77,7 @@ pub async fn user_by_id(id: ObjectUuid, conn: &mut SqliteConnection) -> Result<d
|
||||||
actor: db::Actor {
|
actor: db::Actor {
|
||||||
id: ObjectUri(record.actor_id),
|
id: ObjectUri(record.actor_id),
|
||||||
inbox: record.inbox,
|
inbox: record.inbox,
|
||||||
outbox: record.outbox
|
outbox: record.outbox,
|
||||||
},
|
},
|
||||||
acct: record.acct,
|
acct: record.acct,
|
||||||
remote: record.remote,
|
remote: record.remote,
|
||||||
|
@ -77,8 +85,6 @@ pub async fn user_by_id(id: ObjectUuid, conn: &mut SqliteConnection) -> Result<d
|
||||||
display_name: record.display_name,
|
display_name: record.display_name,
|
||||||
created_at: user_created,
|
created_at: user_created,
|
||||||
url: record.url,
|
url: record.url,
|
||||||
posts: db::UserPosts {
|
posts: db::UserPosts { last_post_at },
|
||||||
last_post_at
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
45
ferri-main/src/types/make.rs
Normal file
45
ferri-main/src/types/make.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use crate::types::{DbError, db};
|
||||||
|
use sqlx::SqliteConnection;
|
||||||
|
|
||||||
|
pub async fn new_user(user: db::User, conn: &mut SqliteConnection) -> Result<db::User, DbError> {
|
||||||
|
let ts = user.created_at.to_rfc3339();
|
||||||
|
sqlx::query!(
|
||||||
|
r#"
|
||||||
|
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,
|
||||||
|
user.url,
|
||||||
|
ts,
|
||||||
|
user.remote,
|
||||||
|
user.username,
|
||||||
|
user.actor.id.0,
|
||||||
|
user.display_name
|
||||||
|
)
|
||||||
|
.execute(conn)
|
||||||
|
.await
|
||||||
|
.map_err(|e| DbError::CreationError(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new_actor(
|
||||||
|
actor: db::Actor,
|
||||||
|
conn: &mut SqliteConnection,
|
||||||
|
) -> Result<db::Actor, DbError> {
|
||||||
|
sqlx::query!(
|
||||||
|
r#"
|
||||||
|
INSERT INTO actor (id, inbox, outbox)
|
||||||
|
VALUES (?1, ?2, ?3)
|
||||||
|
"#,
|
||||||
|
actor.id.0,
|
||||||
|
actor.inbox,
|
||||||
|
actor.outbox
|
||||||
|
)
|
||||||
|
.execute(conn)
|
||||||
|
.await
|
||||||
|
.map_err(|e| DbError::CreationError(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(actor)
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub mod convert;
|
pub mod convert;
|
||||||
|
@ -12,10 +12,10 @@ pub enum DbError {
|
||||||
#[error("an unknown error occured when creating: {0}")]
|
#[error("an unknown error occured when creating: {0}")]
|
||||||
CreationError(String),
|
CreationError(String),
|
||||||
#[error("an unknown error occured when fetching: {0}")]
|
#[error("an unknown error occured when fetching: {0}")]
|
||||||
FetchError(String)
|
FetchError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const AS_CONTEXT_RAW: &'static str = "https://www.w3.org/ns/activitystreams";
|
pub const AS_CONTEXT_RAW: &str = "https://www.w3.org/ns/activitystreams";
|
||||||
pub fn as_context() -> ObjectContext {
|
pub fn as_context() -> ObjectContext {
|
||||||
ObjectContext::Str(AS_CONTEXT_RAW.to_string())
|
ObjectContext::Str(AS_CONTEXT_RAW.to_string())
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,12 @@ pub struct ObjectUri(pub String);
|
||||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
|
||||||
pub struct ObjectUuid(pub String);
|
pub struct ObjectUuid(pub String);
|
||||||
|
|
||||||
|
impl Default for ObjectUuid {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ObjectUuid {
|
impl ObjectUuid {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(Uuid::new_v4().to_string())
|
Self(Uuid::new_v4().to_string())
|
||||||
|
@ -42,13 +48,13 @@ impl ObjectUuid {
|
||||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
#[serde(rename = "@context")]
|
#[serde(rename = "@context")]
|
||||||
context: ObjectContext,
|
pub context: ObjectContext,
|
||||||
id: ObjectUri,
|
pub id: ObjectUri,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod db {
|
pub mod db {
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
pub struct Actor {
|
pub struct Actor {
|
||||||
|
@ -60,7 +66,7 @@ pub mod db {
|
||||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
pub struct UserPosts {
|
pub struct UserPosts {
|
||||||
// User may have no posts
|
// User may have no posts
|
||||||
pub last_post_at: Option<DateTime<Utc>>
|
pub last_post_at: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
|
@ -74,13 +80,112 @@ pub mod db {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
|
|
||||||
pub posts: UserPosts
|
pub posts: UserPosts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod ap {
|
pub mod ap {
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||||
|
pub enum ActivityType {
|
||||||
|
Create,
|
||||||
|
Note,
|
||||||
|
Delete,
|
||||||
|
Accept,
|
||||||
|
Announce,
|
||||||
|
Like,
|
||||||
|
Follow,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct MinimalActivity {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub obj: Object,
|
||||||
|
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,
|
||||||
|
|
||||||
|
pub ty: ActivityType,
|
||||||
|
|
||||||
|
pub object: Post,
|
||||||
|
pub actor: String,
|
||||||
|
pub to: Vec<String>,
|
||||||
|
pub cc: Vec<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "published")]
|
||||||
|
pub ts: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct FollowActivity {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub obj: Object,
|
||||||
|
|
||||||
|
pub ty: ActivityType,
|
||||||
|
|
||||||
|
pub object: String,
|
||||||
|
pub actor: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct AcceptActivity {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub obj: Object,
|
||||||
|
|
||||||
|
pub ty: ActivityType,
|
||||||
|
|
||||||
|
pub object: String,
|
||||||
|
pub actor: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct BoostActivity {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub obj: Object,
|
||||||
|
|
||||||
|
pub ty: ActivityType,
|
||||||
|
|
||||||
|
pub actor: String,
|
||||||
|
pub published: String,
|
||||||
|
pub to: Vec<String>,
|
||||||
|
pub cc: Vec<String>,
|
||||||
|
pub object: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Post {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub obj: Object,
|
||||||
|
|
||||||
|
pub ty: ActivityType,
|
||||||
|
|
||||||
|
#[serde(rename = "published")]
|
||||||
|
pub ts: String,
|
||||||
|
pub content: String,
|
||||||
|
pub to: Vec<String>,
|
||||||
|
pub cc: Vec<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "attributedTo")]
|
||||||
|
pub attributed_to: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||||
pub struct Actor {
|
pub struct Actor {
|
||||||
|
@ -92,6 +197,7 @@ pub mod ap {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Person {
|
pub struct Person {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub obj: Object,
|
pub obj: Object,
|
||||||
|
@ -120,13 +226,37 @@ pub mod ap {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod api {
|
pub mod api {
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
// API will not really use actors so treat them as DB actors
|
// API will not really use actors so treat them as DB actors
|
||||||
// until we require specificity
|
// until we require specificity
|
||||||
pub type Actor = db::Actor;
|
pub type Actor = db::Actor;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct CredentialApplication {
|
||||||
|
pub name: String,
|
||||||
|
pub scopes: String,
|
||||||
|
pub redirect_uris: Vec<String>,
|
||||||
|
pub client_id: String,
|
||||||
|
pub client_secret: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct WebfingerLink {
|
||||||
|
pub rel: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub ty: Option<String>,
|
||||||
|
pub href: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct WebfingerHit {
|
||||||
|
pub subject: String,
|
||||||
|
pub aliases: Vec<String>,
|
||||||
|
pub links: Vec<WebfingerLink>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
pub id: ObjectUuid,
|
pub id: ObjectUuid,
|
||||||
|
@ -293,10 +423,13 @@ mod tests {
|
||||||
|
|
||||||
let db: db::Actor = ap.into();
|
let db: db::Actor = ap.into();
|
||||||
|
|
||||||
assert_eq!(db, db::Actor {
|
assert_eq!(
|
||||||
|
db,
|
||||||
|
db::Actor {
|
||||||
id: ObjectUri("https://example.com/users/sample".to_string()),
|
id: ObjectUri("https://example.com/users/sample".to_string()),
|
||||||
inbox: "https://example.com/users/sample/inbox".to_string(),
|
inbox: "https://example.com/users/sample/inbox".to_string(),
|
||||||
outbox: "https://example.com/users/sample/outbox".to_string(),
|
outbox: "https://example.com/users/sample/outbox".to_string(),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
use crate::types_rewrite::db;
|
|
||||||
use sqlx::SqliteConnection;
|
|
||||||
use crate::types_rewrite::DbError;
|
|
||||||
|
|
||||||
pub async fn new_user(user: db::User, conn: &mut SqliteConnection) -> Result<db::User, DbError> {
|
|
||||||
let ts = user.created_at.to_rfc3339();
|
|
||||||
sqlx::query!(r#"
|
|
||||||
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, user.url, ts,
|
|
||||||
user.remote, user.username, user.actor.id.0, user.display_name
|
|
||||||
)
|
|
||||||
.execute(conn)
|
|
||||||
.await
|
|
||||||
.map_err(|e| DbError::CreationError(e.to_string()))?;
|
|
||||||
|
|
||||||
Ok(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn new_actor(actor: db::Actor, conn: &mut SqliteConnection) -> Result<db::Actor, DbError> {
|
|
||||||
sqlx::query!(r#"
|
|
||||||
INSERT INTO actor (id, inbox, outbox)
|
|
||||||
VALUES (?1, ?2, ?3)
|
|
||||||
"#, actor.id.0, actor.inbox, actor.outbox)
|
|
||||||
.execute(conn)
|
|
||||||
.await
|
|
||||||
.map_err(|e| DbError::CreationError(e.to_string()))?;
|
|
||||||
|
|
||||||
Ok(actor)
|
|
||||||
}
|
|
|
@ -1,11 +1,23 @@
|
||||||
use rocket::{form::Form, post, serde::json::Json};
|
use rocket::{form::Form, post, serde::json::Json};
|
||||||
|
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use crate::types::oauth::{App, CredentialApplication};
|
use main::types::api;
|
||||||
|
use rocket::FromForm;
|
||||||
use rocket_db_pools::Connection;
|
use rocket_db_pools::Connection;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, FromForm, Clone)]
|
||||||
|
pub struct OauthApp {
|
||||||
|
pub client_name: String,
|
||||||
|
pub redirect_uris: Vec<String>,
|
||||||
|
pub scopes: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/apps", data = "<app>")]
|
#[post("/apps", data = "<app>")]
|
||||||
pub async fn new_app(app: Form<App>, mut db: Connection<Db>) -> Json<CredentialApplication> {
|
pub async fn new_app(
|
||||||
|
app: Form<OauthApp>,
|
||||||
|
mut db: Connection<Db>,
|
||||||
|
) -> Json<api::CredentialApplication> {
|
||||||
let secret = main::gen_token(15);
|
let secret = main::gen_token(15);
|
||||||
|
|
||||||
// Abort when we encounter a duplicate
|
// Abort when we encounter a duplicate
|
||||||
|
@ -22,7 +34,7 @@ pub async fn new_app(app: Form<App>, mut db: Connection<Db>) -> Json<CredentialA
|
||||||
.await
|
.await
|
||||||
.is_err();
|
.is_err();
|
||||||
|
|
||||||
let mut app: App = app.clone();
|
let mut app: OauthApp = app.clone();
|
||||||
|
|
||||||
if is_app_present {
|
if is_app_present {
|
||||||
let existing_app = sqlx::query!("SELECT * FROM app WHERE client_id = ?1", app.client_name)
|
let existing_app = sqlx::query!("SELECT * FROM app WHERE client_id = ?1", app.client_name)
|
||||||
|
@ -34,7 +46,7 @@ pub async fn new_app(app: Form<App>, mut db: Connection<Db>) -> Json<CredentialA
|
||||||
app.scopes = existing_app.scopes;
|
app.scopes = existing_app.scopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
Json(CredentialApplication {
|
Json(api::CredentialApplication {
|
||||||
name: app.client_name.clone(),
|
name: app.client_name.clone(),
|
||||||
scopes: app.scopes.clone(),
|
scopes: app.scopes.clone(),
|
||||||
redirect_uris: app.redirect_uris.clone(),
|
redirect_uris: app.redirect_uris.clone(),
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
use rocket::{get, serde::json::Json, State};
|
use rocket::{State, get, serde::json::Json};
|
||||||
|
|
||||||
use crate::Config;
|
use main::types::api::{
|
||||||
|
|
||||||
use main::types_rewrite::api::{
|
|
||||||
Accounts, Configuration, Contact, Instance, MediaAttachments, Polls, Registrations, Statuses,
|
Accounts, Configuration, Contact, Instance, MediaAttachments, Polls, Registrations, Statuses,
|
||||||
Thumbnail, Translation, Urls,
|
Thumbnail, Translation, Urls,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[get("/instance")]
|
#[get("/instance")]
|
||||||
pub async fn instance(config: &State<Config>) -> Json<Instance> {
|
pub async fn instance(helpers: &State<crate::Helpers>) -> Json<Instance> {
|
||||||
|
let config = &helpers.config;
|
||||||
Json(Instance {
|
Json(Instance {
|
||||||
domain: config.host().to_string(),
|
domain: config.host().to_string(),
|
||||||
title: "Ferri".to_string(),
|
title: "Ferri".to_string(),
|
||||||
|
|
|
@ -3,8 +3,7 @@ use main::ap::{self, http::HttpClient};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
FromForm, State,
|
FromForm, State,
|
||||||
form::Form,
|
form::Form,
|
||||||
post,
|
get, post,
|
||||||
get,
|
|
||||||
serde::{Deserialize, Serialize, json::Json},
|
serde::{Deserialize, Serialize, json::Json},
|
||||||
};
|
};
|
||||||
use rocket_db_pools::Connection;
|
use rocket_db_pools::Connection;
|
||||||
|
@ -23,14 +22,14 @@ pub struct Status {
|
||||||
#[serde(crate = "rocket::serde")]
|
#[serde(crate = "rocket::serde")]
|
||||||
pub struct StatusContext {
|
pub struct StatusContext {
|
||||||
ancestors: Vec<Status>,
|
ancestors: Vec<Status>,
|
||||||
descendants: Vec<Status>
|
descendants: Vec<Status>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/statuses/<_status>/context")]
|
#[get("/statuses/<_status>/context")]
|
||||||
pub async fn status_context(
|
pub async fn status_context(
|
||||||
_status: &str,
|
_status: &str,
|
||||||
_user: AuthenticatedUser,
|
_user: AuthenticatedUser,
|
||||||
_db: Connection<Db>
|
_db: Connection<Db>,
|
||||||
) -> Json<StatusContext> {
|
) -> Json<StatusContext> {
|
||||||
Json(StatusContext {
|
Json(StatusContext {
|
||||||
ancestors: vec![],
|
ancestors: vec![],
|
||||||
|
@ -56,7 +55,10 @@ async fn create_status(
|
||||||
|
|
||||||
post.save(&mut **db).await;
|
post.save(&mut **db).await;
|
||||||
|
|
||||||
let actor = sqlx::query!("SELECT * FROM actor WHERE id = ?1", "https://fedi.amy.mov/users/9zkygethkdw60001")
|
let actor = sqlx::query!(
|
||||||
|
"SELECT * FROM actor WHERE id = ?1",
|
||||||
|
"https://fedi.amy.mov/users/9zkygethkdw60001"
|
||||||
|
)
|
||||||
.fetch_one(&mut **db)
|
.fetch_one(&mut **db)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -73,11 +75,7 @@ async fn create_status(
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let actor = ap::Actor::from_raw(
|
let actor = ap::Actor::from_raw(actor.id.clone(), actor.inbox.clone(), actor.outbox.clone());
|
||||||
actor.id.clone(),
|
|
||||||
actor.inbox.clone(),
|
|
||||||
actor.outbox.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let req = ap::OutgoingActivity {
|
let req = ap::OutgoingActivity {
|
||||||
req: activity,
|
req: activity,
|
||||||
|
@ -134,19 +132,19 @@ async fn create_status(
|
||||||
#[post("/statuses", data = "<status>")]
|
#[post("/statuses", data = "<status>")]
|
||||||
pub async fn new_status(
|
pub async fn new_status(
|
||||||
db: Connection<Db>,
|
db: Connection<Db>,
|
||||||
http: &State<HttpClient>,
|
helpers: &State<crate::Helpers>,
|
||||||
status: Form<Status>,
|
status: Form<Status>,
|
||||||
user: AuthenticatedUser,
|
user: AuthenticatedUser,
|
||||||
) -> Json<TimelineStatus> {
|
) -> Json<TimelineStatus> {
|
||||||
Json(create_status(user, db, http.inner(), &status).await)
|
Json(create_status(user, db, &helpers.http, &status).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/statuses", data = "<status>", rank = 2)]
|
#[post("/statuses", data = "<status>", rank = 2)]
|
||||||
pub async fn new_status_json(
|
pub async fn new_status_json(
|
||||||
db: Connection<Db>,
|
db: Connection<Db>,
|
||||||
http: &State<HttpClient>,
|
helpers: &State<crate::Helpers>,
|
||||||
status: Json<Status>,
|
status: Json<Status>,
|
||||||
user: AuthenticatedUser,
|
user: AuthenticatedUser,
|
||||||
) -> Json<TimelineStatus> {
|
) -> Json<TimelineStatus> {
|
||||||
Json(create_status(user, db, http.inner(), &status).await)
|
Json(create_status(user, db, &helpers.http, &status).await)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::{AuthenticatedUser, Db, endpoints::api::user::CredentialAcount, Config};
|
use crate::{AuthenticatedUser, Db, endpoints::api::user::CredentialAcount};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
State,
|
State, get,
|
||||||
get,
|
|
||||||
serde::{Deserialize, Serialize, json::Json},
|
serde::{Deserialize, Serialize, json::Json},
|
||||||
};
|
};
|
||||||
use rocket_db_pools::Connection;
|
use rocket_db_pools::Connection;
|
||||||
|
@ -36,9 +35,11 @@ pub struct TimelineStatus {
|
||||||
#[get("/timelines/home")]
|
#[get("/timelines/home")]
|
||||||
pub async fn home(
|
pub async fn home(
|
||||||
mut db: Connection<Db>,
|
mut db: Connection<Db>,
|
||||||
config: &State<Config>,
|
helpers: &State<crate::Helpers>,
|
||||||
_user: AuthenticatedUser,
|
_user: AuthenticatedUser,
|
||||||
) -> Json<Vec<TimelineStatus>> {
|
) -> Json<Vec<TimelineStatus>> {
|
||||||
|
let config = &helpers.config;
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, Debug)]
|
#[derive(sqlx::FromRow, Debug)]
|
||||||
struct Post {
|
struct Post {
|
||||||
is_boost_source: bool,
|
is_boost_source: bool,
|
||||||
|
@ -49,7 +50,7 @@ pub async fn home(
|
||||||
created_at: String,
|
created_at: String,
|
||||||
boosted_post_id: Option<String>,
|
boosted_post_id: Option<String>,
|
||||||
display_name: String,
|
display_name: String,
|
||||||
username: String
|
username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: query! can't cope with this. returns a type error
|
// FIXME: query! can't cope with this. returns a type error
|
||||||
|
@ -78,7 +79,7 @@ pub async fn home(
|
||||||
FROM get_home_timeline_with_boosts
|
FROM get_home_timeline_with_boosts
|
||||||
JOIN post p ON p.id = get_home_timeline_with_boosts.id
|
JOIN post p ON p.id = get_home_timeline_with_boosts.id
|
||||||
JOIN user u ON u.id = p.user_id;
|
JOIN user u ON u.id = p.user_id;
|
||||||
"#
|
"#,
|
||||||
)
|
)
|
||||||
.bind("https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9")
|
.bind("https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9")
|
||||||
.fetch_all(&mut **db)
|
.fetch_all(&mut **db)
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use main::ap;
|
use main::ap;
|
||||||
|
use rocket::response::status::NotFound;
|
||||||
use rocket::{
|
use rocket::{
|
||||||
State, get, post,
|
State, get, post,
|
||||||
serde::{Deserialize, Serialize, json::Json},
|
serde::{Deserialize, Serialize, json::Json},
|
||||||
};
|
};
|
||||||
use rocket_db_pools::Connection;
|
use rocket_db_pools::Connection;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use rocket::response::status::NotFound;
|
|
||||||
|
|
||||||
use crate::timeline::{TimelineAccount, TimelineStatus};
|
use crate::timeline::{TimelineAccount, TimelineStatus};
|
||||||
use crate::{AuthenticatedUser, Db, http::HttpClient};
|
use crate::{AuthenticatedUser, Db};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(crate = "rocket::serde")]
|
#[serde(crate = "rocket::serde")]
|
||||||
|
@ -60,17 +60,19 @@ pub async fn verify_credentials() -> Json<CredentialAcount> {
|
||||||
#[post("/accounts/<uuid>/follow")]
|
#[post("/accounts/<uuid>/follow")]
|
||||||
pub async fn new_follow(
|
pub async fn new_follow(
|
||||||
mut db: Connection<Db>,
|
mut db: Connection<Db>,
|
||||||
http: &State<HttpClient>,
|
helpers: &State<crate::Helpers>,
|
||||||
uuid: &str,
|
uuid: &str,
|
||||||
user: AuthenticatedUser,
|
user: AuthenticatedUser,
|
||||||
) -> Result<(), NotFound<String>> {
|
) -> Result<(), NotFound<String>> {
|
||||||
|
let http = &helpers.http;
|
||||||
|
|
||||||
let follower = ap::User::from_actor_id(&user.actor_id, &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)
|
let followed = ap::User::from_id(uuid, &mut **db)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| NotFound(e.to_string()))?;
|
.map_err(|e| NotFound(e.to_string()))?;
|
||||||
|
|
||||||
let outbox = ap::Outbox::for_user(follower.clone(), http.inner());
|
let outbox = ap::Outbox::for_user(follower.clone(), http);
|
||||||
|
|
||||||
let activity = ap::Activity {
|
let activity = ap::Activity {
|
||||||
id: format!("https://ferri.amy.mov/activities/{}", Uuid::new_v4()),
|
id: format!("https://ferri.amy.mov/activities/{}", Uuid::new_v4()),
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
|
use crate::{Db, OutboundQueue};
|
||||||
|
use main::types::{ap, api};
|
||||||
use rocket::{State, get, response::status};
|
use rocket::{State, get, response::status};
|
||||||
use rocket_db_pools::Connection;
|
use rocket_db_pools::Connection;
|
||||||
use main::ap;
|
|
||||||
use crate::OutboundQueue;
|
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
Db,
|
|
||||||
types::{self, webfinger},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[get("/finger/<account>")]
|
#[get("/finger/<account>")]
|
||||||
pub async fn finger_account(mut db: Connection<Db>, account: &str) -> status::Accepted<String> {
|
pub async fn finger_account(mut db: Connection<Db>, account: &str) -> status::Accepted<String> {
|
||||||
// user@host.com
|
// user@host.com
|
||||||
|
@ -23,7 +17,7 @@ pub async fn finger_account(mut db: Connection<Db>, account: &str) -> status::Ac
|
||||||
VALUES (?1, ?2, ?3)
|
VALUES (?1, ?2, ?3)
|
||||||
ON CONFLICT(id) DO NOTHING
|
ON CONFLICT(id) DO NOTHING
|
||||||
"#,
|
"#,
|
||||||
user.id,
|
user.obj.id.0,
|
||||||
user.inbox,
|
user.inbox,
|
||||||
user.outbox
|
user.outbox
|
||||||
)
|
)
|
||||||
|
@ -41,7 +35,7 @@ pub async fn finger_account(mut db: Connection<Db>, account: &str) -> status::Ac
|
||||||
"#,
|
"#,
|
||||||
uuid,
|
uuid,
|
||||||
username,
|
username,
|
||||||
user.id,
|
user.obj.id.0,
|
||||||
user.preferred_username
|
user.preferred_username
|
||||||
)
|
)
|
||||||
.execute(&mut **db)
|
.execute(&mut **db)
|
||||||
|
@ -51,7 +45,7 @@ pub async fn finger_account(mut db: Connection<Db>, account: &str) -> status::Ac
|
||||||
status::Accepted(format!("https://ferri.amy.mov/users/{}", uuid))
|
status::Accepted(format!("https://ferri.amy.mov/users/{}", uuid))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn resolve_user(acct: &str, host: &str) -> types::Person {
|
pub async fn resolve_user(acct: &str, host: &str) -> ap::Person {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"https://{}/.well-known/webfinger?resource=acct:{}",
|
"https://{}/.well-known/webfinger?resource=acct:{}",
|
||||||
|
@ -62,7 +56,7 @@ pub async fn resolve_user(acct: &str, host: &str) -> types::Person {
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.json::<webfinger::WebfingerResponse>()
|
.json::<api::WebfingerHit>()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -79,21 +73,18 @@ pub async fn resolve_user(acct: &str, host: &str) -> types::Person {
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.json::<types::Person>()
|
.json::<ap::Person>()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/test")]
|
#[get("/test")]
|
||||||
pub async fn test(
|
pub async fn test(outbound: &State<OutboundQueue>, mut db: Connection<Db>) -> &'static str {
|
||||||
outbound: &State<OutboundQueue>,
|
use main::types::{ObjectUuid, api, get};
|
||||||
mut db: Connection<Db>
|
outbound.0.send(main::ap::QueueMessage::Heartbeat);
|
||||||
) -> &'static str {
|
|
||||||
use main::types_rewrite::{ObjectUuid, get, api};
|
|
||||||
outbound.0.send(ap::QueueMessage::Heartbeat);
|
|
||||||
|
|
||||||
let id = ObjectUuid("9b9d497b-2731-435f-a929-e609ca69dac9".to_string());
|
let id = ObjectUuid("9b9d497b-2731-435f-a929-e609ca69dac9".to_string());
|
||||||
let user= dbg!(get::user_by_id(id, &mut **db).await.unwrap());
|
let user = dbg!(get::user_by_id(id, &mut db).await.unwrap());
|
||||||
let apu: api::Account = user.into();
|
let apu: api::Account = user.into();
|
||||||
dbg!(apu);
|
dbg!(apu);
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,19 @@
|
||||||
|
use crate::http_wrapper::HttpWrapper;
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use tracing::Instrument;
|
|
||||||
use rocket::serde::json::serde_json;
|
use rocket::serde::json::serde_json;
|
||||||
use rocket::{State, post};
|
use rocket::{State, post};
|
||||||
use rocket_db_pools::Connection;
|
use rocket_db_pools::Connection;
|
||||||
use sqlx::SqliteConnection;
|
|
||||||
use sqlx::Sqlite;
|
use sqlx::Sqlite;
|
||||||
|
use sqlx::SqliteConnection;
|
||||||
|
use tracing::Instrument;
|
||||||
|
use tracing::{Level, debug, error, event, info, span, warn};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use tracing::{event, span, Level, debug, warn, info, error};
|
|
||||||
use crate::http_wrapper::HttpWrapper;
|
|
||||||
|
|
||||||
use main::types_rewrite::{make, db, ObjectUuid, ObjectUri, self, ap};
|
use crate::Db;
|
||||||
|
use main::types::{DbError, ObjectUri, ObjectUuid, ap, db, make};
|
||||||
|
|
||||||
use crate::{
|
fn handle_delete_activity(activity: ap::DeleteActivity) {
|
||||||
Db,
|
|
||||||
http::HttpClient,
|
|
||||||
types::{content::Post, activity},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn handle_delete_activity(activity: activity::DeleteActivity) {
|
|
||||||
warn!(?activity, "unimplemented delete activity");
|
warn!(?activity, "unimplemented delete activity");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,9 +43,12 @@ async fn create_user(
|
||||||
conn: impl sqlx::Executor<'_, Database = Sqlite>,
|
conn: impl sqlx::Executor<'_, Database = Sqlite>,
|
||||||
) {
|
) {
|
||||||
// HACK: Allow us to formulate a `user@host` username by assuming the actor is on the same host as the user
|
// HACK: Allow us to formulate a `user@host` username by assuming the actor is on the same host as the user
|
||||||
let url = Url::parse(&actor).unwrap();
|
let url = Url::parse(actor).unwrap();
|
||||||
let host = url.host_str().unwrap();
|
let host = url.host_str().unwrap();
|
||||||
info!("creating user '{}'@'{}' ({:#?})", user.preferred_username, host, user);
|
info!(
|
||||||
|
"creating user '{}'@'{}' ({:#?})",
|
||||||
|
user.preferred_username, host, user
|
||||||
|
);
|
||||||
|
|
||||||
let (acct, remote) = if host != "ferri.amy.mov" {
|
let (acct, remote) = if host != "ferri.amy.mov" {
|
||||||
(format!("{}@{}", user.preferred_username, host), true)
|
(format!("{}@{}", user.preferred_username, host), true)
|
||||||
|
@ -87,7 +85,7 @@ async fn create_user(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_follow(
|
async fn create_follow(
|
||||||
activity: &activity::FollowActivity,
|
activity: &ap::FollowActivity,
|
||||||
conn: impl sqlx::Executor<'_, Database = Sqlite>,
|
conn: impl sqlx::Executor<'_, Database = Sqlite>,
|
||||||
) {
|
) {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
|
@ -96,7 +94,7 @@ async fn create_follow(
|
||||||
VALUES ( ?1, ?2, ?3 )
|
VALUES ( ?1, ?2, ?3 )
|
||||||
ON CONFLICT(id) DO NOTHING;
|
ON CONFLICT(id) DO NOTHING;
|
||||||
"#,
|
"#,
|
||||||
activity.id,
|
activity.obj.id.0,
|
||||||
activity.actor,
|
activity.actor,
|
||||||
activity.object
|
activity.object
|
||||||
)
|
)
|
||||||
|
@ -112,7 +110,7 @@ struct RemoteInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_remote_info(actor_url: &str, person: &ap::Person) -> RemoteInfo {
|
fn get_remote_info(actor_url: &str, person: &ap::Person) -> RemoteInfo {
|
||||||
let url = Url::parse(&actor_url).unwrap();
|
let url = Url::parse(actor_url).unwrap();
|
||||||
let host = url.host_str().unwrap();
|
let host = url.host_str().unwrap();
|
||||||
|
|
||||||
let (acct, remote) = if host != "ferri.amy.mov" {
|
let (acct, remote) = if host != "ferri.amy.mov" {
|
||||||
|
@ -126,22 +124,23 @@ fn get_remote_info(actor_url: &str, person: &ap::Person) -> RemoteInfo {
|
||||||
RemoteInfo {
|
RemoteInfo {
|
||||||
acct: acct.to_string(),
|
acct: acct.to_string(),
|
||||||
web_url: url,
|
web_url: url,
|
||||||
is_remote: remote
|
is_remote: remote,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn resolve_actor<'a>(
|
async fn resolve_actor<'a>(
|
||||||
actor_url: &str,
|
actor_url: &str,
|
||||||
http: &HttpWrapper<'a>,
|
http: &HttpWrapper<'a>,
|
||||||
conn: &mut SqliteConnection
|
conn: &mut SqliteConnection,
|
||||||
) -> Result<db::User, types_rewrite::DbError> {
|
) -> Result<db::User, DbError> {
|
||||||
let person = {
|
let person = {
|
||||||
let res = http.get_person(&actor_url).await;
|
let res = http.get_person(actor_url).await;
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
error!("could not load user {}: {}", actor_url, e.to_string());
|
error!("could not load user {}: {}", actor_url, e.to_string());
|
||||||
return Err(types_rewrite::DbError::FetchError(
|
return Err(DbError::FetchError(format!(
|
||||||
format!("could not load user {}: {}", actor_url, e.to_string())
|
"could not load user {}: {}",
|
||||||
))
|
actor_url, e
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
res.unwrap()
|
res.unwrap()
|
||||||
|
@ -172,9 +171,7 @@ async fn resolve_actor<'a>(
|
||||||
url: remote_info.web_url,
|
url: remote_info.web_url,
|
||||||
created_at: main::ap::now(),
|
created_at: main::ap::now(),
|
||||||
|
|
||||||
posts: db::UserPosts {
|
posts: db::UserPosts { last_post_at: None },
|
||||||
last_post_at: None
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(make::new_user(user.clone(), conn).await.unwrap_or(user))
|
Ok(make::new_user(user.clone(), conn).await.unwrap_or(user))
|
||||||
|
@ -182,25 +179,28 @@ async fn resolve_actor<'a>(
|
||||||
|
|
||||||
async fn handle_follow_activity<'a>(
|
async fn handle_follow_activity<'a>(
|
||||||
followed_account: &str,
|
followed_account: &str,
|
||||||
activity: activity::FollowActivity,
|
activity: ap::FollowActivity,
|
||||||
http: HttpWrapper<'a>,
|
http: HttpWrapper<'a>,
|
||||||
mut db: Connection<Db>,
|
mut db: Connection<Db>,
|
||||||
) {
|
) {
|
||||||
let actor = resolve_actor(&activity.actor, &http, &mut **db)
|
let actor = resolve_actor(&activity.actor, &http, &mut db)
|
||||||
.await.unwrap();
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
info!("{:?} follows {}", actor, followed_account);
|
info!("{:?} follows {}", actor, followed_account);
|
||||||
|
|
||||||
create_follow(&activity, &mut **db).await;
|
create_follow(&activity, &mut **db).await;
|
||||||
|
|
||||||
let follower = main::ap::User::from_actor_id(&activity.actor, &mut **db).await;
|
let follower = main::ap::User::from_actor_id(&activity.actor, &mut **db).await;
|
||||||
let followed = main::ap::User::from_id(&followed_account, &mut **db).await.unwrap();
|
let followed = main::ap::User::from_id(followed_account, &mut **db)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let outbox = main::ap::Outbox::for_user(followed.clone(), http.client());
|
let outbox = main::ap::Outbox::for_user(followed.clone(), http.client());
|
||||||
|
|
||||||
let activity = main::ap::Activity {
|
let activity = main::ap::Activity {
|
||||||
id: format!("https://ferri.amy.mov/activities/{}", Uuid::new_v4()),
|
id: format!("https://ferri.amy.mov/activities/{}", Uuid::new_v4()),
|
||||||
ty: main::ap::ActivityType::Accept,
|
ty: main::ap::ActivityType::Accept,
|
||||||
object: activity.id,
|
object: activity.obj.id.0,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -217,7 +217,7 @@ async fn handle_follow_activity<'a>(
|
||||||
outbox.post(req).await;
|
outbox.post(req).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_like_activity(activity: activity::LikeActivity, mut db: Connection<Db>) {
|
async fn handle_like_activity(activity: ap::LikeActivity, mut db: Connection<Db>) {
|
||||||
warn!(?activity, "unimplemented like activity");
|
warn!(?activity, "unimplemented like activity");
|
||||||
|
|
||||||
let target_post = sqlx::query!("SELECT * FROM post WHERE uri = ?1", activity.object)
|
let target_post = sqlx::query!("SELECT * FROM post WHERE uri = ?1", activity.object)
|
||||||
|
@ -232,17 +232,17 @@ async fn handle_like_activity(activity: activity::LikeActivity, mut db: Connecti
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_boost_activity<'a>(
|
async fn handle_boost_activity<'a>(
|
||||||
activity: activity::BoostActivity,
|
activity: ap::BoostActivity,
|
||||||
http: HttpWrapper<'a>,
|
http: HttpWrapper<'a>,
|
||||||
mut db: Connection<Db>,
|
mut db: Connection<Db>,
|
||||||
) {
|
) {
|
||||||
let key_id = "https://ferri.amy.mov/users/amy#main-key";
|
let key_id = "https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9#main-key";
|
||||||
dbg!(&activity);
|
dbg!(&activity);
|
||||||
let post = http
|
let post = http
|
||||||
.client()
|
.client()
|
||||||
.get(&activity.object)
|
.get(&activity.object)
|
||||||
.activity()
|
.activity()
|
||||||
.sign(&key_id)
|
.sign(key_id)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -252,10 +252,10 @@ async fn handle_boost_activity<'a>(
|
||||||
|
|
||||||
info!("{}", post);
|
info!("{}", post);
|
||||||
|
|
||||||
let post = serde_json::from_str::<Post>(&post);
|
let post = serde_json::from_str::<ap::Post>(&post);
|
||||||
if let Err(e) = post {
|
if let Err(e) = post {
|
||||||
error!(?e, "when decoding post");
|
error!(?e, "when decoding post");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let post = post.unwrap();
|
let post = post.unwrap();
|
||||||
|
@ -265,15 +265,19 @@ async fn handle_boost_activity<'a>(
|
||||||
|
|
||||||
let post_user = http.get_person(&attribution).await;
|
let post_user = http.get_person(&attribution).await;
|
||||||
if let Err(e) = post_user {
|
if let Err(e) = post_user {
|
||||||
error!("could not load post_user {}: {}", attribution, e.to_string());
|
error!(
|
||||||
return
|
"could not load post_user {}: {}",
|
||||||
|
attribution,
|
||||||
|
e.to_string()
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
let post_user = post_user.unwrap();
|
let post_user = post_user.unwrap();
|
||||||
|
|
||||||
let user = http.get_person(&activity.actor).await;
|
let user = http.get_person(&activity.actor).await;
|
||||||
if let Err(e) = user {
|
if let Err(e) = user {
|
||||||
error!("could not load actor {}: {}", activity.actor, e.to_string());
|
error!("could not load actor {}: {}", activity.actor, e.to_string());
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
let user = user.unwrap();
|
let user = user.unwrap();
|
||||||
|
|
||||||
|
@ -300,40 +304,58 @@ async fn handle_boost_activity<'a>(
|
||||||
let attr_id = attributed_user.id();
|
let attr_id = attributed_user.id();
|
||||||
// HACK: ON CONFLICT is to avoid duplicate remote posts coming in
|
// HACK: ON CONFLICT is to avoid duplicate remote posts coming in
|
||||||
// check this better in future
|
// check this better in future
|
||||||
sqlx::query!("
|
sqlx::query!(
|
||||||
|
"
|
||||||
INSERT INTO post (id, uri, user_id, content, created_at)
|
INSERT INTO post (id, uri, user_id, content, created_at)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5)
|
VALUES (?1, ?2, ?3, ?4, ?5)
|
||||||
ON CONFLICT(uri) DO NOTHING
|
ON CONFLICT(uri) DO NOTHING
|
||||||
", reblog_id, post.id, attr_id, post.content, post.ts)
|
",
|
||||||
|
reblog_id,
|
||||||
|
post.obj.id.0,
|
||||||
|
attr_id,
|
||||||
|
post.content,
|
||||||
|
post.ts
|
||||||
|
)
|
||||||
.execute(&mut **db)
|
.execute(&mut **db)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let uri = format!("https://ferri.amy.mov/users/{}/posts/{}", actor_user.id(), base_id);
|
let uri = format!(
|
||||||
|
"https://ferri.amy.mov/users/{}/posts/{}",
|
||||||
|
actor_user.id(),
|
||||||
|
base_id
|
||||||
|
);
|
||||||
let user_id = actor_user.id();
|
let user_id = actor_user.id();
|
||||||
|
|
||||||
sqlx::query!("
|
sqlx::query!(
|
||||||
|
"
|
||||||
INSERT INTO post (id, uri, user_id, content, created_at, boosted_post_id)
|
INSERT INTO post (id, uri, user_id, content, created_at, boosted_post_id)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6)
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6)
|
||||||
", base_id, uri, user_id, "", now, reblog_id)
|
",
|
||||||
|
base_id,
|
||||||
|
uri,
|
||||||
|
user_id,
|
||||||
|
"",
|
||||||
|
now,
|
||||||
|
reblog_id
|
||||||
|
)
|
||||||
.execute(&mut **db)
|
.execute(&mut **db)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_create_activity<'a>(
|
async fn handle_create_activity<'a>(
|
||||||
activity: activity::CreateActivity,
|
activity: ap::CreateActivity,
|
||||||
http: HttpWrapper<'a>,
|
http: HttpWrapper<'a>,
|
||||||
mut db: Connection<Db>,
|
mut db: Connection<Db>,
|
||||||
) {
|
) {
|
||||||
assert!(&activity.object.ty == "Note");
|
assert!(activity.object.ty == ap::ActivityType::Note);
|
||||||
debug!("resolving user {}", activity.actor);
|
debug!("resolving user {}", activity.actor);
|
||||||
|
|
||||||
let user = http.get_person(&activity.actor).await;
|
let user = http.get_person(&activity.actor).await;
|
||||||
if let Err(e) = user {
|
if let Err(e) = user {
|
||||||
error!("could not load user {}: {}", activity.actor, e.to_string());
|
error!("could not load user {}: {}", activity.actor, e.to_string());
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = user.unwrap();
|
let user = user.unwrap();
|
||||||
|
@ -351,7 +373,7 @@ async fn handle_create_activity<'a>(
|
||||||
let now = Local::now().to_rfc3339();
|
let now = Local::now().to_rfc3339();
|
||||||
let content = activity.object.content.clone();
|
let content = activity.object.content.clone();
|
||||||
let post_id = Uuid::new_v4().to_string();
|
let post_id = Uuid::new_v4().to_string();
|
||||||
let uri = activity.id;
|
let uri = activity.obj.id.0;
|
||||||
|
|
||||||
info!(post_id, "creating post");
|
info!(post_id, "creating post");
|
||||||
|
|
||||||
|
@ -372,46 +394,45 @@ async fn handle_create_activity<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/users/<user>/inbox", data = "<body>")]
|
#[post("/users/<user>/inbox", data = "<body>")]
|
||||||
pub async fn inbox(db: Connection<Db>, http: &State<HttpClient>, user: &str, body: String) {
|
pub async fn inbox(db: Connection<Db>, helpers: &State<crate::Helpers>, user: &str, body: String) {
|
||||||
let min = serde_json::from_str::<activity::MinimalActivity>(&body).unwrap();
|
debug!("body in inbox: {}", body);
|
||||||
|
|
||||||
|
let min = serde_json::from_str::<ap::MinimalActivity>(&body).unwrap();
|
||||||
let inbox_span = span!(Level::INFO, "inbox-post", user_id = user);
|
let inbox_span = span!(Level::INFO, "inbox-post", user_id = user);
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
event!(Level::INFO, ?min, "received an activity");
|
event!(Level::INFO, ?min, "received an activity");
|
||||||
|
|
||||||
let key_id = "https://ferri.amy.mov/users/amy#main-key";
|
let key_id = "https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9#main-key";
|
||||||
let wrapper = HttpWrapper::new(http.inner(), key_id);
|
let wrapper = HttpWrapper::new(&helpers.http, key_id);
|
||||||
|
|
||||||
match min.ty.as_str() {
|
match min.ty {
|
||||||
"Delete" => {
|
ap::ActivityType::Delete => {
|
||||||
let activity = serde_json::from_str::<activity::DeleteActivity>(&body).unwrap();
|
let activity = serde_json::from_str::<ap::DeleteActivity>(&body).unwrap();
|
||||||
handle_delete_activity(activity);
|
handle_delete_activity(activity);
|
||||||
}
|
}
|
||||||
"Follow" => {
|
ap::ActivityType::Follow => {
|
||||||
let activity = serde_json::from_str::<activity::FollowActivity>(&body).unwrap();
|
let activity = serde_json::from_str::<ap::FollowActivity>(&body).unwrap();
|
||||||
handle_follow_activity(user, activity, wrapper, db).await;
|
handle_follow_activity(user, activity, wrapper, db).await;
|
||||||
}
|
}
|
||||||
"Create" => {
|
ap::ActivityType::Create => {
|
||||||
let activity = serde_json::from_str::<activity::CreateActivity>(&body).unwrap();
|
let activity = serde_json::from_str::<ap::CreateActivity>(&body).unwrap();
|
||||||
handle_create_activity(activity, wrapper, db).await;
|
handle_create_activity(activity, wrapper, db).await;
|
||||||
}
|
}
|
||||||
"Like" => {
|
ap::ActivityType::Like => {
|
||||||
let activity = serde_json::from_str::<activity::LikeActivity>(&body).unwrap();
|
let activity = serde_json::from_str::<ap::LikeActivity>(&body).unwrap();
|
||||||
handle_like_activity(activity, db).await;
|
handle_like_activity(activity, db).await;
|
||||||
}
|
}
|
||||||
"Announce" => {
|
ap::ActivityType::Announce => {
|
||||||
let activity = serde_json::from_str::<activity::BoostActivity>(&body).unwrap();
|
let activity = serde_json::from_str::<ap::BoostActivity>(&body).unwrap();
|
||||||
handle_boost_activity(activity, wrapper, db).await;
|
handle_boost_activity(activity, wrapper, db).await;
|
||||||
}
|
}
|
||||||
|
ap::ActivityType::Note => todo!(),
|
||||||
act => {
|
ap::ActivityType::Accept => todo!(),
|
||||||
warn!(act, body, "unknown activity");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("body in inbox: {}", body);
|
|
||||||
}
|
|
||||||
// Allow the span to be used inside the async code
|
// Allow the span to be used inside the async code
|
||||||
// https://docs.rs/tracing/latest/tracing/span/struct.EnteredSpan.html#deref-methods-Span
|
// https://docs.rs/tracing/latest/tracing/span/struct.EnteredSpan.html#deref-methods-Span
|
||||||
.instrument(inbox_span).await;
|
.instrument(inbox_span)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,12 +77,16 @@ pub struct NewTokenRequest {
|
||||||
|
|
||||||
#[post("/oauth/token", data = "<req>")]
|
#[post("/oauth/token", data = "<req>")]
|
||||||
pub async fn new_token(req: Form<NewTokenRequest>, mut db: Connection<Db>) -> Json<Token> {
|
pub async fn new_token(req: Form<NewTokenRequest>, mut db: Connection<Db>) -> Json<Token> {
|
||||||
let oauth = sqlx::query!("
|
let oauth = sqlx::query!(
|
||||||
|
"
|
||||||
SELECT o.*, a.*
|
SELECT o.*, a.*
|
||||||
FROM oauth o
|
FROM oauth o
|
||||||
INNER JOIN auth a ON a.token = ?2
|
INNER JOIN auth a ON a.token = ?2
|
||||||
WHERE o.access_token = ?1
|
WHERE o.access_token = ?1
|
||||||
", req.code, req.code)
|
",
|
||||||
|
req.code,
|
||||||
|
req.code
|
||||||
|
)
|
||||||
.fetch_one(&mut **db)
|
.fetch_one(&mut **db)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
use main::ap;
|
use rocket::{
|
||||||
use rocket::{get, http::ContentType, serde::json::Json, State, Responder};
|
Responder, State, get,
|
||||||
use rocket_db_pools::Connection;
|
http::ContentType,
|
||||||
use rocket::response::Redirect;
|
response::{Redirect, status::NotFound},
|
||||||
use rocket::response::status::NotFound;
|
serde::json::Json,
|
||||||
|
|
||||||
use crate::{
|
|
||||||
Config,
|
|
||||||
Db,
|
|
||||||
types::{OrderedCollection, Person, UserKey, content},
|
|
||||||
};
|
};
|
||||||
|
use rocket_db_pools::Connection;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use main::types::{Object, ObjectUri, ObjectUuid, ap, as_context, get};
|
||||||
|
|
||||||
use super::activity_type;
|
use super::activity_type;
|
||||||
|
use crate::Db;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct OrderedCollection {
|
||||||
|
ty: String,
|
||||||
|
total_items: i64,
|
||||||
|
ordered_items: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/users/<_user>/inbox")]
|
#[get("/users/<_user>/inbox")]
|
||||||
pub async fn inbox(_user: String) -> Json<OrderedCollection> {
|
pub async fn inbox(_user: String) -> Json<OrderedCollection> {
|
||||||
|
@ -31,8 +38,11 @@ pub async fn outbox(_user: String) -> Json<OrderedCollection> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/users/<uuid>/followers")]
|
#[get("/users/<uuid>/followers")]
|
||||||
pub async fn followers(mut db: Connection<Db>, uuid: &str) -> Result<Json<OrderedCollection>, NotFound<String>> {
|
pub async fn followers(
|
||||||
let target = ap::User::from_id(uuid, &mut **db)
|
mut db: Connection<Db>,
|
||||||
|
uuid: &str,
|
||||||
|
) -> Result<Json<OrderedCollection>, NotFound<String>> {
|
||||||
|
let target = main::ap::User::from_id(uuid, &mut **db)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| NotFound(e.to_string()))?;
|
.map_err(|e| NotFound(e.to_string()))?;
|
||||||
|
|
||||||
|
@ -60,8 +70,11 @@ pub async fn followers(mut db: Connection<Db>, uuid: &str) -> Result<Json<Ordere
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/users/<uuid>/following")]
|
#[get("/users/<uuid>/following")]
|
||||||
pub async fn following(mut db: Connection<Db>, uuid: &str) -> Result<Json<OrderedCollection>, NotFound<String>> {
|
pub async fn following(
|
||||||
let target = ap::User::from_id(uuid, &mut **db)
|
mut db: Connection<Db>,
|
||||||
|
uuid: &str,
|
||||||
|
) -> Result<Json<OrderedCollection>, NotFound<String>> {
|
||||||
|
let target = main::ap::User::from_id(uuid, &mut **db)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| NotFound(e.to_string()))?;
|
.map_err(|e| NotFound(e.to_string()))?;
|
||||||
|
|
||||||
|
@ -91,10 +104,11 @@ pub async fn following(mut db: Connection<Db>, uuid: &str) -> Result<Json<Ordere
|
||||||
#[get("/users/<uuid>/posts/<post>")]
|
#[get("/users/<uuid>/posts/<post>")]
|
||||||
pub async fn post(
|
pub async fn post(
|
||||||
mut db: Connection<Db>,
|
mut db: Connection<Db>,
|
||||||
config: &State<Config>,
|
helpers: &State<crate::Helpers>,
|
||||||
uuid: &str,
|
uuid: &str,
|
||||||
post: String,
|
post: String,
|
||||||
) -> (ContentType, Json<content::Post>) {
|
) -> (ContentType, Json<ap::Post>) {
|
||||||
|
let config = &helpers.config;
|
||||||
let post = sqlx::query!(
|
let post = sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
SELECT * FROM post WHERE id = ?1
|
SELECT * FROM post WHERE id = ?1
|
||||||
|
@ -107,11 +121,13 @@ pub async fn post(
|
||||||
|
|
||||||
(
|
(
|
||||||
activity_type(),
|
activity_type(),
|
||||||
Json(content::Post {
|
Json(ap::Post {
|
||||||
context: "https://www.w3.org/ns/activitystreams".to_string(),
|
obj: Object {
|
||||||
id: config.post_url(uuid, &post.id),
|
context: as_context(),
|
||||||
|
id: ObjectUri(config.post_url(uuid, &post.id)),
|
||||||
|
},
|
||||||
attributed_to: Some(config.user_url(uuid)),
|
attributed_to: Some(config.user_url(uuid)),
|
||||||
ty: "Note".to_string(),
|
ty: ap::ActivityType::Note,
|
||||||
content: post.content,
|
content: post.content,
|
||||||
ts: post.created_at,
|
ts: post.created_at,
|
||||||
to: vec![config.followers_url(uuid)],
|
to: vec![config.followers_url(uuid)],
|
||||||
|
@ -138,38 +154,17 @@ fn ap_ok<T, E>(t: T) -> Result<ActivityResponse<T>, E> {
|
||||||
#[get("/users/<uuid>")]
|
#[get("/users/<uuid>")]
|
||||||
pub async fn user(
|
pub async fn user(
|
||||||
mut db: Connection<Db>,
|
mut db: Connection<Db>,
|
||||||
config: &State<Config>,
|
uuid: &str,
|
||||||
uuid: &str
|
) -> Result<ActivityResponse<Json<ap::Person>>, UserFetchError> {
|
||||||
) -> Result<ActivityResponse<Json<Person>>, UserFetchError> {
|
|
||||||
if uuid == "amy" {
|
if uuid == "amy" {
|
||||||
return Err(
|
return Err(UserFetchError::Moved(Redirect::permanent(
|
||||||
UserFetchError::Moved(
|
"https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9",
|
||||||
Redirect::permanent("https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9")
|
)));
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = ap::User::from_id(uuid, &mut **db)
|
let user = get::user_by_id(ObjectUuid(uuid.to_string()), &mut db)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| UserFetchError::NotFound(NotFound(e.to_string())))?;
|
.map_err(|e| UserFetchError::NotFound(NotFound(e.to_string())))?;
|
||||||
|
|
||||||
let person = Person {
|
ap_ok(Json(user.into()))
|
||||||
context: "https://www.w3.org/ns/activitystreams".to_string(),
|
|
||||||
ty: "Person".to_string(),
|
|
||||||
id: config.user_url(user.id()),
|
|
||||||
name: user.username().to_string(),
|
|
||||||
preferred_username: user.display_name().to_string(),
|
|
||||||
followers: config.followers_url(user.id()),
|
|
||||||
following: config.following_url(user.id()),
|
|
||||||
summary: format!("ferri {}", user.username()),
|
|
||||||
inbox: config.inbox_url(user.id()),
|
|
||||||
outbox: config.outbox_url(user.id()),
|
|
||||||
public_key: Some(UserKey {
|
|
||||||
id: format!("https://ferri.amy.mov/users/{}#main-key", uuid),
|
|
||||||
owner: config.user_url(user.id()),
|
|
||||||
public_key: include_str!("../../../public.pem").to_string(),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
ap_ok(Json(person))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
|
use crate::Db;
|
||||||
use main::ap;
|
use main::ap;
|
||||||
use rocket::{get, serde::json::Json, State};
|
use main::types::api;
|
||||||
|
use rocket::{State, get, serde::json::Json};
|
||||||
use rocket_db_pools::Connection;
|
use rocket_db_pools::Connection;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
Config,
|
|
||||||
Db,
|
|
||||||
types::webfinger::{Link, WebfingerResponse},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[get("/.well-known/host-meta")]
|
#[get("/.well-known/host-meta")]
|
||||||
pub async fn host_meta() -> &'static str {
|
pub async fn host_meta() -> &'static str {
|
||||||
r#"
|
r#"
|
||||||
|
@ -21,26 +17,31 @@ pub async fn host_meta() -> &'static str {
|
||||||
|
|
||||||
// https://mastodon.social/.well-known/webfinger?resource=acct:gargron@mastodon.social
|
// https://mastodon.social/.well-known/webfinger?resource=acct:gargron@mastodon.social
|
||||||
#[get("/.well-known/webfinger?<resource>")]
|
#[get("/.well-known/webfinger?<resource>")]
|
||||||
pub async fn webfinger(mut db: Connection<Db>, config: &State<Config>, resource: &str) -> Json<WebfingerResponse> {
|
pub async fn webfinger(
|
||||||
|
mut db: Connection<Db>,
|
||||||
|
helpers: &State<crate::Helpers>,
|
||||||
|
resource: &str,
|
||||||
|
) -> Json<api::WebfingerHit> {
|
||||||
|
let config = &helpers.config;
|
||||||
info!(?resource, "incoming webfinger request");
|
info!(?resource, "incoming webfinger request");
|
||||||
|
|
||||||
let acct = resource.strip_prefix("acct:").unwrap();
|
let acct = resource.strip_prefix("acct:").unwrap();
|
||||||
let (user, _) = acct.split_once("@").unwrap();
|
let (user, _) = acct.split_once("@").unwrap();
|
||||||
let user = ap::User::from_username(user, &mut **db).await;
|
let user = ap::User::from_username(user, &mut **db).await;
|
||||||
|
|
||||||
Json(WebfingerResponse {
|
Json(api::WebfingerHit {
|
||||||
subject: resource.to_string(),
|
subject: resource.to_string(),
|
||||||
aliases: vec![
|
aliases: vec![
|
||||||
config.user_url(user.id()),
|
config.user_url(user.id()),
|
||||||
config.user_web_url(user.username())
|
config.user_web_url(user.username()),
|
||||||
],
|
],
|
||||||
links: vec![
|
links: vec![
|
||||||
Link {
|
api::WebfingerLink {
|
||||||
rel: "http://webfinger.net/rel/profile-page".to_string(),
|
rel: "http://webfinger.net/rel/profile-page".to_string(),
|
||||||
ty: Some("text/html".to_string()),
|
ty: Some("text/html".to_string()),
|
||||||
href: Some(config.user_web_url(user.username())),
|
href: Some(config.user_web_url(user.username())),
|
||||||
},
|
},
|
||||||
Link {
|
api::WebfingerLink {
|
||||||
rel: "self".to_string(),
|
rel: "self".to_string(),
|
||||||
ty: Some("application/activity+json".to_string()),
|
ty: Some("application/activity+json".to_string()),
|
||||||
href: Some(config.user_url(user.id())),
|
href: Some(config.user_url(user.id())),
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use thiserror::Error;
|
|
||||||
use tracing::{error, event, Level};
|
|
||||||
use crate::http::HttpClient;
|
use crate::http::HttpClient;
|
||||||
use main::types_rewrite::ap;
|
use main::types::ap;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use thiserror::Error;
|
||||||
|
use tracing::{Level, error, event};
|
||||||
|
|
||||||
pub struct HttpWrapper<'a> {
|
pub struct HttpWrapper<'a> {
|
||||||
client: &'a HttpClient,
|
client: &'a HttpClient,
|
||||||
key_id: &'a str
|
key_id: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
@ -19,21 +19,23 @@ pub enum HttpError {
|
||||||
|
|
||||||
impl<'a> HttpWrapper<'a> {
|
impl<'a> HttpWrapper<'a> {
|
||||||
pub fn new(client: &'a HttpClient, key_id: &'a str) -> HttpWrapper<'a> {
|
pub fn new(client: &'a HttpClient, key_id: &'a str) -> HttpWrapper<'a> {
|
||||||
Self {
|
Self { client, key_id }
|
||||||
client,
|
|
||||||
key_id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn client(&self) -> &'a HttpClient {
|
pub fn client(&self) -> &'a HttpClient {
|
||||||
&self.client
|
self.client
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get<T : serde::de::DeserializeOwned + Debug>(&self, ty: &str, url: &str) -> Result<T, HttpError> {
|
async fn get<T: serde::de::DeserializeOwned + Debug>(
|
||||||
|
&self,
|
||||||
|
ty: &str,
|
||||||
|
url: &str,
|
||||||
|
) -> Result<T, HttpError> {
|
||||||
let ty = ty.to_string();
|
let ty = ty.to_string();
|
||||||
event!(Level::INFO, url, "loading {}", ty);
|
event!(Level::INFO, url, "loading {}", ty);
|
||||||
|
|
||||||
let http_result = self.client
|
let http_result = self
|
||||||
|
.client
|
||||||
.get(url)
|
.get(url)
|
||||||
.sign(self.key_id)
|
.sign(self.key_id)
|
||||||
.activity()
|
.activity()
|
||||||
|
@ -51,15 +53,15 @@ impl <'a> HttpWrapper<'a> {
|
||||||
return Err(HttpError::LoadFailure(ty, url.to_string()));
|
return Err(HttpError::LoadFailure(ty, url.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let decoded = serde_json::from_str::<T>(&raw_body.unwrap());
|
let raw_body = raw_body.unwrap();
|
||||||
|
let decoded = serde_json::from_str::<T>(&raw_body);
|
||||||
|
|
||||||
if let Err(e) = decoded {
|
if let Err(e) = decoded {
|
||||||
error!("could not parse {} for url {}: {:#?}", ty, url, e);
|
error!(
|
||||||
return Err(HttpError::ParseFailure(
|
"could not parse {} for url {}: {:#?} {}",
|
||||||
ty,
|
ty, url, e, &raw_body
|
||||||
url.to_string(),
|
);
|
||||||
e.to_string()
|
return Err(HttpError::ParseFailure(ty, url.to_string(), e.to_string()));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(decoded.unwrap())
|
Ok(decoded.unwrap())
|
||||||
|
|
|
@ -19,7 +19,6 @@ use rocket_db_pools::{Connection, Database, sqlx};
|
||||||
|
|
||||||
mod cors;
|
mod cors;
|
||||||
mod endpoints;
|
mod endpoints;
|
||||||
mod types;
|
|
||||||
mod http_wrapper;
|
mod http_wrapper;
|
||||||
|
|
||||||
#[derive(Database)]
|
#[derive(Database)]
|
||||||
|
@ -32,9 +31,7 @@ async fn user_profile() -> (ContentType, &'static str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/activities/<_activity>")]
|
#[get("/activities/<_activity>")]
|
||||||
async fn activity_endpoint(_activity: String) {
|
async fn activity_endpoint(_activity: String) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AuthenticatedUser {
|
pub struct AuthenticatedUser {
|
||||||
|
@ -45,8 +42,7 @@ pub struct AuthenticatedUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LoginError {
|
pub enum LoginError {}
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::async_trait]
|
#[rocket::async_trait]
|
||||||
impl<'a> FromRequest<'a> for AuthenticatedUser {
|
impl<'a> FromRequest<'a> for AuthenticatedUser {
|
||||||
|
@ -90,6 +86,11 @@ impl<'a> FromRequest<'a> for AuthenticatedUser {
|
||||||
pub struct OutboundQueue(pub ap::QueueHandle);
|
pub struct OutboundQueue(pub ap::QueueHandle);
|
||||||
pub struct InboundQueue(pub ap::QueueHandle);
|
pub struct InboundQueue(pub ap::QueueHandle);
|
||||||
|
|
||||||
|
pub struct Helpers {
|
||||||
|
http: http::HttpClient,
|
||||||
|
config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn launch(cfg: Config) -> Rocket<Build> {
|
pub fn launch(cfg: Config) -> Rocket<Build> {
|
||||||
let format = fmt::format()
|
let format = fmt::format()
|
||||||
.with_ansi(true)
|
.with_ansi(true)
|
||||||
|
@ -111,10 +112,11 @@ pub fn launch(cfg: Config) -> Rocket<Build> {
|
||||||
let inbound = ap::RequestQueue::new("inbound");
|
let inbound = ap::RequestQueue::new("inbound");
|
||||||
let inbound_handle = inbound.spawn();
|
let inbound_handle = inbound.spawn();
|
||||||
|
|
||||||
let http_client = http::HttpClient::new();
|
|
||||||
build()
|
build()
|
||||||
.manage(cfg)
|
.manage(Helpers {
|
||||||
.manage(http_client)
|
config: cfg,
|
||||||
|
http: http::HttpClient::new(),
|
||||||
|
})
|
||||||
.manage(OutboundQueue(outbound_handle))
|
.manage(OutboundQueue(outbound_handle))
|
||||||
.manage(InboundQueue(inbound_handle))
|
.manage(InboundQueue(inbound_handle))
|
||||||
.attach(Db::init())
|
.attach(Db::init())
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
use rocket::serde::{Deserialize, Serialize};
|
|
||||||
use crate::types::content::Post;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
pub struct MinimalActivity {
|
|
||||||
pub id: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub ty: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type DeleteActivity = BasicActivity;
|
|
||||||
pub type LikeActivity = BasicActivity;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
pub struct BasicActivity {
|
|
||||||
pub id: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub ty: String,
|
|
||||||
|
|
||||||
pub object: String,
|
|
||||||
pub actor: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
pub struct CreateActivity {
|
|
||||||
pub id: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub ty: String,
|
|
||||||
|
|
||||||
pub object: Post,
|
|
||||||
pub actor: String,
|
|
||||||
pub to: Vec<String>,
|
|
||||||
pub cc: Vec<String>,
|
|
||||||
|
|
||||||
#[serde(rename = "published")]
|
|
||||||
pub ts: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
pub struct FollowActivity {
|
|
||||||
pub id: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub ty: String,
|
|
||||||
|
|
||||||
pub object: String,
|
|
||||||
pub actor: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
pub struct AcceptActivity {
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub ty: String,
|
|
||||||
|
|
||||||
pub object: String,
|
|
||||||
pub actor: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
pub struct BoostActivity {
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub ty: String,
|
|
||||||
|
|
||||||
pub id: String,
|
|
||||||
pub actor: String,
|
|
||||||
pub published: String,
|
|
||||||
pub to: Vec<String>,
|
|
||||||
pub cc: Vec<String>,
|
|
||||||
pub object: String,
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
use rocket::serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
#[deprecated]
|
|
||||||
pub struct Post {
|
|
||||||
// FIXME: This is because Masto sends an array but we don't care
|
|
||||||
#[serde(rename = "@context")]
|
|
||||||
#[serde(skip_deserializing)]
|
|
||||||
pub context: String,
|
|
||||||
pub id: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub ty: String,
|
|
||||||
#[serde(rename = "published")]
|
|
||||||
pub ts: String,
|
|
||||||
pub content: String,
|
|
||||||
pub to: Vec<String>,
|
|
||||||
pub cc: Vec<String>,
|
|
||||||
|
|
||||||
#[serde(rename = "attributedTo")]
|
|
||||||
pub attributed_to: Option<String>
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
pub mod activity;
|
|
||||||
pub mod content;
|
|
||||||
pub mod oauth;
|
|
||||||
pub mod webfinger;
|
|
||||||
|
|
||||||
use rocket::serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
#[deprecated]
|
|
||||||
pub struct UserKey {
|
|
||||||
pub id: String,
|
|
||||||
pub owner: String,
|
|
||||||
#[serde(rename = "publicKeyPem")]
|
|
||||||
pub public_key: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
#[deprecated]
|
|
||||||
pub struct Person {
|
|
||||||
// FIXME: This is because Masto sends an array but we don't care
|
|
||||||
#[serde(rename = "@context")]
|
|
||||||
#[serde(skip_deserializing)]
|
|
||||||
pub context: String,
|
|
||||||
|
|
||||||
pub id: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub ty: String,
|
|
||||||
pub following: String,
|
|
||||||
pub followers: String,
|
|
||||||
pub summary: String,
|
|
||||||
pub inbox: String,
|
|
||||||
pub outbox: String,
|
|
||||||
pub preferred_username: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub name: String,
|
|
||||||
pub public_key: Option<UserKey>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
pub struct Object {
|
|
||||||
pub id: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub ty: String,
|
|
||||||
pub object: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
pub struct OrderedCollection {
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub ty: String,
|
|
||||||
pub total_items: u64,
|
|
||||||
pub ordered_items: Vec<String>,
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
use rocket::{
|
|
||||||
FromForm,
|
|
||||||
serde::{Deserialize, Serialize},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, FromForm, Clone)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
#[deprecated]
|
|
||||||
pub struct App {
|
|
||||||
pub client_name: String,
|
|
||||||
pub redirect_uris: Vec<String>,
|
|
||||||
pub scopes: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
#[deprecated]
|
|
||||||
pub struct CredentialApplication {
|
|
||||||
pub name: String,
|
|
||||||
pub scopes: String,
|
|
||||||
pub redirect_uris: Vec<String>,
|
|
||||||
pub client_id: String,
|
|
||||||
pub client_secret: String,
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
use rocket::serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
#[deprecated]
|
|
||||||
pub struct Link {
|
|
||||||
pub rel: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub ty: Option<String>,
|
|
||||||
pub href: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
#[deprecated]
|
|
||||||
pub struct WebfingerResponse {
|
|
||||||
pub subject: String,
|
|
||||||
pub aliases: Vec<String>,
|
|
||||||
pub links: Vec<Link>,
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue