client/server: lint

This commit is contained in:
Max Leiter 2022-03-29 00:19:33 -07:00
parent 6afc4c915e
commit 85ae8173bb
No known key found for this signature in database
GPG key ID: A3512F2F2F17EBDA
20 changed files with 323 additions and 328 deletions

View file

@ -77,7 +77,7 @@ const Admin = () => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{posts?.map((post, i) => ( {posts?.map((post) => (
<tr key={post.id}> <tr key={post.id}>
<td><PostModal id={post.id} /></td> <td><PostModal id={post.id} /></td>
<td>{post.visibility}</td> <td>{post.visibility}</td>

View file

@ -53,10 +53,9 @@ export const allowedFileNames = [
".fish_prompt", ".fish_prompt",
".zshrc", ".zshrc",
".zsh", ".zsh",
".zprofile", ".zprofile"
] ]
export const codeFileExtensions = [ export const codeFileExtensions = [
"awk", "awk",
"bat", "bat",
@ -120,5 +119,5 @@ export const allowedFileExtensions = [
"txt", "txt",
"webmanifest", "webmanifest",
"log", "log",
...codeFileExtensions, ...codeFileExtensions
] ]

View file

@ -1,41 +1,43 @@
import { User } from "@lib/types"; import { User } from "@lib/types"
import Cookies from "js-cookie"; import Cookies from "js-cookie"
import { useRouter } from "next/router"; import { useRouter } from "next/router"
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react"
const useUserData = () => { const useUserData = () => {
const [authToken, setAuthToken] = useState<string>(Cookies.get("drift-token") || ''); const [authToken, setAuthToken] = useState<string>(
const [user, setUser] = useState<User>(); Cookies.get("drift-token") || ""
const router = useRouter() )
useEffect(() => { const [user, setUser] = useState<User>()
const token = Cookies.get("drift-token") const router = useRouter()
if (token) { useEffect(() => {
setAuthToken(token) const token = Cookies.get("drift-token")
} if (token) {
}, [setAuthToken]) setAuthToken(token)
}
}, [setAuthToken])
useEffect(() => { useEffect(() => {
if (authToken) { if (authToken) {
const fetchUser = async () => { const fetchUser = async () => {
const response = await fetch(`/server-api/users/self`, { const response = await fetch(`/server-api/users/self`, {
headers: { headers: {
"Authorization": `Bearer ${authToken}` Authorization: `Bearer ${authToken}`
} }
}) })
if (response.ok) { if (response.ok) {
const user = await response.json() const user = await response.json()
setUser(user) setUser(user)
} else { } else {
Cookies.remove("drift-token") Cookies.remove("drift-token")
setAuthToken("") setAuthToken("")
router.push("/") router.push("/")
} }
} }
fetchUser() fetchUser()
} }
}, [authToken, router]) }, [authToken, router])
return user; return user
} }
export default useUserData export default useUserData

View file

@ -1,14 +1,11 @@
import databasePath from "@lib/get-database-path"; import databasePath from "@lib/get-database-path"
import { Sequelize } from "sequelize-typescript" import { Sequelize } from "sequelize-typescript"
import { SequelizeStorage, Umzug } from "umzug" import { SequelizeStorage, Umzug } from "umzug"
export const sequelize = new Sequelize({ export const sequelize = new Sequelize({
dialect: "sqlite", dialect: "sqlite",
database: "drift", database: "drift",
storage: storage: process.env.MEMORY_DB === "true" ? ":memory:" : databasePath,
process.env.MEMORY_DB === "true"
? ":memory:"
: databasePath,
models: [__dirname + "/lib/models"], models: [__dirname + "/lib/models"],
logging: true logging: true
}) })
@ -20,24 +17,28 @@ if (process.env.MEMORY_DB !== "true") {
} }
const umzug = new Umzug({ 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(), context: sequelize.getQueryInterface(),
storage: new SequelizeStorage({ sequelize }), storage: new SequelizeStorage({ sequelize }),
logger: console logger: console
}) })
export type Migration = typeof umzug._types.migration export type Migration = typeof umzug._types.migration
;(async () => {
; (async () => { // Checks migrations and run them if they are not already applied. To keep
// 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
// track of the executed migrations, a table (and sequelize model) called SequelizeMeta // will be automatically created (if it doesn't exist already) and parsed.
// will be automatically created (if it doesn't exist already) and parsed. console.log("Checking migrations...")
console.log("Checking migrations...") const migrations = await umzug.up()
const migrations = await umzug.up() if (migrations.length > 0) {
if (migrations.length > 0) { console.log("Migrations applied:")
console.log("Migrations applied:") console.log(migrations)
console.log(migrations) } else {
} else { console.log("No migrations applied.")
console.log("No migrations applied.") }
} })()
})()

View file

@ -1,5 +1,5 @@
export default { export default {
port: process.env.PORT || 3000, port: process.env.PORT || 3000,
jwt_secret: process.env.JWT_SECRET || "myjwtsecret", jwt_secret: process.env.JWT_SECRET || "myjwtsecret",
drift_home: process.env.DRIFT_HOME || "~/.drift", drift_home: process.env.DRIFT_HOME || "~/.drift"
} }

View file

@ -6,11 +6,12 @@ import config from "./config"
// Expand ~ into the current user home dir. // Expand ~ into the current user home dir.
// This does *not* support `~other_user/tmp` => `/home/other_user/tmp`. // This does *not* support `~other_user/tmp` => `/home/other_user/tmp`.
function getDatabasePath() { function getDatabasePath() {
const fileName = "drift.sqlite" const fileName = "drift.sqlite"
const databasePath = `${config.drift_home}/${fileName}` || `~/.drift/${fileName}` const databasePath =
`${config.drift_home}/${fileName}` || `~/.drift/${fileName}`
const home = os.homedir().replace("$", "$$$$"); const home = os.homedir().replace("$", "$$$$")
return path.resolve(databasePath.replace(/^~($|\/|\\)/, home + "$1")); return path.resolve(databasePath.replace(/^~($|\/|\\)/, home + "$1"))
} }
export default getDatabasePath() export default getDatabasePath()

View file

@ -25,11 +25,11 @@ export default function authenticateToken(
if (err) return res.sendStatus(403) if (err) return res.sendStatus(403)
const userObj = await UserModel.findByPk(user.id, { const userObj = await UserModel.findByPk(user.id, {
attributes: { attributes: {
exclude: ["password"], exclude: ["password"]
} }
}) })
if (!userObj || userObj.role !== 'admin') { if (!userObj || userObj.role !== "admin") {
return res.sendStatus(403) return res.sendStatus(403)
} }

View file

@ -28,11 +28,9 @@ import { User } from "./User"
] ]
} }
})) }))
@Table({ @Table({
tableName: "files", tableName: "files"
}) })
export class File extends Model { export class File extends Model {
@IsUUID(4) @IsUUID(4)
@PrimaryKey @PrimaryKey

View file

@ -38,11 +38,9 @@ import { File } from "./File"
] ]
} }
})) }))
@Table( @Table({
{ tableName: "posts"
tableName: "posts", })
}
)
export class Post extends Model { export class Post extends Model {
@IsUUID(4) @IsUUID(4)
@PrimaryKey @PrimaryKey

View file

@ -12,9 +12,8 @@ import { Post } from "./Post"
import { User } from "./User" import { User } from "./User"
@Table({ @Table({
tableName: "post_authors", tableName: "post_authors"
}) })
export class PostAuthor extends Model { export class PostAuthor extends Model {
@IsUUID(4) @IsUUID(4)
@PrimaryKey @PrimaryKey

View file

@ -30,9 +30,8 @@ import { PostAuthor } from "./PostAuthor"
} }
})) }))
@Table({ @Table({
tableName: "users", tableName: "users"
}) })
export class User extends Model { export class User extends Model {
@IsUUID(4) @IsUUID(4)
@PrimaryKey @PrimaryKey

View file

@ -3,29 +3,29 @@ import { DataTypes } from "sequelize"
import type { Migration } from "../database" import type { Migration } from "../database"
export const up: Migration = async ({ context: queryInterface }) => export const up: Migration = async ({ context: queryInterface }) =>
queryInterface.createTable("users", { queryInterface.createTable("users", {
id: { id: {
type: DataTypes.UUID, type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4, defaultValue: DataTypes.UUIDV4,
primaryKey: true, primaryKey: true,
unique: true unique: true
}, },
username: { username: {
type: DataTypes.STRING, type: DataTypes.STRING
}, },
password: { password: {
type: DataTypes.STRING, type: DataTypes.STRING
}, },
createdAt: { createdAt: {
type: DataTypes.DATE, type: DataTypes.DATE
}, },
updatedAt: { updatedAt: {
type: DataTypes.DATE, type: DataTypes.DATE
}, },
deletedAt: { deletedAt: {
type: DataTypes.DATE, type: DataTypes.DATE
} }
}) })
export const down: Migration = async ({ context: queryInterface }) => export const down: Migration = async ({ context: queryInterface }) =>
queryInterface.dropTable("users") queryInterface.dropTable("users")

View file

@ -3,10 +3,10 @@ import { DataTypes } from "sequelize"
import type { Migration } from "../database" import type { Migration } from "../database"
export const up: Migration = async ({ context: queryInterface }) => export const up: Migration = async ({ context: queryInterface }) =>
queryInterface.addColumn("users", "role", { queryInterface.addColumn("users", "role", {
type: DataTypes.STRING, type: DataTypes.STRING,
defaultValue: "user" defaultValue: "user"
}) })
export const down: Migration = async ({ context: queryInterface }) => export const down: Migration = async ({ context: queryInterface }) =>
queryInterface.removeColumn("users", "role") queryInterface.removeColumn("users", "role")

View file

@ -3,32 +3,32 @@ import { DataTypes } from "sequelize"
import type { Migration } from "../database" import type { Migration } from "../database"
export const up: Migration = async ({ context: queryInterface }) => export const up: Migration = async ({ context: queryInterface }) =>
queryInterface.createTable("posts", { queryInterface.createTable("posts", {
id: { id: {
type: DataTypes.UUID, type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4, defaultValue: DataTypes.UUIDV4,
primaryKey: true, primaryKey: true,
unique: true unique: true
}, },
title: { title: {
type: DataTypes.STRING, type: DataTypes.STRING
}, },
visibility: { visibility: {
type: DataTypes.STRING, type: DataTypes.STRING
}, },
password: { password: {
type: DataTypes.STRING, type: DataTypes.STRING
}, },
createdAt: { createdAt: {
type: DataTypes.DATE, type: DataTypes.DATE
}, },
updatedAt: { updatedAt: {
type: DataTypes.DATE, type: DataTypes.DATE
}, },
deletedAt: { deletedAt: {
type: DataTypes.DATE, type: DataTypes.DATE
} }
}) })
export const down: Migration = async ({ context: queryInterface }) => export const down: Migration = async ({ context: queryInterface }) =>
await queryInterface.dropTable("posts") await queryInterface.dropTable("posts")

View file

@ -3,57 +3,56 @@ import { DataTypes } from "sequelize"
import type { Migration } from "../database" import type { Migration } from "../database"
export const up: Migration = async ({ context: queryInterface }) => export const up: Migration = async ({ context: queryInterface }) =>
queryInterface.createTable("files", { queryInterface.createTable("files", {
id: { id: {
type: DataTypes.UUID, type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4, defaultValue: DataTypes.UUIDV4,
primaryKey: true, primaryKey: true,
allowNull: false, allowNull: false,
unique: true unique: true
}, },
title: { title: {
type: DataTypes.STRING, type: DataTypes.STRING
}, },
content: { content: {
type: DataTypes.STRING, type: DataTypes.STRING
}, },
sha: { sha: {
type: DataTypes.STRING, type: DataTypes.STRING
},
}, html: {
html: { type: DataTypes.STRING
type: DataTypes.STRING, },
}, createdAt: {
createdAt: { type: DataTypes.DATE
type: DataTypes.DATE, },
}, updatedAt: {
updatedAt: { type: DataTypes.DATE
type: DataTypes.DATE, },
}, deletedAt: {
deletedAt: { type: DataTypes.DATE
type: DataTypes.DATE, },
}, userId: {
userId: { type: DataTypes.UUID,
type: DataTypes.UUID, allowNull: false,
allowNull: false, references: {
references: { model: "users",
model: "users", key: "id"
key: "id" },
}, onDelete: "SET NULL",
onDelete: "SET NULL", onUpdate: "CASCADE"
onUpdate: "CASCADE" },
}, postId: {
postId: { type: DataTypes.UUID,
type: DataTypes.UUID, allowNull: false,
allowNull: false, references: {
references: { model: "posts",
model: "posts", key: "id"
key: "id" },
}, onDelete: "SET NULL",
onDelete: "SET NULL", onUpdate: "CASCADE"
onUpdate: "CASCADE" }
} })
})
export const down: Migration = async ({ context: queryInterface }) => export const down: Migration = async ({ context: queryInterface }) =>
await queryInterface.dropTable("files") await queryInterface.dropTable("files")

View file

@ -3,39 +3,39 @@ import { DataTypes } from "sequelize"
import type { Migration } from "../database" import type { Migration } from "../database"
export const up: Migration = async ({ context: queryInterface }) => export const up: Migration = async ({ context: queryInterface }) =>
queryInterface.createTable("post_authors", { queryInterface.createTable("post_authors", {
id: { id: {
type: DataTypes.UUID, type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4, defaultValue: DataTypes.UUIDV4,
primaryKey: true, primaryKey: true
}, },
createdAt: { createdAt: {
type: DataTypes.DATE, type: DataTypes.DATE
}, },
updatedAt: { updatedAt: {
type: DataTypes.DATE, type: DataTypes.DATE
}, },
postId: { postId: {
type: DataTypes.UUID, type: DataTypes.UUID,
primaryKey: true, primaryKey: true,
references: { references: {
model: "posts", model: "posts",
key: "id" key: "id"
}, },
onDelete: "CASCADE", onDelete: "CASCADE",
onUpdate: "CASCADE" onUpdate: "CASCADE"
}, },
userId: { userId: {
type: DataTypes.UUID, type: DataTypes.UUID,
primaryKey: true, primaryKey: true,
references: { references: {
model: "users", model: "users",
key: "id" key: "id"
}, },
onDelete: "CASCADE", onDelete: "CASCADE",
onUpdate: "CASCADE" onUpdate: "CASCADE"
} }
}) })
export const down: Migration = async ({ context: queryInterface }) => export const down: Migration = async ({ context: queryInterface }) =>
await queryInterface.dropTable("post_authors") await queryInterface.dropTable("post_authors")

View file

@ -1,115 +1,114 @@
import isAdmin from "@lib/middleware/is-admin"; import isAdmin from "@lib/middleware/is-admin"
import { Post } from "@lib/models/Post"; import { Post } from "@lib/models/Post"
import { User } from "@lib/models/User"; import { User } from "@lib/models/User"
import { File } from "@lib/models/File"; import { File } from "@lib/models/File"
import { Router } from "express"; import { Router } from "express"
export const admin = Router() export const admin = Router()
admin.use(isAdmin) admin.use(isAdmin)
admin.get("/is-admin", async (req, res) => { admin.get("/is-admin", async (req, res) => {
return res.json({ return res.json({
isAdmin: true isAdmin: true
}) })
}) })
admin.get("/users", async (req, res, next) => { admin.get("/users", async (req, res, next) => {
try { try {
const users = await User.findAll({ const users = await User.findAll({
attributes: { attributes: {
exclude: ["password"], exclude: ["password"],
include: ["id", "username", "createdAt", "updatedAt"] include: ["id", "username", "createdAt", "updatedAt"]
}, },
include: [ include: [
{ {
model: Post, model: Post,
as: "posts", as: "posts",
attributes: ["id"] attributes: ["id"]
} }
], ]
}) })
res.json(users) res.json(users)
} catch (e) { } catch (e) {
next(e) next(e)
} }
}) })
admin.get("/posts", async (req, res, next) => { admin.get("/posts", async (req, res, next) => {
try { try {
const posts = await Post.findAll({ const posts = await Post.findAll({
attributes: { attributes: {
exclude: ["content"], exclude: ["content"],
include: ["id", "title", "visibility", "createdAt"] include: ["id", "title", "visibility", "createdAt"]
}, },
include: [ include: [
{ {
model: File, model: File,
as: "files", as: "files",
attributes: ["id", "title", "createdAt", "html"] attributes: ["id", "title", "createdAt", "html"]
}, },
{ {
model: User, model: User,
as: "users", as: "users",
attributes: ["id", "username"] attributes: ["id", "username"]
} }
] ]
}) })
res.json(posts) res.json(posts)
} catch (e) { } catch (e) {
next(e) next(e)
} }
}) })
admin.get("/post/:id", async (req, res, next) => { admin.get("/post/:id", async (req, res, next) => {
try { try {
const post = await Post.findByPk(req.params.id, { const post = await Post.findByPk(req.params.id, {
attributes: { attributes: {
exclude: ["content"], exclude: ["content"],
include: ["id", "title", "visibility", "createdAt"] include: ["id", "title", "visibility", "createdAt"]
}, },
include: [ include: [
{ {
model: File, model: File,
as: "files", as: "files",
attributes: ["id", "title", "sha", "createdAt", "updatedAt", "html"] attributes: ["id", "title", "sha", "createdAt", "updatedAt", "html"]
}, },
{ {
model: User, model: User,
as: "users", as: "users",
attributes: ["id", "username"] attributes: ["id", "username"]
} }
] ]
}) })
if (!post) { if (!post) {
return res.status(404).json({ return res.status(404).json({
message: "Post not found" message: "Post not found"
}) })
} }
res.json(post) res.json(post)
} catch (e) { } catch (e) {
next(e) next(e)
} }
}) })
admin.delete("/post/:id", async (req, res, next) => { admin.delete("/post/:id", async (req, res, next) => {
try { try {
const post = await Post.findByPk(req.params.id) const post = await Post.findByPk(req.params.id)
if (!post) { if (!post) {
return res.status(404).json({ return res.status(404).json({
message: "Post not found" message: "Post not found"
}) })
} }
if (post.files?.length) if (post.files?.length)
await Promise.all(post.files.map((file) => file.destroy())) await Promise.all(post.files.map((file) => file.destroy()))
await post.destroy({ force: true }) await post.destroy({ force: true })
res.json({ res.json({
message: "Post deleted" message: "Post deleted"
}) })
} catch (e) { } catch (e) {
next(e) next(e)
} }
}) })

View file

@ -68,7 +68,10 @@ auth.post(
const user = { const user = {
username: username as string, username: username as string,
password: await hash(req.body.password, salt), 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) const created_user = await User.create(user)

View file

@ -80,7 +80,7 @@ posts.post(
.update(file.content) .update(file.content)
.digest("hex") .digest("hex")
.toString(), .toString(),
html: html || '', html: html || "",
userId: req.body.userId, userId: req.body.userId,
postId: newPost.id postId: newPost.id
}) })
@ -183,9 +183,7 @@ posts.get(
{ "$files.title$": { [Op.like]: `%${q}%` } }, { "$files.title$": { [Op.like]: `%${q}%` } },
{ "$files.content$": { [Op.like]: `%${q}%` } } { "$files.content$": { [Op.like]: `%${q}%` } }
], ],
[Op.and]: [ [Op.and]: [{ "$users.id$": req.user?.id || "" }]
{ "$users.id$": req.user?.id || "" },
]
}, },
include: [ include: [
{ {
@ -195,7 +193,7 @@ posts.get(
}, },
{ {
model: User, model: User,
as: "users", as: "users"
} }
], ],
attributes: ["id", "title", "visibility", "createdAt", "deletedAt"], 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" }) return res.status(404).json({ error: "Post not found" })
} }
if (req.user?.id !== post.users![0].id) { if (req.user?.id !== post.users![0].id) {
return res.status(403).json({ error: "Forbidden" }) return res.status(403).json({ error: "Forbidden" })
} }

View file

@ -2,9 +2,9 @@ import { createServer } from "http"
import { app } from "./app" import { app } from "./app"
import config from "./lib/config" import config from "./lib/config"
import "./database" import "./database"
; (async () => { ;(async () => {
// await sequelize.sync() // await sequelize.sync()
createServer(app).listen(config.port, () => createServer(app).listen(config.port, () =>
console.info(`Server running on port ${config.port}`) console.info(`Server running on port ${config.port}`)
) )
})() })()