diff --git a/client/components/admin/index.tsx b/client/components/admin/index.tsx index 28338a61..2fa68538 100644 --- a/client/components/admin/index.tsx +++ b/client/components/admin/index.tsx @@ -77,7 +77,7 @@ const Admin = () => { - {posts?.map((post, i) => ( + {posts?.map((post) => ( {post.visibility} diff --git a/client/lib/constants.ts b/client/lib/constants.ts index 7767b3e8..5723ba35 100644 --- a/client/lib/constants.ts +++ b/client/lib/constants.ts @@ -53,10 +53,9 @@ export const allowedFileNames = [ ".fish_prompt", ".zshrc", ".zsh", - ".zprofile", + ".zprofile" ] - export const codeFileExtensions = [ "awk", "bat", @@ -120,5 +119,5 @@ export const allowedFileExtensions = [ "txt", "webmanifest", "log", - ...codeFileExtensions, -] \ No newline at end of file + ...codeFileExtensions +] diff --git a/client/lib/hooks/use-user-data.ts b/client/lib/hooks/use-user-data.ts index d823085c..ce0c9e00 100644 --- a/client/lib/hooks/use-user-data.ts +++ b/client/lib/hooks/use-user-data.ts @@ -1,41 +1,43 @@ -import { User } from "@lib/types"; -import Cookies from "js-cookie"; -import { useRouter } from "next/router"; -import { useEffect, useMemo, useState } from "react"; +import { User } from "@lib/types" +import Cookies from "js-cookie" +import { useRouter } from "next/router" +import { useEffect, useMemo, useState } from "react" const useUserData = () => { - const [authToken, setAuthToken] = useState(Cookies.get("drift-token") || ''); - const [user, setUser] = useState(); - const router = useRouter() - useEffect(() => { - const token = Cookies.get("drift-token") - if (token) { - setAuthToken(token) - } - }, [setAuthToken]) + const [authToken, setAuthToken] = useState( + Cookies.get("drift-token") || "" + ) + const [user, setUser] = useState() + const router = useRouter() + useEffect(() => { + const token = Cookies.get("drift-token") + if (token) { + setAuthToken(token) + } + }, [setAuthToken]) - useEffect(() => { - if (authToken) { - const fetchUser = async () => { - const response = await fetch(`/server-api/users/self`, { - headers: { - "Authorization": `Bearer ${authToken}` - } - }) - if (response.ok) { - const user = await response.json() - setUser(user) - } else { - Cookies.remove("drift-token") - setAuthToken("") - router.push("/") - } - } - fetchUser() - } - }, [authToken, router]) + useEffect(() => { + if (authToken) { + const fetchUser = async () => { + const response = await fetch(`/server-api/users/self`, { + headers: { + Authorization: `Bearer ${authToken}` + } + }) + if (response.ok) { + const user = await response.json() + setUser(user) + } else { + Cookies.remove("drift-token") + setAuthToken("") + router.push("/") + } + } + fetchUser() + } + }, [authToken, router]) - return user; + return user } -export default useUserData \ No newline at end of file +export default useUserData diff --git a/server/src/database.ts b/server/src/database.ts index 77d3e9e9..278737aa 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -1,14 +1,11 @@ -import databasePath from "@lib/get-database-path"; +import databasePath from "@lib/get-database-path" import { Sequelize } from "sequelize-typescript" import { SequelizeStorage, Umzug } from "umzug" export const sequelize = new Sequelize({ dialect: "sqlite", database: "drift", - storage: - process.env.MEMORY_DB === "true" - ? ":memory:" - : databasePath, + storage: process.env.MEMORY_DB === "true" ? ":memory:" : databasePath, models: [__dirname + "/lib/models"], logging: true }) @@ -20,24 +17,28 @@ if (process.env.MEMORY_DB !== "true") { } const umzug = new Umzug({ - migrations: { glob: process.env.NODE_ENV === "production" ? __dirname + "/migrations/*.js" : __dirname + "/migrations/*.ts" }, + migrations: { + glob: + process.env.NODE_ENV === "production" + ? __dirname + "/migrations/*.js" + : __dirname + "/migrations/*.ts" + }, context: sequelize.getQueryInterface(), storage: new SequelizeStorage({ sequelize }), logger: console }) export type Migration = typeof umzug._types.migration - - ; (async () => { - // Checks migrations and run them if they are not already applied. To keep - // track of the executed migrations, a table (and sequelize model) called SequelizeMeta - // will be automatically created (if it doesn't exist already) and parsed. - console.log("Checking migrations...") - const migrations = await umzug.up() - if (migrations.length > 0) { - console.log("Migrations applied:") - console.log(migrations) - } else { - console.log("No migrations applied.") - } - })() +;(async () => { + // Checks migrations and run them if they are not already applied. To keep + // track of the executed migrations, a table (and sequelize model) called SequelizeMeta + // will be automatically created (if it doesn't exist already) and parsed. + console.log("Checking migrations...") + const migrations = await umzug.up() + if (migrations.length > 0) { + console.log("Migrations applied:") + console.log(migrations) + } else { + console.log("No migrations applied.") + } +})() diff --git a/server/src/lib/config.ts b/server/src/lib/config.ts index c77910c8..09fe7ccf 100644 --- a/server/src/lib/config.ts +++ b/server/src/lib/config.ts @@ -1,5 +1,5 @@ export default { port: process.env.PORT || 3000, jwt_secret: process.env.JWT_SECRET || "myjwtsecret", - drift_home: process.env.DRIFT_HOME || "~/.drift", + drift_home: process.env.DRIFT_HOME || "~/.drift" } diff --git a/server/src/lib/get-database-path.ts b/server/src/lib/get-database-path.ts index 738cbf4e..118abcb8 100644 --- a/server/src/lib/get-database-path.ts +++ b/server/src/lib/get-database-path.ts @@ -6,11 +6,12 @@ import config from "./config" // Expand ~ into the current user home dir. // This does *not* support `~other_user/tmp` => `/home/other_user/tmp`. function getDatabasePath() { - const fileName = "drift.sqlite" - const databasePath = `${config.drift_home}/${fileName}` || `~/.drift/${fileName}` + const fileName = "drift.sqlite" + const databasePath = + `${config.drift_home}/${fileName}` || `~/.drift/${fileName}` - const home = os.homedir().replace("$", "$$$$"); - return path.resolve(databasePath.replace(/^~($|\/|\\)/, home + "$1")); + const home = os.homedir().replace("$", "$$$$") + return path.resolve(databasePath.replace(/^~($|\/|\\)/, home + "$1")) } export default getDatabasePath() diff --git a/server/src/lib/middleware/is-admin.ts b/server/src/lib/middleware/is-admin.ts index 5504f483..e2bcc093 100644 --- a/server/src/lib/middleware/is-admin.ts +++ b/server/src/lib/middleware/is-admin.ts @@ -25,11 +25,11 @@ export default function authenticateToken( if (err) return res.sendStatus(403) const userObj = await UserModel.findByPk(user.id, { attributes: { - exclude: ["password"], + exclude: ["password"] } }) - if (!userObj || userObj.role !== 'admin') { + if (!userObj || userObj.role !== "admin") { return res.sendStatus(403) } diff --git a/server/src/lib/models/File.ts b/server/src/lib/models/File.ts index db7f0f11..e2441d9c 100644 --- a/server/src/lib/models/File.ts +++ b/server/src/lib/models/File.ts @@ -28,11 +28,9 @@ import { User } from "./User" ] } })) - @Table({ - tableName: "files", + tableName: "files" }) - export class File extends Model { @IsUUID(4) @PrimaryKey diff --git a/server/src/lib/models/Post.ts b/server/src/lib/models/Post.ts index 93dd39ff..07f94c75 100644 --- a/server/src/lib/models/Post.ts +++ b/server/src/lib/models/Post.ts @@ -38,11 +38,9 @@ import { File } from "./File" ] } })) -@Table( - { - tableName: "posts", - } -) +@Table({ + tableName: "posts" +}) export class Post extends Model { @IsUUID(4) @PrimaryKey diff --git a/server/src/lib/models/PostAuthor.ts b/server/src/lib/models/PostAuthor.ts index eae891ac..0ffd07ae 100644 --- a/server/src/lib/models/PostAuthor.ts +++ b/server/src/lib/models/PostAuthor.ts @@ -12,9 +12,8 @@ import { Post } from "./Post" import { User } from "./User" @Table({ - tableName: "post_authors", + tableName: "post_authors" }) - export class PostAuthor extends Model { @IsUUID(4) @PrimaryKey diff --git a/server/src/lib/models/User.ts b/server/src/lib/models/User.ts index 63d90bab..b4653137 100644 --- a/server/src/lib/models/User.ts +++ b/server/src/lib/models/User.ts @@ -30,9 +30,8 @@ import { PostAuthor } from "./PostAuthor" } })) @Table({ - tableName: "users", + tableName: "users" }) - export class User extends Model { @IsUUID(4) @PrimaryKey diff --git a/server/src/migrations/00_user-table.ts b/server/src/migrations/00_user-table.ts index f0f01ab0..37965296 100644 --- a/server/src/migrations/00_user-table.ts +++ b/server/src/migrations/00_user-table.ts @@ -3,29 +3,29 @@ import { DataTypes } from "sequelize" import type { Migration } from "../database" export const up: Migration = async ({ context: queryInterface }) => - queryInterface.createTable("users", { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - unique: true - }, - username: { - type: DataTypes.STRING, - }, - password: { - type: DataTypes.STRING, - }, - createdAt: { - type: DataTypes.DATE, - }, - updatedAt: { - type: DataTypes.DATE, - }, - deletedAt: { - type: DataTypes.DATE, - } - }) + queryInterface.createTable("users", { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + unique: true + }, + username: { + type: DataTypes.STRING + }, + password: { + type: DataTypes.STRING + }, + createdAt: { + type: DataTypes.DATE + }, + updatedAt: { + type: DataTypes.DATE + }, + deletedAt: { + type: DataTypes.DATE + } + }) export const down: Migration = async ({ context: queryInterface }) => - queryInterface.dropTable("users") + queryInterface.dropTable("users") diff --git a/server/src/migrations/01_user-add-role.ts b/server/src/migrations/01_user-add-role.ts index 186aa629..58878ca4 100644 --- a/server/src/migrations/01_user-add-role.ts +++ b/server/src/migrations/01_user-add-role.ts @@ -3,10 +3,10 @@ import { DataTypes } from "sequelize" import type { Migration } from "../database" export const up: Migration = async ({ context: queryInterface }) => - queryInterface.addColumn("users", "role", { - type: DataTypes.STRING, - defaultValue: "user" - }) + queryInterface.addColumn("users", "role", { + type: DataTypes.STRING, + defaultValue: "user" + }) export const down: Migration = async ({ context: queryInterface }) => - queryInterface.removeColumn("users", "role") + queryInterface.removeColumn("users", "role") diff --git a/server/src/migrations/02_post_table.ts b/server/src/migrations/02_post_table.ts index d751ea5b..5f5ee721 100644 --- a/server/src/migrations/02_post_table.ts +++ b/server/src/migrations/02_post_table.ts @@ -3,32 +3,32 @@ import { DataTypes } from "sequelize" import type { Migration } from "../database" export const up: Migration = async ({ context: queryInterface }) => - queryInterface.createTable("posts", { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - unique: true - }, - title: { - type: DataTypes.STRING, - }, - visibility: { - type: DataTypes.STRING, - }, - password: { - type: DataTypes.STRING, - }, - createdAt: { - type: DataTypes.DATE, - }, - updatedAt: { - type: DataTypes.DATE, - }, - deletedAt: { - type: DataTypes.DATE, - } - }) + queryInterface.createTable("posts", { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + unique: true + }, + title: { + type: DataTypes.STRING + }, + visibility: { + type: DataTypes.STRING + }, + password: { + type: DataTypes.STRING + }, + createdAt: { + type: DataTypes.DATE + }, + updatedAt: { + type: DataTypes.DATE + }, + deletedAt: { + type: DataTypes.DATE + } + }) export const down: Migration = async ({ context: queryInterface }) => - await queryInterface.dropTable("posts") + await queryInterface.dropTable("posts") diff --git a/server/src/migrations/03_files_table.ts b/server/src/migrations/03_files_table.ts index 294bd627..0082f04e 100644 --- a/server/src/migrations/03_files_table.ts +++ b/server/src/migrations/03_files_table.ts @@ -3,57 +3,56 @@ import { DataTypes } from "sequelize" import type { Migration } from "../database" export const up: Migration = async ({ context: queryInterface }) => - queryInterface.createTable("files", { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - allowNull: false, - unique: true - }, - title: { - type: DataTypes.STRING, - }, - content: { - type: DataTypes.STRING, - }, - sha: { - type: DataTypes.STRING, - - }, - html: { - type: DataTypes.STRING, - }, - createdAt: { - type: DataTypes.DATE, - }, - updatedAt: { - type: DataTypes.DATE, - }, - deletedAt: { - type: DataTypes.DATE, - }, - userId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: "users", - key: "id" - }, - onDelete: "SET NULL", - onUpdate: "CASCADE" - }, - postId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: "posts", - key: "id" - }, - onDelete: "SET NULL", - onUpdate: "CASCADE" - } - }) + queryInterface.createTable("files", { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + allowNull: false, + unique: true + }, + title: { + type: DataTypes.STRING + }, + content: { + type: DataTypes.STRING + }, + sha: { + type: DataTypes.STRING + }, + html: { + type: DataTypes.STRING + }, + createdAt: { + type: DataTypes.DATE + }, + updatedAt: { + type: DataTypes.DATE + }, + deletedAt: { + type: DataTypes.DATE + }, + userId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: "users", + key: "id" + }, + onDelete: "SET NULL", + onUpdate: "CASCADE" + }, + postId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: "posts", + key: "id" + }, + onDelete: "SET NULL", + onUpdate: "CASCADE" + } + }) export const down: Migration = async ({ context: queryInterface }) => - await queryInterface.dropTable("files") + await queryInterface.dropTable("files") diff --git a/server/src/migrations/04_post_authors_table.ts b/server/src/migrations/04_post_authors_table.ts index 6b4d4ccd..ee789a8b 100644 --- a/server/src/migrations/04_post_authors_table.ts +++ b/server/src/migrations/04_post_authors_table.ts @@ -3,39 +3,39 @@ import { DataTypes } from "sequelize" import type { Migration } from "../database" export const up: Migration = async ({ context: queryInterface }) => - queryInterface.createTable("post_authors", { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - createdAt: { - type: DataTypes.DATE, - }, - updatedAt: { - type: DataTypes.DATE, - }, - postId: { - type: DataTypes.UUID, - primaryKey: true, - references: { - model: "posts", - key: "id" - }, - onDelete: "CASCADE", - onUpdate: "CASCADE" - }, - userId: { - type: DataTypes.UUID, - primaryKey: true, - references: { - model: "users", - key: "id" - }, - onDelete: "CASCADE", - onUpdate: "CASCADE" - } - }) + queryInterface.createTable("post_authors", { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + createdAt: { + type: DataTypes.DATE + }, + updatedAt: { + type: DataTypes.DATE + }, + postId: { + type: DataTypes.UUID, + primaryKey: true, + references: { + model: "posts", + key: "id" + }, + onDelete: "CASCADE", + onUpdate: "CASCADE" + }, + userId: { + type: DataTypes.UUID, + primaryKey: true, + references: { + model: "users", + key: "id" + }, + onDelete: "CASCADE", + onUpdate: "CASCADE" + } + }) export const down: Migration = async ({ context: queryInterface }) => - await queryInterface.dropTable("post_authors") + await queryInterface.dropTable("post_authors") diff --git a/server/src/routes/admin.ts b/server/src/routes/admin.ts index c501b3b3..8e921879 100644 --- a/server/src/routes/admin.ts +++ b/server/src/routes/admin.ts @@ -1,115 +1,114 @@ -import isAdmin from "@lib/middleware/is-admin"; -import { Post } from "@lib/models/Post"; -import { User } from "@lib/models/User"; -import { File } from "@lib/models/File"; -import { Router } from "express"; +import isAdmin from "@lib/middleware/is-admin" +import { Post } from "@lib/models/Post" +import { User } from "@lib/models/User" +import { File } from "@lib/models/File" +import { Router } from "express" export const admin = Router() admin.use(isAdmin) admin.get("/is-admin", async (req, res) => { - return res.json({ - isAdmin: true - }) + return res.json({ + isAdmin: true + }) }) admin.get("/users", async (req, res, next) => { - try { - const users = await User.findAll({ - attributes: { - exclude: ["password"], - include: ["id", "username", "createdAt", "updatedAt"] - }, - include: [ - { - model: Post, - as: "posts", - attributes: ["id"] - } - ], - }) - res.json(users) - } catch (e) { - next(e) - } + try { + const users = await User.findAll({ + attributes: { + exclude: ["password"], + include: ["id", "username", "createdAt", "updatedAt"] + }, + include: [ + { + model: Post, + as: "posts", + attributes: ["id"] + } + ] + }) + res.json(users) + } catch (e) { + next(e) + } }) admin.get("/posts", async (req, res, next) => { - try { - const posts = await Post.findAll({ - attributes: { - exclude: ["content"], - include: ["id", "title", "visibility", "createdAt"] - }, - include: [ - { - model: File, - as: "files", - attributes: ["id", "title", "createdAt", "html"] - }, - { - model: User, - as: "users", - attributes: ["id", "username"] - } - ] - }) - res.json(posts) - } catch (e) { - next(e) - } + try { + const posts = await Post.findAll({ + attributes: { + exclude: ["content"], + include: ["id", "title", "visibility", "createdAt"] + }, + include: [ + { + model: File, + as: "files", + attributes: ["id", "title", "createdAt", "html"] + }, + { + model: User, + as: "users", + attributes: ["id", "username"] + } + ] + }) + res.json(posts) + } catch (e) { + next(e) + } }) admin.get("/post/:id", async (req, res, next) => { - try { - const post = await Post.findByPk(req.params.id, { - attributes: { - exclude: ["content"], - include: ["id", "title", "visibility", "createdAt"] - }, - include: [ - { - model: File, - as: "files", - attributes: ["id", "title", "sha", "createdAt", "updatedAt", "html"] - }, - { - model: User, - as: "users", - attributes: ["id", "username"] - } - ] - }) - if (!post) { - return res.status(404).json({ - message: "Post not found" - }) - } + try { + const post = await Post.findByPk(req.params.id, { + attributes: { + exclude: ["content"], + include: ["id", "title", "visibility", "createdAt"] + }, + include: [ + { + model: File, + as: "files", + attributes: ["id", "title", "sha", "createdAt", "updatedAt", "html"] + }, + { + model: User, + as: "users", + attributes: ["id", "username"] + } + ] + }) + if (!post) { + return res.status(404).json({ + message: "Post not found" + }) + } - res.json(post) - } catch (e) { - next(e) - } + res.json(post) + } catch (e) { + next(e) + } }) admin.delete("/post/:id", async (req, res, next) => { - try { - const post = await Post.findByPk(req.params.id) - if (!post) { - return res.status(404).json({ - message: "Post not found" - }) - } + try { + const post = await Post.findByPk(req.params.id) + if (!post) { + return res.status(404).json({ + message: "Post not found" + }) + } - if (post.files?.length) - await Promise.all(post.files.map((file) => file.destroy())) - await post.destroy({ force: true }) - res.json({ - message: "Post deleted" - }) - } catch (e) { - next(e) - } + if (post.files?.length) + await Promise.all(post.files.map((file) => file.destroy())) + await post.destroy({ force: true }) + res.json({ + message: "Post deleted" + }) + } catch (e) { + next(e) + } }) - diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 5cc9c984..9d44eafd 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -68,7 +68,10 @@ auth.post( const user = { username: username as string, password: await hash(req.body.password, salt), - role: (!!process.env.MEMORY_DB && process.env.ENABLE_ADMIN && count === 0) ? "admin" : "user" + role: + !!process.env.MEMORY_DB && process.env.ENABLE_ADMIN && count === 0 + ? "admin" + : "user" } const created_user = await User.create(user) diff --git a/server/src/routes/posts.ts b/server/src/routes/posts.ts index f7ece834..8c324c8e 100644 --- a/server/src/routes/posts.ts +++ b/server/src/routes/posts.ts @@ -80,7 +80,7 @@ posts.post( .update(file.content) .digest("hex") .toString(), - html: html || '', + html: html || "", userId: req.body.userId, postId: newPost.id }) @@ -183,9 +183,7 @@ posts.get( { "$files.title$": { [Op.like]: `%${q}%` } }, { "$files.content$": { [Op.like]: `%${q}%` } } ], - [Op.and]: [ - { "$users.id$": req.user?.id || "" }, - ] + [Op.and]: [{ "$users.id$": req.user?.id || "" }] }, include: [ { @@ -195,7 +193,7 @@ posts.get( }, { model: User, - as: "users", + as: "users" } ], attributes: ["id", "title", "visibility", "createdAt", "deletedAt"], @@ -296,7 +294,6 @@ posts.delete("/:id", jwt, async (req: UserJwtRequest, res, next) => { return res.status(404).json({ error: "Post not found" }) } - if (req.user?.id !== post.users![0].id) { return res.status(403).json({ error: "Forbidden" }) } diff --git a/server/src/server.ts b/server/src/server.ts index 2296b12b..2d6db754 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -2,9 +2,9 @@ import { createServer } from "http" import { app } from "./app" import config from "./lib/config" import "./database" - ; (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}`) + ) +})()