This is way too much time to spend on linters

This commit is contained in:
Gender Shrapnel 2020-08-24 04:08:11 +02:00
parent 69ebed5c7e
commit 0c880a7b4e
23 changed files with 1748 additions and 200 deletions

View File

@ -5,7 +5,10 @@ module.exports = {
}, },
extends: [ extends: [
"plugin:react/recommended", "plugin:react/recommended",
"standard" "standard",
"prettier",
"prettier/@typescript-eslint",
"prettier/react"
], ],
parser: "@typescript-eslint/parser", parser: "@typescript-eslint/parser",
parserOptions: { parserOptions: {
@ -25,25 +28,21 @@ module.exports = {
} }
}, },
rules: { rules: {
"space-before-function-paren": ["error", { "no-unused-vars": ["error", {
anonymous: "always", "vars": "all",
named: "never", "args": "after-used"
asyncArrow: "always"
}], }],
"quote-props": ["error", "consistent"],
"quotes": ["error", "double"],
"sort-imports": ["error", {
"ignoreCase": true,
"allowSeparatedGroups": true
}]
}, },
overrides: [ overrides: [
{ {
files: ["*.ts", "*.tsx"], files: ["*.ts", "*.tsx"],
rules: { rules: {
"no-undef": "off",
"no-unused-vars": "off", "no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-unused-vars-experimental": "error" "@typescript-eslint/no-unused-vars-experimental": ["error", {
"ignoreArgsIfArgsAfterAreUsed": true
}]
} }
} }
] ]

2
.prettierignore Normal file
View File

@ -0,0 +1,2 @@
dist
node_packages

3
.prettierrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"quoteProps": "consistent"
}

1640
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -56,10 +56,23 @@
"@types/react-router-dom": "^5.1.3", "@types/react-router-dom": "^5.1.3",
"@types/webpack-dev-middleware": "^2.0.4", "@types/webpack-dev-middleware": "^2.0.4",
"@types/yargs": "^13.0.8", "@types/yargs": "^13.0.8",
"@typescript-eslint/eslint-plugin": "^3.9.1",
"@typescript-eslint/parser": "^3.9.1",
"css-loader": "^3.4.2", "css-loader": "^3.4.2",
"eslint": "^7.7.0",
"eslint-config-prettier": "^6.11.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.20.6",
"eslint-plugin-standard": "^4.0.1",
"html-webpack-plugin": "^4.3.0",
"object-hash": "^2.0.3", "object-hash": "^2.0.3",
"pg-monitor": "^1.3.1", "pg-monitor": "^1.3.1",
"pino-pretty": "^3.6.1", "pino-pretty": "^3.6.1",
"prettier": "2.0.5",
"prettier-plugin-organize-imports": "^1.1.1",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-redux": "^7.2.0", "react-redux": "^7.2.0",
@ -72,8 +85,6 @@
"ts-node": "^8.8.1", "ts-node": "^8.8.1",
"ts-node-dev": "^1.0.0-pre.44", "ts-node-dev": "^1.0.0-pre.44",
"tsconfig-paths": "^3.9.0", "tsconfig-paths": "^3.9.0",
"tslint": "^5.20.1",
"tslint-config-prettier": "^1.18.0",
"typescript": "^4.0.2", "typescript": "^4.0.2",
"webpack": "^4.44.1", "webpack": "^4.44.1",
"webpack-cli": "^3.3.12", "webpack-cli": "^3.3.12",

View File

