Cleaned up things I decided to not use
This commit is contained in:
		
							
								
								
									
										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}` | ||||||
|     }) |     }) | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user