waffle/src/routes/api/v1/channels.ts
2022-09-06 20:52:44 +03:00

291 lines
No EOL
9.4 KiB
TypeScript

import express from "express";
import { body, param, validationResult } from "express-validator";
import { authenticateRoute } from "../../../auth";
import { query } from "../../../database";
import { getMessageById, getMessagesByChannelFirstPage, getMessagesByChannelPage } from "../../../database/templates";
import { errors } from "../../../errors";
import { dispatch, dispatchChannelSubscribe } from "../../../gateway";
import { GatewayPayloadType } from "../../../gateway/gatewaypayloadtype";
import serverConfig from "../../../serverconfig";
const router = express.Router();
router.post(
"/",
authenticateRoute(),
body("name").isLength({ min: 1, max: 32 }).isAlphanumeric("en-US", { ignore: " _-" }),
async (req, res) => {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.status(400).json({ ...errors.INVALID_DATA, errors: validationErrors.array() });
}
if (serverConfig.superuserRequirement.createChannel && !req.user.is_superuser) {
return res.status(403).json({ ...errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS });
}
const { name } = req.body;
const result = await query("INSERT INTO channels(name, owner_id) VALUES ($1, $2) RETURNING id, name, owner_id", [name, req.user.id]);
if (!result || result.rowCount < 1) {
return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA
});
}
dispatch("*", {
t: GatewayPayloadType.ChannelCreate,
d: result.rows[0]
});
// When a new channel is created, we will currently subscribe every client
// on the gateway (this will be changed when the concept of "communities" is added)
dispatchChannelSubscribe("*", `channel:${result.rows[0].id}`);
res.status(201).send(result.rows[0]);
}
);
router.put(
"/:id",
authenticateRoute(),
body("name").isLength({ min: 1, max: 32 }).isAlphanumeric("en-US", { ignore: " _-" }),
param("id").isNumeric(),
async (req, res) => {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.status(400).json({ ...errors.INVALID_DATA, errors: validationErrors.array() });
}
const { name } = req.body;
const id = parseInt(req.params.id); // TODO: ??
const permissionCheckResult = await query("SELECT owner_id FROM channels WHERE id = $1", [id]);
if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
return res.status(404).json({
...errors.NOT_FOUND
});
}
if (permissionCheckResult.rows[0].owner_id !== req.user.id && !req.user.is_superuser) {
return res.status(403).json({
...errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS
});
}
const result = await query("UPDATE channels SET name = $1 WHERE id = $2", [name, id]);
if (!result || result.rowCount < 1) {
return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA
});
}
const updatePayload = {
id,
name,
owner_id: permissionCheckResult.rows[0].owner_id
};
dispatch(`channel:${id}`, {
t: GatewayPayloadType.ChannelUpdate,
d: updatePayload
});
return res.status(200).send(updatePayload);
}
);
router.delete(
"/:id",
authenticateRoute(),
param("id").isNumeric(),
async (req, res) => {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.status(400).json({ ...errors.INVALID_DATA, errors: validationErrors.array() });
}
const id = parseInt(req.params.id); // TODO: ??
const permissionCheckResult = await query("SELECT owner_id FROM channels WHERE id = $1", [id]);
if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
return res.status(404).json({
...errors.NOT_FOUND
});
}
if (permissionCheckResult.rows[0].owner_id !== req.user.id && !req.user.is_superuser) {
return res.status(403).json({
...errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS
});
}
const result = await query("DELETE FROM channels WHERE id = $1", [id]);
if (!result || result.rowCount < 1) {
return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA
});
}
dispatch(`channel:${id}`, {
t: GatewayPayloadType.ChannelDelete,
d: {
id
}
});
return res.status(204).send("");
}
);
router.get(
"/:id",
authenticateRoute(),
param("id").isNumeric(),
async (req, res) => {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.status(400).json({ ...errors.INVALID_DATA, errors: validationErrors.array() });
}
const { id } = req.params;
const result = await query("SELECT id, name, owner_id FROM channels WHERE id = $1", [id]);
if (!result || result.rowCount < 1) {
return res.status(404).json({
...errors.NOT_FOUND
});
}
return res.status(200).send(result.rows[0]);
}
);
router.get(
"/",
authenticateRoute(),
async (req, res) => {
const result = await query("SELECT id, name, owner_id FROM channels");
return res.status(200).send(result ? result.rows : []);
}
);
router.post(
"/:id/messages",
authenticateRoute(),
param("id").isNumeric(),
body("content").isLength({ min: 1, max: 4000 }),
body("optimistic_id").optional().isNumeric(),
async (req, res) => {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.status(400).json({ ...errors.INVALID_DATA, errors: validationErrors.array() });
}
const optimisticId = parseInt(req.body.optimistic_id);
const channelId = parseInt(req.params.id);
const { content } = req.body;
const authorId = req.user.id;
const createdAt = Date.now().toString();
const result = await query("INSERT INTO messages(content, channel_id, author_id, created_at) VALUES ($1, $2, $3, $4) RETURNING id", [content, channelId, authorId, createdAt]);
if (!result || result.rowCount < 1) {
return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA
});
}
let returnObject: any = {
id: result.rows[0].id,
content,
channel_id: channelId,
author_id: authorId,
author_username: req.user.username,
created_at: createdAt
};
dispatch(`channel:${channelId}`, (ws) => {
let payload: any = returnObject;
if (ws.state && ws.state.user && ws.state.user.id === req.user.id && optimisticId) {
payload = {
...payload,
optimistic_id: optimisticId
}
returnObject = {
...returnObject,
optimistic_id: optimisticId
};
}
return {
t: GatewayPayloadType.MessageCreate,
d: payload
};
});
return res.status(201).send(returnObject);
}
);
router.get(
"/:id/messages",
authenticateRoute(),
param("id").isNumeric(),
param("count").optional().isInt({ min: 10, max: 50 }),
async (req, res) => {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.status(400).json({ ...errors.INVALID_DATA, errors: validationErrors.array() });
}
const { before, count } = req.query;
let limit = typeof count === "string" ? parseInt(count || "25") : 25;
if (limit === NaN) {
return res.status(400).json({ ...errors.INVALID_DATA });
}
const channelId = parseInt(req.params.id);
let finalRows = [];
if (before) {
const result = await query(getMessagesByChannelPage(limit), [before, channelId]);
finalRows = result ? result.rows : [];
} else {
const result = await query(getMessagesByChannelFirstPage(limit), [channelId]);
finalRows = result ? result.rows : [];
}
return res.status(200).send(finalRows);
}
);
router.post(
"/:id/typing",
authenticateRoute(),
param("id").isNumeric(),
async (req, res) => {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.status(400).json({ ...errors.INVALID_DATA, errors: validationErrors.array() });
}
const channelId = parseInt(req.params.id);
dispatch(`channel:${channelId}`, {
t: GatewayPayloadType.TypingStart,
d: {
user: {
id: req.publicUser.id,
username: req.publicUser.username
},
channel: {
id: channelId
},
time: 7500
}
});
return res.status(201).send("");
}
);
export default router;