Skip to content

feat: add friends and schedule linking#1511

Open
brelieu05 wants to merge 258 commits intomainfrom
911-suggestion-view-friends-schedules
Open

feat: add friends and schedule linking#1511
brelieu05 wants to merge 258 commits intomainfrom
911-suggestion-view-friends-schedules

Conversation

@brelieu05
Copy link
Copy Markdown
Contributor

@brelieu05 brelieu05 commented Feb 19, 2026

Summary

Builds off of the sharable schedule links system from #1357.

  • Adds a full friends system: send/accept/decline/remove friend requests by email, block/unblock users, and
    view a friend's shared schedule via a direct link.
  • Adds per-schedule sharing toggles so users can choose which of their schedules are visible to friends.
  • Refactors all friends mutations to verify identity via server-issued session tokens instead of
    client-supplied providerAccountId, closing an impersonation/spoofing vector.
image image image

Features

Friends system

  • Friend requests — send by email, accept or decline incoming requests, prevent duplicate requests in
    either direction
  • Unfriend / block / unblock — full relationship lifecycle with a blocked state that prevents
    re-requesting
  • View friend's schedule — navigate to a friend's shared schedule via direct link; non-friends are
    blocked from viewing
  • Friends menu — popover in the header with tabs for Friends, Requests, and Blocked. Skeleton loading
    state while data fetches.
  • Sign-in prompt — clicking Friends while logged out opens a sign-in dialog instead
  • Per-schedule sharing toggle — each schedule row gets a People/PeopleOutline icon to control
    whether friends can see it; defaults to visible for all existing schedules

DB changes

  • packages/db/src/schema/friendship.ts — new friendships table (requesterId, addresseeId, status: PENDING | ACCEPTED | BLOCKED, timestamps)
  • packages/db/src/schema/schedule/schedule.ts — added sharedWithFriends boolean NOT NULL DEFAULT true
  • packages/db/migrations/0008_old_misty_knight.sql — friendships table + self-friend constraint
  • packages/db/migrations/0009_friendships_no_self_friend.sql — check constraint preventing self-friendship
  • packages/db/migrations/0010_schedule_shared_with_friends.sql — new column on schedules

Test plan

  • Send a friend request by email — recipient sees it in the Requests tab
  • Accept the request — both users appear in each other's Friends tab
  • Decline a request — row removed, no ACCEPTED state
  • Block a user — removed from friends, blocked in both directions
  • Try to friend yourself — should return an error
  • Send a duplicate friend request — should show a contextual message, not crash
  • Toggle a schedule to hidden — friend visiting your link no longer sees that schedule
  • Hide all schedules — friend sees a warning banner instead of an error
  • Non-friend tries to visit your schedule link — blocked

Issues

Closes #

Comment thread apps/antalmanac/src/backend/lib/rds.ts Outdated
Copy link
Copy Markdown
Member

@KevinWu098 KevinWu098 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more minor stuff. given that is past midnight already, im opting not to review the Friends- components. if we'd like to merge, im happy to approve in the morning. but otherwise, we can take an extra day to resolve this and maybe take a pass on styling

Comment thread apps/antalmanac/src/backend/routers/userData.ts Outdated
Comment thread apps/antalmanac/src/backend/lib/rds.ts Outdated
const session = useSessionStore.getState();
if (session.sessionIsValid) {
try {
await autoSaveSchedule({});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: why are we calling auto save schedule

throw new TRPCError({
code: 'BAD_REQUEST',
message: theyRequestedYou
? 'This user has already sent you a friend request. Check your Requests tab to accept.'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought (non-blocking): we could probably just "accept" the friend req at this point and success it

* @param input - An object containing the schedule ID.
* @returns The schedule data associated with the schedule ID, or throws NOT_FOUND if not found.
*/
getSharedSchedule: procedure.input(z.object({ scheduleId: z.string() })).query(async ({ input }) => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: is this method not redundant? is there really no method for getting schedule by id on another router?

if it is not redundant, seems like this should... probably live on another router (?) with validation that the user is actually allowed to fetch this schedule?

@KevinWu098
Copy link
Copy Markdown
Member

@cubic

@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai Bot commented May 5, 2026

@cubic

@KevinWu098 I have started the AI code review. It will take a few minutes to complete.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 30 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/antalmanac/src/components/Header/Friends/Friends/UnfriendConfirmationDialog.tsx">

<violation number="1" location="apps/antalmanac/src/components/Header/Friends/Friends/UnfriendConfirmationDialog.tsx:18">
P2: `onRefresh()` failures are treated as unfriend failures, which can show an incorrect error message after a successful remove.</violation>
</file>

<file name="apps/antalmanac/src/backend/lib/rds.ts">

<violation number="1" location="apps/antalmanac/src/backend/lib/rds.ts:79">
P1: Case-insensitive email lookup with `limit(1)` can attach accounts to the wrong user when duplicate normalized emails exist.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Comment on lines +79 to 81
.where(sql`lower(${users.email}) = lower(${email.trim()})`)
.limit(1)
.then((res) => res[0]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Case-insensitive email lookup with limit(1) can attach accounts to the wrong user when duplicate normalized emails exist.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/antalmanac/src/backend/lib/rds.ts, line 79:

<comment>Case-insensitive email lookup with `limit(1)` can attach accounts to the wrong user when duplicate normalized emails exist.</comment>

<file context>
@@ -76,7 +76,8 @@ export class RDS {
             .select()
             .from(users)
-            .where(eq(users.email, email))
+            .where(sql`lower(${users.email}) = lower(${email.trim()})`)
+            .limit(1)
             .then((res) => res[0]);
</file context>
Suggested change
.where(sql`lower(${users.email}) = lower(${email.trim()})`)
.limit(1)
.then((res) => res[0]);
.where(sql`lower(${users.email}) = lower(${email.trim()})`)
.limit(2)
.then((res) => {
if (res.length > 1) {
throw new Error('Ambiguous email match: multiple users found for normalized email.');
}
return res[0];
});

Tip: Review your code locally with the cubic CLI to iterate faster.

await trpc.friends.removeFriend.mutate({ friendId });
openSnackbar('info', 'Friend removed.');
onClose();
await onRefresh();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: onRefresh() failures are treated as unfriend failures, which can show an incorrect error message after a successful remove.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/antalmanac/src/components/Header/Friends/Friends/UnfriendConfirmationDialog.tsx, line 18:

<comment>`onRefresh()` failures are treated as unfriend failures, which can show an incorrect error message after a successful remove.</comment>

<file context>
@@ -0,0 +1,41 @@
+            await trpc.friends.removeFriend.mutate({ friendId });
+            openSnackbar('info', 'Friend removed.');
+            onClose();
+            await onRefresh();
+        } catch (error) {
+            console.error('Error removing friend:', error);
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suggestion: View Friends' Schedules