Cleaned up things I decided to not use
This commit is contained in:
parent
461cebe009
commit
bff5c60a4e
714
package-lock.json
generated
714
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@holdyourwaffle/express-session": "^1.16.2",
|
"@holdyourwaffle/express-session": "^1.16.2",
|
||||||
"@types/pug": "^2.0.4",
|
"@types/pug": "^2.0.4",
|
||||||
"apollo-server-express": "^2.9.7",
|
|
||||||
"argon2": "^0.24.1",
|
"argon2": "^0.24.1",
|
||||||
"cookie-parser": "^1.4.4",
|
"cookie-parser": "^1.4.4",
|
||||||
"csurf": "^1.10.0",
|
"csurf": "^1.10.0",
|
||||||
@ -26,8 +25,6 @@
|
|||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-pino-logger": "^4.0.0",
|
"express-pino-logger": "^4.0.0",
|
||||||
"graphql": "^14.5.8",
|
|
||||||
"graphql-tools": "^4.0.5",
|
|
||||||
"helmet": "^3.21.2",
|
"helmet": "^3.21.2",
|
||||||
"http-errors": "^1.7.3",
|
"http-errors": "^1.7.3",
|
||||||
"luxon": "^1.19.3",
|
"luxon": "^1.19.3",
|
||||||
|
@ -1 +0,0 @@
|
|||||||
DROP TYPE task_schedule;
|
|
@ -1 +0,0 @@
|
|||||||
CREATE TYPE task_schedule AS ENUM('once', 'daily', 'weekly', 'monthly', 'yearly');
|
|
@ -3,12 +3,9 @@ CREATE TABLE tasks (
|
|||||||
owner integer NOT NULL REFERENCES users(id),
|
owner integer NOT NULL REFERENCES users(id),
|
||||||
name text NOT NULL,
|
name text NOT NULL,
|
||||||
notes text,
|
notes text,
|
||||||
schedule task_schedule NOT NULL DEFAULT 'daily',
|
schedule jsonb NOT NULL DEFAULT '{}',
|
||||||
min_frequency integer,
|
|
||||||
max_frequency integer,
|
|
||||||
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
CHECK(min_frequency IS NULL OR max_frequency IS NULL OR max_frequency > min_frequency)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TRIGGER set_tasks_updated BEFORE UPDATE ON tasks FOR EACH ROW EXECUTE PROCEDURE set_updated_timestamp();
|
CREATE TRIGGER set_tasks_updated BEFORE UPDATE ON tasks FOR EACH ROW EXECUTE PROCEDURE set_updated_timestamp();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
CREATE TABLE "sessions" (
|
CREATE TABLE "sessions" (
|
||||||
"sid" text NOT NULL COLLATE "default",
|
"sid" text NOT NULL COLLATE "default",
|
||||||
"session" json NOT NULL,
|
"session" jsonb NOT NULL,
|
||||||
"expires_at" timestamptz NOT NULL,
|
"expires_at" timestamptz NOT NULL,
|
||||||
"created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
|
"updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
@ -1,36 +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 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
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,226 +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 * 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;
|
|
@ -1,79 +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 { 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
|
|
||||||
}
|
|
||||||
`;
|
|
@ -19,14 +19,14 @@ import { IDatabase, IMain } from "pg-promise";
|
|||||||
|
|
||||||
function rowToTask(row: any): Task {
|
function rowToTask(row: any): Task {
|
||||||
return {
|
return {
|
||||||
createdAt: row.created_at,
|
|
||||||
id: +row.id,
|
id: +row.id,
|
||||||
maxFrequency: +row.maxFrequency,
|
owner: +row.owner,
|
||||||
minFrequency: +row.minFrequency,
|
|
||||||
name: row.name,
|
name: row.name,
|
||||||
notes: row.notes,
|
notes: row.notes,
|
||||||
owner: +row.owner,
|
schedule: row.schedule as ScheduleType,
|
||||||
schedule: row.schedule as ScheduleType
|
minFrequency: +row.minFrequency,
|
||||||
|
maxFrequency: +row.maxFrequency,
|
||||||
|
createdAt: row.created_at
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
src/index.ts
14
src/index.ts
@ -14,7 +14,6 @@
|
|||||||
// 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 session, { SessionOptions } from "@holdyourwaffle/express-session";
|
import session, { SessionOptions } from "@holdyourwaffle/express-session";
|
||||||
import { server as graphqlServer } from "@kredens/api";
|
|
||||||
import { authMiddleware } from "@kredens/auth";
|
import { authMiddleware } from "@kredens/auth";
|
||||||
import { db } from "@kredens/db";
|
import { db } from "@kredens/db";
|
||||||
import logger from "@kredens/logger";
|
import logger from "@kredens/logger";
|
||||||
@ -59,10 +58,10 @@ async function main() {
|
|||||||
app.use(express.urlencoded({ extended: false }));
|
app.use(express.urlencoded({ extended: false }));
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
const sessionOptions: SessionOptions = {
|
const sessionOptions: SessionOptions = {
|
||||||
resave: false,
|
|
||||||
saveUninitialized: false,
|
|
||||||
secret: process.env.SECRET,
|
secret: process.env.SECRET,
|
||||||
store: new PgStore()
|
store: new PgStore(),
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: false
|
||||||
};
|
};
|
||||||
|
|
||||||
if (app.get("env") === "production") {
|
if (app.get("env") === "production") {
|
||||||
@ -73,11 +72,6 @@ async function main() {
|
|||||||
app.use("/bootstrap", bootstrapRouter);
|
app.use("/bootstrap", bootstrapRouter);
|
||||||
|
|
||||||
app.use(authMiddleware());
|
app.use(authMiddleware());
|
||||||
const apiServer = graphqlServer();
|
|
||||||
apiServer.applyMiddleware({
|
|
||||||
app,
|
|
||||||
path: "/graphql"
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(csrf());
|
app.use(csrf());
|
||||||
|
|
||||||
@ -99,7 +93,7 @@ async function main() {
|
|||||||
const port = 3000;
|
const port = 3000;
|
||||||
app.listen(port, () =>
|
app.listen(port, () =>
|
||||||
logger.info("Example app listening", {
|
logger.info("Example app listening", {
|
||||||
uri: `http://localhost:${port}${apiServer.graphqlPath}`
|
uri: `http://localhost:${port}`
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user