mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
Clean up timeline code, add new Context API endpoint
This commit is contained in:
parent
ace9f97275
commit
932fc3e4f5
|
|
@ -17,6 +17,7 @@ import { Application } from "./Application";
|
||||||
import { Emoji } from "./Emoji";
|
import { Emoji } from "./Emoji";
|
||||||
import { RawActivity } from "./RawActivity";
|
import { RawActivity } from "./RawActivity";
|
||||||
import { RawObject } from "./RawObject";
|
import { RawObject } from "./RawObject";
|
||||||
|
import { Instance } from "./Instance";
|
||||||
|
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
|
|
||||||
|
|
@ -91,10 +92,18 @@ export class Status extends BaseEntity {
|
||||||
/**
|
/**
|
||||||
* The raw object that this status is a reply to, if any.
|
* The raw object that this status is a reply to, if any.
|
||||||
*/
|
*/
|
||||||
@ManyToOne(() => RawObject, {
|
@ManyToOne(() => Status, {
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
in_reply_to_post!: RawObject | null;
|
in_reply_to_post!: Status | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The status' instance
|
||||||
|
*/
|
||||||
|
@ManyToOne(() => Instance, {
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
instance!: Instance | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The raw actor that this status is a reply to, if any.
|
* The raw actor that this status is a reply to, if any.
|
||||||
|
|
@ -133,6 +142,13 @@ export class Status extends BaseEntity {
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
emojis!: Emoji[];
|
emojis!: Emoji[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The users mentioned (excluding followers and such)
|
||||||
|
*/
|
||||||
|
@ManyToMany(() => User, user => user.id)
|
||||||
|
@JoinTable()
|
||||||
|
mentions!: User[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The activities that have liked this status.
|
* The activities that have liked this status.
|
||||||
*/
|
*/
|
||||||
|
|
@ -180,6 +196,106 @@ export class Status extends BaseEntity {
|
||||||
return emojiObjects.filter(emoji => emoji !== null) as Emoji[];
|
return emojiObjects.filter(emoji => emoji !== null) as Emoji[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this status is viewable by a user.
|
||||||
|
* @param user The user to check.
|
||||||
|
* @returns Whether this status is viewable by the user.
|
||||||
|
*/
|
||||||
|
isViewableByUser(user: User | null) {
|
||||||
|
const relationship = user?.relationships.find(
|
||||||
|
rel => rel.id === this.account.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.visibility === "public") return true;
|
||||||
|
else if (this.visibility === "unlisted") return true;
|
||||||
|
else if (this.visibility === "private") {
|
||||||
|
return !!relationship?.following;
|
||||||
|
} else {
|
||||||
|
return user && this.mentions.includes(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all the ancestors of this post,
|
||||||
|
*/
|
||||||
|
async getAncestors(fetcher: User | null) {
|
||||||
|
const max = fetcher ? 4096 : 40;
|
||||||
|
const ancestors = [];
|
||||||
|
|
||||||
|
let id = this.in_reply_to_post?.id;
|
||||||
|
|
||||||
|
while (ancestors.length < max && id) {
|
||||||
|
const currentStatus = await Status.findOne({
|
||||||
|
where: {
|
||||||
|
id: id,
|
||||||
|
},
|
||||||
|
relations: {
|
||||||
|
in_reply_to_post: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentStatus) {
|
||||||
|
if (currentStatus.isViewableByUser(fetcher)) {
|
||||||
|
ancestors.push(currentStatus);
|
||||||
|
}
|
||||||
|
id = currentStatus.in_reply_to_post?.id;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ancestors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all the descendants of this post,
|
||||||
|
*/
|
||||||
|
async getDescendants(fetcher: User | null) {
|
||||||
|
const max = fetcher ? 4096 : 60;
|
||||||
|
// Go through all descendants in a tree-like manner
|
||||||
|
const descendants: Status[] = [];
|
||||||
|
|
||||||
|
return await Status._getDescendants(this, fetcher, max, descendants);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all the descendants of a post,
|
||||||
|
* @param status The status to get the descendants of.
|
||||||
|
* @param isAuthenticated Whether the user is authenticated.
|
||||||
|
* @param max The maximum number of descendants to get.
|
||||||
|
* @param descendants The descendants to add to.
|
||||||
|
* @returns A promise that resolves with the descendants.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private static async _getDescendants(
|
||||||
|
status: Status,
|
||||||
|
fetcher: User | null,
|
||||||
|
max: number,
|
||||||
|
descendants: Status[]
|
||||||
|
) {
|
||||||
|
const currentStatus = await Status.find({
|
||||||
|
where: {
|
||||||
|
in_reply_to_post: {
|
||||||
|
id: status.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
relations: {
|
||||||
|
in_reply_to_post: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const status of currentStatus) {
|
||||||
|
if (status.isViewableByUser(fetcher)) {
|
||||||
|
descendants.push(status);
|
||||||
|
}
|
||||||
|
if (descendants.length < max) {
|
||||||
|
await this._getDescendants(status, fetcher, max, descendants);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return descendants;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new status and saves it to the database.
|
* Creates a new status and saves it to the database.
|
||||||
* @param data The data for the new status.
|
* @param data The data for the new status.
|
||||||
|
|
@ -194,7 +310,7 @@ export class Status extends BaseEntity {
|
||||||
spoiler_text: string;
|
spoiler_text: string;
|
||||||
emojis: Emoji[];
|
emojis: Emoji[];
|
||||||
reply?: {
|
reply?: {
|
||||||
object: RawObject;
|
status: Status;
|
||||||
user: User;
|
user: User;
|
||||||
};
|
};
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -211,11 +327,13 @@ export class Status extends BaseEntity {
|
||||||
newStatus.announces = [];
|
newStatus.announces = [];
|
||||||
newStatus.isReblog = false;
|
newStatus.isReblog = false;
|
||||||
newStatus.announces = [];
|
newStatus.announces = [];
|
||||||
|
newStatus.mentions = [];
|
||||||
|
newStatus.instance = data.account.instance;
|
||||||
|
|
||||||
newStatus.object = new RawObject();
|
newStatus.object = new RawObject();
|
||||||
|
|
||||||
if (data.reply) {
|
if (data.reply) {
|
||||||
newStatus.in_reply_to_post = data.reply.object;
|
newStatus.in_reply_to_post = data.reply.status;
|
||||||
newStatus.in_reply_to_account = data.reply.user;
|
newStatus.in_reply_to_account = data.reply.user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,8 +342,8 @@ export class Status extends BaseEntity {
|
||||||
type: "Note",
|
type: "Note",
|
||||||
summary: data.spoiler_text,
|
summary: data.spoiler_text,
|
||||||
content: data.content,
|
content: data.content,
|
||||||
inReplyTo: data.reply?.object
|
inReplyTo: data.reply?.status
|
||||||
? data.reply.object.data.id
|
? data.reply.status.object.data.id
|
||||||
: undefined,
|
: undefined,
|
||||||
published: new Date().toISOString(),
|
published: new Date().toISOString(),
|
||||||
tag: [],
|
tag: [],
|
||||||
|
|
@ -265,6 +383,8 @@ export class Status extends BaseEntity {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
newStatus.mentions.push(user as User);
|
||||||
|
|
||||||
return user?.actor.data.id;
|
return user?.actor.data.id;
|
||||||
} else {
|
} else {
|
||||||
const user = await User.findOne({
|
const user = await User.findOne({
|
||||||
|
|
@ -276,6 +396,8 @@ export class Status extends BaseEntity {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
newStatus.mentions.push(user as User);
|
||||||
|
|
||||||
return user?.actor.data.id;
|
return user?.actor.data.id;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -318,6 +440,9 @@ export class Status extends BaseEntity {
|
||||||
* @returns A promise that resolves with the API status.
|
* @returns A promise that resolves with the API status.
|
||||||
*/
|
*/
|
||||||
async toAPI(): Promise<APIStatus> {
|
async toAPI(): Promise<APIStatus> {
|
||||||
return await this.object.toAPI();
|
return {
|
||||||
|
...(await this.object.toAPI()),
|
||||||
|
id: this.id,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { applyConfig } from "@api";
|
import { applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { MatchedRoute } from "bun";
|
import { MatchedRoute } from "bun";
|
||||||
import { RawObject } from "~database/entities/RawObject";
|
|
||||||
import { Status } from "~database/entities/Status";
|
import { Status } from "~database/entities/Status";
|
||||||
import { User } from "~database/entities/User";
|
import { User } from "~database/entities/User";
|
||||||
import { APIRouteMeta } from "~types/api";
|
import { APIRouteMeta } from "~types/api";
|
||||||
|
|
@ -31,9 +30,9 @@ export default async (
|
||||||
|
|
||||||
const { user } = await User.getFromRequest(req);
|
const { user } = await User.getFromRequest(req);
|
||||||
|
|
||||||
let foundStatus: RawObject | null;
|
let foundStatus: Status | null;
|
||||||
try {
|
try {
|
||||||
foundStatus = await RawObject.findOneBy({
|
foundStatus = await Status.findOneBy({
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -43,7 +42,13 @@ export default async (
|
||||||
if (!foundStatus) return errorResponse("Record not found", 404);
|
if (!foundStatus) return errorResponse("Record not found", 404);
|
||||||
|
|
||||||
// Get all ancestors
|
// 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 { applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { MatchedRoute } from "bun";
|
import { MatchedRoute } from "bun";
|
||||||
import { RawObject } from "~database/entities/RawObject";
|
|
||||||
import { Status } from "~database/entities/Status";
|
import { Status } from "~database/entities/Status";
|
||||||
import { User } from "~database/entities/User";
|
import { User } from "~database/entities/User";
|
||||||
import { APIRouteMeta } from "~types/api";
|
import { APIRouteMeta } from "~types/api";
|
||||||
|
|
@ -30,10 +29,13 @@ export default async (
|
||||||
|
|
||||||
const { user } = await User.getFromRequest(req);
|
const { user } = await User.getFromRequest(req);
|
||||||
|
|
||||||
let foundStatus: RawObject | null;
|
let foundStatus: Status | null;
|
||||||
try {
|
try {
|
||||||
foundStatus = await RawObject.findOneBy({
|
foundStatus = await Status.findOne({
|
||||||
|
where: {
|
||||||
id,
|
id,
|
||||||
|
},
|
||||||
|
relations: ["account", "object"],
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return errorResponse("Invalid ID", 404);
|
return errorResponse("Invalid ID", 404);
|
||||||
|
|
@ -42,38 +44,27 @@ export default async (
|
||||||
if (!foundStatus) return errorResponse("Record not found", 404);
|
if (!foundStatus) return errorResponse("Record not found", 404);
|
||||||
|
|
||||||
// Check if user is authorized to view this status (if it's private)
|
// Check if user is authorized to view this status (if it's private)
|
||||||
if (
|
if (!foundStatus.isViewableByUser(user)) {
|
||||||
(await foundStatus.toAPI()).visibility === "private" &&
|
|
||||||
(await foundStatus.toAPI()).account.id !== user?.id
|
|
||||||
) {
|
|
||||||
return errorResponse("Record not found", 404);
|
return errorResponse("Record not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method === "GET") {
|
if (req.method === "GET") {
|
||||||
return jsonResponse(await foundStatus.toAPI());
|
return jsonResponse(await foundStatus.toAPI());
|
||||||
} else if (req.method === "DELETE") {
|
} else if (req.method === "DELETE") {
|
||||||
if ((await foundStatus.toAPI()).account.id !== user?.id) {
|
if (foundStatus.account.id !== user?.id) {
|
||||||
return errorResponse("Unauthorized", 401);
|
return errorResponse("Unauthorized", 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement delete and redraft functionality
|
// TODO: Implement delete and redraft functionality
|
||||||
|
|
||||||
// Get associated Status object
|
// 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
|
// Delete status and all associated objects
|
||||||
await status.object.remove();
|
await foundStatus.remove();
|
||||||
|
|
||||||
return jsonResponse(
|
return jsonResponse(
|
||||||
{
|
{
|
||||||
...(await status.toAPI()),
|
...(await foundStatus.toAPI()),
|
||||||
// TODO: Add
|
// TODO: Add
|
||||||
// text: Add source text
|
// text: Add source text
|
||||||
// poll: Add source poll
|
// poll: Add source poll
|
||||||
|
|
|
||||||
|
|
@ -124,19 +124,20 @@ export default async (req: Request): Promise<Response> => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get reply account and status if exists
|
// Get reply account and status if exists
|
||||||
let replyObject: RawObject | null = null;
|
let replyStatus: Status | null = null;
|
||||||
let replyUser: User | null = null;
|
let replyUser: User | null = null;
|
||||||
|
|
||||||
if (in_reply_to_id) {
|
if (in_reply_to_id) {
|
||||||
replyObject = await RawObject.findOne({
|
replyStatus = await Status.findOne({
|
||||||
where: {
|
where: {
|
||||||
id: in_reply_to_id,
|
id: in_reply_to_id,
|
||||||
},
|
},
|
||||||
|
relations: {
|
||||||
|
account: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
replyUser = await User.getByActorId(
|
replyUser = replyStatus?.account || null;
|
||||||
(replyObject?.data.attributedTo as APActor).id ?? ""
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if status body doesnt match filters
|
// Check if status body doesnt match filters
|
||||||
|
|
@ -160,10 +161,10 @@ export default async (req: Request): Promise<Response> => {
|
||||||
spoiler_text: spoiler_text || "",
|
spoiler_text: spoiler_text || "",
|
||||||
emojis: [],
|
emojis: [],
|
||||||
reply:
|
reply:
|
||||||
replyObject && replyUser
|
replyStatus && replyUser
|
||||||
? {
|
? {
|
||||||
user: replyUser,
|
user: replyUser,
|
||||||
object: replyObject,
|
status: replyStatus,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
import { applyConfig } from "@api";
|
import { applyConfig } from "@api";
|
||||||
import { parseRequest } from "@request";
|
import { parseRequest } from "@request";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
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";
|
import { APIRouteMeta } from "~types/api";
|
||||||
|
|
||||||
export const meta: APIRouteMeta = applyConfig({
|
export const meta: APIRouteMeta = applyConfig({
|
||||||
|
|
@ -32,49 +35,83 @@ export default async (req: Request): Promise<Response> => {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
}>(req);
|
}>(req);
|
||||||
|
|
||||||
|
const { user } = await User.getFromRequest(req);
|
||||||
|
|
||||||
if (limit < 1 || limit > 40) {
|
if (limit < 1 || limit > 40) {
|
||||||
return errorResponse("Limit must be between 1 and 40", 400);
|
return errorResponse("Limit must be between 1 and 40", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
let query = RawObject.createQueryBuilder("object")
|
let query: FindManyOptions<Status> = {
|
||||||
.where("object.data->>'type' = 'Note'")
|
where: {
|
||||||
// From a user followed by the current user
|
visibility: "public",
|
||||||
.andWhere("CAST(object.data->>'to' AS jsonb) @> CAST(:to AS jsonb)", {
|
account: [
|
||||||
to: JSON.stringify([
|
{
|
||||||
"https://www.w3.org/ns/activitystreams#Public",
|
relationships: {
|
||||||
]),
|
id: user?.id,
|
||||||
})
|
followed_by: true,
|
||||||
.orderBy("object.data->>'published'", "DESC")
|
},
|
||||||
.take(limit);
|
},
|
||||||
|
{
|
||||||
|
id: user?.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
created_at: "DESC",
|
||||||
|
},
|
||||||
|
take: limit,
|
||||||
|
relations: ["object"],
|
||||||
|
};
|
||||||
|
|
||||||
if (max_id) {
|
if (max_id) {
|
||||||
const maxPost = await RawObject.findOneBy({ id: max_id });
|
const maxPost = await Status.findOneBy({ id: max_id });
|
||||||
if (maxPost) {
|
if (maxPost) {
|
||||||
query = query.andWhere("object.data->>'published' < :max_date", {
|
query = {
|
||||||
max_date: maxPost.data.published,
|
...query,
|
||||||
});
|
where: {
|
||||||
|
...query.where,
|
||||||
|
created_at: {
|
||||||
|
...(query.where as any)?.created_at,
|
||||||
|
$lt: maxPost.created_at,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (min_id) {
|
if (min_id) {
|
||||||
const minPost = await RawObject.findOneBy({ id: min_id });
|
const minPost = await Status.findOneBy({ id: min_id });
|
||||||
if (minPost) {
|
if (minPost) {
|
||||||
query = query.andWhere("object.data->>'published' > :min_date", {
|
query = {
|
||||||
min_date: minPost.data.published,
|
...query,
|
||||||
});
|
where: {
|
||||||
|
...query.where,
|
||||||
|
created_at: {
|
||||||
|
...(query.where as any)?.created_at,
|
||||||
|
$gt: minPost.created_at,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (since_id) {
|
if (since_id) {
|
||||||
const sincePost = await RawObject.findOneBy({ id: since_id });
|
const sincePost = await Status.findOneBy({ id: since_id });
|
||||||
if (sincePost) {
|
if (sincePost) {
|
||||||
query = query.andWhere("object.data->>'published' >= :since_date", {
|
query = {
|
||||||
since_date: sincePost.data.published,
|
...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(
|
return jsonResponse(
|
||||||
await Promise.all(objects.map(async object => await object.toAPI()))
|
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 { applyConfig } from "@api";
|
||||||
import { parseRequest } from "@request";
|
import { parseRequest } from "@request";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
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";
|
import { APIRouteMeta } from "~types/api";
|
||||||
|
|
||||||
export const meta: APIRouteMeta = applyConfig({
|
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);
|
return errorResponse("Limit must be between 1 and 40", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
let query = RawObject.createQueryBuilder("object")
|
if (local && remote) {
|
||||||
.where("object.data->>'type' = 'Note'")
|
return errorResponse("Cannot use both local and remote", 400);
|
||||||
.andWhere("CAST(object.data->>'to' AS jsonb) @> CAST(:to AS jsonb)", {
|
}
|
||||||
to: JSON.stringify([
|
|
||||||
"https://www.w3.org/ns/activitystreams#Public",
|
let query: FindManyOptions<Status> = {
|
||||||
]),
|
where: {
|
||||||
})
|
visibility: "public",
|
||||||
.orderBy("object.data->>'published'", "DESC")
|
},
|
||||||
.take(limit);
|
order: {
|
||||||
|
created_at: "DESC",
|
||||||
|
},
|
||||||
|
take: limit,
|
||||||
|
relations: ["object"],
|
||||||
|
};
|
||||||
|
|
||||||
if (max_id) {
|
if (max_id) {
|
||||||
const maxPost = await RawObject.findOneBy({ id: max_id });
|
const maxPost = await Status.findOneBy({ id: max_id });
|
||||||
if (maxPost) {
|
if (maxPost) {
|
||||||
query = query.andWhere("object.data->>'published' < :max_date", {
|
query = {
|
||||||
max_date: maxPost.data.published,
|
...query,
|
||||||
});
|
where: {
|
||||||
|
...query.where,
|
||||||
|
created_at: {
|
||||||
|
...(query.where as any)?.created_at,
|
||||||
|
$lt: maxPost.created_at,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (min_id) {
|
if (min_id) {
|
||||||
const minPost = await RawObject.findOneBy({ id: min_id });
|
const minPost = await Status.findOneBy({ id: min_id });
|
||||||
if (minPost) {
|
if (minPost) {
|
||||||
query = query.andWhere("object.data->>'published' > :min_date", {
|
query = {
|
||||||
min_date: minPost.data.published,
|
...query,
|
||||||
});
|
where: {
|
||||||
|
...query.where,
|
||||||
|
created_at: {
|
||||||
|
...(query.where as any)?.created_at,
|
||||||
|
$gt: minPost.created_at,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (since_id) {
|
if (since_id) {
|
||||||
const sincePost = await RawObject.findOneBy({ id: since_id });
|
const sincePost = await Status.findOneBy({ id: since_id });
|
||||||
if (sincePost) {
|
if (sincePost) {
|
||||||
query = query.andWhere("object.data->>'published' >= :since_date", {
|
query = {
|
||||||
since_date: sincePost.data.published,
|
...query,
|
||||||
});
|
where: {
|
||||||
|
...query.where,
|
||||||
|
created_at: {
|
||||||
|
...(query.where as any)?.created_at,
|
||||||
|
$gte: sincePost.created_at,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (only_media) {
|
if (only_media) {
|
||||||
query = query.andWhere("object.data->'attachment' IS NOT NULL");
|
// TODO: add
|
||||||
}
|
}
|
||||||
|
|
||||||
if (local) {
|
if (local) {
|
||||||
query = query.andWhere("object.data->>'actor' LIKE :actor", {
|
query = {
|
||||||
actor: `%${new URL(req.url).hostname}%`,
|
...query,
|
||||||
});
|
where: {
|
||||||
|
...query.where,
|
||||||
|
instance: IsNull(),
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remote) {
|
if (remote) {
|
||||||
query = query.andWhere("object.data->>'actor' NOT LIKE :actor", {
|
query = {
|
||||||
actor: `%${new URL(req.url).hostname}%`,
|
...query,
|
||||||
});
|
where: {
|
||||||
|
...query.where,
|
||||||
|
instance: Not(IsNull()),
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const objects = await query.getMany();
|
const objects = await Status.find(query);
|
||||||
|
|
||||||
return jsonResponse(
|
return jsonResponse(
|
||||||
await Promise.all(objects.map(async object => await object.toAPI()))
|
await Promise.all(objects.map(async object => await object.toAPI()))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue