A simple basic API

Included: removal of remains of GraphQL adaptation. IMO the after/before
behaviour is actually pretty badly defined if entities can be deleted, and
delivers complexity trying to fix behaviour that users generally expect
and are comfortable coping with. Of course special cases _do_ justify
going extra length to provide consistent pagination.
This commit is contained in:
Gender Shrapnel 2019-10-27 00:24:58 +02:00
parent fd264d3d41
commit ab791efe42
10 changed files with 81 additions and 57 deletions

View File

@ -1 +0,0 @@
SELECT COUNT(*) > 0 r FROM "tasks" WHERE owner = $1 AND "id" > $2;

View File

@ -1 +0,0 @@
SELECT COUNT(*) > 0 r FROM "tasks" WHERE owner = $1 AND "id" < $2;

View File

@ -1,14 +1,14 @@
SELECT SELECT
id, id,
owner,
name, name,
notes, notes,
schedule, schedule,
min_frequency,
max_frequency,
created_at created_at
FROM FROM
tasks tasks
WHERE WHERE
owner = $1 AND ($2 IS NULL OR id > $2) AND ($3 IS NULL OR id < $3) owner = $1
ORDER BY created_at ASC ORDER BY created_at ASC, id ASC
LIMIT $4; LIMIT $2 OFFSET $3;

View File

@ -1,14 +0,0 @@
SELECT
id,
name,
notes,
schedule,
min_frequency,
max_frequency,
created_at
FROM
tasks
WHERE
owner = $1 AND ($2 IS NULL OR id > $2) AND ($3 IS NULL OR id < $3)
ORDER BY created_at DESC
LIMIT $4;

View File

@ -40,10 +40,16 @@ export const authMiddleware: () => express.Handler = () => async (
next(); next();
}; };
export const requireAuthMiddleware: express.Handler = (req, res, next) => { export const requireAuthMiddleware: (redirect?: string) => express.Handler = (
redirect = null
) => (req, res, next) => {
if (!req.user) { if (!req.user) {
if (redirect) {
res.redirect(redirect);
} else {
next(createHttpError(401)); next(createHttpError(401));
} }
} else {
next(); next();
}
}; };

View File

@ -39,9 +39,7 @@ export interface Task {
owner: number; owner: number;
name: string; name: string;
notes?: string; notes?: string;
schedule: ScheduleType; schedule: object;
minFrequency?: number;
maxFrequency?: number;
createdAt: DateTime; createdAt: DateTime;
} }

View File

@ -23,9 +23,7 @@ function rowToTask(row: any): Task {
owner: +row.owner, owner: +row.owner,
name: row.name, name: row.name,
notes: row.notes, notes: row.notes,
schedule: row.schedule as ScheduleType, schedule: row.schedule,
minFrequency: +row.minFrequency,
maxFrequency: +row.maxFrequency,
createdAt: row.created_at createdAt: row.created_at
}; };
} }
@ -40,32 +38,14 @@ export class TaskRepository {
public async list( public async list(
owner: number, owner: number,
limit?: number, limit?: number,
after?: number, offset?: number
before?: number
): Promise<Task[]> { ): Promise<Task[]> {
return this.db.map(sql.list, [owner, after, before, limit || 10], row => return this.db.map(sql.list, [owner, limit || 10, offset || 0], row =>
rowToTask(row) rowToTask(row)
); );
} }
public async listReverse( public async count(owner: number): Promise<number> {
owner: number, return this.db.one(sql.count, [owner], row => +row.c);
limit?: number,
after?: number,
before?: number
): Promise<Task[]> {
return this.db.map(
sql.listReverse,
[owner, after, before, limit || 10],
row => rowToTask(row)
);
}
public async hasNext(owner: number, after: number): Promise<boolean> {
return this.db.one(sql.hasNext, [owner, after]).then(row => row.r);
}
public async hasPrev(owner: number, before: number): Promise<boolean> {
return this.db.one(sql.hasPrev, [owner, before]).then(row => row.r);
} }
} }

View File

@ -45,10 +45,7 @@ const users = {
const tasks = { const tasks = {
count: sql("tasks/count.sql"), count: sql("tasks/count.sql"),
hasNext: sql("tasks/hasNext.sql"), list: sql("tasks/list.sql")
hasPrev: sql("tasks/hasPrev.sql"),
list: sql("tasks/list.sql"),
listReverse: sql("tasks/listReverse.sql")
}; };
const sessions = { const sessions = {

57
src/server/routes/api.ts Normal file
View File

@ -0,0 +1,57 @@
// Copyright (C) 2019 ModZero <modzero@modzero.xyz>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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/>.
import { requireAuthMiddleware } from "@kredens/server/auth";
import { db } from "@kredens/server/db";
import express from "express";
const router = express.Router();
function tryInt(v: string, def?: number): number | undefined {
if (v) {
const value = parseInt(v, 10);
return isNaN(value) ? def : value;
}
return def;
}
router.use(requireAuthMiddleware());
router.get("/tasks", async (req, res, next) => {
const limit = tryInt(req.query.limit, 10);
const offset = tryInt(req.query.offset, 0);
const result = await db.tx(async tx => {
const tasks = await tx.tasks.list(req.user.id, limit, offset);
const count = await tx.tasks.count(req.user.id);
return {
tasks,
count
};
});
res.json({
tasks: result.tasks.map(t => ({
id: t.id,
name: t.name,
notes: t.notes,
schedule: t.schedule,
createdAt: t.createdAt.toISO
})),
count: result.count
});
});
export default router;

View File

@ -14,12 +14,14 @@
// 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 express from "express"; import express from "express";
import apiRouter from "./api";
import authRouter from "./auth"; import authRouter from "./auth";
import homeRouter from "./home"; import homeRouter from "./home";
const router = express.Router({ strict: true }); const router = express.Router({ strict: true });
router.use("/auth$", authRouter); router.use("/auth$", authRouter);
router.use("/api/", apiRouter);
router.use("/*", homeRouter); router.use("/*", homeRouter);
export default router; export default router;