This commit is contained in:
Puyodead1 2023-03-23 11:56:17 -04:00
parent 3335f16ad1
commit 4a7811a25c
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
21 changed files with 128384 additions and 1282 deletions

File diff suppressed because it is too large Load Diff

View File

@ -16,18 +16,18 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { route } from "@spacebar/api";
import { import {
Channel, Channel,
ChannelDeleteEvent, ChannelDeleteEvent,
ChannelModifySchema,
ChannelType, ChannelType,
ChannelUpdateEvent, ChannelUpdateEvent,
emitEvent,
Recipient, Recipient,
emitEvent,
handleFile, handleFile,
ChannelModifySchema,
} from "@spacebar/util"; } from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
const router: Router = Router(); const router: Router = Router();
// TODO: delete channel // TODO: delete channel
@ -35,7 +35,15 @@ const router: Router = Router();
router.get( router.get(
"/", "/",
route({ permission: "VIEW_CHANNEL" }), route({
permission: "VIEW_CHANNEL",
responses: {
200: {
body: "Channel",
},
404: {},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id } = req.params; const { channel_id } = req.params;
@ -49,7 +57,15 @@ router.get(
router.delete( router.delete(
"/", "/",
route({ permission: "MANAGE_CHANNELS" }), route({
permission: "MANAGE_CHANNELS",
responses: {
200: {
body: "Channel",
},
404: {},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id } = req.params; const { channel_id } = req.params;
@ -90,7 +106,19 @@ router.delete(
router.patch( router.patch(
"/", "/",
route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), route({
body: "ChannelModifySchema",
permission: "MANAGE_CHANNELS",
responses: {
200: {
body: "Channel",
},
404: {},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const payload = req.body as ChannelModifySchema; const payload = req.body as ChannelModifySchema;
const { channel_id } = req.params; const { channel_id } = req.params;

View File

@ -16,19 +16,18 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Router, Request, Response } from "express"; import { random, route } from "@spacebar/api";
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
import { random } from "@spacebar/api";
import { import {
Channel, Channel,
Guild,
Invite, Invite,
InviteCreateEvent, InviteCreateEvent,
emitEvent,
User,
Guild,
PublicInviteRelation, PublicInviteRelation,
User,
emitEvent,
} from "@spacebar/util"; } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { isTextChannel } from "./messages"; import { isTextChannel } from "./messages";
const router: Router = Router(); const router: Router = Router();
@ -39,6 +38,15 @@ router.post(
body: "InviteCreateSchema", body: "InviteCreateSchema",
permission: "CREATE_INSTANT_INVITE", permission: "CREATE_INSTANT_INVITE",
right: "CREATE_INVITES", right: "CREATE_INVITES",
responses: {
201: {
body: "Invite",
},
404: {},
400: {
body: "APIErrorResponse",
},
},
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { user_id } = req; const { user_id } = req;
@ -84,7 +92,15 @@ router.post(
router.get( router.get(
"/", "/",
route({ permission: "MANAGE_CHANNELS" }), route({
permission: "MANAGE_CHANNELS",
responses: {
200: {
body: "ChannelInvitesResponse",
},
404: {},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id } = req.params; const { channel_id } = req.params;
const channel = await Channel.findOneOrFail({ const channel = await Channel.findOneOrFail({

View File

@ -16,6 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { route } from "@spacebar/api";
import { import {
emitEvent, emitEvent,
getPermission, getPermission,
@ -23,7 +24,6 @@ import {
ReadState, ReadState,
} from "@spacebar/util"; } from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
const router = Router(); const router = Router();
@ -33,7 +33,13 @@ const router = Router();
router.post( router.post(
"/", "/",
route({ body: "MessageAcknowledgeSchema" }), route({
body: "MessageAcknowledgeSchema",
responses: {
200: {},
403: {},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params; const { channel_id, message_id } = req.params;

View File

@ -16,14 +16,21 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router(); const router = Router();
router.post( router.post(
"/", "/",
route({ permission: "MANAGE_MESSAGES" }), route({
permission: "MANAGE_MESSAGES",
responses: {
200: {
body: "Message",
},
},
}),
(req: Request, res: Response) => { (req: Request, res: Response) => {
// TODO: // TODO:
res.json({ res.json({

View File

@ -19,24 +19,23 @@
import { import {
Attachment, Attachment,
Channel, Channel,
emitEvent,
SpacebarApiErrors,
getPermission,
getRights,
Message, Message,
MessageCreateEvent, MessageCreateEvent,
MessageCreateSchema,
MessageDeleteEvent, MessageDeleteEvent,
MessageEditSchema,
MessageUpdateEvent, MessageUpdateEvent,
Snowflake, Snowflake,
SpacebarApiErrors,
emitEvent,
getPermission,
getRights,
uploadFile, uploadFile,
MessageCreateSchema,
MessageEditSchema,
} from "@spacebar/util"; } from "@spacebar/util";
import { Router, Response, Request } from "express"; import { Request, Response, Router } from "express";
import multer from "multer";
import { route } from "@spacebar/api";
import { handleMessage, postHandleMessage } from "@spacebar/api";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import multer from "multer";
import { handleMessage, postHandleMessage, route } from "../../../../../util";
const router = Router(); const router = Router();
// TODO: message content/embed string length limit // TODO: message content/embed string length limit
@ -56,6 +55,16 @@ router.patch(
body: "MessageEditSchema", body: "MessageEditSchema",
permission: "SEND_MESSAGES", permission: "SEND_MESSAGES",
right: "SEND_MESSAGES", right: "SEND_MESSAGES",
responses: {
200: {
body: "Message",
},
400: {
body: "APIErrorResponse",
},
403: {},
404: {},
},
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params; const { message_id, channel_id } = req.params;
@ -146,6 +155,16 @@ router.put(
body: "MessageCreateSchema", body: "MessageCreateSchema",
permission: "SEND_MESSAGES", permission: "SEND_MESSAGES",
right: "SEND_BACKDATED_EVENTS", right: "SEND_BACKDATED_EVENTS",
responses: {
200: {
body: "Message",
},
400: {
body: "APIErrorResponse",
},
403: {},
404: {},
},
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params; const { channel_id, message_id } = req.params;
@ -230,7 +249,19 @@ router.put(
router.get( router.get(
"/", "/",
route({ permission: "VIEW_CHANNEL" }), route({
permission: "VIEW_CHANNEL",
responses: {
200: {
body: "Message",
},
400: {
body: "APIErrorResponse",
},
403: {},
404: {},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params; const { message_id, channel_id } = req.params;
@ -252,38 +283,54 @@ router.get(
}, },
); );
router.delete("/", route({}), async (req: Request, res: Response) => { router.delete(
const { message_id, channel_id } = req.params; "/",
route({
const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); responses: {
const message = await Message.findOneOrFail({ where: { id: message_id } }); 204: {},
400: {
const rights = await getRights(req.user_id); body: "APIErrorResponse",
},
if (message.author_id !== req.user_id) { 404: {},
if (!rights.has("MANAGE_MESSAGES")) {
const permission = await getPermission(
req.user_id,
channel.guild_id,
channel_id,
);
permission.hasThrow("MANAGE_MESSAGES");
}
} else rights.hasThrow("SELF_DELETE_MESSAGES");
await Message.delete({ id: message_id });
await emitEvent({
event: "MESSAGE_DELETE",
channel_id,
data: {
id: message_id,
channel_id,
guild_id: channel.guild_id,
}, },
} as MessageDeleteEvent); }),
async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params;
res.sendStatus(204); const channel = await Channel.findOneOrFail({
}); where: { id: channel_id },
});
const message = await Message.findOneOrFail({
where: { id: message_id },
});
const rights = await getRights(req.user_id);
if (message.author_id !== req.user_id) {
if (!rights.has("MANAGE_MESSAGES")) {
const permission = await getPermission(
req.user_id,
channel.guild_id,
channel_id,
);
permission.hasThrow("MANAGE_MESSAGES");
}
} else rights.hasThrow("SELF_DELETE_MESSAGES");
await Message.delete({ id: message_id });
await emitEvent({
event: "MESSAGE_DELETE",
channel_id,
data: {
id: message_id,
channel_id,
guild_id: channel.guild_id,
},
} as MessageDeleteEvent);
res.sendStatus(204);
},
);
export default router; export default router;

View File

@ -16,6 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { route } from "@spacebar/api";
import { import {
Channel, Channel,
emitEvent, emitEvent,
@ -32,8 +33,7 @@ import {
PublicUserProjection, PublicUserProjection,
User, User,
} from "@spacebar/util"; } from "@spacebar/util";
import { route } from "@spacebar/api"; import { Request, Response, Router } from "express";
import { Router, Response, Request } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { In } from "typeorm"; import { In } from "typeorm";
@ -57,7 +57,17 @@ function getEmoji(emoji: string): PartialEmoji {
router.delete( router.delete(
"/", "/",
route({ permission: "MANAGE_MESSAGES" }), route({
permission: "MANAGE_MESSAGES",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {},
403: {},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params; const { message_id, channel_id } = req.params;
@ -83,7 +93,17 @@ router.delete(
router.delete( router.delete(
"/:emoji", "/:emoji",
route({ permission: "MANAGE_MESSAGES" }), route({
permission: "MANAGE_MESSAGES",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {},
403: {},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params; const { message_id, channel_id } = req.params;
const emoji = getEmoji(req.params.emoji); const emoji = getEmoji(req.params.emoji);
@ -120,7 +140,19 @@ router.delete(
router.get( router.get(
"/:emoji", "/:emoji",
route({ permission: "VIEW_CHANNEL" }), route({
permission: "VIEW_CHANNEL",
responses: {
200: {
body: "UserPublic",
},
400: {
body: "APIErrorResponse",
},
404: {},
403: {},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params; const { message_id, channel_id } = req.params;
const emoji = getEmoji(req.params.emoji); const emoji = getEmoji(req.params.emoji);
@ -148,7 +180,18 @@ router.get(
router.put( router.put(
"/:emoji/:user_id", "/:emoji/:user_id",
route({ permission: "READ_MESSAGE_HISTORY", right: "SELF_ADD_REACTIONS" }), route({
permission: "READ_MESSAGE_HISTORY",
right: "SELF_ADD_REACTIONS",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {},
403: {},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { message_id, channel_id, user_id } = req.params; const { message_id, channel_id, user_id } = req.params;
if (user_id !== "@me") throw new HTTPError("Invalid user"); if (user_id !== "@me") throw new HTTPError("Invalid user");
@ -219,7 +262,16 @@ router.put(
router.delete( router.delete(
"/:emoji/:user_id", "/:emoji/:user_id",
route({}), route({
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {},
403: {},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
let { user_id } = req.params; let { user_id } = req.params;
const { message_id, channel_id } = req.params; const { message_id, channel_id } = req.params;

View File

@ -16,18 +16,18 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Router, Response, Request } from "express"; import { route } from "@spacebar/api";
import { import {
Channel, Channel,
Config, Config,
emitEvent, emitEvent,
getPermission, getPermission,
getRights, getRights,
MessageDeleteBulkEvent,
Message, Message,
MessageDeleteBulkEvent,
} from "@spacebar/util"; } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
const router: Router = Router(); const router: Router = Router();
@ -38,7 +38,17 @@ export default router;
// https://discord.com/developers/docs/resources/channel#bulk-delete-messages // https://discord.com/developers/docs/resources/channel#bulk-delete-messages
router.post( router.post(
"/", "/",
route({ body: "BulkDeleteSchema" }), route({
body: "BulkDeleteSchema",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
403: {},
404: {},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id } = req.params; const { channel_id } = req.params;
const channel = await Channel.findOneOrFail({ const channel = await Channel.findOneOrFail({

View File

@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Router, Response, Request } from "express"; import { handleMessage, postHandleMessage, route } from "@spacebar/api";
import { import {
Attachment, Attachment,
Channel, Channel,
@ -26,19 +26,19 @@ import {
emitEvent, emitEvent,
FieldErrors, FieldErrors,
getPermission, getPermission,
Member,
Message, Message,
MessageCreateEvent, MessageCreateEvent,
Snowflake,
uploadFile,
Member,
MessageCreateSchema, MessageCreateSchema,
Reaction,
ReadState, ReadState,
Rights, Rights,
Reaction, Snowflake,
uploadFile,
User, User,
} from "@spacebar/util"; } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { handleMessage, postHandleMessage, route } from "@spacebar/api";
import multer from "multer"; import multer from "multer";
import { FindManyOptions, FindOperator, LessThan, MoreThan } from "typeorm"; import { FindManyOptions, FindOperator, LessThan, MoreThan } from "typeorm";
import { URL } from "url"; import { URL } from "url";
@ -73,108 +73,123 @@ export function isTextChannel(type: ChannelType): boolean {
// https://discord.com/developers/docs/resources/channel#create-message // https://discord.com/developers/docs/resources/channel#create-message
// get messages // get messages
router.get("/", route({}), async (req: Request, res: Response) => { router.get(
const channel_id = req.params.channel_id; "/",
const channel = await Channel.findOneOrFail({ route({
where: { id: channel_id }, responses: {
}); 200: {
if (!channel) throw new HTTPError("Channel not found", 404); body: "ChannelMessagesResponse",
},
400: {
body: "APIErrorResponse",
},
403: {},
404: {},
},
}),
async (req: Request, res: Response) => {
const channel_id = req.params.channel_id;
const channel = await Channel.findOneOrFail({
where: { id: channel_id },
});
if (!channel) throw new HTTPError("Channel not found", 404);
isTextChannel(channel.type); isTextChannel(channel.type);
const around = req.query.around ? `${req.query.around}` : undefined; const around = req.query.around ? `${req.query.around}` : undefined;
const before = req.query.before ? `${req.query.before}` : undefined; const before = req.query.before ? `${req.query.before}` : undefined;
const after = req.query.after ? `${req.query.after}` : undefined; const after = req.query.after ? `${req.query.after}` : undefined;
const limit = Number(req.query.limit) || 50; const limit = Number(req.query.limit) || 50;
if (limit < 1 || limit > 100) if (limit < 1 || limit > 100)
throw new HTTPError("limit must be between 1 and 100", 422); throw new HTTPError("limit must be between 1 and 100", 422);
const halfLimit = Math.floor(limit / 2); const halfLimit = Math.floor(limit / 2);
const permissions = await getPermission( const permissions = await getPermission(
req.user_id, req.user_id,
channel.guild_id, channel.guild_id,
channel_id, channel_id,
); );
permissions.hasThrow("VIEW_CHANNEL"); permissions.hasThrow("VIEW_CHANNEL");
if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
const query: FindManyOptions<Message> & { const query: FindManyOptions<Message> & {
where: { id?: FindOperator<string> | FindOperator<string>[] }; where: { id?: FindOperator<string> | FindOperator<string>[] };
} = { } = {
order: { timestamp: "DESC" }, order: { timestamp: "DESC" },
take: limit, take: limit,
where: { channel_id }, where: { channel_id },
relations: [ relations: [
"author", "author",
"webhook", "webhook",
"application", "application",
"mentions", "mentions",
"mention_roles", "mention_roles",
"mention_channels", "mention_channels",
"sticker_items", "sticker_items",
"attachments", "attachments",
], ],
}; };
if (after) { if (after) {
if (BigInt(after) > BigInt(Snowflake.generate())) if (BigInt(after) > BigInt(Snowflake.generate()))
return res.status(422); return res.status(422);
query.where.id = MoreThan(after); query.where.id = MoreThan(after);
} else if (before) { } else if (before) {
if (BigInt(before) < BigInt(req.params.channel_id)) if (BigInt(before) < BigInt(req.params.channel_id))
return res.status(422); return res.status(422);
query.where.id = LessThan(before); query.where.id = LessThan(before);
} else if (around) { } else if (around) {
query.where.id = [ query.where.id = [
MoreThan((BigInt(around) - BigInt(halfLimit)).toString()), MoreThan((BigInt(around) - BigInt(halfLimit)).toString()),
LessThan((BigInt(around) + BigInt(halfLimit)).toString()), LessThan((BigInt(around) + BigInt(halfLimit)).toString()),
]; ];
return res.json([]); // TODO: fix around return res.json([]); // TODO: fix around
} }
const messages = await Message.find(query); const messages = await Message.find(query);
const endpoint = Config.get().cdn.endpointPublic; const endpoint = Config.get().cdn.endpointPublic;
return res.json( return res.json(
messages.map((x: Partial<Message>) => { messages.map((x: Partial<Message>) => {
(x.reactions || []).forEach((y: Partial<Reaction>) => { (x.reactions || []).forEach((y: Partial<Reaction>) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore //@ts-ignore
if ((y.user_ids || []).includes(req.user_id)) y.me = true; if ((y.user_ids || []).includes(req.user_id)) y.me = true;
delete y.user_ids; delete y.user_ids;
}); });
if (!x.author) if (!x.author)
x.author = User.create({ x.author = User.create({
id: "4", id: "4",
discriminator: "0000", discriminator: "0000",
username: "Spacebar Ghost", username: "Fosscord Ghost",
public_flags: 0, public_flags: 0,
});
x.attachments?.forEach((y: Attachment) => {
// dynamically set attachment proxy_url in case the endpoint changed
const uri = y.proxy_url.startsWith("http")
? y.proxy_url
: `https://example.org${y.proxy_url}`;
y.proxy_url = `${endpoint == null ? "" : endpoint}${
new URL(uri).pathname
}`;
}); });
x.attachments?.forEach((y: Attachment) => {
// dynamically set attachment proxy_url in case the endpoint changed
const uri = y.proxy_url.startsWith("http")
? y.proxy_url
: `https://example.org${y.proxy_url}`;
y.proxy_url = `${endpoint == null ? "" : endpoint}${
new URL(uri).pathname
}`;
});
/** /**
Some clients ( discord.js ) only check if a property exists within the response, Some clients ( discord.js ) only check if a property exists within the response,
which causes errors when, say, the `application` property is `null`. which causes errors when, say, the `application` property is `null`.
**/ **/
// for (var curr in x) { // for (var curr in x) {
// if (x[curr] === null) // if (x[curr] === null)
// delete x[curr]; // delete x[curr];
// } // }
return x; return x;
}), }),
); );
}); },
);
// TODO: config max upload size // TODO: config max upload size
const messageUpload = multer({ const messageUpload = multer({
@ -208,6 +223,16 @@ router.post(
body: "MessageCreateSchema", body: "MessageCreateSchema",
permission: "SEND_MESSAGES", permission: "SEND_MESSAGES",
right: "SEND_MESSAGES", right: "SEND_MESSAGES",
responses: {
200: {
body: "Message",
},
400: {
body: "APIErrorResponse",
},
403: {},
404: {},
},
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id } = req.params; const { channel_id } = req.params;

View File

@ -19,13 +19,13 @@
import { import {
Channel, Channel,
ChannelPermissionOverwrite, ChannelPermissionOverwrite,
ChannelPermissionOverwriteSchema,
ChannelUpdateEvent, ChannelUpdateEvent,
emitEvent, emitEvent,
Member, Member,
Role, Role,
ChannelPermissionOverwriteSchema,
} from "@spacebar/util"; } from "@spacebar/util";
import { Router, Response, Request } from "express"; import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
@ -38,6 +38,12 @@ router.put(
route({ route({
body: "ChannelPermissionOverwriteSchema", body: "ChannelPermissionOverwriteSchema",
permission: "MANAGE_ROLES", permission: "MANAGE_ROLES",
responses: {
204: {},
404: {},
501: {},
400: { body: "APIErrorResponse" },
},
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id, overwrite_id } = req.params; const { channel_id, overwrite_id } = req.params;
@ -92,7 +98,7 @@ router.put(
// TODO: check permission hierarchy // TODO: check permission hierarchy
router.delete( router.delete(
"/:overwrite_id", "/:overwrite_id",
route({ permission: "MANAGE_ROLES" }), route({ permission: "MANAGE_ROLES", responses: { 204: {}, 404: {} } }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id, overwrite_id } = req.params; const { channel_id, overwrite_id } = req.params;

View File

@ -16,23 +16,33 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { route } from "@spacebar/api";
import { import {
Channel, Channel,
ChannelPinsUpdateEvent, ChannelPinsUpdateEvent,
Config, Config,
DiscordApiErrors,
emitEvent, emitEvent,
Message, Message,
MessageUpdateEvent, MessageUpdateEvent,
DiscordApiErrors,
} from "@spacebar/util"; } from "@spacebar/util";
import { Router, Request, Response } from "express"; import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
const router: Router = Router(); const router: Router = Router();
router.put( router.put(
"/:message_id", "/:message_id",
route({ permission: "VIEW_CHANNEL" }), route({
permission: "VIEW_CHANNEL",
responses: {
204: {},
403: {},
404: {},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params; const { channel_id, message_id } = req.params;
@ -74,7 +84,17 @@ router.put(
router.delete( router.delete(
"/:message_id", "/:message_id",
route({ permission: "VIEW_CHANNEL" }), route({
permission: "VIEW_CHANNEL",
responses: {
204: {},
403: {},
404: {},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params; const { channel_id, message_id } = req.params;
@ -114,7 +134,17 @@ router.delete(
router.get( router.get(
"/", "/",
route({ permission: ["READ_MESSAGE_HISTORY"] }), route({
permission: ["READ_MESSAGE_HISTORY"],
responses: {
200: {
body: "ChannelPinsResponse",
},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id } = req.params; const { channel_id } = req.params;

View File

@ -16,20 +16,20 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { isTextChannel } from "./messages";
import { FindManyOptions, Between, Not, FindOperator } from "typeorm";
import { import {
Channel, Channel,
emitEvent,
getPermission,
getRights,
Message, Message,
MessageDeleteBulkEvent, MessageDeleteBulkEvent,
PurgeSchema, PurgeSchema,
emitEvent,
getPermission,
getRights,
} from "@spacebar/util"; } from "@spacebar/util";
import { Router, Response, Request } from "express"; import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { Between, FindManyOptions, FindOperator, Not } from "typeorm";
import { isTextChannel } from "./messages";
const router: Router = Router(); const router: Router = Router();
@ -42,6 +42,14 @@ router.post(
"/", "/",
route({ route({
/*body: "PurgeSchema",*/ /*body: "PurgeSchema",*/
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {},
403: {},
},
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id } = req.params; const { channel_id } = req.params;

View File

@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Request, Response, Router } from "express"; import { route } from "@spacebar/api";
import { import {
Channel, Channel,
ChannelRecipientAddEvent, ChannelRecipientAddEvent,
@ -28,80 +28,98 @@ import {
Recipient, Recipient,
User, User,
} from "@spacebar/util"; } from "@spacebar/util";
import { route } from "@spacebar/api"; import { Request, Response, Router } from "express";
const router: Router = Router(); const router: Router = Router();
router.put("/:user_id", route({}), async (req: Request, res: Response) => { router.put(
const { channel_id, user_id } = req.params; "/:user_id",
const channel = await Channel.findOneOrFail({ route({
where: { id: channel_id }, responses: {
relations: ["recipients"], 201: {},
}); 404: {},
},
}),
async (req: Request, res: Response) => {
const { channel_id, user_id } = req.params;
const channel = await Channel.findOneOrFail({
where: { id: channel_id },
relations: ["recipients"],
});
if (channel.type !== ChannelType.GROUP_DM) { if (channel.type !== ChannelType.GROUP_DM) {
const recipients = [ const recipients = [
...(channel.recipients?.map((r) => r.user_id) || []), ...(channel.recipients?.map((r) => r.user_id) || []),
user_id, user_id,
].unique(); ].unique();
const new_channel = await Channel.createDMChannel( const new_channel = await Channel.createDMChannel(
recipients, recipients,
req.user_id, req.user_id,
); );
return res.status(201).json(new_channel); return res.status(201).json(new_channel);
} else { } else {
if (channel.recipients?.map((r) => r.user_id).includes(user_id)) { if (channel.recipients?.map((r) => r.user_id).includes(user_id)) {
throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
}
channel.recipients?.push(
Recipient.create({ channel_id: channel_id, user_id: user_id }),
);
await channel.save();
await emitEvent({
event: "CHANNEL_CREATE",
data: await DmChannelDTO.from(channel, [user_id]),
user_id: user_id,
});
await emitEvent({
event: "CHANNEL_RECIPIENT_ADD",
data: {
channel_id: channel_id,
user: await User.findOneOrFail({
where: { id: user_id },
select: PublicUserProjection,
}),
},
channel_id: channel_id,
} as ChannelRecipientAddEvent);
return res.sendStatus(204);
}
},
);
router.delete(
"/:user_id",
route({
responses: {
204: {},
404: {},
},
}),
async (req: Request, res: Response) => {
const { channel_id, user_id } = req.params;
const channel = await Channel.findOneOrFail({
where: { id: channel_id },
relations: ["recipients"],
});
if (
!(
channel.type === ChannelType.GROUP_DM &&
(channel.owner_id === req.user_id || user_id === req.user_id)
)
)
throw DiscordApiErrors.MISSING_PERMISSIONS;
if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) {
throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
} }
channel.recipients?.push( await Channel.removeRecipientFromChannel(channel, user_id);
Recipient.create({ channel_id: channel_id, user_id: user_id }),
);
await channel.save();
await emitEvent({
event: "CHANNEL_CREATE",
data: await DmChannelDTO.from(channel, [user_id]),
user_id: user_id,
});
await emitEvent({
event: "CHANNEL_RECIPIENT_ADD",
data: {
channel_id: channel_id,
user: await User.findOneOrFail({
where: { id: user_id },
select: PublicUserProjection,
}),
},
channel_id: channel_id,
} as ChannelRecipientAddEvent);
return res.sendStatus(204); return res.sendStatus(204);
} },
}); );
router.delete("/:user_id", route({}), async (req: Request, res: Response) => {
const { channel_id, user_id } = req.params;
const channel = await Channel.findOneOrFail({
where: { id: channel_id },
relations: ["recipients"],
});
if (
!(
channel.type === ChannelType.GROUP_DM &&
(channel.owner_id === req.user_id || user_id === req.user_id)
)
)
throw DiscordApiErrors.MISSING_PERMISSIONS;
if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) {
throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
}
await Channel.removeRecipientFromChannel(channel, user_id);
return res.sendStatus(204);
});
export default router; export default router;

View File

@ -16,15 +16,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Channel, emitEvent, Member, TypingStartEvent } from "@spacebar/util";
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { Router, Request, Response } from "express"; import { Channel, emitEvent, Member, TypingStartEvent } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router: Router = Router(); const router: Router = Router();
router.post( router.post(
"/", "/",
route({ permission: "SEND_MESSAGES" }), route({
permission: "SEND_MESSAGES",
responses: {
204: {},
404: {},
403: {},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id } = req.params; const { channel_id } = req.params;
const user_id = req.user_id; const user_id = req.user_id;

View File

@ -16,34 +16,56 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { import {
Channel, Channel,
Config, Config,
handleFile, DiscordApiErrors,
trimSpecial,
User, User,
Webhook, Webhook,
WebhookCreateSchema, WebhookCreateSchema,
WebhookType, WebhookType,
handleFile,
trimSpecial,
} from "@spacebar/util"; } from "@spacebar/util";
import crypto from "crypto";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { isTextChannel } from "./messages/index"; import { isTextChannel } from "./messages/index";
import { DiscordApiErrors } from "@spacebar/util";
import crypto from "crypto";
const router: Router = Router(); const router: Router = Router();
//TODO: implement webhooks //TODO: implement webhooks
router.get("/", route({}), async (req: Request, res: Response) => { router.get(
res.json([]); "/",
}); route({
responses: {
200: {
body: "ChannelWebhooksResponse",
},
},
}),
async (req: Request, res: Response) => {
res.json([]);
},
);
// TODO: use Image Data Type for avatar instead of String // TODO: use Image Data Type for avatar instead of String
router.post( router.post(
"/", "/",
route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }), route({
body: "WebhookCreateSchema",
permission: "MANAGE_WEBHOOKS",
responses: {
200: {
body: "WebhookCreateResponse",
},
400: {
body: "APIErrorResponse",
},
403: {},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const channel_id = req.params.channel_id; const channel_id = req.params.channel_id;
const channel = await Channel.findOneOrFail({ const channel = await Channel.findOneOrFail({

View File

@ -0,0 +1,3 @@
import { Invite } from "../../entities";
export type ChannelInvitesResponse = Invite[];

View File

@ -0,0 +1,3 @@
import { Message } from "../../entities";
export type ChannelMessagesResponse = Message[];

View File

@ -0,0 +1,3 @@
import { Message } from "../../entities";
export type ChannelPinsResponse = Message[];

View File

@ -0,0 +1,3 @@
import { Webhook } from "../../entities";
export type ChannelWebhooksResponse = Webhook[];

View File

@ -0,0 +1,6 @@
import { User, Webhook } from "../../entities";
export interface WebhookCreateResponse {
user: User;
hook: Webhook;
}

View File

@ -6,6 +6,10 @@ export * from "./ApplicationSkusResponse";
export * from "./ApplicationsResponse"; export * from "./ApplicationsResponse";
export * from "./BackupCodesChallengeResponse"; export * from "./BackupCodesChallengeResponse";
export * from "./CaptchaRequiredResponse"; export * from "./CaptchaRequiredResponse";
export * from "./ChannelInvitesResponse";
export * from "./ChannelPinsResponse";
export * from "./ChannelWebhooksResponse";
export * from "./GenerateRegistrationTokensResponse"; export * from "./GenerateRegistrationTokensResponse";
export * from "./LocationMetadataResponse"; export * from "./LocationMetadataResponse";
export * from "./TokenResponse"; export * from "./TokenResponse";
export * from "./WebhookCreateResponse";