diff --git a/database/entities/Relationship.ts b/database/entities/Relationship.ts index 8deaa0e2..696a5b49 100644 --- a/database/entities/Relationship.ts +++ b/database/entities/Relationship.ts @@ -4,7 +4,9 @@ import { Relationships } from "~/drizzle/schema"; import type { User } from "~/packages/database-interface/user"; import type { Relationship as APIRelationship } from "~/types/mastodon/relationship"; -export type Relationship = InferSelectModel; +export type Relationship = InferSelectModel & { + requestedBy: boolean; +}; /** * Creates a new relationship between two users. @@ -16,29 +18,32 @@ export const createNewRelationship = async ( owner: User, other: User, ): Promise => { - return ( - await db - .insert(Relationships) - .values({ - ownerId: owner.id, - subjectId: other.id, - languages: [], - following: false, - showingReblogs: false, - notifying: false, - followedBy: false, - blocking: false, - blockedBy: false, - muting: false, - mutingNotifications: false, - requested: false, - domainBlocking: false, - endorsed: false, - note: "", - updatedAt: new Date().toISOString(), - }) - .returning() - )[0]; + return { + ...( + await db + .insert(Relationships) + .values({ + ownerId: owner.id, + subjectId: other.id, + languages: [], + following: false, + showingReblogs: false, + notifying: false, + followedBy: false, + blocking: false, + blockedBy: false, + muting: false, + mutingNotifications: false, + requested: false, + domainBlocking: false, + endorsed: false, + note: "", + updatedAt: new Date().toISOString(), + }) + .returning() + )[0], + requestedBy: false, + }; }; export const checkForBidirectionalRelationships = async ( @@ -81,6 +86,7 @@ export const relationshipToAPI = (rel: Relationship): APIRelationship => { muting_notifications: rel.mutingNotifications, notifying: rel.notifying, requested: rel.requested, + requested_by: rel.requestedBy, showing_reblogs: rel.showingReblogs, note: rel.note, }; diff --git a/database/entities/User.ts b/database/entities/User.ts index 852555be..b82df81f 100644 --- a/database/entities/User.ts +++ b/database/entities/User.ts @@ -17,7 +17,7 @@ import { LogLevel } from "~/packages/log-manager"; import type { Application } from "./Application"; import type { EmojiWithInstance } from "./Emoji"; import { objectToInboxRequest } from "./Federation"; -import { createNewRelationship } from "./Relationship"; +import { type Relationship, createNewRelationship } from "./Relationship"; import type { Token } from "./Token"; export type UserType = InferSelectModel; @@ -121,22 +121,33 @@ export const followRequestUser = async ( reblogs = false, notify = false, languages: string[] = [], -): Promise> => { +): Promise => { const isRemote = followee.isRemote(); - const updatedRelationship = ( - await db - .update(Relationships) - .set({ - following: isRemote ? false : !followee.getUser().isLocked, - requested: isRemote ? true : followee.getUser().isLocked, - showingReblogs: reblogs, - notifying: notify, - languages: languages, - }) - .where(eq(Relationships.id, relationshipId)) - .returning() - )[0]; + await db + .update(Relationships) + .set({ + following: isRemote ? false : !followee.getUser().isLocked, + requested: isRemote ? true : followee.getUser().isLocked, + showingReblogs: reblogs, + notifying: notify, + languages: languages, + }) + .where(eq(Relationships.id, relationshipId)); + + const updatedRelationship = await db.query.Relationships.findFirst({ + where: (rel, { eq }) => eq(rel.id, relationshipId), + extras: { + requestedBy: + sql`(SELECT "requested" FROM "Relationships" WHERE "Relationships"."ownerId" = ${followee.id} AND "Relationships"."subjectId" = ${follower.id})`.as( + "requested_by", + ), + }, + }); + + if (!updatedRelationship) { + throw new Error("Failed to update relationship"); + } if (isRemote) { // Federate @@ -165,16 +176,29 @@ export const followRequestUser = async ( } to ${followee.getUri()}`, ); - return ( - await db - .update(Relationships) - .set({ - following: false, - requested: false, - }) - .where(eq(Relationships.id, relationshipId)) - .returning() - )[0]; + await db + .update(Relationships) + .set({ + following: false, + requested: false, + }) + .where(eq(Relationships.id, relationshipId)); + + const result = await db.query.Relationships.findFirst({ + where: (rel, { eq }) => eq(rel.id, relationshipId), + extras: { + requestedBy: + sql`(SELECT "requested" FROM "Relationships" WHERE "Relationships"."ownerId" = ${followee.id} AND "Relationships"."subjectId" = ${follower.id})`.as( + "requested_by", + ), + }, + }); + + if (!result) { + throw new Error("Failed to update relationship"); + } + + return result; } } else { await db.insert(Notifications).values({ @@ -458,13 +482,19 @@ export const retrieveToken = async ( export const getRelationshipToOtherUser = async ( user: User, other: User, -): Promise> => { +): Promise => { const foundRelationship = await db.query.Relationships.findFirst({ where: (relationship, { and, eq }) => and( eq(relationship.ownerId, user.id), eq(relationship.subjectId, other.id), ), + extras: { + requestedBy: + sql`(SELECT "requested" FROM "Relationships" WHERE "Relationships"."ownerId" = ${other.id} AND "Relationships"."subjectId" = ${user.id})`.as( + "requested_by", + ), + }, }); if (!foundRelationship) { diff --git a/server/api/api/v1/accounts/relationships/index.ts b/server/api/api/v1/accounts/relationships/index.ts index 7ccf080a..cf310ba6 100644 --- a/server/api/api/v1/accounts/relationships/index.ts +++ b/server/api/api/v1/accounts/relationships/index.ts @@ -1,6 +1,7 @@ import { applyConfig, auth, handleZodError, qsQuery } from "@/api"; import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; +import { sql } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; import { @@ -8,7 +9,7 @@ import { relationshipToAPI, } from "~/database/entities/Relationship"; import { db } from "~/drizzle/db"; -import { RolePermissions } from "~/drizzle/schema"; +import { Relationships, RolePermissions } from "~/drizzle/schema"; import { User } from "~/packages/database-interface/user"; export const meta = applyConfig({ @@ -54,6 +55,12 @@ export default (app: Hono) => inArray(relationship.subjectId, ids), eq(relationship.ownerId, self.id), ), + extras: { + requestedBy: + sql`(SELECT "requested" FROM "Relationships" WHERE "Relationships"."ownerId" = ${Relationships.id} AND "Relationships"."subjectId" = ${self.id})`.as( + "requested_by", + ), + }, }); const missingIds = ids.filter( diff --git a/types/mastodon/relationship.ts b/types/mastodon/relationship.ts index 1d5540de..c2bb9c2a 100644 --- a/types/mastodon/relationship.ts +++ b/types/mastodon/relationship.ts @@ -7,6 +7,7 @@ export type Relationship = { muting: boolean; muting_notifications: boolean; requested: boolean; + requested_by: boolean; domain_blocking: boolean; showing_reblogs: boolean; endorsed: boolean;