diff --git a/crates/adapters/postgres-search/src/lib.rs b/crates/adapters/postgres-search/src/lib.rs index e0fcea6..2240fc7 100644 --- a/crates/adapters/postgres-search/src/lib.rs +++ b/crates/adapters/postgres-search/src/lib.rs @@ -29,6 +29,7 @@ struct FeedRow { t_user_id: uuid::Uuid, content: String, in_reply_to_id: Option, + in_reply_to_url: Option, visibility: String, content_warning: Option, sensitive: bool, @@ -57,7 +58,7 @@ fn feed_select(viewer: Option) -> String { format!( "\n SELECT\n\ t.id AS thought_id, t.user_id AS t_user_id, t.content,\n\ - t.in_reply_to_id,\n\ + t.in_reply_to_id, t.in_reply_to_url,\n\ t.visibility, t.content_warning, t.sensitive, t.local AS t_local,\n\ t.created_at AS thought_created_at, t.updated_at AS thought_updated_at, t.note_extensions, t.mood,\n\ u.id, u.username, u.email, u.password_hash,\n\ @@ -78,6 +79,7 @@ fn row_to_entry(r: FeedRow, viewer: Option) -> Result, + in_reply_to_url: Option, visibility: String, content_warning: Option, sensitive: bool, @@ -52,6 +53,7 @@ fn row_to_entry(r: FeedRow, viewer: Option) -> Result FeedSqlBuilder<'a> { " SELECT t.id AS thought_id, t.user_id AS t_user_id, t.content, - t.in_reply_to_id, + t.in_reply_to_id, t.in_reply_to_url, t.visibility, t.content_warning, t.sensitive, t.local AS t_local, t.created_at AS thought_created_at, t.updated_at AS thought_updated_at, t.note_extensions, t.mood, diff --git a/crates/adapters/postgres/src/thought/mod.rs b/crates/adapters/postgres/src/thought/mod.rs index cf14583..0bb470b 100644 --- a/crates/adapters/postgres/src/thought/mod.rs +++ b/crates/adapters/postgres/src/thought/mod.rs @@ -28,6 +28,7 @@ pub(crate) struct ThoughtRow { pub user_id: uuid::Uuid, pub content: String, pub in_reply_to_id: Option, + pub in_reply_to_url: Option, pub visibility: String, pub content_warning: Option, pub sensitive: bool, @@ -46,6 +47,7 @@ impl TryFrom for Thought { user_id: UserId::from_uuid(r.user_id), content: Content::new_remote(r.content), in_reply_to_id: r.in_reply_to_id.map(ThoughtId::from_uuid), + in_reply_to_url: r.in_reply_to_url, visibility: Visibility::from_db_str(&r.visibility)?, content_warning: r.content_warning, sensitive: r.sensitive, @@ -59,7 +61,7 @@ impl TryFrom for Thought { } const THOUGHT_SELECT: &str = - "SELECT id,user_id,content,in_reply_to_id,visibility,content_warning,sensitive,local,created_at,updated_at,note_extensions,mood FROM thoughts"; + "SELECT id,user_id,content,in_reply_to_id,in_reply_to_url,visibility,content_warning,sensitive,local,created_at,updated_at,note_extensions,mood FROM thoughts"; #[async_trait] impl ThoughtRepository for PgThoughtRepository { @@ -121,11 +123,11 @@ impl ThoughtRepository for PgThoughtRepository { // Recursive CTE: fetches the root thought and all nested replies at any depth. sqlx::query_as::<_, ThoughtRow>( "WITH RECURSIVE thread AS ( - SELECT id,user_id,content,in_reply_to_id, + SELECT id,user_id,content,in_reply_to_id,in_reply_to_url, visibility,content_warning,sensitive,local,created_at,updated_at,note_extensions,mood FROM thoughts WHERE id = $1 UNION ALL - SELECT t.id,t.user_id,t.content,t.in_reply_to_id, + SELECT t.id,t.user_id,t.content,t.in_reply_to_id,t.in_reply_to_url, t.visibility,t.content_warning,t.sensitive,t.local,t.created_at,t.updated_at,t.note_extensions,t.mood FROM thoughts t JOIN thread ON t.in_reply_to_id = thread.id ) diff --git a/crates/domain/src/models/thought.rs b/crates/domain/src/models/thought.rs index 038c5b5..9f2a4fa 100644 --- a/crates/domain/src/models/thought.rs +++ b/crates/domain/src/models/thought.rs @@ -15,6 +15,7 @@ pub struct Thought { pub user_id: UserId, pub content: Content, pub in_reply_to_id: Option, + pub in_reply_to_url: Option, pub visibility: Visibility, pub content_warning: Option, pub sensitive: bool, @@ -66,6 +67,7 @@ impl Thought { user_id: p.user_id, content: p.content, in_reply_to_id: p.in_reply_to_id, + in_reply_to_url: None, visibility: p.visibility, content_warning: p.content_warning, sensitive: p.sensitive, diff --git a/crates/presentation/src/handlers/feed.rs b/crates/presentation/src/handlers/feed.rs index 47b58f5..485de91 100644 --- a/crates/presentation/src/handlers/feed.rs +++ b/crates/presentation/src/handlers/feed.rs @@ -97,7 +97,7 @@ pub fn to_thought_response(e: &domain::models::feed::FeedEntry) -> ThoughtRespon content: e.thought.content.as_str().to_string(), author: to_user_response(&e.author), in_reply_to_id: e.thought.in_reply_to_id.as_ref().map(|id| id.as_uuid()), - in_reply_to_url: None, + in_reply_to_url: e.thought.in_reply_to_url.clone(), visibility: e.thought.visibility.as_str().to_string(), content_warning: e.thought.content_warning.clone(), sensitive: e.thought.sensitive, diff --git a/thoughts-frontend/app/actions/profile.ts b/thoughts-frontend/app/actions/profile.ts deleted file mode 100644 index 214fcdf..0000000 --- a/thoughts-frontend/app/actions/profile.ts +++ /dev/null @@ -1,23 +0,0 @@ -"use server"; - -import { revalidateTag } from "next/cache"; -import { cookies } from "next/headers"; -import { updateProfile as apiUpdateProfile, UpdateProfileSchema } from "@/lib/api"; -import { z } from "zod"; - -async function getToken(): Promise { - const token = (await cookies()).get("auth_token")?.value; - if (!token) throw new Error("Not authenticated"); - return token; -} - -export async function updateProfile( - username: string, - data: z.infer -) { - const token = await getToken(); - const updated = await apiUpdateProfile(data, token); - revalidateTag(`profile:${username}`); - revalidateTag("me"); - return updated; -} diff --git a/thoughts-frontend/app/actions/social.ts b/thoughts-frontend/app/actions/social.ts deleted file mode 100644 index adb0f7e..0000000 --- a/thoughts-frontend/app/actions/social.ts +++ /dev/null @@ -1,28 +0,0 @@ -"use server"; - -import { revalidateTag } from "next/cache"; -import { cookies } from "next/headers"; -import { - followUser as apiFollowUser, - unfollowUser as apiUnfollowUser, -} from "@/lib/api"; - -async function getToken(): Promise { - const token = (await cookies()).get("auth_token")?.value; - if (!token) throw new Error("Not authenticated"); - return token; -} - -export async function followUser(username: string) { - const token = await getToken(); - await apiFollowUser(username, token); - revalidateTag(`profile:${username}`); - revalidateTag("feed"); -} - -export async function unfollowUser(username: string) { - const token = await getToken(); - await apiUnfollowUser(username, token); - revalidateTag(`profile:${username}`); - revalidateTag("feed"); -} diff --git a/thoughts-frontend/app/actions/thoughts.ts b/thoughts-frontend/app/actions/thoughts.ts deleted file mode 100644 index 6d9e149..0000000 --- a/thoughts-frontend/app/actions/thoughts.ts +++ /dev/null @@ -1,30 +0,0 @@ -"use server"; - -import { revalidateTag } from "next/cache"; -import { cookies } from "next/headers"; -import { - createThought as apiCreateThought, - deleteThought as apiDeleteThought, - CreateThoughtSchema, -} from "@/lib/api"; -import { z } from "zod"; - -async function getToken(): Promise { - const token = (await cookies()).get("auth_token")?.value; - if (!token) throw new Error("Not authenticated"); - return token; -} - -export async function createThought(data: z.infer) { - const token = await getToken(); - const thought = await apiCreateThought(data, token); - revalidateTag("feed"); - return thought; -} - -export async function deleteThought(thoughtId: string) { - const token = await getToken(); - await apiDeleteThought(thoughtId, token); - revalidateTag("feed"); - revalidateTag(`thought:${thoughtId}`); -} diff --git a/thoughts-frontend/components/top-friends-combobox.tsx b/thoughts-frontend/components/top-friends-combobox.tsx deleted file mode 100644 index 0d0b92f..0000000 --- a/thoughts-frontend/components/top-friends-combobox.tsx +++ /dev/null @@ -1,105 +0,0 @@ -"use client"; - -import * as React from "react"; -import { Check, ChevronsUpDown } from "lucide-react"; -import { cn } from "@/lib/utils"; -import { Button } from "@/components/ui/button"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { getFriends, User } from "@/lib/api"; -import { useAuth } from "@/hooks/use-auth"; -import { Skeleton } from "./ui/skeleton"; - -interface TopFriendsComboboxProps { - value: string[]; - onChange: (value: string[]) => void; -} - -export function TopFriendsCombobox({ - value, - onChange, -}: TopFriendsComboboxProps) { - const [open, setOpen] = React.useState(false); - const [friends, setFriends] = React.useState([]); - const [isLoading, setIsLoading] = React.useState(true); - const { token } = useAuth(); - - React.useEffect(() => { - if (token) { - getFriends(token) - .then((data) => setFriends(data.users)) - .catch(() => console.error("Failed to fetch friends")) - .finally(() => setIsLoading(false)); - } else { - setIsLoading(false); - } - }, [token]); - - if (isLoading) { - return ; - } - - return ( - - - - - - - - - No friends found. - - {friends.map((friend) => ( - { - const newValue = value.includes(currentValue) - ? value.filter((v) => v !== currentValue) - : [...value, currentValue]; - - if (newValue.length <= 8) { - onChange(newValue); - } - }} - > - - {friend.username} - - ))} - - - - - - ); -} diff --git a/thoughts-frontend/lib/api.ts b/thoughts-frontend/lib/api.ts index de608dd..57dc237 100644 --- a/thoughts-frontend/lib/api.ts +++ b/thoughts-frontend/lib/api.ts @@ -101,9 +101,6 @@ export const ApiKeyResponseSchema = ApiKeySchema.extend({ key: z.string().optional(), }); -export const ApiKeyListSchema = z.object({ - keys: z.array(ApiKeySchema), -}); export const CreateApiKeySchema = z.object({ name: z.string().min(1, "Key name cannot be empty."),