mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
feat(federation): ✨ Implement inbound federation of likes and like deletion
This commit is contained in:
parent
df84572148
commit
0a31b7a8f6
|
|
@ -12,12 +12,13 @@ import { eq } from "drizzle-orm";
|
|||
import { matches } from "ip-matching";
|
||||
import { z } from "zod";
|
||||
import { type ValidationError, isValidationError } from "zod-validation-error";
|
||||
import { Like } from "~/classes/database/like";
|
||||
import { Note } from "~/classes/database/note";
|
||||
import { Relationship } from "~/classes/database/relationship";
|
||||
import { User } from "~/classes/database/user";
|
||||
import { sendFollowAccept } from "~/classes/functions/user";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Notes, Notifications } from "~/drizzle/schema";
|
||||
import { Likes, Notes, Notifications } from "~/drizzle/schema";
|
||||
import { config } from "~/packages/config-manager";
|
||||
import { ErrorSchema } from "~/types/api";
|
||||
|
||||
|
|
@ -363,6 +364,23 @@ export default apiRoute((app) =>
|
|||
|
||||
return context.text("Follow request rejected", 200);
|
||||
},
|
||||
"pub.versia:likes/Like": async (like) => {
|
||||
const author = await User.resolve(like.author);
|
||||
|
||||
if (!author) {
|
||||
return context.json({ error: "Author not found" }, 400);
|
||||
}
|
||||
|
||||
const note = await Note.resolve(like.liked);
|
||||
|
||||
if (!note) {
|
||||
return context.json({ error: "Note not found" }, 400);
|
||||
}
|
||||
|
||||
await author.like(note, like.uri);
|
||||
|
||||
return context.text("Like added", 200);
|
||||
},
|
||||
// "delete" is a reserved keyword in JS
|
||||
delete: async (delete_) => {
|
||||
// Delete the specified object from database, if it exists and belongs to the user
|
||||
|
|
@ -401,6 +419,24 @@ export default apiRoute((app) =>
|
|||
|
||||
break;
|
||||
}
|
||||
case "pub.versia:likes/Like": {
|
||||
const like = await Like.fromSql(
|
||||
eq(Likes.uri, toDelete),
|
||||
eq(Likes.likerId, user.id),
|
||||
);
|
||||
|
||||
if (like) {
|
||||
await like.delete();
|
||||
return context.text("Like deleted", 200);
|
||||
}
|
||||
|
||||
return context.json(
|
||||
{
|
||||
error: "Like not found or not owned by user",
|
||||
},
|
||||
404,
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return context.json(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -447,9 +447,10 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
*
|
||||
* If the note is already liked, it will return the existing like. Also creates a notification for the author of the note.
|
||||
* @param note The note to like
|
||||
* @param uri The URI of the like, if it is remote
|
||||
* @returns The like object created or the existing like
|
||||
*/
|
||||
public async like(note: Note): Promise<Like> {
|
||||
public async like(note: Note, uri?: string): Promise<Like> {
|
||||
// Check if the user has already liked the note
|
||||
const existingLike = await Like.fromSql(
|
||||
and(eq(Likes.likerId, this.id), eq(Likes.likedId, note.id)),
|
||||
|
|
@ -462,9 +463,10 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
const newLike = await Like.insert({
|
||||
likerId: this.id,
|
||||
likedId: note.id,
|
||||
uri,
|
||||
});
|
||||
|
||||
if (note.author.data.instanceId === this.data.instanceId) {
|
||||
if (this.isLocal() && note.author.isLocal()) {
|
||||
// Notify the user that their post has been favourited
|
||||
await db.insert(Notifications).values({
|
||||
accountId: this.id,
|
||||
|
|
@ -472,7 +474,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
notifiedId: note.author.id,
|
||||
noteId: note.id,
|
||||
});
|
||||
} else {
|
||||
} else if (this.isLocal() && note.author.isRemote()) {
|
||||
// Federate the like
|
||||
this.federateToFollowers(newLike.toVersia());
|
||||
}
|
||||
|
|
@ -498,6 +500,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
|
||||
await likeToDelete.delete();
|
||||
|
||||
if (this.isLocal() && note.author.isLocal()) {
|
||||
// Remove any eventual notifications for this like
|
||||
await db
|
||||
.delete(Notifications)
|
||||
|
|
@ -509,8 +512,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
eq(Notifications.noteId, note.id),
|
||||
),
|
||||
);
|
||||
|
||||
if (this.isLocal() && note.author.isRemote()) {
|
||||
} else if (this.isLocal() && note.author.isRemote()) {
|
||||
// User is local, federate the delete
|
||||
this.federateToFollowers(likeToDelete.unlikeToVersia(this));
|
||||
}
|
||||
|
|
|
|||
2
drizzle/migrations/0034_jittery_proemial_gods.sql
Normal file
2
drizzle/migrations/0034_jittery_proemial_gods.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "Likes" ADD COLUMN "uri" text;--> statement-breakpoint
|
||||
ALTER TABLE "Likes" ADD CONSTRAINT "Likes_uri_unique" UNIQUE("uri");
|
||||
2165
drizzle/migrations/meta/0034_snapshot.json
Normal file
2165
drizzle/migrations/meta/0034_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -239,6 +239,13 @@
|
|||
"when": 1726491670160,
|
||||
"tag": "0033_panoramic_sister_grimm",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 34,
|
||||
"version": "7",
|
||||
"when": 1729789587213,
|
||||
"tag": "0034_jittery_proemial_gods",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ export const Markers = pgTable("Markers", {
|
|||
|
||||
export const Likes = pgTable("Likes", {
|
||||
id: uuid("id").default(sql`uuid_generate_v7()`).primaryKey().notNull(),
|
||||
uri: text("uri").unique(),
|
||||
likerId: uuid("likerId")
|
||||
.notNull()
|
||||
.references(() => Users.id, {
|
||||
|
|
|
|||
Loading…
Reference in a new issue