feat: add visibility feature to thoughts, including new enum, database migration, and update related endpoints and tests

This commit is contained in:
2025-09-06 17:42:50 +02:00
parent 0abd275946
commit 82c6de8da8
12 changed files with 307 additions and 17 deletions

View File

@@ -1,8 +1,10 @@
mod auth;
mod json;
mod optional_auth;
mod valid;
pub use auth::AuthUser;
pub use auth::Claims;
pub use json::Json;
pub use optional_auth::OptionalAuthUser;
pub use valid::Valid;

View File

@@ -0,0 +1,21 @@
use super::AuthUser;
use crate::error::ApiError;
use app::state::AppState;
use axum::{extract::FromRequestParts, http::request::Parts};
pub struct OptionalAuthUser(pub Option<AuthUser>);
impl FromRequestParts<AppState> for OptionalAuthUser {
type Rejection = ApiError;
async fn from_request_parts(
parts: &mut Parts,
state: &AppState,
) -> Result<Self, Self::Rejection> {
match AuthUser::from_request_parts(parts, state).await {
Ok(user) => Ok(OptionalAuthUser(Some(user))),
// If the user is not authenticated for any reason, we just treat them as a guest.
Err(_) => Ok(OptionalAuthUser(None)),
}
}
}

View File

@@ -24,9 +24,11 @@ async fn feed_get(
auth_user: AuthUser,
) -> Result<impl IntoResponse, ApiError> {
let following_ids = get_following_ids(&state.conn, auth_user.id).await?;
let mut thoughts_with_authors = get_feed_for_user(&state.conn, following_ids).await?;
let mut thoughts_with_authors =
get_feed_for_user(&state.conn, following_ids, Some(auth_user.id)).await?;
let own_thoughts = get_feed_for_user(&state.conn, vec![auth_user.id]).await?;
let own_thoughts =
get_feed_for_user(&state.conn, vec![auth_user.id], Some(auth_user.id)).await?;
thoughts_with_authors.extend(own_thoughts);
let thoughts_schema: Vec<ThoughtSchema> = thoughts_with_authors

View File

@@ -1,4 +1,4 @@
use crate::error::ApiError;
use crate::{error::ApiError, extractor::OptionalAuthUser};
use app::{
persistence::{tag, thought::get_thoughts_by_tag_name},
state::AppState,
@@ -20,8 +20,10 @@ use models::schemas::thought::{ThoughtListSchema, ThoughtSchema};
async fn get_thoughts_by_tag(
State(state): State<AppState>,
Path(tag_name): Path<String>,
viewer: OptionalAuthUser,
) -> Result<impl IntoResponse, ApiError> {
let thoughts_with_authors = get_thoughts_by_tag_name(&state.conn, &tag_name).await;
let thoughts_with_authors =
get_thoughts_by_tag_name(&state.conn, &tag_name, viewer.0.map(|u| u.id)).await;
let thoughts_with_authors = thoughts_with_authors?;
let thoughts_schema: Vec<ThoughtSchema> = thoughts_with_authors
.into_iter()

View File

@@ -19,8 +19,8 @@ use models::schemas::user::{UserListSchema, UserSchema};
use models::{params::user::UpdateUserParams, schemas::thought::ThoughtListSchema};
use models::{queries::user::UserQuery, schemas::thought::ThoughtSchema};
use crate::models::ApiErrorResponse;
use crate::{error::ApiError, extractor::AuthUser};
use crate::{extractor::OptionalAuthUser, models::ApiErrorResponse};
use crate::{
extractor::{Json, Valid},
routers::api_key::create_api_key_router,
@@ -63,12 +63,14 @@ async fn users_get(
async fn user_thoughts_get(
State(state): State<AppState>,
Path(username): Path<String>,
viewer: OptionalAuthUser,
) -> Result<impl IntoResponse, ApiError> {
let user = get_user_by_username(&state.conn, &username)
.await?
.ok_or(UserError::NotFound)?;
let thoughts_with_authors = get_thoughts_by_user(&state.conn, user.id).await?;
let thoughts_with_authors =
get_thoughts_by_user(&state.conn, user.id, viewer.0.map(|u| u.id)).await?;
let thoughts_schema: Vec<ThoughtSchema> = thoughts_with_authors
.into_iter()
@@ -272,12 +274,13 @@ async fn get_user_by_param(
async fn user_outbox_get(
State(state): State<AppState>,
Path(username): Path<String>,
viewer: OptionalAuthUser,
) -> Result<impl IntoResponse, ApiError> {
let user = get_user_by_username(&state.conn, &username)
.await?
.ok_or(UserError::NotFound)?;
let thoughts = get_thoughts_by_user(&state.conn, user.id).await?;
let thoughts = get_thoughts_by_user(&state.conn, user.id, viewer.0.map(|u| u.id)).await?;
// Format the outbox as an ActivityPub OrderedCollection
let outbox_url = format!("{}/users/{}/outbox", &state.base_url, username);