- Introduced single Redis connection for managing federation delivery jobs, improving reliability and performance. - Updated environment configuration to include Redis connection details and allowed hostnames for CORS. - Refactored existing code to streamline federation processes and improve error handling. - Enhanced database schema to track acknowledgment status for follow requests. This update aims to strengthen the federation's communication capabilities and ensure better handling of server interactions. #3 #4
256 lines
No EOL
5.2 KiB
TypeScript
256 lines
No EOL
5.2 KiB
TypeScript
import { BetterAuthPluginDBSchema } from "better-auth";
|
|
import { z } from "zod";
|
|
|
|
const postContentBlockSchema = z.discriminatedUnion("type", [
|
|
z.object({
|
|
type: z.literal("text"),
|
|
value: z.string().min(1, "Text content cannot be empty"),
|
|
}),
|
|
z.object({
|
|
type: z.literal("image"),
|
|
url: z.url("Image must be a valid URL"),
|
|
index: z.number().min(0, "Index must be a positive number"),
|
|
size: z.number().min(0, "Size must be a positive number"),
|
|
}),
|
|
z.object({
|
|
type: z.literal("video"),
|
|
url: z.url("Video must be a valid URL"),
|
|
size: z.number().min(0, "Size must be a positive number"),
|
|
index: z.number().min(0, "Index must be a positive number"),
|
|
}),
|
|
z.object({
|
|
type: z.literal("audio"),
|
|
url: z.url("Audio must be a valid URL"),
|
|
size: z.number().min(0, "Size must be a positive number"),
|
|
}),
|
|
z.object({
|
|
type: z.literal("link"),
|
|
url: z.url("Link must be a valid URL"),
|
|
}),
|
|
], { error: 'Block "type" must be one of: text, image, video, audio, link' });
|
|
|
|
export const postContentSchema = z
|
|
.array(postContentBlockSchema, { error: "Post content must be an array of blocks" })
|
|
.min(1, "Post must contain at least one content block");
|
|
|
|
export default {
|
|
posts: {
|
|
fields: {
|
|
content: {
|
|
type: "json",
|
|
required: true,
|
|
index: false,
|
|
transform: {
|
|
output: (value) => {
|
|
let parsed: unknown;
|
|
try {
|
|
parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
} catch {
|
|
throw new Error("Post content is not valid JSON");
|
|
}
|
|
|
|
const validated = postContentSchema.safeParse(parsed);
|
|
if (!validated.success) {
|
|
const issues = validated.error.issues
|
|
.map((i) => `[${i.path.join(".")}] ${i.message}`)
|
|
.join("; ");
|
|
throw new Error(`Invalid post content: ${issues}`);
|
|
}
|
|
|
|
return validated.data;
|
|
}
|
|
}
|
|
},
|
|
authorId: {
|
|
type: "string",
|
|
required: false,
|
|
index: false,
|
|
references: {
|
|
model: "user",
|
|
field: "id"
|
|
}
|
|
},
|
|
federatedAuthorId: {
|
|
type: "string",
|
|
required: false,
|
|
index: false,
|
|
},
|
|
published: {
|
|
type: "date",
|
|
required: true,
|
|
index: false,
|
|
},
|
|
// "isLocal" will be used to determine if the post should only exist
|
|
// on the local server or if it should be propagated to other servers
|
|
isLocal: {
|
|
type: "boolean",
|
|
required: true,
|
|
index: false,
|
|
defaultValue: false,
|
|
},
|
|
// "isPrivate" will be used to determine if the post should be visible only for the user's followers
|
|
// If "isLocal" is set to true and this to false, only users on the same server will be able to see the psot
|
|
isPrivate: {
|
|
type: "boolean",
|
|
required: false,
|
|
index: false,
|
|
defaultValue: false,
|
|
},
|
|
createdAt: {
|
|
type: "date",
|
|
required: true,
|
|
index: false
|
|
},
|
|
federationUrl: {
|
|
type: "string",
|
|
required: false,
|
|
index: true,
|
|
},
|
|
// This serves as a reference to the post on the original server this post came from
|
|
federationPostId: {
|
|
type: "string",
|
|
required: false,
|
|
index: true,
|
|
}
|
|
}
|
|
},
|
|
follows: {
|
|
fields: {
|
|
followerId: {
|
|
type: "string",
|
|
required: true,
|
|
index: false,
|
|
},
|
|
followingId: {
|
|
type: "string",
|
|
required: true,
|
|
index: false,
|
|
},
|
|
accepted: {
|
|
type: "boolean",
|
|
required: true,
|
|
index: false,
|
|
defaultValue: false,
|
|
},
|
|
createdAt: {
|
|
type: "date",
|
|
required: true,
|
|
index: false
|
|
},
|
|
followerServerUrl: {
|
|
type: "string",
|
|
required: false,
|
|
index: true,
|
|
references: {
|
|
model: "serverRegistry",
|
|
field: "url"
|
|
}
|
|
},
|
|
followingServerUrl: {
|
|
type: "string",
|
|
required: false,
|
|
index: true,
|
|
references: {
|
|
model: "serverRegistry",
|
|
field: "url"
|
|
}
|
|
},
|
|
acknowledged: {
|
|
type: "boolean",
|
|
required: true,
|
|
index: false,
|
|
defaultValue: false,
|
|
},
|
|
}
|
|
},
|
|
deliveryJobs: {
|
|
fields: {
|
|
targetUrl: {
|
|
type: "string",
|
|
required: true,
|
|
index: false
|
|
},
|
|
// This could be encrypted, so we're not using a transform function to check for validity
|
|
payload: {
|
|
type: "string",
|
|
required: true,
|
|
index: false
|
|
},
|
|
attempts: {
|
|
type: "number",
|
|
required: true,
|
|
index: false,
|
|
defaultValue: 0,
|
|
},
|
|
lastAttemptedAt: {
|
|
type: "date",
|
|
required: false,
|
|
index: false,
|
|
},
|
|
nextAttemptAt: {
|
|
type: "date",
|
|
required: false,
|
|
index: false,
|
|
},
|
|
createdAt: {
|
|
type: "date",
|
|
required: true,
|
|
index: false
|
|
}
|
|
}
|
|
},
|
|
mutes: {
|
|
fields: {
|
|
userId: {
|
|
type: "string",
|
|
required: true,
|
|
index: false,
|
|
references: {
|
|
model: "user",
|
|
field: "id"
|
|
}
|
|
},
|
|
mutedUserId: {
|
|
type: "string",
|
|
required: true,
|
|
index: false,
|
|
references: {
|
|
model: "user",
|
|
field: "id"
|
|
}
|
|
},
|
|
createdAt: {
|
|
type: "date",
|
|
required: true,
|
|
index: false
|
|
}
|
|
}
|
|
},
|
|
blocks: {
|
|
fields: {
|
|
blockerId: {
|
|
type: "string",
|
|
required: true,
|
|
index: false,
|
|
references: {
|
|
model: "user",
|
|
field: "id"
|
|
}
|
|
},
|
|
blockedUserId: {
|
|
type: "string",
|
|
required: true,
|
|
index: false,
|
|
references: {
|
|
model: "user",
|
|
field: "id"
|
|
}
|
|
},
|
|
createdAt: {
|
|
type: "date",
|
|
required: true,
|
|
index: false
|
|
}
|
|
}
|
|
}
|
|
} satisfies BetterAuthPluginDBSchema |