wildebeest/functions/ap/users/[id]/outbox/page.ts

120 wiersze
3.8 KiB
TypeScript

import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import * as objects from 'wildebeest/backend/src/activitypub/objects'
import type { Activity } from 'wildebeest/backend/src/activitypub/activities'
import { getPersonById } from 'wildebeest/backend/src/activitypub/actors'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import type { Env } from 'wildebeest/backend/src/types/env'
import type { Note } from 'wildebeest/backend/src/activitypub/objects/note'
import * as activityCreate from 'wildebeest/backend/src/activitypub/activities/create'
import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE, params.id as string)
}
const headers = {
'content-type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'content-type, authorization',
}
const DEFAULT_LIMIT = 20
export async function handleRequest(domain: string, db: D1Database, id: string): Promise<Response> {
const handle = parseHandle(id)
if (handle.domain !== null) {
return new Response('', { status: 403 })
}
const actorId = actorURL(domain, handle.localPart)
const actor = await getPersonById(db, actorId)
if (actor === null) {
return new Response('', { status: 404 })
}
const items: Array<Activity> = []
// TODO: eventually move to a shared file
const QUERY = `
SELECT objects.*
FROM outbox_objects
INNER JOIN objects ON objects.id = outbox_objects.object_id
WHERE outbox_objects.actor_id = ?1
AND objects.type = 'Note'
AND outbox_objects.target = '${PUBLIC_GROUP}'
ORDER by outbox_objects.cdate DESC
LIMIT ?2
`
const { success, error, results } = await db.prepare(QUERY).bind(actorId.toString(), DEFAULT_LIMIT).all()
if (!success) {
throw new Error('SQL error: ' + error)
}
if (results && results.length > 0) {
for (let i = 0, len = results.length; i < len; i++) {
const result: any = results[i]
const properties = JSON.parse(result.properties)
const note: Note = {
id: new URL(objects.uri(domain, result.id)),
atomUri: new URL(objects.uri(domain, result.id)),
type: 'Note',
published: new Date(result.cdate).toISOString(),
// FIXME: stub
sensitive: false,
attachment: [],
tag: [],
replies: {
first: {
items: [],
// FIXME: stub values
next: 'https://example.com/users/a/statuses/109372762645660352/replies?only_other_accounts=true&page=true',
partOf: 'https://example.com/users/a/statuses/109372762645660352/replies',
type: 'CollectionPage',
},
id: 'https://example.com/users/a/statuses/109372762645660352/replies',
type: 'Collection',
},
...properties,
}
const activity = activityCreate.create(domain, actor, note)
delete activity['@context']
activity.id = note.id + '/activity'
activity.published = new Date(result.cdate).toISOString()
activity.to = ['https://www.w3.org/ns/activitystreams#Public']
activity.cc = [actor.followers]
items.push(activity)
}
}
const out = {
'@context': [
'https://www.w3.org/ns/activitystreams',
{
ostatus: 'http://ostatus.org#',
atomUri: 'ostatus:atomUri',
inReplyToAtomUri: 'ostatus:inReplyToAtomUri',
conversation: 'ostatus:conversation',
sensitive: 'as:sensitive',
toot: 'http://joinmastodon.org/ns#',
votersCount: 'toot:votersCount',
},
],
id: new URL(actor.outbox + '/page'),
type: 'OrderedCollectionPage',
partOf: actor.outbox,
orderedItems: items,
// FIXME: stub values
prev: 'https://example.com/todo',
}
return new Response(JSON.stringify(out), { headers })
}