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:
parent
fd264d3d41
commit
ab791efe42
@ -1 +0,0 @@
|
|||||||
SELECT COUNT(*) > 0 r FROM "tasks" WHERE owner = $1 AND "id" > $2;
|
|
@ -1 +0,0 @@
|
|||||||
SELECT COUNT(*) > 0 r FROM "tasks" WHERE owner = $1 AND "id" < $2;
|
|
@ -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;
|
||||||
|
@ -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;
|
|
@ -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) {
|
||||||
next(createHttpError(401));
|
if (redirect) {
|
||||||
|
res.redirect(redirect);
|
||||||
|
} else {
|
||||||
|
next(createHttpError(401));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
next();
|
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
57
src/server/routes/api.ts
Normal 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;
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user