115 lines
3.7 KiB
TypeScript
115 lines
3.7 KiB
TypeScript
|
|
import { createFollowing, deleteFollowing, doActivity, getFollowing, listFollowers } from "./db"
|
||
|
|
import { ACTOR, ADMIN_PASSWORD, ADMIN_USERNAME, BASE_URL } from "./env"
|
||
|
|
import { send } from "./request"
|
||
|
|
|
||
|
|
export default (req: Request): Response | Promise<Response> | undefined => {
|
||
|
|
const url = new URL(req.url)
|
||
|
|
|
||
|
|
if(!url.pathname.startsWith('/admin')) return undefined
|
||
|
|
url.pathname = url.pathname.substring(6)
|
||
|
|
|
||
|
|
if(ADMIN_USERNAME && ADMIN_PASSWORD && !checkAuth(req.headers)) return new Response("", { status: 401 })
|
||
|
|
|
||
|
|
let match
|
||
|
|
if(req.method === "GET" && (match = url.pathname.match(/^\/test\/?$/i))) return new Response("", { status: 204 })
|
||
|
|
else if(req.method == "POST" && (match = url.pathname.match(/^\/create\/?$/i))) return create(req)
|
||
|
|
else if(req.method == "POST" && (match = url.pathname.match(/^\/follow\/([^\/]+)\/?$/i))) return follow(req, match[1])
|
||
|
|
else if(req.method == "DELETE" && (match = url.pathname.match(/^\/follow\/([^\/]+)\/?$/i))) return unfollow(req, match[1])
|
||
|
|
|
||
|
|
return undefined
|
||
|
|
}
|
||
|
|
|
||
|
|
const checkAuth = (headers: Headers): Boolean => {
|
||
|
|
// check the basic auth header
|
||
|
|
const auth = headers.get("Authorization")
|
||
|
|
const split = auth?.split("")
|
||
|
|
if(!split || split.length != 2 || split[0] !== "Basic") return false
|
||
|
|
const decoded = atob(split[1])
|
||
|
|
const [username, password] = decoded.split(":")
|
||
|
|
return username === ADMIN_USERNAME && password === ADMIN_PASSWORD
|
||
|
|
}
|
||
|
|
|
||
|
|
// create an activity
|
||
|
|
const create = async (req:Request):Promise<Response> => {
|
||
|
|
const body = await req.json()
|
||
|
|
|
||
|
|
// create the object, merging in supplied data
|
||
|
|
const date = new Date()
|
||
|
|
const id = date.getTime().toString(16)
|
||
|
|
const object = {
|
||
|
|
attributedTo: ACTOR,
|
||
|
|
published: date.toISOString(),
|
||
|
|
to: ["https://www.w3.org/ns/activitystreams#Public"],
|
||
|
|
cc: [`${ACTOR}/followers`],
|
||
|
|
url: `${ACTOR}/post/${id}`,
|
||
|
|
id: `${ACTOR}/post/${id}`,
|
||
|
|
...body.object
|
||
|
|
}
|
||
|
|
|
||
|
|
const activity = {
|
||
|
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||
|
|
id: `${ACTOR}/post/${id}/activity`,
|
||
|
|
type: "Create",
|
||
|
|
published: date.toISOString(),
|
||
|
|
actor: ACTOR,
|
||
|
|
to: ["https://www.w3.org/ns/activitystreams#Public"],
|
||
|
|
cc: [`${ACTOR}/followers`],
|
||
|
|
...body,
|
||
|
|
object: { ...object }
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO: actually create the object (and the activity??)
|
||
|
|
await doActivity(activity, id)
|
||
|
|
|
||
|
|
// loop through the list of followers
|
||
|
|
for (const follower of await listFollowers()) {
|
||
|
|
// send the activity to each follower
|
||
|
|
send(ACTOR, follower.actor, {
|
||
|
|
...activity,
|
||
|
|
cc: [follower.actor],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// return HTTP 204: no content (success)
|
||
|
|
return new Response("", { status: 204 })
|
||
|
|
}
|
||
|
|
|
||
|
|
const follow = async (req:Request, handle:string):Promise<Response> => {
|
||
|
|
const id = BASE_URL + '@' + handle
|
||
|
|
// send the follow request to the supplied actor
|
||
|
|
await send(ACTOR, handle, {
|
||
|
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||
|
|
id,
|
||
|
|
type: "Follow",
|
||
|
|
actor: ACTOR,
|
||
|
|
object: handle,
|
||
|
|
});
|
||
|
|
await createFollowing(handle, id)
|
||
|
|
|
||
|
|
return new Response("", { status: 204 })
|
||
|
|
}
|
||
|
|
|
||
|
|
const unfollow = async (req:Request, handle:string):Promise<Response> => {
|
||
|
|
// check to see if we are already following. If not, just return success
|
||
|
|
const existing = await getFollowing(handle)
|
||
|
|
if (!existing) return new Response("", { status: 204 })
|
||
|
|
|
||
|
|
// TODO: send the unfollow request (technically an undo follow activity)
|
||
|
|
await send(ACTOR, handle, {
|
||
|
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||
|
|
id: existing.id + "/undo",
|
||
|
|
type: "Undo",
|
||
|
|
actor: ACTOR,
|
||
|
|
object: {
|
||
|
|
id: existing.id,
|
||
|
|
type: "Follow",
|
||
|
|
actor: ACTOR,
|
||
|
|
object: handle,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
// delete the following reference from the database
|
||
|
|
deleteFollowing(handle);
|
||
|
|
|
||
|
|
return new Response("", { status: 204 })
|
||
|
|
}
|