Federation Refactoring & Architecture Improvements (#930)

* initial commit

* add lists

* update permissions

* fix waypoint create

* needs_full_sync for activitypub trails

* fixes build issues

* fix list get

* further CSRF protection

* update API docs

* adds rate limiter

* fix tiptap mentions

* a bit more cleanup of main.go

* Fix migration order

* fixes reviewed notes

* require context for activitpub server calls

* improve hashing for identifier

* fix copy paste error

* fix sync trail/list issues

* fix summit log/comment duplicates

* adaptions after trail merge

* fix dockerignore

---------

Co-authored-by: Christian Beutel <>
Co-authored-by: slothful-vassal <89943360+slothful-vassal@users.noreply.github.com>
This commit is contained in:
Flomp
2026-05-09 17:30:34 +02:00
committed by GitHub
parent 992ebfc0c4
commit d2ac49470a
73 changed files with 3773 additions and 2153 deletions

View File

@@ -43,7 +43,7 @@
error = "",
placeholder = "",
extraClasses = "",
searchListPosition = "absolute"
searchListPosition = "absolute",
}: Props = $props();
const fontSizes: SelectItem[] = [
@@ -87,7 +87,9 @@
return [
"a",
mergeAttributes(
{ href: `/profile/@${options.HTMLAttributes["data-label"]}` },
{
href: `/profile/@${options.HTMLAttributes["data-label"]}`,
},
options.HTMLAttributes,
),
options.renderText?.({
@@ -141,8 +143,9 @@
if (!box) {
return;
}
searchListElement.style.position = searchListPosition;
searchListElement.style.position =
searchListPosition;
searchListElement.style.top = `${box.bottom + window.scrollY + 4}px`;
searchListElement.style.left = `${box.left + window.scrollX}px`;
searchListElement.style.zIndex = "1001";
@@ -179,12 +182,12 @@
onKeyDown(props) {
if (props.event.key === "Escape") {
this.onExit?.({} as unknown as any)
this.onExit?.({} as unknown as any);
return true;
}
return false
return false;
},
onExit() {
unmount(component);
@@ -315,7 +318,7 @@
})
.run();
modal.closeModal();
modal.closeModal();
} catch (e) {
if (
e instanceof ZodError &&

View File

@@ -547,16 +547,19 @@
clusterPopup.on("close", () => {
unHighlightCluster(false);
});
map.on("mousemove", unHighlightClusterDistanceNotifier)
map.on("mousemove", unHighlightClusterDistanceNotifier);
}
function unHighlightClusterDistanceNotifier(e: M.MapMouseEvent) {
if (!clusterPopup || !map) {
return
return;
}
if (map.project(clusterPopup.getLngLat()).dist(map.project(e.lngLat)) > 60) {
if (
map.project(clusterPopup.getLngLat()).dist(map.project(e.lngLat)) >
60
) {
clusterPopup.remove();
map.off("mousemove", unHighlightClusterDistanceNotifier)
map.off("mousemove", unHighlightClusterDistanceNotifier);
}
}

View File

@@ -129,9 +129,8 @@
async function fetchComments() {
commentsLoading = true;
const trailId = trail.iri ? trail.iri : trail.id!;
try {
await comments_index(trailId, handle);
await comments_index(trail.id!);
} catch (e) {
show_toast({
type: "error",

View File

@@ -7,8 +7,8 @@ export interface Actor {
domain?: string;
summary?: string;
published?: string;
followerCount?: number,
followingCount?: number,
follower_count?: number,
following_count?: number,
iri: string;
inbox: string;
outbox?: string;

View File

@@ -22,6 +22,7 @@ export class List {
}
created?: string;
updated?: string;
author: string;
constructor(name: string, trails: Trail[], params?: { description?: string, public?: boolean, avatar?: string, author?: string }) {

View File

@@ -1,25 +1,17 @@
import { Comment } from "$lib/models/comment";
import type { Trail } from "$lib/models/trail";
import { APIError } from "$lib/util/api_util";
import { type ListResult } from "pocketbase";
import { get, writable, type Writable } from "svelte/store";
import { currentUser } from "./user_store";
import { isURL } from "$lib/util/file_util";
export const comments: Writable<Comment[]> = writable([])
export async function comments_index(trailId: string, handle?: string) {
export async function comments_index(trailId: string) {
let filter: string;
if (isURL(trailId)) {
filter = `trail="${trailId}"||trail.iri="${trailId}"||trail="${trailId.substring(trailId.length - 15)}"`
} else {
filter = `trail="${trailId}"`
}
let r = await fetch(`/api/v1/comment?` + new URLSearchParams({
filter,
let r = await fetch(`/api/v1/trail/${trailId}/comment?` + new URLSearchParams({
expand: "author",
sort: "-created",
...(handle ? { handle } : {})
}), {
method: 'GET',
})

View File

@@ -5,11 +5,9 @@ import { type ListResult } from "pocketbase";
let follows: Actor[] = [];
export async function follows_index(data: { username: string, type: "followers" | "following" }, page: number = 1, perPage: number = 10, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
export async function follows_index(page: number = 1, perPage: number = 10, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
const r = await f(`/api/v1/follow?` + new URLSearchParams({
handle: data.username,
type: data.type,
page: page.toString(),
perPage: perPage.toString(),
}), {
@@ -25,7 +23,7 @@ export async function follows_index(data: { username: string, type: "followers"
const result = page > 1 ? [...follows, ...fetchedFollows.items] : fetchedFollows.items
follows = result;
follows = result;
return { ...fetchedFollows, items: result };
}

View File

@@ -9,9 +9,10 @@ import { searchResultToLists } from "./list_store";
import type { ListSearchResult } from "./search_store";
import { buildFilterText } from "./summit_log_store";
import { searchResultToTrailList } from "./trail_store";
import type { Actor } from "$lib/models/activitypub/actor";
let feed: FeedItem[] = []
let follows: Actor[] = [];
export async function profile_show(handle: string, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
let r = await f('/api/v1/profile/' + handle, {
@@ -132,4 +133,26 @@ export async function profile_stats_index(handle: string, filter: SummitLogFilte
return result;
}
export async function profile_follows_index(handle: string, type: "followers" | "following", page: number, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
const r = await f(`/api/v1/profile/${handle}/follows?` + new URLSearchParams({
type,
page: page.toString(),
}), {
method: 'GET',
})
if (!r.ok) {
const response = await r.json();
throw new APIError(r.status, response.message, response.detail)
}
const fetchedFollows: ListResult<Actor> = await r.json();
const result = page > 1 ? [...follows, ...fetchedFollows.items] : fetchedFollows.items
follows = result;
return { ...fetchedFollows, items: result };
}

View File

@@ -12,7 +12,7 @@ export async function waypoints_create(waypoint: Waypoint, f: (url: RequestInfo
throw Error("Unauthenticated")
}
waypoint.author = user.id
waypoint.author = user.actor
let r = await f('/api/v1/waypoint', {
method: 'PUT',