@ -18,7 +18,7 @@ export const getTasks = async (
throw new APIError("Invalid response from server."); throw new APIError("Invalid response from server.");
} }
const count: number = json.count; const count: number = json.count;
const items: Task[] = (json.tasks as any[]).map(t => { const items: Task[] = (json.tasks as any[]).map((t) => {
if ( if (
typeof t !== "object" || typeof t !== "object" ||
!Number.isSafeInteger(t.id) || !Number.isSafeInteger(t.id) ||
@ -30,7 +30,7 @@ export const getTasks = async (
return { return {
id: t.id, id: t.id,
name: t.name, name: t.name,
schedule: { type: "Plain" } schedule: { type: "Plain" },
}; };
}); });

View File

@ -2,24 +2,33 @@ import * as React from "react";
import { BrowserRouter as Router, Link, Route, Switch } from "react-router-dom"; import { BrowserRouter as Router, Link, Route, Switch } from "react-router-dom";
import TaskList from "./TaskList"; import TaskList from "./TaskList";
export default () => <Router> function App() {
<div> return (
<nav> <Router>
<ul> <div>
<li><a href="/auth">Login</a></li> <nav>
<li><Link to="/about">About</Link></li> <ul>
<li><Link to="/">Tasks</Link></li> <li>
</ul> <a href="/auth">Login</a>
</nav> </li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/">Tasks</Link>
</li>
</ul>
</nav>
<Switch> <Switch>
<Route path="/about"> <Route path="/about">About things, yay!</Route>
About things, yay! <Route path="/">
</Route> <TaskList />
<Route path="/"> </Route>
<TaskList/> </Switch>
</Route> </div>
</Switch> </Router>
</div> );
</Router> }
export default App

View File

@ -4,7 +4,7 @@ import { Task, TaskScheduleType } from "@kredens/frontend/store/tasks/types";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
export default () => { function TaskList() {
const [taskName, setTaskName] = useState(""); const [taskName, setTaskName] = useState("");
const tasks = useSelector<AppState, { [key: string]: Task }>(state => state.tasks.items); const tasks = useSelector<AppState, { [key: string]: Task }>(state => state.tasks.items);
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -37,3 +37,5 @@ export default () => {
</> </>
); );
} }
export default TaskList

View File

@ -1,15 +0,0 @@
{
"extends": "../../tslint.json",
"rules": {
"no-implicit-dependencies": [
true,
"dev",
["@kredens/frontend"]
],
"no-submodule-imports": [
true,
"@kredens/frontend",
"redux-saga/effects"
]
}
}

View File

@ -24,7 +24,7 @@ export const getUser = async (req: express.Request) =>
export const authMiddleware: () => express.Handler = () => async ( export const authMiddleware: () => express.Handler = () => async (
req, req,
res, _res,
next next
) => { ) => {
if (req.session.userID) { if (req.session.userID) {

View File

@ -18,7 +18,7 @@ import {
decodeBase64, decodeBase64,
decodeUTF8, decodeUTF8,
encodeBase64, encodeBase64,
encodeUTF8 encodeUTF8,
} from "tweetnacl-util"; } from "tweetnacl-util";
const secret = decodeBase64(process.env.SECRET); const secret = decodeBase64(process.env.SECRET);

View File

@ -18,7 +18,7 @@ import {
MigrationRepository, MigrationRepository,
SessionRepository, SessionRepository,
TaskRepository, TaskRepository,
UserRepository UserRepository,
} from "@kredens/server/db/repos"; } from "@kredens/server/db/repos";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import pg from "pg"; import pg from "pg";
@ -31,19 +31,19 @@ types.setTypeParser(types.builtins.TIMESTAMP, DateTime.fromSQL);
type ExtendedProtocol = IDatabase<Extensions> & Extensions; type ExtendedProtocol = IDatabase<Extensions> & Extensions;
const initOptions: IInitOptions<Extensions> = { const initOptions: IInitOptions<Extensions> = {
extend(obj: ExtendedProtocol, dc: any) { extend(obj: ExtendedProtocol) {
obj.migrations = new MigrationRepository(obj, pgp); obj.migrations = new MigrationRepository(obj, pgp);
obj.tasks = new TaskRepository(obj, pgp); obj.tasks = new TaskRepository(obj, pgp);
obj.users = new UserRepository(obj, pgp); obj.users = new UserRepository(obj, pgp);
obj.sessions = new SessionRepository(obj, pgp); obj.sessions = new SessionRepository(obj, pgp);
} },
}; };
const pgp: pgPromise.IMain = pgPromise(initOptions); const pgp: pgPromise.IMain = pgPromise(initOptions);
if (process.env.NODE_ENV !== "production") { if (process.env.NODE_ENV !== "production") {
// tslint:disable-next-line:no-implicit-dependencies // tslint:disable-next-line:no-implicit-dependencies
import("pg-monitor").then(monitor => monitor.attach(initOptions)); import("pg-monitor").then((monitor) => monitor.attach(initOptions));
} }
const db: ExtendedProtocol = pgp(process.env.DATABASE_URL); const db: ExtendedProtocol = pgp(process.env.DATABASE_URL);

View File

@ -24,7 +24,7 @@ export class LockError extends Error {}
export class MigrationRepository { export class MigrationRepository {
private db: IDatabase<any>; private db: IDatabase<any>;
constructor(db: IDatabase<any>, pgp: IMain) { constructor(db: IDatabase<any>, _pgp: IMain) {
this.db = db; this.db = db;
} }
@ -45,15 +45,15 @@ export class MigrationRepository {
public async apply() { public async apply() {
await this.lock(); await this.lock();
const applied = (await this.applied()).map(m => m.name); const applied = (await this.applied()).map((m) => m.name);
const toApply = sql.patches.filter( const toApply = sql.patches.filter(
p => p.up.isSome() && !applied.find(o => o === p.name) (p) => p.up.isSome() && !applied.find((o) => o === p.name)
); );
for (const patch of toApply) { for (const patch of toApply) {
logger.info("Applying migration", { name: patch.name }); logger.info("Applying migration", { name: patch.name });
await patch.up await patch.up
.map(async qf => { .map(async (qf) => {
await this.db.none(qf); await this.db.none(qf);
await this.db.none(sql.apply, [patch.name]); await this.db.none(sql.apply, [patch.name]);
}) })
@ -63,10 +63,10 @@ export class MigrationRepository {
} }
public async applied(): Promise<Migration[]> { public async applied(): Promise<Migration[]> {
return this.db.map(sql.applied, [], row => ({ return this.db.map(sql.applied, [], (row) => ({
appliedAt: row.applied_at as DateTime, appliedAt: row.applied_at as DateTime,
id: +row.id, id: +row.id,
name: row.name name: row.name,
})); }));
} }
} }

View File

@ -22,14 +22,14 @@ import { IDatabase, IMain } from "pg-promise";
export class SessionRepository { export class SessionRepository {
private db: IDatabase<any>; private db: IDatabase<any>;
constructor(db: IDatabase<any>, pgp: IMain) { constructor(db: IDatabase<any>, _pgp: IMain) {
this.db = db; this.db = db;
} }
public async all(): Promise<Session[]> { public async all(): Promise<Session[]> {
return this.db.map(sql.all, [], row => ({ return this.db.map(sql.all, [], (row) => ({
session: row.session, session: row.session,
sid: row.sid sid: row.sid,
})); }));
} }
@ -42,25 +42,25 @@ export class SessionRepository {
} }
public async get(sid: string): Promise<Maybe<Session>> { public async get(sid: string): Promise<Maybe<Session>> {
return this.db.oneOrNone(sql.get, [sid]).then(row => return this.db.oneOrNone(sql.get, [sid]).then((row) =>
row row
? Maybe.Some({ ? Maybe.Some({
session: row.session, session: row.session,
sid: row.sid sid: row.sid,
}) })
: Maybe.None() : Maybe.None()
); );
} }
public async length(): Promise<number> { public async length(): Promise<number> {
return this.db.one(sql.length).then(row => +row.length); return this.db.one(sql.length).then((row) => +row.length);
} }
public async set(sid: string, session: SessionData, expiresAt: DateTime) { public async set(sid: string, session: SessionData, expiresAt: DateTime) {
return this.db.none(sql.set, [ return this.db.none(sql.set, [
sid, sid,
JSON.stringify(session), JSON.stringify(session),
expiresAt.toSQL() expiresAt.toSQL(),
]); ]);
} }

View File

@ -13,7 +13,7 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
import { ScheduleType, Task } from "@kredens/server/db/models"; import { Task } from "@kredens/server/db/models";
import { tasks as sql } from "@kredens/server/db/sql"; import { tasks as sql } from "@kredens/server/db/sql";
import { IDatabase, IMain } from "pg-promise"; import { IDatabase, IMain } from "pg-promise";
@ -24,14 +24,14 @@ function rowToTask(row: any): Task {
name: row.name, name: row.name,
notes: row.notes, notes: row.notes,
schedule: row.schedule, schedule: row.schedule,
createdAt: row.created_at createdAt: row.created_at,
}; };
} }
export class TaskRepository { export class TaskRepository {
private db: IDatabase<any>; private db: IDatabase<any>;
constructor(db: IDatabase<any>, pgp: IMain) { constructor(db: IDatabase<any>, _pgp: IMain) {
this.db = db; this.db = db;
} }
@ -40,12 +40,12 @@ export class TaskRepository {
limit?: number, limit?: number,
offset?: number offset?: number
): Promise<Task[]> { ): Promise<Task[]> {
return this.db.map(sql.list, [owner, limit || 10, offset || 0], row => return this.db.map(sql.list, [owner, limit || 10, offset || 0], (row) =>
rowToTask(row) rowToTask(row)
); );
} }
public async count(owner: number): Promise<number> { public async count(owner: number): Promise<number> {
return this.db.one(sql.count, [owner], row => +row.c); return this.db.one(sql.count, [owner], (row) => +row.c);
} }
} }

View File

@ -22,16 +22,16 @@ import { IDatabase, IMain } from "pg-promise";
export class UserRepository { export class UserRepository {
private db: IDatabase<any>; private db: IDatabase<any>;
constructor(db: IDatabase<any>, pgp: IMain) { constructor(db: IDatabase<any>, _pgp: IMain) {
this.db = db; this.db = db;
} }
public async login(email: string, password: string): Promise<Maybe<number>> { public async login(email: string, password: string): Promise<Maybe<number>> {
const { id, encryptedPassword } = await this.db const { id, encryptedPassword } = await this.db
.oneOrNone(sql.login, [email]) .oneOrNone(sql.login, [email])
.then(user => ({ .then((user) => ({
encryptedPassword: user.encrypted_password, encryptedPassword: user.encrypted_password,
id: +user.id id: +user.id,
})); }));
if (id === null) { if (id === null) {
return None(); return None();

View File

@ -13,34 +13,34 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
import { requireAuthMiddleware } from "@kredens/server/auth"; import { db } from "@kredens/server/db"
import { db } from "@kredens/server/db"; import express from "express"
import express from "express"; import { requireAuthMiddleware } from "@kredens/server/auth"
const router = express.Router(); const router = express.Router()
function tryInt(v: string, def?: number): number | undefined { function tryInt(v: string, def?: number): number | undefined {
if (v) { if (v) {
const value = parseInt(v, 10); const value = parseInt(v, 10)
return isNaN(value) ? def : value; return isNaN(value) ? def : value
} }
return def; return def
} }
router.use(requireAuthMiddleware()); router.use(requireAuthMiddleware())
router.get("/tasks", async (req, res, next) => { router.get("/tasks", async (req, res) => {
const limit = tryInt(req.query.limit, 10); const limit = tryInt(req.query.limit, 10)
const offset = tryInt(req.query.offset, 0); const offset = tryInt(req.query.offset, 0)
const result = await db.tx(async tx => { const result = await db.tx(async tx => {
const tasks = await tx.tasks.list(req.user.id, limit, offset); const tasks = await tx.tasks.list(req.user.id, limit, offset)
const count = await tx.tasks.count(req.user.id); const count = await tx.tasks.count(req.user.id)
return { return {
tasks, tasks,
count count
}; }
}); })
res.json({ res.json({
tasks: result.tasks.map(t => ({ tasks: result.tasks.map(t => ({
@ -51,7 +51,7 @@ router.get("/tasks", async (req, res, next) => {
createdAt: t.createdAt.toISO createdAt: t.createdAt.toISO
})), })),
count: result.count count: result.count
}); })
}); })
export default router; export default router

View File

@ -18,11 +18,11 @@ import express from "express";
const router = express.Router(); const router = express.Router();
router.get("/", async (req, res, next) => { router.get("/", async (req, res) => {
res.render("login"); res.render("login");
}); });
router.post("/", async (req, res, next) => { router.post("/", async (req, res) => {
const userID = await db.users.login(req.body.email, req.body.password); const userID = await db.users.login(req.body.email, req.body.password);
if (userID.isSome()) { if (userID.isSome()) {
req.session.userID = userID.some(); req.session.userID = userID.some();

View File

@ -13,43 +13,43 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
import { box, unbox } from "@kredens/server/crypto"; import { box, unbox } from "@kredens/server/crypto"
import { db } from "@kredens/server/db"; import createHttpError from "http-errors"
import express from "express"; import { DateTime } from "luxon"
import createHttpError from "http-errors"; import { db } from "@kredens/server/db"
import { DateTime } from "luxon"; import express from "express"
interface Token { interface Token {
expires: string; expires: string;
} }
const router = express.Router(); const router = express.Router()
router.get("/", async (req, res, next) => { router.get("/", async (req) => {
const token: Token = { const token: Token = {
expires: DateTime.local() expires: DateTime.local()
.plus({ hours: 2 }) .plus({ hours: 2 })
.toISO() .toISO()
}; }
req.log.info("Token issued", { token: box(token) }); req.log.info("Token issued", { token: box(token) })
}); })
router.post("/", async (req, res, next) => { router.post("/", async (req, res, next) => {
const token: Token = unbox(req.body.token); const token: Token = unbox(req.body.token)
const expired = DateTime.fromISO(token.expires).diffNow(); const expired = DateTime.fromISO(token.expires).diffNow()
if (expired.as("milliseconds") < 0) { if (expired.as("milliseconds") < 0) {
next(createHttpError(401)); next(createHttpError(401))
return; return
} }
const email: string = req.body.email; const email: string = req.body.email
const password: string = req.body.password; const password: string = req.body.password
if (!email || !password || password.length < 8) { if (!email || !password || password.length < 8) {
res.send("Please provide an email and a password longer than 8 characters"); res.send("Please provide an email and a password longer than 8 characters")
return; return
} }
await db.users.create(email, password); await db.users.create(email, password)
}); })
export default router; export default router

View File

@ -17,7 +17,7 @@ import express from "express";
const router = express.Router(); const router = express.Router();
router.get("/", (req, res, next) => { router.get("/", (req, res) => {
res.render("index", { title: "Hey", message: "Hi!" }); res.render("index", { title: "Hey", message: "Hi!" });
}); });

View File

@ -13,9 +13,9 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Store } from "@holdyourwaffle/express-session";
import { db } from "@kredens/server/db";
import { DateTime, Duration } from "luxon"; import { DateTime, Duration } from "luxon";
import { db } from "@kredens/server/db";
import { Store } from "@holdyourwaffle/express-session";
export class PgStore extends Store { export class PgStore extends Store {
private ttl: Duration; private ttl: Duration;
@ -30,8 +30,8 @@ export class PgStore extends Store {
) { ) {
db.sessions db.sessions
.get(sid) .get(sid)
.then(s => cb(null, s.map(ss => ss.session).orNull())) .then((s) => cb(null, s.map((ss) => ss.session).orNull()))
.catch(r => cb(r, null)); .catch((r) => cb(r, null));
} }
public set(sid: string, session: SessionData, cb?: (err?: any) => void) { public set(sid: string, session: SessionData, cb?: (err?: any) => void) {
@ -39,7 +39,7 @@ export class PgStore extends Store {
const p = db.sessions.set(sid, session, expiresAt); const p = db.sessions.set(sid, session, expiresAt);
if (cb) { if (cb) {
p.then(s => cb(null)).catch(r => cb(r)); p.then(() => cb(null)).catch((r) => cb(r));
} }
} }
@ -47,7 +47,7 @@ export class PgStore extends Store {
const p = db.sessions.destroy(sid); const p = db.sessions.destroy(sid);
if (cb) { if (cb) {
p.then(s => cb()).catch(r => cb(r)); p.then(() => cb()).catch((r) => cb(r));
} }
} }
@ -56,7 +56,7 @@ export class PgStore extends Store {
) { ) {
db.sessions db.sessions
.all() .all()
.then(ss => { .then((ss) => {
const sessions: { [sid: string]: SessionData } = {}; const sessions: { [sid: string]: SessionData } = {};
for (const s of ss) { for (const s of ss) {
@ -65,27 +65,27 @@ export class PgStore extends Store {
cb(null, sessions); cb(null, sessions);
}) })
.catch(r => cb(r, null)); .catch((r) => cb(r, null));
} }
public length(cb: (err: any, length: number) => void) { public length(cb: (err: any, length: number) => void) {
db.sessions db.sessions
.length() .length()
.then(l => cb(null, l)) .then((l) => cb(null, l))
.catch(r => cb(r, 0)); .catch((r) => cb(r, 0));
} }
public clear(cb?: (err?: any) => void) { public clear(cb?: (err?: any) => void) {
const p = db.sessions.clear(); const p = db.sessions.clear();
if (cb) { if (cb) {
p.then(() => cb()).catch(r => cb(r)); p.then(() => cb()).catch((r) => cb(r));
} }
} }
public touch(sid: string, session: SessionData, cb?: (err?: any) => void) { public touch(sid: string, session: SessionData, cb?: (err?: any) => void) {
const p = db.sessions.touch(sid, this.getExpiresAt(session)); const p = db.sessions.touch(sid, this.getExpiresAt(session));
if (cb) { if (cb) {
p.then(() => cb()).catch(r => cb(r)); p.then(() => cb()).catch((r) => cb(r));
} }
} }

View File

@ -1,13 +0,0 @@
{
"extends": "../../tslint.json",
"rules": {
"no-implicit-dependencies": [
true,
["@kredens/server"]
],
"no-submodule-imports": [
true,
"@kredens/server"
]
}
}

View File

@ -1,5 +1,5 @@
import path from "path"; import path from "path";
import webpack from "webpack"; // tslint:disable-line:no-implicit-dependencies import webpack from "webpack";
const config: webpack.Configuration = { const config: webpack.Configuration = {
mode: "development", mode: "development",
@ -8,28 +8,28 @@ const config: webpack.Configuration = {
output: { output: {
filename: "[name].bundle.js", filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist"), path: path.resolve(__dirname, "dist"),
publicPath: "/assets/" publicPath: "/assets/",
}, },
module: { module: {
rules: [ rules: [
{ {
exclude: /node_modules/, exclude: /node_modules/,
loader: "ts-loader", loader: "ts-loader",
test: /\.tsx?$/ test: /\.tsx?$/,
} },
] ],
}, },
resolve: { resolve: {
extensions: [".ts", ".tsx", ".js"], extensions: [".ts", ".tsx", ".js"],
alias: { alias: {
"@kredens": path.resolve(__dirname, "src/") "@kredens": path.resolve(__dirname, "src/"),
} },
}, },
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
API_URL: JSON.stringify("http://localhost:3000/api/") API_URL: JSON.stringify("http://localhost:3000/api/"),
}) }),
] ],
}; };
export default config; export default config;