Implement social-feed: local follows + /feed + profile visibility
Implements the social-feed change end-to-end. Local-only follows
between users on the same instance, an aggregated /feed of public
activities from people you follow, and an explicit profile_visibility
setting so the question "can someone follow me?" has a deterministic
answer.
Schema (additive, drizzle-kit push --force in cd-apps handles it):
- journal.follows table keyed by `followed_actor_iri TEXT` for
federation forward-compat. Local IRIs look like
`${ORIGIN}/users/${username}`. `accepted_at` is nullable so the
Pending state from social-federation slots in without migration.
- journal.users.profile_visibility ('public' | 'private', default
'public'). Existing users land 'public' via the default; current
effective behavior is unchanged.
Server (apps/journal/app/lib):
- actor-iri.ts: localActorIri(username) helper — single source of
truth for IRI construction.
- follow.server.ts: followUser / unfollowUser / getFollowState /
countFollowers / countFollowing / listFollowers / listFollowing.
Refuses self-follow + private targets. Idempotent.
- activities.server.ts: listSocialFeed(followerId, limit) joining
follows → activities WHERE visibility='public', reverse-chrono.
Routes:
- POST /api/users/:username/follow + /unfollow (session-bound)
- /feed (signed-in only; redirects anon to /auth/login)
- /users/:username/followers + /users/:username/following (paginated)
- /users/:username gates on profile_visibility AND has-public-content
for visitors; owners on private get an amber explainer banner.
- /settings adds a Public/Private radio with explainer text.
UI:
- FollowButton component on profile page (hidden for owner + anon).
- Follower/following counts on profile linking to collection pages.
- "Feed" link in nav (signed-in) + on personal dashboard alongside
"New Activity".
Privacy manifest updated to document the new follows relation and
profile_visibility setting.
Tests: follow.integration.test.ts (FOLLOW_INTEGRATION=1) for the
follow lifecycle; e2e/social.test.ts for /feed redirect, follow
button + count transitions, and the profile_visibility 404 toggle.
Local development: run `pnpm db:push` after pulling to apply the
schema additions. Production migrates automatically via cd-apps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6440631be4
commit
811d5f62f5
23 changed files with 1115 additions and 40 deletions
|
|
@ -229,10 +229,38 @@ export default {
|
|||
},
|
||||
profile: {
|
||||
ownNote: "Das ist dein Profil — Besucher:innen sehen nur Inhalte, die du als öffentlich markiert hast.",
|
||||
privateNote: "Dein Profil ist auf Privat gestellt. Besucher:innen sehen 404; öffentliche Inhalte sind weiterhin per direkter URL erreichbar, aber du kannst nicht gefolgt werden.",
|
||||
goToSettings: "Zu den Einstellungen",
|
||||
noPublicRoutes: "Noch keine öffentlichen Routen.",
|
||||
noPublicActivities: "Noch keine öffentlichen Aktivitäten.",
|
||||
},
|
||||
social: {
|
||||
follow: "Folgen",
|
||||
unfollow: "Entfolgen",
|
||||
followers: {
|
||||
label: "Follower",
|
||||
heading: "Follower von {{user}}",
|
||||
count: "{{count}} Follower",
|
||||
count_other: "{{count}} Follower",
|
||||
empty: "Noch niemand.",
|
||||
},
|
||||
following: {
|
||||
label: "Folgt",
|
||||
heading: "{{user}} folgt",
|
||||
count: "Folgt {{count}}",
|
||||
count_other: "Folgt {{count}}",
|
||||
empty: "Folgt noch niemandem.",
|
||||
},
|
||||
prevPage: "Zurück",
|
||||
nextPage: "Weiter",
|
||||
pageOfTotal: "Seite {{page}} von {{totalPages}}",
|
||||
feed: {
|
||||
title: "Feed",
|
||||
heading: "Folge ich",
|
||||
empty: "Du folgst noch niemandem. Stöbere Profile durch und klicke auf Folgen, um deinen Feed aufzubauen.",
|
||||
publicFeedLink: "Oder durchstöbere den öffentlichen Feed dieser Instanz →",
|
||||
},
|
||||
},
|
||||
demo: {
|
||||
badge: "🐕 Demo-Konto",
|
||||
},
|
||||
|
|
@ -259,6 +287,13 @@ export default {
|
|||
displayName: "Anzeigename",
|
||||
bio: "Bio",
|
||||
saved: "Profil gespeichert.",
|
||||
visibility: {
|
||||
label: "Profil-Sichtbarkeit",
|
||||
public: "Öffentlich",
|
||||
publicHelp: "Deine Profilseite ist für alle sichtbar (sobald du öffentliche Inhalte hast). Du kannst gefolgt werden.",
|
||||
private: "Privat",
|
||||
privateHelp: "Deine Profilseite gibt 404 zurück. Öffentliche Inhalte sind weiterhin per direkter URL erreichbar, aber du kannst nicht gefolgt werden.",
|
||||
},
|
||||
},
|
||||
security: {
|
||||
title: "Sicherheit",
|
||||
|
|
|
|||
|
|
@ -229,10 +229,38 @@ export default {
|
|||
},
|
||||
profile: {
|
||||
ownNote: "This is your profile — visitors see only what you've marked public.",
|
||||
privateNote: "Your profile is set to private. Visitors see a 404; public posts are still reachable by direct URL but you can't be followed.",
|
||||
goToSettings: "Go to settings",
|
||||
noPublicRoutes: "No public routes yet.",
|
||||
noPublicActivities: "No public activities yet.",
|
||||
},
|
||||
social: {
|
||||
follow: "Follow",
|
||||
unfollow: "Unfollow",
|
||||
followers: {
|
||||
label: "Followers",
|
||||
heading: "Followers of {{user}}",
|
||||
count: "{{count}} follower",
|
||||
count_other: "{{count}} followers",
|
||||
empty: "Nobody yet.",
|
||||
},
|
||||
following: {
|
||||
label: "Following",
|
||||
heading: "{{user}} is following",
|
||||
count: "Following {{count}}",
|
||||
count_other: "Following {{count}}",
|
||||
empty: "Not following anyone yet.",
|
||||
},
|
||||
prevPage: "Previous",
|
||||
nextPage: "Next",
|
||||
pageOfTotal: "Page {{page}} of {{totalPages}}",
|
||||
feed: {
|
||||
title: "Feed",
|
||||
heading: "Following",
|
||||
empty: "You're not following anyone yet. Browse profiles and tap Follow to start building your feed.",
|
||||
publicFeedLink: "Or browse the instance public feed →",
|
||||
},
|
||||
},
|
||||
demo: {
|
||||
badge: "🐕 Demo account",
|
||||
},
|
||||
|
|
@ -259,6 +287,13 @@ export default {
|
|||
displayName: "Display Name",
|
||||
bio: "Bio",
|
||||
saved: "Profile saved.",
|
||||
visibility: {
|
||||
label: "Profile visibility",
|
||||
public: "Public",
|
||||
publicHelp: "Your profile page is visible to anyone (when you have any public content). You can be followed.",
|
||||
private: "Private",
|
||||
privateHelp: "Your profile page returns 404. Public posts are still reachable by direct URL but you can't be followed.",
|
||||
},
|
||||
},
|
||||
security: {
|
||||
title: "Security",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue