add message
resource crud and gateway events (messages + message history)
This commit is contained in:
parent
ce9d331bc6
commit
6a6cc1aafd
7 changed files with 261 additions and 11 deletions
|
@ -11,7 +11,15 @@ export default async function databaseInit() {
|
||||||
CREATE TABLE IF NOT EXISTS channels(
|
CREATE TABLE IF NOT EXISTS channels(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
name VARCHAR(32) UNIQUE NOT NULL,
|
name VARCHAR(32) UNIQUE NOT NULL,
|
||||||
owner_id SERIAL REFERENCES users
|
owner_id SERIAL REFERENCES users ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS messages(
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
content VARCHAR(4000) NOT NULL,
|
||||||
|
channel_id SERIAL REFERENCES channels ON DELETE CASCADE,
|
||||||
|
author_id SERIAL REFERENCES users ON DELETE CASCADE,
|
||||||
|
created_at BIGINT
|
||||||
);
|
);
|
||||||
`));
|
`));
|
||||||
}
|
}
|
|
@ -4,7 +4,11 @@ export enum GatewayPayloadType {
|
||||||
Ready,
|
Ready,
|
||||||
Ping,
|
Ping,
|
||||||
|
|
||||||
ChannelCreate = 1000,
|
ChannelCreate = 110,
|
||||||
ChannelUpdate,
|
ChannelUpdate,
|
||||||
ChannelDelete
|
ChannelDelete,
|
||||||
|
|
||||||
|
MessageCreate = 120,
|
||||||
|
MessageUpdate,
|
||||||
|
MessageDelete
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,8 +55,10 @@ function clientUnsubscribeAll(ws: WebSocket) {
|
||||||
export function dispatch(channel: string, message: GatewayPayload) {
|
export function dispatch(channel: string, message: GatewayPayload) {
|
||||||
const members = dispatchChannels.get(channel);
|
const members = dispatchChannels.get(channel);
|
||||||
if (!members) return;
|
if (!members) return;
|
||||||
|
|
||||||
members.forEach(e => e.send(JSON.stringify(message)));
|
members.forEach(e => {
|
||||||
|
e.send(JSON.stringify(message));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeWithError(ws: WebSocket, { code, message }: { code: number, message: string }) {
|
function closeWithError(ws: WebSocket, { code, message }: { code: number, message: string }) {
|
||||||
|
|
|
@ -135,6 +135,11 @@ router.get(
|
||||||
authenticateRoute(),
|
authenticateRoute(),
|
||||||
param("id").isNumeric(),
|
param("id").isNumeric(),
|
||||||
async (req, res) => {
|
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 { id } = req.params;
|
||||||
const result = await query("SELECT id, name, owner_id FROM channels WHERE id = $1", [id]);
|
const result = await query("SELECT id, name, owner_id FROM channels WHERE id = $1", [id]);
|
||||||
if (result.rowCount < 1) {
|
if (result.rowCount < 1) {
|
||||||
|
@ -157,4 +162,72 @@ router.get(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/:id/messages",
|
||||||
|
authenticateRoute(),
|
||||||
|
param("id").isNumeric(),
|
||||||
|
body("content").isLength({ min: 1, max: 4000 }),
|
||||||
|
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);
|
||||||
|
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.rowCount < 1) {
|
||||||
|
return res.status(500).json({
|
||||||
|
...errors.GOT_NO_DATABASE_DATA
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnObject = {
|
||||||
|
id: result.rows[0].id,
|
||||||
|
content,
|
||||||
|
channel_id: channelId,
|
||||||
|
author_id: authorId,
|
||||||
|
created_at: createdAt
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatch(`channel:${channelId}`, {
|
||||||
|
t: GatewayPayloadType.MessageCreate,
|
||||||
|
d: returnObject
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(201).send(returnObject);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/:id/messages",
|
||||||
|
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 { before } = req.query;
|
||||||
|
const channelId = parseInt(req.params.id);
|
||||||
|
|
||||||
|
let finalRows = [];
|
||||||
|
|
||||||
|
if (before) {
|
||||||
|
const result = await query("SELECT id, content, channel_id, author_id, created_at FROM messages WHERE id < $1 AND channel_id = $2 ORDER BY id DESC LIMIT 50", [before, channelId]);
|
||||||
|
finalRows = result.rows;
|
||||||
|
} else {
|
||||||
|
const result = await query("SELECT id, content, channel_id, author_id, created_at FROM messages WHERE channel_id = $1 ORDER BY id DESC LIMIT 50", [channelId]);
|
||||||
|
finalRows = result.rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send(finalRows);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
126
src/routes/api/v1/messages.ts
Normal file
126
src/routes/api/v1/messages.ts
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import express from "express";
|
||||||
|
import { body, param, validationResult } from "express-validator";
|
||||||
|
import { authenticateRoute } from "../../../auth";
|
||||||
|
import { query } from "../../../database";
|
||||||
|
import { errors } from "../../../errors";
|
||||||
|
import { dispatch } from "../../../gateway";
|
||||||
|
import { GatewayPayloadType } from "../../../gateway/gatewaypayloadtype";
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
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 author_id, channel_id FROM messages WHERE id = $1", [id]);
|
||||||
|
if (permissionCheckResult.rowCount < 1) {
|
||||||
|
return res.status(404).json({
|
||||||
|
...errors.NOT_FOUND
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (permissionCheckResult.rows[0].author_id !== req.user.id) {
|
||||||
|
return res.status(403).json({
|
||||||
|
...errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await query("DELETE FROM messages WHERE id = $1", [id]);
|
||||||
|
if (result.rowCount < 1) {
|
||||||
|
return res.status(500).json({
|
||||||
|
...errors.GOT_NO_DATABASE_DATA
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(`channel:${permissionCheckResult.rows[0].channel_id}`, {
|
||||||
|
t: GatewayPayloadType.MessageDelete,
|
||||||
|
d: {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(204).send("");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.put(
|
||||||
|
"/:id",
|
||||||
|
authenticateRoute(),
|
||||||
|
body("content").isLength({ min: 1, max: 4000 }),
|
||||||
|
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 { content } = req.body;
|
||||||
|
const id = parseInt(req.params.id); // TODO: ??
|
||||||
|
|
||||||
|
const permissionCheckResult = await query("SELECT author_id, channel_id, created_at FROM messages WHERE id = $1", [id]);
|
||||||
|
if (permissionCheckResult.rowCount < 1) {
|
||||||
|
return res.status(404).json({
|
||||||
|
...errors.NOT_FOUND
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (permissionCheckResult.rows[0].author_id !== req.user.id) {
|
||||||
|
return res.status(403).json({
|
||||||
|
...errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await query("UPDATE messages SET content = $1 WHERE id = $2", [content, id]);
|
||||||
|
if (result.rowCount < 1) {
|
||||||
|
return res.status(500).json({
|
||||||
|
...errors.GOT_NO_DATABASE_DATA
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnObject = {
|
||||||
|
id,
|
||||||
|
content,
|
||||||
|
channel_id: permissionCheckResult.rows[0].channel_id,
|
||||||
|
author_id: permissionCheckResult.rows[0].author_id,
|
||||||
|
created_at: permissionCheckResult.rows[0].created_at
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatch(`channel:${permissionCheckResult.rows[0].channel_id}`, {
|
||||||
|
t: GatewayPayloadType.MessageUpdate,
|
||||||
|
d: returnObject
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send(returnObject);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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, content, channel_id, author_id, created_at FROM messages WHERE id = $1", [id]);
|
||||||
|
if (result.rowCount < 1) {
|
||||||
|
return res.status(404).json({
|
||||||
|
...errors.NOT_FOUND
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send(result.rows[0]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export default router;
|
|
@ -1,6 +1,7 @@
|
||||||
import { Application, json } from "express";
|
import { Application, json } from "express";
|
||||||
import usersRouter from "./routes/api/v1/users";
|
import usersRouter from "./routes/api/v1/users";
|
||||||
import channelsRouter from "./routes/api/v1/channels";
|
import channelsRouter from "./routes/api/v1/channels";
|
||||||
|
import messagesRouter from "./routes/api/v1/messages";
|
||||||
|
|
||||||
export default function(app: Application) {
|
export default function(app: Application) {
|
||||||
app.get("/", (req, res) => res.send("hello!"));
|
app.get("/", (req, res) => res.send("hello!"));
|
||||||
|
@ -8,4 +9,5 @@ export default function(app: Application) {
|
||||||
app.use(json());
|
app.use(json());
|
||||||
app.use("/api/v1/users", usersRouter);
|
app.use("/api/v1/users", usersRouter);
|
||||||
app.use("/api/v1/channels", channelsRouter);
|
app.use("/api/v1/channels", channelsRouter);
|
||||||
|
app.use("/api/v1/messages", messagesRouter);
|
||||||
};
|
};
|
||||||
|
|
47
test.rest
47
test.rest
|
@ -19,13 +19,13 @@ content-type: application/json
|
||||||
###
|
###
|
||||||
|
|
||||||
GET http://localhost:3000/api/v1/users/self HTTP/1.1
|
GET http://localhost:3000/api/v1/users/self HTTP/1.1
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NTI0NDA1LCJleHAiOjE2NDk2OTcyMDV9.4nIDs0K8MCT18GsdlKdicT_GK2KbEqi_P7cND3_aZvE
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
POST http://localhost:3000/api/v1/channels HTTP/1.1
|
POST http://localhost:3000/api/v1/channels HTTP/1.1
|
||||||
content-type: application/json
|
content-type: application/json
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NTI0NDA1LCJleHAiOjE2NDk2OTcyMDV9.4nIDs0K8MCT18GsdlKdicT_GK2KbEqi_P7cND3_aZvE
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "yet another channel"
|
"name": "yet another channel"
|
||||||
|
@ -35,7 +35,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6M
|
||||||
|
|
||||||
PUT http://localhost:3000/api/v1/channels/5 HTTP/1.1
|
PUT http://localhost:3000/api/v1/channels/5 HTTP/1.1
|
||||||
content-type: application/json
|
content-type: application/json
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NTI0NDA1LCJleHAiOjE2NDk2OTcyMDV9.4nIDs0K8MCT18GsdlKdicT_GK2KbEqi_P7cND3_aZvE
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc
|
||||||
#Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5MjU5NDUwLCJleHAiOjE2NDk0MzIyNTB9.JmF9NujFZnln7A-ynNpeyayGBqmR5poAyACYV6RnSQY
|
#Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5MjU5NDUwLCJleHAiOjE2NDk0MzIyNTB9.JmF9NujFZnln7A-ynNpeyayGBqmR5poAyACYV6RnSQY
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -45,15 +45,50 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6M
|
||||||
###
|
###
|
||||||
|
|
||||||
DELETE http://localhost:3000/api/v1/channels/1 HTTP/1.1
|
DELETE http://localhost:3000/api/v1/channels/1 HTTP/1.1
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NTI0NDA1LCJleHAiOjE2NDk2OTcyMDV9.4nIDs0K8MCT18GsdlKdicT_GK2KbEqi_P7cND3_aZvE
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc
|
||||||
#Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5MjU5NDUwLCJleHAiOjE2NDk0MzIyNTB9.JmF9NujFZnln7A-ynNpeyayGBqmR5poAyACYV6RnSQY
|
#Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5MjU5NDUwLCJleHAiOjE2NDk0MzIyNTB9.JmF9NujFZnln7A-ynNpeyayGBqmR5poAyACYV6RnSQY
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET http://localhost:3000/api/v1/channels/1 HTTP/1.1
|
GET http://localhost:3000/api/v1/channels/1 HTTP/1.1
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NTI0NDA1LCJleHAiOjE2NDk2OTcyMDV9.4nIDs0K8MCT18GsdlKdicT_GK2KbEqi_P7cND3_aZvE
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET http://localhost:3000/api/v1/channels HTTP/1.1
|
GET http://localhost:3000/api/v1/channels HTTP/1.1
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NTI0NDA1LCJleHAiOjE2NDk2OTcyMDV9.4nIDs0K8MCT18GsdlKdicT_GK2KbEqi_P7cND3_aZvE
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST http://localhost:3000/api/v1/channels/5/messages HTTP/1.1
|
||||||
|
content-type: application/json
|
||||||
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc
|
||||||
|
|
||||||
|
{
|
||||||
|
"content": "i hate cheese"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
GET http://localhost:3000/api/v1/channels/5/messages HTTP/1.1
|
||||||
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
PUT http://localhost:3000/api/v1/messages/2 HTTP/1.1
|
||||||
|
content-type: application/json
|
||||||
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc
|
||||||
|
|
||||||
|
{
|
||||||
|
"content": "hello again!"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
GET http://localhost:3000/api/v1/messages/2
|
||||||
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
DELETE http://localhost:3000/api/v1/messages/2 HTTP/1.1
|
||||||
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5NzAwMTE0LCJleHAiOjE2NDk4NzI5MTR9.EOn8MBHZLCxfU5fHc0ZY2x9p3y-_RdD7X915L1B6Ftc
|
||||||
|
|
Loading…
Reference in a new issue