mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 13:59:16 +01:00
Clean up timeline code, add new Context API endpoint
This commit is contained in:
parent
ace9f97275
commit
932fc3e4f5
6 changed files with 287 additions and 92 deletions
|
|
@ -1,7 +1,6 @@
|
|||
import { applyConfig } from "@api";
|
||||
import { errorResponse, jsonResponse } from "@response";
|
||||
import { MatchedRoute } from "bun";
|
||||
import { RawObject } from "~database/entities/RawObject";
|
||||
import { Status } from "~database/entities/Status";
|
||||
import { User } from "~database/entities/User";
|
||||
import { APIRouteMeta } from "~types/api";
|
||||
|
|
@ -31,9 +30,9 @@ export default async (
|
|||
|
||||
const { user } = await User.getFromRequest(req);
|
||||
|
||||
let foundStatus: RawObject | null;
|
||||
let foundStatus: Status | null;
|
||||
try {
|
||||
foundStatus = await RawObject.findOneBy({
|
||||
foundStatus = await Status.findOneBy({
|
||||
id,
|
||||
});
|
||||
} catch (e) {
|
||||
|
|
@ -43,7 +42,13 @@ export default async (
|
|||
if (!foundStatus) return errorResponse("Record not found", 404);
|
||||
|
||||
// Get all ancestors
|
||||
const ancestors = await foundStatus.getAncestors();
|
||||
const ancestors = await foundStatus.getAncestors(user);
|
||||
const descendants = await foundStatus.getDescendants(user);
|
||||
|
||||
return jsonResponse({});
|
||||
return jsonResponse({
|
||||
ancestors: await Promise.all(ancestors.map(status => status.toAPI())),
|
||||
descendants: await Promise.all(
|
||||
descendants.map(status => status.toAPI())
|
||||
),
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { applyConfig } from "@api";
|
||||
import { errorResponse, jsonResponse } from "@response";
|
||||
import { MatchedRoute } from "bun";
|
||||
import { RawObject } from "~database/entities/RawObject";
|
||||
import { Status } from "~database/entities/Status";
|
||||
import { User } from "~database/entities/User";
|
||||
import { APIRouteMeta } from "~types/api";
|
||||
|
|
@ -30,10 +29,13 @@ export default async (
|
|||
|
||||
const { user } = await User.getFromRequest(req);
|
||||
|
||||
let foundStatus: RawObject | null;
|
||||
let foundStatus: Status | null;
|
||||
try {
|
||||
foundStatus = await RawObject.findOneBy({
|
||||
id,
|
||||
foundStatus = await Status.findOne({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
relations: ["account", "object"],
|
||||
});
|
||||
} catch (e) {
|
||||
return errorResponse("Invalid ID", 404);
|
||||
|
|
@ -42,38 +44,27 @@ export default async (
|
|||
if (!foundStatus) return errorResponse("Record not found", 404);
|
||||
|
||||
// Check if user is authorized to view this status (if it's private)
|
||||
if (
|
||||
(await foundStatus.toAPI()).visibility === "private" &&
|
||||
(await foundStatus.toAPI()).account.id !== user?.id
|
||||
) {
|
||||
if (!foundStatus.isViewableByUser(user)) {
|
||||
return errorResponse("Record not found", 404);
|
||||
}
|
||||
|
||||
if (req.method === "GET") {
|
||||
return jsonResponse(await foundStatus.toAPI());
|
||||
} else if (req.method === "DELETE") {
|
||||
if ((await foundStatus.toAPI()).account.id !== user?.id) {
|
||||
if (foundStatus.account.id !== user?.id) {
|
||||
return errorResponse("Unauthorized", 401);
|
||||
}
|
||||
|
||||
// TODO: Implement delete and redraft functionality
|
||||
|
||||
// Get associated Status object
|
||||
const status = await Status.createQueryBuilder("status")
|
||||
.leftJoinAndSelect("status.object", "object")
|
||||
.where("object.id = :id", { id: foundStatus.id })
|
||||
.getOne();
|
||||
|
||||
if (!status) {
|
||||
return errorResponse("Status not found", 404);
|
||||
}
|
||||
|
||||
// Delete status and all associated objects
|
||||
await status.object.remove();
|
||||
await foundStatus.remove();
|
||||
|
||||
return jsonResponse(
|
||||
{
|
||||
...(await status.toAPI()),
|
||||
...(await foundStatus.toAPI()),
|
||||
// TODO: Add
|
||||
// text: Add source text
|
||||
// poll: Add source poll
|
||||
|
|
|
|||
|
|
@ -124,19 +124,20 @@ export default async (req: Request): Promise<Response> => {
|
|||
}
|
||||
|
||||
// Get reply account and status if exists
|
||||
let replyObject: RawObject | null = null;
|
||||
let replyStatus: Status | null = null;
|
||||
let replyUser: User | null = null;
|
||||
|
||||
if (in_reply_to_id) {
|
||||
replyObject = await RawObject.findOne({
|
||||
replyStatus = await Status.findOne({
|
||||
where: {
|
||||
id: in_reply_to_id,
|
||||
},
|
||||
relations: {
|
||||
account: true,
|
||||
},
|
||||
});
|
||||
|
||||
replyUser = await User.getByActorId(
|
||||
(replyObject?.data.attributedTo as APActor).id ?? ""
|
||||
);
|
||||
replyUser = replyStatus?.account || null;
|
||||
}
|
||||
|
||||
// Check if status body doesnt match filters
|
||||
|
|
@ -160,10 +161,10 @@ export default async (req: Request): Promise<Response> => {
|
|||
spoiler_text: spoiler_text || "",
|
||||
emojis: [],
|
||||
reply:
|
||||
replyObject && replyUser
|
||||
replyStatus && replyUser
|
||||
? {
|
||||
user: replyUser,
|
||||
object: replyObject,
|
||||
status: replyStatus,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import { applyConfig } from "@api";
|
||||
import { parseRequest } from "@request";
|
||||
import { errorResponse, jsonResponse } from "@response";
|
||||
import { RawObject } from "~database/entities/RawObject";
|
||||
import { FindManyOptions } from "typeorm";
|
||||
import { Status } from "~database/entities/Status";
|
||||
import { User } from "~database/entities/User";
|
||||
import { APIRouteMeta } from "~types/api";
|
||||
|
||||
export const meta: APIRouteMeta = applyConfig({
|
||||
|
|
@ -32,49 +35,83 @@ export default async (req: Request): Promise<Response> => {
|
|||
limit?: number;
|
||||
}>(req);
|
||||
|
||||
const { user } = await User.getFromRequest(req);
|
||||
|
||||
if (limit < 1 || limit > 40) {
|
||||
return errorResponse("Limit must be between 1 and 40", 400);
|
||||
}
|
||||
|
||||
let query = RawObject.createQueryBuilder("object")
|
||||
.where("object.data->>'type' = 'Note'")
|
||||
// From a user followed by the current user
|
||||
.andWhere("CAST(object.data->>'to' AS jsonb) @> CAST(:to AS jsonb)", {
|
||||
to: JSON.stringify([
|
||||
"https://www.w3.org/ns/activitystreams#Public",
|
||||
]),
|
||||
})
|
||||
.orderBy("object.data->>'published'", "DESC")
|
||||
.take(limit);
|
||||
let query: FindManyOptions<Status> = {
|
||||
where: {
|
||||
visibility: "public",
|
||||
account: [
|
||||
{
|
||||
relationships: {
|
||||
id: user?.id,
|
||||
followed_by: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: user?.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
order: {
|
||||
created_at: "DESC",
|
||||
},
|
||||
take: limit,
|
||||
relations: ["object"],
|
||||
};
|
||||
|
||||
if (max_id) {
|
||||
const maxPost = await RawObject.findOneBy({ id: max_id });
|
||||
const maxPost = await Status.findOneBy({ id: max_id });
|
||||
if (maxPost) {
|
||||
query = query.andWhere("object.data->>'published' < :max_date", {
|
||||
max_date: maxPost.data.published,
|
||||
});
|
||||
query = {
|
||||
...query,
|
||||
where: {
|
||||
...query.where,
|
||||
created_at: {
|
||||
...(query.where as any)?.created_at,
|
||||
$lt: maxPost.created_at,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (min_id) {
|
||||
const minPost = await RawObject.findOneBy({ id: min_id });
|
||||
const minPost = await Status.findOneBy({ id: min_id });
|
||||
if (minPost) {
|
||||
query = query.andWhere("object.data->>'published' > :min_date", {
|
||||
min_date: minPost.data.published,
|
||||
});
|
||||
query = {
|
||||
...query,
|
||||
where: {
|
||||
...query.where,
|
||||
created_at: {
|
||||
...(query.where as any)?.created_at,
|
||||
$gt: minPost.created_at,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (since_id) {
|
||||
const sincePost = await RawObject.findOneBy({ id: since_id });
|
||||
const sincePost = await Status.findOneBy({ id: since_id });
|
||||
if (sincePost) {
|
||||
query = query.andWhere("object.data->>'published' >= :since_date", {
|
||||
since_date: sincePost.data.published,
|
||||
});
|
||||
query = {
|
||||
...query,
|
||||
where: {
|
||||
...query.where,
|
||||
created_at: {
|
||||
...(query.where as any)?.created_at,
|
||||
$gte: sincePost.created_at,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const objects = await query.getMany();
|
||||
const objects = await Status.find(query);
|
||||
|
||||
return jsonResponse(
|
||||
await Promise.all(objects.map(async object => await object.toAPI()))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import { applyConfig } from "@api";
|
||||
import { parseRequest } from "@request";
|
||||
import { errorResponse, jsonResponse } from "@response";
|
||||
import { RawObject } from "~database/entities/RawObject";
|
||||
import { FindManyOptions, IsNull, Not } from "typeorm";
|
||||
import { Status } from "~database/entities/Status";
|
||||
import { APIRouteMeta } from "~types/api";
|
||||
|
||||
export const meta: APIRouteMeta = applyConfig({
|
||||
|
|
@ -42,60 +44,94 @@ export default async (req: Request): Promise<Response> => {
|
|||
return errorResponse("Limit must be between 1 and 40", 400);
|
||||
}
|
||||
|
||||
let query = RawObject.createQueryBuilder("object")
|
||||
.where("object.data->>'type' = 'Note'")
|
||||
.andWhere("CAST(object.data->>'to' AS jsonb) @> CAST(:to AS jsonb)", {
|
||||
to: JSON.stringify([
|
||||
"https://www.w3.org/ns/activitystreams#Public",
|
||||
]),
|
||||
})
|
||||
.orderBy("object.data->>'published'", "DESC")
|
||||
.take(limit);
|
||||
if (local && remote) {
|
||||
return errorResponse("Cannot use both local and remote", 400);
|
||||
}
|
||||
|
||||
let query: FindManyOptions<Status> = {
|
||||
where: {
|
||||
visibility: "public",
|
||||
},
|
||||
order: {
|
||||
created_at: "DESC",
|
||||
},
|
||||
take: limit,
|
||||
relations: ["object"],
|
||||
};
|
||||
|
||||
if (max_id) {
|
||||
const maxPost = await RawObject.findOneBy({ id: max_id });
|
||||
const maxPost = await Status.findOneBy({ id: max_id });
|
||||
if (maxPost) {
|
||||
query = query.andWhere("object.data->>'published' < :max_date", {
|
||||
max_date: maxPost.data.published,
|
||||
});
|
||||
query = {
|
||||
...query,
|
||||
where: {
|
||||
...query.where,
|
||||
created_at: {
|
||||
...(query.where as any)?.created_at,
|
||||
$lt: maxPost.created_at,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (min_id) {
|
||||
const minPost = await RawObject.findOneBy({ id: min_id });
|
||||
const minPost = await Status.findOneBy({ id: min_id });
|
||||
if (minPost) {
|
||||
query = query.andWhere("object.data->>'published' > :min_date", {
|
||||
min_date: minPost.data.published,
|
||||
});
|
||||
query = {
|
||||
...query,
|
||||
where: {
|
||||
...query.where,
|
||||
created_at: {
|
||||
...(query.where as any)?.created_at,
|
||||
$gt: minPost.created_at,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (since_id) {
|
||||
const sincePost = await RawObject.findOneBy({ id: since_id });
|
||||
const sincePost = await Status.findOneBy({ id: since_id });
|
||||
if (sincePost) {
|
||||
query = query.andWhere("object.data->>'published' >= :since_date", {
|
||||
since_date: sincePost.data.published,
|
||||
});
|
||||
query = {
|
||||
...query,
|
||||
where: {
|
||||
...query.where,
|
||||
created_at: {
|
||||
...(query.where as any)?.created_at,
|
||||
$gte: sincePost.created_at,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (only_media) {
|
||||
query = query.andWhere("object.data->'attachment' IS NOT NULL");
|
||||
// TODO: add
|
||||
}
|
||||
|
||||
if (local) {
|
||||
query = query.andWhere("object.data->>'actor' LIKE :actor", {
|
||||
actor: `%${new URL(req.url).hostname}%`,
|
||||
});
|
||||
query = {
|
||||
...query,
|
||||
where: {
|
||||
...query.where,
|
||||
instance: IsNull(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (remote) {
|
||||
query = query.andWhere("object.data->>'actor' NOT LIKE :actor", {
|
||||
actor: `%${new URL(req.url).hostname}%`,
|
||||
});
|
||||
query = {
|
||||
...query,
|
||||
where: {
|
||||
...query.where,
|
||||
instance: Not(IsNull()),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const objects = await query.getMany();
|
||||
const objects = await Status.find(query);
|
||||
|
||||
return jsonResponse(
|
||||
await Promise.all(objects.map(async object => await object.toAPI()))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue