add crud api for channels

This commit is contained in:
hippoz 2022-04-06 18:50:36 +03:00
parent 72dbb36dbb
commit aa320e1b54
Signed by: hippoz
GPG key ID: 7C52899193467641
8 changed files with 189 additions and 19 deletions

View file

@ -2,10 +2,16 @@ import { query } from ".";
export default async function databaseInit() {
console.log(await query(`
CREATE TABLE users(
CREATE TABLE IF NOT EXISTS users(
id SERIAL PRIMARY KEY,
username VARCHAR(32) UNIQUE NOT NULL,
password TEXT
);
CREATE TABLE IF NOT EXISTS channels(
id SERIAL PRIMARY KEY,
name VARCHAR(32) UNIQUE NOT NULL,
owner_id SERIAL REFERENCES users
);
`));
}

View file

@ -2,5 +2,7 @@ export const errors = {
INVALID_DATA: { code: 6001, message: "Invalid data" },
BAD_LOGIN_CREDENTIALS: { code: 6002, message: "Bad login credentials provided" },
BAD_AUTH: { code: 6003, message: "Bad authentication" },
NOT_FOUND: { code: 6004, message: "Not found" },
FORBIDDEN_DUE_TO_MISSING_PERMISSIONS: { code: 6005, message: "Forbidden due to missing permission(s)" },
GOT_NO_DATABASE_DATA: { code: 7001, message: "Unexpectedly got no data from database" }
};

View file

@ -0,0 +1,128 @@
import express from "express";
import { body, param, validationResult } from "express-validator";
import { authenticateRoute } from "../../../auth";
import { query } from "../../../database";
import { errors } from "../../../errors";
const router = express.Router();
router.use(express.json());
router.post(
"/",
authenticateRoute(),
body("name").isLength({ min: 1, max: 40 }).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() });
}
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.rowCount < 1) {
return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA
});
}
res.status(201).send(result.rows[0]);
}
);
router.put(
"/:id",
authenticateRoute(),
body("name").isLength({ min: 1, max: 40 }).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 } = req.params;
const permissionCheckResult = await query("SELECT owner_id FROM channels WHERE id = $1", [id]);
if (permissionCheckResult.rowCount < 1) {
return res.status(404).json({
...errors.NOT_FOUND
});
}
if (permissionCheckResult.rows[0].owner_id !== req.user.id) {
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.rowCount < 1) {
return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA
});
}
return res.status(200).send({
id: parseInt(id), // TODO: ??
name,
owner_id: permissionCheckResult.rows[0].owner_id
});
}
);
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 { name } = req.body;
const { id } = req.params;
const permissionCheckResult = await query("SELECT owner_id FROM channels WHERE id = $1", [id]);
if (permissionCheckResult.rowCount < 1) {
return res.status(404).json({
...errors.NOT_FOUND
});
}
if (permissionCheckResult.rows[0].owner_id !== req.user.id) {
return res.status(403).json({
...errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS
});
}
const result = await query("DELETE FROM channels WHERE id = $1", [id]);
if (result.rowCount < 1) {
return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA
});
}
return res.status(204).send("");
}
);
router.get(
"/:id",
authenticateRoute(),
param("id").isNumeric(),
async (req, res) => {
const { id } = req.params;
const result = await query("SELECT id, name, owner_id FROM channels 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;

View file

@ -5,15 +5,11 @@ import { body, validationResult } from "express-validator";
import { compare, hash } from "bcrypt";
import { authenticateRoute, signToken } from "../../../auth";
const route = express.Router();
const router = express.Router();
route.use(express.json());
router.use(express.json());
route.get("/", (req, res) => {
res.send({ message: "api/v1/users OK" });
});
route.post(
router.post(
"/register",
body("username").isLength({ min: 3, max: 32 }).isAlphanumeric("en-US", { ignore: " _-" }),
body("password").isLength({ min: 8, max: 1000 }),
@ -45,7 +41,7 @@ route.post(
}
);
route.post(
router.post(
"/login",
body("username").isLength({ min: 3, max: 32 }).isAlphanumeric("en-US", { ignore: " _-" }),
body("password").isLength({ min: 8, max: 1000 }),
@ -72,7 +68,7 @@ route.post(
}
);
route.get(
router.get(
"/self",
authenticateRoute(),
(req, res) => {
@ -80,4 +76,4 @@ route.get(
}
);
export default route;
export default router;

View file

@ -1,6 +1,8 @@
import { Application } from "express";
import route from "./routes/api/v1/users";
import usersRouter from "./routes/api/v1/users";
import channelsRouter from "./routes/api/v1/channels";
export default function(app: Application) {
app.use("/api/v1/users", route);
app.use("/api/v1/users", usersRouter);
app.use("/api/v1/channels", channelsRouter);
};

5
src/types/channel.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
interface Channel {
id: number,
name: number,
owner_id: number
}

View file

@ -1,6 +1,6 @@
declare namespace Express {
export interface Request {
user: User | null,
publicUser: User | null
user: User,
publicUser: User
}
}

View file

@ -2,8 +2,8 @@ POST http://localhost:3000/api/v1/users/register HTTP/1.1
content-type: application/json
{
"username": "testuser",
"password": "123123123"
"username": "another user",
"password": "234234234"
}
###
@ -12,12 +12,43 @@ POST http://localhost:3000/api/v1/users/login HTTP/1.1
content-type: application/json
{
"username": "testuser",
"password": "123123123"
"username": "another user",
"password": "234234234"
}
###
GET http://localhost:3000/api/v1/users/self HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidHlwZSI6MSwiaWF0IjoxNjQ5MTg3MDc5LCJleHAiOjE2NDkzNTk4Nzl9.tVzJWnBP7IFhA88XRwByKGXQ4cihWdJSoxUkrWHkIVU
###
POST http://localhost:3000/api/v1/channels HTTP/1.1
content-type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidHlwZSI6MSwiaWF0IjoxNjQ5MTg3MDc5LCJleHAiOjE2NDkzNTk4Nzl9.tVzJWnBP7IFhA88XRwByKGXQ4cihWdJSoxUkrWHkIVU
{
"name": "my channel"
}
###
PUT http://localhost:3000/api/v1/channels/2 HTTP/1.1
content-type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidHlwZSI6MSwiaWF0IjoxNjQ5MTg3MDc5LCJleHAiOjE2NDkzNTk4Nzl9.tVzJWnBP7IFhA88XRwByKGXQ4cihWdJSoxUkrWHkIVU
#Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5MjU5NDUwLCJleHAiOjE2NDk0MzIyNTB9.JmF9NujFZnln7A-ynNpeyayGBqmR5poAyACYV6RnSQY
{
"name": "this is my channel"
}
###
DELETE http://localhost:3000/api/v1/channels/1 HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidHlwZSI6MSwiaWF0IjoxNjQ5MTg3MDc5LCJleHAiOjE2NDkzNTk4Nzl9.tVzJWnBP7IFhA88XRwByKGXQ4cihWdJSoxUkrWHkIVU
#Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidHlwZSI6MSwiaWF0IjoxNjQ5MjU5NDUwLCJleHAiOjE2NDk0MzIyNTB9.JmF9NujFZnln7A-ynNpeyayGBqmR5poAyACYV6RnSQY
###
GET http://localhost:3000/api/v1/channels/1 HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidHlwZSI6MSwiaWF0IjoxNjQ5MTg3MDc5LCJleHAiOjE2NDkzNTk4Nzl9.tVzJWnBP7IFhA88XRwByKGXQ4cihWdJSoxUkrWHkIVU