diff --git a/server/.prettierrc b/server/.prettierrc new file mode 100644 index 00000000..cda1eceb --- /dev/null +++ b/server/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": false, + "trailingComma": "none", + "singleQuote": false, + "printWidth": 80, + "useTabs": true +} diff --git a/server/index.ts b/server/index.ts index 79b14b17..ce33db53 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,4 +1,4 @@ -import * as dotenv from 'dotenv'; -dotenv.config(); +import * as dotenv from "dotenv" +dotenv.config() -import './src/server'; +import "./src/server" diff --git a/server/package.json b/server/package.json index e80e987c..40318093 100644 --- a/server/package.json +++ b/server/package.json @@ -7,7 +7,8 @@ "start": "ts-node index.ts", "dev": "nodemon index.ts", "build": "tsc -p .", - "migrate": "sequelize db:migrate" + "migrate": "sequelize db:migrate", + "lint": "prettier --config .prettierrc 'src/**/*.ts' 'index.ts' --write" }, "author": "", "license": "ISC", @@ -40,6 +41,7 @@ "@types/marked": "^4.0.3", "@types/node": "^17.0.21", "@types/react-dom": "^17.0.14", + "prettier": "^2.6.0", "ts-node": "^10.6.0", "tsconfig-paths": "^3.14.1", "tslint": "^6.1.3", diff --git a/server/src/app.ts b/server/src/app.ts index c7eb8a58..10072cee 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -1,29 +1,30 @@ -import * as express from 'express'; -import * as bodyParser from 'body-parser'; -import * as errorhandler from 'strong-error-handler'; -import * as cors from 'cors'; -import { posts, users, auth, files } from '@routes/index'; -import { errors } from 'celebrate' +import * as express from "express" +import * as bodyParser from "body-parser" +import * as errorhandler from "strong-error-handler" +import * as cors from "cors" +import { posts, users, auth, files } from "@routes/index" +import { errors } from "celebrate" -export const app = express(); +export const app = express() -app.use(bodyParser.urlencoded({ extended: true })); -app.use(bodyParser.json({ limit: '5mb' })); +app.use(bodyParser.urlencoded({ extended: true })) +app.use(bodyParser.json({ limit: "5mb" })) const corsOptions = { - origin: `http://localhost:3001`, -}; -app.use(cors(corsOptions)); + origin: `http://localhost:3001` +} +app.use(cors(corsOptions)) app.use("/auth", auth) app.use("/posts", posts) app.use("/users", users) app.use("/files", files) -app.use(errors()); - -app.use(errorhandler({ - debug: process.env.ENV !== 'production', - log: true, -})); +app.use(errors()) +app.use( + errorhandler({ + debug: process.env.ENV !== "production", + log: true + }) +) diff --git a/server/src/lib/config.ts b/server/src/lib/config.ts index e08c630f..20d59c3f 100644 --- a/server/src/lib/config.ts +++ b/server/src/lib/config.ts @@ -1,4 +1,4 @@ export default { - port: process.env.PORT || 3000, - jwt_secret: process.env.JWT_SECRET || 'myjwtsecret', + port: process.env.PORT || 3000, + jwt_secret: process.env.JWT_SECRET || "myjwtsecret" } diff --git a/server/src/lib/middleware/jwt.ts b/server/src/lib/middleware/jwt.ts index 9df4a9f7..1de91fef 100644 --- a/server/src/lib/middleware/jwt.ts +++ b/server/src/lib/middleware/jwt.ts @@ -1,30 +1,34 @@ -import { NextFunction, Request, Response } from 'express'; -import * as jwt from 'jsonwebtoken'; -import config from '../config'; -import { User as UserModel } from '../models/User'; +import { NextFunction, Request, Response } from "express" +import * as jwt from "jsonwebtoken" +import config from "../config" +import { User as UserModel } from "../models/User" export interface User { - id: string; + id: string } export interface UserJwtRequest extends Request { - user?: User; + user?: User } -export default function authenticateToken(req: UserJwtRequest, res: Response, next: NextFunction) { - const authHeader = req.headers['authorization'] - const token = authHeader && authHeader.split(' ')[1] +export default function authenticateToken( + req: UserJwtRequest, + res: Response, + next: NextFunction +) { + const authHeader = req.headers["authorization"] + const token = authHeader && authHeader.split(" ")[1] - if (token == null) return res.sendStatus(401) + if (token == null) return res.sendStatus(401) - jwt.verify(token, config.jwt_secret, async (err: any, user: any) => { - if (err) return res.sendStatus(403) - const userObj = await UserModel.findByPk(user.id); - if (!userObj) { - return res.sendStatus(403); - } - req.user = user + jwt.verify(token, config.jwt_secret, async (err: any, user: any) => { + if (err) return res.sendStatus(403) + const userObj = await UserModel.findByPk(user.id) + if (!userObj) { + return res.sendStatus(403) + } + req.user = user - next() - }) + next() + }) } diff --git a/server/src/lib/middleware/secret-key.ts b/server/src/lib/middleware/secret-key.ts index 241fe647..aa912c92 100644 --- a/server/src/lib/middleware/secret-key.ts +++ b/server/src/lib/middleware/secret-key.ts @@ -1,14 +1,18 @@ -import { NextFunction, Request, Response } from 'express'; +import { NextFunction, Request, Response } from "express" -const key = process.env.SECRET_KEY; +const key = process.env.SECRET_KEY if (!key) { - throw new Error('SECRET_KEY is not set.'); + throw new Error("SECRET_KEY is not set.") } -export default function authenticateToken(req: Request, res: Response, next: NextFunction) { - const requestKey = req.headers['x-secret-key'] - if (requestKey !== key) { - return res.sendStatus(401) - } - next() +export default function authenticateToken( + req: Request, + res: Response, + next: NextFunction +) { + const requestKey = req.headers["x-secret-key"] + if (requestKey !== key) { + return res.sendStatus(401) + } + next() } diff --git a/server/src/lib/models/File.ts b/server/src/lib/models/File.ts index 85f19e18..f8c7e756 100644 --- a/server/src/lib/models/File.ts +++ b/server/src/lib/models/File.ts @@ -1,53 +1,65 @@ -import { BelongsTo, Column, CreatedAt, DataType, ForeignKey, IsUUID, Model, PrimaryKey, Scopes, Table, Unique } from 'sequelize-typescript'; -import { Post } from './Post'; -import { User } from './User'; - +import { + BelongsTo, + Column, + CreatedAt, + DataType, + ForeignKey, + IsUUID, + Model, + PrimaryKey, + Scopes, + Table, + Unique +} from "sequelize-typescript" +import { Post } from "./Post" +import { User } from "./User" @Scopes(() => ({ - full: { - include: [{ - model: User, - through: { attributes: [] }, - }, - { - model: Post, - through: { attributes: [] }, - }] - } + full: { + include: [ + { + model: User, + through: { attributes: [] } + }, + { + model: Post, + through: { attributes: [] } + } + ] + } })) - @Table export class File extends Model { - @IsUUID(4) - @PrimaryKey - @Unique - @Column({ - type: DataType.UUID, - defaultValue: DataType.UUIDV4, - }) - id!: string + @IsUUID(4) + @PrimaryKey + @Unique + @Column({ + type: DataType.UUID, + defaultValue: DataType.UUIDV4 + }) + id!: string - @Column - title!: string; + @Column + title!: string - @Column - content!: string; + @Column + content!: string - @Column - sha!: string; + @Column + sha!: string - @Column - html!: string; + @Column + html!: string - @ForeignKey(() => User) - @BelongsTo(() => User, 'userId') - user!: User; + @ForeignKey(() => User) + @BelongsTo(() => User, "userId") + user!: User - @ForeignKey(() => Post) - @BelongsTo(() => Post, 'postId') - post!: Post; + @ForeignKey(() => Post) + @BelongsTo(() => Post, "postId") + post!: Post - @CreatedAt - @Column - createdAt!: Date; + @CreatedAt + @Column + createdAt!: Date } diff --git a/server/src/lib/models/Post.ts b/server/src/lib/models/Post.ts index 0992d078..ed5a8bc6 100644 --- a/server/src/lib/models/Post.ts +++ b/server/src/lib/models/Post.ts @@ -1,58 +1,74 @@ -import { BelongsToMany, Column, CreatedAt, DataType, HasMany, IsUUID, Model, PrimaryKey, Scopes, Table, Unique, UpdatedAt } from 'sequelize-typescript'; -import { PostAuthor } from './PostAuthor'; -import { User } from './User'; -import { File } from './File'; +import { + BelongsToMany, + Column, + CreatedAt, + DataType, + HasMany, + IsUUID, + Model, + PrimaryKey, + Scopes, + Table, + Unique, + UpdatedAt +} from "sequelize-typescript" +import { PostAuthor } from "./PostAuthor" +import { User } from "./User" +import { File } from "./File" @Scopes(() => ({ - user: { - include: [{ - model: User, - through: { attributes: [] }, - }], - }, - full: { - include: [{ - model: User, - through: { attributes: [] }, - }, - { - model: File, - through: { attributes: [] }, - }] - } + user: { + include: [ + { + model: User, + through: { attributes: [] } + } + ] + }, + full: { + include: [ + { + model: User, + through: { attributes: [] } + }, + { + model: File, + through: { attributes: [] } + } + ] + } })) - @Table export class Post extends Model { - @IsUUID(4) - @PrimaryKey - @Unique - @Column({ - type: DataType.UUID, - defaultValue: DataType.UUIDV4, - }) - id!: string + @IsUUID(4) + @PrimaryKey + @Unique + @Column({ + type: DataType.UUID, + defaultValue: DataType.UUIDV4 + }) + id!: string - @Column - title!: string; + @Column + title!: string - @BelongsToMany(() => User, () => PostAuthor) - users?: User[]; + @BelongsToMany(() => User, () => PostAuthor) + users?: User[] - @HasMany(() => File, { constraints: false }) - files?: File[]; + @HasMany(() => File, { constraints: false }) + files?: File[] - @CreatedAt - @Column - createdAt!: Date; + @CreatedAt + @Column + createdAt!: Date - @Column - visibility!: string; + @Column + visibility!: string - @Column - password?: string; + @Column + password?: string - @UpdatedAt - @Column - updatedAt!: Date; + @UpdatedAt + @Column + updatedAt!: Date } diff --git a/server/src/lib/models/PostAuthor.ts b/server/src/lib/models/PostAuthor.ts index 76af896f..0d4eeec5 100644 --- a/server/src/lib/models/PostAuthor.ts +++ b/server/src/lib/models/PostAuthor.ts @@ -1,23 +1,32 @@ -import { Model, Column, Table, ForeignKey, IsUUID, PrimaryKey, DataType, Unique } from "sequelize-typescript"; -import { Post } from "./Post"; -import { User } from "./User"; +import { + Model, + Column, + Table, + ForeignKey, + IsUUID, + PrimaryKey, + DataType, + Unique +} from "sequelize-typescript" +import { Post } from "./Post" +import { User } from "./User" @Table export class PostAuthor extends Model { - @IsUUID(4) - @PrimaryKey - @Unique - @Column({ - type: DataType.UUID, - defaultValue: DataType.UUIDV4, - }) - id!: string + @IsUUID(4) + @PrimaryKey + @Unique + @Column({ + type: DataType.UUID, + defaultValue: DataType.UUIDV4 + }) + id!: string - @ForeignKey(() => Post) - @Column - postId!: number; + @ForeignKey(() => Post) + @Column + postId!: number - @ForeignKey(() => User) - @Column - authorId!: number; + @ForeignKey(() => User) + @Column + authorId!: number } diff --git a/server/src/lib/models/User.ts b/server/src/lib/models/User.ts index d050e956..17c8a750 100644 --- a/server/src/lib/models/User.ts +++ b/server/src/lib/models/User.ts @@ -1,48 +1,59 @@ -import { Model, Column, Table, BelongsToMany, Scopes, CreatedAt, UpdatedAt, IsUUID, PrimaryKey, DataType, Unique } from "sequelize-typescript"; -import { Post } from "./Post"; -import { PostAuthor } from "./PostAuthor"; +import { + Model, + Column, + Table, + BelongsToMany, + Scopes, + CreatedAt, + UpdatedAt, + IsUUID, + PrimaryKey, + DataType, + Unique +} from "sequelize-typescript" +import { Post } from "./Post" +import { PostAuthor } from "./PostAuthor" @Scopes(() => ({ - posts: { - include: [ - { - model: Post, - through: { attributes: [] }, - }, - ], - }, - withoutPassword: { - attributes: { - exclude: ["password"] - } - } + posts: { + include: [ + { + model: Post, + through: { attributes: [] } + } + ] + }, + withoutPassword: { + attributes: { + exclude: ["password"] + } + } })) - @Table export class User extends Model { - @IsUUID(4) - @PrimaryKey - @Unique - @Column({ - type: DataType.UUID, - defaultValue: DataType.UUIDV4, - }) - id!: string + @IsUUID(4) + @PrimaryKey + @Unique + @Column({ + type: DataType.UUID, + defaultValue: DataType.UUIDV4 + }) + id!: string - @Column - username!: string; + @Column + username!: string - @Column - password!: string; + @Column + password!: string - @BelongsToMany(() => Post, () => PostAuthor) - posts?: Post[]; + @BelongsToMany(() => Post, () => PostAuthor) + posts?: Post[] - @CreatedAt - @Column - createdAt!: Date; + @CreatedAt + @Column + createdAt!: Date - @UpdatedAt - @Column - updatedAt!: Date; + @UpdatedAt + @Column + updatedAt!: Date } diff --git a/server/src/lib/sequelize.ts b/server/src/lib/sequelize.ts index edc6bd19..261724af 100644 --- a/server/src/lib/sequelize.ts +++ b/server/src/lib/sequelize.ts @@ -1,9 +1,12 @@ -import { Sequelize } from 'sequelize-typescript'; +import { Sequelize } from "sequelize-typescript" export const sequelize = new Sequelize({ - dialect: 'sqlite', - database: 'drift', - storage: process.env.MEMORY_DB === "true" ? ":memory:" : __dirname + '/../../drift.sqlite', - models: [__dirname + '/models'], - host: 'localhost', -}); + dialect: "sqlite", + database: "drift", + storage: + process.env.MEMORY_DB === "true" + ? ":memory:" + : __dirname + "/../../drift.sqlite", + models: [__dirname + "/models"], + host: "localhost" +}) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index bade025d..7abc6916 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -1,121 +1,137 @@ -import { Router } from 'express' +import { Router } from "express" import { genSalt, hash, compare } from "bcrypt" -import { User } from '@lib/models/User' -import { sign } from 'jsonwebtoken' -import config from '@lib/config' -import jwt from '@lib/middleware/jwt' -import { celebrate, Joi } from 'celebrate' +import { User } from "@lib/models/User" +import { sign } from "jsonwebtoken" +import config from "@lib/config" +import jwt from "@lib/middleware/jwt" +import { celebrate, Joi } from "celebrate" const NO_EMPTY_SPACE_REGEX = /^\S*$/ -export const requiresServerPassword = (process.env.MEMORY_DB || process.env.ENV === 'production') && !!process.env.REGISTRATION_PASSWORD +export const requiresServerPassword = + (process.env.MEMORY_DB || process.env.ENV === "production") && + !!process.env.REGISTRATION_PASSWORD console.log(`Registration password required: ${requiresServerPassword}`) export const auth = Router() -const validateAuthPayload = (username: string, password: string, serverPassword?: string): void => { - if (!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6) { - throw new Error("Authentication data does not fulfill requirements") - } +const validateAuthPayload = ( + username: string, + password: string, + serverPassword?: string +): void => { + if (!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6) { + throw new Error("Authentication data does not fulfill requirements") + } - if (requiresServerPassword) { - if (!serverPassword || process.env.REGISTRATION_PASSWORD !== serverPassword) { - throw new Error("Server password is incorrect. Please contact the server administrator.") - } - } + if (requiresServerPassword) { + if ( + !serverPassword || + process.env.REGISTRATION_PASSWORD !== serverPassword + ) { + throw new Error( + "Server password is incorrect. Please contact the server administrator." + ) + } + } } -auth.post('/signup', - celebrate({ - body: { - username: Joi.string().required(), - password: Joi.string().required(), - serverPassword: Joi.string().required().allow('', null), - } - }), - async (req, res, next) => { - try { - validateAuthPayload(req.body.username, req.body.password, req.body.serverPassword) - const username = req.body.username.toLowerCase(); +auth.post( + "/signup", + celebrate({ + body: { + username: Joi.string().required(), + password: Joi.string().required(), + serverPassword: Joi.string().required().allow("", null) + } + }), + async (req, res, next) => { + try { + validateAuthPayload( + req.body.username, + req.body.password, + req.body.serverPassword + ) + const username = req.body.username.toLowerCase() - const existingUser = await User.findOne({ - where: { username: username }, - }); - if (existingUser) { - throw new Error("Username already exists"); - } + const existingUser = await User.findOne({ + where: { username: username } + }) + if (existingUser) { + throw new Error("Username already exists") + } - const salt = await genSalt(10); - const user = { - username: username as string, - password: await hash(req.body.password, salt), - }; + const salt = await genSalt(10) + const user = { + username: username as string, + password: await hash(req.body.password, salt) + } - const created_user = await User.create(user); + const created_user = await User.create(user) - const token = generateAccessToken(created_user.id); + const token = generateAccessToken(created_user.id) - res.status(201).json({ token: token, userId: created_user.id }); - } catch (e) { - next(e); - } - } -); + res.status(201).json({ token: token, userId: created_user.id }) + } catch (e) { + next(e) + } + } +) auth.post( - "/signin", - celebrate({ - body: { - username: Joi.string().required(), - password: Joi.string().required(), - serverPassword: Joi.string().required().allow('', null), - }, - }), - async (req, res, next) => { - const error = "User does not exist or password is incorrect"; - const errorToThrow = new Error(error); - try { - if (!req.body.username || !req.body.password) { - throw errorToThrow; - } + "/signin", + celebrate({ + body: { + username: Joi.string().required(), + password: Joi.string().required(), + serverPassword: Joi.string().required().allow("", null) + } + }), + async (req, res, next) => { + const error = "User does not exist or password is incorrect" + const errorToThrow = new Error(error) + try { + if (!req.body.username || !req.body.password) { + throw errorToThrow + } - const username = req.body.username.toLowerCase(); - const user = await User.findOne({ where: { username: username } }); - if (!user) { - throw errorToThrow; - } + const username = req.body.username.toLowerCase() + const user = await User.findOne({ where: { username: username } }) + if (!user) { + throw errorToThrow + } - const password_valid = await compare(req.body.password, user.password); - if (password_valid) { - const token = generateAccessToken(user.id); - res.status(200).json({ token: token, userId: user.id }); - } else { - throw errorToThrow; - } - } catch (e) { - next(e); - } - } -); + const password_valid = await compare(req.body.password, user.password) + if (password_valid) { + const token = generateAccessToken(user.id) + res.status(200).json({ token: token, userId: user.id }) + } else { + throw errorToThrow + } + } catch (e) { + next(e) + } + } +) auth.get("/requires-passcode", async (req, res, next) => { - if (requiresServerPassword) { - res.status(200).json({ requiresPasscode: true }); - } else { - res.status(200).json({ requiresPasscode: false }); - } -}); + if (requiresServerPassword) { + res.status(200).json({ requiresPasscode: true }) + } else { + res.status(200).json({ requiresPasscode: false }) + } +}) function generateAccessToken(id: string) { - return sign({ id: id }, config.jwt_secret, { expiresIn: "2d" }); + return sign({ id: id }, config.jwt_secret, { expiresIn: "2d" }) } auth.get("/verify-token", jwt, async (req, res, next) => { - try { - res.status(200).json({ - message: "You are authenticated", - }); - } catch (e) { - next(e); - } -}); + try { + res.status(200).json({ + message: "You are authenticated" + }) + } catch (e) { + next(e) + } +}) diff --git a/server/src/routes/files.ts b/server/src/routes/files.ts index 3cc98f72..fe227bed 100644 --- a/server/src/routes/files.ts +++ b/server/src/routes/files.ts @@ -1,72 +1,72 @@ -import { celebrate, Joi } from "celebrate"; -import { Router } from "express"; -import { File } from "@lib/models/File"; -import secretKey from "@lib/middleware/secret-key"; +import { celebrate, Joi } from "celebrate" +import { Router } from "express" +import { File } from "@lib/models/File" +import secretKey from "@lib/middleware/secret-key" -export const files = Router(); +export const files = Router() -files.get("/raw/:id", - celebrate({ - params: { - id: Joi.string().required(), - }, - }), - secretKey, - async (req, res, next) => { - try { - const file = await File.findOne({ - where: { - id: req.params.id - }, - attributes: ["title", "content"], - }) +files.get( + "/raw/:id", + celebrate({ + params: { + id: Joi.string().required() + } + }), + secretKey, + async (req, res, next) => { + try { + const file = await File.findOne({ + where: { + id: req.params.id + }, + attributes: ["title", "content"] + }) - if (!file) { - return res.status(404).json({ error: "File not found" }) - } + if (!file) { + return res.status(404).json({ error: "File not found" }) + } - // TODO: JWT-checkraw files - if (file?.post?.visibility === "private") { - // jwt(req as UserJwtRequest, res, () => { - // res.json(file); - // }) - res.json(file); - } else { - res.json(file); - } - } - catch (e) { - next(e); - } - } + // TODO: JWT-checkraw files + if (file?.post?.visibility === "private") { + // jwt(req as UserJwtRequest, res, () => { + // res.json(file); + // }) + res.json(file) + } else { + res.json(file) + } + } catch (e) { + next(e) + } + } ) +files.get( + "/html/:id", + celebrate({ + params: { + id: Joi.string().required() + } + }), + async (req, res, next) => { + try { + const file = await File.findOne({ + where: { + id: req.params.id + }, + attributes: ["html"] + }) -files.get("/html/:id", - celebrate({ - params: { - id: Joi.string().required(), - }, - }), - async (req, res, next) => { - try { - const file = await File.findOne({ - where: { - id: req.params.id - }, - attributes: ["html"], - }) + if (!file) { + return res.status(404).json({ error: "File not found" }) + } - if (!file) { - return res.status(404).json({ error: "File not found" }) - } - - res.setHeader('Content-Type', 'text/plain') - res.setHeader('Cache-Control', 'public, max-age=4800') - res.status(200).write(file.html) - res.end() - } catch (error) { - next(error) - } - } + res.setHeader("Content-Type", "text/plain") + res.setHeader("Cache-Control", "public, max-age=4800") + res.status(200).write(file.html) + res.end() + } catch (error) { + next(error) + } + } ) diff --git a/server/src/routes/index.ts b/server/src/routes/index.ts index bd7fe99c..415b51fa 100644 --- a/server/src/routes/index.ts +++ b/server/src/routes/index.ts @@ -1,4 +1,4 @@ -export { auth } from "./auth"; -export { posts } from "./posts"; -export { users } from "./users"; -export { files } from "./files"; +export { auth } from "./auth" +export { posts } from "./posts" +export { users } from "./users" +export { files } from "./files" diff --git a/server/src/routes/posts.ts b/server/src/routes/posts.ts index 9a8b4575..8d98d12a 100644 --- a/server/src/routes/posts.ts +++ b/server/src/routes/posts.ts @@ -1,209 +1,236 @@ -import { Router } from "express"; -import { celebrate, Joi } from "celebrate"; -import { File } from '@lib/models/File' -import { Post } from '@lib/models/Post'; -import jwt, { UserJwtRequest } from '@lib/middleware/jwt'; -import * as crypto from "crypto"; -import { User } from '@lib/models/User'; -import secretKey from '@lib/middleware/secret-key'; -import markdown from '@lib/render-markdown'; +import { Router } from "express" +import { celebrate, Joi } from "celebrate" +import { File } from "@lib/models/File" +import { Post } from "@lib/models/Post" +import jwt, { UserJwtRequest } from "@lib/middleware/jwt" +import * as crypto from "crypto" +import { User } from "@lib/models/User" +import secretKey from "@lib/middleware/secret-key" +import markdown from "@lib/render-markdown" -export const posts = Router(); +export const posts = Router() const postVisibilitySchema = (value: string) => { - if (value === 'public' || value === 'private') { - return value; - } else { - throw new Error('Invalid post visibility'); - } + if (value === "public" || value === "private") { + return value + } else { + throw new Error("Invalid post visibility") + } } posts.post( - "/create", - jwt, - celebrate({ - body: { - title: Joi.string().required(), - files: Joi.any().required(), - visibility: Joi.string().custom(postVisibilitySchema, 'valid visibility').required(), - userId: Joi.string().required(), - password: Joi.string().optional(), - }, - }), - async (req, res, next) => { - try { - let hashedPassword: string = '' - if (req.body.visibility === 'protected') { - hashedPassword = crypto.createHash('sha256').update(req.body.password).digest('hex'); - } + "/create", + jwt, + celebrate({ + body: { + title: Joi.string().required().allow("", null), + files: Joi.any().required(), + visibility: Joi.string() + .custom(postVisibilitySchema, "valid visibility") + .required(), + userId: Joi.string().required(), + password: Joi.string().optional() + } + }), + async (req, res, next) => { + try { + let hashedPassword: string = "" + if (req.body.visibility === "protected") { + hashedPassword = crypto + .createHash("sha256") + .update(req.body.password) + .digest("hex") + } - const newPost = new Post({ - title: req.body.title, - visibility: req.body.visibility, - password: hashedPassword, - }) + const newPost = new Post({ + title: req.body.title, + visibility: req.body.visibility, + password: hashedPassword + }) - await newPost.save() - await newPost.$add('users', req.body.userId); - const newFiles = await Promise.all(req.body.files.map(async (file) => { - const html = getHtmlFromFile(file); - const newFile = new File({ - title: file.title, - content: file.content, - sha: crypto.createHash('sha256').update(file.content).digest('hex').toString(), - html - }) + await newPost.save() + await newPost.$add("users", req.body.userId) + const newFiles = await Promise.all( + req.body.files.map(async (file) => { + const html = getHtmlFromFile(file) + const newFile = new File({ + title: file.title || "", + content: file.content, + sha: crypto + .createHash("sha256") + .update(file.content) + .digest("hex") + .toString(), + html + }) - await newFile.$set("user", req.body.userId); - await newFile.$set("post", newPost.id); - await newFile.save(); - return newFile; - })) + await newFile.$set("user", req.body.userId) + await newFile.$set("post", newPost.id) + await newFile.save() + return newFile + }) + ) - await Promise.all(newFiles.map((file) => { - newPost.$add("files", file.id); - newPost.save(); - })) + await Promise.all( + newFiles.map((file) => { + newPost.$add("files", file.id) + newPost.save() + }) + ) - res.json(newPost); - } catch (e) { - next(e); - } - } -); + res.json(newPost) + } catch (e) { + next(e) + } + } +) posts.get("/", secretKey, async (req, res, next) => { - try { - const posts = await Post.findAll({ - attributes: ["id", "title", "visibility", "createdAt"], - }) + try { + const posts = await Post.findAll({ + attributes: ["id", "title", "visibility", "createdAt"] + }) - res.json(posts); - } catch (e) { - next(e); - } -}); + res.json(posts) + } catch (e) { + next(e) + } +}) posts.get("/mine", jwt, secretKey, async (req: UserJwtRequest, res, next) => { - if (!req.user) { - return res.status(401).json({ error: "Unauthorized" }) - } + if (!req.user) { + return res.status(401).json({ error: "Unauthorized" }) + } - try { - const user = await User.findByPk(req.user.id, { - include: [ - { - model: Post, - as: "posts", - include: [ - { - model: File, - as: "files" - } - ] - }, - ], - }) - if (!user) { - return res.status(404).json({ error: "User not found" }) - } - return res.json(user.posts?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())) - } catch (error) { - next(error) - } + try { + const user = await User.findByPk(req.user.id, { + include: [ + { + model: Post, + as: "posts", + include: [ + { + model: File, + as: "files" + } + ] + } + ] + }) + if (!user) { + return res.status(404).json({ error: "User not found" }) + } + return res.json( + user.posts?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) + ) + } catch (error) { + next(error) + } }) posts.get( - "/:id", - celebrate({ - params: { - id: Joi.string().required(), - }, - }), - async (req: UserJwtRequest, res, next) => { - try { - const post = await Post.findOne({ - where: { - id: req.params.id, - }, - include: [ - { - model: File, - as: "files", - attributes: [ - "id", - "title", - "content", - "sha", - "createdAt", - "updatedAt", - ], - }, - { - model: User, - as: "users", - attributes: ["id", "username"], - }, - ], - }); + "/:id", + celebrate({ + params: { + id: Joi.string().required() + } + }), + async (req: UserJwtRequest, res, next) => { + try { + const post = await Post.findOne({ + where: { + id: req.params.id + }, + include: [ + { + model: File, + as: "files", + attributes: [ + "id", + "title", + "content", + "sha", + "createdAt", + "updatedAt" + ] + }, + { + model: User, + as: "users", + attributes: ["id", "username"] + } + ] + }) - if (!post) { - return res.status(404).json({ error: "Post not found" }) - } + if (!post) { + return res.status(404).json({ error: "Post not found" }) + } - // if public or unlisted, cache - if (post.visibility === 'public' || post.visibility === 'unlisted') { - res.set('Cache-Control', 'public, max-age=4800') - } + // if public or unlisted, cache + if (post.visibility === "public" || post.visibility === "unlisted") { + res.set("Cache-Control", "public, max-age=4800") + } - if (post.visibility === 'public' || post?.visibility === 'unlisted') { - secretKey(req, res, () => { - res.json(post); - }) - } else if (post.visibility === 'private') { - jwt(req as UserJwtRequest, res, () => { - res.json(post); - }) - } else if (post.visibility === 'protected') { - const { password } = req.query - if (!password || typeof password !== 'string') { - return jwt(req as UserJwtRequest, res, () => { - res.json(post); - }) - } - const hash = crypto.createHash('sha256').update(password).digest('hex').toString() - if (hash !== post.password) { - return res.status(400).json({ error: "Incorrect password." }) - } + if (post.visibility === "public" || post?.visibility === "unlisted") { + secretKey(req, res, () => { + res.json(post) + }) + } else if (post.visibility === "private") { + jwt(req as UserJwtRequest, res, () => { + res.json(post) + }) + } else if (post.visibility === "protected") { + const { password } = req.query + if (!password || typeof password !== "string") { + return jwt(req as UserJwtRequest, res, () => { + res.json(post) + }) + } + const hash = crypto + .createHash("sha256") + .update(password) + .digest("hex") + .toString() + if (hash !== post.password) { + return res.status(400).json({ error: "Incorrect password." }) + } - res.json(post); - } - } - catch (e) { - next(e); - } - } -); + res.json(post) + } + } catch (e) { + next(e) + } + } +) function getHtmlFromFile(file: any) { - const renderAsMarkdown = ['markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', '']; - const fileType = () => { - const pathParts = file.title.split("."); - const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : ""; - return language; - }; - const type = fileType(); - let contentToRender: string = (file.content || ''); + const renderAsMarkdown = [ + "markdown", + "md", + "mdown", + "mkdn", + "mkd", + "mdwn", + "mdtxt", + "mdtext", + "text", + "" + ] + const fileType = () => { + const pathParts = file.title.split(".") + const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" + return language + } + const type = fileType() + let contentToRender: string = file.content || "" - if (!renderAsMarkdown.includes(type)) { - contentToRender = - `~~~${type} + if (!renderAsMarkdown.includes(type)) { + contentToRender = `~~~${type} ${file.content} -~~~`; - } else { - contentToRender = '\n' + file.content; - } - const html = markdown(contentToRender); - return html; +~~~` + } else { + contentToRender = "\n" + file.content + } + const html = markdown(contentToRender) + return html } - diff --git a/server/src/routes/users.ts b/server/src/routes/users.ts index 94be3c01..0ccc0b10 100644 --- a/server/src/routes/users.ts +++ b/server/src/routes/users.ts @@ -1,8 +1,8 @@ -import { Router } from "express"; +import { Router } from "express" // import jwt from "@lib/middleware/jwt"; // import { User } from "@lib/models/User"; -export const users = Router(); +export const users = Router() // users.get("/", jwt, async (req, res, next) => { // try { diff --git a/server/src/server.ts b/server/src/server.ts index dd741cd0..826154ec 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -1,13 +1,11 @@ -import { createServer } from 'http'; -import { app } from './app'; -import config from './lib/config'; -import { sequelize } from './lib/sequelize'; +import { createServer } from "http" +import { app } from "./app" +import config from "./lib/config" +import { sequelize } from "./lib/sequelize" -(async () => { - await sequelize.sync({}); - createServer(app) - .listen( - config.port, - () => console.info(`Server running on port ${config.port}`) - ); -})(); +;(async () => { + await sequelize.sync({}) + createServer(app).listen(config.port, () => + console.info(`Server running on port ${config.port}`) + ) +})() diff --git a/server/yarn.lock b/server/yarn.lock index 7eaf6f5a..0002e7aa 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -2027,6 +2027,11 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= +prettier@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.0.tgz#12f8f504c4d8ddb76475f441337542fa799207d4" + integrity sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A== + prism-react-renderer@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.1.tgz#88fc9d0df6bed06ca2b9097421349f8c2f24e30d"