mirror of
https://github.com/versia-pub/server.git
synced 2025-12-08 17:28:19 +01:00
refactor: ♻️ Rewrite relationship system
This commit is contained in:
parent
62b68a64ac
commit
3baac85cf7
|
|
@ -1,96 +0,0 @@
|
||||||
import type { Relationship as ApiRelationship } from "@lysand-org/client/types";
|
|
||||||
import type { InferSelectModel } from "drizzle-orm";
|
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { Relationships } from "~/drizzle/schema";
|
|
||||||
import type { User } from "~/packages/database-interface/user";
|
|
||||||
|
|
||||||
export type Relationship = InferSelectModel<typeof Relationships> & {
|
|
||||||
requestedBy: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new relationship between two users.
|
|
||||||
* @param owner The user who owns the relationship.
|
|
||||||
* @param other The user who is the subject of the relationship.
|
|
||||||
* @returns The newly created relationship.
|
|
||||||
*/
|
|
||||||
export const createNewRelationship = async (
|
|
||||||
owner: User,
|
|
||||||
other: User,
|
|
||||||
): Promise<Relationship> => {
|
|
||||||
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 (
|
|
||||||
user1: User,
|
|
||||||
user2: User,
|
|
||||||
createIfNotExists = true,
|
|
||||||
): Promise<boolean> => {
|
|
||||||
const relationship1 = await db.query.Relationships.findFirst({
|
|
||||||
where: (rel, { and, eq }) =>
|
|
||||||
and(eq(rel.ownerId, user1.id), eq(rel.subjectId, user2.id)),
|
|
||||||
});
|
|
||||||
|
|
||||||
const relationship2 = await db.query.Relationships.findFirst({
|
|
||||||
where: (rel, { and, eq }) =>
|
|
||||||
and(eq(rel.ownerId, user2.id), eq(rel.subjectId, user1.id)),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!relationship1 && createIfNotExists) {
|
|
||||||
await createNewRelationship(user1, user2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!relationship2 && createIfNotExists) {
|
|
||||||
await createNewRelationship(user2, user1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return !!relationship1 && !!relationship2;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the relationship to an API-friendly format.
|
|
||||||
* @returns The API-friendly relationship.
|
|
||||||
*/
|
|
||||||
export const relationshipToApi = (rel: Relationship): ApiRelationship => {
|
|
||||||
return {
|
|
||||||
blocked_by: rel.blockedBy,
|
|
||||||
blocking: rel.blocking,
|
|
||||||
domain_blocking: rel.domainBlocking,
|
|
||||||
endorsed: rel.endorsed,
|
|
||||||
followed_by: rel.followedBy,
|
|
||||||
following: rel.following,
|
|
||||||
id: rel.subjectId,
|
|
||||||
muting: rel.muting,
|
|
||||||
muting_notifications: rel.mutingNotifications,
|
|
||||||
notifying: rel.notifying,
|
|
||||||
requested: rel.requested,
|
|
||||||
requested_by: rel.requestedBy,
|
|
||||||
showing_reblogs: rel.showingReblogs,
|
|
||||||
note: rel.note,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -4,13 +4,11 @@ import type {
|
||||||
FollowReject,
|
FollowReject,
|
||||||
} from "@lysand-org/federation/types";
|
} from "@lysand-org/federation/types";
|
||||||
import { config } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import { type InferSelectModel, and, eq, sql } from "drizzle-orm";
|
import { type InferSelectModel, eq, sql } from "drizzle-orm";
|
||||||
import { db } from "~/drizzle/db";
|
import { db } from "~/drizzle/db";
|
||||||
import {
|
import {
|
||||||
Applications,
|
Applications,
|
||||||
type Instances,
|
type Instances,
|
||||||
Notifications,
|
|
||||||
Relationships,
|
|
||||||
type Roles,
|
type Roles,
|
||||||
Tokens,
|
Tokens,
|
||||||
type Users,
|
type Users,
|
||||||
|
|
@ -18,11 +16,6 @@ import {
|
||||||
import type { EmojiWithInstance } from "~/packages/database-interface/emoji";
|
import type { EmojiWithInstance } from "~/packages/database-interface/emoji";
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
import type { Application } from "./application";
|
import type { Application } from "./application";
|
||||||
import {
|
|
||||||
type Relationship,
|
|
||||||
checkForBidirectionalRelationships,
|
|
||||||
createNewRelationship,
|
|
||||||
} from "./relationship";
|
|
||||||
import type { Token } from "./token";
|
import type { Token } from "./token";
|
||||||
|
|
||||||
export type UserType = InferSelectModel<typeof Users>;
|
export type UserType = InferSelectModel<typeof Users>;
|
||||||
|
|
@ -119,85 +112,6 @@ export const getFromHeader = async (value: string): Promise<AuthData> => {
|
||||||
return { user, token, application };
|
return { user, token, application };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const followRequestUser = async (
|
|
||||||
follower: User,
|
|
||||||
followee: User,
|
|
||||||
relationshipId: string,
|
|
||||||
reblogs = false,
|
|
||||||
notify = false,
|
|
||||||
languages: string[] = [],
|
|
||||||
): Promise<Relationship> => {
|
|
||||||
const isRemote = followee.isRemote();
|
|
||||||
|
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
following: isRemote ? false : !followee.data.isLocked,
|
|
||||||
requested: isRemote ? true : followee.data.isLocked,
|
|
||||||
showingReblogs: reblogs,
|
|
||||||
notifying: notify,
|
|
||||||
languages: languages,
|
|
||||||
})
|
|
||||||
.where(eq(Relationships.id, relationshipId));
|
|
||||||
|
|
||||||
// Set requested_by on other side
|
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
requestedBy: isRemote ? true : followee.data.isLocked,
|
|
||||||
followedBy: isRemote ? false : followee.data.isLocked,
|
|
||||||
})
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(Relationships.ownerId, followee.id),
|
|
||||||
eq(Relationships.subjectId, follower.id),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const updatedRelationship = await db.query.Relationships.findFirst({
|
|
||||||
where: (rel, { eq }) => eq(rel.id, relationshipId),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!updatedRelationship) {
|
|
||||||
throw new Error("Failed to update relationship");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRemote) {
|
|
||||||
const { ok } = await follower.federateToUser(
|
|
||||||
followRequestToLysand(follower, followee),
|
|
||||||
followee,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
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),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
throw new Error("Failed to update relationship");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await db.insert(Notifications).values({
|
|
||||||
accountId: follower.id,
|
|
||||||
type: followee.data.isLocked ? "follow_request" : "follow",
|
|
||||||
notifiedId: followee.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatedRelationship;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendFollowAccept = async (follower: User, followee: User) => {
|
export const sendFollowAccept = async (follower: User, followee: User) => {
|
||||||
await follower.federateToUser(
|
await follower.federateToUser(
|
||||||
followAcceptToLysand(follower, followee),
|
followAcceptToLysand(follower, followee),
|
||||||
|
|
@ -344,36 +258,6 @@ export const retrieveToken = async (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the relationship to another user.
|
|
||||||
* @param other The other user to get the relationship to.
|
|
||||||
* @returns The relationship to the other user.
|
|
||||||
*/
|
|
||||||
export const getRelationshipToOtherUser = async (
|
|
||||||
user: User,
|
|
||||||
other: User,
|
|
||||||
): Promise<Relationship> => {
|
|
||||||
await checkForBidirectionalRelationships(user, other);
|
|
||||||
|
|
||||||
const foundRelationship = await db.query.Relationships.findFirst({
|
|
||||||
where: (relationship, { and, eq }) =>
|
|
||||||
and(
|
|
||||||
eq(relationship.ownerId, user.id),
|
|
||||||
eq(relationship.subjectId, other.id),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!foundRelationship) {
|
|
||||||
// Create new relationship
|
|
||||||
|
|
||||||
const newRelationship = await createNewRelationship(user, other);
|
|
||||||
|
|
||||||
return newRelationship;
|
|
||||||
}
|
|
||||||
|
|
||||||
return foundRelationship;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const followRequestToLysand = (
|
export const followRequestToLysand = (
|
||||||
follower: User,
|
follower: User,
|
||||||
followee: User,
|
followee: User,
|
||||||
|
|
|
||||||
3
drizzle/migrations/0031_mature_demogoblin.sql
Normal file
3
drizzle/migrations/0031_mature_demogoblin.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
ALTER TABLE "Relationships" DROP COLUMN IF EXISTS "followed_by";--> statement-breakpoint
|
||||||
|
ALTER TABLE "Relationships" DROP COLUMN IF EXISTS "blocked_by";--> statement-breakpoint
|
||||||
|
ALTER TABLE "Relationships" DROP COLUMN IF EXISTS "requested_by";
|
||||||
2126
drizzle/migrations/meta/0031_snapshot.json
Normal file
2126
drizzle/migrations/meta/0031_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -218,6 +218,13 @@
|
||||||
"when": 1721223331975,
|
"when": 1721223331975,
|
||||||
"tag": "0030_curvy_vulture",
|
"tag": "0030_curvy_vulture",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 31,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1722100203904,
|
||||||
|
"tag": "0031_mature_demogoblin",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -181,13 +181,10 @@ export const Relationships = pgTable("Relationships", {
|
||||||
following: boolean("following").notNull(),
|
following: boolean("following").notNull(),
|
||||||
showingReblogs: boolean("showing_reblogs").notNull(),
|
showingReblogs: boolean("showing_reblogs").notNull(),
|
||||||
notifying: boolean("notifying").notNull(),
|
notifying: boolean("notifying").notNull(),
|
||||||
followedBy: boolean("followed_by").notNull(),
|
|
||||||
blocking: boolean("blocking").notNull(),
|
blocking: boolean("blocking").notNull(),
|
||||||
blockedBy: boolean("blocked_by").notNull(),
|
|
||||||
muting: boolean("muting").notNull(),
|
muting: boolean("muting").notNull(),
|
||||||
mutingNotifications: boolean("muting_notifications").notNull(),
|
mutingNotifications: boolean("muting_notifications").notNull(),
|
||||||
requested: boolean("requested").notNull(),
|
requested: boolean("requested").notNull(),
|
||||||
requestedBy: boolean("requested_by").notNull().default(false),
|
|
||||||
domainBlocking: boolean("domain_blocking").notNull(),
|
domainBlocking: boolean("domain_blocking").notNull(),
|
||||||
endorsed: boolean("endorsed").notNull(),
|
endorsed: boolean("endorsed").notNull(),
|
||||||
languages: text("languages").array(),
|
languages: text("languages").array(),
|
||||||
|
|
|
||||||
295
packages/database-interface/relationship.ts
Normal file
295
packages/database-interface/relationship.ts
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
import type { Relationship as APIRelationship } from "@lysand-org/client/types";
|
||||||
|
import {
|
||||||
|
type InferInsertModel,
|
||||||
|
type InferSelectModel,
|
||||||
|
type SQL,
|
||||||
|
and,
|
||||||
|
desc,
|
||||||
|
eq,
|
||||||
|
inArray,
|
||||||
|
} from "drizzle-orm";
|
||||||
|
import { db } from "~/drizzle/db";
|
||||||
|
import { Relationships } from "~/drizzle/schema";
|
||||||
|
import { BaseInterface } from "./base";
|
||||||
|
import type { User } from "./user";
|
||||||
|
|
||||||
|
export type RelationshipType = InferSelectModel<typeof Relationships>;
|
||||||
|
|
||||||
|
export type RelationshipWithOpposite = RelationshipType & {
|
||||||
|
followedBy: boolean;
|
||||||
|
blockedBy: boolean;
|
||||||
|
requestedBy: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Relationship extends BaseInterface<
|
||||||
|
typeof Relationships,
|
||||||
|
RelationshipWithOpposite
|
||||||
|
> {
|
||||||
|
async reload(): Promise<void> {
|
||||||
|
const reloaded = await Relationship.fromId(this.data.id);
|
||||||
|
|
||||||
|
if (!reloaded) {
|
||||||
|
throw new Error("Failed to reload relationship");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data = reloaded.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async fromId(
|
||||||
|
id: string | null,
|
||||||
|
): Promise<Relationship | null> {
|
||||||
|
if (!id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Relationship.fromSql(eq(Relationships.id, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async fromIds(ids: string[]): Promise<Relationship[]> {
|
||||||
|
return await Relationship.manyFromSql(inArray(Relationships.id, ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async fromOwnerAndSubject(
|
||||||
|
owner: User,
|
||||||
|
subject: User,
|
||||||
|
): Promise<Relationship> {
|
||||||
|
const found = await Relationship.fromSql(
|
||||||
|
and(
|
||||||
|
eq(Relationships.ownerId, owner.id),
|
||||||
|
eq(Relationships.subjectId, subject.id),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
// Create a new relationship if one doesn't exist
|
||||||
|
return await Relationship.insert({
|
||||||
|
ownerId: owner.id,
|
||||||
|
subjectId: subject.id,
|
||||||
|
languages: [],
|
||||||
|
following: false,
|
||||||
|
showingReblogs: false,
|
||||||
|
notifying: false,
|
||||||
|
blocking: false,
|
||||||
|
muting: false,
|
||||||
|
mutingNotifications: false,
|
||||||
|
requested: false,
|
||||||
|
domainBlocking: false,
|
||||||
|
endorsed: false,
|
||||||
|
note: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async fromOwnerAndSubjects(
|
||||||
|
owner: User,
|
||||||
|
subjectIds: string[],
|
||||||
|
): Promise<Relationship[]> {
|
||||||
|
const found = await Relationship.manyFromSql(
|
||||||
|
and(
|
||||||
|
eq(Relationships.ownerId, owner.id),
|
||||||
|
inArray(Relationships.subjectId, subjectIds),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const missingSubjectsIds = subjectIds.filter(
|
||||||
|
(id) => !found.find((rel) => rel.data.subjectId === id),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const subjectId of missingSubjectsIds) {
|
||||||
|
await Relationship.insert({
|
||||||
|
ownerId: owner.id,
|
||||||
|
subjectId: subjectId,
|
||||||
|
languages: [],
|
||||||
|
following: false,
|
||||||
|
showingReblogs: false,
|
||||||
|
notifying: false,
|
||||||
|
blocking: false,
|
||||||
|
muting: false,
|
||||||
|
mutingNotifications: false,
|
||||||
|
requested: false,
|
||||||
|
domainBlocking: false,
|
||||||
|
endorsed: false,
|
||||||
|
note: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Relationship.manyFromSql(
|
||||||
|
and(
|
||||||
|
eq(Relationships.ownerId, owner.id),
|
||||||
|
inArray(Relationships.subjectId, subjectIds),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async fromSql(
|
||||||
|
sql: SQL<unknown> | undefined,
|
||||||
|
orderBy: SQL<unknown> | undefined = desc(Relationships.id),
|
||||||
|
): Promise<Relationship | null> {
|
||||||
|
const found = await db.query.Relationships.findFirst({
|
||||||
|
where: sql,
|
||||||
|
orderBy,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const opposite = await Relationship.getOpposite(found);
|
||||||
|
|
||||||
|
return new Relationship({
|
||||||
|
...found,
|
||||||
|
followedBy: opposite.following,
|
||||||
|
blockedBy: opposite.blocking,
|
||||||
|
requestedBy: opposite.requested,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async manyFromSql(
|
||||||
|
sql: SQL<unknown> | undefined,
|
||||||
|
orderBy: SQL<unknown> | undefined = desc(Relationships.id),
|
||||||
|
limit?: number,
|
||||||
|
offset?: number,
|
||||||
|
extra?: Parameters<typeof db.query.Relationships.findMany>[0],
|
||||||
|
): Promise<Relationship[]> {
|
||||||
|
const found = await db.query.Relationships.findMany({
|
||||||
|
where: sql,
|
||||||
|
orderBy,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
with: extra?.with,
|
||||||
|
});
|
||||||
|
|
||||||
|
const opposites = await Promise.all(
|
||||||
|
found.map((rel) => Relationship.getOpposite(rel)),
|
||||||
|
);
|
||||||
|
|
||||||
|
return found.map((s, i) => {
|
||||||
|
return new Relationship({
|
||||||
|
...s,
|
||||||
|
followedBy: opposites[i].following,
|
||||||
|
blockedBy: opposites[i].blocking,
|
||||||
|
requestedBy: opposites[i].requested,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getOpposite(oppositeTo: {
|
||||||
|
subjectId: string;
|
||||||
|
ownerId: string;
|
||||||
|
}): Promise<RelationshipType> {
|
||||||
|
let output = await db.query.Relationships.findFirst({
|
||||||
|
where: (rel, { and, eq }) =>
|
||||||
|
and(
|
||||||
|
eq(rel.ownerId, oppositeTo.subjectId),
|
||||||
|
eq(rel.subjectId, oppositeTo.ownerId),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the opposite relationship doesn't exist, create it
|
||||||
|
if (!output) {
|
||||||
|
output = (
|
||||||
|
await db
|
||||||
|
.insert(Relationships)
|
||||||
|
.values({
|
||||||
|
ownerId: oppositeTo.subjectId,
|
||||||
|
subjectId: oppositeTo.ownerId,
|
||||||
|
languages: [],
|
||||||
|
following: false,
|
||||||
|
showingReblogs: false,
|
||||||
|
notifying: false,
|
||||||
|
blocking: false,
|
||||||
|
domainBlocking: false,
|
||||||
|
endorsed: false,
|
||||||
|
note: "",
|
||||||
|
muting: false,
|
||||||
|
mutingNotifications: false,
|
||||||
|
requested: false,
|
||||||
|
})
|
||||||
|
.returning()
|
||||||
|
)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
newRelationship: Partial<RelationshipType>,
|
||||||
|
): Promise<RelationshipWithOpposite> {
|
||||||
|
await db
|
||||||
|
.update(Relationships)
|
||||||
|
.set(newRelationship)
|
||||||
|
.where(eq(Relationships.id, this.id));
|
||||||
|
|
||||||
|
const updated = await Relationship.fromId(this.data.id);
|
||||||
|
|
||||||
|
if (!updated) {
|
||||||
|
throw new Error("Failed to update relationship");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data = updated.data;
|
||||||
|
return updated.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
save(): Promise<RelationshipWithOpposite> {
|
||||||
|
return this.update(this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(ids: string[]): Promise<void>;
|
||||||
|
async delete(): Promise<void>;
|
||||||
|
async delete(ids?: unknown): Promise<void> {
|
||||||
|
if (Array.isArray(ids)) {
|
||||||
|
await db
|
||||||
|
.delete(Relationships)
|
||||||
|
.where(inArray(Relationships.id, ids));
|
||||||
|
} else {
|
||||||
|
await db.delete(Relationships).where(eq(Relationships.id, this.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async insert(
|
||||||
|
data: InferInsertModel<typeof Relationships>,
|
||||||
|
): Promise<Relationship> {
|
||||||
|
const inserted = (
|
||||||
|
await db.insert(Relationships).values(data).returning()
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
const relationship = await Relationship.fromId(inserted.id);
|
||||||
|
|
||||||
|
if (!relationship) {
|
||||||
|
throw new Error("Failed to insert relationship");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create opposite relationship if necessary
|
||||||
|
await Relationship.getOpposite({
|
||||||
|
subjectId: relationship.data.subjectId,
|
||||||
|
ownerId: relationship.data.ownerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return relationship;
|
||||||
|
}
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return this.data.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toApi(): APIRelationship {
|
||||||
|
return {
|
||||||
|
id: this.data.subjectId,
|
||||||
|
blocked_by: this.data.blockedBy,
|
||||||
|
blocking: this.data.blocking,
|
||||||
|
domain_blocking: this.data.domainBlocking,
|
||||||
|
endorsed: this.data.endorsed,
|
||||||
|
followed_by: this.data.followedBy,
|
||||||
|
following: this.data.following,
|
||||||
|
muting_notifications: this.data.mutingNotifications,
|
||||||
|
muting: this.data.muting,
|
||||||
|
note: this.data.note,
|
||||||
|
notifying: this.data.notifying,
|
||||||
|
requested_by: this.data.requestedBy,
|
||||||
|
requested: this.data.requested,
|
||||||
|
showing_reblogs: this.data.showingReblogs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -34,6 +34,7 @@ import { htmlToText } from "html-to-text";
|
||||||
import {
|
import {
|
||||||
type UserWithRelations,
|
type UserWithRelations,
|
||||||
findManyUsers,
|
findManyUsers,
|
||||||
|
followRequestToLysand,
|
||||||
} from "~/classes/functions/user";
|
} from "~/classes/functions/user";
|
||||||
import { searchManager } from "~/classes/search/search-manager";
|
import { searchManager } from "~/classes/search/search-manager";
|
||||||
import { db } from "~/drizzle/db";
|
import { db } from "~/drizzle/db";
|
||||||
|
|
@ -41,6 +42,7 @@ import {
|
||||||
EmojiToUser,
|
EmojiToUser,
|
||||||
NoteToMentions,
|
NoteToMentions,
|
||||||
Notes,
|
Notes,
|
||||||
|
Notifications,
|
||||||
type RolePermissions,
|
type RolePermissions,
|
||||||
UserToPinnedNotes,
|
UserToPinnedNotes,
|
||||||
Users,
|
Users,
|
||||||
|
|
@ -50,6 +52,7 @@ import { BaseInterface } from "./base";
|
||||||
import { Emoji } from "./emoji";
|
import { Emoji } from "./emoji";
|
||||||
import { Instance } from "./instance";
|
import { Instance } from "./instance";
|
||||||
import type { Note } from "./note";
|
import type { Note } from "./note";
|
||||||
|
import { Relationship } from "./relationship";
|
||||||
import { Role } from "./role";
|
import { Role } from "./role";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -205,6 +208,52 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async followRequest(
|
||||||
|
otherUser: User,
|
||||||
|
options?: {
|
||||||
|
reblogs?: boolean;
|
||||||
|
notify?: boolean;
|
||||||
|
languages?: string[];
|
||||||
|
},
|
||||||
|
): Promise<Relationship> {
|
||||||
|
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
|
this,
|
||||||
|
otherUser,
|
||||||
|
);
|
||||||
|
|
||||||
|
await foundRelationship.update({
|
||||||
|
following: otherUser.isRemote() ? false : !otherUser.data.isLocked,
|
||||||
|
requested: otherUser.isRemote() ? true : otherUser.data.isLocked,
|
||||||
|
showingReblogs: options?.reblogs,
|
||||||
|
notifying: options?.notify,
|
||||||
|
languages: options?.languages,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (otherUser.isRemote()) {
|
||||||
|
const { ok } = await this.federateToUser(
|
||||||
|
followRequestToLysand(this, otherUser),
|
||||||
|
otherUser,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
await foundRelationship.update({
|
||||||
|
requested: false,
|
||||||
|
following: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return foundRelationship;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await db.insert(Notifications).values({
|
||||||
|
accountId: this.id,
|
||||||
|
type: otherUser.data.isLocked ? "follow_request" : "follow",
|
||||||
|
notifiedId: otherUser.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundRelationship;
|
||||||
|
}
|
||||||
|
|
||||||
static async webFinger(
|
static async webFinger(
|
||||||
manager: FederationRequester,
|
manager: FederationRequester,
|
||||||
username: string,
|
username: string,
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,9 @@ import { applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { errorResponse, jsonResponse } from "@/response";
|
import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { relationshipToApi } from "~/classes/functions/relationship";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { getRelationshipToOtherUser } from "~/classes/functions/user";
|
import { Relationship } from "~/packages/database-interface/relationship";
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -55,22 +52,17 @@ export default (app: Hono) =>
|
||||||
return errorResponse("User not found", 404);
|
return errorResponse("User not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundRelationship = await getRelationshipToOtherUser(
|
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
user,
|
user,
|
||||||
otherUser,
|
otherUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!foundRelationship.blocking) {
|
if (!foundRelationship.data.blocking) {
|
||||||
foundRelationship.blocking = true;
|
await foundRelationship.update({
|
||||||
|
blocking: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await db
|
return jsonResponse(foundRelationship.toApi());
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
blocking: true,
|
|
||||||
})
|
|
||||||
.where(eq(Relationships.id, foundRelationship.id));
|
|
||||||
|
|
||||||
return jsonResponse(relationshipToApi(foundRelationship));
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,8 @@ import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { relationshipToApi } from "~/classes/functions/relationship";
|
|
||||||
import {
|
|
||||||
followRequestUser,
|
|
||||||
getRelationshipToOtherUser,
|
|
||||||
} from "~/classes/functions/user";
|
|
||||||
import { RolePermissions } from "~/drizzle/schema";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
|
import { Relationship } from "~/packages/database-interface/relationship";
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -69,22 +65,19 @@ export default (app: Hono) =>
|
||||||
return errorResponse("User not found", 404);
|
return errorResponse("User not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
let relationship = await getRelationshipToOtherUser(
|
let relationship = await Relationship.fromOwnerAndSubject(
|
||||||
user,
|
user,
|
||||||
otherUser,
|
otherUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!relationship.following) {
|
if (!relationship.data.following) {
|
||||||
relationship = await followRequestUser(
|
relationship = await user.followRequest(otherUser, {
|
||||||
user,
|
|
||||||
otherUser,
|
|
||||||
relationship.id,
|
|
||||||
reblogs,
|
reblogs,
|
||||||
notify,
|
notify,
|
||||||
languages,
|
languages,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonResponse(relationshipToApi(relationship));
|
return jsonResponse(relationship.toApi());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,9 @@ import { applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { errorResponse, jsonResponse } from "@/response";
|
import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { relationshipToApi } from "~/classes/functions/relationship";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { getRelationshipToOtherUser } from "~/classes/functions/user";
|
import { Relationship } from "~/packages/database-interface/relationship";
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -67,28 +64,17 @@ export default (app: Hono) =>
|
||||||
return errorResponse("User not found", 404);
|
return errorResponse("User not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundRelationship = await getRelationshipToOtherUser(
|
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
user,
|
user,
|
||||||
otherUser,
|
otherUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!foundRelationship.muting) {
|
// TODO: Implement duration
|
||||||
foundRelationship.muting = true;
|
await foundRelationship.update({
|
||||||
}
|
|
||||||
if (notifications ?? true) {
|
|
||||||
foundRelationship.mutingNotifications = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
muting: true,
|
muting: true,
|
||||||
mutingNotifications: notifications ?? true,
|
mutingNotifications: notifications ?? true,
|
||||||
})
|
});
|
||||||
.where(eq(Relationships.id, foundRelationship.id));
|
|
||||||
|
|
||||||
// TODO: Implement duration
|
return jsonResponse(foundRelationship.toApi());
|
||||||
|
|
||||||
return jsonResponse(relationshipToApi(foundRelationship));
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,9 @@ import { applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { errorResponse, jsonResponse } from "@/response";
|
import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { relationshipToApi } from "~/classes/functions/relationship";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { getRelationshipToOtherUser } from "~/classes/functions/user";
|
import { Relationship } from "~/packages/database-interface/relationship";
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -60,20 +57,15 @@ export default (app: Hono) =>
|
||||||
return errorResponse("User not found", 404);
|
return errorResponse("User not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundRelationship = await getRelationshipToOtherUser(
|
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
user,
|
user,
|
||||||
otherUser,
|
otherUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
foundRelationship.note = comment ?? "";
|
await foundRelationship.update({
|
||||||
|
note: comment,
|
||||||
|
});
|
||||||
|
|
||||||
await db
|
return jsonResponse(foundRelationship.toApi());
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
note: foundRelationship.note,
|
|
||||||
})
|
|
||||||
.where(eq(Relationships.id, foundRelationship.id));
|
|
||||||
|
|
||||||
return jsonResponse(relationshipToApi(foundRelationship));
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,9 @@ import { applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { errorResponse, jsonResponse } from "@/response";
|
import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { relationshipToApi } from "~/classes/functions/relationship";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { getRelationshipToOtherUser } from "~/classes/functions/user";
|
import { Relationship } from "~/packages/database-interface/relationship";
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -55,22 +52,15 @@ export default (app: Hono) =>
|
||||||
return errorResponse("User not found", 404);
|
return errorResponse("User not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundRelationship = await getRelationshipToOtherUser(
|
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
user,
|
user,
|
||||||
otherUser,
|
otherUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!foundRelationship.endorsed) {
|
await foundRelationship.update({
|
||||||
foundRelationship.endorsed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
endorsed: true,
|
endorsed: true,
|
||||||
})
|
});
|
||||||
.where(eq(Relationships.id, foundRelationship.id));
|
|
||||||
|
|
||||||
return jsonResponse(relationshipToApi(foundRelationship));
|
return jsonResponse(foundRelationship.toApi());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,9 @@ import { applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { errorResponse, jsonResponse } from "@/response";
|
import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { and, eq } from "drizzle-orm";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { relationshipToApi } from "~/classes/functions/relationship";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { getRelationshipToOtherUser } from "~/classes/functions/user";
|
import { Relationship } from "~/packages/database-interface/relationship";
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -55,36 +52,22 @@ export default (app: Hono) =>
|
||||||
return errorResponse("User not found", 404);
|
return errorResponse("User not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundRelationship = await getRelationshipToOtherUser(
|
const oppositeRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
|
otherUser,
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (oppositeRelationship.data.following) {
|
||||||
|
await oppositeRelationship.update({
|
||||||
|
following: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
self,
|
self,
|
||||||
otherUser,
|
otherUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (foundRelationship.followedBy) {
|
return jsonResponse(foundRelationship.toApi());
|
||||||
foundRelationship.followedBy = false;
|
|
||||||
|
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
followedBy: false,
|
|
||||||
})
|
|
||||||
.where(eq(Relationships.id, foundRelationship.id));
|
|
||||||
|
|
||||||
if (otherUser.isLocal()) {
|
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
following: false,
|
|
||||||
})
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(Relationships.ownerId, otherUser.id),
|
|
||||||
eq(Relationships.subjectId, self.id),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonResponse(relationshipToApi(foundRelationship));
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,9 @@ import { applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { errorResponse, jsonResponse } from "@/response";
|
import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { relationshipToApi } from "~/classes/functions/relationship";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { getRelationshipToOtherUser } from "~/classes/functions/user";
|
import { Relationship } from "~/packages/database-interface/relationship";
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -55,22 +52,17 @@ export default (app: Hono) =>
|
||||||
return errorResponse("User not found", 404);
|
return errorResponse("User not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundRelationship = await getRelationshipToOtherUser(
|
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
user,
|
user,
|
||||||
otherUser,
|
otherUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (foundRelationship.blocking) {
|
if (foundRelationship.data.blocking) {
|
||||||
foundRelationship.blocking = false;
|
await foundRelationship.update({
|
||||||
|
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
blocking: false,
|
blocking: false,
|
||||||
})
|
});
|
||||||
.where(eq(Relationships.id, foundRelationship.id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonResponse(relationshipToApi(foundRelationship));
|
return jsonResponse(foundRelationship.toApi());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,9 @@ import { applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { errorResponse, jsonResponse } from "@/response";
|
import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { relationshipToApi } from "~/classes/functions/relationship";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { getRelationshipToOtherUser } from "~/classes/functions/user";
|
import { Relationship } from "~/packages/database-interface/relationship";
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -55,23 +52,17 @@ export default (app: Hono) =>
|
||||||
return errorResponse("User not found", 404);
|
return errorResponse("User not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundRelationship = await getRelationshipToOtherUser(
|
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
self,
|
self,
|
||||||
otherUser,
|
otherUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (foundRelationship.following) {
|
if (foundRelationship.data.following) {
|
||||||
foundRelationship.following = false;
|
await foundRelationship.update({
|
||||||
|
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
following: false,
|
following: false,
|
||||||
requested: false,
|
});
|
||||||
})
|
|
||||||
.where(eq(Relationships.id, foundRelationship.id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonResponse(relationshipToApi(foundRelationship));
|
return jsonResponse(foundRelationship.toApi());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,9 @@ import { applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { errorResponse, jsonResponse } from "@/response";
|
import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { relationshipToApi } from "~/classes/functions/relationship";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { getRelationshipToOtherUser } from "~/classes/functions/user";
|
import { Relationship } from "~/packages/database-interface/relationship";
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -55,24 +52,18 @@ export default (app: Hono) =>
|
||||||
return errorResponse("User not found", 404);
|
return errorResponse("User not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundRelationship = await getRelationshipToOtherUser(
|
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
self,
|
self,
|
||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (foundRelationship.muting) {
|
if (foundRelationship.data.muting) {
|
||||||
foundRelationship.muting = false;
|
await foundRelationship.update({
|
||||||
foundRelationship.mutingNotifications = false;
|
|
||||||
|
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
muting: false,
|
muting: false,
|
||||||
mutingNotifications: false,
|
mutingNotifications: false,
|
||||||
})
|
});
|
||||||
.where(eq(Relationships.id, foundRelationship.id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonResponse(relationshipToApi(foundRelationship));
|
return jsonResponse(foundRelationship.toApi());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,9 @@ import { applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { errorResponse, jsonResponse } from "@/response";
|
import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { relationshipToApi } from "~/classes/functions/relationship";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { getRelationshipToOtherUser } from "~/classes/functions/user";
|
import { Relationship } from "~/packages/database-interface/relationship";
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -55,22 +52,17 @@ export default (app: Hono) =>
|
||||||
return errorResponse("User not found", 404);
|
return errorResponse("User not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundRelationship = await getRelationshipToOtherUser(
|
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
self,
|
self,
|
||||||
otherUser,
|
otherUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (foundRelationship.endorsed) {
|
if (foundRelationship.data.endorsed) {
|
||||||
foundRelationship.endorsed = false;
|
await foundRelationship.update({
|
||||||
|
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
endorsed: false,
|
endorsed: false,
|
||||||
})
|
});
|
||||||
.where(eq(Relationships.id, foundRelationship.id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonResponse(relationshipToApi(foundRelationship));
|
return jsonResponse(foundRelationship.toApi());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ describe(meta.route, () => {
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
following: false,
|
following: false,
|
||||||
followed_by: true,
|
followed_by: false,
|
||||||
blocking: false,
|
blocking: false,
|
||||||
muting: false,
|
muting: false,
|
||||||
muting_notifications: false,
|
muting_notifications: false,
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,8 @@ import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
|
||||||
createNewRelationship,
|
|
||||||
relationshipToApi,
|
|
||||||
} from "~/classes/functions/relationship";
|
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { RolePermissions } from "~/drizzle/schema";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { Relationship } from "~/packages/database-interface/relationship";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -50,32 +45,17 @@ export default (app: Hono) =>
|
||||||
return errorResponse("Unauthorized", 401);
|
return errorResponse("Unauthorized", 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
const relationships = await db.query.Relationships.findMany({
|
const relationships = await Relationship.fromOwnerAndSubjects(
|
||||||
where: (relationship, { inArray, and, eq }) =>
|
self,
|
||||||
and(
|
ids,
|
||||||
inArray(relationship.subjectId, ids),
|
|
||||||
eq(relationship.ownerId, self.id),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const missingIds = ids.filter(
|
|
||||||
(id) => !relationships.some((r) => r.subjectId === id),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const id of missingIds) {
|
|
||||||
const user = await User.fromId(id);
|
|
||||||
if (!user) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const relationship = await createNewRelationship(self, user);
|
|
||||||
|
|
||||||
relationships.push(relationship);
|
|
||||||
}
|
|
||||||
|
|
||||||
relationships.sort(
|
relationships.sort(
|
||||||
(a, b) => ids.indexOf(a.subjectId) - ids.indexOf(b.subjectId),
|
(a, b) =>
|
||||||
|
ids.indexOf(a.data.subjectId) -
|
||||||
|
ids.indexOf(b.data.subjectId),
|
||||||
);
|
);
|
||||||
|
|
||||||
return jsonResponse(relationships.map((r) => relationshipToApi(r)));
|
return jsonResponse(relationships.map((r) => r.toApi()));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,10 @@ import { applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { errorResponse, jsonResponse } from "@/response";
|
import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { and, eq } from "drizzle-orm";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import { sendFollowAccept } from "~/classes/functions/user";
|
||||||
checkForBidirectionalRelationships,
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
relationshipToApi,
|
import { Relationship } from "~/packages/database-interface/relationship";
|
||||||
} from "~/classes/functions/relationship";
|
|
||||||
import {
|
|
||||||
getRelationshipToOtherUser,
|
|
||||||
sendFollowAccept,
|
|
||||||
} from "~/classes/functions/user";
|
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -58,52 +50,27 @@ export default (app: Hono) =>
|
||||||
return errorResponse("Account not found", 404);
|
return errorResponse("Account not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there is a relationship on both sides
|
const oppositeRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
await checkForBidirectionalRelationships(user, account);
|
account,
|
||||||
|
user,
|
||||||
|
);
|
||||||
|
|
||||||
// Authorize follow request
|
await oppositeRelationship.update({
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
requested: false,
|
requested: false,
|
||||||
following: true,
|
following: true,
|
||||||
})
|
});
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(Relationships.subjectId, user.id),
|
|
||||||
eq(Relationships.ownerId, account.id),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update followedBy for other user
|
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
followedBy: true,
|
|
||||||
requestedBy: false,
|
|
||||||
})
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(Relationships.subjectId, account.id),
|
|
||||||
eq(Relationships.ownerId, user.id),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const foundRelationship = await getRelationshipToOtherUser(
|
|
||||||
user,
|
user,
|
||||||
account,
|
account,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!foundRelationship) {
|
|
||||||
return errorResponse("Relationship not found", 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if accepting remote follow
|
// Check if accepting remote follow
|
||||||
if (account.isRemote()) {
|
if (account.isRemote()) {
|
||||||
// Federate follow accept
|
// Federate follow accept
|
||||||
await sendFollowAccept(account, user);
|
await sendFollowAccept(account, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonResponse(relationshipToApi(foundRelationship));
|
return jsonResponse(foundRelationship.toApi());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,10 @@ import { applyConfig, auth, handleZodError } from "@/api";
|
||||||
import { errorResponse, jsonResponse } from "@/response";
|
import { errorResponse, jsonResponse } from "@/response";
|
||||||
import type { Hono } from "@hono/hono";
|
import type { Hono } from "@hono/hono";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { and, eq } from "drizzle-orm";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import { sendFollowReject } from "~/classes/functions/user";
|
||||||
checkForBidirectionalRelationships,
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
relationshipToApi,
|
import { Relationship } from "~/packages/database-interface/relationship";
|
||||||
} from "~/classes/functions/relationship";
|
|
||||||
import {
|
|
||||||
getRelationshipToOtherUser,
|
|
||||||
sendFollowReject,
|
|
||||||
} from "~/classes/functions/user";
|
|
||||||
import { db } from "~/drizzle/db";
|
|
||||||
import { Relationships, RolePermissions } from "~/drizzle/schema";
|
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -58,52 +50,27 @@ export default (app: Hono) =>
|
||||||
return errorResponse("Account not found", 404);
|
return errorResponse("Account not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there is a relationship on both sides
|
const oppositeRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
await checkForBidirectionalRelationships(user, account);
|
account,
|
||||||
|
user,
|
||||||
|
);
|
||||||
|
|
||||||
// Reject follow request
|
await oppositeRelationship.update({
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
requested: false,
|
requested: false,
|
||||||
following: false,
|
following: false,
|
||||||
})
|
});
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(Relationships.subjectId, user.id),
|
|
||||||
eq(Relationships.ownerId, account.id),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update followedBy for other user
|
const foundRelationship = await Relationship.fromOwnerAndSubject(
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
followedBy: false,
|
|
||||||
requestedBy: false,
|
|
||||||
})
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(Relationships.subjectId, account.id),
|
|
||||||
eq(Relationships.ownerId, user.id),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const foundRelationship = await getRelationshipToOtherUser(
|
|
||||||
user,
|
user,
|
||||||
account,
|
account,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!foundRelationship) {
|
|
||||||
return errorResponse("Relationship not found", 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if rejecting remote follow
|
// Check if rejecting remote follow
|
||||||
if (account.isRemote()) {
|
if (account.isRemote()) {
|
||||||
// Federate follow reject
|
// Federate follow reject
|
||||||
await sendFollowReject(account, user);
|
await sendFollowReject(account, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonResponse(relationshipToApi(foundRelationship));
|
return jsonResponse(foundRelationship.toApi());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -11,18 +11,16 @@ import {
|
||||||
} from "@lysand-org/federation";
|
} from "@lysand-org/federation";
|
||||||
import type { Entity } from "@lysand-org/federation/types";
|
import type { Entity } from "@lysand-org/federation/types";
|
||||||
import type { SocketAddress } from "bun";
|
import type { SocketAddress } from "bun";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { matches } from "ip-matching";
|
import { matches } from "ip-matching";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { type ValidationError, isValidationError } from "zod-validation-error";
|
import { type ValidationError, isValidationError } from "zod-validation-error";
|
||||||
import {
|
import { sendFollowAccept } from "~/classes/functions/user";
|
||||||
getRelationshipToOtherUser,
|
|
||||||
sendFollowAccept,
|
|
||||||
} from "~/classes/functions/user";
|
|
||||||
import { db } from "~/drizzle/db";
|
import { db } from "~/drizzle/db";
|
||||||
import { Notes, Notifications, Relationships } from "~/drizzle/schema";
|
import { Notes, Notifications } from "~/drizzle/schema";
|
||||||
import { config } from "~/packages/config-manager";
|
import { config } from "~/packages/config-manager";
|
||||||
import { Note } from "~/packages/database-interface/note";
|
import { Note } from "~/packages/database-interface/note";
|
||||||
|
import { Relationship } from "~/packages/database-interface/relationship";
|
||||||
import { User } from "~/packages/database-interface/user";
|
import { User } from "~/packages/database-interface/user";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -228,35 +226,22 @@ export default (app: Hono) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundRelationship =
|
const foundRelationship =
|
||||||
await getRelationshipToOtherUser(account, user);
|
await Relationship.fromOwnerAndSubject(
|
||||||
|
account,
|
||||||
|
user,
|
||||||
|
);
|
||||||
|
|
||||||
if (foundRelationship.following) {
|
if (foundRelationship.data.following) {
|
||||||
return response("Already following", 200);
|
return response("Already following", 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db
|
await foundRelationship.update({
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
following: !user.data.isLocked,
|
following: !user.data.isLocked,
|
||||||
requested: user.data.isLocked,
|
requested: user.data.isLocked,
|
||||||
showingReblogs: true,
|
showingReblogs: true,
|
||||||
notifying: true,
|
notifying: true,
|
||||||
languages: [],
|
languages: [],
|
||||||
})
|
});
|
||||||
.where(eq(Relationships.id, foundRelationship.id));
|
|
||||||
|
|
||||||
// Update other user's requested_by
|
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
requestedBy: user.data.isLocked,
|
|
||||||
})
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(Relationships.subjectId, account.id),
|
|
||||||
eq(Relationships.ownerId, user.id),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await db.insert(Notifications).values({
|
await db.insert(Notifications).values({
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
|
|
@ -280,36 +265,22 @@ export default (app: Hono) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundRelationship =
|
const foundRelationship =
|
||||||
await getRelationshipToOtherUser(user, account);
|
await Relationship.fromOwnerAndSubject(
|
||||||
|
user,
|
||||||
|
account,
|
||||||
|
);
|
||||||
|
|
||||||
if (!foundRelationship.requested) {
|
if (!foundRelationship.data.requested) {
|
||||||
return response(
|
return response(
|
||||||
"There is no follow request to accept",
|
"There is no follow request to accept",
|
||||||
200,
|
200,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db
|
await foundRelationship.update({
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
following: true,
|
|
||||||
requested: false,
|
requested: false,
|
||||||
})
|
following: true,
|
||||||
.where(eq(Relationships.id, foundRelationship.id));
|
});
|
||||||
|
|
||||||
// Update other user's data
|
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
followedBy: true,
|
|
||||||
requestedBy: false,
|
|
||||||
})
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(Relationships.subjectId, account.id),
|
|
||||||
eq(Relationships.ownerId, user.id),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return response("Follow request accepted", 200);
|
return response("Follow request accepted", 200);
|
||||||
},
|
},
|
||||||
|
|
@ -321,36 +292,22 @@ export default (app: Hono) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundRelationship =
|
const foundRelationship =
|
||||||
await getRelationshipToOtherUser(user, account);
|
await Relationship.fromOwnerAndSubject(
|
||||||
|
user,
|
||||||
|
account,
|
||||||
|
);
|
||||||
|
|
||||||
if (!foundRelationship.requested) {
|
if (!foundRelationship.data.requested) {
|
||||||
return response(
|
return response(
|
||||||
"There is no follow request to reject",
|
"There is no follow request to reject",
|
||||||
200,
|
200,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db
|
await foundRelationship.update({
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
requested: false,
|
requested: false,
|
||||||
following: false,
|
following: false,
|
||||||
})
|
});
|
||||||
.where(eq(Relationships.id, foundRelationship.id));
|
|
||||||
|
|
||||||
// Update other user's data
|
|
||||||
await db
|
|
||||||
.update(Relationships)
|
|
||||||
.set({
|
|
||||||
followedBy: false,
|
|
||||||
requestedBy: false,
|
|
||||||
})
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(Relationships.subjectId, account.id),
|
|
||||||
eq(Relationships.ownerId, user.id),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return response("Follow request rejected", 200);
|
return response("Follow request rejected", 200);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue