Tasks endpoint
This commit is contained in:
parent
9b7324e20a
commit
d53a3081c0
@ -26,6 +26,7 @@
|
|||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-pino-logger": "^4.0.0",
|
"express-pino-logger": "^4.0.0",
|
||||||
"graphql": "^14.5.7",
|
"graphql": "^14.5.7",
|
||||||
|
"graphql-tools": "^4.0.5",
|
||||||
"helmet": "^3.21.1",
|
"helmet": "^3.21.1",
|
||||||
"http-errors": "^1.7.3",
|
"http-errors": "^1.7.3",
|
||||||
"luxon": "^1.19.2",
|
"luxon": "^1.19.2",
|
||||||
|
1
sql/tasks/count.sql
Normal file
1
sql/tasks/count.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
SELECT count(*) c FROM "tasks" WHERE owner = $1;
|
1
sql/tasks/hasNext.sql
Normal file
1
sql/tasks/hasNext.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
SELECT COUNT(*) > 0 r FROM "tasks" WHERE owner = $1 AND "id" > $2;
|
1
sql/tasks/hasPrev.sql
Normal file
1
sql/tasks/hasPrev.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
SELECT COUNT(*) > 0 r FROM "tasks" WHERE owner = $1 AND "id" < $2;
|
@ -9,6 +9,6 @@ SELECT
|
|||||||
FROM
|
FROM
|
||||||
tasks
|
tasks
|
||||||
WHERE
|
WHERE
|
||||||
owner = $1
|
owner = $1 AND ($2 IS NULL OR id > $2) AND ($3 IS NULL OR id < $3)
|
||||||
ORDER BY created_at ASC
|
ORDER BY created_at ASC
|
||||||
LIMIT $2 OFFSET $3;
|
LIMIT $4;
|
||||||
|
14
sql/tasks/listReverse.sql
Normal file
14
sql/tasks/listReverse.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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;
|
127
src/api.ts
127
src/api.ts
@ -1,127 +0,0 @@
|
|||||||
// 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 { db } from "@kredens/db";
|
|
||||||
import { User } from "@kredens/db/models";
|
|
||||||
import { ApolloServer, AuthenticationError, gql } from "apollo-server-express";
|
|
||||||
import { Kind } from "graphql/language";
|
|
||||||
import { GraphQLScalarType, GraphQLScalarTypeConfig } from "graphql/type";
|
|
||||||
import { DateTime } from "luxon";
|
|
||||||
|
|
||||||
const typeDefs = gql`
|
|
||||||
type Query {
|
|
||||||
"A simple type for getting started"
|
|
||||||
hello: String
|
|
||||||
migrations: [Migration]!
|
|
||||||
user(id: ID): User
|
|
||||||
}
|
|
||||||
|
|
||||||
type Migration {
|
|
||||||
id: ID!
|
|
||||||
name: String!
|
|
||||||
applied_at: DateTime!
|
|
||||||
}
|
|
||||||
|
|
||||||
type User {
|
|
||||||
id: ID!
|
|
||||||
email: String!
|
|
||||||
tasks: [Task]!
|
|
||||||
}
|
|
||||||
|
|
||||||
type Task {
|
|
||||||
id: ID
|
|
||||||
name: String!
|
|
||||||
notes: String
|
|
||||||
schedule: ScheduleType!
|
|
||||||
min_frequency: Int
|
|
||||||
max_frequency: Int
|
|
||||||
created_at: DateTime!
|
|
||||||
}
|
|
||||||
|
|
||||||
scalar DateTime
|
|
||||||
|
|
||||||
enum ScheduleType {
|
|
||||||
ONCE
|
|
||||||
DAILY
|
|
||||||
WEEKLY
|
|
||||||
MONTHLY
|
|
||||||
YEARLY
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const dateTimeConfig: GraphQLScalarTypeConfig<DateTime, string> = {
|
|
||||||
description: "Date custom scalar type",
|
|
||||||
name: "DateTime",
|
|
||||||
parseValue(value) {
|
|
||||||
return DateTime.fromISO(value as string);
|
|
||||||
},
|
|
||||||
serialize(value) {
|
|
||||||
return (value as DateTime).toISO();
|
|
||||||
},
|
|
||||||
parseLiteral(ast) {
|
|
||||||
if (ast.kind === Kind.STRING) {
|
|
||||||
return DateTime.fromISO(ast.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolvers = {
|
|
||||||
DateTime: new GraphQLScalarType(dateTimeConfig),
|
|
||||||
Query: {
|
|
||||||
hello: () => `Hello, world!`,
|
|
||||||
migrations: () => db.migrations.applied(),
|
|
||||||
user: async (
|
|
||||||
parent: any,
|
|
||||||
{ id }: { id: string },
|
|
||||||
context: { user?: User }
|
|
||||||
) => {
|
|
||||||
if (!context.user || context.user.id !== +id) {
|
|
||||||
throw new AuthenticationError(
|
|
||||||
"You cannot query users other than yourself"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return db.users.details(+id).then(user => user.orNull());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
User: {
|
|
||||||
tasks: (user: User) =>
|
|
||||||
db.tasks.list(user.id).then(tasks =>
|
|
||||||
tasks.map(t => ({
|
|
||||||
...t,
|
|
||||||
schedule: t.schedule.toUpperCase()
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export function server() {
|
|
||||||
return new ApolloServer({
|
|
||||||
context: async req => {
|
|
||||||
const user = req.req.user;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
throw new AuthenticationError("you must be logged in");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
user
|
|
||||||
};
|
|
||||||
},
|
|
||||||
resolvers,
|
|
||||||
typeDefs
|
|
||||||
});
|
|
||||||
}
|
|
36
src/api/index.ts
Normal file
36
src/api/index.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// 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 resolvers from "@kredens/api/resolvers";
|
||||||
|
import typeDefs from "@kredens/api/typeDefs";
|
||||||
|
import { ApolloServer, AuthenticationError } from "apollo-server-express";
|
||||||
|
|
||||||
|
export function server() {
|
||||||
|
return new ApolloServer({
|
||||||
|
context: async req => {
|
||||||
|
const user = req.req.user;
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new AuthenticationError("you must be logged in");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
user
|
||||||
|
};
|
||||||
|
},
|
||||||
|
resolvers,
|
||||||
|
typeDefs
|
||||||
|
});
|
||||||
|
}
|
226
src/api/resolvers.ts
Normal file
226
src/api/resolvers.ts
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
// 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 { db } from "@kredens/db";
|
||||||
|
import * as models from "@kredens/db/models";
|
||||||
|
import { IResolvers } from "graphql-tools";
|
||||||
|
import { Kind } from "graphql/language";
|
||||||
|
import { GraphQLScalarType, GraphQLScalarTypeConfig } from "graphql/type";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
import { Maybe } from "monet";
|
||||||
|
|
||||||
|
const dateTimeConfig: GraphQLScalarTypeConfig<DateTime, string> = {
|
||||||
|
name: "DateTime",
|
||||||
|
description: "Date custom scalar type",
|
||||||
|
serialize(value) {
|
||||||
|
return (value as DateTime).toISO();
|
||||||
|
},
|
||||||
|
parseValue(value) {
|
||||||
|
return DateTime.fromISO(value as string);
|
||||||
|
},
|
||||||
|
parseLiteral(ast) {
|
||||||
|
if (ast.kind === Kind.STRING) {
|
||||||
|
return DateTime.fromISO(ast.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Context {
|
||||||
|
user?: models.User;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Node {
|
||||||
|
ID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface User extends Node {
|
||||||
|
email?: string;
|
||||||
|
tasks?: Connection<Task>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScheduleType = "ONCE" | "DAILY" | "WEEKLY" | "MONTHLY" | "YEARLY";
|
||||||
|
|
||||||
|
interface Task extends Node {
|
||||||
|
name: string;
|
||||||
|
notes?: string;
|
||||||
|
schedule: ScheduleType;
|
||||||
|
minFrequency?: number;
|
||||||
|
maxFrequency?: number;
|
||||||
|
createdAt: DateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PaginationArguments {
|
||||||
|
after?: string;
|
||||||
|
first?: number;
|
||||||
|
before?: string;
|
||||||
|
last?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageInfo {
|
||||||
|
hasNextPage: boolean;
|
||||||
|
hasPrevPage: boolean;
|
||||||
|
startCursor?: string;
|
||||||
|
endCursor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Edge<T extends Node> {
|
||||||
|
cursor: string;
|
||||||
|
node?: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Connection<T extends Node> {
|
||||||
|
edges?: Array<Edge<T>>;
|
||||||
|
pageInfo: PageInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NodeKind {
|
||||||
|
User = "USER",
|
||||||
|
Task = "TASK"
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeKindFrom = (kind: string) => {
|
||||||
|
const keys = Object.keys(NodeKind).filter(x => NodeKind[x] === kind);
|
||||||
|
return keys.length > 0
|
||||||
|
? Maybe.some<NodeKind>(NodeKind[keys[0]])
|
||||||
|
: Maybe.none<NodeKind>();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ID {
|
||||||
|
kind: NodeKind;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getId = (id: string) => {
|
||||||
|
const [kindStr, idStr] = Buffer.from(id, "base64")
|
||||||
|
.toString()
|
||||||
|
.split(":");
|
||||||
|
const kind = nodeKindFrom(kindStr);
|
||||||
|
|
||||||
|
return kind.flatMap<ID>(k => {
|
||||||
|
const actualId = parseInt(idStr, 10);
|
||||||
|
|
||||||
|
return isNaN(actualId)
|
||||||
|
? Maybe.none()
|
||||||
|
: Maybe.some({ id: actualId, kind: k });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIdOfKind = (id: string, kind: NodeKind) =>
|
||||||
|
getId(id).filter(i => i.kind === kind);
|
||||||
|
|
||||||
|
const buildId = (kind: NodeKind, id: number) =>
|
||||||
|
Buffer.from(`${kind}:${id}`).toString("base64");
|
||||||
|
|
||||||
|
const scheduleTypeMap: { [id: string]: ScheduleType } = {
|
||||||
|
[models.ScheduleType.Once]: "ONCE",
|
||||||
|
[models.ScheduleType.Daily]: "DAILY",
|
||||||
|
[models.ScheduleType.Weekly]: "WEEKLY",
|
||||||
|
[models.ScheduleType.Monthly]: "MONTHLY",
|
||||||
|
[models.ScheduleType.Yearly]: "YEARLY"
|
||||||
|
};
|
||||||
|
|
||||||
|
async function userTasks(
|
||||||
|
user: { id: string },
|
||||||
|
args: PaginationArguments
|
||||||
|
): Promise<Connection<Task>> {
|
||||||
|
return db.task<Connection<Task>>(async t => {
|
||||||
|
const after =
|
||||||
|
args.after &&
|
||||||
|
Maybe.some(args.before)
|
||||||
|
.flatMap(id => getIdOfKind(id, NodeKind.Task))
|
||||||
|
.some().id;
|
||||||
|
const before =
|
||||||
|
args.before &&
|
||||||
|
Maybe.fromFalsy(args.before)
|
||||||
|
.flatMap(id => getIdOfKind(id, NodeKind.Task))
|
||||||
|
.some().id;
|
||||||
|
const userId = getId(user.id).some().id;
|
||||||
|
|
||||||
|
let rows =
|
||||||
|
args.first || !(args.first || args.last)
|
||||||
|
? await t.tasks.list(userId, args.first, after, before)
|
||||||
|
: (await t.tasks.listReverse(
|
||||||
|
userId,
|
||||||
|
args.last,
|
||||||
|
after,
|
||||||
|
before
|
||||||
|
)).reverse();
|
||||||
|
|
||||||
|
if (args.first && args.last) {
|
||||||
|
rows = rows.slice(-1 * args.last);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
edges: rows.map(row => ({
|
||||||
|
cursor: buildId(NodeKind.Task, row.id),
|
||||||
|
node: {
|
||||||
|
ID: buildId(NodeKind.Task, row.id),
|
||||||
|
createdAt: row.createdAt,
|
||||||
|
maxFrequency: row.maxFrequency,
|
||||||
|
minFrequency: row.minFrequency,
|
||||||
|
name: row.name,
|
||||||
|
notes: row.notes,
|
||||||
|
schedule: scheduleTypeMap[row.schedule]
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage:
|
||||||
|
rows.length > 0 ? await t.tasks.hasNext(userId, rows[0].id) : false,
|
||||||
|
hasPrevPage:
|
||||||
|
rows.length > 0
|
||||||
|
? await t.tasks.hasPrev(userId, rows[rows.length - 1].id)
|
||||||
|
: false,
|
||||||
|
startCursor:
|
||||||
|
rows.length > 0 ? buildId(NodeKind.Task, rows[0].id) : null,
|
||||||
|
endCursor:
|
||||||
|
rows.length > 0
|
||||||
|
? buildId(NodeKind.Task, rows[rows.length - 1].id)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvers: IResolvers<any, Context> = {
|
||||||
|
DateTime: new GraphQLScalarType(dateTimeConfig),
|
||||||
|
Query: {
|
||||||
|
hello: () => `Hello, world!`,
|
||||||
|
migrations: () => db.migrations.applied(),
|
||||||
|
user: async (parent: any, { id }: { id?: string }, context) =>
|
||||||
|
Maybe.fromFalsy(id)
|
||||||
|
.orElse(Maybe.some(buildId(NodeKind.User, context.user.id)))
|
||||||
|
.flatMap(i => getId(i))
|
||||||
|
.filter(i => i.kind === NodeKind.User)
|
||||||
|
.filter(i => i.id === context.user.id)
|
||||||
|
.map(i =>
|
||||||
|
db.users.details(i.id).then(user =>
|
||||||
|
user
|
||||||
|
.map(u => ({
|
||||||
|
email: u.email,
|
||||||
|
id: buildId(NodeKind.User, u.id)
|
||||||
|
}))
|
||||||
|
.orNull()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orNull()
|
||||||
|
},
|
||||||
|
User: {
|
||||||
|
tasks: userTasks
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default resolvers;
|
79
src/api/typeDefs.ts
Normal file
79
src/api/typeDefs.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// 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 { gql } from "apollo-server-express";
|
||||||
|
|
||||||
|
export default gql`
|
||||||
|
type Query {
|
||||||
|
"A simple type for getting started"
|
||||||
|
node(id: ID!): Node
|
||||||
|
hello: String
|
||||||
|
migrations: [Migration]!
|
||||||
|
user(id: ID): User
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Node {
|
||||||
|
id: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Migration implements Node {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
appliedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type User implements Node {
|
||||||
|
id: ID!
|
||||||
|
email: String!
|
||||||
|
tasks(after: String, first: Int, before: String, last: Int): TaskConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
type Task implements Node {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
notes: String
|
||||||
|
schedule: ScheduleType!
|
||||||
|
minFrequency: Int
|
||||||
|
maxFrequency: Int
|
||||||
|
createdAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskConnection {
|
||||||
|
edges: [TaskEdge]
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskEdge {
|
||||||
|
cursor: String!
|
||||||
|
node: Task
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageInfo {
|
||||||
|
hasNextPage: Boolean!
|
||||||
|
hasPrevPage: Boolean!
|
||||||
|
startCursor: String
|
||||||
|
endCursor: String
|
||||||
|
}
|
||||||
|
|
||||||
|
scalar DateTime
|
||||||
|
|
||||||
|
enum ScheduleType {
|
||||||
|
ONCE
|
||||||
|
DAILY
|
||||||
|
WEEKLY
|
||||||
|
MONTHLY
|
||||||
|
YEARLY
|
||||||
|
}
|
||||||
|
`;
|
@ -18,7 +18,7 @@ import { DateTime } from "luxon";
|
|||||||
export interface Migration {
|
export interface Migration {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
applied_at: DateTime;
|
appliedAt: DateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
@ -26,7 +26,13 @@ export interface User {
|
|||||||
email: string;
|
email: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ScheduleType = "once" | "daily" | "weekly" | "monthly" | "yearly";
|
export enum ScheduleType {
|
||||||
|
Once = "once",
|
||||||
|
Daily = "daily",
|
||||||
|
Weekly = "weekly",
|
||||||
|
Monthly = "monthly",
|
||||||
|
Yearly = "yearly"
|
||||||
|
}
|
||||||
|
|
||||||
export interface Task {
|
export interface Task {
|
||||||
id: number;
|
id: number;
|
||||||
@ -34,9 +40,9 @@ export interface Task {
|
|||||||
name: string;
|
name: string;
|
||||||
notes?: string;
|
notes?: string;
|
||||||
schedule: ScheduleType;
|
schedule: ScheduleType;
|
||||||
min_frequency?: number;
|
minFrequency?: number;
|
||||||
max_frequency?: number;
|
maxFrequency?: number;
|
||||||
created_at: DateTime;
|
createdAt: DateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Session {
|
export interface Session {
|
||||||
|
@ -48,12 +48,10 @@ export class MigrationRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async applied(): Promise<Migration[]> {
|
public async applied(): Promise<Migration[]> {
|
||||||
return this.db.map<Migration>(sql.applied, [], row => {
|
return this.db.map(sql.applied, [], row => ({
|
||||||
return {
|
appliedAt: row.applied_at as DateTime,
|
||||||
applied_at: row.applied_at as DateTime,
|
|
||||||
id: +row.id,
|
id: +row.id,
|
||||||
name: row.name
|
name: row.name
|
||||||
};
|
}));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,19 @@ import { ScheduleType, Task } from "@kredens/db/models";
|
|||||||
import { tasks as sql } from "@kredens/db/sql";
|
import { tasks as sql } from "@kredens/db/sql";
|
||||||
import { IDatabase, IMain } from "pg-promise";
|
import { IDatabase, IMain } from "pg-promise";
|
||||||
|
|
||||||
|
function rowToTask(row: any): Task {
|
||||||
|
return {
|
||||||
|
createdAt: row.created_at,
|
||||||
|
id: +row.id,
|
||||||
|
maxFrequency: +row.maxFrequency,
|
||||||
|
minFrequency: +row.minFrequency,
|
||||||
|
name: row.name,
|
||||||
|
notes: row.notes,
|
||||||
|
owner: +row.owner,
|
||||||
|
schedule: row.schedule as ScheduleType
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export class TaskRepository {
|
export class TaskRepository {
|
||||||
private db: IDatabase<any>;
|
private db: IDatabase<any>;
|
||||||
|
|
||||||
@ -24,9 +37,33 @@ export class TaskRepository {
|
|||||||
this.db = db;
|
this.db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async list(owner: number): Promise<Task[]> {
|
public async list(
|
||||||
return this.db
|
owner: number,
|
||||||
.manyOrNone<Task>(sql.list, [owner, 10, 0])
|
limit?: number,
|
||||||
.then(rows => (rows ? rows : []));
|
after?: number,
|
||||||
|
before?: number
|
||||||
|
): Promise<Task[]> {
|
||||||
|
return this.db.map(sql.list, [owner, after, before, limit || 10], row =>
|
||||||
|
rowToTask(row)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listReverse(
|
||||||
|
owner: number,
|
||||||
|
limit?: number,
|
||||||
|
after?: number,
|
||||||
|
before?: number
|
||||||
|
): Promise<Task[]> {
|
||||||
|
return this.db.map(sql.list, [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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,11 @@ const users = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const tasks = {
|
const tasks = {
|
||||||
list: sql("tasks/list.sql")
|
count: sql("tasks/count.sql"),
|
||||||
|
hasNext: sql("tasks/hasNext.sql"),
|
||||||
|
hasPrev: sql("tasks/hasPrev.sql"),
|
||||||
|
list: sql("tasks/list.sql"),
|
||||||
|
listReverse: sql("tasks/listReverse.sql")
|
||||||
};
|
};
|
||||||
|
|
||||||
const sessions = {
|
const sessions = {
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
"vue-loader/lib/plugin",
|
"vue-loader/lib/plugin",
|
||||||
"@kredens"
|
"@kredens"
|
||||||
],
|
],
|
||||||
"no-implicit-dependencies": [true, ["@kredens"]]
|
"no-implicit-dependencies": [true, ["@kredens"]],
|
||||||
|
"object-literal-sort-keys": [true, "match-declaration-order-only", "shorthand-first"]
|
||||||
},
|
},
|
||||||
"rulesDirectory": []
|
"rulesDirectory": []
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user