UNPKG

6.92 kBPlain TextView Raw
1import Boom = require("boom");
2import * as Bookshelf from "bookshelf";
3import { FetchOptions, FetchAllOptions, Collection } from "bookshelf";
4import Knex = require("knex");
5import Path = require("path");
6import dotenv = require("dotenv");
7import pg = require("pg");
8import { Controller } from "./controller";
9import { IRepository, IExtendedModel } from "./types";
10import { eventBus } from "./event-bus";
11import { Observable } from "rxjs/Observable";
12import "rxjs/add/operator/finally";
13
14dotenv.config();
15pg.types.setTypeParser(20, "text", parseInt);
16
17const env = process.env.NODE_ENV || "development";
18const rootPath = Path.resolve(process.cwd());
19const knexfilePath = Path.join(rootPath, "./knexfile");
20const knexfile = require(knexfilePath);
21const knex = Knex(knexfile[env]);
22
23export const bookshelf: any = Bookshelf(knex);
24
25bookshelf.plugin("registry");
26bookshelf.plugin("visibility");
27
28export const tableModelMap = {};
29
30// function getModelDependencies(data: any): string[] {
31// const dependencies = [];
32
33// Object.values(data.relations).forEach((relation: any) => {
34// const tableName = relation.relatedData.targetTableName;
35// dependencies.push(tableModelMap[tableName]);
36// });
37
38// return dependencies;
39// }
40
41// function getCollectionDependencies(data: Collection<any>): string[] {
42// const dependencies = [];
43
44// Object.values(data.head().relations).forEach((relation: any) => {
45// const tableName = relation.relatedData.targetTableName;
46// dependencies.push(tableModelMap[tableName]);
47// });
48
49// return dependencies;
50// }
51
52function getDependencies(model) {
53 const dependencies = [];
54 if (!model || !model.relations) {
55 return;
56 }
57 const relations = [...Object.values(model.relations)];
58 while (relations.length) {
59 const relation: any = relations.pop();
60 let tableName;
61 if (
62 relation.head &&
63 typeof relation.head === "function" &&
64 relation.head().relatedData
65 ) {
66 tableName = relation.head().relatedData.targetTableName;
67 if (relation.head().relations) {
68 Object.values(relation.head().relations).forEach(i =>
69 relations.push(i)
70 );
71 }
72 } else if (relation.relatedData && relation.relatedData.targetTableName) {
73 tableName = relation.relatedData.targetTableName;
74 if (relation.relations) {
75 Object.values(relation.relations).forEach(i => relations.push(i));
76 }
77 }
78 console.log(relations);
79 console.log(tableName);
80 console.log(tableModelMap[tableName]);
81 if (!dependencies.includes(tableModelMap[tableName])) {
82 dependencies.push(tableModelMap[tableName]);
83 }
84 }
85 return dependencies;
86}
87
88function getCollectionDependencies(data: Collection<any>): string[] {
89 return getDependencies(data.head());
90}
91
92export class Repository implements IRepository {
93 public model: IExtendedModel;
94 public name: string;
95 public path: string;
96 public controller: Controller;
97 public websocket: boolean;
98 constructor(
99 path: string,
100 options: any,
101 name: string,
102 props: any,
103 websocket = false,
104 rest = true
105 ) {
106 tableModelMap[props.tableName] = name;
107 this.name = name;
108 this.path = path;
109 this.websocket = websocket;
110 props = {
111 ...props,
112 initialize() {
113 if (websocket) {
114 this.on("created", model => {
115 eventBus.emit(`DB_CHANGE:${name.toUpperCase()}`);
116 });
117 this.on("updated", model => {
118 eventBus.emit(`DB_CHANGE:${name.toUpperCase()}`);
119 });
120 }
121 }
122 };
123 this.model = bookshelf.model(name, props);
124 this.makeCrud(name, options);
125 this.makeReactive(name);
126
127 if (rest) {
128 this.controller = new Controller(path, (server, opts, next) => {
129 server.get("/", this.model.findAll);
130 server.get("/:id", this.model.findById);
131 server.post("/", this.model.upsert);
132 server.delete("/:id", this.model.remove);
133 server.post("/:id/undelete", this.model.recover);
134 });
135 }
136 }
137 public makeCrud(name: string, options: any) {
138 this.model.findAll = async () => {
139 return this.model.where("deleted", false).fetchAll(options) || [];
140 };
141 this.model.findById = async (request, reply) => {
142 const item = await this.model
143 .where("id", request.params.id)
144 .fetch(options);
145
146 if (!item) {
147 return Boom.badData(
148 `Entity ${name} with ID ${request.params.id} not exists`
149 );
150 }
151
152 return item;
153 };
154 this.model.upsert = async (request, reply) => {
155 const item = request.body;
156
157 if (!item) {
158 return Boom.badData(`Entity ${name}, Undefined Body`);
159 }
160
161 return this.model.forge(item).save();
162 };
163 this.model.remove = async (request, reply) => {
164 const id = request.params.id;
165
166 if (!id) {
167 return Boom.badData(`Entity ${name} undefined ID`);
168 }
169
170 const old = await this.model.where("id", id).fetch();
171
172 if (!old) {
173 return Boom.badData(`Entity ${name} with ID ${id} not exists`);
174 }
175
176 return old.save({ deleted: true });
177 };
178 this.model.recover = async (request, reply) => {
179 const item = request.body;
180
181 if (!item) {
182 return Boom.badData(`Entity ${name}, Undefined Body`);
183 }
184
185 const old = await this.model.where("id", item.id).fetch();
186
187 if (!old) {
188 return Boom.badData(`Entity ${name} with ID ${item.id} not exists`);
189 }
190
191 return old.save({ deleted: false });
192 };
193 }
194 public makeReactive(name: string) {
195 this.model.watch = (options?: FetchOptions): Observable<any> => {
196 const subscriptions = [];
197 return Observable.create(observer => {
198 this.model.fetch(options).then(data => {
199 observer.next(data);
200 const listenTo = [name, ...getDependencies(data)];
201 listenTo.map(l => l.toUpperCase()).forEach(dep => {
202 const sub = () =>
203 this.model.fetch(options).then(stream => observer.next(stream));
204 subscriptions.push(sub);
205 eventBus.on(`DB_CHANGE:${dep}`, sub);
206 });
207 });
208 }).finally(() => subscriptions.forEach(s => eventBus.unsubscribe(s)));
209 };
210 this.model.watchAll = (options?: FetchAllOptions): Observable<any> => {
211 const subscriptions = [];
212 return Observable.create(observer => {
213 this.model.fetchAll(options).then(data => {
214 observer.next(data);
215 const listenTo = [name, ...getCollectionDependencies(data)];
216 listenTo.map(l => l.toUpperCase()).forEach(dep => {
217 const sub = () =>
218 this.model
219 .fetchAll(options)
220 .then(stream => observer.next(stream));
221 subscriptions.push(sub);
222 eventBus.on(`DB_CHANGE:${dep}`, sub);
223 });
224 });
225 }).finally(() => subscriptions.forEach(s => eventBus.unsubscribe(s)));
226 };
227 }
228}