Federation (#327)
* initial commit federation * more federation * more federation * more federation * completes follow, accept, undo * add trail create activity * process trail create activity * more trail create activity * adds missing endpoints * adds update and delete activity * adds comment activities * adds activities back * adds summit logs activities * deletes follow counts table * fixes migrations * fixes migrations * fixes migrations * fixes migrations * adds remote profiles * ctd * ctd * we are getting closer... * adds public summit logs * fixes federated trails in lists * adds remote lists * adds list activites * fixes lists * adds notifications * adds iri redirect * fixes comments and summitlogs * fixes follows and profiles * adds asynchronous send * fixes list search * adds encryption key * bug fixes * fixes activity signing * removes custon activity object types * adds html editor * fixes html editor * fixes display issues on mastodon * adds federated sharing * finishes announcements * fixes small summit log issues * adds trail likes * finalizes likes * fixes images for komoot * add disable federation option * adds private profiles * updates docs * adds federated comments * adds trail and comments actvitiypub routes * adds mentions to editor * adds mentions to trails, comments, summit logs * updates theme * updates theme * update docs * update docs * updates docs * fixes various frontend problems * fixes activitypub follows api * updates docs * updates docs --------- Co-authored-by: Christian Beutel <>
This commit is contained in:
14
web/src/lib/models/activitypub/activity.ts
Normal file
14
web/src/lib/models/activitypub/activity.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { User } from "../user";
|
||||
|
||||
export interface Activity {
|
||||
id: string;
|
||||
iri: string;
|
||||
type: string;
|
||||
to: string[];
|
||||
cc: string[];
|
||||
bto: string[];
|
||||
bcc: string[];
|
||||
object: any;
|
||||
actor: string;
|
||||
published: string;
|
||||
}
|
||||
25
web/src/lib/models/activitypub/actor.ts
Normal file
25
web/src/lib/models/activitypub/actor.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { User } from "../user";
|
||||
|
||||
export interface Actor {
|
||||
id?: string;
|
||||
username: string;
|
||||
preferred_username: string;
|
||||
domain?: string;
|
||||
summary?: string;
|
||||
published?: string;
|
||||
followerCount?: number,
|
||||
followingCount?: number,
|
||||
iri: string;
|
||||
inbox: string;
|
||||
outbox?: string;
|
||||
icon?: string;
|
||||
followers?: string;
|
||||
following?: string;
|
||||
isLocal: boolean;
|
||||
public_key: string;
|
||||
last_fetched: string;
|
||||
user?: string
|
||||
expand?: {
|
||||
user?: User
|
||||
}
|
||||
}
|
||||
12
web/src/lib/models/activitypub/webfinger_response.ts
Normal file
12
web/src/lib/models/activitypub/webfinger_response.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface WebfingerResponse {
|
||||
subject: string
|
||||
aliases: string[]
|
||||
links: Link[]
|
||||
}
|
||||
|
||||
export interface Link {
|
||||
rel: string
|
||||
type: string
|
||||
href: string
|
||||
}
|
||||
|
||||
@@ -3,15 +3,13 @@ import type { Comment } from "../comment";
|
||||
|
||||
const CommentCreateSchema = z.object({
|
||||
text: z.string(),
|
||||
rating: z.number().optional(),
|
||||
author: z.string().length(15),
|
||||
trail: z.string().length(15),
|
||||
trail: z.string(),
|
||||
|
||||
}) satisfies ZodType<Comment>
|
||||
|
||||
const CommentUpdateSchema = z.object({
|
||||
text: z.string().optional(),
|
||||
rating: z.number().optional(),
|
||||
}) satisfies ZodType<Partial<Comment>>
|
||||
|
||||
export { CommentCreateSchema, CommentUpdateSchema }
|
||||
@@ -2,10 +2,9 @@ import { z, ZodType } from "zod";
|
||||
import type { Follow } from "../follow";
|
||||
|
||||
const FollowCreateSchema = z.object({
|
||||
follower: z.string().length(15),
|
||||
followee: z.string().length(15),
|
||||
|
||||
}) satisfies ZodType<Follow>
|
||||
})
|
||||
|
||||
|
||||
export { FollowCreateSchema };
|
||||
|
||||
@@ -2,7 +2,7 @@ import { z, ZodType } from "zod";
|
||||
import type { ListShare } from "../list_share";
|
||||
|
||||
const ListShareCreateSchema = z.object({
|
||||
user: z.string().length(15),
|
||||
actor: z.string().url(),
|
||||
list: z.string().length(15),
|
||||
permission: z.enum(["view", "edit"])
|
||||
|
||||
|
||||
@@ -12,11 +12,12 @@ const SummitLogCreateSchema = z.object({
|
||||
elevation_loss: z.number().nonnegative().optional(),
|
||||
duration: z.number().nonnegative().optional(),
|
||||
author: z.string().length(15),
|
||||
trail: z.string().length(15).optional(),
|
||||
photos: z.array(z.string()).default([])
|
||||
}) satisfies ZodType<Partial<SummitLog>>
|
||||
|
||||
const SummitLogUpdateSchema = z.object({
|
||||
date: z.string().date().refine((val) => !val || !isNaN(Date.parse(val)), "invalid-date").optional(),
|
||||
date: z.string().date().refine((val) => !val || !isNaN(Date.parse(val)), "invalid-date").optional(),
|
||||
text: z.string().optional(),
|
||||
gpx: z.string().optional(),
|
||||
distance: z.number().nonnegative().optional(),
|
||||
|
||||
10
web/src/lib/models/api/trail_like_schema.ts
Normal file
10
web/src/lib/models/api/trail_like_schema.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { z, ZodType } from "zod";
|
||||
import type { TrailLike } from "../trail_like";
|
||||
|
||||
const TrailLikeCreateSchema = z.object({
|
||||
actor: z.string().length(15),
|
||||
trail: z.string().length(15),
|
||||
|
||||
}) satisfies ZodType<TrailLike>
|
||||
|
||||
export { TrailLikeCreateSchema };
|
||||
@@ -19,7 +19,7 @@ const TrailCreateSchema = z.object({
|
||||
photos: z.array(z.string()).default([]),
|
||||
thumbnail: z.number().int().nonnegative().optional(),
|
||||
waypoints: z.array(z.string()).default([]),
|
||||
summit_logs: z.array(z.string()).default([]),
|
||||
like_count: z.number().int().min(0).optional().default(0),
|
||||
category: z.string().length(15).optional().or(z.literal('')),
|
||||
tags: z.array(z.string()).default([]),
|
||||
gpx: z.string().optional(),
|
||||
@@ -45,7 +45,7 @@ const TrailUpdateSchema = z.object({
|
||||
"photos+": z.string().optional(),
|
||||
thumbnail: z.number().int().nonnegative().optional(),
|
||||
waypoints: z.array(z.string()).optional(),
|
||||
summit_logs: z.array(z.string()).optional(),
|
||||
like_count: z.number().int().min(0).optional().default(0),
|
||||
category: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
gpx: z.string().optional(),
|
||||
|
||||
@@ -2,7 +2,7 @@ import { z, ZodType } from "zod";
|
||||
import type { TrailShare } from "../trail_share";
|
||||
|
||||
const TrailShareCreateSchema = z.object({
|
||||
user: z.string().length(15),
|
||||
actor: z.string().url(),
|
||||
trail: z.string().length(15),
|
||||
permission: z.enum(["view", "edit"])
|
||||
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import type { UserAnonymous } from "./user";
|
||||
import type { Actor } from "./activitypub/actor";
|
||||
|
||||
export class Comment {
|
||||
id?: string;
|
||||
text: string;
|
||||
rating?: number;
|
||||
author: string;
|
||||
trail: string;
|
||||
created?: string;
|
||||
updated?: string;
|
||||
iri?: string;
|
||||
expand?: {
|
||||
author: UserAnonymous
|
||||
author: Actor
|
||||
}
|
||||
|
||||
constructor(text: string, rating: number, author: string, trail: string) {
|
||||
constructor(text: string, author: string, trail: string) {
|
||||
this.text = text;
|
||||
this.rating = rating;
|
||||
this.author = author;
|
||||
this.trail = trail;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import type { User, UserAnonymous } from "./user";
|
||||
import type { Actor } from "./activitypub/actor";
|
||||
|
||||
export class Follow {
|
||||
id?: string;
|
||||
follower: string;
|
||||
followee: string;
|
||||
status: "pending" | "accepted";
|
||||
expand?: {
|
||||
follower: UserAnonymous,
|
||||
followee: UserAnonymous
|
||||
follower: Actor,
|
||||
followee: Actor
|
||||
}
|
||||
|
||||
constructor(follower: string, followee: string) {
|
||||
this.follower = follower;
|
||||
this.followee = followee;
|
||||
this.status = "pending"
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,24 @@
|
||||
import type { Trail } from "./trail";
|
||||
import type { ListShare } from "./list_share";
|
||||
import type { UserAnonymous } from "./user";
|
||||
import type { Actor } from "./activitypub/actor";
|
||||
|
||||
export class List {
|
||||
id?: string;
|
||||
name: string;
|
||||
public: boolean;
|
||||
description?: string;
|
||||
elevation_gain?: number;
|
||||
elevation_loss?: number;
|
||||
distance?: number;
|
||||
duration?: number;
|
||||
avatar?: string;
|
||||
trails?: string[];
|
||||
iri?: string;
|
||||
expand?: {
|
||||
trails?: Trail[]
|
||||
list_share_via_list?: ListShare[]
|
||||
author?: UserAnonymous;
|
||||
author?: Actor;
|
||||
|
||||
}
|
||||
author: string;
|
||||
@@ -22,7 +28,7 @@ export class List {
|
||||
this.public = params?.public ?? false
|
||||
this.expand = { trails: trails };
|
||||
this.trails = trails.map(t => t.id!);
|
||||
this.description = params?.description;
|
||||
this.description = params?.description ?? "";
|
||||
this.avatar = params?.avatar;
|
||||
this.author = params?.author ?? "000000000000000";
|
||||
}
|
||||
@@ -35,35 +41,4 @@ export interface ListFilter {
|
||||
public?: boolean;
|
||||
shared?: boolean;
|
||||
sortOrder?: "+" | "-"
|
||||
}
|
||||
|
||||
export const enum ExpandType {
|
||||
None = 0,
|
||||
Trails = 1 << 0,
|
||||
Waypoints = 1 << 1,
|
||||
TrailCategories = 1 << 2,
|
||||
ListShares = 1 << 3,
|
||||
All = ~(~0 << 4),
|
||||
}
|
||||
|
||||
export function ExpandTypeToString(e: ExpandType) : string {
|
||||
|
||||
if (e == ExpandType.None)
|
||||
return "";
|
||||
|
||||
var ret = "";
|
||||
if ((e & ExpandType.Trails) === ExpandType.Trails) {
|
||||
ret += "trails,";
|
||||
}
|
||||
if ((e & ExpandType.Waypoints) === ExpandType.Waypoints) {
|
||||
ret += "trails.waypoints,";
|
||||
}
|
||||
if ((e & ExpandType.TrailCategories) === ExpandType.TrailCategories) {
|
||||
ret += "trails.category,";
|
||||
}
|
||||
if ((e & ExpandType.ListShares) === ExpandType.ListShares) {
|
||||
ret += "list_share_via_list,";
|
||||
}
|
||||
|
||||
return ret.slice(0, -1);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
import type { Actor } from "./activitypub/actor";
|
||||
import type { User } from "./user";
|
||||
|
||||
export class ListShare {
|
||||
id?: string;
|
||||
user: string;
|
||||
actor: string;
|
||||
list: string;
|
||||
permission: "view" | "edit"
|
||||
expand?: {
|
||||
user: User
|
||||
actor: Actor
|
||||
}
|
||||
|
||||
constructor(user: string, list: string, permission: "view" | "edit") {
|
||||
this.user = user;
|
||||
this.actor = user;
|
||||
this.list = list;
|
||||
this.permission = permission
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import type { UserAnonymous } from "./user";
|
||||
import type { Actor } from "./activitypub/actor";
|
||||
|
||||
enum NotificationType {
|
||||
trailCreate = "trail_create",
|
||||
trailShare = "trail_share",
|
||||
listCreate = "list_create",
|
||||
trailLike = "trail_like",
|
||||
listShare = "list_share",
|
||||
newFollower = "new_follower",
|
||||
trailComment = "trail_comment"
|
||||
trailComment = "trail_comment",
|
||||
summitLogCreate = "summit_log_create",
|
||||
commentMention = "comment_mention",
|
||||
trailMention = "trail_mention",
|
||||
summitLogMention = "summit_log_mention",
|
||||
|
||||
};
|
||||
|
||||
interface Notification {
|
||||
@@ -18,9 +22,9 @@ interface Notification {
|
||||
author: string
|
||||
created: string;
|
||||
expand: {
|
||||
recipient: UserAnonymous;
|
||||
author: UserAnonymous;
|
||||
recipient: Actor;
|
||||
author: Actor;
|
||||
}
|
||||
}
|
||||
|
||||
export { type Notification, NotificationType }
|
||||
export { NotificationType, type Notification };
|
||||
|
||||
15
web/src/lib/models/profile.ts
Normal file
15
web/src/lib/models/profile.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { DateTime } from "activitypub-types";
|
||||
|
||||
export interface Profile {
|
||||
id: string;
|
||||
username: string;
|
||||
preferredUsername: string;
|
||||
acct: string;
|
||||
bio: string;
|
||||
followers: number;
|
||||
following: number;
|
||||
uri: string;
|
||||
icon: string;
|
||||
createdAt: DateTime;
|
||||
error?: string;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Actor } from "./activitypub/actor";
|
||||
import type { Trail } from "./trail";
|
||||
import type { UserAnonymous } from "./user";
|
||||
|
||||
class SummitLog {
|
||||
id?: string;
|
||||
@@ -14,11 +14,14 @@ class SummitLog {
|
||||
elevation_loss?: number
|
||||
duration?: number
|
||||
author: string;
|
||||
trail?: string;
|
||||
iri?: string;
|
||||
created?: string;
|
||||
|
||||
expand?: {
|
||||
gpx_data?: string;
|
||||
trails_via_summit_logs?: Trail[];
|
||||
author?: UserAnonymous
|
||||
trail?: Trail;
|
||||
author?: Actor
|
||||
}
|
||||
|
||||
constructor(date: string, params?: { id?: string, text?: string, distance?: number, elevation_loss?: number, elevation_gain?: number, duration?: number, photos?: string[] }) {
|
||||
@@ -41,6 +44,7 @@ interface SummitLogFilter {
|
||||
category: string[],
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
trail?: string;
|
||||
}
|
||||
|
||||
export { SummitLog, type SummitLogFilter };
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
interface Activity {
|
||||
interface TimelineItem {
|
||||
id: string;
|
||||
trail_id: string;
|
||||
trail_author_username: string;
|
||||
trail_author_domain: string;
|
||||
trail_iri: string;
|
||||
name: string,
|
||||
description: string;
|
||||
date: string;
|
||||
@@ -15,4 +18,4 @@ interface Activity {
|
||||
created: string;
|
||||
}
|
||||
|
||||
export { type Activity }
|
||||
export { type TimelineItem }
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { Actor } from "./activitypub/actor";
|
||||
import type { Category } from "./category";
|
||||
import type { Comment } from "./comment";
|
||||
import type { SummitLog } from "./summit_log";
|
||||
import type { Tag } from "./tag";
|
||||
import type { TrailLike } from "./trail_like";
|
||||
import type { TrailShare } from "./trail_share";
|
||||
import type { UserAnonymous } from "./user";
|
||||
import type { Waypoint } from "./waypoint";
|
||||
|
||||
class Trail {
|
||||
@@ -26,17 +27,21 @@ class Trail {
|
||||
category?: string;
|
||||
tags: string[];
|
||||
waypoints: string[];
|
||||
summit_logs: string[];
|
||||
polyline?: string;
|
||||
domain?: string;
|
||||
iri?: string;
|
||||
like_count: number;
|
||||
expand?: {
|
||||
tags?: Tag[]
|
||||
category?: Category;
|
||||
waypoints?: Waypoint[]
|
||||
summit_logs?: SummitLog[]
|
||||
author?: UserAnonymous
|
||||
summit_logs_via_trail?: SummitLog[]
|
||||
author?: Actor
|
||||
comments_via_trail?: Comment[]
|
||||
gpx_data?: string
|
||||
trail_share_via_trail?: TrailShare[]
|
||||
trail_like_via_trail?: TrailLike[]
|
||||
|
||||
}
|
||||
description?: string;
|
||||
author: string;
|
||||
@@ -83,13 +88,13 @@ class Trail {
|
||||
this.thumbnail = params?.thumbnail ?? 0;
|
||||
this.photos = params?.photos ?? [];
|
||||
this.waypoints = [];
|
||||
this.summit_logs = [];
|
||||
this.tags = []
|
||||
this.gpx = params?.gpx;
|
||||
this.like_count = 0
|
||||
this.expand = {
|
||||
category: params?.category,
|
||||
waypoints: params?.waypoints ?? [],
|
||||
summit_logs: params?.summit_logs ?? [],
|
||||
summit_logs_via_trail: params?.summit_logs ?? [],
|
||||
comments_via_trail: params?.comments ?? [],
|
||||
trail_share_via_trail: params?.shares ?? []
|
||||
}
|
||||
@@ -125,6 +130,7 @@ interface TrailFilter {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
completed?: boolean;
|
||||
liked?: boolean;
|
||||
sort: "name" | "distance" | "elevation_gain" | "created";
|
||||
sortOrder: "+" | "-"
|
||||
}
|
||||
@@ -168,8 +174,12 @@ interface TrailSearchResult {
|
||||
public: boolean;
|
||||
thumbnail: string;
|
||||
polyline?: string;
|
||||
likes?: string[];
|
||||
like_count: number;
|
||||
shares?: string[];
|
||||
tags?: string[]
|
||||
domain?: string;
|
||||
iri?: string;
|
||||
gpx: string;
|
||||
_geo: {
|
||||
lat: number,
|
||||
@@ -196,12 +206,16 @@ export const defaultTrailSearchAttributes = [
|
||||
"created",
|
||||
"public",
|
||||
"thumbnail",
|
||||
"domain",
|
||||
"gpx",
|
||||
"tags",
|
||||
"like_count",
|
||||
"shares",
|
||||
"iri",
|
||||
"_geo",]
|
||||
|
||||
|
||||
export { Trail };
|
||||
|
||||
export type { TrailBoundingBox, TrailFilter, TrailFilterValues, TrailSearchResult };
|
||||
|
||||
|
||||
17
web/src/lib/models/trail_like.ts
Normal file
17
web/src/lib/models/trail_like.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Actor } from "./activitypub/actor";
|
||||
import type { Trail } from "./trail";
|
||||
|
||||
export class TrailLike {
|
||||
id?: string;
|
||||
actor: string;
|
||||
trail: string;
|
||||
expand?: {
|
||||
actor: Actor,
|
||||
trail?: Trail
|
||||
}
|
||||
|
||||
constructor(actor: string, trail: string) {
|
||||
this.actor = actor;
|
||||
this.trail = trail;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
import type { User, UserAnonymous } from "./user";
|
||||
import type { Actor } from "./activitypub/actor";
|
||||
|
||||
export class TrailShare {
|
||||
id?: string;
|
||||
user: string;
|
||||
actor: string;
|
||||
trail: string;
|
||||
permission: "view" | "edit"
|
||||
expand?: {
|
||||
user: UserAnonymous
|
||||
actor: Actor
|
||||
}
|
||||
|
||||
constructor(user: string, trail: string, permission: "view" | "edit") {
|
||||
this.user = user;
|
||||
constructor(actor: string, trail: string, permission: "view" | "edit") {
|
||||
this.actor = actor;
|
||||
this.trail = trail;
|
||||
this.permission = permission
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ export type User = AuthRecord & {
|
||||
email?: string,
|
||||
password: string,
|
||||
avatar?: string;
|
||||
bio?: string;
|
||||
created?: string;
|
||||
}
|
||||
|
||||
@@ -15,6 +14,7 @@ export type UserAnonymous = {
|
||||
id: string,
|
||||
username?: string,
|
||||
avatar?: string;
|
||||
bio?: string;
|
||||
created?: string;
|
||||
private: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user