add crud api for channels
This commit is contained in:
parent
72dbb36dbb
commit
aa320e1b54
8 changed files with 189 additions and 19 deletions
|
@ -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
|
||||
);
|
||||
`));
|
||||
}
|
|
@ -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" }
|
||||
};
|
||||
|
|
128
src/routes/api/v1/channels.ts
Normal file
128
src/routes/api/v1/channels.ts
Normal 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;
|
|
@ -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;
|
||||
|
|
|
@ -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
5
src/types/channel.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
interface Channel {
|
||||
id: number,
|
||||
name: number,
|
||||
owner_id: number
|
||||
}
|
4
src/types/express.d.ts
vendored
4
src/types/express.d.ts
vendored
|
@ -1,6 +1,6 @@
|
|||
declare namespace Express {
|
||||
export interface Request {
|
||||
user: User | null,
|
||||
publicUser: User | null
|
||||
user: User,
|
||||
publicUser: User
|
||||
}
|
||||
}
|
||||
|
|
39
test.rest
39
test.rest
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue