diff --git a/Server/db-testing.sqlite b/Server/db-testing.sqlite new file mode 100644 index 0000000000..4679670ece Binary files /dev/null and b/Server/db-testing.sqlite differ diff --git a/Server/dist/app/app.js b/Server/dist/app/app.js new file mode 100644 index 0000000000..7476051537 --- /dev/null +++ b/Server/dist/app/app.js @@ -0,0 +1,109 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +require('app-module-path').addPath(__dirname + "/.."); +var Koa = require("koa"); +var sessions_1 = require("./routes/api/sessions"); +var ping_1 = require("./routes/api/ping"); +var files_1 = require("./routes/api/files"); +var errors_1 = require("./utils/errors"); +var fs = require("fs-extra"); +var koaBody = require("koa-body"); +var yargs_1 = require("yargs"); +var routeUtils_1 = require("./utils/routeUtils"); +var appLogger_1 = require("./utils/appLogger"); +var koaIf_1 = require("./utils/koaIf"); +var port = 3222; +appLogger_1.default.info("Starting server on port " + port + " and PID " + process.pid + "..."); +var app = new Koa(); +var routes = { + 'api/ping': ping_1.default, + 'api/sessions': sessions_1.default, + 'api/files': files_1.default, +}; +var koaBodyMiddleware = koaBody({ + multipart: true, + includeUnparsed: true, +}); +app.use(koaIf_1.default(koaBodyMiddleware, function (ctx) { + var match = routeUtils_1.findMatchingRoute(ctx.path, routes); + console.info('match.route.needsBodyMiddleware', match.route.needsBodyMiddleware); + return match.route.needsBodyMiddleware === true; +})); +app.use(function (ctx) { return __awaiter(void 0, void 0, void 0, function () { + var match, responseObject, error_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + match = routeUtils_1.findMatchingRoute(ctx.path, routes); + _a.label = 1; + case 1: + _a.trys.push([1, 5, , 6]); + if (!match) return [3 /*break*/, 3]; + return [4 /*yield*/, match.route.exec(match.subPath, ctx)]; + case 2: + responseObject = _a.sent(); + if (responseObject instanceof routeUtils_1.ApiResponse) { + ctx.response = responseObject.response; + } + else { + ctx.response.status = 200; + ctx.response.body = responseObject; + } + return [3 /*break*/, 4]; + case 3: throw new errors_1.ErrorNotFound(); + case 4: return [3 /*break*/, 6]; + case 5: + error_1 = _a.sent(); + appLogger_1.default.error(error_1); + ctx.response.status = error_1.httpCode ? error_1.httpCode : 500; + ctx.response.body = { error: error_1.message }; + return [3 /*break*/, 6]; + case 6: return [2 /*return*/]; + } + }); +}); }); +var pidFile = yargs_1.argv.pidfile; +if (pidFile) { + appLogger_1.default.info("Writing PID to " + pidFile + "..."); + fs.removeSync(pidFile); + fs.writeFileSync(pidFile, "" + process.pid); +} +app.listen(port); + +//# sourceMappingURL=data:application/json;charset=utf8;base64,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LE9BQU8sRUFBRSxLQUFHLE9BQU8sQ0FBQyxHQUFLLENBQUMsQ0FBQztDQUM1QztBQUVELEdBQUcsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMiLCJmaWxlIjoiYXBwLmpzIiwic291cmNlc0NvbnRlbnQiOlsicmVxdWlyZSgnYXBwLW1vZHVsZS1wYXRoJykuYWRkUGF0aChgJHtfX2Rpcm5hbWV9Ly4uYCk7XG5cbmltcG9ydCAqIGFzIEtvYSBmcm9tICdrb2EnO1xuaW1wb3J0IGFwaVNlc3Npb25zUm91dGUgZnJvbSAnLi9yb3V0ZXMvYXBpL3Nlc3Npb25zJztcbmltcG9ydCBhcGlQaW5nUm91dGUgZnJvbSAnLi9yb3V0ZXMvYXBpL3BpbmcnO1xuaW1wb3J0IGFwaUZpbGVzUm91dGUgZnJvbSAnLi9yb3V0ZXMvYXBpL2ZpbGVzJztcbmltcG9ydCB7IEVycm9yTm90Rm91bmQgfSBmcm9tICcuL3V0aWxzL2Vycm9ycyc7XG5pbXBvcnQgKiBhcyBmcyBmcm9tICdmcy1leHRyYSc7XG5pbXBvcnQgKiBhcyBrb2FCb2R5IGZyb20gJ2tvYS1ib2R5JztcbmltcG9ydCB7IGFyZ3YgfSBmcm9tICd5YXJncyc7XG5pbXBvcnQgeyBSb3V0ZXMsIGZpbmRNYXRjaGluZ1JvdXRlLCBBcGlSZXNwb25zZSB9IGZyb20gJy4vdXRpbHMvcm91dGVVdGlscyc7XG5pbXBvcnQgYXBwTG9nZ2VyIGZyb20gJy4vdXRpbHMvYXBwTG9nZ2VyJztcbmltcG9ydCBrb2FJZiBmcm9tICcuL3V0aWxzL2tvYUlmJztcblxuY29uc3QgcG9ydCA9IDMyMjI7XG5cbmFwcExvZ2dlci5pbmZvKGBTdGFydGluZyBzZXJ2ZXIgb24gcG9ydCAke3BvcnR9IGFuZCBQSUQgJHtwcm9jZXNzLnBpZH0uLi5gKTtcblxuY29uc3QgYXBwID0gbmV3IEtvYSgpO1xuXG5jb25zdCByb3V0ZXM6Um91dGVzID0ge1xuXHQnYXBpL3BpbmcnOiBhcGlQaW5nUm91dGUsXG5cdCdhcGkvc2Vzc2lvbnMnOiBhcGlTZXNzaW9uc1JvdXRlLFxuXHQnYXBpL2ZpbGVzJzogYXBpRmlsZXNSb3V0ZSxcbn07XG5cbmNvbnN0IGtvYUJvZHlNaWRkbGV3YXJlID0ga29hQm9keSh7XG5cdG11bHRpcGFydDogdHJ1ZSxcblx0aW5jbHVkZVVucGFyc2VkOiB0cnVlLFxufSk7XG5cbmFwcC51c2Uoa29hSWYoa29hQm9keU1pZGRsZXdhcmUsIChjdHg6S29hLkNvbnRleHQpID0+IHtcblx0Y29uc3QgbWF0Y2ggPSBmaW5kTWF0Y2hpbmdSb3V0ZShjdHgucGF0aCwgcm91dGVzKTtcblx0Y29uc29sZS5pbmZvKCdtYXRjaC5yb3V0ZS5uZWVkc0JvZHlNaWRkbGV3YXJlJywgbWF0Y2gucm91dGUubmVlZHNCb2R5TWlkZGxld2FyZSk7XG5cdHJldHVybiBtYXRjaC5yb3V0ZS5uZWVkc0JvZHlNaWRkbGV3YXJlID09PSB0cnVlO1xufSkpO1xuXG5hcHAudXNlKGFzeW5jIChjdHg6S29hLkNvbnRleHQpID0+IHtcblx0Y29uc3QgbWF0Y2ggPSBmaW5kTWF0Y2hpbmdSb3V0ZShjdHgucGF0aCwgcm91dGVzKTtcblxuXHR0cnkge1xuXHRcdGlmIChtYXRjaCkge1xuXHRcdFx0Y29uc3QgcmVzcG9uc2VPYmplY3QgPSBhd2FpdCBtYXRjaC5yb3V0ZS5leGVjKG1hdGNoLnN1YlBhdGgsIGN0eCk7XG5cblx0XHRcdGlmIChyZXNwb25zZU9iamVjdCBpbnN0YW5jZW9mIEFwaVJlc3BvbnNlKSB7XG5cdFx0XHRcdGN0eC5yZXNwb25zZSA9IHJlc3BvbnNlT2JqZWN0LnJlc3BvbnNlO1xuXHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0Y3R4LnJlc3BvbnNlLnN0YXR1cyA9IDIwMDtcblx0XHRcdFx0Y3R4LnJlc3BvbnNlLmJvZHkgPSByZXNwb25zZU9iamVjdDtcblx0XHRcdH1cblx0XHR9IGVsc2Uge1xuXHRcdFx0dGhyb3cgbmV3IEVycm9yTm90Rm91bmQoKTtcblx0XHR9XG5cdH0gY2F0Y2ggKGVycm9yKSB7XG5cdFx0YXBwTG9nZ2VyLmVycm9yKGVycm9yKTtcblx0XHRjdHgucmVzcG9uc2Uuc3RhdHVzID0gZXJyb3IuaHR0cENvZGUgPyBlcnJvci5odHRwQ29kZSA6IDUwMDtcblx0XHRjdHgucmVzcG9uc2UuYm9keSA9IHsgZXJyb3I6IGVycm9yLm1lc3NhZ2UgfTtcblx0fVxufSk7XG5cbmNvbnN0IHBpZEZpbGUgPSBhcmd2LnBpZGZpbGUgYXMgc3RyaW5nO1xuaWYgKHBpZEZpbGUpIHtcblx0YXBwTG9nZ2VyLmluZm8oYFdyaXRpbmcgUElEIHRvICR7cGlkRmlsZX0uLi5gKTtcblx0ZnMucmVtb3ZlU3luYyhwaWRGaWxlIGFzIHN0cmluZyk7XG5cdGZzLndyaXRlRmlsZVN5bmMocGlkRmlsZSwgYCR7cHJvY2Vzcy5waWR9YCk7XG59XG5cbmFwcC5saXN0ZW4ocG9ydCk7XG4iXX0= diff --git a/Server/dist/app/controllers/BaseController.js b/Server/dist/app/controllers/BaseController.js new file mode 100644 index 0000000000..92d900e44b --- /dev/null +++ b/Server/dist/app/controllers/BaseController.js @@ -0,0 +1,68 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var errors_1 = require("../utils/errors"); +var SessionModel_1 = require("../models/SessionModel"); +var BaseController = /** @class */ (function () { + function BaseController() { + } + BaseController.prototype.initSession = function (sessionId, mustBeAdmin) { + if (mustBeAdmin === void 0) { mustBeAdmin = false; } + return __awaiter(this, void 0, void 0, function () { + var sessionModel, user; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + sessionModel = new SessionModel_1.default(); + return [4 /*yield*/, sessionModel.sessionUser(sessionId)]; + case 1: + user = _a.sent(); + if (!user) + throw new errors_1.ErrorForbidden("Invalid session ID: " + sessionId); + if (!user.is_admin && mustBeAdmin) + throw new errors_1.ErrorForbidden('Non-admin user is not allowed'); + return [2 /*return*/, user]; + } + }); + }); + }; + return BaseController; +}()); +exports.default = BaseController; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkJhc2VDb250cm9sbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQ0EsMENBQWlEO0FBQ2pELHVEQUFrRDtBQUVsRDtJQUFBO0lBVUEsQ0FBQztJQVJNLG9DQUFXLEdBQWpCLFVBQWtCLFNBQWdCLEVBQUUsV0FBMkI7UUFBM0IsNEJBQUEsRUFBQSxtQkFBMkI7Ozs7Ozt3QkFDeEQsWUFBWSxHQUFHLElBQUksc0JBQVksRUFBRSxDQUFDO3dCQUN0QixxQkFBTSxZQUFZLENBQUMsV0FBVyxDQUFDLFNBQVMsQ0FBQyxFQUFBOzt3QkFBckQsSUFBSSxHQUFRLFNBQXlDO3dCQUMzRCxJQUFJLENBQUMsSUFBSTs0QkFBRSxNQUFNLElBQUksdUJBQWMsQ0FBQyx5QkFBdUIsU0FBVyxDQUFDLENBQUM7d0JBQ3hFLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxJQUFJLFdBQVc7NEJBQUUsTUFBTSxJQUFJLHVCQUFjLENBQUMsK0JBQStCLENBQUMsQ0FBQzt3QkFDN0Ysc0JBQU8sSUFBSSxFQUFDOzs7O0tBQ1o7SUFFRixxQkFBQztBQUFELENBVkEsQUFVQyxJQUFBIiwiZmlsZSI6IkJhc2VDb250cm9sbGVyLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgVXNlciB9IGZyb20gJy4uL2RiJztcbmltcG9ydCB7IEVycm9yRm9yYmlkZGVuIH0gZnJvbSAnLi4vdXRpbHMvZXJyb3JzJztcbmltcG9ydCBTZXNzaW9uTW9kZWwgZnJvbSAnLi4vbW9kZWxzL1Nlc3Npb25Nb2RlbCc7XG5cbmV4cG9ydCBkZWZhdWx0IGFic3RyYWN0IGNsYXNzIEJhc2VDb250cm9sbGVyIHtcblxuXHRhc3luYyBpbml0U2Vzc2lvbihzZXNzaW9uSWQ6c3RyaW5nLCBtdXN0QmVBZG1pbjpib29sZWFuID0gZmFsc2UpOlByb21pc2U8VXNlcj4ge1xuXHRcdGNvbnN0IHNlc3Npb25Nb2RlbCA9IG5ldyBTZXNzaW9uTW9kZWwoKTtcblx0XHRjb25zdCB1c2VyOlVzZXIgPSBhd2FpdCBzZXNzaW9uTW9kZWwuc2Vzc2lvblVzZXIoc2Vzc2lvbklkKTtcblx0XHRpZiAoIXVzZXIpIHRocm93IG5ldyBFcnJvckZvcmJpZGRlbihgSW52YWxpZCBzZXNzaW9uIElEOiAke3Nlc3Npb25JZH1gKTtcblx0XHRpZiAoIXVzZXIuaXNfYWRtaW4gJiYgbXVzdEJlQWRtaW4pIHRocm93IG5ldyBFcnJvckZvcmJpZGRlbignTm9uLWFkbWluIHVzZXIgaXMgbm90IGFsbG93ZWQnKTtcblx0XHRyZXR1cm4gdXNlcjtcblx0fVxuXG59XG4iXX0= diff --git a/Server/dist/app/controllers/FileController.js b/Server/dist/app/controllers/FileController.js new file mode 100644 index 0000000000..5e8d78492e --- /dev/null +++ b/Server/dist/app/controllers/FileController.js @@ -0,0 +1,200 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var FileModel_1 = require("../models/FileModel"); +var BaseController_1 = require("./BaseController"); +var routeUtils_1 = require("../utils/routeUtils"); +var FileController = /** @class */ (function (_super) { + __extends(FileController, _super); + function FileController() { + return _super !== null && _super.apply(this, arguments) || this; + } + FileController.prototype.createFile = function (sessionId, file) { + return __awaiter(this, void 0, void 0, function () { + var user, fileModel, newFile; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.initSession(sessionId)]; + case 1: + user = _a.sent(); + fileModel = new FileModel_1.default({ userId: user.id }); + return [4 /*yield*/, fileModel.fromApiInput(file)]; + case 2: + newFile = _a.sent(); + return [4 /*yield*/, fileModel.save(file)]; + case 3: + newFile = _a.sent(); + return [2 /*return*/, fileModel.toApiOutput(newFile)]; + } + }); + }); + }; + FileController.prototype.getFile = function (sessionId, fileId) { + return __awaiter(this, void 0, void 0, function () { + var user, fileModel, _a, _b; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: return [4 /*yield*/, this.initSession(sessionId)]; + case 1: + user = _c.sent(); + fileModel = new FileModel_1.default({ userId: user.id }); + _b = (_a = fileModel).toApiOutput; + return [4 /*yield*/, fileModel.load(fileId)]; + case 2: return [2 /*return*/, _b.apply(_a, [_c.sent()])]; + } + }); + }); + }; + FileController.prototype.getFileContent = function (sessionId, fileId) { + return __awaiter(this, void 0, void 0, function () { + var user, fileModel, file; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.initSession(sessionId)]; + case 1: + user = _a.sent(); + fileModel = new FileModel_1.default({ userId: user.id }); + return [4 /*yield*/, fileModel.loadWithContent(fileId)]; + case 2: + file = _a.sent(); + return [2 /*return*/, file]; + } + }); + }); + }; + FileController.prototype.getAll = function (sessionId, parentId) { + if (parentId === void 0) { parentId = ''; } + return __awaiter(this, void 0, void 0, function () { + var user, fileModel; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.initSession(sessionId)]; + case 1: + user = _a.sent(); + fileModel = new FileModel_1.default({ userId: user.id }); + return [2 /*return*/, fileModel.allByParent(parentId)]; + } + }); + }); + }; + FileController.prototype.updateFile = function (sessionId, file) { + return __awaiter(this, void 0, void 0, function () { + var user, fileModel, newFile; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.initSession(sessionId)]; + case 1: + user = _a.sent(); + fileModel = new FileModel_1.default({ userId: user.id }); + return [4 /*yield*/, fileModel.fromApiInput(file)]; + case 2: + newFile = _a.sent(); + return [4 /*yield*/, fileModel.save(newFile)]; + case 3: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + FileController.prototype.updateFileContent = function (sessionId, fileId, content) { + return __awaiter(this, void 0, void 0, function () { + var user, fileModel, file, id; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.initSession(sessionId)]; + case 1: + user = _a.sent(); + fileModel = new FileModel_1.default({ userId: user.id }); + file = { content: content }; + if (!(typeof fileId === 'string')) return [3 /*break*/, 2]; + file.id = fileId; + return [3 /*break*/, 4]; + case 2: return [4 /*yield*/, fileModel.idFromItemId(fileId, false)]; + case 3: + id = _a.sent(); + if (id) { + file.id = id; + } + else { + file.name = routeUtils_1.removeFilePathPrefix(fileId.value); + } + _a.label = 4; + case 4: return [4 /*yield*/, fileModel.save(file)]; + case 5: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + FileController.prototype.deleteFile = function (sessionId, fileId) { + return __awaiter(this, void 0, void 0, function () { + var user, fileModel; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.initSession(sessionId)]; + case 1: + user = _a.sent(); + fileModel = new FileModel_1.default({ userId: user.id }); + return [4 /*yield*/, fileModel.delete(fileId)]; + case 2: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + return FileController; +}(BaseController_1.default)); +exports.default = FileController; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkZpbGVDb250cm9sbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUNBLGlEQUE0QztBQUM1QyxtREFBOEM7QUFDOUMsa0RBQTJEO0FBRTNEO0lBQTRDLGtDQUFjO0lBQTFEOztJQThEQSxDQUFDO0lBNURNLG1DQUFVLEdBQWhCLFVBQWlCLFNBQWdCLEVBQUUsSUFBUzs7Ozs7NEJBQzlCLHFCQUFNLElBQUksQ0FBQyxXQUFXLENBQUMsU0FBUyxDQUFDLEVBQUE7O3dCQUF4QyxJQUFJLEdBQUcsU0FBaUM7d0JBQ3hDLFNBQVMsR0FBRyxJQUFJLG1CQUFTLENBQUMsRUFBRSxNQUFNLEVBQUUsSUFBSSxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7d0JBQ3ZDLHFCQUFNLFNBQVMsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLEVBQUE7O3dCQUE1QyxPQUFPLEdBQUcsU0FBa0M7d0JBQ3RDLHFCQUFNLFNBQVMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUE7O3dCQUFwQyxPQUFPLEdBQUcsU0FBMEIsQ0FBQzt3QkFDckMsc0JBQU8sU0FBUyxDQUFDLFdBQVcsQ0FBQyxPQUFPLENBQUMsRUFBQzs7OztLQUN0QztJQUVLLGdDQUFPLEdBQWIsVUFBYyxTQUFnQixFQUFFLE1BQXNCOzs7Ozs0QkFDeEMscUJBQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxTQUFTLENBQUMsRUFBQTs7d0JBQXhDLElBQUksR0FBRyxTQUFpQzt3QkFDeEMsU0FBUyxHQUFHLElBQUksbUJBQVMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQzt3QkFDOUMsS0FBQSxDQUFBLEtBQUEsU0FBUyxDQUFBLENBQUMsV0FBVyxDQUFBO3dCQUFDLHFCQUFNLFNBQVMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUE7NEJBQXpELHNCQUFPLGNBQXNCLFNBQTRCLEVBQUMsRUFBQzs7OztLQUMzRDtJQUVLLHVDQUFjLEdBQXBCLFVBQXFCLFNBQWdCLEVBQUUsTUFBc0I7Ozs7OzRCQUMvQyxxQkFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLFNBQVMsQ0FBQyxFQUFBOzt3QkFBeEMsSUFBSSxHQUFHLFNBQWlDO3dCQUN4QyxTQUFTLEdBQUcsSUFBSSxtQkFBUyxDQUFDLEVBQUUsTUFBTSxFQUFFLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO3dCQUNuQyxxQkFBTSxTQUFTLENBQUMsZUFBZSxDQUFDLE1BQU0sQ0FBQyxFQUFBOzt3QkFBbkQsSUFBSSxHQUFRLFNBQXVDO3dCQUN6RCxzQkFBTyxJQUFJLEVBQUM7Ozs7S0FDWjtJQUVLLCtCQUFNLEdBQVosVUFBYSxTQUFnQixFQUFFLFFBQW9CO1FBQXBCLHlCQUFBLEVBQUEsYUFBb0I7Ozs7OzRCQUNyQyxxQkFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLFNBQVMsQ0FBQyxFQUFBOzt3QkFBeEMsSUFBSSxHQUFHLFNBQWlDO3dCQUN4QyxTQUFTLEdBQUcsSUFBSSxtQkFBUyxDQUFDLEVBQUUsTUFBTSxFQUFFLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO3dCQUNyRCxzQkFBTyxTQUFTLENBQUMsV0FBVyxDQUFDLFFBQVEsQ0FBQyxFQUFDOzs7O0tBQ3ZDO0lBRUssbUNBQVUsR0FBaEIsVUFBaUIsU0FBZ0IsRUFBRSxJQUFTOzs7Ozs0QkFDOUIscUJBQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxTQUFTLENBQUMsRUFBQTs7d0JBQXhDLElBQUksR0FBRyxTQUFpQzt3QkFDeEMsU0FBUyxHQUFHLElBQUksbUJBQVMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQzt3QkFDckMscUJBQU0sU0FBUyxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsRUFBQTs7d0JBQTVDLE9BQU8sR0FBRyxTQUFrQzt3QkFDbEQscUJBQU0sU0FBUyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBQTs7d0JBQTdCLFNBQTZCLENBQUM7Ozs7O0tBQzlCO0lBRUssMENBQWlCLEdBQXZCLFVBQXdCLFNBQWdCLEVBQUUsTUFBc0IsRUFBRSxPQUFXOzs7Ozs0QkFDL0QscUJBQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxTQUFTLENBQUMsRUFBQTs7d0JBQXhDLElBQUksR0FBRyxTQUFpQzt3QkFDeEMsU0FBUyxHQUFHLElBQUksbUJBQVMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQzt3QkFFL0MsSUFBSSxHQUFRLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxDQUFDOzZCQUVuQyxDQUFBLE9BQU8sTUFBTSxLQUFLLFFBQVEsQ0FBQSxFQUExQix3QkFBMEI7d0JBQzdCLElBQUksQ0FBQyxFQUFFLEdBQUcsTUFBTSxDQUFDOzs0QkFFTixxQkFBTSxTQUFTLENBQUMsWUFBWSxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsRUFBQTs7d0JBQWhELEVBQUUsR0FBRyxTQUEyQzt3QkFDdEQsSUFBSSxFQUFFLEVBQUU7NEJBQ1AsSUFBSSxDQUFDLEVBQUUsR0FBRyxFQUFFLENBQUM7eUJBQ2I7NkJBQU07NEJBQ04sSUFBSSxDQUFDLElBQUksR0FBRyxpQ0FBb0IsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7eUJBQy9DOzs0QkFHRixxQkFBTSxTQUFTLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFBOzt3QkFBMUIsU0FBMEIsQ0FBQzs7Ozs7S0FDM0I7SUFFSyxtQ0FBVSxHQUFoQixVQUFpQixTQUFnQixFQUFFLE1BQXNCOzs7Ozs0QkFDM0MscUJBQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxTQUFTLENBQUMsRUFBQTs7d0JBQXhDLElBQUksR0FBRyxTQUFpQzt3QkFDeEMsU0FBUyxHQUFHLElBQUksbUJBQVMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQzt3QkFDckQscUJBQU0sU0FBUyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBQTs7d0JBQTlCLFNBQThCLENBQUM7Ozs7O0tBQy9CO0lBRUYscUJBQUM7QUFBRCxDQTlEQSxBQThEQyxDQTlEMkMsd0JBQWMsR0E4RHpEIiwiZmlsZSI6IkZpbGVDb250cm9sbGVyLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRmlsZSwgSXRlbUlkIH0gZnJvbSAnLi4vZGInO1xuaW1wb3J0IEZpbGVNb2RlbCBmcm9tICcuLi9tb2RlbHMvRmlsZU1vZGVsJztcbmltcG9ydCBCYXNlQ29udHJvbGxlciBmcm9tICcuL0Jhc2VDb250cm9sbGVyJztcbmltcG9ydCB7IHJlbW92ZUZpbGVQYXRoUHJlZml4IH0gZnJvbSAnLi4vdXRpbHMvcm91dGVVdGlscyc7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIEZpbGVDb250cm9sbGVyIGV4dGVuZHMgQmFzZUNvbnRyb2xsZXIge1xuXG5cdGFzeW5jIGNyZWF0ZUZpbGUoc2Vzc2lvbklkOnN0cmluZywgZmlsZTpGaWxlKTpQcm9taXNlPEZpbGU+IHtcblx0XHRjb25zdCB1c2VyID0gYXdhaXQgdGhpcy5pbml0U2Vzc2lvbihzZXNzaW9uSWQpO1xuXHRcdGNvbnN0IGZpbGVNb2RlbCA9IG5ldyBGaWxlTW9kZWwoeyB1c2VySWQ6IHVzZXIuaWQgfSk7XG5cdFx0bGV0IG5ld0ZpbGUgPSBhd2FpdCBmaWxlTW9kZWwuZnJvbUFwaUlucHV0KGZpbGUpO1xuXHRcdG5ld0ZpbGUgPSBhd2FpdCBmaWxlTW9kZWwuc2F2ZShmaWxlKTtcblx0XHRyZXR1cm4gZmlsZU1vZGVsLnRvQXBpT3V0cHV0KG5ld0ZpbGUpO1xuXHR9XG5cblx0YXN5bmMgZ2V0RmlsZShzZXNzaW9uSWQ6c3RyaW5nLCBmaWxlSWQ6c3RyaW5nIHwgSXRlbUlkKTpQcm9taXNlPEZpbGU+IHtcblx0XHRjb25zdCB1c2VyID0gYXdhaXQgdGhpcy5pbml0U2Vzc2lvbihzZXNzaW9uSWQpO1xuXHRcdGNvbnN0IGZpbGVNb2RlbCA9IG5ldyBGaWxlTW9kZWwoeyB1c2VySWQ6IHVzZXIuaWQgfSk7XG5cdFx0cmV0dXJuIGZpbGVNb2RlbC50b0FwaU91dHB1dChhd2FpdCBmaWxlTW9kZWwubG9hZChmaWxlSWQpKTtcblx0fVxuXG5cdGFzeW5jIGdldEZpbGVDb250ZW50KHNlc3Npb25JZDpzdHJpbmcsIGZpbGVJZDpzdHJpbmcgfCBJdGVtSWQpOlByb21pc2U8RmlsZT4ge1xuXHRcdGNvbnN0IHVzZXIgPSBhd2FpdCB0aGlzLmluaXRTZXNzaW9uKHNlc3Npb25JZCk7XG5cdFx0Y29uc3QgZmlsZU1vZGVsID0gbmV3IEZpbGVNb2RlbCh7IHVzZXJJZDogdXNlci5pZCB9KTtcblx0XHRjb25zdCBmaWxlOkZpbGUgPSBhd2FpdCBmaWxlTW9kZWwubG9hZFdpdGhDb250ZW50KGZpbGVJZCk7XG5cdFx0cmV0dXJuIGZpbGU7XG5cdH1cblxuXHRhc3luYyBnZXRBbGwoc2Vzc2lvbklkOnN0cmluZywgcGFyZW50SWQ6c3RyaW5nID0gJycpOlByb21pc2U8RmlsZVtdPiB7XG5cdFx0Y29uc3QgdXNlciA9IGF3YWl0IHRoaXMuaW5pdFNlc3Npb24oc2Vzc2lvbklkKTtcblx0XHRjb25zdCBmaWxlTW9kZWwgPSBuZXcgRmlsZU1vZGVsKHsgdXNlcklkOiB1c2VyLmlkIH0pO1xuXHRcdHJldHVybiBmaWxlTW9kZWwuYWxsQnlQYXJlbnQocGFyZW50SWQpO1xuXHR9XG5cblx0YXN5bmMgdXBkYXRlRmlsZShzZXNzaW9uSWQ6c3RyaW5nLCBmaWxlOkZpbGUpOlByb21pc2U8dm9pZD4ge1xuXHRcdGNvbnN0IHVzZXIgPSBhd2FpdCB0aGlzLmluaXRTZXNzaW9uKHNlc3Npb25JZCk7XG5cdFx0Y29uc3QgZmlsZU1vZGVsID0gbmV3IEZpbGVNb2RlbCh7IHVzZXJJZDogdXNlci5pZCB9KTtcblx0XHRjb25zdCBuZXdGaWxlID0gYXdhaXQgZmlsZU1vZGVsLmZyb21BcGlJbnB1dChmaWxlKTtcblx0XHRhd2FpdCBmaWxlTW9kZWwuc2F2ZShuZXdGaWxlKTtcblx0fVxuXG5cdGFzeW5jIHVwZGF0ZUZpbGVDb250ZW50KHNlc3Npb25JZDpzdHJpbmcsIGZpbGVJZDpzdHJpbmcgfCBJdGVtSWQsIGNvbnRlbnQ6YW55KTpQcm9taXNlPGFueT4ge1xuXHRcdGNvbnN0IHVzZXIgPSBhd2FpdCB0aGlzLmluaXRTZXNzaW9uKHNlc3Npb25JZCk7XG5cdFx0Y29uc3QgZmlsZU1vZGVsID0gbmV3IEZpbGVNb2RlbCh7IHVzZXJJZDogdXNlci5pZCB9KTtcblxuXHRcdGNvbnN0IGZpbGU6RmlsZSA9IHsgY29udGVudDogY29udGVudCB9O1xuXG5cdFx0aWYgKHR5cGVvZiBmaWxlSWQgPT09ICdzdHJpbmcnKSB7XG5cdFx0XHRmaWxlLmlkID0gZmlsZUlkO1xuXHRcdH0gZWxzZSB7XG5cdFx0XHRjb25zdCBpZCA9IGF3YWl0IGZpbGVNb2RlbC5pZEZyb21JdGVtSWQoZmlsZUlkLCBmYWxzZSk7XG5cdFx0XHRpZiAoaWQpIHtcblx0XHRcdFx0ZmlsZS5pZCA9IGlkO1xuXHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0ZmlsZS5uYW1lID0gcmVtb3ZlRmlsZVBhdGhQcmVmaXgoZmlsZUlkLnZhbHVlKTtcblx0XHRcdH1cblx0XHR9XG5cblx0XHRhd2FpdCBmaWxlTW9kZWwuc2F2ZShmaWxlKTtcblx0fVxuXG5cdGFzeW5jIGRlbGV0ZUZpbGUoc2Vzc2lvbklkOnN0cmluZywgZmlsZUlkOnN0cmluZyB8IEl0ZW1JZCk6UHJvbWlzZTx2b2lkPiB7XG5cdFx0Y29uc3QgdXNlciA9IGF3YWl0IHRoaXMuaW5pdFNlc3Npb24oc2Vzc2lvbklkKTtcblx0XHRjb25zdCBmaWxlTW9kZWwgPSBuZXcgRmlsZU1vZGVsKHsgdXNlcklkOiB1c2VyLmlkIH0pO1xuXHRcdGF3YWl0IGZpbGVNb2RlbC5kZWxldGUoZmlsZUlkKTtcblx0fVxuXG59XG4iXX0= diff --git a/Server/dist/app/controllers/SessionController.js b/Server/dist/app/controllers/SessionController.js new file mode 100644 index 0000000000..b27c782aa9 --- /dev/null +++ b/Server/dist/app/controllers/SessionController.js @@ -0,0 +1,72 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var auth_1 = require("../utils/auth"); +var errors_1 = require("../utils/errors"); +var SessionModel_1 = require("../models/SessionModel"); +var UserModel_1 = require("../models/UserModel"); +var uuidgen_1 = require("../utils/uuidgen"); +var SessionController = /** @class */ (function () { + function SessionController() { + } + SessionController.prototype.authenticate = function (email, password) { + return __awaiter(this, void 0, void 0, function () { + var userModel, user, session, sessionModel; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + userModel = new UserModel_1.default(); + return [4 /*yield*/, userModel.loadByEmail(email)]; + case 1: + user = _a.sent(); + if (!user) + throw new errors_1.ErrorForbidden('Invalid username or password'); + if (!auth_1.checkPassword(password, user.password)) + throw new errors_1.ErrorForbidden('Invalid username or password'); + session = { id: uuidgen_1.default(), user_id: user.id }; + sessionModel = new SessionModel_1.default(); + return [2 /*return*/, sessionModel.save(session, { isNew: true })]; + } + }); + }); + }; + return SessionController; +}()); +exports.default = SessionController; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIlNlc3Npb25Db250cm9sbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQ0Esc0NBQThDO0FBQzlDLDBDQUFpRDtBQUNqRCx1REFBa0Q7QUFDbEQsaURBQTRDO0FBQzVDLDRDQUF1QztBQUV2QztJQUFBO0lBWUEsQ0FBQztJQVZNLHdDQUFZLEdBQWxCLFVBQW1CLEtBQWEsRUFBRSxRQUFnQjs7Ozs7O3dCQUMzQyxTQUFTLEdBQUcsSUFBSSxtQkFBUyxFQUFFLENBQUM7d0JBQ2hCLHFCQUFNLFNBQVMsQ0FBQyxXQUFXLENBQUMsS0FBSyxDQUFDLEVBQUE7O3dCQUE5QyxJQUFJLEdBQVEsU0FBa0M7d0JBQ3BELElBQUksQ0FBQyxJQUFJOzRCQUFFLE1BQU0sSUFBSSx1QkFBYyxDQUFDLDhCQUE4QixDQUFDLENBQUM7d0JBQ3BFLElBQUksQ0FBQyxvQkFBYSxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDOzRCQUFFLE1BQU0sSUFBSSx1QkFBYyxDQUFDLDhCQUE4QixDQUFDLENBQUM7d0JBQ2hHLE9BQU8sR0FBVyxFQUFFLEVBQUUsRUFBRSxpQkFBTyxFQUFFLEVBQUUsT0FBTyxFQUFFLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQzt3QkFDdEQsWUFBWSxHQUFHLElBQUksc0JBQVksRUFBRSxDQUFDO3dCQUN4QyxzQkFBTyxZQUFZLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsQ0FBQyxFQUFDOzs7O0tBQ25EO0lBRUYsd0JBQUM7QUFBRCxDQVpBLEFBWUMsSUFBQSIsImZpbGUiOiJTZXNzaW9uQ29udHJvbGxlci5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IFNlc3Npb24sIFVzZXIgfSBmcm9tICcuLi9kYic7XG5pbXBvcnQgeyBjaGVja1Bhc3N3b3JkIH0gZnJvbSAnLi4vdXRpbHMvYXV0aCc7XG5pbXBvcnQgeyBFcnJvckZvcmJpZGRlbiB9IGZyb20gJy4uL3V0aWxzL2Vycm9ycyc7XG5pbXBvcnQgU2Vzc2lvbk1vZGVsIGZyb20gJy4uL21vZGVscy9TZXNzaW9uTW9kZWwnO1xuaW1wb3J0IFVzZXJNb2RlbCBmcm9tICcuLi9tb2RlbHMvVXNlck1vZGVsJztcbmltcG9ydCB1dWlkZ2VuIGZyb20gJy4uL3V0aWxzL3V1aWRnZW4nO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBTZXNzaW9uQ29udHJvbGxlciB7XG5cblx0YXN5bmMgYXV0aGVudGljYXRlKGVtYWlsOiBzdHJpbmcsIHBhc3N3b3JkOiBzdHJpbmcpOlByb21pc2U8U2Vzc2lvbj4ge1xuXHRcdGNvbnN0IHVzZXJNb2RlbCA9IG5ldyBVc2VyTW9kZWwoKTtcblx0XHRjb25zdCB1c2VyOlVzZXIgPSBhd2FpdCB1c2VyTW9kZWwubG9hZEJ5RW1haWwoZW1haWwpO1xuXHRcdGlmICghdXNlcikgdGhyb3cgbmV3IEVycm9yRm9yYmlkZGVuKCdJbnZhbGlkIHVzZXJuYW1lIG9yIHBhc3N3b3JkJyk7XG5cdFx0aWYgKCFjaGVja1Bhc3N3b3JkKHBhc3N3b3JkLCB1c2VyLnBhc3N3b3JkKSkgdGhyb3cgbmV3IEVycm9yRm9yYmlkZGVuKCdJbnZhbGlkIHVzZXJuYW1lIG9yIHBhc3N3b3JkJyk7XG5cdFx0Y29uc3Qgc2Vzc2lvbjpTZXNzaW9uID0geyBpZDogdXVpZGdlbigpLCB1c2VyX2lkOiB1c2VyLmlkIH07XG5cdFx0Y29uc3Qgc2Vzc2lvbk1vZGVsID0gbmV3IFNlc3Npb25Nb2RlbCgpO1xuXHRcdHJldHVybiBzZXNzaW9uTW9kZWwuc2F2ZShzZXNzaW9uLCB7IGlzTmV3OiB0cnVlIH0pO1xuXHR9XG5cbn1cbiJdfQ== diff --git a/Server/dist/app/controllers/UserController.js b/Server/dist/app/controllers/UserController.js new file mode 100644 index 0000000000..279ee41137 --- /dev/null +++ b/Server/dist/app/controllers/UserController.js @@ -0,0 +1,136 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var UserModel_1 = require("../models/UserModel"); +var BaseController_1 = require("./BaseController"); +var UserController = /** @class */ (function (_super) { + __extends(UserController, _super); + function UserController() { + return _super !== null && _super.apply(this, arguments) || this; + } + UserController.prototype.createUser = function (sessionId, user) { + return __awaiter(this, void 0, void 0, function () { + var owner, userModel, newUser; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.initSession(sessionId, true)]; + case 1: + owner = _a.sent(); + userModel = new UserModel_1.default({ userId: owner.id }); + return [4 /*yield*/, userModel.fromApiInput(user)]; + case 2: + newUser = _a.sent(); + return [4 /*yield*/, userModel.save(newUser)]; + case 3: + newUser = _a.sent(); + return [2 /*return*/, userModel.toApiOutput(newUser)]; + } + }); + }); + }; + UserController.prototype.getUser = function (sessionId, userId) { + return __awaiter(this, void 0, void 0, function () { + var owner, userModel, _a, _b; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: return [4 /*yield*/, this.initSession(sessionId)]; + case 1: + owner = _c.sent(); + userModel = new UserModel_1.default({ userId: owner.id }); + _b = (_a = userModel).toApiOutput; + return [4 /*yield*/, userModel.load(userId)]; + case 2: return [2 /*return*/, _b.apply(_a, [_c.sent()])]; + } + }); + }); + }; + UserController.prototype.updateUser = function (sessionId, user) { + return __awaiter(this, void 0, void 0, function () { + var owner, userModel, newUser; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.initSession(sessionId)]; + case 1: + owner = _a.sent(); + userModel = new UserModel_1.default({ userId: owner.id }); + return [4 /*yield*/, userModel.fromApiInput(user)]; + case 2: + newUser = _a.sent(); + return [4 /*yield*/, userModel.save(newUser, { isNew: false })]; + case 3: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + UserController.prototype.deleteUser = function (sessionId, userId) { + return __awaiter(this, void 0, void 0, function () { + var user, userModel; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.initSession(sessionId)]; + case 1: + user = _a.sent(); + userModel = new UserModel_1.default({ userId: user.id }); + return [4 /*yield*/, userModel.delete(userId)]; + case 2: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + return UserController; +}(BaseController_1.default)); +exports.default = UserController; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIlVzZXJDb250cm9sbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUNBLGlEQUE0QztBQUM1QyxtREFBOEM7QUFFOUM7SUFBNEMsa0NBQWM7SUFBMUQ7O0lBNkJBLENBQUM7SUEzQk0sbUNBQVUsR0FBaEIsVUFBaUIsU0FBZ0IsRUFBRSxJQUFTOzs7Ozs0QkFDN0IscUJBQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxTQUFTLEVBQUUsSUFBSSxDQUFDLEVBQUE7O3dCQUEvQyxLQUFLLEdBQUcsU0FBdUM7d0JBQy9DLFNBQVMsR0FBRyxJQUFJLG1CQUFTLENBQUMsRUFBRSxNQUFNLEVBQUUsS0FBSyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7d0JBQ3hDLHFCQUFNLFNBQVMsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLEVBQUE7O3dCQUE1QyxPQUFPLEdBQUcsU0FBa0M7d0JBQ3RDLHFCQUFNLFNBQVMsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUE7O3dCQUF2QyxPQUFPLEdBQUcsU0FBNkIsQ0FBQzt3QkFDeEMsc0JBQU8sU0FBUyxDQUFDLFdBQVcsQ0FBQyxPQUFPLENBQUMsRUFBQzs7OztLQUN0QztJQUVLLGdDQUFPLEdBQWIsVUFBYyxTQUFnQixFQUFFLE1BQWE7Ozs7OzRCQUM5QixxQkFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLFNBQVMsQ0FBQyxFQUFBOzt3QkFBekMsS0FBSyxHQUFHLFNBQWlDO3dCQUN6QyxTQUFTLEdBQUcsSUFBSSxtQkFBUyxDQUFDLEVBQUUsTUFBTSxFQUFFLEtBQUssQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO3dCQUMvQyxLQUFBLENBQUEsS0FBQSxTQUFTLENBQUEsQ0FBQyxXQUFXLENBQUE7d0JBQUMscUJBQU0sU0FBUyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBQTs0QkFBekQsc0JBQU8sY0FBc0IsU0FBNEIsRUFBQyxFQUFDOzs7O0tBQzNEO0lBRUssbUNBQVUsR0FBaEIsVUFBaUIsU0FBZ0IsRUFBRSxJQUFTOzs7Ozs0QkFDN0IscUJBQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxTQUFTLENBQUMsRUFBQTs7d0JBQXpDLEtBQUssR0FBRyxTQUFpQzt3QkFDekMsU0FBUyxHQUFHLElBQUksbUJBQVMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxLQUFLLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQzt3QkFDdEMscUJBQU0sU0FBUyxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsRUFBQTs7d0JBQTVDLE9BQU8sR0FBRyxTQUFrQzt3QkFDbEQscUJBQU0sU0FBUyxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLENBQUMsRUFBQTs7d0JBQS9DLFNBQStDLENBQUM7Ozs7O0tBQ2hEO0lBRUssbUNBQVUsR0FBaEIsVUFBaUIsU0FBZ0IsRUFBRSxNQUFhOzs7Ozs0QkFDbEMscUJBQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxTQUFTLENBQUMsRUFBQTs7d0JBQXhDLElBQUksR0FBRyxTQUFpQzt3QkFDeEMsU0FBUyxHQUFHLElBQUksbUJBQVMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQzt3QkFDckQscUJBQU0sU0FBUyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBQTs7d0JBQTlCLFNBQThCLENBQUM7Ozs7O0tBQy9CO0lBRUYscUJBQUM7QUFBRCxDQTdCQSxBQTZCQyxDQTdCMkMsd0JBQWMsR0E2QnpEIiwiZmlsZSI6IlVzZXJDb250cm9sbGVyLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgVXNlciB9IGZyb20gJy4uL2RiJztcbmltcG9ydCBVc2VyTW9kZWwgZnJvbSAnLi4vbW9kZWxzL1VzZXJNb2RlbCc7XG5pbXBvcnQgQmFzZUNvbnRyb2xsZXIgZnJvbSAnLi9CYXNlQ29udHJvbGxlcic7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFVzZXJDb250cm9sbGVyIGV4dGVuZHMgQmFzZUNvbnRyb2xsZXIge1xuXG5cdGFzeW5jIGNyZWF0ZVVzZXIoc2Vzc2lvbklkOnN0cmluZywgdXNlcjpVc2VyKTpQcm9taXNlPFVzZXI+IHtcblx0XHRjb25zdCBvd25lciA9IGF3YWl0IHRoaXMuaW5pdFNlc3Npb24oc2Vzc2lvbklkLCB0cnVlKTtcblx0XHRjb25zdCB1c2VyTW9kZWwgPSBuZXcgVXNlck1vZGVsKHsgdXNlcklkOiBvd25lci5pZCB9KTtcblx0XHRsZXQgbmV3VXNlciA9IGF3YWl0IHVzZXJNb2RlbC5mcm9tQXBpSW5wdXQodXNlcik7XG5cdFx0bmV3VXNlciA9IGF3YWl0IHVzZXJNb2RlbC5zYXZlKG5ld1VzZXIpO1xuXHRcdHJldHVybiB1c2VyTW9kZWwudG9BcGlPdXRwdXQobmV3VXNlcik7XG5cdH1cblxuXHRhc3luYyBnZXRVc2VyKHNlc3Npb25JZDpzdHJpbmcsIHVzZXJJZDpzdHJpbmcpOlByb21pc2U8VXNlcj4ge1xuXHRcdGNvbnN0IG93bmVyID0gYXdhaXQgdGhpcy5pbml0U2Vzc2lvbihzZXNzaW9uSWQpO1xuXHRcdGNvbnN0IHVzZXJNb2RlbCA9IG5ldyBVc2VyTW9kZWwoeyB1c2VySWQ6IG93bmVyLmlkIH0pO1xuXHRcdHJldHVybiB1c2VyTW9kZWwudG9BcGlPdXRwdXQoYXdhaXQgdXNlck1vZGVsLmxvYWQodXNlcklkKSk7XG5cdH1cblxuXHRhc3luYyB1cGRhdGVVc2VyKHNlc3Npb25JZDpzdHJpbmcsIHVzZXI6VXNlcik6UHJvbWlzZTx2b2lkPiB7XG5cdFx0Y29uc3Qgb3duZXIgPSBhd2FpdCB0aGlzLmluaXRTZXNzaW9uKHNlc3Npb25JZCk7XG5cdFx0Y29uc3QgdXNlck1vZGVsID0gbmV3IFVzZXJNb2RlbCh7IHVzZXJJZDogb3duZXIuaWQgfSk7XG5cdFx0Y29uc3QgbmV3VXNlciA9IGF3YWl0IHVzZXJNb2RlbC5mcm9tQXBpSW5wdXQodXNlcik7XG5cdFx0YXdhaXQgdXNlck1vZGVsLnNhdmUobmV3VXNlciwgeyBpc05ldzogZmFsc2UgfSk7XG5cdH1cblxuXHRhc3luYyBkZWxldGVVc2VyKHNlc3Npb25JZDpzdHJpbmcsIHVzZXJJZDpzdHJpbmcpOlByb21pc2U8dm9pZD4ge1xuXHRcdGNvbnN0IHVzZXIgPSBhd2FpdCB0aGlzLmluaXRTZXNzaW9uKHNlc3Npb25JZCk7XG5cdFx0Y29uc3QgdXNlck1vZGVsID0gbmV3IFVzZXJNb2RlbCh7IHVzZXJJZDogdXNlci5pZCB9KTtcblx0XHRhd2FpdCB1c2VyTW9kZWwuZGVsZXRlKHVzZXJJZCk7XG5cdH1cblxufVxuIl19 diff --git a/Server/dist/app/db.js b/Server/dist/app/db.js new file mode 100644 index 0000000000..1ba651cfeb --- /dev/null +++ b/Server/dist/app/db.js @@ -0,0 +1,66 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var nodeEnv = process.env.NODE_ENV || 'development'; +var knex = require('knex')({ + client: 'sqlite3', + connection: { + filename: __dirname + "/../../db-" + nodeEnv + ".sqlite", + }, + useNullAsDefault: true, + // Allow propery stack traces in case of an error, however + // it has a small performance overhead so only enable in testing and dev + asyncStackTraces: nodeEnv == 'development' || nodeEnv === 'testing', +}); +exports.default = knex; +var ItemAddressingType; +(function (ItemAddressingType) { + ItemAddressingType[ItemAddressingType["Id"] = 1] = "Id"; + ItemAddressingType[ItemAddressingType["Path"] = 2] = "Path"; +})(ItemAddressingType = exports.ItemAddressingType || (exports.ItemAddressingType = {})); +var ItemType; +(function (ItemType) { + ItemType[ItemType["File"] = 1] = "File"; + ItemType[ItemType["User"] = 2] = "User"; +})(ItemType = exports.ItemType || (exports.ItemType = {})); +exports.databaseSchema = { + users: { + id: { type: 'string' }, + email: { type: 'string' }, + password: { type: 'string' }, + is_admin: { type: 'number' }, + updated_time: { type: 'number' }, + created_time: { type: 'number' }, + }, + sessions: { + id: { type: 'string' }, + user_id: { type: 'string' }, + updated_time: { type: 'number' }, + created_time: { type: 'number' }, + }, + permissions: { + id: { type: 'string' }, + user_id: { type: 'string' }, + item_type: { type: 'number' }, + item_id: { type: 'string' }, + is_owner: { type: 'number' }, + can_read: { type: 'number' }, + can_write: { type: 'number' }, + updated_time: { type: 'number' }, + created_time: { type: 'number' }, + }, + files: { + id: { type: 'string' }, + name: { type: 'string' }, + content: { type: 'any' }, + mime_type: { type: 'string' }, + size: { type: 'number' }, + is_directory: { type: 'number' }, + is_root: { type: 'number' }, + parent_id: { type: 'string' }, + updated_time: { type: 'number' }, + created_time: { type: 'number' }, + }, +}; +// AUTO-GENERATED-TYPES + +//# sourceMappingURL=data:application/json;charset=utf8;base64,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SSxFQUFFLFFBQVEsRUFBRTtRQUM3QixZQUFZLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFO1FBQ2hDLFlBQVksRUFBRSxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUU7S0FDaEM7Q0FDRCxDQUFBO0FBQ0QsdUJBQXVCIiwiZmlsZSI6ImRiLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgS25leCBmcm9tICdrbmV4JztcblxuY29uc3Qgbm9kZUVudiA9IHByb2Nlc3MuZW52Lk5PREVfRU5WIHx8ICdkZXZlbG9wbWVudCc7XG5cbmxldCBrbmV4OktuZXggPSByZXF1aXJlKCdrbmV4Jykoe1xuXHRjbGllbnQ6ICdzcWxpdGUzJyxcblx0Y29ubmVjdGlvbjoge1xuXHRcdGZpbGVuYW1lOiBgJHtfX2Rpcm5hbWV9Ly4uLy4uL2RiLSR7bm9kZUVudn0uc3FsaXRlYCxcblx0fSxcblx0dXNlTnVsbEFzRGVmYXVsdDogdHJ1ZSxcblx0Ly8gQWxsb3cgcHJvcGVyeSBzdGFjayB0cmFjZXMgaW4gY2FzZSBvZiBhbiBlcnJvciwgaG93ZXZlclxuXHQvLyBpdCBoYXMgYSBzbWFsbCBwZXJmb3JtYW5jZSBvdmVyaGVhZCBzbyBvbmx5IGVuYWJsZSBpbiB0ZXN0aW5nIGFuZCBkZXZcblx0YXN5bmNTdGFja1RyYWNlczogbm9kZUVudiA9PSAnZGV2ZWxvcG1lbnQnIHx8IG5vZGVFbnYgPT09ICd0ZXN0aW5nJyxcblx0Ly8gZGVidWc6IG5vZGVFbnYgPT0gJ2RldmVsb3BtZW50JyB8fCBub2RlRW52ID09PSAndGVzdGluZycsXG59KTtcblxuZXhwb3J0IGRlZmF1bHQga25leDtcblxuZXhwb3J0IGVudW0gSXRlbUFkZHJlc3NpbmdUeXBlIHtcblx0SWQgPSAxLFxuXHRQYXRoLFxufVxuXG5leHBvcnQgaW50ZXJmYWNlIEl0ZW1JZCB7XG5cdHZhbHVlOiBzdHJpbmdcblx0YWRkcmVzc2luZ1R5cGU6IEl0ZW1BZGRyZXNzaW5nVHlwZVxufVxuXG5leHBvcnQgZW51bSBJdGVtVHlwZSB7XG4gICAgRmlsZSA9IDEsXG4gICAgVXNlcixcbn1cblxuZXhwb3J0IGludGVyZmFjZSBXaXRoRGF0ZXMge1xuXHR1cGRhdGVkX3RpbWU/OiBudW1iZXJcblx0Y3JlYXRlZF90aW1lPzogbnVtYmVyXG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgV2l0aFV1aWQge1xuXHRpZD86IHN0cmluZ1xufVxuXG5pbnRlcmZhY2UgRGF0YWJhc2VUYWJsZUNvbHVtbiB7XG5cdHR5cGU6IHN0cmluZ1xufVxuXG5pbnRlcmZhY2UgRGF0YWJhc2VUYWJsZSB7XG5cdFtrZXk6c3RyaW5nXTogRGF0YWJhc2VUYWJsZUNvbHVtblxufVxuXG5pbnRlcmZhY2UgRGF0YWJhc2VUYWJsZXMge1xuXHRba2V5OnN0cmluZ106IERhdGFiYXNlVGFibGVcbn1cblxuLy8gQVVUTy1HRU5FUkFURUQtVFlQRVNcbi8vIEF1dG8tZ2VuZXJhdGVkIHVzaW5nIGBucG0gcnVuIGdlbmVyYXRlLXR5cGVzYFxuZXhwb3J0IGludGVyZmFjZSBVc2VyIGV4dGVuZHMgV2l0aERhdGVzLCBXaXRoVXVpZCB7XG5cdGVtYWlsPzogc3RyaW5nXG5cdHBhc3N3b3JkPzogc3RyaW5nXG5cdGlzX2FkbWluPzogbnVtYmVyXG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgU2Vzc2lvbiBleHRlbmRzIFdpdGhEYXRlcywgV2l0aFV1aWQge1xuXHR1c2VyX2lkPzogc3RyaW5nXG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUGVybWlzc2lvbiBleHRlbmRzIFdpdGhEYXRlcywgV2l0aFV1aWQge1xuXHR1c2VyX2lkPzogc3RyaW5nXG5cdGl0ZW1fdHlwZT86IEl0ZW1UeXBlXG5cdGl0ZW1faWQ/OiBzdHJpbmdcblx0aXNfb3duZXI/OiBudW1iZXJcblx0Y2FuX3JlYWQ/OiBudW1iZXJcblx0Y2FuX3dyaXRlPzogbnVtYmVyXG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgRmlsZSBleHRlbmRzIFdpdGhEYXRlcywgV2l0aFV1aWQge1xuXHRuYW1lPzogc3RyaW5nXG5cdGNvbnRlbnQ/OiBCdWZmZXJcblx0bWltZV90eXBlPzogc3RyaW5nXG5cdHNpemU/OiBudW1iZXJcblx0aXNfZGlyZWN0b3J5PzogbnVtYmVyXG5cdGlzX3Jvb3Q/OiBudW1iZXJcblx0cGFyZW50X2lkPzogc3RyaW5nXG59XG5cbmV4cG9ydCBjb25zdCBkYXRhYmFzZVNjaGVtYTpEYXRhYmFzZVRhYmxlcyA9IHtcblx0dXNlcnM6IHtcblx0XHRpZDogeyB0eXBlOiAnc3RyaW5nJyB9LFxuXHRcdGVtYWlsOiB7IHR5cGU6ICdzdHJpbmcnIH0sXG5cdFx0cGFzc3dvcmQ6IHsgdHlwZTogJ3N0cmluZycgfSxcblx0XHRpc19hZG1pbjogeyB0eXBlOiAnbnVtYmVyJyB9LFxuXHRcdHVwZGF0ZWRfdGltZTogeyB0eXBlOiAnbnVtYmVyJyB9LFxuXHRcdGNyZWF0ZWRfdGltZTogeyB0eXBlOiAnbnVtYmVyJyB9LFxuXHR9LFxuXHRzZXNzaW9uczoge1xuXHRcdGlkOiB7IHR5cGU6ICdzdHJpbmcnIH0sXG5cdFx0dXNlcl9pZDogeyB0eXBlOiAnc3RyaW5nJyB9LFxuXHRcdHVwZGF0ZWRfdGltZTogeyB0eXBlOiAnbnVtYmVyJyB9LFxuXHRcdGNyZWF0ZWRfdGltZTogeyB0eXBlOiAnbnVtYmVyJyB9LFxuXHR9LFxuXHRwZXJtaXNzaW9uczoge1xuXHRcdGlkOiB7IHR5cGU6ICdzdHJpbmcnIH0sXG5cdFx0dXNlcl9pZDogeyB0eXBlOiAnc3RyaW5nJyB9LFxuXHRcdGl0ZW1fdHlwZTogeyB0eXBlOiAnbnVtYmVyJyB9LFxuXHRcdGl0ZW1faWQ6IHsgdHlwZTogJ3N0cmluZycgfSxcblx0XHRpc19vd25lcjogeyB0eXBlOiAnbnVtYmVyJyB9LFxuXHRcdGNhbl9yZWFkOiB7IHR5cGU6ICdudW1iZXInIH0sXG5cdFx0Y2FuX3dyaXRlOiB7IHR5cGU6ICdudW1iZXInIH0sXG5cdFx0dXBkYXRlZF90aW1lOiB7IHR5cGU6ICdudW1iZXInIH0sXG5cdFx0Y3JlYXRlZF90aW1lOiB7IHR5cGU6ICdudW1iZXInIH0sXG5cdH0sXG5cdGZpbGVzOiB7XG5cdFx0aWQ6IHsgdHlwZTogJ3N0cmluZycgfSxcblx0XHRuYW1lOiB7IHR5cGU6ICdzdHJpbmcnIH0sXG5cdFx0Y29udGVudDogeyB0eXBlOiAnYW55JyB9LFxuXHRcdG1pbWVfdHlwZTogeyB0eXBlOiAnc3RyaW5nJyB9LFxuXHRcdHNpemU6IHsgdHlwZTogJ251bWJlcicgfSxcblx0XHRpc19kaXJlY3Rvcnk6IHsgdHlwZTogJ251bWJlcicgfSxcblx0XHRpc19yb290OiB7IHR5cGU6ICdudW1iZXInIH0sXG5cdFx0cGFyZW50X2lkOiB7IHR5cGU6ICdzdHJpbmcnIH0sXG5cdFx0dXBkYXRlZF90aW1lOiB7IHR5cGU6ICdudW1iZXInIH0sXG5cdFx0Y3JlYXRlZF90aW1lOiB7IHR5cGU6ICdudW1iZXInIH0sXG5cdH0sXG59XG4vLyBBVVRPLUdFTkVSQVRFRC1UWVBFU1xuIl19 diff --git a/Server/dist/app/models/BaseModel.js b/Server/dist/app/models/BaseModel.js new file mode 100644 index 0000000000..8a5a22cf80 --- /dev/null +++ b/Server/dist/app/models/BaseModel.js @@ -0,0 +1,275 @@ +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var db_1 = require("../db"); +var dbUtils_1 = require("../utils/dbUtils"); +var uuidgen_1 = require("../utils/uuidgen"); +var errors_1 = require("../utils/errors"); +var cache_1 = require("../utils/cache"); +var BaseModel = /** @class */ (function () { + function BaseModel(options) { + if (options === void 0) { options = null; } + this.options_ = null; + this.defaultFields_ = []; + this.options_ = Object.assign({}, options); + if ('userId' in this.options && !this.options.userId) + throw new Error('If userId is set, it cannot be null'); + } + Object.defineProperty(BaseModel.prototype, "options", { + get: function () { + return this.options_; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(BaseModel.prototype, "userId", { + get: function () { + return this.options.userId; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(BaseModel.prototype, "db", { + get: function () { + if (dbUtils_1.transactionHandler.activeTransaction) + return dbUtils_1.transactionHandler.activeTransaction; + return db_1.default; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(BaseModel.prototype, "defaultFields", { + get: function () { + if (!this.defaultFields_.length) { + this.defaultFields_ = Object.keys(db_1.databaseSchema[this.tableName]); + } + return this.defaultFields_.slice(); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(BaseModel.prototype, "tableName", { + get: function () { + throw new Error('Not implemented'); + }, + enumerable: true, + configurable: true + }); + BaseModel.prototype.hasDateProperties = function () { + return true; + }; + BaseModel.prototype.startTransaction = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, dbUtils_1.transactionHandler.start()]; + }); + }); + }; + BaseModel.prototype.commitTransaction = function (txIndex) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, dbUtils_1.transactionHandler.commit(txIndex)]; + }); + }); + }; + BaseModel.prototype.rollbackTransaction = function (txIndex) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, dbUtils_1.transactionHandler.rollback(txIndex)]; + }); + }); + }; + BaseModel.prototype.all = function () { + return __awaiter(this, void 0, void 0, function () { + var _a; + return __generator(this, function (_b) { + return [2 /*return*/, (_a = this.db(this.tableName)).select.apply(_a, this.defaultFields)]; + }); + }); + }; + BaseModel.prototype.fromApiInput = function (object) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, object]; + }); + }); + }; + BaseModel.prototype.toApiOutput = function (object) { + return __assign({}, object); + }; + BaseModel.prototype.validate = function (object, options) { + if (options === void 0) { options = {}; } + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + if (!options.isNew && !object.id) + throw new errors_1.ErrorUnprocessableEntity('id is missing'); + return [2 /*return*/, object]; + }); + }); + }; + BaseModel.prototype.isNew = function (object, options) { + if (options.isNew === false) + return false; + if (options.isNew === true) + return true; + return !object.id; + }; + BaseModel.prototype.save = function (object, options) { + if (options === void 0) { options = {}; } + return __awaiter(this, void 0, void 0, function () { + var toSave, isNew, timestamp, objectId, updatedCount; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!object) + throw new Error('Object cannot be empty'); + toSave = Object.assign({}, object); + isNew = this.isNew(object, options); + if (isNew && !toSave.id) { + toSave.id = uuidgen_1.default(); + } + if (this.hasDateProperties()) { + timestamp = Date.now(); + if (isNew) { + toSave.created_time = timestamp; + } + toSave.updated_time = timestamp; + } + if (!(options.skipValidation !== true)) return [3 /*break*/, 2]; + return [4 /*yield*/, this.validate(object, { isNew: isNew })]; + case 1: + object = _a.sent(); + _a.label = 2; + case 2: + if (!isNew) return [3 /*break*/, 4]; + return [4 /*yield*/, this.db(this.tableName).insert(toSave)]; + case 3: + _a.sent(); + return [3 /*break*/, 7]; + case 4: + objectId = toSave.id; + if (!objectId) + throw new Error('Missing "id" property'); + return [4 /*yield*/, cache_1.default.delete(objectId)]; + case 5: + _a.sent(); + delete toSave.id; + return [4 /*yield*/, this.db(this.tableName).update(toSave).where({ id: objectId })]; + case 6: + updatedCount = _a.sent(); + toSave.id = objectId; + // Sanity check: + if (updatedCount !== 1) + throw new errors_1.ErrorBadRequest("one row should have been updated, but " + updatedCount + " row(s) were updated"); + _a.label = 7; + case 7: return [2 /*return*/, toSave]; + } + }); + }); + }; + BaseModel.prototype.load = function (id) { + return __awaiter(this, void 0, void 0, function () { + var cached; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!id) + throw new Error('id cannot be empty'); + if (!(typeof id === 'string')) + throw new Error('ItemId support must be implemented in sub-class'); + return [4 /*yield*/, cache_1.default.object(id)]; + case 1: + cached = _a.sent(); + if (cached) + return [2 /*return*/, cached]; + return [4 /*yield*/, this.db(this.tableName).select(this.defaultFields).where({ id: id }).first()]; + case 2: + cached = _a.sent(); + return [4 /*yield*/, cache_1.default.setObject(id, cached)]; + case 3: + _a.sent(); + return [2 /*return*/, cached]; + } + }); + }); + }; + BaseModel.prototype.delete = function (id) { + return __awaiter(this, void 0, void 0, function () { + var ids, query, i, deletedCount; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!id) + throw new Error('id cannot be empty'); + if (!(typeof id === 'string') && !Array.isArray(id)) + throw new Error('ItemId support must be implemented in sub-class'); + ids = typeof id === 'string' ? [id] : id; + if (!ids.length) + throw new Error('no id provided'); + query = this.db(this.tableName).where({ id: ids[0] }); + for (i = 1; i < ids.length; i++) + query.orWhere({ id: ids[i] }); + return [4 /*yield*/, cache_1.default.delete(ids)]; + case 1: + _a.sent(); + return [4 /*yield*/, query.del()]; + case 2: + deletedCount = _a.sent(); + if (deletedCount !== ids.length) + throw new Error(ids.length + " row(s) should have been deleted by " + deletedCount + " row(s) were deleted"); + return [2 /*return*/]; + } + }); + }); + }; + return BaseModel; +}()); +exports.default = BaseModel; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,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QUFFLENBQUMsRUFBRTs0QkFBRSxLQUFLLENBQUMsT0FBTyxDQUFDLEVBQUUsRUFBRSxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUM7d0JBRW5FLHFCQUFNLGVBQUssQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEVBQUE7O3dCQUF2QixTQUF1QixDQUFDO3dCQUVILHFCQUFNLEtBQUssQ0FBQyxHQUFHLEVBQUUsRUFBQTs7d0JBQWhDLFlBQVksR0FBRyxTQUFpQjt3QkFDdEMsSUFBSSxZQUFZLEtBQUssR0FBRyxDQUFDLE1BQU07NEJBQUUsTUFBTSxJQUFJLEtBQUssQ0FBSSxHQUFHLENBQUMsTUFBTSw0Q0FBdUMsWUFBWSx5QkFBc0IsQ0FBQyxDQUFDOzs7OztLQUN6STtJQUVGLGdCQUFDO0FBQUQsQ0EvSUEsQUErSUMsSUFBQSIsImZpbGUiOiJCYXNlTW9kZWwuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgZGIsIHsgV2l0aERhdGVzLCBXaXRoVXVpZCwgRmlsZSwgVXNlciwgU2Vzc2lvbiwgUGVybWlzc2lvbiwgZGF0YWJhc2VTY2hlbWEsIEl0ZW1JZCB9IGZyb20gJy4uL2RiJztcbmltcG9ydCAqIGFzIEtuZXggZnJvbSAna25leCc7XG5pbXBvcnQgeyB0cmFuc2FjdGlvbkhhbmRsZXIgfSBmcm9tICcuLi91dGlscy9kYlV0aWxzJztcbmltcG9ydCB1dWlkZ2VuIGZyb20gJy4uL3V0aWxzL3V1aWRnZW4nO1xuaW1wb3J0IHsgRXJyb3JVbnByb2Nlc3NhYmxlRW50aXR5LCBFcnJvckJhZFJlcXVlc3QgfSBmcm9tICcuLi91dGlscy9lcnJvcnMnO1xuaW1wb3J0IGNhY2hlIGZyb20gJy4uL3V0aWxzL2NhY2hlJztcblxuZXhwb3J0IGludGVyZmFjZSBNb2RlbE9wdGlvbnMge1xuXHR1c2VySWQ/OiBzdHJpbmdcbn1cblxuZXhwb3J0IGludGVyZmFjZSBTYXZlT3B0aW9ucyB7XG5cdGlzTmV3PzogYm9vbGVhbixcblx0c2tpcFZhbGlkYXRpb24/OiBib29sZWFuLFxufVxuXG5leHBvcnQgaW50ZXJmYWNlIFZhbGlkYXRlT3B0aW9ucyB7XG5cdGlzTmV3PzogYm9vbGVhblxufVxuXG5leHBvcnQgZGVmYXVsdCBhYnN0cmFjdCBjbGFzcyBCYXNlTW9kZWwge1xuXG5cdHByaXZhdGUgb3B0aW9uc186TW9kZWxPcHRpb25zID0gbnVsbDtcblx0cHJpdmF0ZSBkZWZhdWx0RmllbGRzXzpzdHJpbmdbXSA9IFtdO1xuXG5cdGNvbnN0cnVjdG9yKG9wdGlvbnM6TW9kZWxPcHRpb25zID0gbnVsbCkge1xuXHRcdHRoaXMub3B0aW9uc18gPSBPYmplY3QuYXNzaWduKHt9LCBvcHRpb25zKTtcblxuXHRcdGlmICgndXNlcklkJyBpbiB0aGlzLm9wdGlvbnMgJiYgIXRoaXMub3B0aW9ucy51c2VySWQpIHRocm93IG5ldyBFcnJvcignSWYgdXNlcklkIGlzIHNldCwgaXQgY2Fubm90IGJlIG51bGwnKTtcblx0fVxuXG5cdGdldCBvcHRpb25zKCk6TW9kZWxPcHRpb25zIHtcblx0XHRyZXR1cm4gdGhpcy5vcHRpb25zXztcblx0fVxuXG5cdGdldCB1c2VySWQoKTpzdHJpbmcge1xuXHRcdHJldHVybiB0aGlzLm9wdGlvbnMudXNlcklkO1xuXHR9XG5cblx0Z2V0IGRiKCk6S25leDxhbnksIGFueVtdPiB7XG5cdFx0aWYgKHRyYW5zYWN0aW9uSGFuZGxlci5hY3RpdmVUcmFuc2FjdGlvbikgcmV0dXJuIHRyYW5zYWN0aW9uSGFuZGxlci5hY3RpdmVUcmFuc2FjdGlvbjtcblx0XHRyZXR1cm4gZGI7XG5cdH1cblxuXHRnZXQgZGVmYXVsdEZpZWxkcygpOnN0cmluZ1tdIHtcblx0XHRpZiAoIXRoaXMuZGVmYXVsdEZpZWxkc18ubGVuZ3RoKSB7XG5cdFx0XHR0aGlzLmRlZmF1bHRGaWVsZHNfID0gT2JqZWN0LmtleXMoZGF0YWJhc2VTY2hlbWFbdGhpcy50YWJsZU5hbWVdKTtcblx0XHR9XG5cdFx0cmV0dXJuIHRoaXMuZGVmYXVsdEZpZWxkc18uc2xpY2UoKTtcblx0fVxuXG5cdGdldCB0YWJsZU5hbWUoKTpzdHJpbmcge1xuXHRcdHRocm93IG5ldyBFcnJvcignTm90IGltcGxlbWVudGVkJyk7XG5cdH1cblxuXHRoYXNEYXRlUHJvcGVydGllcygpOmJvb2xlYW4ge1xuXHRcdHJldHVybiB0cnVlO1xuXHR9XG5cblx0YXN5bmMgc3RhcnRUcmFuc2FjdGlvbigpOlByb21pc2U8bnVtYmVyPiB7XG5cdFx0cmV0dXJuIHRyYW5zYWN0aW9uSGFuZGxlci5zdGFydCgpO1xuXHR9XG5cblx0YXN5bmMgY29tbWl0VHJhbnNhY3Rpb24odHhJbmRleDpudW1iZXIpOlByb21pc2U8dm9pZD4ge1xuXHRcdHJldHVybiB0cmFuc2FjdGlvbkhhbmRsZXIuY29tbWl0KHR4SW5kZXgpO1xuXHR9XG5cblx0YXN5bmMgcm9sbGJhY2tUcmFuc2FjdGlvbih0eEluZGV4Om51bWJlcik6UHJvbWlzZTx2b2lkPiB7XG5cdFx0cmV0dXJuIHRyYW5zYWN0aW9uSGFuZGxlci5yb2xsYmFjayh0eEluZGV4KTtcblx0fVxuXG5cdGFzeW5jIGFsbCgpOlByb21pc2U8RmlsZVtdIHwgVXNlcltdIHwgU2Vzc2lvbltdIHwgUGVybWlzc2lvbltdPiB7XG5cdFx0cmV0dXJuIHRoaXMuZGIodGhpcy50YWJsZU5hbWUpLnNlbGVjdCguLi50aGlzLmRlZmF1bHRGaWVsZHMpO1xuXHR9XG5cblx0YXN5bmMgZnJvbUFwaUlucHV0KG9iamVjdDpGaWxlIHwgVXNlciB8IFNlc3Npb24gfCBQZXJtaXNzaW9uKTpQcm9taXNlPEZpbGUgfCBVc2VyIHwgU2Vzc2lvbiB8IFBlcm1pc3Npb24+IHtcblx0XHRyZXR1cm4gb2JqZWN0O1xuXHR9XG5cblx0dG9BcGlPdXRwdXQob2JqZWN0OmFueSk6YW55IHtcblx0XHRyZXR1cm4geyAuLi5vYmplY3QgfTtcblx0fVxuXG5cdGFzeW5jIHZhbGlkYXRlKG9iamVjdDpGaWxlIHwgVXNlciB8IFNlc3Npb24gfCBQZXJtaXNzaW9uLCBvcHRpb25zOlZhbGlkYXRlT3B0aW9ucyA9IHt9KTpQcm9taXNlPEZpbGUgfCBVc2VyIHwgU2Vzc2lvbiB8IFBlcm1pc3Npb24+IHtcblx0XHRpZiAoIW9wdGlvbnMuaXNOZXcgJiYgIShvYmplY3QgYXMgV2l0aFV1aWQpLmlkKSB0aHJvdyBuZXcgRXJyb3JVbnByb2Nlc3NhYmxlRW50aXR5KCdpZCBpcyBtaXNzaW5nJyk7XG5cdFx0cmV0dXJuIG9iamVjdDtcblx0fVxuXG5cdGlzTmV3KG9iamVjdDpGaWxlIHwgVXNlciB8IFNlc3Npb24gfCBQZXJtaXNzaW9uLCBvcHRpb25zOlNhdmVPcHRpb25zKTpib29sZWFuIHtcblx0XHRpZiAob3B0aW9ucy5pc05ldyA9PT0gZmFsc2UpIHJldHVybiBmYWxzZTtcblx0XHRpZiAob3B0aW9ucy5pc05ldyA9PT0gdHJ1ZSkgcmV0dXJuIHRydWU7XG5cdFx0cmV0dXJuICEob2JqZWN0IGFzIFdpdGhVdWlkKS5pZDtcblx0fVxuXG5cdGFzeW5jIHNhdmUob2JqZWN0OkZpbGUgfCBVc2VyIHwgU2Vzc2lvbiB8IFBlcm1pc3Npb24sIG9wdGlvbnM6U2F2ZU9wdGlvbnMgPSB7fSk6UHJvbWlzZTxGaWxlIHwgVXNlciB8IFNlc3Npb24gfCBQZXJtaXNzaW9uPiB7XG5cdFx0aWYgKCFvYmplY3QpIHRocm93IG5ldyBFcnJvcignT2JqZWN0IGNhbm5vdCBiZSBlbXB0eScpO1xuXG5cdFx0Y29uc3QgdG9TYXZlID0gT2JqZWN0LmFzc2lnbih7fSwgb2JqZWN0KTtcblxuXHRcdGNvbnN0IGlzTmV3ID0gdGhpcy5pc05ldyhvYmplY3QsIG9wdGlvbnMpO1xuXG5cdFx0aWYgKGlzTmV3ICYmICEodG9TYXZlIGFzIFdpdGhVdWlkKS5pZCkge1xuXHRcdFx0KHRvU2F2ZSBhcyBXaXRoVXVpZCkuaWQgPSB1dWlkZ2VuKCk7XG5cdFx0fVxuXG5cdFx0aWYgKHRoaXMuaGFzRGF0ZVByb3BlcnRpZXMoKSkge1xuXHRcdFx0Y29uc3QgdGltZXN0YW1wID0gRGF0ZS5ub3coKTtcblx0XHRcdGlmIChpc05ldykge1xuXHRcdFx0XHQodG9TYXZlIGFzIFdpdGhEYXRlcykuY3JlYXRlZF90aW1lID0gdGltZXN0YW1wO1xuXHRcdFx0fVxuXHRcdFx0KHRvU2F2ZSBhcyBXaXRoRGF0ZXMpLnVwZGF0ZWRfdGltZSA9IHRpbWVzdGFtcDtcblx0XHR9XG5cblx0XHRpZiAob3B0aW9ucy5za2lwVmFsaWRhdGlvbiAhPT0gdHJ1ZSkgb2JqZWN0ID0gYXdhaXQgdGhpcy52YWxpZGF0ZShvYmplY3QsIHsgaXNOZXc6IGlzTmV3IH0pO1xuXG5cdFx0aWYgKGlzTmV3KSB7XG5cdFx0XHRhd2FpdCB0aGlzLmRiKHRoaXMudGFibGVOYW1lKS5pbnNlcnQodG9TYXZlKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0Y29uc3Qgb2JqZWN0SWQ6c3RyaW5nID0gKHRvU2F2ZSBhcyBXaXRoVXVpZCkuaWQ7XG5cdFx0XHRpZiAoIW9iamVjdElkKSB0aHJvdyBuZXcgRXJyb3IoJ01pc3NpbmcgXCJpZFwiIHByb3BlcnR5Jyk7XG5cdFx0XHRhd2FpdCBjYWNoZS5kZWxldGUob2JqZWN0SWQpO1xuXHRcdFx0ZGVsZXRlICh0b1NhdmUgYXMgV2l0aFV1aWQpLmlkO1xuXHRcdFx0Y29uc3QgdXBkYXRlZENvdW50Om51bWJlciA9IGF3YWl0IHRoaXMuZGIodGhpcy50YWJsZU5hbWUpLnVwZGF0ZSh0b1NhdmUpLndoZXJlKHtpZDogb2JqZWN0SWQgfSk7XG5cdFx0XHR0b1NhdmUuaWQgPSBvYmplY3RJZDtcblxuXHRcdFx0Ly8gU2FuaXR5IGNoZWNrOlxuXHRcdFx0aWYgKHVwZGF0ZWRDb3VudCAhPT0gMSkgdGhyb3cgbmV3IEVycm9yQmFkUmVxdWVzdChgb25lIHJvdyBzaG91bGQgaGF2ZSBiZWVuIHVwZGF0ZWQsIGJ1dCAke3VwZGF0ZWRDb3VudH0gcm93KHMpIHdlcmUgdXBkYXRlZGApO1xuXHRcdH1cblxuXHRcdHJldHVybiB0b1NhdmU7XG5cdH1cblxuXHRhc3luYyBsb2FkKGlkOnN0cmluZyB8IEl0ZW1JZCk6UHJvbWlzZTxGaWxlIHwgVXNlciB8IFNlc3Npb24gfCBQZXJtaXNzaW9uPiB7XG5cdFx0aWYgKCFpZCkgdGhyb3cgbmV3IEVycm9yKCdpZCBjYW5ub3QgYmUgZW1wdHknKTtcblxuXHRcdGlmICghKHR5cGVvZiBpZCA9PT0gJ3N0cmluZycpKSB0aHJvdyBuZXcgRXJyb3IoJ0l0ZW1JZCBzdXBwb3J0IG11c3QgYmUgaW1wbGVtZW50ZWQgaW4gc3ViLWNsYXNzJyk7XG5cblx0XHRsZXQgY2FjaGVkOm9iamVjdCA9IGF3YWl0IGNhY2hlLm9iamVjdChpZCk7XG5cdFx0aWYgKGNhY2hlZCkgcmV0dXJuIGNhY2hlZDtcblxuXHRcdGNhY2hlZCA9IGF3YWl0IHRoaXMuZGIodGhpcy50YWJsZU5hbWUpLnNlbGVjdCh0aGlzLmRlZmF1bHRGaWVsZHMpLndoZXJlKHsgaWQ6IGlkIH0pLmZpcnN0KCk7XG5cdFx0YXdhaXQgY2FjaGUuc2V0T2JqZWN0KGlkLCBjYWNoZWQpO1xuXHRcdHJldHVybiBjYWNoZWQ7XG5cdH1cblxuXHRhc3luYyBkZWxldGUoaWQ6c3RyaW5nIHwgc3RyaW5nW10gfCBJdGVtSWQpOlByb21pc2U8dm9pZD4ge1xuXHRcdGlmICghaWQpIHRocm93IG5ldyBFcnJvcignaWQgY2Fubm90IGJlIGVtcHR5Jyk7XG5cblx0XHRpZiAoISh0eXBlb2YgaWQgPT09ICdzdHJpbmcnKSAmJiAhQXJyYXkuaXNBcnJheShpZCkpIHRocm93IG5ldyBFcnJvcignSXRlbUlkIHN1cHBvcnQgbXVzdCBiZSBpbXBsZW1lbnRlZCBpbiBzdWItY2xhc3MnKTtcblxuXHRcdGNvbnN0IGlkcyA9IHR5cGVvZiBpZCA9PT0gJ3N0cmluZycgPyBbaWRdIDogaWQ7XG5cblx0XHRpZiAoIWlkcy5sZW5ndGgpIHRocm93IG5ldyBFcnJvcignbm8gaWQgcHJvdmlkZWQnKTtcblxuXHRcdGNvbnN0IHF1ZXJ5ID0gdGhpcy5kYih0aGlzLnRhYmxlTmFtZSkud2hlcmUoeyBpZDogaWRzWzBdIH0pO1xuXHRcdGZvciAobGV0IGkgPSAxOyBpIDwgaWRzLmxlbmd0aDsgaSsrKSBxdWVyeS5vcldoZXJlKHsgaWQ6IGlkc1tpXSB9KTtcblxuXHRcdGF3YWl0IGNhY2hlLmRlbGV0ZShpZHMpO1xuXG5cdFx0Y29uc3QgZGVsZXRlZENvdW50ID0gYXdhaXQgcXVlcnkuZGVsKCk7XG5cdFx0aWYgKGRlbGV0ZWRDb3VudCAhPT0gaWRzLmxlbmd0aCkgdGhyb3cgbmV3IEVycm9yKGAke2lkcy5sZW5ndGh9IHJvdyhzKSBzaG91bGQgaGF2ZSBiZWVuIGRlbGV0ZWQgYnkgJHtkZWxldGVkQ291bnR9IHJvdyhzKSB3ZXJlIGRlbGV0ZWRgKTtcblx0fVxuXG59XG4iXX0= diff --git a/Server/dist/app/models/FileModel.js b/Server/dist/app/models/FileModel.js new file mode 100644 index 0000000000..f47985e8fb --- /dev/null +++ b/Server/dist/app/models/FileModel.js @@ -0,0 +1,504 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseModel_1 = require("./BaseModel"); +var PermissionModel_1 = require("./PermissionModel"); +var db_1 = require("../db"); +var errors_1 = require("../utils/errors"); +var uuidgen_1 = require("../utils/uuidgen"); +var routeUtils_1 = require("../utils/routeUtils"); +var nodeEnv = process.env.NODE_ENV || 'development'; +var FileModel = /** @class */ (function (_super) { + __extends(FileModel, _super); + function FileModel() { + return _super !== null && _super.apply(this, arguments) || this; + } + Object.defineProperty(FileModel.prototype, "tableName", { + get: function () { + return 'files'; + }, + enumerable: true, + configurable: true + }); + FileModel.prototype.userRootFile = function () { + return __awaiter(this, void 0, void 0, function () { + var r; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.db(this.tableName).select('files.id').from(this.tableName).leftJoin('permissions', 'permissions.item_id', 'files.id').where({ + 'item_type': db_1.ItemType.File, + 'user_id': this.userId, + 'is_root': 1, + }).first()]; + case 1: + r = _a.sent(); + if (!r) + return [2 /*return*/, null]; + return [2 /*return*/, this.load(r.id)]; + } + }); + }); + }; + FileModel.prototype.userRootFileId = function () { + return __awaiter(this, void 0, void 0, function () { + var r; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.userRootFile()]; + case 1: + r = _a.sent(); + return [2 /*return*/, r ? r.id : '']; + } + }); + }); + }; + FileModel.prototype.fileOwnerId = function (fileId) { + return __awaiter(this, void 0, void 0, function () { + var r; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.db('permissions').select('permissions.user_id').where({ + 'item_type': db_1.ItemType.File, + 'item_id': fileId, + 'is_owner': 1, + }).first()]; + case 1: + r = _a.sent(); + if (!r) + return [2 /*return*/, null]; + return [2 /*return*/, r.user_id]; + } + }); + }); + }; + Object.defineProperty(FileModel.prototype, "defaultFields", { + get: function () { + return Object.keys(db_1.databaseSchema[this.tableName]).filter(function (f) { return f !== 'content'; }); + }, + enumerable: true, + configurable: true + }); + FileModel.prototype.allByParent = function (parentId) { + return __awaiter(this, void 0, void 0, function () { + var _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (!!parentId) return [3 /*break*/, 2]; + return [4 /*yield*/, this.userRootFileId()]; + case 1: + parentId = _b.sent(); + _b.label = 2; + case 2: return [2 /*return*/, (_a = this.db(this.tableName)).select.apply(_a, this.defaultFields).where({ parent_id: parentId })]; + } + }); + }); + }; + FileModel.prototype.fileByName = function (parentId, name) { + return __awaiter(this, void 0, void 0, function () { + var _a; + return __generator(this, function (_b) { + return [2 /*return*/, (_a = this.db(this.tableName)).select.apply(_a, this.defaultFields).where({ + parent_id: parentId, + name: name, + }).first()]; + }); + }); + }; + FileModel.prototype.validate = function (object, options) { + if (options === void 0) { options = {}; } + return __awaiter(this, void 0, void 0, function () { + var file, parentId, invalidParentError, parentFile, error_1, permissionModel, canWrite, existingFile; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + file = object; + if (options.isNew) { + if (!file.is_root && !file.name) + throw new errors_1.ErrorUnprocessableEntity('name cannot be empty'); + } + else { + if ('name' in file && !file.name) + throw new errors_1.ErrorUnprocessableEntity('name cannot be empty'); + } + parentId = file.parent_id; + if (!!parentId) return [3 /*break*/, 2]; + return [4 /*yield*/, this.userRootFileId()]; + case 1: + parentId = _a.sent(); + _a.label = 2; + case 2: + if (!('parent_id' in file && !file.is_root)) return [3 /*break*/, 8]; + invalidParentError = function (extraInfo) { + var msg = "Invalid parent ID or no permission to write to it: " + parentId; + if (nodeEnv !== 'production') + msg += " (" + extraInfo + ")"; + return new errors_1.ErrorForbidden(msg); + }; + if (!parentId) + throw invalidParentError('No parent ID'); + _a.label = 3; + case 3: + _a.trys.push([3, 5, , 6]); + return [4 /*yield*/, this.load(parentId)]; + case 4: + parentFile = _a.sent(); + if (!parentFile) + throw invalidParentError('Cannot load parent file'); + if (!parentFile.is_directory) + throw invalidParentError('Specified parent is not a directory'); + return [3 /*break*/, 6]; + case 5: + error_1 = _a.sent(); + if (error_1.message.indexOf('Invalid parent') === 0) + throw error_1; + throw invalidParentError("Unknown: " + error_1.message); + case 6: + permissionModel = new PermissionModel_1.default(); + return [4 /*yield*/, permissionModel.canWrite(parentId, this.userId)]; + case 7: + canWrite = _a.sent(); + if (!canWrite) + throw invalidParentError('Cannot write to file'); + _a.label = 8; + case 8: + if (!('name' in file && !file.is_root)) return [3 /*break*/, 10]; + return [4 /*yield*/, this.fileByName(parentId, file.name)]; + case 9: + existingFile = _a.sent(); + if (existingFile && options.isNew) + throw new errors_1.ErrorUnprocessableEntity("Already a file with name \"" + file.name + "\""); + if (existingFile && file.id === existingFile.id) + throw new errors_1.ErrorUnprocessableEntity("Already a file with name \"" + file.name + "\""); + _a.label = 10; + case 10: return [2 /*return*/, file]; + } + }); + }); + }; + FileModel.prototype.fromApiInput = function (object) { + return __awaiter(this, void 0, void 0, function () { + var file; + return __generator(this, function (_a) { + file = {}; + if ('id' in object) + file.id = object.id; + if ('name' in object) + file.name = object.name; + if ('parent_id' in object) + file.parent_id = object.parent_id; + if ('mime_type' in object) + file.mime_type = object.mime_type; + return [2 /*return*/, file]; + }); + }); + }; + FileModel.prototype.toApiOutput = function (object) { + var output = __assign({}, object); + delete output.content; + return output; + }; + FileModel.prototype.createRootFile = function () { + return __awaiter(this, void 0, void 0, function () { + var existingRootFile, fileModel, id; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.userRootFile()]; + case 1: + existingRootFile = _a.sent(); + if (existingRootFile) + throw new Error("User " + this.userId + " has already a root file"); + fileModel = new FileModel({ userId: this.userId }); + id = uuidgen_1.default(); + return [2 /*return*/, fileModel.save({ + id: id, + is_directory: 1, + is_root: 1, + name: id, + }, { isNew: true })]; + } + }); + }); + }; + FileModel.prototype.checkCanReadPermissions = function (id) { + return __awaiter(this, void 0, void 0, function () { + var permissionModel, canRead; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + permissionModel = new PermissionModel_1.default(); + return [4 /*yield*/, permissionModel.canRead(id, this.userId)]; + case 1: + canRead = _a.sent(); + if (!canRead) + throw new errors_1.ErrorForbidden(); + return [2 /*return*/]; + } + }); + }); + }; + FileModel.prototype.pathToFiles = function (path, mustExist) { + if (mustExist === void 0) { mustExist = true; } + return __awaiter(this, void 0, void 0, function () { + var filenames, output, parent, i, filename, file; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + filenames = routeUtils_1.splitItemPath(path); + output = []; + parent = null; + i = 0; + _a.label = 1; + case 1: + if (!(i < filenames.length)) return [3 /*break*/, 7]; + filename = filenames[i]; + file = null; + if (!(i === 0)) return [3 /*break*/, 3]; + // For now we only support "root" as a root component, but potentially it could + // be any special directory like "documents", "pictures", etc. + if (filename !== 'root') + throw new errors_1.ErrorBadRequest("unknown path root component: " + filename); + return [4 /*yield*/, this.userRootFile()]; + case 2: + file = _a.sent(); + return [3 /*break*/, 5]; + case 3: return [4 /*yield*/, this.fileByName(parent.id, filename)]; + case 4: + file = _a.sent(); + _a.label = 5; + case 5: + if (!file && !mustExist) + return [2 /*return*/, []]; + if (!file) + throw new errors_1.ErrorNotFound("file not found: \"" + filename + "\" on parent \"" + (parent ? parent.name : '') + "\""); + output.push(file); + parent = __assign({}, file); + _a.label = 6; + case 6: + i++; + return [3 /*break*/, 1]; + case 7: return [2 /*return*/, output]; + } + }); + }); + }; + FileModel.prototype.idFromItemId = function (id, mustExist) { + if (mustExist === void 0) { mustExist = true; } + return __awaiter(this, void 0, void 0, function () { + var itemId, files; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (typeof id === 'string') + return [2 /*return*/, id]; + itemId = id; + if (!(itemId.addressingType === db_1.ItemAddressingType.Id)) return [3 /*break*/, 1]; + return [2 /*return*/, itemId.value]; + case 1: return [4 /*yield*/, this.pathToFiles(itemId.value, mustExist)]; + case 2: + files = _a.sent(); + if (!files.length && !mustExist) + return [2 /*return*/, '']; + if (!files.length) + throw new errors_1.ErrorNotFound("invalid path: " + itemId.value); + return [2 /*return*/, files[files.length - 1].id]; + } + }); + }); + }; + FileModel.prototype.loadWithContent = function (id) { + return __awaiter(this, void 0, void 0, function () { + var idString, file; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.idFromItemId(id)]; + case 1: + idString = _a.sent(); + return [4 /*yield*/, this.checkCanReadPermissions(idString)]; + case 2: + _a.sent(); + return [4 /*yield*/, this.db(this.tableName).select('*').where({ id: idString }).first()]; + case 3: + file = _a.sent(); + return [2 /*return*/, file]; + } + }); + }); + }; + FileModel.prototype.load = function (id) { + return __awaiter(this, void 0, void 0, function () { + var idString; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.idFromItemId(id)]; + case 1: + idString = _a.sent(); + return [4 /*yield*/, this.checkCanReadPermissions(idString)]; + case 2: + _a.sent(); + return [2 /*return*/, _super.prototype.load.call(this, idString)]; + } + }); + }); + }; + FileModel.prototype.save = function (object, options) { + if (options === void 0) { options = {}; } + return __awaiter(this, void 0, void 0, function () { + var isNew, txIndex, file, _a, permission, permissionModel, error_2; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + isNew = this.isNew(object, options); + return [4 /*yield*/, this.startTransaction()]; + case 1: + txIndex = _b.sent(); + file = __assign({}, object); + _b.label = 2; + case 2: + _b.trys.push([2, 8, , 10]); + if (!(!file.parent_id && !file.is_root)) return [3 /*break*/, 4]; + _a = file; + return [4 /*yield*/, this.userRootFileId()]; + case 3: + _a.parent_id = _b.sent(); + _b.label = 4; + case 4: + if ('content' in file) + file.size = file.content ? file.content.byteLength : 0; + return [4 /*yield*/, _super.prototype.save.call(this, file, options)]; + case 5: + file = _b.sent(); + if (!isNew) return [3 /*break*/, 7]; + permission = { + user_id: this.options.userId, + is_owner: 1, + item_type: db_1.ItemType.File, + item_id: file.id, + can_read: 1, + can_write: 1, + }; + permissionModel = new PermissionModel_1.default(); + return [4 /*yield*/, permissionModel.save(permission)]; + case 6: + _b.sent(); + _b.label = 7; + case 7: return [3 /*break*/, 10]; + case 8: + error_2 = _b.sent(); + return [4 /*yield*/, this.rollbackTransaction(txIndex)]; + case 9: + _b.sent(); + throw error_2; + case 10: return [4 /*yield*/, this.commitTransaction(txIndex)]; + case 11: + _b.sent(); + return [2 /*return*/, file]; + } + }); + }); + }; + FileModel.prototype.delete = function (id) { + return __awaiter(this, void 0, void 0, function () { + var idString, permissionModel, canWrite, txIndex, error_3; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.idFromItemId(id)]; + case 1: + idString = _a.sent(); + permissionModel = new PermissionModel_1.default(); + return [4 /*yield*/, permissionModel.canWrite(idString, this.userId)]; + case 2: + canWrite = _a.sent(); + if (!canWrite) + throw new errors_1.ErrorForbidden(); + return [4 /*yield*/, this.startTransaction()]; + case 3: + txIndex = _a.sent(); + _a.label = 4; + case 4: + _a.trys.push([4, 7, , 9]); + return [4 /*yield*/, permissionModel.deleteByFileId(idString)]; + case 5: + _a.sent(); + return [4 /*yield*/, _super.prototype.delete.call(this, idString)]; + case 6: + _a.sent(); + return [3 /*break*/, 9]; + case 7: + error_3 = _a.sent(); + return [4 /*yield*/, this.rollbackTransaction(txIndex)]; + case 8: + _a.sent(); + throw error_3; + case 9: return [4 /*yield*/, this.commitTransaction(txIndex)]; + case 10: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + return FileModel; +}(BaseModel_1.default)); +exports.default = FileModel; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,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LENBQUMsaUJBQWlCLENBQUMsT0FBTyxDQUFDLEVBQUE7O3dCQUFyQyxTQUFxQyxDQUFDO3dCQUV0QyxzQkFBTyxJQUFJLEVBQUM7Ozs7S0FDWjtJQUVLLDBCQUFNLEdBQVosVUFBYSxFQUFrQjs7Ozs7NEJBQ2IscUJBQU0sSUFBSSxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUMsRUFBQTs7d0JBQXRDLFFBQVEsR0FBRyxTQUEyQjt3QkFFdEMsZUFBZSxHQUFHLElBQUkseUJBQWUsRUFBRSxDQUFDO3dCQUNyQixxQkFBTSxlQUFlLENBQUMsUUFBUSxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUE7O3dCQUF4RSxRQUFRLEdBQVcsU0FBcUQ7d0JBQzlFLElBQUksQ0FBQyxRQUFROzRCQUFFLE1BQU0sSUFBSSx1QkFBYyxFQUFFLENBQUM7d0JBRTFCLHFCQUFNLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxFQUFBOzt3QkFBdkMsT0FBTyxHQUFHLFNBQTZCOzs7O3dCQUc1QyxxQkFBTSxlQUFlLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQyxFQUFBOzt3QkFBOUMsU0FBOEMsQ0FBQzt3QkFDL0MscUJBQU0saUJBQU0sTUFBTSxZQUFDLFFBQVEsQ0FBQyxFQUFBOzt3QkFBNUIsU0FBNEIsQ0FBQzs7Ozt3QkFFN0IscUJBQU0sSUFBSSxDQUFDLG1CQUFtQixDQUFDLE9BQU8sQ0FBQyxFQUFBOzt3QkFBdkMsU0FBdUMsQ0FBQzt3QkFDeEMsTUFBTSxPQUFLLENBQUM7NEJBR2IscUJBQU0sSUFBSSxDQUFDLGlCQUFpQixDQUFDLE9BQU8sQ0FBQyxFQUFBOzt3QkFBckMsU0FBcUMsQ0FBQzs7Ozs7S0FDdEM7SUFFRixnQkFBQztBQUFELENBdlBBLEFBdVBDLENBdlBzQyxtQkFBUyxHQXVQL0MiLCJmaWxlIjoiRmlsZU1vZGVsLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IEJhc2VNb2RlbCwgeyBWYWxpZGF0ZU9wdGlvbnMsIFNhdmVPcHRpb25zIH0gZnJvbSAnLi9CYXNlTW9kZWwnO1xuaW1wb3J0IFBlcm1pc3Npb25Nb2RlbCBmcm9tICcuL1Blcm1pc3Npb25Nb2RlbCc7XG5pbXBvcnQgeyBGaWxlLCBQZXJtaXNzaW9uLCBJdGVtVHlwZSwgZGF0YWJhc2VTY2hlbWEsIEl0ZW1JZCwgSXRlbUFkZHJlc3NpbmdUeXBlIH0gZnJvbSAnLi4vZGInO1xuaW1wb3J0IHsgRXJyb3JGb3JiaWRkZW4sIEVycm9yVW5wcm9jZXNzYWJsZUVudGl0eSwgRXJyb3JOb3RGb3VuZCwgRXJyb3JCYWRSZXF1ZXN0IH0gZnJvbSAnLi4vdXRpbHMvZXJyb3JzJztcbmltcG9ydCB1dWlkZ2VuIGZyb20gJy4uL3V0aWxzL3V1aWRnZW4nO1xuaW1wb3J0IHsgc3BsaXRJdGVtUGF0aCB9IGZyb20gJy4uL3V0aWxzL3JvdXRlVXRpbHMnO1xuXG5jb25zdCBub2RlRW52ID0gcHJvY2Vzcy5lbnYuTk9ERV9FTlYgfHwgJ2RldmVsb3BtZW50JztcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgRmlsZU1vZGVsIGV4dGVuZHMgQmFzZU1vZGVsIHtcblxuXHRnZXQgdGFibGVOYW1lKCk6c3RyaW5nIHtcblx0XHRyZXR1cm4gJ2ZpbGVzJztcblx0fVxuXG5cdGFzeW5jIHVzZXJSb290RmlsZSgpOlByb21pc2U8RmlsZT4ge1xuXHRcdGNvbnN0IHIgPSBhd2FpdCB0aGlzLmRiKHRoaXMudGFibGVOYW1lKS5zZWxlY3QoJ2ZpbGVzLmlkJykuZnJvbSh0aGlzLnRhYmxlTmFtZSkubGVmdEpvaW4oJ3Blcm1pc3Npb25zJywgJ3Blcm1pc3Npb25zLml0ZW1faWQnLCAnZmlsZXMuaWQnKS53aGVyZSh7XG5cdFx0XHQnaXRlbV90eXBlJzogSXRlbVR5cGUuRmlsZSxcblx0XHRcdCd1c2VyX2lkJzogdGhpcy51c2VySWQsXG5cdFx0XHQnaXNfcm9vdCc6IDEsXG5cdFx0fSkuZmlyc3QoKTtcblxuXHRcdGlmICghcikgcmV0dXJuIG51bGw7XG5cblx0XHRyZXR1cm4gdGhpcy5sb2FkKHIuaWQpO1xuXHR9XG5cblx0YXN5bmMgdXNlclJvb3RGaWxlSWQoKTpQcm9taXNlPHN0cmluZz4ge1xuXHRcdGNvbnN0IHIgPSBhd2FpdCB0aGlzLnVzZXJSb290RmlsZSgpO1xuXHRcdHJldHVybiByID8gci5pZCA6ICcnO1xuXHR9XG5cblx0YXN5bmMgZmlsZU93bmVySWQoZmlsZUlkOnN0cmluZyk6UHJvbWlzZTxzdHJpbmc+IHtcblx0XHRjb25zdCByID0gYXdhaXQgdGhpcy5kYigncGVybWlzc2lvbnMnKS5zZWxlY3QoJ3Blcm1pc3Npb25zLnVzZXJfaWQnKS53aGVyZSh7XG5cdFx0XHQnaXRlbV90eXBlJzogSXRlbVR5cGUuRmlsZSxcblx0XHRcdCdpdGVtX2lkJzogZmlsZUlkLFxuXHRcdFx0J2lzX293bmVyJzogMSxcblx0XHR9KS5maXJzdCgpO1xuXG5cdFx0aWYgKCFyKSByZXR1cm4gbnVsbDtcblxuXHRcdHJldHVybiByLnVzZXJfaWQ7XG5cdH1cblxuXHRnZXQgZGVmYXVsdEZpZWxkcygpOnN0cmluZ1tdIHtcblx0XHRyZXR1cm4gT2JqZWN0LmtleXMoZGF0YWJhc2VTY2hlbWFbdGhpcy50YWJsZU5hbWVdKS5maWx0ZXIoZiA9PiBmICE9PSAnY29udGVudCcpO1xuXHR9XG5cblx0YXN5bmMgYWxsQnlQYXJlbnQocGFyZW50SWQ6c3RyaW5nKTpQcm9taXNlPEZpbGVbXT4ge1xuXHRcdGlmICghcGFyZW50SWQpIHBhcmVudElkID0gYXdhaXQgdGhpcy51c2VyUm9vdEZpbGVJZCgpO1xuXHRcdHJldHVybiB0aGlzLmRiKHRoaXMudGFibGVOYW1lKS5zZWxlY3QoLi4udGhpcy5kZWZhdWx0RmllbGRzKS53aGVyZSh7IHBhcmVudF9pZDogcGFyZW50SWQgfSk7XG5cdH1cblxuXHRhc3luYyBmaWxlQnlOYW1lKHBhcmVudElkOnN0cmluZywgbmFtZTpzdHJpbmcpOlByb21pc2U8RmlsZT4ge1xuXHRcdHJldHVybiB0aGlzLmRiPEZpbGU+KHRoaXMudGFibGVOYW1lKS5zZWxlY3QoLi4udGhpcy5kZWZhdWx0RmllbGRzKS53aGVyZSh7XG5cdFx0XHRwYXJlbnRfaWQ6IHBhcmVudElkLFxuXHRcdFx0bmFtZTogbmFtZSxcblx0XHR9KS5maXJzdCgpO1xuXHR9XG5cblx0YXN5bmMgdmFsaWRhdGUob2JqZWN0OkZpbGUsIG9wdGlvbnM6VmFsaWRhdGVPcHRpb25zID0ge30pOlByb21pc2U8RmlsZT4ge1xuXHRcdGNvbnN0IGZpbGU6RmlsZSA9IG9iamVjdDtcblxuXHRcdGlmIChvcHRpb25zLmlzTmV3KSB7XG5cdFx0XHRpZiAoIWZpbGUuaXNfcm9vdCAmJiAhZmlsZS5uYW1lKSB0aHJvdyBuZXcgRXJyb3JVbnByb2Nlc3NhYmxlRW50aXR5KCduYW1lIGNhbm5vdCBiZSBlbXB0eScpO1xuXHRcdH0gZWxzZSB7XG5cdFx0XHRpZiAoJ25hbWUnIGluIGZpbGUgJiYgIWZpbGUubmFtZSkgdGhyb3cgbmV3IEVycm9yVW5wcm9jZXNzYWJsZUVudGl0eSgnbmFtZSBjYW5ub3QgYmUgZW1wdHknKTtcblx0XHR9XG5cblx0XHRsZXQgcGFyZW50SWQgPSBmaWxlLnBhcmVudF9pZDtcblx0XHRpZiAoIXBhcmVudElkKSBwYXJlbnRJZCA9IGF3YWl0IHRoaXMudXNlclJvb3RGaWxlSWQoKTtcblxuXHRcdGlmICgncGFyZW50X2lkJyBpbiBmaWxlICYmICFmaWxlLmlzX3Jvb3QpIHtcblx0XHRcdGNvbnN0IGludmFsaWRQYXJlbnRFcnJvciA9IGZ1bmN0aW9uKGV4dHJhSW5mbzpzdHJpbmcpIHtcblx0XHRcdFx0bGV0IG1zZyA9IGBJbnZhbGlkIHBhcmVudCBJRCBvciBubyBwZXJtaXNzaW9uIHRvIHdyaXRlIHRvIGl0OiAke3BhcmVudElkfWA7XG5cdFx0XHRcdGlmIChub2RlRW52ICE9PSAncHJvZHVjdGlvbicpIG1zZyArPSBgICgke2V4dHJhSW5mb30pYDtcblx0XHRcdFx0cmV0dXJuIG5ldyBFcnJvckZvcmJpZGRlbihtc2cpO1xuXHRcdFx0fTtcblxuXHRcdFx0aWYgKCFwYXJlbnRJZCkgdGhyb3cgaW52YWxpZFBhcmVudEVycm9yKCdObyBwYXJlbnQgSUQnKTtcblxuXHRcdFx0dHJ5IHtcblx0XHRcdFx0Y29uc3QgcGFyZW50RmlsZTpGaWxlID0gYXdhaXQgdGhpcy5sb2FkKHBhcmVudElkKTtcblx0XHRcdFx0aWYgKCFwYXJlbnRGaWxlKSB0aHJvdyBpbnZhbGlkUGFyZW50RXJyb3IoJ0Nhbm5vdCBsb2FkIHBhcmVudCBmaWxlJyk7XG5cdFx0XHRcdGlmICghcGFyZW50RmlsZS5pc19kaXJlY3RvcnkpIHRocm93IGludmFsaWRQYXJlbnRFcnJvcignU3BlY2lmaWVkIHBhcmVudCBpcyBub3QgYSBkaXJlY3RvcnknKTtcblx0XHRcdH0gY2F0Y2ggKGVycm9yKSB7XG5cdFx0XHRcdGlmIChlcnJvci5tZXNzYWdlLmluZGV4T2YoJ0ludmFsaWQgcGFyZW50JykgPT09IDApIHRocm93IGVycm9yO1xuXHRcdFx0XHR0aHJvdyBpbnZhbGlkUGFyZW50RXJyb3IoYFVua25vd246ICR7ZXJyb3IubWVzc2FnZX1gKTtcblx0XHRcdH1cblxuXHRcdFx0Y29uc3QgcGVybWlzc2lvbk1vZGVsID0gbmV3IFBlcm1pc3Npb25Nb2RlbCgpO1xuXG5cdFx0XHRjb25zdCBjYW5Xcml0ZTpib29sZWFuID0gYXdhaXQgcGVybWlzc2lvbk1vZGVsLmNhbldyaXRlKHBhcmVudElkLCB0aGlzLnVzZXJJZCk7XG5cdFx0XHRpZiAoIWNhbldyaXRlKSB0aHJvdyBpbnZhbGlkUGFyZW50RXJyb3IoJ0Nhbm5vdCB3cml0ZSB0byBmaWxlJyk7XG5cdFx0fVxuXG5cdFx0aWYgKCduYW1lJyBpbiBmaWxlICYmICFmaWxlLmlzX3Jvb3QpIHtcblx0XHRcdGNvbnN0IGV4aXN0aW5nRmlsZSA9IGF3YWl0IHRoaXMuZmlsZUJ5TmFtZShwYXJlbnRJZCwgZmlsZS5uYW1lKTtcblx0XHRcdGlmIChleGlzdGluZ0ZpbGUgJiYgb3B0aW9ucy5pc05ldykgdGhyb3cgbmV3IEVycm9yVW5wcm9jZXNzYWJsZUVudGl0eShgQWxyZWFkeSBhIGZpbGUgd2l0aCBuYW1lIFwiJHtmaWxlLm5hbWV9XCJgKTtcblx0XHRcdGlmIChleGlzdGluZ0ZpbGUgJiYgZmlsZS5pZCA9PT0gZXhpc3RpbmdGaWxlLmlkKSB0aHJvdyBuZXcgRXJyb3JVbnByb2Nlc3NhYmxlRW50aXR5KGBBbHJlYWR5IGEgZmlsZSB3aXRoIG5hbWUgXCIke2ZpbGUubmFtZX1cImApO1xuXHRcdH1cblxuXHRcdHJldHVybiBmaWxlO1xuXHR9XG5cblx0YXN5bmMgZnJvbUFwaUlucHV0KG9iamVjdDpGaWxlKTpQcm9taXNlPEZpbGU+IHtcblx0XHRjb25zdCBmaWxlOkZpbGUgPSB7fTtcblxuXHRcdGlmICgnaWQnIGluIG9iamVjdCkgZmlsZS5pZCA9IG9iamVjdC5pZDtcblx0XHRpZiAoJ25hbWUnIGluIG9iamVjdCkgZmlsZS5uYW1lID0gb2JqZWN0Lm5hbWU7XG5cdFx0aWYgKCdwYXJlbnRfaWQnIGluIG9iamVjdCkgZmlsZS5wYXJlbnRfaWQgPSBvYmplY3QucGFyZW50X2lkO1xuXHRcdGlmICgnbWltZV90eXBlJyBpbiBvYmplY3QpIGZpbGUubWltZV90eXBlID0gb2JqZWN0Lm1pbWVfdHlwZTtcblxuXHRcdHJldHVybiBmaWxlO1xuXHR9XG5cblx0dG9BcGlPdXRwdXQob2JqZWN0OmFueSk6YW55IHtcblx0XHRjb25zdCBvdXRwdXQ6RmlsZSA9IHsgLi4ub2JqZWN0IH07XG5cdFx0ZGVsZXRlIG91dHB1dC5jb250ZW50O1xuXHRcdHJldHVybiBvdXRwdXQ7XG5cdH1cblxuXHRhc3luYyBjcmVhdGVSb290RmlsZSgpOlByb21pc2U8RmlsZT4ge1xuXHRcdGNvbnN0IGV4aXN0aW5nUm9vdEZpbGUgPSBhd2FpdCB0aGlzLnVzZXJSb290RmlsZSgpO1xuXHRcdGlmIChleGlzdGluZ1Jvb3RGaWxlKSB0aHJvdyBuZXcgRXJyb3IoYFVzZXIgJHt0aGlzLnVzZXJJZH0gaGFzIGFscmVhZHkgYSByb290IGZpbGVgKTtcblxuXHRcdGNvbnN0IGZpbGVNb2RlbCA9IG5ldyBGaWxlTW9kZWwoeyB1c2VySWQ6IHRoaXMudXNlcklkIH0pO1xuXG5cdFx0Y29uc3QgaWQgPSB1dWlkZ2VuKCk7XG5cblx0XHRyZXR1cm4gZmlsZU1vZGVsLnNhdmUoe1xuXHRcdFx0aWQ6IGlkLFxuXHRcdFx0aXNfZGlyZWN0b3J5OiAxLFxuXHRcdFx0aXNfcm9vdDogMSxcblx0XHRcdG5hbWU6IGlkLCAvLyBOYW1lIG11c3QgYmUgdW5pcXVlIHNvIHdlIHNldCBpdCB0byB0aGUgSURcblx0XHR9LCB7IGlzTmV3OiB0cnVlIH0pO1xuXHR9XG5cblx0cHJpdmF0ZSBhc3luYyBjaGVja0NhblJlYWRQZXJtaXNzaW9ucyhpZDpzdHJpbmcpOlByb21pc2U8dm9pZD4ge1xuXHRcdGNvbnN0IHBlcm1pc3Npb25Nb2RlbCA9IG5ldyBQZXJtaXNzaW9uTW9kZWwoKTtcblx0XHRjb25zdCBjYW5SZWFkOmJvb2xlYW4gPSBhd2FpdCBwZXJtaXNzaW9uTW9kZWwuY2FuUmVhZChpZCwgdGhpcy51c2VySWQpO1xuXHRcdGlmICghY2FuUmVhZCkgdGhyb3cgbmV3IEVycm9yRm9yYmlkZGVuKCk7XG5cdH1cblxuXHRwcml2YXRlIGFzeW5jIHBhdGhUb0ZpbGVzKHBhdGg6c3RyaW5nLCBtdXN0RXhpc3Q6Ym9vbGVhbiA9IHRydWUpOlByb21pc2U8RmlsZVtdPiB7XG5cdFx0Y29uc3QgZmlsZW5hbWVzID0gc3BsaXRJdGVtUGF0aChwYXRoKTtcblx0XHRjb25zdCBvdXRwdXQ6RmlsZVtdID0gW107XG5cdFx0bGV0IHBhcmVudDpGaWxlID0gbnVsbDtcblxuXHRcdGZvciAobGV0IGkgPSAwOyBpIDwgZmlsZW5hbWVzLmxlbmd0aDsgaSsrKSB7XG5cdFx0XHRjb25zdCBmaWxlbmFtZSA9IGZpbGVuYW1lc1tpXTtcblx0XHRcdGxldCBmaWxlOkZpbGUgPSBudWxsO1xuXHRcdFx0aWYgKGkgPT09IDApIHtcblx0XHRcdFx0Ly8gRm9yIG5vdyB3ZSBvbmx5IHN1cHBvcnQgXCJyb290XCIgYXMgYSByb290IGNvbXBvbmVudCwgYnV0IHBvdGVudGlhbGx5IGl0IGNvdWxkXG5cdFx0XHRcdC8vIGJlIGFueSBzcGVjaWFsIGRpcmVjdG9yeSBsaWtlIFwiZG9jdW1lbnRzXCIsIFwicGljdHVyZXNcIiwgZXRjLlxuXHRcdFx0XHRpZiAoZmlsZW5hbWUgIT09ICdyb290JykgdGhyb3cgbmV3IEVycm9yQmFkUmVxdWVzdChgdW5rbm93biBwYXRoIHJvb3QgY29tcG9uZW50OiAke2ZpbGVuYW1lfWApO1xuXHRcdFx0XHRmaWxlID0gYXdhaXQgdGhpcy51c2VyUm9vdEZpbGUoKTtcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdGZpbGUgPSBhd2FpdCB0aGlzLmZpbGVCeU5hbWUocGFyZW50LmlkLCBmaWxlbmFtZSk7XG5cdFx0XHR9XG5cblx0XHRcdGlmICghZmlsZSAmJiAhbXVzdEV4aXN0KSByZXR1cm4gW107XG5cblx0XHRcdGlmICghZmlsZSkgdGhyb3cgbmV3IEVycm9yTm90Rm91bmQoYGZpbGUgbm90IGZvdW5kOiBcIiR7ZmlsZW5hbWV9XCIgb24gcGFyZW50IFwiJHtwYXJlbnQgPyBwYXJlbnQubmFtZSA6ICcnfVwiYCk7XG5cblx0XHRcdG91dHB1dC5wdXNoKGZpbGUpO1xuXHRcdFx0cGFyZW50ID0gey4uLmZpbGV9O1xuXHRcdH1cblxuXHRcdHJldHVybiBvdXRwdXQ7XG5cdH1cblxuXHRhc3luYyBpZEZyb21JdGVtSWQoaWQ6c3RyaW5nIHwgSXRlbUlkLCBtdXN0RXhpc3Q6Ym9vbGVhbiA9IHRydWUpOlByb21pc2U8c3RyaW5nPiB7XG5cdFx0aWYgKHR5cGVvZiBpZCA9PT0gJ3N0cmluZycpIHJldHVybiBpZDtcblxuXHRcdGNvbnN0IGl0ZW1JZCA9IGlkIGFzIEl0ZW1JZDtcblx0XHRpZiAoaXRlbUlkLmFkZHJlc3NpbmdUeXBlID09PSBJdGVtQWRkcmVzc2luZ1R5cGUuSWQpIHtcblx0XHRcdHJldHVybiBpdGVtSWQudmFsdWU7XG5cdFx0fSBlbHNlIHtcblx0XHRcdGNvbnN0IGZpbGVzID0gYXdhaXQgdGhpcy5wYXRoVG9GaWxlcyhpdGVtSWQudmFsdWUsIG11c3RFeGlzdCk7XG5cdFx0XHRpZiAoIWZpbGVzLmxlbmd0aCAmJiAhbXVzdEV4aXN0KSByZXR1cm4gJyc7XG5cdFx0XHRpZiAoIWZpbGVzLmxlbmd0aCkgdGhyb3cgbmV3IEVycm9yTm90Rm91bmQoYGludmFsaWQgcGF0aDogJHtpdGVtSWQudmFsdWV9YCk7XG5cdFx0XHRyZXR1cm4gZmlsZXNbZmlsZXMubGVuZ3RoIC0gMV0uaWQ7XG5cdFx0fVxuXHR9XG5cblx0YXN5bmMgbG9hZFdpdGhDb250ZW50KGlkOnN0cmluZyB8IEl0ZW1JZCk6UHJvbWlzZTxhbnk+IHtcblx0XHRjb25zdCBpZFN0cmluZyA9IGF3YWl0IHRoaXMuaWRGcm9tSXRlbUlkKGlkKTtcblx0XHRhd2FpdCB0aGlzLmNoZWNrQ2FuUmVhZFBlcm1pc3Npb25zKGlkU3RyaW5nKTtcblx0XHRjb25zdCBmaWxlOkZpbGUgPSBhd2FpdCB0aGlzLmRiPEZpbGU+KHRoaXMudGFibGVOYW1lKS5zZWxlY3QoJyonKS53aGVyZSh7IGlkOiBpZFN0cmluZyB9KS5maXJzdCgpO1xuXHRcdHJldHVybiBmaWxlO1xuXHR9XG5cblx0YXN5bmMgbG9hZChpZDpzdHJpbmcgfCBJdGVtSWQpOlByb21pc2U8RmlsZT4ge1xuXHRcdGNvbnN0IGlkU3RyaW5nID0gYXdhaXQgdGhpcy5pZEZyb21JdGVtSWQoaWQpO1xuXHRcdGF3YWl0IHRoaXMuY2hlY2tDYW5SZWFkUGVybWlzc2lvbnMoaWRTdHJpbmcpO1xuXHRcdHJldHVybiBzdXBlci5sb2FkKGlkU3RyaW5nKTtcblx0fVxuXG5cdGFzeW5jIHNhdmUob2JqZWN0OkZpbGUsIG9wdGlvbnM6U2F2ZU9wdGlvbnMgPSB7fSk6UHJvbWlzZTxGaWxlPiB7XG5cdFx0Y29uc3QgaXNOZXcgPSB0aGlzLmlzTmV3KG9iamVjdCwgb3B0aW9ucyk7XG5cblx0XHRjb25zdCB0eEluZGV4ID0gYXdhaXQgdGhpcy5zdGFydFRyYW5zYWN0aW9uKCk7XG5cblx0XHRsZXQgZmlsZTpGaWxlID0geyAuLi4gb2JqZWN0IH07XG5cblx0XHR0cnkge1xuXHRcdFx0aWYgKCFmaWxlLnBhcmVudF9pZCAmJiAhZmlsZS5pc19yb290KSBmaWxlLnBhcmVudF9pZCA9IGF3YWl0IHRoaXMudXNlclJvb3RGaWxlSWQoKTtcblxuXHRcdFx0aWYgKCdjb250ZW50JyBpbiBmaWxlKSBmaWxlLnNpemUgPSBmaWxlLmNvbnRlbnQgPyBmaWxlLmNvbnRlbnQuYnl0ZUxlbmd0aCA6IDA7XG5cblx0XHRcdGZpbGUgPSBhd2FpdCBzdXBlci5zYXZlKGZpbGUsIG9wdGlvbnMpO1xuXG5cdFx0XHRpZiAoaXNOZXcpIHtcblx0XHRcdFx0Y29uc3QgcGVybWlzc2lvbjpQZXJtaXNzaW9uID0ge1xuXHRcdFx0XHRcdHVzZXJfaWQ6IHRoaXMub3B0aW9ucy51c2VySWQsXG5cdFx0XHRcdFx0aXNfb3duZXI6IDEsXG5cdFx0XHRcdFx0aXRlbV90eXBlOiBJdGVtVHlwZS5GaWxlLFxuXHRcdFx0XHRcdGl0ZW1faWQ6IGZpbGUuaWQsXG5cdFx0XHRcdFx0Y2FuX3JlYWQ6IDEsXG5cdFx0XHRcdFx0Y2FuX3dyaXRlOiAxLFxuXHRcdFx0XHR9O1xuXG5cdFx0XHRcdGNvbnN0IHBlcm1pc3Npb25Nb2RlbCA9IG5ldyBQZXJtaXNzaW9uTW9kZWwoKTtcblx0XHRcdFx0YXdhaXQgcGVybWlzc2lvbk1vZGVsLnNhdmUocGVybWlzc2lvbik7XG5cdFx0XHR9XG5cdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdGF3YWl0IHRoaXMucm9sbGJhY2tUcmFuc2FjdGlvbih0eEluZGV4KTtcblx0XHRcdHRocm93IGVycm9yO1xuXHRcdH1cblxuXHRcdGF3YWl0IHRoaXMuY29tbWl0VHJhbnNhY3Rpb24odHhJbmRleCk7XG5cblx0XHRyZXR1cm4gZmlsZTtcblx0fVxuXG5cdGFzeW5jIGRlbGV0ZShpZDpzdHJpbmcgfCBJdGVtSWQpOlByb21pc2U8dm9pZD4ge1xuXHRcdGNvbnN0IGlkU3RyaW5nID0gYXdhaXQgdGhpcy5pZEZyb21JdGVtSWQoaWQpO1xuXG5cdFx0Y29uc3QgcGVybWlzc2lvbk1vZGVsID0gbmV3IFBlcm1pc3Npb25Nb2RlbCgpO1xuXHRcdGNvbnN0IGNhbldyaXRlOmJvb2xlYW4gPSBhd2FpdCBwZXJtaXNzaW9uTW9kZWwuY2FuV3JpdGUoaWRTdHJpbmcsIHRoaXMudXNlcklkKTtcblx0XHRpZiAoIWNhbldyaXRlKSB0aHJvdyBuZXcgRXJyb3JGb3JiaWRkZW4oKTtcblxuXHRcdGNvbnN0IHR4SW5kZXggPSBhd2FpdCB0aGlzLnN0YXJ0VHJhbnNhY3Rpb24oKTtcblxuXHRcdHRyeSB7XG5cdFx0XHRhd2FpdCBwZXJtaXNzaW9uTW9kZWwuZGVsZXRlQnlGaWxlSWQoaWRTdHJpbmcpO1xuXHRcdFx0YXdhaXQgc3VwZXIuZGVsZXRlKGlkU3RyaW5nKTtcblx0XHR9IGNhdGNoIChlcnJvcikge1xuXHRcdFx0YXdhaXQgdGhpcy5yb2xsYmFja1RyYW5zYWN0aW9uKHR4SW5kZXgpO1xuXHRcdFx0dGhyb3cgZXJyb3I7XG5cdFx0fVxuXG5cdFx0YXdhaXQgdGhpcy5jb21taXRUcmFuc2FjdGlvbih0eEluZGV4KTtcblx0fVxuXG59XG4iXX0= diff --git a/Server/dist/app/models/PermissionModel.js b/Server/dist/app/models/PermissionModel.js new file mode 100644 index 0000000000..c0a31a8938 --- /dev/null +++ b/Server/dist/app/models/PermissionModel.js @@ -0,0 +1,147 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseModel_1 = require("./BaseModel"); +var db_1 = require("../db"); +var UserModel_1 = require("./UserModel"); +var ReadOrWriteKeys; +(function (ReadOrWriteKeys) { + ReadOrWriteKeys["CanRead"] = "can_read"; + ReadOrWriteKeys["CanWrite"] = "can_write"; +})(ReadOrWriteKeys || (ReadOrWriteKeys = {})); +var PermissionModel = /** @class */ (function (_super) { + __extends(PermissionModel, _super); + function PermissionModel() { + return _super !== null && _super.apply(this, arguments) || this; + } + Object.defineProperty(PermissionModel.prototype, "tableName", { + get: function () { + return 'permissions'; + }, + enumerable: true, + configurable: true + }); + PermissionModel.prototype.filePermissions = function (fileId, userId) { + if (userId === void 0) { userId = null; } + return __awaiter(this, void 0, void 0, function () { + var p; + return __generator(this, function (_a) { + p = { + item_type: db_1.ItemType.File, + item_id: fileId, + }; + if (userId) + p.user_id = userId; + return [2 /*return*/, this.db(this.tableName).where(p).select()]; + }); + }); + }; + PermissionModel.prototype.canReadOrWrite = function (fileId, userId, method) { + return __awaiter(this, void 0, void 0, function () { + var permissions, _i, permissions_1, p, userModel, owner; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!userId || !fileId) + return [2 /*return*/, false]; + return [4 /*yield*/, this.filePermissions(fileId, userId)]; + case 1: + permissions = _a.sent(); + for (_i = 0, permissions_1 = permissions; _i < permissions_1.length; _i++) { + p = permissions_1[_i]; + if (p[method] || p.is_owner) + return [2 /*return*/, true]; + } + userModel = new UserModel_1.default({ userId: userId }); + return [4 /*yield*/, userModel.load(userId)]; + case 2: + owner = _a.sent(); + if (owner.is_admin) + return [2 /*return*/, true]; + return [2 /*return*/, false]; + } + }); + }); + }; + PermissionModel.prototype.canRead = function (fileId, userId) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, this.canReadOrWrite(fileId, userId, ReadOrWriteKeys.CanRead)]; + }); + }); + }; + PermissionModel.prototype.canWrite = function (fileId, userId) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, this.canReadOrWrite(fileId, userId, ReadOrWriteKeys.CanWrite)]; + }); + }); + }; + PermissionModel.prototype.deleteByFileId = function (fileId) { + return __awaiter(this, void 0, void 0, function () { + var permissions, ids; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.filePermissions(fileId)]; + case 1: + permissions = _a.sent(); + ids = permissions.map(function (m) { return m.id; }); + _super.prototype.delete.call(this, ids); + return [2 /*return*/]; + } + }); + }); + }; + return PermissionModel; +}(BaseModel_1.default)); +exports.default = PermissionModel; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,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bnQiOlsiaW1wb3J0IEJhc2VNb2RlbCBmcm9tICcuL0Jhc2VNb2RlbCc7XG5pbXBvcnQgeyBQZXJtaXNzaW9uLCBJdGVtVHlwZSwgVXNlciB9IGZyb20gJy4uL2RiJztcbmltcG9ydCBVc2VyTW9kZWwgZnJvbSAnLi9Vc2VyTW9kZWwnO1xuXG5lbnVtIFJlYWRPcldyaXRlS2V5cyB7XG5cdENhblJlYWQgPSAnY2FuX3JlYWQnLFxuXHRDYW5Xcml0ZSA9ICdjYW5fd3JpdGUnLFxufVxuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBQZXJtaXNzaW9uTW9kZWwgZXh0ZW5kcyBCYXNlTW9kZWwge1xuXG5cdGdldCB0YWJsZU5hbWUoKTpzdHJpbmcge1xuXHRcdHJldHVybiAncGVybWlzc2lvbnMnO1xuXHR9XG5cblx0YXN5bmMgZmlsZVBlcm1pc3Npb25zKGZpbGVJZDpzdHJpbmcsIHVzZXJJZDpzdHJpbmcgPSBudWxsKTpQcm9taXNlPEFycmF5PFBlcm1pc3Npb24+PiB7XG5cdFx0Y29uc3QgcDpQZXJtaXNzaW9uID0ge1xuXHRcdFx0aXRlbV90eXBlOiBJdGVtVHlwZS5GaWxlLFxuXHRcdFx0aXRlbV9pZDogZmlsZUlkLFxuXHRcdH07XG5cblx0XHRpZiAodXNlcklkKSBwLnVzZXJfaWQgPSB1c2VySWQ7XG5cblx0XHRyZXR1cm4gdGhpcy5kYjxQZXJtaXNzaW9uPih0aGlzLnRhYmxlTmFtZSkud2hlcmUocCkuc2VsZWN0KCk7XG5cdH1cblxuXHRwcml2YXRlIGFzeW5jIGNhblJlYWRPcldyaXRlKGZpbGVJZDpzdHJpbmcsIHVzZXJJZDpzdHJpbmcsIG1ldGhvZDpSZWFkT3JXcml0ZUtleXMpOlByb21pc2U8Ym9vbGVhbj4ge1xuXHRcdGlmICghdXNlcklkIHx8ICFmaWxlSWQpIHJldHVybiBmYWxzZTtcblx0XHRjb25zdCBwZXJtaXNzaW9ucyA9IGF3YWl0IHRoaXMuZmlsZVBlcm1pc3Npb25zKGZpbGVJZCwgdXNlcklkKTtcblx0XHRmb3IgKGNvbnN0IHAgb2YgcGVybWlzc2lvbnMpIHtcblx0XHRcdGlmIChwW21ldGhvZF0gfHwgcC5pc19vd25lcikgcmV0dXJuIHRydWU7XG5cdFx0fVxuXG5cdFx0Y29uc3QgdXNlck1vZGVsID0gbmV3IFVzZXJNb2RlbCh7IHVzZXJJZDogdXNlcklkIH0pO1xuXHRcdGNvbnN0IG93bmVyOlVzZXIgPSBhd2FpdCB1c2VyTW9kZWwubG9hZCh1c2VySWQpO1xuXHRcdGlmIChvd25lci5pc19hZG1pbikgcmV0dXJuIHRydWU7XG5cblx0XHRyZXR1cm4gZmFsc2U7XG5cdH1cblxuXHRhc3luYyBjYW5SZWFkKGZpbGVJZDpzdHJpbmcsIHVzZXJJZDpzdHJpbmcpOlByb21pc2U8Ym9vbGVhbj4ge1xuXHRcdHJldHVybiB0aGlzLmNhblJlYWRPcldyaXRlKGZpbGVJZCwgdXNlcklkLCBSZWFkT3JXcml0ZUtleXMuQ2FuUmVhZCk7XG5cdH1cblxuXHRhc3luYyBjYW5Xcml0ZShmaWxlSWQ6c3RyaW5nLCB1c2VySWQ6c3RyaW5nKTpQcm9taXNlPGJvb2xlYW4+IHtcblx0XHRyZXR1cm4gdGhpcy5jYW5SZWFkT3JXcml0ZShmaWxlSWQsIHVzZXJJZCwgUmVhZE9yV3JpdGVLZXlzLkNhbldyaXRlKTtcblx0fVxuXG5cdGFzeW5jIGRlbGV0ZUJ5RmlsZUlkKGZpbGVJZDpzdHJpbmcpOlByb21pc2U8dm9pZD4ge1xuXHRcdGNvbnN0IHBlcm1pc3Npb25zID0gYXdhaXQgdGhpcy5maWxlUGVybWlzc2lvbnMoZmlsZUlkKTtcblx0XHRjb25zdCBpZHMgPSBwZXJtaXNzaW9ucy5tYXAobSA9PiBtLmlkKTtcblx0XHRzdXBlci5kZWxldGUoaWRzKTtcblx0fVxuXG59XG4iXX0= diff --git a/Server/dist/app/models/SessionModel.js b/Server/dist/app/models/SessionModel.js new file mode 100644 index 0000000000..aa12165097 --- /dev/null +++ b/Server/dist/app/models/SessionModel.js @@ -0,0 +1,86 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseModel_1 = require("./BaseModel"); +var UserModel_1 = require("./UserModel"); +var SessionModel = /** @class */ (function (_super) { + __extends(SessionModel, _super); + function SessionModel() { + return _super !== null && _super.apply(this, arguments) || this; + } + Object.defineProperty(SessionModel.prototype, "tableName", { + get: function () { + return 'sessions'; + }, + enumerable: true, + configurable: true + }); + SessionModel.prototype.sessionUser = function (sessionId) { + return __awaiter(this, void 0, void 0, function () { + var session, userModel; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.load(sessionId)]; + case 1: + session = _a.sent(); + if (!session) + return [2 /*return*/, null]; + userModel = new UserModel_1.default({ userId: session.user_id }); + return [2 /*return*/, userModel.load(session.user_id)]; + } + }); + }); + }; + return SessionModel; +}(BaseModel_1.default)); +exports.default = SessionModel; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIlNlc3Npb25Nb2RlbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSx5Q0FBb0M7QUFDcEMseUNBQW9DO0FBR3BDO0lBQTBDLGdDQUFTO0lBQW5EOztJQWFBLENBQUM7SUFYQSxzQkFBSSxtQ0FBUzthQUFiO1lBQ0MsT0FBTyxVQUFVLENBQUM7UUFDbkIsQ0FBQzs7O09BQUE7SUFFSyxrQ0FBVyxHQUFqQixVQUFrQixTQUFnQjs7Ozs7NEJBQ1QscUJBQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBQTs7d0JBQTVDLE9BQU8sR0FBVyxTQUEwQjt3QkFDbEQsSUFBSSxDQUFDLE9BQU87NEJBQUUsc0JBQU8sSUFBSSxFQUFDO3dCQUNwQixTQUFTLEdBQUcsSUFBSSxtQkFBUyxDQUFDLEVBQUUsTUFBTSxFQUFFLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO3dCQUM3RCxzQkFBTyxTQUFTLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBQzs7OztLQUN2QztJQUVGLG1CQUFDO0FBQUQsQ0FiQSxBQWFDLENBYnlDLG1CQUFTLEdBYWxEIiwiZmlsZSI6IlNlc3Npb25Nb2RlbC5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBCYXNlTW9kZWwgZnJvbSAnLi9CYXNlTW9kZWwnO1xuaW1wb3J0IFVzZXJNb2RlbCBmcm9tICcuL1VzZXJNb2RlbCc7XG5pbXBvcnQgeyBVc2VyLCBTZXNzaW9uIH0gZnJvbSAnLi4vZGInO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBTZXNzaW9uTW9kZWwgZXh0ZW5kcyBCYXNlTW9kZWwge1xuXG5cdGdldCB0YWJsZU5hbWUoKTpzdHJpbmcge1xuXHRcdHJldHVybiAnc2Vzc2lvbnMnO1xuXHR9XG5cblx0YXN5bmMgc2Vzc2lvblVzZXIoc2Vzc2lvbklkOnN0cmluZyk6UHJvbWlzZTxVc2VyPiB7XG5cdFx0Y29uc3Qgc2Vzc2lvbjpTZXNzaW9uID0gYXdhaXQgdGhpcy5sb2FkKHNlc3Npb25JZCk7XG5cdFx0aWYgKCFzZXNzaW9uKSByZXR1cm4gbnVsbDtcblx0XHRjb25zdCB1c2VyTW9kZWwgPSBuZXcgVXNlck1vZGVsKHsgdXNlcklkOiBzZXNzaW9uLnVzZXJfaWQgfSk7XG5cdFx0cmV0dXJuIHVzZXJNb2RlbC5sb2FkKHNlc3Npb24udXNlcl9pZCk7XG5cdH1cblxufVxuIl19 diff --git a/Server/dist/app/models/UserModel.js b/Server/dist/app/models/UserModel.js new file mode 100644 index 0000000000..bffee5221f --- /dev/null +++ b/Server/dist/app/models/UserModel.js @@ -0,0 +1,269 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseModel_1 = require("./BaseModel"); +var FileModel_1 = require("./FileModel"); +var auth = require("../utils/auth"); +var errors_1 = require("../utils/errors"); +var UserModel = /** @class */ (function (_super) { + __extends(UserModel, _super); + function UserModel() { + return _super !== null && _super.apply(this, arguments) || this; + } + Object.defineProperty(UserModel.prototype, "tableName", { + get: function () { + return 'users'; + }, + enumerable: true, + configurable: true + }); + UserModel.prototype.loadByEmail = function (email) { + return __awaiter(this, void 0, void 0, function () { + var user; + return __generator(this, function (_a) { + user = { email: email }; + return [2 /*return*/, this.db(this.tableName).where(user).first()]; + }); + }); + }; + UserModel.prototype.fromApiInput = function (object) { + return __awaiter(this, void 0, void 0, function () { + var user; + return __generator(this, function (_a) { + user = {}; + if ('id' in object) + user.id = object.id; + if ('email' in object) + user.email = object.email; + if ('password' in object) + user.password = object.password; + if ('is_admin' in object) + user.is_admin = object.is_admin; + return [2 /*return*/, user]; + }); + }); + }; + UserModel.prototype.toApiOutput = function (object) { + var output = __assign({}, object); + delete output.password; + return output; + }; + UserModel.prototype.validate = function (object, options) { + if (options === void 0) { options = {}; } + return __awaiter(this, void 0, void 0, function () { + var user, owner, existingUser; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, _super.prototype.validate.call(this, object, options)]; + case 1: + user = _a.sent(); + return [4 /*yield*/, this.load(this.userId)]; + case 2: + owner = _a.sent(); + if (options.isNew) { + if (!owner.is_admin) + throw new errors_1.ErrorForbidden('non-admin user cannot create a new user'); + if (!user.email) + throw new errors_1.ErrorUnprocessableEntity('email must be set'); + if (!user.password) + throw new errors_1.ErrorUnprocessableEntity('password must be set'); + } + else { + if (!owner.is_admin && user.id !== owner.id) + throw new errors_1.ErrorForbidden('non-admin user cannot modify another user'); + if ('email' in user && !user.email) + throw new errors_1.ErrorUnprocessableEntity('email must be set'); + if ('password' in user && !user.password) + throw new errors_1.ErrorUnprocessableEntity('password must be set'); + if (!owner.is_admin && 'is_admin' in user) + throw new errors_1.ErrorForbidden('non-admin user cannot make a user an admin'); + if (owner.is_admin && owner.id === user.id && 'is_admin' in user && !user.is_admin) + throw new errors_1.ErrorUnprocessableEntity('non-admin user cannot remove admin bit from themselves'); + } + if (!('email' in user)) return [3 /*break*/, 4]; + return [4 /*yield*/, this.loadByEmail(user.email)]; + case 3: + existingUser = _a.sent(); + if (existingUser && existingUser.id !== user.id) + throw new errors_1.ErrorUnprocessableEntity("there is already a user with this email: " + user.email); + _a.label = 4; + case 4: return [2 /*return*/, user]; + } + }); + }); + }; + UserModel.prototype.checkIsOwnerOrAdmin = function (userId) { + return __awaiter(this, void 0, void 0, function () { + var owner; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!this.userId) + throw new errors_1.ErrorForbidden('no user is active'); + if (userId === this.userId) + return [2 /*return*/]; + return [4 /*yield*/, this.load(this.userId)]; + case 1: + owner = _a.sent(); + if (!owner.is_admin) + throw new errors_1.ErrorForbidden(); + return [2 /*return*/]; + } + }); + }); + }; + UserModel.prototype.load = function (id) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.checkIsOwnerOrAdmin(id)]; + case 1: + _a.sent(); + return [2 /*return*/, _super.prototype.load.call(this, id)]; + } + }); + }); + }; + UserModel.prototype.delete = function (id) { + return __awaiter(this, void 0, void 0, function () { + var txIndex, fileModel, rootFile, error_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.checkIsOwnerOrAdmin(id)]; + case 1: + _a.sent(); + return [4 /*yield*/, this.startTransaction()]; + case 2: + txIndex = _a.sent(); + _a.label = 3; + case 3: + _a.trys.push([3, 7, , 9]); + fileModel = new FileModel_1.default({ userId: this.userId }); + return [4 /*yield*/, fileModel.userRootFile()]; + case 4: + rootFile = _a.sent(); + return [4 /*yield*/, fileModel.delete(rootFile.id)]; + case 5: + _a.sent(); + return [4 /*yield*/, _super.prototype.delete.call(this, id)]; + case 6: + _a.sent(); + return [3 /*break*/, 9]; + case 7: + error_1 = _a.sent(); + return [4 /*yield*/, this.rollbackTransaction(txIndex)]; + case 8: + _a.sent(); + throw error_1; + case 9: return [4 /*yield*/, this.commitTransaction(txIndex)]; + case 10: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + UserModel.prototype.save = function (object, options) { + if (options === void 0) { options = {}; } + return __awaiter(this, void 0, void 0, function () { + var txIndex, isNew, newUser, fileModel, error_2; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.startTransaction()]; + case 1: + txIndex = _a.sent(); + isNew = this.isNew(object, options); + newUser = __assign({}, object); + if (isNew && newUser.password) + newUser.password = auth.hashPassword(newUser.password); + _a.label = 2; + case 2: + _a.trys.push([2, 6, , 8]); + return [4 /*yield*/, _super.prototype.save.call(this, newUser, options)]; + case 3: + newUser = _a.sent(); + if (!isNew) return [3 /*break*/, 5]; + fileModel = new FileModel_1.default({ userId: newUser.id }); + return [4 /*yield*/, fileModel.createRootFile()]; + case 4: + _a.sent(); + _a.label = 5; + case 5: return [3 /*break*/, 8]; + case 6: + error_2 = _a.sent(); + return [4 /*yield*/, this.rollbackTransaction(txIndex)]; + case 7: + _a.sent(); + throw error_2; + case 8: return [4 /*yield*/, this.commitTransaction(txIndex)]; + case 9: + _a.sent(); + return [2 /*return*/, newUser]; + } + }); + }); + }; + return UserModel; +}(BaseModel_1.default)); +exports.default = UserModel; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,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QUFDLENBQUM7d0JBRTFCLElBQUksS0FBSyxJQUFJLE9BQU8sQ0FBQyxRQUFROzRCQUFFLE9BQU8sQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUM7Ozs7d0JBRzNFLHFCQUFNLGlCQUFNLElBQUksWUFBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLEVBQUE7O3dCQUE1QyxPQUFPLEdBQUcsU0FBa0MsQ0FBQzs2QkFFekMsS0FBSyxFQUFMLHdCQUFLO3dCQUNGLFNBQVMsR0FBRyxJQUFJLG1CQUFTLENBQUMsRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7d0JBQ3hELHFCQUFNLFNBQVMsQ0FBQyxjQUFjLEVBQUUsRUFBQTs7d0JBQWhDLFNBQWdDLENBQUM7Ozs7O3dCQUdsQyxxQkFBTSxJQUFJLENBQUMsbUJBQW1CLENBQUMsT0FBTyxDQUFDLEVBQUE7O3dCQUF2QyxTQUF1QyxDQUFDO3dCQUN4QyxNQUFNLE9BQUssQ0FBQzs0QkFHYixxQkFBTSxJQUFJLENBQUMsaUJBQWlCLENBQUMsT0FBTyxDQUFDLEVBQUE7O3dCQUFyQyxTQUFxQyxDQUFDO3dCQUV0QyxzQkFBTyxPQUFPLEVBQUM7Ozs7S0FDZjtJQUVGLGdCQUFDO0FBQUQsQ0EvR0EsQUErR0MsQ0EvR3NDLG1CQUFTLEdBK0cvQyIsImZpbGUiOiJVc2VyTW9kZWwuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgQmFzZU1vZGVsLCB7IFNhdmVPcHRpb25zLCBWYWxpZGF0ZU9wdGlvbnMgfSBmcm9tICcuL0Jhc2VNb2RlbCc7XG5pbXBvcnQgeyBVc2VyIH0gZnJvbSAnLi4vZGInO1xuaW1wb3J0IEZpbGVNb2RlbCBmcm9tICcuL0ZpbGVNb2RlbCc7XG5pbXBvcnQgKiBhcyBhdXRoIGZyb20gJy4uL3V0aWxzL2F1dGgnO1xuaW1wb3J0IHsgRXJyb3JVbnByb2Nlc3NhYmxlRW50aXR5LCBFcnJvckZvcmJpZGRlbiB9IGZyb20gJy4uL3V0aWxzL2Vycm9ycyc7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFVzZXJNb2RlbCBleHRlbmRzIEJhc2VNb2RlbCB7XG5cblx0Z2V0IHRhYmxlTmFtZSgpOnN0cmluZyB7XG5cdFx0cmV0dXJuICd1c2Vycyc7XG5cdH1cblxuXHRhc3luYyBsb2FkQnlFbWFpbChlbWFpbDpzdHJpbmcpOlByb21pc2U8VXNlcj4ge1xuXHRcdGNvbnN0IHVzZXI6VXNlciA9IHsgZW1haWw6IGVtYWlsIH07XG5cdFx0cmV0dXJuIHRoaXMuZGI8VXNlcj4odGhpcy50YWJsZU5hbWUpLndoZXJlKHVzZXIpLmZpcnN0KCk7XG5cdH1cblxuXHRhc3luYyBmcm9tQXBpSW5wdXQob2JqZWN0OlVzZXIpOlByb21pc2U8VXNlcj4ge1xuXHRcdGNvbnN0IHVzZXI6VXNlciA9IHt9O1xuXG5cdFx0aWYgKCdpZCcgaW4gb2JqZWN0KSB1c2VyLmlkID0gb2JqZWN0LmlkO1xuXHRcdGlmICgnZW1haWwnIGluIG9iamVjdCkgdXNlci5lbWFpbCA9IG9iamVjdC5lbWFpbDtcblx0XHRpZiAoJ3Bhc3N3b3JkJyBpbiBvYmplY3QpIHVzZXIucGFzc3dvcmQgPSBvYmplY3QucGFzc3dvcmQ7XG5cdFx0aWYgKCdpc19hZG1pbicgaW4gb2JqZWN0KSB1c2VyLmlzX2FkbWluID0gb2JqZWN0LmlzX2FkbWluO1xuXG5cdFx0cmV0dXJuIHVzZXI7XG5cdH1cblxuXHR0b0FwaU91dHB1dChvYmplY3Q6VXNlcik6VXNlciB7XG5cdFx0Y29uc3Qgb3V0cHV0OlVzZXIgPSB7IC4uLm9iamVjdCB9O1xuXHRcdGRlbGV0ZSBvdXRwdXQucGFzc3dvcmQ7XG5cdFx0cmV0dXJuIG91dHB1dDtcblx0fVxuXG5cdGFzeW5jIHZhbGlkYXRlKG9iamVjdDpVc2VyLCBvcHRpb25zOlZhbGlkYXRlT3B0aW9ucyA9IHt9KTpQcm9taXNlPFVzZXI+IHtcblx0XHRjb25zdCB1c2VyOlVzZXIgPSBhd2FpdCBzdXBlci52YWxpZGF0ZShvYmplY3QsIG9wdGlvbnMpO1xuXG5cdFx0Y29uc3Qgb3duZXI6VXNlciA9IGF3YWl0IHRoaXMubG9hZCh0aGlzLnVzZXJJZCk7XG5cblx0XHRpZiAob3B0aW9ucy5pc05ldykge1xuXHRcdFx0aWYgKCFvd25lci5pc19hZG1pbikgdGhyb3cgbmV3IEVycm9yRm9yYmlkZGVuKCdub24tYWRtaW4gdXNlciBjYW5ub3QgY3JlYXRlIGEgbmV3IHVzZXInKTtcblx0XHRcdGlmICghdXNlci5lbWFpbCkgdGhyb3cgbmV3IEVycm9yVW5wcm9jZXNzYWJsZUVudGl0eSgnZW1haWwgbXVzdCBiZSBzZXQnKTtcblx0XHRcdGlmICghdXNlci5wYXNzd29yZCkgdGhyb3cgbmV3IEVycm9yVW5wcm9jZXNzYWJsZUVudGl0eSgncGFzc3dvcmQgbXVzdCBiZSBzZXQnKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0aWYgKCFvd25lci5pc19hZG1pbiAmJiB1c2VyLmlkICE9PSBvd25lci5pZCkgdGhyb3cgbmV3IEVycm9yRm9yYmlkZGVuKCdub24tYWRtaW4gdXNlciBjYW5ub3QgbW9kaWZ5IGFub3RoZXIgdXNlcicpO1xuXHRcdFx0aWYgKCdlbWFpbCcgaW4gdXNlciAmJiAhdXNlci5lbWFpbCkgdGhyb3cgbmV3IEVycm9yVW5wcm9jZXNzYWJsZUVudGl0eSgnZW1haWwgbXVzdCBiZSBzZXQnKTtcblx0XHRcdGlmICgncGFzc3dvcmQnIGluIHVzZXIgJiYgIXVzZXIucGFzc3dvcmQpIHRocm93IG5ldyBFcnJvclVucHJvY2Vzc2FibGVFbnRpdHkoJ3Bhc3N3b3JkIG11c3QgYmUgc2V0Jyk7XG5cdFx0XHRpZiAoIW93bmVyLmlzX2FkbWluICYmICdpc19hZG1pbicgaW4gdXNlcikgdGhyb3cgbmV3IEVycm9yRm9yYmlkZGVuKCdub24tYWRtaW4gdXNlciBjYW5ub3QgbWFrZSBhIHVzZXIgYW4gYWRtaW4nKTtcblx0XHRcdGlmIChvd25lci5pc19hZG1pbiAmJiBvd25lci5pZCA9PT0gdXNlci5pZCAmJiAnaXNfYWRtaW4nIGluIHVzZXIgJiYgIXVzZXIuaXNfYWRtaW4pIHRocm93IG5ldyBFcnJvclVucHJvY2Vzc2FibGVFbnRpdHkoJ25vbi1hZG1pbiB1c2VyIGNhbm5vdCByZW1vdmUgYWRtaW4gYml0IGZyb20gdGhlbXNlbHZlcycpO1xuXHRcdH1cblxuXHRcdGlmICgnZW1haWwnIGluIHVzZXIpIHtcblx0XHRcdGNvbnN0IGV4aXN0aW5nVXNlciA9IGF3YWl0IHRoaXMubG9hZEJ5RW1haWwodXNlci5lbWFpbCk7XG5cdFx0XHRpZiAoZXhpc3RpbmdVc2VyICYmIGV4aXN0aW5nVXNlci5pZCAhPT0gdXNlci5pZCkgdGhyb3cgbmV3IEVycm9yVW5wcm9jZXNzYWJsZUVudGl0eShgdGhlcmUgaXMgYWxyZWFkeSBhIHVzZXIgd2l0aCB0aGlzIGVtYWlsOiAke3VzZXIuZW1haWx9YCk7XG5cdFx0fVxuXG5cdFx0cmV0dXJuIHVzZXI7XG5cdH1cblxuXHRhc3luYyBjaGVja0lzT3duZXJPckFkbWluKHVzZXJJZDpzdHJpbmcpOlByb21pc2U8dm9pZD4ge1xuXHRcdGlmICghdGhpcy51c2VySWQpIHRocm93IG5ldyBFcnJvckZvcmJpZGRlbignbm8gdXNlciBpcyBhY3RpdmUnKTtcblxuXHRcdGlmICh1c2VySWQgPT09IHRoaXMudXNlcklkKSByZXR1cm47XG5cblx0XHRjb25zdCBvd25lciA9IGF3YWl0IHRoaXMubG9hZCh0aGlzLnVzZXJJZCk7XG5cdFx0aWYgKCFvd25lci5pc19hZG1pbikgdGhyb3cgbmV3IEVycm9yRm9yYmlkZGVuKCk7XG5cdH1cblxuXHRhc3luYyBsb2FkKGlkOnN0cmluZyk6UHJvbWlzZTxVc2VyPiB7XG5cdFx0YXdhaXQgdGhpcy5jaGVja0lzT3duZXJPckFkbWluKGlkKTtcblx0XHRyZXR1cm4gc3VwZXIubG9hZChpZCk7XG5cdH1cblxuXHRhc3luYyBkZWxldGUoaWQ6c3RyaW5nKTpQcm9taXNlPHZvaWQ+IHtcblx0XHRhd2FpdCB0aGlzLmNoZWNrSXNPd25lck9yQWRtaW4oaWQpO1xuXG5cdFx0Y29uc3QgdHhJbmRleCA9IGF3YWl0IHRoaXMuc3RhcnRUcmFuc2FjdGlvbigpO1xuXG5cdFx0dHJ5IHtcblx0XHRcdGNvbnN0IGZpbGVNb2RlbCA9IG5ldyBGaWxlTW9kZWwoeyB1c2VySWQ6IHRoaXMudXNlcklkIH0pO1xuXHRcdFx0Y29uc3Qgcm9vdEZpbGUgPSBhd2FpdCBmaWxlTW9kZWwudXNlclJvb3RGaWxlKCk7XG5cdFx0XHRhd2FpdCBmaWxlTW9kZWwuZGVsZXRlKHJvb3RGaWxlLmlkKTtcblx0XHRcdGF3YWl0IHN1cGVyLmRlbGV0ZShpZCk7XG5cdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdGF3YWl0IHRoaXMucm9sbGJhY2tUcmFuc2FjdGlvbih0eEluZGV4KTtcblx0XHRcdHRocm93IGVycm9yO1xuXHRcdH1cblxuXHRcdGF3YWl0IHRoaXMuY29tbWl0VHJhbnNhY3Rpb24odHhJbmRleCk7XG5cdH1cblxuXHRhc3luYyBzYXZlKG9iamVjdDpVc2VyLCBvcHRpb25zOlNhdmVPcHRpb25zID0ge30pOlByb21pc2U8VXNlcj4ge1xuXHRcdGNvbnN0IHR4SW5kZXggPSBhd2FpdCB0aGlzLnN0YXJ0VHJhbnNhY3Rpb24oKTtcblxuXHRcdGNvbnN0IGlzTmV3ID0gdGhpcy5pc05ldyhvYmplY3QsIG9wdGlvbnMpO1xuXG5cdFx0bGV0IG5ld1VzZXIgPSB7Li4ub2JqZWN0fTtcblxuXHRcdGlmIChpc05ldyAmJiBuZXdVc2VyLnBhc3N3b3JkKSBuZXdVc2VyLnBhc3N3b3JkID0gYXV0aC5oYXNoUGFzc3dvcmQobmV3VXNlci5wYXNzd29yZCk7XG5cblx0XHR0cnkge1xuXHRcdFx0bmV3VXNlciA9IGF3YWl0IHN1cGVyLnNhdmUobmV3VXNlciwgb3B0aW9ucyk7XG5cblx0XHRcdGlmIChpc05ldykge1xuXHRcdFx0XHRjb25zdCBmaWxlTW9kZWwgPSBuZXcgRmlsZU1vZGVsKHsgdXNlcklkOiBuZXdVc2VyLmlkIH0pO1xuXHRcdFx0XHRhd2FpdCBmaWxlTW9kZWwuY3JlYXRlUm9vdEZpbGUoKTtcblx0XHRcdH1cblx0XHR9IGNhdGNoIChlcnJvcikge1xuXHRcdFx0YXdhaXQgdGhpcy5yb2xsYmFja1RyYW5zYWN0aW9uKHR4SW5kZXgpO1xuXHRcdFx0dGhyb3cgZXJyb3I7XG5cdFx0fVxuXG5cdFx0YXdhaXQgdGhpcy5jb21taXRUcmFuc2FjdGlvbih0eEluZGV4KTtcblxuXHRcdHJldHVybiBuZXdVc2VyO1xuXHR9XG5cbn1cbiJdfQ== diff --git a/Server/dist/app/routes/api/files.js b/Server/dist/app/routes/api/files.js new file mode 100644 index 0000000000..a64e6a2079 --- /dev/null +++ b/Server/dist/app/routes/api/files.js @@ -0,0 +1,121 @@ +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var errors_1 = require("../../utils/errors"); +var FileController_1 = require("../../controllers/FileController"); +var requestUtils_1 = require("../../utils/requestUtils"); +var routeUtils_1 = require("../../utils/routeUtils"); +var getRawBody = require("raw-body"); +var route = { + exec: function (path, ctx) { + return __awaiter(this, void 0, void 0, function () { + var fileController, body, koaResponse, file, body; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + fileController = new FileController_1.default(); + if (!path.link) { + if (ctx.method === 'GET') { + return [2 /*return*/, fileController.getFile(requestUtils_1.sessionIdFromHeaders(ctx.headers), path.value)]; + } + if (ctx.method === 'PUT') { + body = __assign({}, ctx.request.body); + body.id = path.value; + return [2 /*return*/, fileController.updateFile(requestUtils_1.sessionIdFromHeaders(ctx.headers), body)]; + } + throw new errors_1.ErrorMethodNotAllowed(); + } + if (!(path.link === 'content')) return [3 /*break*/, 5]; + if (!(ctx.method === 'GET')) return [3 /*break*/, 2]; + koaResponse = ctx.response; + return [4 /*yield*/, fileController.getFileContent(requestUtils_1.sessionIdFromHeaders(ctx.headers), path.value)]; + case 1: + file = _a.sent(); + koaResponse.body = file.content; + koaResponse.set('Content-Type', file.mime_type); + koaResponse.set('Content-Length', file.size.toString()); + return [2 /*return*/, new routeUtils_1.ApiResponse(routeUtils_1.ApiResponseType.KoaResponse, koaResponse)]; + case 2: + if (!(ctx.method === 'PUT')) return [3 /*break*/, 4]; + return [4 /*yield*/, getRawBody(ctx.req)]; + case 3: + body = _a.sent(); + return [2 /*return*/, fileController.updateFileContent(requestUtils_1.sessionIdFromHeaders(ctx.headers), path, body)]; + case 4: + // if (ctx.method === 'POST') { + // const files = ctx.request.files; + // console.info(files); + // // if (!files || !files.data) throw new ErrorBadRequest('Missing "data" field'); + // // const data = files.data; + // // const props:any = ctx.request.body.props; + // // const file:File = { + // // name: data.name, + // // content: await fs.readFile(data.path), + // // mime_type: data.type, + // // parent_id: props.parent_id ? props.parent_id : '', + // // }; + // // return fileController.createFile(sessionIdFromHeaders(ctx.headers), file); + // } + // if (ctx.method === 'PUT') { + // const files = ctx.request.files; + // if (!files || !files.data) throw new ErrorBadRequest('Missing "data" field'); + // const data = files.data; + // const content = await fs.readFile(data.path); + // return fileController.updateFileContent(sessionIdFromHeaders(ctx.headers), path.value, content); + // } + throw new errors_1.ErrorMethodNotAllowed(); + case 5: throw new errors_1.ErrorNotFound("Invalid link: " + path.link); + } + }); + }); + }, +}; +exports.default = route; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,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QUksc0JBQWEsQ0FBQyxtQkFBaUIsSUFBSSxDQUFDLElBQU0sQ0FBQyxDQUFDOzs7O0tBQ3REO0NBSUQsQ0FBQztBQUVGLGtCQUFlLEtBQUssQ0FBQyIsImZpbGUiOiJmaWxlcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIEtvYSBmcm9tICdrb2EnO1xuaW1wb3J0ICogYXMgZnMgZnJvbSAnZnMtZXh0cmEnO1xuaW1wb3J0IHsgRXJyb3JCYWRSZXF1ZXN0LCBFcnJvck5vdEZvdW5kLCBFcnJvck1ldGhvZE5vdEFsbG93ZWQgfSBmcm9tICcuLi8uLi91dGlscy9lcnJvcnMnO1xuaW1wb3J0IHsgRmlsZSB9IGZyb20gJy4uLy4uL2RiJztcbmltcG9ydCBGaWxlQ29udHJvbGxlciBmcm9tICcuLi8uLi9jb250cm9sbGVycy9GaWxlQ29udHJvbGxlcic7XG5pbXBvcnQgeyBzZXNzaW9uSWRGcm9tSGVhZGVycyB9IGZyb20gJy4uLy4uL3V0aWxzL3JlcXVlc3RVdGlscyc7XG5pbXBvcnQgeyBTdWJQYXRoLCBSb3V0ZSwgQXBpUmVzcG9uc2VUeXBlLCBBcGlSZXNwb25zZSB9IGZyb20gJy4uLy4uL3V0aWxzL3JvdXRlVXRpbHMnO1xuaW1wb3J0ICogYXMgZ2V0UmF3Qm9keSBmcm9tICdyYXctYm9keSc7XG5cbmNvbnN0IHJvdXRlOlJvdXRlID0ge1xuXG5cdGV4ZWM6IGFzeW5jIGZ1bmN0aW9uKHBhdGg6U3ViUGF0aCwgY3R4OktvYS5Db250ZXh0KSB7XG5cdFx0Y29uc3QgZmlsZUNvbnRyb2xsZXIgPSBuZXcgRmlsZUNvbnRyb2xsZXIoKTtcblxuXHRcdGlmICghcGF0aC5saW5rKSB7XG5cdFx0XHRpZiAoY3R4Lm1ldGhvZCA9PT0gJ0dFVCcpIHtcblx0XHRcdFx0cmV0dXJuIGZpbGVDb250cm9sbGVyLmdldEZpbGUoc2Vzc2lvbklkRnJvbUhlYWRlcnMoY3R4LmhlYWRlcnMpLCBwYXRoLnZhbHVlKTtcblx0XHRcdH1cblxuXHRcdFx0aWYgKGN0eC5tZXRob2QgPT09ICdQVVQnKSB7XG5cdFx0XHRcdGNvbnN0IGJvZHkgPSB7IC4uLmN0eC5yZXF1ZXN0LmJvZHkgfTtcblx0XHRcdFx0Ym9keS5pZCA9IHBhdGgudmFsdWU7XG5cdFx0XHRcdHJldHVybiBmaWxlQ29udHJvbGxlci51cGRhdGVGaWxlKHNlc3Npb25JZEZyb21IZWFkZXJzKGN0eC5oZWFkZXJzKSwgYm9keSk7XG5cdFx0XHR9XG5cblx0XHRcdHRocm93IG5ldyBFcnJvck1ldGhvZE5vdEFsbG93ZWQoKTtcblx0XHR9XG5cblx0XHRpZiAocGF0aC5saW5rID09PSAnY29udGVudCcpIHtcblx0XHRcdGlmIChjdHgubWV0aG9kID09PSAnR0VUJykge1xuXHRcdFx0XHRjb25zdCBrb2FSZXNwb25zZSA9IGN0eC5yZXNwb25zZTtcblx0XHRcdFx0Y29uc3QgZmlsZTpGaWxlID0gYXdhaXQgZmlsZUNvbnRyb2xsZXIuZ2V0RmlsZUNvbnRlbnQoc2Vzc2lvbklkRnJvbUhlYWRlcnMoY3R4LmhlYWRlcnMpLCBwYXRoLnZhbHVlKTtcblx0XHRcdFx0a29hUmVzcG9uc2UuYm9keSA9IGZpbGUuY29udGVudDtcblx0XHRcdFx0a29hUmVzcG9uc2Uuc2V0KCdDb250ZW50LVR5cGUnLCBmaWxlLm1pbWVfdHlwZSk7XG5cdFx0XHRcdGtvYVJlc3BvbnNlLnNldCgnQ29udGVudC1MZW5ndGgnLCBmaWxlLnNpemUudG9TdHJpbmcoKSk7XG5cdFx0XHRcdHJldHVybiBuZXcgQXBpUmVzcG9uc2UoQXBpUmVzcG9uc2VUeXBlLktvYVJlc3BvbnNlLCBrb2FSZXNwb25zZSk7XG5cdFx0XHR9XG5cblx0XHRcdGlmIChjdHgubWV0aG9kID09PSAnUFVUJykge1xuXHRcdFx0XHRjb25zdCBib2R5ID0gYXdhaXQgZ2V0UmF3Qm9keShjdHgucmVxKVxuXHRcdFx0XHRyZXR1cm4gZmlsZUNvbnRyb2xsZXIudXBkYXRlRmlsZUNvbnRlbnQoc2Vzc2lvbklkRnJvbUhlYWRlcnMoY3R4LmhlYWRlcnMpLCBwYXRoLCBib2R5KTtcblx0XHRcdFx0Ly8gY29uc3QgZmlsZXMgPSBjdHgucmVxdWVzdC5maWxlcztcblx0XHRcdFx0Ly9jb25zb2xlLmluZm8oY3R4LnJlcXVlc3QuZmlsZXMpO1xuXHRcdFx0XHQvLyBpZiAoIWZpbGVzIHx8ICFmaWxlcy5kYXRhKSB0aHJvdyBuZXcgRXJyb3JCYWRSZXF1ZXN0KCdNaXNzaW5nIFwiZGF0YVwiIGZpZWxkJyk7XG5cdFx0XHRcdC8vIGNvbnN0IGRhdGEgPSBmaWxlcy5kYXRhO1xuXHRcdFx0XHQvLyBjb25zdCBjb250ZW50ID0gYXdhaXQgZnMucmVhZEZpbGUoZGF0YS5wYXRoKTtcblx0XHRcdFx0Ly8gcmV0dXJuIGZpbGVDb250cm9sbGVyLnVwZGF0ZUZpbGVDb250ZW50KHNlc3Npb25JZEZyb21IZWFkZXJzKGN0eC5oZWFkZXJzKSwgcGF0aC52YWx1ZSwgY29udGVudCk7XG5cdFx0XHR9XG5cblx0XHRcdC8vIGlmIChjdHgubWV0aG9kID09PSAnUE9TVCcpIHtcblx0XHRcdC8vIFx0Y29uc3QgZmlsZXMgPSBjdHgucmVxdWVzdC5maWxlcztcblx0XHRcdC8vIFx0Y29uc29sZS5pbmZvKGZpbGVzKTtcblx0XHRcdC8vIFx0Ly8gaWYgKCFmaWxlcyB8fCAhZmlsZXMuZGF0YSkgdGhyb3cgbmV3IEVycm9yQmFkUmVxdWVzdCgnTWlzc2luZyBcImRhdGFcIiBmaWVsZCcpO1xuXHRcdFx0Ly8gXHQvLyBjb25zdCBkYXRhID0gZmlsZXMuZGF0YTtcblx0XHRcdC8vIFx0Ly8gY29uc3QgcHJvcHM6YW55ID0gY3R4LnJlcXVlc3QuYm9keS5wcm9wcztcblxuXHRcdFx0Ly8gXHQvLyBjb25zdCBmaWxlOkZpbGUgPSB7XG5cdFx0XHQvLyBcdC8vIFx0bmFtZTogZGF0YS5uYW1lLFxuXHRcdFx0Ly8gXHQvLyBcdGNvbnRlbnQ6IGF3YWl0IGZzLnJlYWRGaWxlKGRhdGEucGF0aCksXG5cdFx0XHQvLyBcdC8vIFx0bWltZV90eXBlOiBkYXRhLnR5cGUsXG5cdFx0XHQvLyBcdC8vIFx0cGFyZW50X2lkOiBwcm9wcy5wYXJlbnRfaWQgPyBwcm9wcy5wYXJlbnRfaWQgOiAnJyxcblx0XHRcdC8vIFx0Ly8gfTtcblxuXHRcdFx0Ly8gXHQvLyByZXR1cm4gZmlsZUNvbnRyb2xsZXIuY3JlYXRlRmlsZShzZXNzaW9uSWRGcm9tSGVhZGVycyhjdHguaGVhZGVycyksIGZpbGUpO1xuXHRcdFx0Ly8gfVxuXG5cdFx0XHQvLyBpZiAoY3R4Lm1ldGhvZCA9PT0gJ1BVVCcpIHtcblx0XHRcdC8vIFx0Y29uc3QgZmlsZXMgPSBjdHgucmVxdWVzdC5maWxlcztcblx0XHRcdC8vIFx0aWYgKCFmaWxlcyB8fCAhZmlsZXMuZGF0YSkgdGhyb3cgbmV3IEVycm9yQmFkUmVxdWVzdCgnTWlzc2luZyBcImRhdGFcIiBmaWVsZCcpO1xuXHRcdFx0Ly8gXHRjb25zdCBkYXRhID0gZmlsZXMuZGF0YTtcblx0XHRcdC8vIFx0Y29uc3QgY29udGVudCA9IGF3YWl0IGZzLnJlYWRGaWxlKGRhdGEucGF0aCk7XG5cdFx0XHQvLyBcdHJldHVybiBmaWxlQ29udHJvbGxlci51cGRhdGVGaWxlQ29udGVudChzZXNzaW9uSWRGcm9tSGVhZGVycyhjdHguaGVhZGVycyksIHBhdGgudmFsdWUsIGNvbnRlbnQpO1xuXHRcdFx0Ly8gfVxuXG5cdFx0XHR0aHJvdyBuZXcgRXJyb3JNZXRob2ROb3RBbGxvd2VkKCk7XG5cdFx0fVxuXG5cdFx0dGhyb3cgbmV3IEVycm9yTm90Rm91bmQoYEludmFsaWQgbGluazogJHtwYXRoLmxpbmt9YCk7XG5cdH0sXG5cblx0Ly8gbmVlZHNCb2R5TWlkZGxld2FyZTogdHJ1ZSxcblxufTtcblxuZXhwb3J0IGRlZmF1bHQgcm91dGU7XG4iXX0= diff --git a/Server/dist/app/routes/api/index.js b/Server/dist/app/routes/api/index.js new file mode 100644 index 0000000000..464bf92557 --- /dev/null +++ b/Server/dist/app/routes/api/index.js @@ -0,0 +1,50 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var route = { + exec: function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, { status: 'ok', message: 'Joplin Server is running' }]; + }); + }); + }, +}; +exports.default = route; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImluZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBRUEsSUFBTSxLQUFLLEdBQVM7SUFFbkIsSUFBSSxFQUFFOzs7Z0JBQ0wsc0JBQU8sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSwwQkFBMEIsRUFBRSxFQUFDOzs7S0FDN0Q7Q0FFRCxDQUFDO0FBRUYsa0JBQWUsS0FBSyxDQUFDIiwiZmlsZSI6ImluZGV4LmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgUm91dGUgfSBmcm9tICcuLi8uLi91dGlscy9yb3V0ZVV0aWxzJztcblxuY29uc3Qgcm91dGU6Um91dGUgPSB7XG5cblx0ZXhlYzogYXN5bmMgZnVuY3Rpb24oKSB7XG5cdFx0cmV0dXJuIHsgc3RhdHVzOiAnb2snLCBtZXNzYWdlOiAnSm9wbGluIFNlcnZlciBpcyBydW5uaW5nJyB9O1xuXHR9LFxuXG59O1xuXG5leHBvcnQgZGVmYXVsdCByb3V0ZTtcbiJdfQ== diff --git a/Server/dist/app/routes/api/ping.js b/Server/dist/app/routes/api/ping.js new file mode 100644 index 0000000000..e3d045cd4a --- /dev/null +++ b/Server/dist/app/routes/api/ping.js @@ -0,0 +1,50 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var route = { + exec: function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, { status: 'ok', message: 'Joplin Server is running' }]; + }); + }); + }, +}; +exports.default = route; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInBpbmcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFFQSxJQUFNLEtBQUssR0FBUztJQUVuQixJQUFJLEVBQUU7OztnQkFDTCxzQkFBTyxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLDBCQUEwQixFQUFFLEVBQUM7OztLQUM3RDtDQUVELENBQUM7QUFFRixrQkFBZSxLQUFLLENBQUMiLCJmaWxlIjoicGluZy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IFJvdXRlIH0gZnJvbSAnLi4vLi4vdXRpbHMvcm91dGVVdGlscyc7XG5cbmNvbnN0IHJvdXRlOlJvdXRlID0ge1xuXG5cdGV4ZWM6IGFzeW5jIGZ1bmN0aW9uKCkge1xuXHRcdHJldHVybiB7IHN0YXR1czogJ29rJywgbWVzc2FnZTogJ0pvcGxpbiBTZXJ2ZXIgaXMgcnVubmluZycgfTtcblx0fSxcblxufTtcblxuZXhwb3J0IGRlZmF1bHQgcm91dGU7XG4iXX0= diff --git a/Server/dist/app/routes/api/sessions.js b/Server/dist/app/routes/api/sessions.js new file mode 100644 index 0000000000..ef49948fdd --- /dev/null +++ b/Server/dist/app/routes/api/sessions.js @@ -0,0 +1,67 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var parse = require("co-body"); +var SessionController_1 = require("../../controllers/SessionController"); +var errors_1 = require("../../utils/errors"); +var route = { + exec: function (path, ctx) { + return __awaiter(this, void 0, void 0, function () { + var user, sessionController, session; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!!path.link) return [3 /*break*/, 3]; + if (!(ctx.method === 'POST')) return [3 /*break*/, 3]; + return [4 /*yield*/, parse.json(ctx)]; + case 1: + user = _a.sent(); + sessionController = new SessionController_1.default(); + return [4 /*yield*/, sessionController.authenticate(user.email, user.password)]; + case 2: + session = _a.sent(); + return [2 /*return*/, { id: session.id }]; + case 3: throw new errors_1.ErrorNotFound("Invalid link: " + path.link); + } + }); + }); + }, +}; +exports.default = route; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNlc3Npb25zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQ0EsK0JBQWlDO0FBQ2pDLHlFQUFvRTtBQUVwRSw2Q0FBbUQ7QUFFbkQsSUFBTSxLQUFLLEdBQVM7SUFFbkIsSUFBSSxFQUFFLFVBQWUsSUFBWSxFQUFFLEdBQWU7Ozs7Ozs2QkFDN0MsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFWLHdCQUFVOzZCQUNULENBQUEsR0FBRyxDQUFDLE1BQU0sS0FBSyxNQUFNLENBQUEsRUFBckIsd0JBQXFCO3dCQUNYLHFCQUFNLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUE7O3dCQUE1QixJQUFJLEdBQUcsU0FBcUI7d0JBQzVCLGlCQUFpQixHQUFHLElBQUksMkJBQWlCLEVBQUUsQ0FBQzt3QkFDbEMscUJBQU0saUJBQWlCLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFBOzt3QkFBekUsT0FBTyxHQUFHLFNBQStEO3dCQUMvRSxzQkFBTyxFQUFFLEVBQUUsRUFBRSxPQUFPLENBQUMsRUFBRSxFQUFFLEVBQUM7NEJBSTVCLE1BQU0sSUFBSSxzQkFBYSxDQUFDLG1CQUFpQixJQUFJLENBQUMsSUFBTSxDQUFDLENBQUM7Ozs7S0FDdEQ7Q0FFRCxDQUFDO0FBRUYsa0JBQWUsS0FBSyxDQUFDIiwiZmlsZSI6InNlc3Npb25zLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgS29hIGZyb20gJ2tvYSc7XG5pbXBvcnQgKiBhcyBwYXJzZSBmcm9tICdjby1ib2R5JztcbmltcG9ydCBTZXNzaW9uQ29udHJvbGxlciBmcm9tICcuLi8uLi9jb250cm9sbGVycy9TZXNzaW9uQ29udHJvbGxlcic7XG5pbXBvcnQgeyBTdWJQYXRoLCBSb3V0ZSB9IGZyb20gJy4uLy4uL3V0aWxzL3JvdXRlVXRpbHMnO1xuaW1wb3J0IHsgRXJyb3JOb3RGb3VuZCB9IGZyb20gJy4uLy4uL3V0aWxzL2Vycm9ycyc7XG5cbmNvbnN0IHJvdXRlOlJvdXRlID0ge1xuXG5cdGV4ZWM6IGFzeW5jIGZ1bmN0aW9uKHBhdGg6U3ViUGF0aCwgY3R4OktvYS5Db250ZXh0KSB7XG5cdFx0aWYgKCFwYXRoLmxpbmspIHtcblx0XHRcdGlmIChjdHgubWV0aG9kID09PSAnUE9TVCcpIHtcblx0XHRcdFx0Y29uc3QgdXNlciA9IGF3YWl0IHBhcnNlLmpzb24oY3R4KTtcblx0XHRcdFx0Y29uc3Qgc2Vzc2lvbkNvbnRyb2xsZXIgPSBuZXcgU2Vzc2lvbkNvbnRyb2xsZXIoKTtcblx0XHRcdFx0Y29uc3Qgc2Vzc2lvbiA9IGF3YWl0IHNlc3Npb25Db250cm9sbGVyLmF1dGhlbnRpY2F0ZSh1c2VyLmVtYWlsLCB1c2VyLnBhc3N3b3JkKTtcblx0XHRcdFx0cmV0dXJuIHsgaWQ6IHNlc3Npb24uaWQgfTtcblx0XHRcdH1cblx0XHR9XG5cblx0XHR0aHJvdyBuZXcgRXJyb3JOb3RGb3VuZChgSW52YWxpZCBsaW5rOiAke3BhdGgubGlua31gKTtcblx0fSxcblxufTtcblxuZXhwb3J0IGRlZmF1bHQgcm91dGU7XG4iXX0= diff --git a/Server/dist/app/uploadTest.js b/Server/dist/app/uploadTest.js new file mode 100644 index 0000000000..d7ac2cb42f --- /dev/null +++ b/Server/dist/app/uploadTest.js @@ -0,0 +1,65 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var Koa = require("koa"); +var getRawBody = require("raw-body"); +var fs = require("fs-extra"); +var port = 3222; +var app = new Koa(); +// app.use(koaBody({ +// multipart: true, +// // includeUnparsed: true, +// }); +app.use(function (ctx) { return __awaiter(void 0, void 0, void 0, function () { + var body; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, getRawBody(ctx.req)]; + case 1: + body = _a.sent(); + return [4 /*yield*/, fs.writeFile('/mnt/c/Users/laurent/src/joplin/testingpost.jpg', body)]; + case 2: + _a.sent(); + return [2 /*return*/]; + } + }); +}); }); +console.info('Starting server on port ' + port); +app.listen(port); + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInVwbG9hZFRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSx5QkFBMkI7QUFFM0IscUNBQXVDO0FBRXZDLDZCQUErQjtBQUUvQixJQUFNLElBQUksR0FBRyxJQUFJLENBQUM7QUFFbEIsSUFBTSxHQUFHLEdBQUcsSUFBSSxHQUFHLEVBQUUsQ0FBQztBQUV0QixvQkFBb0I7QUFDcEIsb0JBQW9CO0FBQ3BCLDZCQUE2QjtBQUM3QixNQUFNO0FBRU4sR0FBRyxDQUFDLEdBQUcsQ0FBQyxVQUFPLEdBQWU7Ozs7b0JBSWhCLHFCQUFNLFVBQVUsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLEVBQUE7O2dCQUFoQyxJQUFJLEdBQUcsU0FBeUI7Z0JBRXRDLHFCQUFNLEVBQUUsQ0FBQyxTQUFTLENBQUMsaURBQWlELEVBQUUsSUFBSSxDQUFDLEVBQUE7O2dCQUEzRSxTQUEyRSxDQUFDOzs7O0tBRTVFLENBQUMsQ0FBQztBQUVILE9BQU8sQ0FBQyxJQUFJLENBQUMsMEJBQTBCLEdBQUcsSUFBSSxDQUFDLENBQUM7QUFFaEQsR0FBRyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyIsImZpbGUiOiJ1cGxvYWRUZXN0LmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgS29hIGZyb20gJ2tvYSc7XG5pbXBvcnQgKiBhcyBrb2FCb2R5IGZyb20gJ2tvYS1ib2R5JztcbmltcG9ydCAqIGFzIGdldFJhd0JvZHkgZnJvbSAncmF3LWJvZHknO1xuXG5pbXBvcnQgKiBhcyBmcyBmcm9tICdmcy1leHRyYSc7XG5cbmNvbnN0IHBvcnQgPSAzMjIyO1xuXG5jb25zdCBhcHAgPSBuZXcgS29hKCk7XG5cbi8vIGFwcC51c2Uoa29hQm9keSh7XG4vLyBcdG11bHRpcGFydDogdHJ1ZSxcbi8vIFx0Ly8gaW5jbHVkZVVucGFyc2VkOiB0cnVlLFxuLy8gfSk7XG5cbmFwcC51c2UoYXN5bmMgKGN0eDpLb2EuQ29udGV4dCkgPT4ge1xuXHQvL2NvbnNvbGUuaW5mbyhcIkZJTEVTXCIsIGN0eC5yZXF1ZXN0LmZpbGVzKTtcblx0Ly8gY29uc29sZS5pbmZvKFwiQk9EWVwiLCBjdHgucmVxdWVzdC5ib2R5KTtcblxuXHRjb25zdCBib2R5ID0gYXdhaXQgZ2V0UmF3Qm9keShjdHgucmVxKVxuXG5cdGF3YWl0IGZzLndyaXRlRmlsZSgnL21udC9jL1VzZXJzL2xhdXJlbnQvc3JjL2pvcGxpbi90ZXN0aW5ncG9zdC5qcGcnLCBib2R5KTtcblxufSk7XG5cbmNvbnNvbGUuaW5mbygnU3RhcnRpbmcgc2VydmVyIG9uIHBvcnQgJyArIHBvcnQpO1xuXG5hcHAubGlzdGVuKHBvcnQpO1xuIl19 diff --git a/Server/dist/app/utils/appLogger.js b/Server/dist/app/utils/appLogger.js new file mode 100644 index 0000000000..e579c6d8fe --- /dev/null +++ b/Server/dist/app/utils/appLogger.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var fs = require("fs-extra"); +var FsDriverNode = require('lib/fs-driver-node').FsDriverNode; +var Logger = require('lib/logger').Logger; +var logDir = __dirname + "/../../../logs"; +fs.mkdirpSync(logDir); +Logger.fsDriver_ = new FsDriverNode(); +var logger = new Logger(); +logger.addTarget('file', { path: logDir + "/app.txt" }); +logger.addTarget('console'); +exports.default = logger; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFwcExvZ2dlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLDZCQUErQjtBQUN2QixJQUFBLHlEQUFZLENBQW1DO0FBQy9DLElBQUEscUNBQU0sQ0FBMkI7QUFFekMsSUFBTSxNQUFNLEdBQU0sU0FBUyxtQkFBZ0IsQ0FBQztBQUM1QyxFQUFFLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBRXRCLE1BQU0sQ0FBQyxTQUFTLEdBQUcsSUFBSSxZQUFZLEVBQUUsQ0FBQztBQUN0QyxJQUFNLE1BQU0sR0FBRyxJQUFJLE1BQU0sRUFBRSxDQUFDO0FBQzVCLE1BQU0sQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLEVBQUUsSUFBSSxFQUFLLE1BQU0sYUFBVSxFQUFFLENBQUMsQ0FBQztBQUN4RCxNQUFNLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBRTVCLGtCQUFlLE1BQU0sQ0FBQyIsImZpbGUiOiJhcHBMb2dnZXIuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBmcyBmcm9tICdmcy1leHRyYSc7XG5jb25zdCB7IEZzRHJpdmVyTm9kZSB9ID0gcmVxdWlyZSgnbGliL2ZzLWRyaXZlci1ub2RlJyk7XG5jb25zdCB7IExvZ2dlciB9ID0gcmVxdWlyZSgnbGliL2xvZ2dlcicpO1xuXG5jb25zdCBsb2dEaXIgPSBgJHtfX2Rpcm5hbWV9Ly4uLy4uLy4uL2xvZ3NgO1xuZnMubWtkaXJwU3luYyhsb2dEaXIpO1xuXG5Mb2dnZXIuZnNEcml2ZXJfID0gbmV3IEZzRHJpdmVyTm9kZSgpO1xuY29uc3QgbG9nZ2VyID0gbmV3IExvZ2dlcigpO1xubG9nZ2VyLmFkZFRhcmdldCgnZmlsZScsIHsgcGF0aDogYCR7bG9nRGlyfS9hcHAudHh0YCB9KTtcbmxvZ2dlci5hZGRUYXJnZXQoJ2NvbnNvbGUnKTtcblxuZXhwb3J0IGRlZmF1bHQgbG9nZ2VyO1xuIl19 diff --git a/Server/dist/app/utils/auth.js b/Server/dist/app/utils/auth.js new file mode 100644 index 0000000000..614ee3097e --- /dev/null +++ b/Server/dist/app/utils/auth.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var bcrypt = require('bcryptjs'); +function hashPassword(password) { + var salt = bcrypt.genSaltSync(10); + return bcrypt.hashSync(password, salt); +} +exports.hashPassword = hashPassword; +function checkPassword(password, hash) { + return bcrypt.compareSync(password, hash); +} +exports.checkPassword = checkPassword; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImF1dGgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxJQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUM7QUFFbkMsU0FBZ0IsWUFBWSxDQUFDLFFBQWU7SUFDM0MsSUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUNwQyxPQUFPLE1BQU0sQ0FBQyxRQUFRLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxDQUFDO0FBQ3hDLENBQUM7QUFIRCxvQ0FHQztBQUVELFNBQWdCLGFBQWEsQ0FBQyxRQUFlLEVBQUUsSUFBVztJQUN6RCxPQUFPLE1BQU0sQ0FBQyxXQUFXLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxDQUFDO0FBQzNDLENBQUM7QUFGRCxzQ0FFQyIsImZpbGUiOiJhdXRoLmpzIiwic291cmNlc0NvbnRlbnQiOlsiY29uc3QgYmNyeXB0ID0gcmVxdWlyZSgnYmNyeXB0anMnKTtcblxuZXhwb3J0IGZ1bmN0aW9uIGhhc2hQYXNzd29yZChwYXNzd29yZDpzdHJpbmcpOnN0cmluZyB7XG5cdGNvbnN0IHNhbHQgPSBiY3J5cHQuZ2VuU2FsdFN5bmMoMTApO1xuXHRyZXR1cm4gYmNyeXB0Lmhhc2hTeW5jKHBhc3N3b3JkLCBzYWx0KTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNoZWNrUGFzc3dvcmQocGFzc3dvcmQ6c3RyaW5nLCBoYXNoOnN0cmluZyk6Ym9vbGVhbiB7XG5cdHJldHVybiBiY3J5cHQuY29tcGFyZVN5bmMocGFzc3dvcmQsIGhhc2gpO1xufVxuIl19 diff --git a/Server/dist/app/utils/cache.js b/Server/dist/app/utils/cache.js new file mode 100644 index 0000000000..f4c8b0b030 --- /dev/null +++ b/Server/dist/app/utils/cache.js @@ -0,0 +1,103 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var Cache = /** @class */ (function () { + function Cache() { + this.cache = {}; + } + Cache.prototype.setAny = function (key, o) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + this.cache[key] = { + object: JSON.stringify(o), + timestamp: Date.now(), + }; + return [2 /*return*/]; + }); + }); + }; + Cache.prototype.setObject = function (key, object) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, this.setAny(key, object)]; + }); + }); + }; + Cache.prototype.getAny = function (key) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + if (!this.cache[key]) + return [2 /*return*/, null]; + return [2 /*return*/, JSON.parse(this.cache[key].object)]; + }); + }); + }; + Cache.prototype.object = function (key) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, this.getAny(key)]; + }); + }); + }; + Cache.prototype.delete = function (key) { + return __awaiter(this, void 0, void 0, function () { + var keys, _i, keys_1, k; + return __generator(this, function (_a) { + keys = typeof key === 'string' ? [key] : key; + for (_i = 0, keys_1 = keys; _i < keys_1.length; _i++) { + k = keys_1[_i]; + delete this.cache[k]; + } + return [2 /*return*/]; + }); + }); + }; + Cache.prototype.clearAll = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + this.cache = {}; + return [2 /*return*/]; + }); + }); + }; + return Cache; +}()); +var cache = new Cache(); +exports.default = cache; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImNhY2hlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBU0E7SUFBQTtRQUVDLFVBQUssR0FBZ0IsRUFBRSxDQUFDO0lBK0J6QixDQUFDO0lBN0JjLHNCQUFNLEdBQXBCLFVBQXFCLEdBQVUsRUFBRSxDQUFLOzs7Z0JBQ3JDLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUc7b0JBQ2pCLE1BQU0sRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztvQkFDekIsU0FBUyxFQUFFLElBQUksQ0FBQyxHQUFHLEVBQUU7aUJBQ3JCLENBQUM7Ozs7S0FDRjtJQUVLLHlCQUFTLEdBQWYsVUFBZ0IsR0FBVSxFQUFFLE1BQWE7OztnQkFDeEMsc0JBQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLEVBQUUsTUFBTSxDQUFDLEVBQUM7OztLQUNoQztJQUVhLHNCQUFNLEdBQXBCLFVBQXFCLEdBQVU7OztnQkFDOUIsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDO29CQUFFLHNCQUFPLElBQUksRUFBQztnQkFDbEMsc0JBQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxFQUFDOzs7S0FDMUM7SUFFSyxzQkFBTSxHQUFaLFVBQWEsR0FBVTs7O2dCQUN0QixzQkFBTyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBVyxFQUFDOzs7S0FDbEM7SUFFSyxzQkFBTSxHQUFaLFVBQWEsR0FBcUI7Ozs7Z0JBQzNCLElBQUksR0FBRyxPQUFPLEdBQUcsS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQztnQkFDbkQsV0FBb0IsRUFBSixhQUFJLEVBQUosa0JBQUksRUFBSixJQUFJO29CQUFULENBQUM7b0JBQVUsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO2lCQUFBOzs7O0tBQzNDO0lBRUssd0JBQVEsR0FBZDs7O2dCQUNDLElBQUksQ0FBQyxLQUFLLEdBQUcsRUFBRSxDQUFDOzs7O0tBQ2hCO0lBRUYsWUFBQztBQUFELENBakNBLEFBaUNDLElBQUE7QUFFRCxJQUFNLEtBQUssR0FBUyxJQUFJLEtBQUssRUFBRSxDQUFDO0FBRWhDLGtCQUFlLEtBQUssQ0FBQyIsImZpbGUiOiJjYWNoZS5qcyIsInNvdXJjZXNDb250ZW50IjpbImludGVyZmFjZSBDYWNoZUVudHJ5IHtcblx0b2JqZWN0OiBhbnksXG5cdHRpbWVzdGFtcDogbnVtYmVyLFxufVxuXG5pbnRlcmZhY2UgQ2FjaGVFbnRyaWVzIHtcblx0W2tleTogc3RyaW5nXTogQ2FjaGVFbnRyeSxcbn1cblxuY2xhc3MgQ2FjaGUge1xuXG5cdGNhY2hlOkNhY2hlRW50cmllcyA9IHt9O1xuXG5cdHByaXZhdGUgYXN5bmMgc2V0QW55KGtleTpzdHJpbmcsIG86YW55KTpQcm9taXNlPHZvaWQ+IHtcblx0XHR0aGlzLmNhY2hlW2tleV0gPSB7XG5cdFx0XHRvYmplY3Q6IEpTT04uc3RyaW5naWZ5KG8pLFxuXHRcdFx0dGltZXN0YW1wOiBEYXRlLm5vdygpLFxuXHRcdH07XG5cdH1cblxuXHRhc3luYyBzZXRPYmplY3Qoa2V5OnN0cmluZywgb2JqZWN0Ok9iamVjdCk6UHJvbWlzZTx2b2lkPiB7XG5cdFx0cmV0dXJuIHRoaXMuc2V0QW55KGtleSwgb2JqZWN0KTtcblx0fVxuXG5cdHByaXZhdGUgYXN5bmMgZ2V0QW55KGtleTpzdHJpbmcpOlByb21pc2U8YW55PiB7XG5cdFx0aWYgKCF0aGlzLmNhY2hlW2tleV0pIHJldHVybiBudWxsO1xuXHRcdHJldHVybiBKU09OLnBhcnNlKHRoaXMuY2FjaGVba2V5XS5vYmplY3QpO1xuXHR9XG5cblx0YXN5bmMgb2JqZWN0KGtleTpzdHJpbmcpOlByb21pc2U8b2JqZWN0PiB7XG5cdFx0cmV0dXJuIHRoaXMuZ2V0QW55KGtleSkgYXMgb2JqZWN0O1xuXHR9XG5cblx0YXN5bmMgZGVsZXRlKGtleTpzdHJpbmcgfCBzdHJpbmdbXSk6UHJvbWlzZTx2b2lkPiB7XG5cdFx0Y29uc3Qga2V5cyA9IHR5cGVvZiBrZXkgPT09ICdzdHJpbmcnID8gW2tleV0gOiBrZXk7XG5cdFx0Zm9yIChjb25zdCBrIG9mIGtleXMpIGRlbGV0ZSB0aGlzLmNhY2hlW2tdO1xuXHR9XG5cblx0YXN5bmMgY2xlYXJBbGwoKTpQcm9taXNlPHZvaWQ+IHtcblx0XHR0aGlzLmNhY2hlID0ge307XG5cdH1cblxufVxuXG5jb25zdCBjYWNoZTpDYWNoZSA9IG5ldyBDYWNoZSgpO1xuXG5leHBvcnQgZGVmYXVsdCBjYWNoZTtcbiJdfQ== diff --git a/Server/dist/app/utils/dbUtils.js b/Server/dist/app/utils/dbUtils.js new file mode 100644 index 0000000000..0e7228afa4 --- /dev/null +++ b/Server/dist/app/utils/dbUtils.js @@ -0,0 +1,140 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var db_1 = require("../db"); +// This transaction handler allows abstracting away the complexity of managing nested transactions +// within models. +// Any method in a model can start a transaction and, if one is already started, it +// simply won't do anything. The last active transaction commits the results. If a rollback +// happens, the following calls to rollback will be a no-op. +// Set logEnabled_ to `true` to see what happens with nested transactions. +var TransactionHandler = /** @class */ (function () { + function TransactionHandler() { + this.db_ = null; + this.transactionStack_ = []; + this.activeTransaction_ = null; + this.transactionIndex_ = 0; + this.logEnabled_ = false; + this.db_ = db_1.default; + } + Object.defineProperty(TransactionHandler.prototype, "db", { + get: function () { + return this.db_; + }, + enumerable: true, + configurable: true + }); + TransactionHandler.prototype.log = function (s) { + if (!this.logEnabled_) + return; + console.info("TransactionHandler: " + s); + }; + Object.defineProperty(TransactionHandler.prototype, "activeTransaction", { + get: function () { + return this.activeTransaction_; + }, + enumerable: true, + configurable: true + }); + TransactionHandler.prototype.start = function () { + return __awaiter(this, void 0, void 0, function () { + var txIndex, _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + txIndex = ++this.transactionIndex_; + this.log("Starting transaction: " + txIndex); + if (!!this.transactionStack_.length) return [3 /*break*/, 2]; + if (this.activeTransaction_) + throw new Error('An active transaction was found when no transaction was in stack'); // Sanity check + this.log("Trying to acquire transaction: " + txIndex); + _a = this; + return [4 /*yield*/, this.db.transaction()]; + case 1: + _a.activeTransaction_ = _b.sent(); + this.log("Got transaction: " + txIndex); + _b.label = 2; + case 2: + this.transactionStack_.push(txIndex); + return [2 /*return*/, txIndex]; + } + }); + }); + }; + TransactionHandler.prototype.finishTransaction = function (txIndex) { + if (!this.transactionStack_.length) + throw new Error('Committing but no transaction was started'); + var lastTxIndex = this.transactionStack_.pop(); + if (lastTxIndex !== txIndex) + throw new Error("Committing a transaction but was not last to start one: " + txIndex + ". Expected: " + lastTxIndex); + return !this.transactionStack_.length; + }; + TransactionHandler.prototype.commit = function (txIndex) { + return __awaiter(this, void 0, void 0, function () { + var isLastTransaction; + return __generator(this, function (_a) { + this.log("Commit transaction: " + txIndex); + isLastTransaction = this.finishTransaction(txIndex); + if (isLastTransaction) { + this.log("Is last transaction - doing commit: " + txIndex); + this.activeTransaction_.commit(); + this.activeTransaction_ = null; + } + return [2 /*return*/]; + }); + }); + }; + TransactionHandler.prototype.rollback = function (txIndex) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + this.log("Rollback transaction: " + txIndex); + this.finishTransaction(txIndex); + if (this.activeTransaction_) { + this.log("Transaction is active - doing rollback: " + txIndex); + this.activeTransaction_.rollback(); + this.activeTransaction_ = null; + } + return [2 /*return*/]; + }); + }); + }; + return TransactionHandler; +}()); +exports.transactionHandler = new TransactionHandler(); + +//# sourceMappingURL=data:application/json;charset=utf8;base64,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L0I7Ozs7S0FDRDtJQUVLLHFDQUFRLEdBQWQsVUFBZSxPQUFjOzs7Z0JBQzVCLElBQUksQ0FBQyxHQUFHLENBQUMsMkJBQXlCLE9BQVMsQ0FBQyxDQUFDO2dCQUM3QyxJQUFJLENBQUMsaUJBQWlCLENBQUMsT0FBTyxDQUFDLENBQUM7Z0JBQ2hDLElBQUksSUFBSSxDQUFDLGtCQUFrQixFQUFFO29CQUM1QixJQUFJLENBQUMsR0FBRyxDQUFDLDZDQUEyQyxPQUFTLENBQUMsQ0FBQztvQkFDL0QsSUFBSSxDQUFDLGtCQUFrQixDQUFDLFFBQVEsRUFBRSxDQUFDO29CQUNuQyxJQUFJLENBQUMsa0JBQWtCLEdBQUcsSUFBSSxDQUFDO2lCQUMvQjs7OztLQUNEO0lBRUYseUJBQUM7QUFBRCxDQW5FQSxBQW1FQyxJQUFBO0FBRVksUUFBQSxrQkFBa0IsR0FBRyxJQUFJLGtCQUFrQixFQUFFLENBQUMiLCJmaWxlIjoiZGJVdGlscy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBkYiBmcm9tICcuLi9kYic7XG5pbXBvcnQgKiBhcyBLbmV4IGZyb20gJ2tuZXgnO1xuXG4vLyBUaGlzIHRyYW5zYWN0aW9uIGhhbmRsZXIgYWxsb3dzIGFic3RyYWN0aW5nIGF3YXkgdGhlIGNvbXBsZXhpdHkgb2YgbWFuYWdpbmcgbmVzdGVkIHRyYW5zYWN0aW9uc1xuLy8gd2l0aGluIG1vZGVscy5cbi8vIEFueSBtZXRob2QgaW4gYSBtb2RlbCBjYW4gc3RhcnQgYSB0cmFuc2FjdGlvbiBhbmQsIGlmIG9uZSBpcyBhbHJlYWR5IHN0YXJ0ZWQsIGl0XG4vLyBzaW1wbHkgd29uJ3QgZG8gYW55dGhpbmcuIFRoZSBsYXN0IGFjdGl2ZSB0cmFuc2FjdGlvbiBjb21taXRzIHRoZSByZXN1bHRzLiBJZiBhIHJvbGxiYWNrXG4vLyBoYXBwZW5zLCB0aGUgZm9sbG93aW5nIGNhbGxzIHRvIHJvbGxiYWNrIHdpbGwgYmUgYSBuby1vcC5cbi8vIFNldCBsb2dFbmFibGVkXyB0byBgdHJ1ZWAgdG8gc2VlIHdoYXQgaGFwcGVucyB3aXRoIG5lc3RlZCB0cmFuc2FjdGlvbnMuXG5jbGFzcyBUcmFuc2FjdGlvbkhhbmRsZXIge1xuXG5cdGRiXzpLbmV4PGFueSwgYW55W10+ID0gbnVsbDtcblx0dHJhbnNhY3Rpb25TdGFja186bnVtYmVyW10gPSBbXTtcblx0YWN0aXZlVHJhbnNhY3Rpb25fOktuZXguVHJhbnNhY3Rpb24gPSBudWxsO1xuXHR0cmFuc2FjdGlvbkluZGV4XzpudW1iZXIgPSAwO1xuXHRsb2dFbmFibGVkXzpib29sZWFuID0gZmFsc2U7XG5cblx0Y29uc3RydWN0b3IoKSB7XG5cdFx0dGhpcy5kYl8gPSBkYjtcblx0fVxuXG5cdGdldCBkYigpOktuZXg8YW55LCBhbnlbXT4ge1xuXHRcdHJldHVybiB0aGlzLmRiXztcblx0fVxuXG5cdGxvZyhzOnN0cmluZyk6dm9pZCB7XG5cdFx0aWYgKCF0aGlzLmxvZ0VuYWJsZWRfKSByZXR1cm47XG5cdFx0Y29uc29sZS5pbmZvKGBUcmFuc2FjdGlvbkhhbmRsZXI6ICR7c31gKTtcblx0fVxuXG5cdGdldCBhY3RpdmVUcmFuc2FjdGlvbigpOktuZXguVHJhbnNhY3Rpb24ge1xuXHRcdHJldHVybiB0aGlzLmFjdGl2ZVRyYW5zYWN0aW9uXztcblx0fVxuXG5cdGFzeW5jIHN0YXJ0KCk6UHJvbWlzZTxudW1iZXI+IHtcblx0XHRjb25zdCB0eEluZGV4ID0gKyt0aGlzLnRyYW5zYWN0aW9uSW5kZXhfO1xuXHRcdHRoaXMubG9nKGBTdGFydGluZyB0cmFuc2FjdGlvbjogJHt0eEluZGV4fWApO1xuXG5cdFx0aWYgKCF0aGlzLnRyYW5zYWN0aW9uU3RhY2tfLmxlbmd0aCkge1xuXHRcdFx0aWYgKHRoaXMuYWN0aXZlVHJhbnNhY3Rpb25fKSB0aHJvdyBuZXcgRXJyb3IoJ0FuIGFjdGl2ZSB0cmFuc2FjdGlvbiB3YXMgZm91bmQgd2hlbiBubyB0cmFuc2FjdGlvbiB3YXMgaW4gc3RhY2snKTsgLy8gU2FuaXR5IGNoZWNrXG5cdFx0XHR0aGlzLmxvZyhgVHJ5aW5nIHRvIGFjcXVpcmUgdHJhbnNhY3Rpb246ICR7dHhJbmRleH1gKTtcblx0XHRcdHRoaXMuYWN0aXZlVHJhbnNhY3Rpb25fID0gYXdhaXQgdGhpcy5kYi50cmFuc2FjdGlvbigpO1xuXHRcdFx0dGhpcy5sb2coYEdvdCB0cmFuc2FjdGlvbjogJHt0eEluZGV4fWApO1xuXHRcdH1cblxuXHRcdHRoaXMudHJhbnNhY3Rpb25TdGFja18ucHVzaCh0eEluZGV4KTtcblx0XHRyZXR1cm4gdHhJbmRleDtcblx0fVxuXG5cdHByaXZhdGUgZmluaXNoVHJhbnNhY3Rpb24odHhJbmRleDpudW1iZXIpOmJvb2xlYW4ge1xuXHRcdGlmICghdGhpcy50cmFuc2FjdGlvblN0YWNrXy5sZW5ndGgpIHRocm93IG5ldyBFcnJvcignQ29tbWl0dGluZyBidXQgbm8gdHJhbnNhY3Rpb24gd2FzIHN0YXJ0ZWQnKTtcblx0XHRjb25zdCBsYXN0VHhJbmRleCA9IHRoaXMudHJhbnNhY3Rpb25TdGFja18ucG9wKCk7XG5cdFx0aWYgKGxhc3RUeEluZGV4ICE9PSB0eEluZGV4KSB0aHJvdyBuZXcgRXJyb3IoYENvbW1pdHRpbmcgYSB0cmFuc2FjdGlvbiBidXQgd2FzIG5vdCBsYXN0IHRvIHN0YXJ0IG9uZTogJHt0eEluZGV4fS4gRXhwZWN0ZWQ6ICR7bGFzdFR4SW5kZXh9YCk7XG5cdFx0cmV0dXJuICF0aGlzLnRyYW5zYWN0aW9uU3RhY2tfLmxlbmd0aDtcblx0fVxuXG5cdGFzeW5jIGNvbW1pdCh0eEluZGV4Om51bWJlcik6UHJvbWlzZTx2b2lkPiB7XG5cdFx0dGhpcy5sb2coYENvbW1pdCB0cmFuc2FjdGlvbjogJHt0eEluZGV4fWApO1xuXHRcdGNvbnN0IGlzTGFzdFRyYW5zYWN0aW9uID0gdGhpcy5maW5pc2hUcmFuc2FjdGlvbih0eEluZGV4KTtcblx0XHRpZiAoaXNMYXN0VHJhbnNhY3Rpb24pIHtcblx0XHRcdHRoaXMubG9nKGBJcyBsYXN0IHRyYW5zYWN0aW9uIC0gZG9pbmcgY29tbWl0OiAke3R4SW5kZXh9YCk7XG5cdFx0XHR0aGlzLmFjdGl2ZVRyYW5zYWN0aW9uXy5jb21taXQoKTtcblx0XHRcdHRoaXMuYWN0aXZlVHJhbnNhY3Rpb25fID0gbnVsbDtcblx0XHR9XG5cdH1cblxuXHRhc3luYyByb2xsYmFjayh0eEluZGV4Om51bWJlcik6UHJvbWlzZTx2b2lkPiB7XG5cdFx0dGhpcy5sb2coYFJvbGxiYWNrIHRyYW5zYWN0aW9uOiAke3R4SW5kZXh9YCk7XG5cdFx0dGhpcy5maW5pc2hUcmFuc2FjdGlvbih0eEluZGV4KTtcblx0XHRpZiAodGhpcy5hY3RpdmVUcmFuc2FjdGlvbl8pIHtcblx0XHRcdHRoaXMubG9nKGBUcmFuc2FjdGlvbiBpcyBhY3RpdmUgLSBkb2luZyByb2xsYmFjazogJHt0eEluZGV4fWApO1xuXHRcdFx0dGhpcy5hY3RpdmVUcmFuc2FjdGlvbl8ucm9sbGJhY2soKTtcblx0XHRcdHRoaXMuYWN0aXZlVHJhbnNhY3Rpb25fID0gbnVsbDtcblx0XHR9XG5cdH1cblxufVxuXG5leHBvcnQgY29uc3QgdHJhbnNhY3Rpb25IYW5kbGVyID0gbmV3IFRyYW5zYWN0aW9uSGFuZGxlcigpO1xuIl19 diff --git a/Server/dist/app/utils/errors.js b/Server/dist/app/utils/errors.js new file mode 100644 index 0000000000..5216310afb --- /dev/null +++ b/Server/dist/app/utils/errors.js @@ -0,0 +1,85 @@ +"use strict"; +// For explanation of the setPrototypeOf call, see: +// https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var ApiError = /** @class */ (function (_super) { + __extends(ApiError, _super); + function ApiError(message, httpCode) { + if (httpCode === void 0) { httpCode = 400; } + var _this = _super.call(this, message) || this; + _this.httpCode = httpCode; + Object.setPrototypeOf(_this, ApiError.prototype); + return _this; + } + return ApiError; +}(Error)); +var ErrorMethodNotAllowed = /** @class */ (function (_super) { + __extends(ErrorMethodNotAllowed, _super); + function ErrorMethodNotAllowed(message) { + if (message === void 0) { message = 'Method Not Allowed'; } + var _this = _super.call(this, message, 405) || this; + Object.setPrototypeOf(_this, ErrorMethodNotAllowed.prototype); + return _this; + } + return ErrorMethodNotAllowed; +}(ApiError)); +exports.ErrorMethodNotAllowed = ErrorMethodNotAllowed; +var ErrorNotFound = /** @class */ (function (_super) { + __extends(ErrorNotFound, _super); + function ErrorNotFound(message) { + if (message === void 0) { message = 'Not Found'; } + var _this = _super.call(this, message, 404) || this; + Object.setPrototypeOf(_this, ErrorNotFound.prototype); + return _this; + } + return ErrorNotFound; +}(ApiError)); +exports.ErrorNotFound = ErrorNotFound; +var ErrorForbidden = /** @class */ (function (_super) { + __extends(ErrorForbidden, _super); + function ErrorForbidden(message) { + if (message === void 0) { message = 'Forbidden'; } + var _this = _super.call(this, message, 403) || this; + Object.setPrototypeOf(_this, ErrorForbidden.prototype); + return _this; + } + return ErrorForbidden; +}(ApiError)); +exports.ErrorForbidden = ErrorForbidden; +var ErrorBadRequest = /** @class */ (function (_super) { + __extends(ErrorBadRequest, _super); + function ErrorBadRequest(message) { + if (message === void 0) { message = 'Bad Request'; } + var _this = _super.call(this, message, 400) || this; + Object.setPrototypeOf(_this, ErrorBadRequest.prototype); + return _this; + } + return ErrorBadRequest; +}(ApiError)); +exports.ErrorBadRequest = ErrorBadRequest; +var ErrorUnprocessableEntity = /** @class */ (function (_super) { + __extends(ErrorUnprocessableEntity, _super); + function ErrorUnprocessableEntity(message) { + if (message === void 0) { message = 'Unprocessable Entity'; } + var _this = _super.call(this, message, 422) || this; + Object.setPrototypeOf(_this, ErrorUnprocessableEntity.prototype); + return _this; + } + return ErrorUnprocessableEntity; +}(ApiError)); +exports.ErrorUnprocessableEntity = ErrorUnprocessableEntity; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImVycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsbURBQW1EO0FBQ25ELCtJQUErSTs7Ozs7Ozs7Ozs7Ozs7O0FBRS9JO0lBQXVCLDRCQUFLO0lBRTNCLGtCQUFZLE9BQWMsRUFBRSxRQUFxQjtRQUFyQix5QkFBQSxFQUFBLGNBQXFCO1FBQWpELFlBQ0Msa0JBQU0sT0FBTyxDQUFDLFNBR2Q7UUFGQSxLQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQztRQUN6QixNQUFNLENBQUMsY0FBYyxDQUFDLEtBQUksRUFBRSxRQUFRLENBQUMsU0FBUyxDQUFDLENBQUM7O0lBQ2pELENBQUM7SUFDRixlQUFDO0FBQUQsQ0FQQSxBQU9DLENBUHNCLEtBQUssR0FPM0I7QUFFRDtJQUEyQyx5Q0FBUTtJQUNsRCwrQkFBWSxPQUFxQztRQUFyQyx3QkFBQSxFQUFBLDhCQUFxQztRQUFqRCxZQUNDLGtCQUFNLE9BQU8sRUFBRSxHQUFHLENBQUMsU0FFbkI7UUFEQSxNQUFNLENBQUMsY0FBYyxDQUFDLEtBQUksRUFBRSxxQkFBcUIsQ0FBQyxTQUFTLENBQUMsQ0FBQzs7SUFDOUQsQ0FBQztJQUNGLDRCQUFDO0FBQUQsQ0FMQSxBQUtDLENBTDBDLFFBQVEsR0FLbEQ7QUFMWSxzREFBcUI7QUFPbEM7SUFBbUMsaUNBQVE7SUFDMUMsdUJBQVksT0FBNEI7UUFBNUIsd0JBQUEsRUFBQSxxQkFBNEI7UUFBeEMsWUFDQyxrQkFBTSxPQUFPLEVBQUUsR0FBRyxDQUFDLFNBRW5CO1FBREEsTUFBTSxDQUFDLGNBQWMsQ0FBQyxLQUFJLEVBQUUsYUFBYSxDQUFDLFNBQVMsQ0FBQyxDQUFDOztJQUN0RCxDQUFDO0lBQ0Ysb0JBQUM7QUFBRCxDQUxBLEFBS0MsQ0FMa0MsUUFBUSxHQUsxQztBQUxZLHNDQUFhO0FBTzFCO0lBQW9DLGtDQUFRO0lBQzNDLHdCQUFZLE9BQTRCO1FBQTVCLHdCQUFBLEVBQUEscUJBQTRCO1FBQXhDLFlBQ0Msa0JBQU0sT0FBTyxFQUFFLEdBQUcsQ0FBQyxTQUVuQjtRQURBLE1BQU0sQ0FBQyxjQUFjLENBQUMsS0FBSSxFQUFFLGNBQWMsQ0FBQyxTQUFTLENBQUMsQ0FBQzs7SUFDdkQsQ0FBQztJQUNGLHFCQUFDO0FBQUQsQ0FMQSxBQUtDLENBTG1DLFFBQVEsR0FLM0M7QUFMWSx3Q0FBYztBQU8zQjtJQUFxQyxtQ0FBUTtJQUM1Qyx5QkFBWSxPQUE4QjtRQUE5Qix3QkFBQSxFQUFBLHVCQUE4QjtRQUExQyxZQUNDLGtCQUFNLE9BQU8sRUFBRSxHQUFHLENBQUMsU0FFbkI7UUFEQSxNQUFNLENBQUMsY0FBYyxDQUFDLEtBQUksRUFBRSxlQUFlLENBQUMsU0FBUyxDQUFDLENBQUM7O0lBQ3hELENBQUM7SUFFRixzQkFBQztBQUFELENBTkEsQUFNQyxDQU5vQyxRQUFRLEdBTTVDO0FBTlksMENBQWU7QUFRNUI7SUFBOEMsNENBQVE7SUFDckQsa0NBQVksT0FBdUM7UUFBdkMsd0JBQUEsRUFBQSxnQ0FBdUM7UUFBbkQsWUFDQyxrQkFBTSxPQUFPLEVBQUUsR0FBRyxDQUFDLFNBRW5CO1FBREEsTUFBTSxDQUFDLGNBQWMsQ0FBQyxLQUFJLEVBQUUsd0JBQXdCLENBQUMsU0FBUyxDQUFDLENBQUM7O0lBQ2pFLENBQUM7SUFDRiwrQkFBQztBQUFELENBTEEsQUFLQyxDQUw2QyxRQUFRLEdBS3JEO0FBTFksNERBQXdCIiwiZmlsZSI6ImVycm9ycy5qcyIsInNvdXJjZXNDb250ZW50IjpbIi8vIEZvciBleHBsYW5hdGlvbiBvZiB0aGUgc2V0UHJvdG90eXBlT2YgY2FsbCwgc2VlOlxuLy8gaHR0cHM6Ly9naXRodWIuY29tL01pY3Jvc29mdC9UeXBlU2NyaXB0LXdpa2kvYmxvYi9tYXN0ZXIvQnJlYWtpbmctQ2hhbmdlcy5tZCNleHRlbmRpbmctYnVpbHQtaW5zLWxpa2UtZXJyb3ItYXJyYXktYW5kLW1hcC1tYXktbm8tbG9uZ2VyLXdvcmtcblxuY2xhc3MgQXBpRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG5cdGh0dHBDb2RlOiBudW1iZXJcblx0Y29uc3RydWN0b3IobWVzc2FnZTpzdHJpbmcsIGh0dHBDb2RlOm51bWJlciA9IDQwMCkge1xuXHRcdHN1cGVyKG1lc3NhZ2UpO1xuXHRcdHRoaXMuaHR0cENvZGUgPSBodHRwQ29kZTtcblx0XHRPYmplY3Quc2V0UHJvdG90eXBlT2YodGhpcywgQXBpRXJyb3IucHJvdG90eXBlKTtcblx0fVxufVxuXG5leHBvcnQgY2xhc3MgRXJyb3JNZXRob2ROb3RBbGxvd2VkIGV4dGVuZHMgQXBpRXJyb3Ige1xuXHRjb25zdHJ1Y3RvcihtZXNzYWdlOnN0cmluZyA9ICdNZXRob2QgTm90IEFsbG93ZWQnKSB7XG5cdFx0c3VwZXIobWVzc2FnZSwgNDA1KTtcblx0XHRPYmplY3Quc2V0UHJvdG90eXBlT2YodGhpcywgRXJyb3JNZXRob2ROb3RBbGxvd2VkLnByb3RvdHlwZSk7XG5cdH1cbn1cblxuZXhwb3J0IGNsYXNzIEVycm9yTm90Rm91bmQgZXh0ZW5kcyBBcGlFcnJvciB7XG5cdGNvbnN0cnVjdG9yKG1lc3NhZ2U6c3RyaW5nID0gJ05vdCBGb3VuZCcpIHtcblx0XHRzdXBlcihtZXNzYWdlLCA0MDQpO1xuXHRcdE9iamVjdC5zZXRQcm90b3R5cGVPZih0aGlzLCBFcnJvck5vdEZvdW5kLnByb3RvdHlwZSk7XG5cdH1cbn1cblxuZXhwb3J0IGNsYXNzIEVycm9yRm9yYmlkZGVuIGV4dGVuZHMgQXBpRXJyb3Ige1xuXHRjb25zdHJ1Y3RvcihtZXNzYWdlOnN0cmluZyA9ICdGb3JiaWRkZW4nKSB7XG5cdFx0c3VwZXIobWVzc2FnZSwgNDAzKTtcblx0XHRPYmplY3Quc2V0UHJvdG90eXBlT2YodGhpcywgRXJyb3JGb3JiaWRkZW4ucHJvdG90eXBlKTtcblx0fVxufVxuXG5leHBvcnQgY2xhc3MgRXJyb3JCYWRSZXF1ZXN0IGV4dGVuZHMgQXBpRXJyb3Ige1xuXHRjb25zdHJ1Y3RvcihtZXNzYWdlOnN0cmluZyA9ICdCYWQgUmVxdWVzdCcpIHtcblx0XHRzdXBlcihtZXNzYWdlLCA0MDApO1xuXHRcdE9iamVjdC5zZXRQcm90b3R5cGVPZih0aGlzLCBFcnJvckJhZFJlcXVlc3QucHJvdG90eXBlKTtcblx0fVxuXG59XG5cbmV4cG9ydCBjbGFzcyBFcnJvclVucHJvY2Vzc2FibGVFbnRpdHkgZXh0ZW5kcyBBcGlFcnJvciB7XG5cdGNvbnN0cnVjdG9yKG1lc3NhZ2U6c3RyaW5nID0gJ1VucHJvY2Vzc2FibGUgRW50aXR5Jykge1xuXHRcdHN1cGVyKG1lc3NhZ2UsIDQyMik7XG5cdFx0T2JqZWN0LnNldFByb3RvdHlwZU9mKHRoaXMsIEVycm9yVW5wcm9jZXNzYWJsZUVudGl0eS5wcm90b3R5cGUpO1xuXHR9XG59XG4iXX0= diff --git a/Server/dist/app/utils/koaIf.js b/Server/dist/app/utils/koaIf.js new file mode 100644 index 0000000000..7904da3ef7 --- /dev/null +++ b/Server/dist/app/utils/koaIf.js @@ -0,0 +1,68 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +function koaIf(middleware, condition) { + var _this = this; + if (condition === void 0) { condition = null; } + return function (ctx, next) { return __awaiter(_this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!(typeof condition === 'function' && condition(ctx))) return [3 /*break*/, 2]; + return [4 /*yield*/, middleware(ctx, next)]; + case 1: + _a.sent(); + return [3 /*break*/, 6]; + case 2: + if (!(typeof condition === 'boolean' && condition)) return [3 /*break*/, 4]; + return [4 /*yield*/, middleware(ctx, next)]; + case 3: + _a.sent(); + return [3 /*break*/, 6]; + case 4: return [4 /*yield*/, next()]; + case 5: + _a.sent(); + _a.label = 6; + case 6: return [2 /*return*/]; + } + }); + }); }; +} +exports.default = koaIf; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImtvYUlmLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBRUEsU0FBd0IsS0FBSyxDQUFDLFVBQW1CLEVBQUUsU0FBa0I7SUFBckUsaUJBVUM7SUFWa0QsMEJBQUEsRUFBQSxnQkFBa0I7SUFDcEUsT0FBTyxVQUFPLEdBQVcsRUFBRSxJQUFhOzs7O3lCQUNuQyxDQUFBLE9BQU8sU0FBUyxLQUFLLFVBQVUsSUFBSSxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUEsRUFBakQsd0JBQWlEO29CQUNwRCxxQkFBTSxVQUFVLENBQUMsR0FBRyxFQUFFLElBQUksQ0FBQyxFQUFBOztvQkFBM0IsU0FBMkIsQ0FBQzs7O3lCQUNsQixDQUFBLE9BQU8sU0FBUyxLQUFLLFNBQVMsSUFBSSxTQUFTLENBQUEsRUFBM0Msd0JBQTJDO29CQUNyRCxxQkFBTSxVQUFVLENBQUMsR0FBRyxFQUFFLElBQUksQ0FBQyxFQUFBOztvQkFBM0IsU0FBMkIsQ0FBQzs7d0JBRTVCLHFCQUFNLElBQUksRUFBRSxFQUFBOztvQkFBWixTQUFZLENBQUM7Ozs7O1NBRWQsQ0FBQztBQUNILENBQUM7QUFWRCx3QkFVQyIsImZpbGUiOiJrb2FJZi5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbnRleHQgfSBmcm9tICdrb2EnO1xuXG5leHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBrb2FJZihtaWRkbGV3YXJlOkZ1bmN0aW9uLCBjb25kaXRpb246YW55PW51bGwpIHtcblx0cmV0dXJuIGFzeW5jIChjdHg6Q29udGV4dCwgbmV4dDpGdW5jdGlvbikgPT4ge1xuXHRcdGlmICh0eXBlb2YgY29uZGl0aW9uID09PSAnZnVuY3Rpb24nICYmIGNvbmRpdGlvbihjdHgpKSB7XG5cdFx0XHRhd2FpdCBtaWRkbGV3YXJlKGN0eCwgbmV4dCk7XG5cdFx0fSBlbHNlIGlmICh0eXBlb2YgY29uZGl0aW9uID09PSAnYm9vbGVhbicgJiYgY29uZGl0aW9uKSB7XG5cdFx0XHRhd2FpdCBtaWRkbGV3YXJlKGN0eCwgbmV4dCk7XG5cdFx0fSBlbHNlIHtcblx0XHRcdGF3YWl0IG5leHQoKTtcblx0XHR9XG5cdH07XG59XG4iXX0= diff --git a/Server/dist/app/utils/requestUtils.js b/Server/dist/app/utils/requestUtils.js new file mode 100644 index 0000000000..a4b8d595f3 --- /dev/null +++ b/Server/dist/app/utils/requestUtils.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function sessionIdFromHeaders(headers) { + return headers['x-api-auth'] ? headers['x-api-auth'] : ''; +} +exports.sessionIdFromHeaders = sessionIdFromHeaders; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInJlcXVlc3RVdGlscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLFNBQWdCLG9CQUFvQixDQUFDLE9BQVc7SUFDL0MsT0FBTyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0FBQzNELENBQUM7QUFGRCxvREFFQyIsImZpbGUiOiJyZXF1ZXN0VXRpbHMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZnVuY3Rpb24gc2Vzc2lvbklkRnJvbUhlYWRlcnMoaGVhZGVyczphbnkpOnN0cmluZyB7XG5cdHJldHVybiBoZWFkZXJzWyd4LWFwaS1hdXRoJ10gPyBoZWFkZXJzWyd4LWFwaS1hdXRoJ10gOiAnJztcbn1cbiJdfQ== diff --git a/Server/dist/app/utils/routeUtils.js b/Server/dist/app/utils/routeUtils.js new file mode 100644 index 0000000000..9a1c9236a5 --- /dev/null +++ b/Server/dist/app/utils/routeUtils.js @@ -0,0 +1,97 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var db_1 = require("../db"); +var _a = require('lib/path-utils'), ltrimSlashes = _a.ltrimSlashes, rtrimSlashes = _a.rtrimSlashes; +var ApiResponseType; +(function (ApiResponseType) { + ApiResponseType[ApiResponseType["KoaResponse"] = 0] = "KoaResponse"; + ApiResponseType[ApiResponseType["Object"] = 1] = "Object"; +})(ApiResponseType = exports.ApiResponseType || (exports.ApiResponseType = {})); +var ApiResponse = /** @class */ (function () { + function ApiResponse(type, response) { + this.type = type; + this.response = response; + } + return ApiResponse; +}()); +exports.ApiResponse = ApiResponse; +// root:/Documents/MyFile.md +function splitItemPath(path) { + if (!path) + return []; + var output = path.split('/'); + // Remove trailing ":" from root dir + if (output.length && output[0][output[0].length - 1] === ':') + output[0] = output[0].substr(0, output[0].length - 1); + return output; +} +exports.splitItemPath = splitItemPath; +// Converts root:/path/to/file.md to /path/to/file.md +function removeFilePathPrefix(path) { + if (!path || path.indexOf(':') < 0) + return path; + var p = path.split(':'); + return p[1]; +} +exports.removeFilePathPrefix = removeFilePathPrefix; +// Allows parsing the two types of paths supported by the API: +// +// root:/Documents/MyFile.md:/content +// ABCDEFG/content +function parseSubPath(p) { + p = rtrimSlashes(ltrimSlashes(p)); + var output = { + value: '', + link: '', + addressingType: db_1.ItemAddressingType.Id, + }; + var prefix = 'root:'; + if (p.indexOf(prefix) === 0) { + output.addressingType = db_1.ItemAddressingType.Path; + var secondIdx = p.indexOf(':', prefix.length); + if (secondIdx < 0) { + output.value = p; + } + else { + output.value = p.substr(0, secondIdx); + output.link = ltrimSlashes(p.substr(secondIdx + 1)); + } + } + else { + var s = p.split('/'); + if (s.length >= 1) + output.value = s[0]; + if (s.length >= 2) + output.link = s[1]; + } + return output; +} +exports.parseSubPath = parseSubPath; +function findMatchingRoute(path, routes) { + var splittedPath = path.split('/'); + splittedPath.splice(0, 1); + if (splittedPath.length >= 2) { + var basePath_1 = splittedPath[0] + "/" + splittedPath[1]; + if (routes[basePath_1]) { + splittedPath.splice(0, 2); + return { + route: routes[basePath_1], + basePath: basePath_1, + subPath: parseSubPath("/" + splittedPath.join('/')), + }; + } + } + var basePath = splittedPath[0]; + if (routes[basePath]) { + splittedPath.splice(0, 1); + return { + route: routes[basePath], + basePath: basePath, + subPath: parseSubPath("/" + splittedPath.join('/')), + }; + } + return null; +} +exports.findMatchingRoute = findMatchingRoute; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,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QUFDLFFBQVEsQ0FBQztZQUN2QixRQUFRLEVBQUUsUUFBUTtZQUNsQixPQUFPLEVBQUUsWUFBWSxDQUFDLE1BQUksWUFBWSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUcsQ0FBQztTQUNuRCxDQUFDO0tBQ0Y7SUFFRCxPQUFPLElBQUksQ0FBQztBQUNiLENBQUM7QUEzQkQsOENBMkJDIiwiZmlsZSI6InJvdXRlVXRpbHMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJdGVtSWQsIEl0ZW1BZGRyZXNzaW5nVHlwZSB9IGZyb20gJy4uL2RiJztcblxuY29uc3QgeyBsdHJpbVNsYXNoZXMsIHJ0cmltU2xhc2hlcyB9ID0gcmVxdWlyZSgnbGliL3BhdGgtdXRpbHMnKTtcblxuZXhwb3J0IGludGVyZmFjZSBSb3V0ZSB7XG5cdGV4ZWM6IEZ1bmN0aW9uLFxuXHRuZWVkc0JvZHlNaWRkbGV3YXJlPzogYm9vbGVhblxufVxuXG5leHBvcnQgaW50ZXJmYWNlIFJvdXRlcyB7XG5cdFtrZXk6IHN0cmluZ106IFJvdXRlLFxufVxuXG5leHBvcnQgaW50ZXJmYWNlIFN1YlBhdGggZXh0ZW5kcyBJdGVtSWQge1xuXHRsaW5rOiBzdHJpbmdcbn1cblxuZXhwb3J0IGludGVyZmFjZSBNYXRjaGVkUm91dGUge1xuXHRyb3V0ZTogUm91dGUsXG5cdGJhc2VQYXRoOiBzdHJpbmcsXG5cdHN1YlBhdGg6IFN1YlBhdGgsXG59XG5cbmV4cG9ydCBlbnVtIEFwaVJlc3BvbnNlVHlwZSB7XG5cdEtvYVJlc3BvbnNlLFxuXHRPYmplY3Rcbn1cblxuZXhwb3J0IGNsYXNzIEFwaVJlc3BvbnNlIHtcblx0dHlwZTogQXBpUmVzcG9uc2VUeXBlXG5cdHJlc3BvbnNlOiBhbnlcblxuXHRjb25zdHJ1Y3Rvcih0eXBlOkFwaVJlc3BvbnNlVHlwZSwgcmVzcG9uc2U6YW55KSB7XG5cdFx0dGhpcy50eXBlID0gdHlwZTtcblx0XHR0aGlzLnJlc3BvbnNlID0gcmVzcG9uc2U7XG5cdH1cbn1cblxuLy8gcm9vdDovRG9jdW1lbnRzL015RmlsZS5tZFxuZXhwb3J0IGZ1bmN0aW9uIHNwbGl0SXRlbVBhdGgocGF0aDpzdHJpbmcpOnN0cmluZ1tdIHtcblx0aWYgKCFwYXRoKSByZXR1cm4gW107XG5cblx0Y29uc3Qgb3V0cHV0ID0gcGF0aC5zcGxpdCgnLycpO1xuXHQvLyBSZW1vdmUgdHJhaWxpbmcgXCI6XCIgZnJvbSByb290IGRpclxuXHRpZiAob3V0cHV0Lmxlbmd0aCAmJiBvdXRwdXRbMF1bb3V0cHV0WzBdLmxlbmd0aCAtIDFdID09PSAnOicpIG91dHB1dFswXSA9IG91dHB1dFswXS5zdWJzdHIoMCwgb3V0cHV0WzBdLmxlbmd0aCAtIDEpO1xuXHRyZXR1cm4gb3V0cHV0O1xufVxuXG4vLyBDb252ZXJ0cyByb290Oi9wYXRoL3RvL2ZpbGUubWQgdG8gL3BhdGgvdG8vZmlsZS5tZFxuZXhwb3J0IGZ1bmN0aW9uIHJlbW92ZUZpbGVQYXRoUHJlZml4KHBhdGg6c3RyaW5nKTpzdHJpbmcge1xuXHRpZiAoIXBhdGggfHwgcGF0aC5pbmRleE9mKCc6JykgPCAwKSByZXR1cm4gcGF0aDtcblx0Y29uc3QgcCA9IHBhdGguc3BsaXQoJzonKTtcblx0cmV0dXJuIHBbMV07XG59XG5cbi8vIEFsbG93cyBwYXJzaW5nIHRoZSB0d28gdHlwZXMgb2YgcGF0aHMgc3VwcG9ydGVkIGJ5IHRoZSBBUEk6XG4vL1xuLy8gcm9vdDovRG9jdW1lbnRzL015RmlsZS5tZDovY29udGVudFxuLy8gQUJDREVGRy9jb250ZW50XG5leHBvcnQgZnVuY3Rpb24gcGFyc2VTdWJQYXRoKHA6c3RyaW5nKTpTdWJQYXRoIHtcblx0cCA9IHJ0cmltU2xhc2hlcyhsdHJpbVNsYXNoZXMocCkpO1xuXG5cdGNvbnN0IG91dHB1dDpTdWJQYXRoID0ge1xuXHRcdHZhbHVlOiAnJyxcblx0XHRsaW5rOiAnJyxcblx0XHRhZGRyZXNzaW5nVHlwZTogSXRlbUFkZHJlc3NpbmdUeXBlLklkLFxuXHR9O1xuXG5cdGNvbnN0IHByZWZpeCA9ICdyb290Oic7XG5cdGlmIChwLmluZGV4T2YocHJlZml4KSA9PT0gMCkge1xuXHRcdG91dHB1dC5hZGRyZXNzaW5nVHlwZSA9IEl0ZW1BZGRyZXNzaW5nVHlwZS5QYXRoO1xuXG5cdFx0Y29uc3Qgc2Vjb25kSWR4ID0gcC5pbmRleE9mKCc6JywgcHJlZml4Lmxlbmd0aCk7XG5cblx0XHRpZiAoc2Vjb25kSWR4IDwgMCkge1xuXHRcdFx0b3V0cHV0LnZhbHVlID0gcDtcblx0XHR9IGVsc2Uge1xuXHRcdFx0b3V0cHV0LnZhbHVlID0gcC5zdWJzdHIoMCwgc2Vjb25kSWR4KTtcblx0XHRcdG91dHB1dC5saW5rID0gbHRyaW1TbGFzaGVzKHAuc3Vic3RyKHNlY29uZElkeCArIDEpKTtcblx0XHR9XG5cdH0gZWxzZSB7XG5cdFx0Y29uc3QgcyA9IHAuc3BsaXQoJy8nKTtcblx0XHRpZiAocy5sZW5ndGggPj0gMSkgb3V0cHV0LnZhbHVlID0gc1swXTtcblx0XHRpZiAocy5sZW5ndGggPj0gMikgb3V0cHV0LmxpbmsgPSBzWzFdO1xuXHR9XG5cblx0cmV0dXJuIG91dHB1dDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZpbmRNYXRjaGluZ1JvdXRlKHBhdGg6c3RyaW5nLCByb3V0ZXM6Um91dGVzKTpNYXRjaGVkUm91dGUge1xuXHRsZXQgc3BsaXR0ZWRQYXRoID0gcGF0aC5zcGxpdCgnLycpO1xuXHRzcGxpdHRlZFBhdGguc3BsaWNlKDAsIDEpO1xuXG5cdGlmIChzcGxpdHRlZFBhdGgubGVuZ3RoID49IDIpIHtcblx0XHRjb25zdCBiYXNlUGF0aCA9IGAke3NwbGl0dGVkUGF0aFswXX0vJHtzcGxpdHRlZFBhdGhbMV19YDtcblx0XHRpZiAocm91dGVzW2Jhc2VQYXRoXSkge1xuXHRcdFx0c3BsaXR0ZWRQYXRoLnNwbGljZSgwLCAyKTtcblx0XHRcdHJldHVybiB7XG5cdFx0XHRcdHJvdXRlOiByb3V0ZXNbYmFzZVBhdGhdLFxuXHRcdFx0XHRiYXNlUGF0aDogYmFzZVBhdGgsXG5cdFx0XHRcdHN1YlBhdGg6IHBhcnNlU3ViUGF0aChgLyR7c3BsaXR0ZWRQYXRoLmpvaW4oJy8nKX1gKSxcblx0XHRcdH07XG5cdFx0fVxuXHR9XG5cblx0Y29uc3QgYmFzZVBhdGggPSBzcGxpdHRlZFBhdGhbMF07XG5cdGlmIChyb3V0ZXNbYmFzZVBhdGhdKSB7XG5cdFx0c3BsaXR0ZWRQYXRoLnNwbGljZSgwLCAxKTtcblx0XHRyZXR1cm4ge1xuXHRcdFx0cm91dGU6IHJvdXRlc1tiYXNlUGF0aF0sXG5cdFx0XHRiYXNlUGF0aDogYmFzZVBhdGgsXG5cdFx0XHRzdWJQYXRoOiBwYXJzZVN1YlBhdGgoYC8ke3NwbGl0dGVkUGF0aC5qb2luKCcvJyl9YCksXG5cdFx0fTtcblx0fVxuXG5cdHJldHVybiBudWxsO1xufVxuIl19 diff --git a/Server/dist/app/utils/uuidgen.js b/Server/dist/app/utils/uuidgen.js new file mode 100644 index 0000000000..deb88da57b --- /dev/null +++ b/Server/dist/app/utils/uuidgen.js @@ -0,0 +1,9 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var generate = require('nanoid/generate'); +function uuidgen() { + return generate('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 22); +} +exports.default = uuidgen; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInV1aWRnZW4udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxJQUFNLFFBQVEsR0FBRyxPQUFPLENBQUMsaUJBQWlCLENBQUMsQ0FBQztBQUU1QyxTQUF3QixPQUFPO0lBQzlCLE9BQU8sUUFBUSxDQUFDLGdFQUFnRSxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBQ3ZGLENBQUM7QUFGRCwwQkFFQyIsImZpbGUiOiJ1dWlkZ2VuLmpzIiwic291cmNlc0NvbnRlbnQiOlsiY29uc3QgZ2VuZXJhdGUgPSByZXF1aXJlKCduYW5vaWQvZ2VuZXJhdGUnKTtcblxuZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gdXVpZGdlbigpOnN0cmluZyB7XG5cdHJldHVybiBnZW5lcmF0ZSgnMDEyMzQ1Njc4OUFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXonLCAyMik7XG59XG4iXX0= diff --git a/Server/dist/lib/ArrayUtils.js b/Server/dist/lib/ArrayUtils.js new file mode 100644 index 0000000000..b40b5efba7 --- /dev/null +++ b/Server/dist/lib/ArrayUtils.js @@ -0,0 +1,60 @@ +const ArrayUtils = {}; + +ArrayUtils.unique = function(array) { + return array.filter(function(elem, index, self) { + return index === self.indexOf(elem); + }); +}; + +ArrayUtils.removeElement = function(array, element) { + const index = array.indexOf(element); + if (index < 0) return array; + array.splice(index, 1); + return array; +}; + +// https://stackoverflow.com/a/10264318/561309 +ArrayUtils.binarySearch = function(items, value) { + var startIndex = 0, + stopIndex = items.length - 1, + middle = Math.floor((stopIndex + startIndex) / 2); + + while (items[middle] != value && startIndex < stopIndex) { + //adjust search area + if (value < items[middle]) { + stopIndex = middle - 1; + } else if (value > items[middle]) { + startIndex = middle + 1; + } + + //recalculate middle + middle = Math.floor((stopIndex + startIndex) / 2); + } + + //make sure it's the right value + return items[middle] != value ? -1 : middle; +}; + +ArrayUtils.findByKey = function(array, key, value) { + for (let i = 0; i < array.length; i++) { + const o = array[i]; + if (typeof o !== 'object') continue; + if (o[key] === value) return o; + } + return null; +}; + +ArrayUtils.contentEquals = function(array1, array2) { + if (array1 === array2) return true; + if (!array1.length && !array2.length) return true; + if (array1.length !== array2.length) return false; + + for (let i = 0; i < array1.length; i++) { + const a1 = array1[i]; + if (array2.indexOf(a1) < 0) return false; + } + + return true; +}; + +module.exports = ArrayUtils; diff --git a/Server/dist/lib/BaseApplication.js b/Server/dist/lib/BaseApplication.js new file mode 100644 index 0000000000..9e1f13644c --- /dev/null +++ b/Server/dist/lib/BaseApplication.js @@ -0,0 +1,665 @@ +const { createStore, applyMiddleware } = require('redux'); +const { reducer, defaultState, stateUtils } = require('lib/reducer.js'); +const { JoplinDatabase } = require('lib/joplin-database.js'); +const { FoldersScreenUtils } = require('lib/folders-screen-utils.js'); +const { DatabaseDriverNode } = require('lib/database-driver-node.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const BaseItem = require('lib/models/BaseItem.js'); +const Note = require('lib/models/Note.js'); +const Tag = require('lib/models/Tag.js'); +const Setting = require('lib/models/Setting.js'); +const { Logger } = require('lib/logger.js'); +const { splitCommandString } = require('lib/string-utils.js'); +const { reg } = require('lib/registry.js'); +const { time } = require('lib/time-utils.js'); +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const { shim } = require('lib/shim.js'); +const { _, setLocale } = require('lib/locale.js'); +const reduxSharedMiddleware = require('lib/components/shared/reduxSharedMiddleware'); +const os = require('os'); +const fs = require('fs-extra'); +const JoplinError = require('lib/JoplinError'); +const EventEmitter = require('events'); +const syswidecas = require('syswide-cas'); +const SyncTargetRegistry = require('lib/SyncTargetRegistry.js'); +const SyncTargetFilesystem = require('lib/SyncTargetFilesystem.js'); +const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js'); +const SyncTargetOneDriveDev = require('lib/SyncTargetOneDriveDev.js'); +const SyncTargetNextcloud = require('lib/SyncTargetNextcloud.js'); +const SyncTargetWebDAV = require('lib/SyncTargetWebDAV.js'); +const SyncTargetDropbox = require('lib/SyncTargetDropbox.js'); +const EncryptionService = require('lib/services/EncryptionService'); +const ResourceFetcher = require('lib/services/ResourceFetcher'); +const SearchEngineUtils = require('lib/services/SearchEngineUtils'); +const RevisionService = require('lib/services/RevisionService'); +const DecryptionWorker = require('lib/services/DecryptionWorker'); +const BaseService = require('lib/services/BaseService'); +const SearchEngine = require('lib/services/SearchEngine'); +const KvStore = require('lib/services/KvStore'); +const MigrationService = require('lib/services/MigrationService'); + +SyncTargetRegistry.addClass(SyncTargetFilesystem); +SyncTargetRegistry.addClass(SyncTargetOneDrive); +SyncTargetRegistry.addClass(SyncTargetOneDriveDev); +SyncTargetRegistry.addClass(SyncTargetNextcloud); +SyncTargetRegistry.addClass(SyncTargetWebDAV); +SyncTargetRegistry.addClass(SyncTargetDropbox); + +class BaseApplication { + constructor() { + this.logger_ = new Logger(); + this.dbLogger_ = new Logger(); + this.eventEmitter_ = new EventEmitter(); + + // Note: this is basically a cache of state.selectedFolderId. It should *only* + // be derived from the state and not set directly since that would make the + // state and UI out of sync. + this.currentFolder_ = null; + + this.decryptionWorker_resourceMetadataButNotBlobDecrypted = this.decryptionWorker_resourceMetadataButNotBlobDecrypted.bind(this); + } + + logger() { + return this.logger_; + } + + store() { + return this.store_; + } + + currentFolder() { + return this.currentFolder_; + } + + async refreshCurrentFolder() { + let newFolder = null; + + if (this.currentFolder_) newFolder = await Folder.load(this.currentFolder_.id); + if (!newFolder) newFolder = await Folder.defaultFolder(); + + this.switchCurrentFolder(newFolder); + } + + switchCurrentFolder(folder) { + if (!this.hasGui()) { + this.currentFolder_ = Object.assign({}, folder); + Setting.setValue('activeFolderId', folder ? folder.id : ''); + } else { + this.dispatch({ + type: 'FOLDER_SELECT', + id: folder ? folder.id : '', + }); + } + } + + // Handles the initial flags passed to main script and + // returns the remaining args. + async handleStartFlags_(argv, setDefaults = true) { + let matched = {}; + argv = argv.slice(0); + argv.splice(0, 2); // First arguments are the node executable, and the node JS file + + while (argv.length) { + let arg = argv[0]; + let nextArg = argv.length >= 2 ? argv[1] : null; + + if (arg == '--profile') { + if (!nextArg) throw new JoplinError(_('Usage: %s', '--profile '), 'flagError'); + matched.profileDir = nextArg; + argv.splice(0, 2); + continue; + } + + if (arg == '--no-welcome') { + matched.welcomeDisabled = true; + argv.splice(0, 1); + continue; + } + + if (arg == '--env') { + if (!nextArg) throw new JoplinError(_('Usage: %s', '--env '), 'flagError'); + matched.env = nextArg; + argv.splice(0, 2); + continue; + } + + if (arg == '--is-demo') { + Setting.setConstant('isDemo', true); + argv.splice(0, 1); + continue; + } + + if (arg == '--open-dev-tools') { + Setting.setConstant('openDevTools', true); + argv.splice(0, 1); + continue; + } + + if (arg == '--update-geolocation-disabled') { + Note.updateGeolocationEnabled_ = false; + argv.splice(0, 1); + continue; + } + + if (arg == '--stack-trace-enabled') { + this.showStackTraces_ = true; + argv.splice(0, 1); + continue; + } + + if (arg == '--log-level') { + if (!nextArg) throw new JoplinError(_('Usage: %s', '--log-level '), 'flagError'); + matched.logLevel = Logger.levelStringToId(nextArg); + argv.splice(0, 2); + continue; + } + + if (arg.indexOf('-psn') === 0) { + // Some weird flag passed by macOS - can be ignored. + // https://github.com/laurent22/joplin/issues/480 + // https://stackoverflow.com/questions/10242115 + argv.splice(0, 1); + continue; + } + + if (arg === '--enable-logging') { + // Electron-specific flag used for debugging - ignore it + argv.splice(0, 1); + continue; + } + + if (arg.length && arg[0] == '-') { + throw new JoplinError(_('Unknown flag: %s', arg), 'flagError'); + } else { + break; + } + } + + if (setDefaults) { + if (!matched.logLevel) matched.logLevel = Logger.LEVEL_INFO; + if (!matched.env) matched.env = 'prod'; + } + + return { + matched: matched, + argv: argv, + }; + } + + on(eventName, callback) { + return this.eventEmitter_.on(eventName, callback); + } + + async exit(code = 0) { + await Setting.saveAll(); + process.exit(code); + } + + async refreshNotes(state, useSelectedNoteId = false, noteHash = '') { + let parentType = state.notesParentType; + let parentId = null; + + if (parentType === 'Folder') { + parentId = state.selectedFolderId; + parentType = BaseModel.TYPE_FOLDER; + } else if (parentType === 'Tag') { + parentId = state.selectedTagId; + parentType = BaseModel.TYPE_TAG; + } else if (parentType === 'Search') { + parentId = state.selectedSearchId; + parentType = BaseModel.TYPE_SEARCH; + } + + this.logger().debug('Refreshing notes:', parentType, parentId); + + let options = { + order: stateUtils.notesOrder(state.settings), + uncompletedTodosOnTop: Setting.value('uncompletedTodosOnTop'), + showCompletedTodos: Setting.value('showCompletedTodos'), + caseInsensitive: true, + }; + + const source = JSON.stringify({ + options: options, + parentId: parentId, + }); + + let notes = []; + + if (parentId) { + if (parentType === Folder.modelType()) { + notes = await Note.previews(parentId, options); + } else if (parentType === Tag.modelType()) { + notes = await Tag.notes(parentId, options); + } else if (parentType === BaseModel.TYPE_SEARCH) { + const search = BaseModel.byId(state.searches, parentId); + notes = await SearchEngineUtils.notesForQuery(search.query_pattern); + } + } + + this.store().dispatch({ + type: 'NOTE_UPDATE_ALL', + notes: notes, + notesSource: source, + }); + + if (useSelectedNoteId) { + this.store().dispatch({ + type: 'NOTE_SELECT', + id: state.selectedNoteIds && state.selectedNoteIds.length ? state.selectedNoteIds[0] : null, + hash: noteHash, + }); + } else { + const lastSelectedNoteIds = stateUtils.lastSelectedNoteIds(state); + const foundIds = []; + for (let i = 0; i < lastSelectedNoteIds.length; i++) { + const noteId = lastSelectedNoteIds[i]; + let found = false; + for (let j = 0; j < notes.length; j++) { + if (notes[j].id === noteId) { + found = true; + break; + } + } + if (found) foundIds.push(noteId); + } + + let selectedNoteId = null; + if (foundIds.length) { + selectedNoteId = foundIds[0]; + } else { + selectedNoteId = notes.length ? notes[0].id : null; + } + + this.store().dispatch({ + type: 'NOTE_SELECT', + id: selectedNoteId, + }); + } + } + + resourceFetcher_downloadComplete(event) { + if (event.encrypted) { + DecryptionWorker.instance().scheduleStart(); + } + } + + async decryptionWorker_resourceMetadataButNotBlobDecrypted() { + this.scheduleAutoAddResources(); + } + + scheduleAutoAddResources() { + if (this.scheduleAutoAddResourcesIID_) return; + + this.scheduleAutoAddResourcesIID_ = setTimeout(() => { + this.scheduleAutoAddResourcesIID_ = null; + ResourceFetcher.instance().autoAddResources(); + }, 1000); + } + + reducerActionToString(action) { + let o = [action.type]; + if ('id' in action) o.push(action.id); + if ('noteId' in action) o.push(action.noteId); + if ('folderId' in action) o.push(action.folderId); + if ('tagId' in action) o.push(action.tagId); + if ('tag' in action) o.push(action.tag.id); + if ('folder' in action) o.push(action.folder.id); + if ('notesSource' in action) o.push(JSON.stringify(action.notesSource)); + return o.join(', '); + } + + hasGui() { + return false; + } + + uiType() { + return this.hasGui() ? 'gui' : 'cli'; + } + + generalMiddlewareFn() { + const middleware = store => next => action => { + return this.generalMiddleware(store, next, action); + }; + + return middleware; + } + + async applySettingsSideEffects(action = null) { + const sideEffects = { + 'dateFormat': async () => { + time.setLocale(Setting.value('locale')); + time.setDateFormat(Setting.value('dateFormat')); + time.setTimeFormat(Setting.value('timeFormat')); + }, + 'net.ignoreTlsErrors': async () => { + process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = Setting.value('net.ignoreTlsErrors') ? '0' : '1'; + }, + 'net.customCertificates': async () => { + const caPaths = Setting.value('net.customCertificates').split(','); + for (let i = 0; i < caPaths.length; i++) { + const f = caPaths[i].trim(); + if (!f) continue; + syswidecas.addCAs(f); + } + }, + 'encryption.enabled': async () => { + if (this.hasGui()) { + await EncryptionService.instance().loadMasterKeysFromSettings(); + DecryptionWorker.instance().scheduleStart(); + const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds(); + + this.dispatch({ + type: 'MASTERKEY_REMOVE_NOT_LOADED', + ids: loadedMasterKeyIds, + }); + + // Schedule a sync operation so that items that need to be encrypted + // are sent to sync target. + reg.scheduleSync(); + } + }, + 'sync.interval': async () => { + if (this.hasGui()) reg.setupRecurrentSync(); + }, + }; + + sideEffects['timeFormat'] = sideEffects['dateFormat']; + sideEffects['locale'] = sideEffects['dateFormat']; + sideEffects['encryption.activeMasterKeyId'] = sideEffects['encryption.enabled']; + sideEffects['encryption.passwordCache'] = sideEffects['encryption.enabled']; + + if (action) { + const effect = sideEffects[action.key]; + if (effect) await effect(); + } else { + for (const key in sideEffects) { + await sideEffects[key](); + } + } + } + + async generalMiddleware(store, next, action) { + // this.logger().debug('Reducer action', this.reducerActionToString(action)); + + const result = next(action); + const newState = store.getState(); + let refreshNotes = false; + let refreshFolders = false; + // let refreshTags = false; + let refreshNotesUseSelectedNoteId = false; + let refreshNotesHash = ''; + + await reduxSharedMiddleware(store, next, action); + + if (this.hasGui() && ['NOTE_UPDATE_ONE', 'NOTE_DELETE', 'FOLDER_UPDATE_ONE', 'FOLDER_DELETE'].indexOf(action.type) >= 0) { + if (!(await reg.syncTarget().syncStarted())) reg.scheduleSync(30 * 1000, { syncSteps: ['update_remote', 'delete_remote'] }); + SearchEngine.instance().scheduleSyncTables(); + } + + // Don't add FOLDER_UPDATE_ALL as refreshFolders() is calling it too, which + // would cause the sidebar to refresh all the time. + if (this.hasGui() && ['FOLDER_UPDATE_ONE'].indexOf(action.type) >= 0) { + refreshFolders = true; + } + + if (action.type == 'FOLDER_SELECT' || action.type === 'FOLDER_DELETE' || action.type === 'FOLDER_AND_NOTE_SELECT' || (action.type === 'SEARCH_UPDATE' && newState.notesParentType === 'Folder')) { + Setting.setValue('activeFolderId', newState.selectedFolderId); + this.currentFolder_ = newState.selectedFolderId ? await Folder.load(newState.selectedFolderId) : null; + refreshNotes = true; + + if (action.type === 'FOLDER_AND_NOTE_SELECT') { + refreshNotesUseSelectedNoteId = true; + refreshNotesHash = action.hash; + } + } + + if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key == 'uncompletedTodosOnTop') || action.type == 'SETTING_UPDATE_ALL')) { + refreshNotes = true; + } + + if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key == 'showCompletedTodos') || action.type == 'SETTING_UPDATE_ALL')) { + refreshNotes = true; + } + + if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key.indexOf('notes.sortOrder') === 0) || action.type == 'SETTING_UPDATE_ALL')) { + refreshNotes = true; + } + + if (action.type == 'TAG_SELECT' || action.type === 'TAG_DELETE') { + refreshNotes = true; + } + + if (action.type == 'SEARCH_SELECT' || action.type === 'SEARCH_DELETE') { + refreshNotes = true; + } + + if (refreshNotes) { + await this.refreshNotes(newState, refreshNotesUseSelectedNoteId, refreshNotesHash); + } + + if (action.type === 'NOTE_UPDATE_ONE') { + refreshFolders = true; + } + + if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key.indexOf('folders.sortOrder') === 0) || action.type == 'SETTING_UPDATE_ALL')) { + refreshFolders = 'now'; + } + + if (this.hasGui() && action.type === 'SYNC_GOT_ENCRYPTED_ITEM') { + DecryptionWorker.instance().scheduleStart(); + } + + if (this.hasGui() && action.type === 'SYNC_CREATED_RESOURCE') { + ResourceFetcher.instance().autoAddResources(); + } + + if (action.type == 'SETTING_UPDATE_ONE') { + await this.applySettingsSideEffects(action); + } else if (action.type == 'SETTING_UPDATE_ALL') { + await this.applySettingsSideEffects(); + } + + if (refreshFolders) { + if (refreshFolders === 'now') { + await FoldersScreenUtils.refreshFolders(); + } else { + await FoldersScreenUtils.scheduleRefreshFolders(); + } + } + + return result; + } + + dispatch(action) { + if (this.store()) return this.store().dispatch(action); + } + + reducer(state = defaultState, action) { + return reducer(state, action); + } + + initRedux() { + this.store_ = createStore(this.reducer, applyMiddleware(this.generalMiddlewareFn())); + BaseModel.dispatch = this.store().dispatch; + FoldersScreenUtils.dispatch = this.store().dispatch; + reg.dispatch = this.store().dispatch; + BaseSyncTarget.dispatch = this.store().dispatch; + DecryptionWorker.instance().dispatch = this.store().dispatch; + ResourceFetcher.instance().dispatch = this.store().dispatch; + } + + async readFlagsFromFile(flagPath) { + if (!fs.existsSync(flagPath)) return {}; + let flagContent = fs.readFileSync(flagPath, 'utf8'); + if (!flagContent) return {}; + + flagContent = flagContent.trim(); + + let flags = splitCommandString(flagContent); + flags.splice(0, 0, 'cmd'); + flags.splice(0, 0, 'node'); + + flags = await this.handleStartFlags_(flags, false); + + return flags.matched; + } + + determineProfileDir(initArgs) { + if (initArgs.profileDir) return initArgs.profileDir; + + if (process && process.env && process.env.PORTABLE_EXECUTABLE_DIR) return `${process.env.PORTABLE_EXECUTABLE_DIR}/JoplinProfile`; + + return `${os.homedir()}/.config/${Setting.value('appName')}`; + } + + async testing() { + const markdownUtils = require('lib/markdownUtils'); + const ClipperServer = require('lib/ClipperServer'); + const server = new ClipperServer(); + const HtmlToMd = require('lib/HtmlToMd'); + const service = new HtmlToMd(); + const html = await shim.fsDriver().readFile('/mnt/d/test.html'); + let markdown = service.parse(html, { baseUrl: 'https://duckduckgo.com/' }); + console.info(markdown); + console.info('--------------------------------------------------'); + + const imageUrls = markdownUtils.extractImageUrls(markdown); + let result = await server.downloadImages_(imageUrls); + result = await server.createResourcesFromPaths_(result); + console.info(result); + markdown = server.replaceImageUrlsByResources_(markdown, result); + console.info('--------------------------------------------------'); + console.info(markdown); + console.info('--------------------------------------------------'); + } + + async start(argv) { + let startFlags = await this.handleStartFlags_(argv); + + argv = startFlags.argv; + let initArgs = startFlags.matched; + if (argv.length) this.showPromptString_ = false; + + let appName = initArgs.env == 'dev' ? 'joplindev' : 'joplin'; + if (Setting.value('appId').indexOf('-desktop') >= 0) appName += '-desktop'; + Setting.setConstant('appName', appName); + + const profileDir = this.determineProfileDir(initArgs); + const resourceDirName = 'resources'; + const resourceDir = `${profileDir}/${resourceDirName}`; + const tempDir = `${profileDir}/tmp`; + + Setting.setConstant('env', initArgs.env); + Setting.setConstant('profileDir', profileDir); + Setting.setConstant('templateDir', `${profileDir}/templates`); + Setting.setConstant('resourceDirName', resourceDirName); + Setting.setConstant('resourceDir', resourceDir); + Setting.setConstant('tempDir', tempDir); + + await shim.fsDriver().remove(tempDir); + + await fs.mkdirp(profileDir, 0o755); + await fs.mkdirp(resourceDir, 0o755); + await fs.mkdirp(tempDir, 0o755); + + // Clean up any remaining watched files (they start with "edit-") + await shim.fsDriver().removeAllThatStartWith(profileDir, 'edit-'); + + const extraFlags = await this.readFlagsFromFile(`${profileDir}/flags.txt`); + initArgs = Object.assign(initArgs, extraFlags); + + this.logger_.addTarget('file', { path: `${profileDir}/log.txt` }); + if (Setting.value('env') === 'dev') this.logger_.addTarget('console', { level: Logger.LEVEL_WARN }); + this.logger_.setLevel(initArgs.logLevel); + + reg.setLogger(this.logger_); + reg.dispatch = () => {}; + + this.dbLogger_.addTarget('file', { path: `${profileDir}/log-database.txt` }); + this.dbLogger_.setLevel(initArgs.logLevel); + + if (Setting.value('env') === 'dev') { + this.dbLogger_.setLevel(Logger.LEVEL_INFO); + } + + this.logger_.info(`Profile directory: ${profileDir}`); + + this.database_ = new JoplinDatabase(new DatabaseDriverNode()); + this.database_.setLogExcludedQueryTypes(['SELECT']); + this.database_.setLogger(this.dbLogger_); + await this.database_.open({ name: `${profileDir}/database.sqlite` }); + + // if (Setting.value('env') === 'dev') await this.database_.clearForTesting(); + + reg.setDb(this.database_); + BaseModel.db_ = this.database_; + + await Setting.load(); + + if (Setting.value('firstStart')) { + const locale = shim.detectAndSetLocale(Setting); + reg.logger().info(`First start: detected locale as ${locale}`); + + if (Setting.value('env') === 'dev') { + Setting.setValue('showTrayIcon', 0); + Setting.setValue('autoUpdateEnabled', 0); + Setting.setValue('sync.interval', 3600); + } + + Setting.setValue('firstStart', 0); + } else { + setLocale(Setting.value('locale')); + } + + if ('welcomeDisabled' in initArgs) Setting.setValue('welcome.enabled', !initArgs.welcomeDisabled); + + if (!Setting.value('api.token')) { + EncryptionService.instance() + .randomHexString(64) + .then(token => { + Setting.setValue('api.token', token); + }); + } + + time.setDateFormat(Setting.value('dateFormat')); + time.setTimeFormat(Setting.value('timeFormat')); + + BaseItem.revisionService_ = RevisionService.instance(); + + KvStore.instance().setDb(reg.db()); + + BaseService.logger_ = this.logger_; + EncryptionService.instance().setLogger(this.logger_); + BaseItem.encryptionService_ = EncryptionService.instance(); + DecryptionWorker.instance().setLogger(this.logger_); + DecryptionWorker.instance().setEncryptionService(EncryptionService.instance()); + DecryptionWorker.instance().setKvStore(KvStore.instance()); + await EncryptionService.instance().loadMasterKeysFromSettings(); + DecryptionWorker.instance().on('resourceMetadataButNotBlobDecrypted', this.decryptionWorker_resourceMetadataButNotBlobDecrypted); + + ResourceFetcher.instance().setFileApi(() => { + return reg.syncTarget().fileApi(); + }); + ResourceFetcher.instance().setLogger(this.logger_); + ResourceFetcher.instance().on('downloadComplete', this.resourceFetcher_downloadComplete); + ResourceFetcher.instance().start(); + + SearchEngine.instance().setDb(reg.db()); + SearchEngine.instance().setLogger(reg.logger()); + SearchEngine.instance().scheduleSyncTables(); + + let currentFolderId = Setting.value('activeFolderId'); + let currentFolder = null; + if (currentFolderId) currentFolder = await Folder.load(currentFolderId); + if (!currentFolder) currentFolder = await Folder.defaultFolder(); + Setting.setValue('activeFolderId', currentFolder ? currentFolder.id : ''); + + await MigrationService.instance().run(); + + return argv; + } +} + +module.exports = { BaseApplication }; diff --git a/Server/dist/lib/BaseModel.js b/Server/dist/lib/BaseModel.js new file mode 100644 index 0000000000..7eef67e725 --- /dev/null +++ b/Server/dist/lib/BaseModel.js @@ -0,0 +1,549 @@ +const { Database } = require('lib/database.js'); +const { uuid } = require('lib/uuid.js'); +const { time } = require('lib/time-utils.js'); +const Mutex = require('async-mutex').Mutex; + +class BaseModel { + static modelType() { + throw new Error('Must be overriden'); + } + + static tableName() { + throw new Error('Must be overriden'); + } + + static addModelMd(model) { + if (!model) return model; + + if (Array.isArray(model)) { + let output = []; + for (let i = 0; i < model.length; i++) { + output.push(this.addModelMd(model[i])); + } + return output; + } else { + model = Object.assign({}, model); + model.type_ = this.modelType(); + return model; + } + } + + static logger() { + return this.db().logger(); + } + + static useUuid() { + return false; + } + + static byId(items, id) { + for (let i = 0; i < items.length; i++) { + if (items[i].id == id) return items[i]; + } + return null; + } + + static modelIndexById(items, id) { + for (let i = 0; i < items.length; i++) { + if (items[i].id == id) return i; + } + return -1; + } + + static modelsByIds(items, ids) { + const output = []; + for (let i = 0; i < items.length; i++) { + if (ids.indexOf(items[i].id) >= 0) { + output.push(items[i]); + } + } + return output; + } + + // Prefer the use of this function to compare IDs as it handles the case where + // one ID is null and the other is "", in which case they are actually considered to be the same. + static idsEqual(id1, id2) { + if (!id1 && !id2) return true; + if (!id1 && !!id2) return false; + if (!!id1 && !id2) return false; + return id1 === id2; + } + + static modelTypeToName(type) { + for (let i = 0; i < BaseModel.typeEnum_.length; i++) { + const e = BaseModel.typeEnum_[i]; + if (e[1] === type) return e[0].substr(5).toLowerCase(); + } + throw new Error(`Unknown model type: ${type}`); + } + + static hasField(name) { + let fields = this.fieldNames(); + return fields.indexOf(name) >= 0; + } + + static fieldNames(withPrefix = false) { + let output = this.db().tableFieldNames(this.tableName()); + if (!withPrefix) return output; + + let p = withPrefix === true ? this.tableName() : withPrefix; + let temp = []; + for (let i = 0; i < output.length; i++) { + temp.push(`${p}.${output[i]}`); + } + + return temp; + } + + static fieldType(name, defaultValue = null) { + let fields = this.fields(); + for (let i = 0; i < fields.length; i++) { + if (fields[i].name == name) return fields[i].type; + } + if (defaultValue !== null) return defaultValue; + throw new Error(`Unknown field: ${name}`); + } + + static fields() { + return this.db().tableFields(this.tableName()); + } + + static removeUnknownFields(model) { + const newModel = {}; + for (let n in model) { + if (!model.hasOwnProperty(n)) continue; + if (!this.hasField(n) && n !== 'type_') continue; + newModel[n] = model[n]; + } + return newModel; + } + + static new() { + let fields = this.fields(); + let output = {}; + for (let i = 0; i < fields.length; i++) { + let f = fields[i]; + output[f.name] = f.default; + } + return output; + } + + static modOptions(options) { + if (!options) { + options = {}; + } else { + options = Object.assign({}, options); + } + if (!('isNew' in options)) options.isNew = 'auto'; + if (!('autoTimestamp' in options)) options.autoTimestamp = true; + return options; + } + + static count(options = null) { + if (!options) options = {}; + let sql = `SELECT count(*) as total FROM \`${this.tableName()}\``; + if (options.where) sql += ` WHERE ${options.where}`; + return this.db() + .selectOne(sql) + .then(r => { + return r ? r['total'] : 0; + }); + } + + static load(id) { + return this.loadByField('id', id); + } + + static shortId(id) { + return id.substr(0, 5); + } + + static loadByPartialId(partialId) { + return this.modelSelectAll(`SELECT * FROM \`${this.tableName()}\` WHERE \`id\` LIKE ?`, [`${partialId}%`]); + } + + static applySqlOptions(options, sql, params = null) { + if (!options) options = {}; + + if (options.order && options.order.length) { + let items = []; + for (let i = 0; i < options.order.length; i++) { + const o = options.order[i]; + let item = o.by; + if (options.caseInsensitive === true) item += ' COLLATE NOCASE'; + if (o.dir) item += ` ${o.dir}`; + items.push(item); + } + sql += ` ORDER BY ${items.join(', ')}`; + } + + if (options.limit) sql += ` LIMIT ${options.limit}`; + + return { sql: sql, params: params }; + } + + static async allIds(options = null) { + let q = this.applySqlOptions(options, `SELECT id FROM \`${this.tableName()}\``); + const rows = await this.db().selectAll(q.sql, q.params); + return rows.map(r => r.id); + } + + static async all(options = null) { + if (!options) options = {}; + if (!options.fields) options.fields = '*'; + + let q = this.applySqlOptions(options, `SELECT ${this.db().escapeFields(options.fields)} FROM \`${this.tableName()}\``); + return this.modelSelectAll(q.sql); + } + + static async byIds(ids, options = null) { + if (!ids.length) return []; + if (!options) options = {}; + if (!options.fields) options.fields = '*'; + + let sql = `SELECT ${this.db().escapeFields(options.fields)} FROM \`${this.tableName()}\``; + sql += ` WHERE id IN ("${ids.join('","')}")`; + let q = this.applySqlOptions(options, sql); + return this.modelSelectAll(q.sql); + } + + static async search(options = null) { + if (!options) options = {}; + if (!options.fields) options.fields = '*'; + + let conditions = options.conditions ? options.conditions.slice(0) : []; + let params = options.conditionsParams ? options.conditionsParams.slice(0) : []; + + if (options.titlePattern) { + let pattern = options.titlePattern.replace(/\*/g, '%'); + conditions.push('title LIKE ?'); + params.push(pattern); + } + + if ('limit' in options && options.limit <= 0) return []; + + let sql = `SELECT ${this.db().escapeFields(options.fields)} FROM \`${this.tableName()}\``; + if (conditions.length) sql += ` WHERE ${conditions.join(' AND ')}`; + + let query = this.applySqlOptions(options, sql, params); + return this.modelSelectAll(query.sql, query.params); + } + + static modelSelectOne(sql, params = null) { + if (params === null) params = []; + return this.db() + .selectOne(sql, params) + .then(model => { + return this.filter(this.addModelMd(model)); + }); + } + + static modelSelectAll(sql, params = null) { + if (params === null) params = []; + return this.db() + .selectAll(sql, params) + .then(models => { + return this.filterArray(this.addModelMd(models)); + }); + } + + static loadByField(fieldName, fieldValue, options = null) { + if (!options) options = {}; + if (!('caseInsensitive' in options)) options.caseInsensitive = false; + let sql = `SELECT * FROM \`${this.tableName()}\` WHERE \`${fieldName}\` = ?`; + if (options.caseInsensitive) sql += ' COLLATE NOCASE'; + return this.modelSelectOne(sql, [fieldValue]); + } + + static loadByTitle(fieldValue) { + return this.modelSelectOne(`SELECT * FROM \`${this.tableName()}\` WHERE \`title\` = ?`, [fieldValue]); + } + + static diffObjects(oldModel, newModel) { + let output = {}; + const fields = this.diffObjectsFields(oldModel, newModel); + for (let i = 0; i < fields.length; i++) { + output[fields[i]] = newModel[fields[i]]; + } + if ('type_' in newModel) output.type_ = newModel.type_; + return output; + } + + static diffObjectsFields(oldModel, newModel) { + let output = []; + for (let n in newModel) { + if (!newModel.hasOwnProperty(n)) continue; + if (n == 'type_') continue; + if (!(n in oldModel) || newModel[n] !== oldModel[n]) { + output.push(n); + } + } + return output; + } + + static modelsAreSame(oldModel, newModel) { + const diff = this.diffObjects(oldModel, newModel); + delete diff.type_; + return !Object.getOwnPropertyNames(diff).length; + } + + static saveMutex(modelOrId) { + const noLockMutex = { + acquire: function() { + return null; + }, + }; + + if (!modelOrId) return noLockMutex; + + let modelId = typeof modelOrId === 'string' ? modelOrId : modelOrId.id; + + if (!modelId) return noLockMutex; + + let mutex = BaseModel.saveMutexes_[modelId]; + if (mutex) return mutex; + + mutex = new Mutex(); + BaseModel.saveMutexes_[modelId] = mutex; + return mutex; + } + + static releaseSaveMutex(modelOrId, release) { + if (!release) return; + if (!modelOrId) return release(); + + let modelId = typeof modelOrId === 'string' ? modelOrId : modelOrId.id; + + if (!modelId) return release(); + + let mutex = BaseModel.saveMutexes_[modelId]; + if (!mutex) return release(); + + delete BaseModel.saveMutexes_[modelId]; + release(); + } + + static saveQuery(o, options) { + let temp = {}; + let fieldNames = this.fieldNames(); + for (let i = 0; i < fieldNames.length; i++) { + let n = fieldNames[i]; + if (n in o) temp[n] = o[n]; + } + + // Remove fields that are not in the `fields` list, if provided. + // Note that things like update_time, user_updated_time will still + // be part of the final list of fields if autoTimestamp is on. + // id also will stay. + if (!options.isNew && options.fields) { + const filtered = {}; + for (let k in temp) { + if (!temp.hasOwnProperty(k)) continue; + if (k !== 'id' && options.fields.indexOf(k) < 0) continue; + filtered[k] = temp[k]; + } + temp = filtered; + } + + o = temp; + + let modelId = temp.id; + let query = {}; + + const timeNow = time.unixMs(); + + if (options.autoTimestamp && this.hasField('updated_time')) { + o.updated_time = timeNow; + } + + // The purpose of user_updated_time is to allow the user to manually set the time of a note (in which case + // options.autoTimestamp will be `false`). However note that if the item is later changed, this timestamp + // will be set again to the current time. + // + // The technique to modify user_updated_time while keeping updated_time current (so that sync can happen) is to + // manually set updated_time when saving and to set autoTimestamp to false, for example: + // Note.save({ id: "...", updated_time: Date.now(), user_updated_time: 1436342618000 }, { autoTimestamp: false }) + if (options.autoTimestamp && this.hasField('user_updated_time')) { + o.user_updated_time = timeNow; + } + + if (options.isNew) { + if (this.useUuid() && !o.id) { + modelId = uuid.create(); + o.id = modelId; + } + + if (!o.created_time && this.hasField('created_time')) { + o.created_time = timeNow; + } + + if (!o.user_created_time && this.hasField('user_created_time')) { + o.user_created_time = o.created_time ? o.created_time : timeNow; + } + + if (!o.user_updated_time && this.hasField('user_updated_time')) { + o.user_updated_time = o.updated_time ? o.updated_time : timeNow; + } + + query = Database.insertQuery(this.tableName(), o); + } else { + let where = { id: o.id }; + let temp = Object.assign({}, o); + delete temp.id; + + query = Database.updateQuery(this.tableName(), temp, where); + } + + query.id = modelId; + query.modObject = o; + + return query; + } + + static async save(o, options = null) { + // When saving, there's a mutex per model ID. This is because the model returned from this function + // is basically its input `o` (instead of being read from the database, for performance reasons). + // This works well in general except if that model is saved simultaneously in two places. In that + // case, the output won't be up-to-date and would cause for example display issues with out-dated + // notes being displayed. This was an issue when notes were being synchronised while being decrypted + // at the same time. + + const mutexRelease = await this.saveMutex(o).acquire(); + + options = this.modOptions(options); + options.isNew = this.isNew(o, options); + + // Diff saving is an optimisation which takes a new version of the item and an old one, + // do a diff and save only this diff. IMPORTANT: When using this make sure that both + // models have been normalised using ItemClass.filter() + const isDiffSaving = options && options.oldItem && !options.isNew; + + if (isDiffSaving) { + const newObject = BaseModel.diffObjects(options.oldItem, o); + newObject.type_ = o.type_; + newObject.id = o.id; + o = newObject; + } + + o = this.filter(o); + + let queries = []; + let saveQuery = this.saveQuery(o, options); + let modelId = saveQuery.id; + + queries.push(saveQuery); + + if (options.nextQueries && options.nextQueries.length) { + queries = queries.concat(options.nextQueries); + } + + let output = null; + + try { + await this.db().transactionExecBatch(queries); + + o = Object.assign({}, o); + // eslint-disable-next-line require-atomic-updates + if (modelId) o.id = modelId; + // eslint-disable-next-line require-atomic-updates + if ('updated_time' in saveQuery.modObject) o.updated_time = saveQuery.modObject.updated_time; + // eslint-disable-next-line require-atomic-updates + if ('created_time' in saveQuery.modObject) o.created_time = saveQuery.modObject.created_time; + // eslint-disable-next-line require-atomic-updates + if ('user_updated_time' in saveQuery.modObject) o.user_updated_time = saveQuery.modObject.user_updated_time; + // eslint-disable-next-line require-atomic-updates + if ('user_created_time' in saveQuery.modObject) o.user_created_time = saveQuery.modObject.user_created_time; + o = this.addModelMd(o); + + if (isDiffSaving) { + for (let n in options.oldItem) { + if (!options.oldItem.hasOwnProperty(n)) continue; + if (n in o) continue; + o[n] = options.oldItem[n]; + } + } + + output = this.filter(o); + } finally { + this.releaseSaveMutex(o, mutexRelease); + } + + return output; + } + + static isNew(object, options) { + if (options && 'isNew' in options) { + // options.isNew can be "auto" too + if (options.isNew === true) return true; + if (options.isNew === false) return false; + } + + return !object.id; + } + + static filterArray(models) { + let output = []; + for (let i = 0; i < models.length; i++) { + output.push(this.filter(models[i])); + } + return output; + } + + static filter(model) { + if (!model) return model; + + let output = Object.assign({}, model); + for (let n in output) { + if (!output.hasOwnProperty(n)) continue; + + // The SQLite database doesn't have booleans so cast everything to int + if (output[n] === true) { + output[n] = 1; + } else if (output[n] === false) { + output[n] = 0; + } else { + const t = this.fieldType(n, Database.TYPE_UNKNOWN); + if (t === Database.TYPE_INT) { + output[n] = !n ? 0 : parseInt(output[n], 10); + } + } + } + + return output; + } + + static delete(id) { + if (!id) throw new Error('Cannot delete object without an ID'); + return this.db().exec(`DELETE FROM ${this.tableName()} WHERE id = ?`, [id]); + } + + static batchDelete(ids, options = null) { + if (!ids.length) return; + options = this.modOptions(options); + const idFieldName = options.idFieldName ? options.idFieldName : 'id'; + const sql = `DELETE FROM ${this.tableName()} WHERE ${idFieldName} IN ("${ids.join('","')}")`; + return this.db().exec(sql); + } + + static db() { + if (!this.db_) throw new Error('Accessing database before it has been initialised'); + return this.db_; + } + + static isReady() { + return !!this.db_; + } +} + +BaseModel.typeEnum_ = [['TYPE_NOTE', 1], ['TYPE_FOLDER', 2], ['TYPE_SETTING', 3], ['TYPE_RESOURCE', 4], ['TYPE_TAG', 5], ['TYPE_NOTE_TAG', 6], ['TYPE_SEARCH', 7], ['TYPE_ALARM', 8], ['TYPE_MASTER_KEY', 9], ['TYPE_ITEM_CHANGE', 10], ['TYPE_NOTE_RESOURCE', 11], ['TYPE_RESOURCE_LOCAL_STATE', 12], ['TYPE_REVISION', 13], ['TYPE_MIGRATION', 14]]; + +for (let i = 0; i < BaseModel.typeEnum_.length; i++) { + const e = BaseModel.typeEnum_[i]; + BaseModel[e[0]] = e[1]; +} + +BaseModel.db_ = null; +BaseModel.dispatch = function() {}; +BaseModel.saveMutexes_ = {}; + +module.exports = BaseModel; diff --git a/Server/dist/lib/BaseSyncTarget.js b/Server/dist/lib/BaseSyncTarget.js new file mode 100644 index 0000000000..e0d2c75288 --- /dev/null +++ b/Server/dist/lib/BaseSyncTarget.js @@ -0,0 +1,127 @@ +const EncryptionService = require('lib/services/EncryptionService.js'); + +class BaseSyncTarget { + constructor(db, options = null) { + this.db_ = db; + this.synchronizer_ = null; + this.initState_ = null; + this.logger_ = null; + this.options_ = options; + } + + static supportsConfigCheck() { + return false; + } + + static resourceDirName() { + return '.resource'; + } + + option(name, defaultValue = null) { + return this.options_ && name in this.options_ ? this.options_[name] : defaultValue; + } + + logger() { + return this.logger_; + } + + setLogger(v) { + this.logger_ = v; + } + + db() { + return this.db_; + } + + async isAuthenticated() { + return false; + } + + authRouteName() { + return null; + } + + static id() { + throw new Error('id() not implemented'); + } + + // Note: it cannot be called just "name()" because that's a reserved keyword and + // it would throw an obscure error in React Native. + static targetName() { + throw new Error('targetName() not implemented'); + } + + static label() { + throw new Error('label() not implemented'); + } + + async initSynchronizer() { + throw new Error('initSynchronizer() not implemented'); + } + + async initFileApi() { + throw new Error('initFileApi() not implemented'); + } + + async fileApi() { + if (this.fileApi_) return this.fileApi_; + this.fileApi_ = await this.initFileApi(); + return this.fileApi_; + } + + fileApiSync() { + return this.fileApi_; + } + + // Usually each sync target should create and setup its own file API via initFileApi() + // but for testing purposes it might be convenient to provide it here so that multiple + // clients can share and sync to the same file api (see test-utils.js) + setFileApi(v) { + this.fileApi_ = v; + } + + async synchronizer() { + if (this.synchronizer_) return this.synchronizer_; + + if (this.initState_ == 'started') { + // Synchronizer is already being initialized, so wait here till it's done. + return new Promise((resolve, reject) => { + const iid = setInterval(() => { + if (this.initState_ == 'ready') { + clearInterval(iid); + resolve(this.synchronizer_); + } + if (this.initState_ == 'error') { + clearInterval(iid); + reject(new Error('Could not initialise synchroniser')); + } + }, 1000); + }); + } else { + this.initState_ = 'started'; + + try { + this.synchronizer_ = await this.initSynchronizer(); + this.synchronizer_.setLogger(this.logger()); + this.synchronizer_.setEncryptionService(EncryptionService.instance()); + this.synchronizer_.dispatch = BaseSyncTarget.dispatch; + this.initState_ = 'ready'; + return this.synchronizer_; + } catch (error) { + this.initState_ = 'error'; + throw error; + } + } + } + + async syncStarted() { + if (!this.synchronizer_) return false; + if (!(await this.isAuthenticated())) return false; + const sync = await this.synchronizer(); + return sync.state() != 'idle'; + } +} + +BaseSyncTarget.dispatch = () => {}; + +module.exports = BaseSyncTarget; diff --git a/Server/dist/lib/Cache.js b/Server/dist/lib/Cache.js new file mode 100644 index 0000000000..7e5dd27e58 --- /dev/null +++ b/Server/dist/lib/Cache.js @@ -0,0 +1,33 @@ +class Cache { + async getItem(name) { + let output = null; + try { + const storage = await Cache.storage(); + output = await storage.getItem(name); + } catch (error) { + console.info(error); + // Defaults to returning null + } + return output; + } + + async setItem(name, value, ttl = null) { + try { + const storage = await Cache.storage(); + const options = {}; + if (ttl !== null) options.ttl = ttl; + await storage.setItem(name, value, options); + } catch (error) { + // Defaults to not saving to cache + } + } +} + +Cache.storage = async function() { + if (Cache.storage_) return Cache.storage_; + Cache.storage_ = require('node-persist'); + await Cache.storage_.init({ dir: `${require('os').tmpdir()}/joplin-cache`, ttl: 1000 * 60 }); + return Cache.storage_; +}; + +module.exports = Cache; diff --git a/Server/dist/lib/ClipperServer.js b/Server/dist/lib/ClipperServer.js new file mode 100644 index 0000000000..e4327b5f1c --- /dev/null +++ b/Server/dist/lib/ClipperServer.js @@ -0,0 +1,226 @@ +const urlParser = require('url'); +const Setting = require('lib/models/Setting'); +const { Logger } = require('lib/logger.js'); +const { randomClipperPort, startPort } = require('lib/randomClipperPort'); +const enableServerDestroy = require('server-destroy'); +const Api = require('lib/services/rest/Api'); +const ApiResponse = require('lib/services/rest/ApiResponse'); +const multiparty = require('multiparty'); + +class ClipperServer { + constructor() { + this.logger_ = new Logger(); + this.startState_ = 'idle'; + this.server_ = null; + this.port_ = null; + this.api_ = new Api(() => { + return Setting.value('api.token'); + }); + } + + static instance() { + if (this.instance_) return this.instance_; + this.instance_ = new ClipperServer(); + return this.instance_; + } + + setLogger(l) { + this.logger_ = l; + this.api_.setLogger(l); + } + + logger() { + return this.logger_; + } + + setDispatch(d) { + this.dispatch_ = d; + } + + dispatch(action) { + if (!this.dispatch_) throw new Error('dispatch not set!'); + this.dispatch_(action); + } + + setStartState(v) { + if (this.startState_ === v) return; + this.startState_ = v; + this.dispatch({ + type: 'CLIPPER_SERVER_SET', + startState: v, + }); + } + + setPort(v) { + if (this.port_ === v) return; + this.port_ = v; + this.dispatch({ + type: 'CLIPPER_SERVER_SET', + port: v, + }); + } + + async findAvailablePort() { + const tcpPortUsed = require('tcp-port-used'); + + let state = null; + for (let i = 0; i < 10000; i++) { + state = randomClipperPort(state, Setting.value('env')); + const inUse = await tcpPortUsed.check(state.port); + if (!inUse) return state.port; + } + + throw new Error('All potential ports are in use or not available.'); + } + + async isRunning() { + const tcpPortUsed = require('tcp-port-used'); + const port = Setting.value('api.port') ? Setting.value('api.port') : startPort(Setting.value('env')); + const inUse = await tcpPortUsed.check(port); + return inUse ? port : 0; + } + + async start() { + this.setPort(null); + + this.setStartState('starting'); + + const settingPort = Setting.value('api.port'); + + try { + const p = settingPort ? settingPort : await this.findAvailablePort(); + this.setPort(p); + } catch (error) { + this.setStartState('idle'); + this.logger().error(error); + return; + } + + this.server_ = require('http').createServer(); + + this.server_.on('request', async (request, response) => { + const writeCorsHeaders = (code, contentType = 'application/json', additionalHeaders = null) => { + const headers = Object.assign( + {}, + { + 'Content-Type': contentType, + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE', + 'Access-Control-Allow-Headers': 'X-Requested-With,content-type', + }, + additionalHeaders ? additionalHeaders : {} + ); + response.writeHead(code, headers); + }; + + const writeResponseJson = (code, object) => { + writeCorsHeaders(code); + response.write(JSON.stringify(object)); + response.end(); + }; + + const writeResponseText = (code, text) => { + writeCorsHeaders(code, 'text/plain'); + response.write(text); + response.end(); + }; + + const writeResponseInstance = (code, instance) => { + if (instance.type === 'attachment') { + const filename = instance.attachmentFilename ? instance.attachmentFilename : 'file'; + writeCorsHeaders(code, instance.contentType ? instance.contentType : 'application/octet-stream', { + 'Content-disposition': `attachment; filename=${filename}`, + 'Content-Length': instance.body.length, + }); + response.end(instance.body); + } else { + throw new Error('Not implemented'); + } + }; + + const writeResponse = (code, response) => { + if (response instanceof ApiResponse) { + writeResponseInstance(code, response); + } else if (typeof response === 'string') { + writeResponseText(code, response); + } else { + writeResponseJson(code, response); + } + }; + + this.logger().info(`Request: ${request.method} ${request.url}`); + + const url = urlParser.parse(request.url, true); + + const execRequest = async (request, body = '', files = []) => { + try { + const response = await this.api_.route(request.method, url.pathname, url.query, body, files); + writeResponse(200, response ? response : ''); + } catch (error) { + this.logger().error(error); + const httpCode = error.httpCode ? error.httpCode : 500; + const msg = []; + if (httpCode >= 500) msg.push('Internal Server Error'); + if (error.message) msg.push(error.message); + + writeResponse(httpCode, { error: msg.join(': ') }); + } + }; + + const contentType = request.headers['content-type'] ? request.headers['content-type'] : ''; + + if (request.method === 'OPTIONS') { + writeCorsHeaders(200); + response.end(); + } else { + if (contentType.indexOf('multipart/form-data') === 0) { + const form = new multiparty.Form(); + + form.parse(request, function(error, fields, files) { + if (error) { + writeResponse(error.httpCode ? error.httpCode : 500, error.message); + return; + } else { + execRequest(request, fields && fields.props && fields.props.length ? fields.props[0] : '', files && files.data ? files.data : []); + } + }); + } else { + if (request.method === 'POST' || request.method === 'PUT') { + let body = ''; + + request.on('data', data => { + body += data; + }); + + request.on('end', async () => { + execRequest(request, body); + }); + } else { + execRequest(request); + } + } + } + }); + + enableServerDestroy(this.server_); + + this.logger().info(`Starting Clipper server on port ${this.port_}`); + + this.server_.listen(this.port_, '127.0.0.1'); + + this.setStartState('started'); + + // We return an empty promise that never resolves so that it's possible to `await` the server indefinitely. + // This is used only in command-server.js + return new Promise(() => {}); + } + + async stop() { + this.server_.destroy(); + this.server_ = null; + this.setStartState('idle'); + this.setPort(null); + } +} + +module.exports = ClipperServer; diff --git a/Server/dist/lib/DropboxApi.js b/Server/dist/lib/DropboxApi.js new file mode 100644 index 0000000000..c86ae7c556 --- /dev/null +++ b/Server/dist/lib/DropboxApi.js @@ -0,0 +1,213 @@ +const { Logger } = require('lib/logger.js'); +const { shim } = require('lib/shim.js'); +const JoplinError = require('lib/JoplinError'); +const { time } = require('lib/time-utils'); +const EventDispatcher = require('lib/EventDispatcher'); + +class DropboxApi { + constructor(options) { + this.logger_ = new Logger(); + this.options_ = options; + this.authToken_ = null; + this.dispatcher_ = new EventDispatcher(); + } + + clientId() { + return this.options_.id; + } + + clientSecret() { + return this.options_.secret; + } + + setLogger(l) { + this.logger_ = l; + } + + logger() { + return this.logger_; + } + + authToken() { + return this.authToken_; // Without the "Bearer " prefix + } + + on(eventName, callback) { + return this.dispatcher_.on(eventName, callback); + } + + setAuthToken(v) { + this.authToken_ = v; + this.dispatcher_.dispatch('authRefreshed', this.authToken()); + } + + loginUrl() { + return `https://www.dropbox.com/oauth2/authorize?response_type=code&client_id=${this.clientId()}`; + } + + baseUrl(endPointFormat) { + if (['content', 'api'].indexOf(endPointFormat) < 0) throw new Error(`Invalid end point format: ${endPointFormat}`); + return `https://${endPointFormat}.dropboxapi.com/2`; + } + + requestToCurl_(url, options) { + let output = []; + output.push('curl'); + if (options.method) output.push(`-X ${options.method}`); + if (options.headers) { + for (let n in options.headers) { + if (!options.headers.hasOwnProperty(n)) continue; + output.push(`${'-H ' + '\''}${n}: ${options.headers[n]}'`); + } + } + if (options.body) output.push(`${'--data ' + '"'}${options.body}"`); + output.push(url); + + return output.join(' '); + } + + async execAuthToken(authCode) { + const postData = { + code: authCode, + grant_type: 'authorization_code', + client_id: this.clientId(), + client_secret: this.clientSecret(), + }; + + var formBody = []; + for (var property in postData) { + var encodedKey = encodeURIComponent(property); + var encodedValue = encodeURIComponent(postData[property]); + formBody.push(`${encodedKey}=${encodedValue}`); + } + formBody = formBody.join('&'); + + const response = await shim.fetch('https://api.dropboxapi.com/oauth2/token', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', + }, + body: formBody, + }); + + const responseText = await response.text(); + if (!response.ok) throw new Error(responseText); + return JSON.parse(responseText); + } + + isTokenError(status, responseText) { + if (status === 401) return true; + if (responseText.indexOf('OAuth 2 access token is malformed') >= 0) return true; + // eg. Error: POST files/create_folder_v2: Error (400): Error in call to API function "files/create_folder_v2": Must provide HTTP header "Authorization" or URL parameter "authorization". + if (responseText.indexOf('Must provide HTTP header "Authorization"') >= 0) return true; + return false; + } + + async exec(method, path = '', body = null, headers = null, options = null) { + if (headers === null) headers = {}; + if (options === null) options = {}; + if (!options.target) options.target = 'string'; + + const authToken = this.authToken(); + + if (authToken) headers['Authorization'] = `Bearer ${authToken}`; + + const endPointFormat = ['files/upload', 'files/download'].indexOf(path) >= 0 ? 'content' : 'api'; + + if (endPointFormat === 'api') { + headers['Content-Type'] = 'application/json'; + if (body && typeof body === 'object') body = JSON.stringify(body); + } else { + headers['Content-Type'] = 'application/octet-stream'; + } + + const fetchOptions = {}; + fetchOptions.headers = headers; + fetchOptions.method = method; + if (options.path) fetchOptions.path = options.path; + if (body) fetchOptions.body = body; + + const url = path.indexOf('https://') === 0 ? path : `${this.baseUrl(endPointFormat)}/${path}`; + + let tryCount = 0; + + while (true) { + try { + let response = null; + + // console.info(this.requestToCurl_(url, fetchOptions)); + + // console.info(method + ' ' + url); + + if (options.source == 'file' && (method == 'POST' || method == 'PUT')) { + response = await shim.uploadBlob(url, fetchOptions); + } else if (options.target == 'string') { + response = await shim.fetch(url, fetchOptions); + } else { + // file + response = await shim.fetchBlob(url, fetchOptions); + } + + const responseText = await response.text(); + + // console.info('Response: ' + responseText); + + let responseJson_ = null; + const loadResponseJson = () => { + if (!responseText) return null; + if (responseJson_) return responseJson_; + try { + responseJson_ = JSON.parse(responseText); + } catch (error) { + return { error: responseText }; + } + return responseJson_; + }; + + // Creates an error object with as much data as possible as it will appear in the log, which will make debugging easier + const newError = message => { + const json = loadResponseJson(); + let code = ''; + if (json && json.error_summary) { + code = json.error_summary; + } + + // Gives a shorter response for error messages. Useful for cases where a full HTML page is accidentally loaded instead of + // JSON. That way the error message will still show there's a problem but without filling up the log or screen. + const shortResponseText = (`${responseText}`).substr(0, 1024); + const error = new JoplinError(`${method} ${path}: ${message} (${response.status}): ${shortResponseText}`, code); + error.httpStatus = response.status; + return error; + }; + + if (!response.ok) { + if (this.isTokenError(response.status, responseText)) { + this.setAuthToken(null); + } + + // When using fetchBlob we only get a string (not xml or json) back + if (options.target === 'file') throw newError('fetchBlob error'); + + throw newError('Error'); + } + + if (options.responseFormat === 'text') return responseText; + + return loadResponseJson(); + } catch (error) { + tryCount++; + if (error && typeof error.code === 'string' && error.code.indexOf('too_many_write_operations') >= 0) { + this.logger().warn(`too_many_write_operations ${tryCount}`); + if (tryCount >= 3) { + throw error; + } + await time.sleep(tryCount * 2); + } else { + throw error; + } + } + } + } +} + +module.exports = DropboxApi; diff --git a/Server/dist/lib/EventDispatcher.js b/Server/dist/lib/EventDispatcher.js new file mode 100644 index 0000000000..758d7eee6e --- /dev/null +++ b/Server/dist/lib/EventDispatcher.js @@ -0,0 +1,33 @@ +class EventDispatcher { + constructor() { + this.listeners_ = []; + } + + dispatch(eventName, event = null) { + if (!this.listeners_[eventName]) return; + + let ls = this.listeners_[eventName]; + for (let i = 0; i < ls.length; i++) { + ls[i](event); + } + } + + on(eventName, callback) { + if (!this.listeners_[eventName]) this.listeners_[eventName] = []; + this.listeners_[eventName].push(callback); + } + + off(eventName, callback) { + if (!this.listeners_[eventName]) return; + + let ls = this.listeners_[eventName]; + for (let i = 0; i < ls.length; i++) { + if (ls[i] === callback) { + ls.splice(i, 1); + return; + } + } + } +} + +module.exports = EventDispatcher; diff --git a/Server/dist/lib/HtmlToMd.js b/Server/dist/lib/HtmlToMd.js new file mode 100644 index 0000000000..391298bcf6 --- /dev/null +++ b/Server/dist/lib/HtmlToMd.js @@ -0,0 +1,21 @@ +const TurndownService = require('joplin-turndown'); +const markdownUtils = require('lib/markdownUtils'); + +class HtmlToMd { + parse(html, options = {}) { + const turndownPluginGfm = require('joplin-turndown-plugin-gfm').gfm; + const turndown = new TurndownService({ + headingStyle: 'atx', + anchorNames: options.anchorNames ? options.anchorNames.map(n => n.trim().toLowerCase()) : [], + codeBlockStyle: 'fenced', + }); + turndown.use(turndownPluginGfm); + turndown.remove('script'); + turndown.remove('style'); + let md = turndown.turndown(html); + if (options.baseUrl) md = markdownUtils.prependBaseUrl(md, options.baseUrl); + return md; + } +} + +module.exports = HtmlToMd; diff --git a/Server/dist/lib/JoplinError.js b/Server/dist/lib/JoplinError.js new file mode 100644 index 0000000000..11724fcf41 --- /dev/null +++ b/Server/dist/lib/JoplinError.js @@ -0,0 +1,8 @@ +class JoplinError extends Error { + constructor(message, code = null) { + super(message); + this.code = code; + } +} + +module.exports = JoplinError; diff --git a/Server/dist/lib/JoplinServerApi.js b/Server/dist/lib/JoplinServerApi.js new file mode 100644 index 0000000000..ebbcee0bbc --- /dev/null +++ b/Server/dist/lib/JoplinServerApi.js @@ -0,0 +1,184 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var Logger = require('lib/logger.js').Logger; +var rtrimSlashes = require('lib/path-utils.js').rtrimSlashes; +var shim = require('lib/shim').shim; +var JoplinServerApi = /** @class */ (function () { + function JoplinServerApi(options) { + this.logger_ = null; + this.options_ = null; + this.sessionId_ = null; + this.logger_ = new Logger(); + this.options_ = options; + } + JoplinServerApi.prototype.setLogger = function (l) { + this.logger_ = l; + }; + JoplinServerApi.prototype.logger = function () { + return this.logger_; + }; + JoplinServerApi.prototype.baseUrl = function () { + return rtrimSlashes(this.options_.baseUrl()); + }; + JoplinServerApi.prototype.setSessionId = function (v) { + this.sessionId_ = v; + }; + JoplinServerApi.prototype.sessionId = function () { + return this.sessionId_; + }; + JoplinServerApi.prototype.requestToCurl_ = function (url, options) { + var output = []; + output.push('curl'); + output.push('-v'); + if (options.method) + output.push("-X " + options.method); + if (options.headers) { + for (var n in options.headers) { + if (!options.headers.hasOwnProperty(n)) + continue; + output.push("" + '-H ' + '"' + n + ": " + options.headers[n] + "\""); + } + } + if (options.body) + output.push("" + '--data ' + '\'' + options.body + "'"); + output.push(url); + return output.join(' '); + }; + JoplinServerApi.prototype.exec = function (method, path, body, headers, options) { + if (path === void 0) { path = ''; } + if (body === void 0) { body = null; } + if (headers === void 0) { headers = null; } + if (options === void 0) { options = null; } + return __awaiter(this, void 0, void 0, function () { + var fetchOptions, url, response, responseText; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (headers === null) + headers = {}; + if (options === null) + options = {}; + if (!options.responseFormat) + options.responseFormat = 'json'; + if (!options.target) + options.target = 'string'; + if (this.sessionId()) + headers['X-API-AUTH'] = this.sessionId(); + fetchOptions = {}; + fetchOptions.headers = headers; + fetchOptions.method = method; + if (options.path) + fetchOptions.path = options.path; + if (body) + fetchOptions.body = body; + url = this.baseUrl() + "/" + path; + response = null; + console.info('Joplin API Call', method + ' ' + url, headers, options); + console.info(this.requestToCurl_(url, fetchOptions)); + if (!(options.source == 'file' && (method == 'POST' || method == 'PUT'))) return [3 /*break*/, 1]; + return [3 /*break*/, 5]; + case 1: + if (!(options.target == 'string')) return [3 /*break*/, 3]; + if (typeof body === 'string') + fetchOptions.headers['Content-Length'] = "" + shim.stringByteLength(body); + return [4 /*yield*/, shim.fetch(url, fetchOptions)]; + case 2: + response = _a.sent(); + return [3 /*break*/, 5]; + case 3: return [4 /*yield*/, shim.fetchBlob(url, fetchOptions)]; + case 4: + // file + response = _a.sent(); + _a.label = 5; + case 5: return [4 /*yield*/, response.text()]; + case 6: + responseText = _a.sent(); + console.info('Joplin API Response', responseText); + // // Creates an error object with as much data as possible as it will appear in the log, which will make debugging easier + // const newError = (message, code = 0) => { + // // Gives a shorter response for error messages. Useful for cases where a full HTML page is accidentally loaded instead of + // // JSON. That way the error message will still show there's a problem but without filling up the log or screen. + // const shortResponseText = (`${responseText}`).substr(0, 1024); + // return new JoplinError(`${method} ${path}: ${message} (${code}): ${shortResponseText}`, code); + // }; + // let responseJson_ = null; + // const loadResponseJson = async () => { + // if (!responseText) return null; + // if (responseJson_) return responseJson_; + // // eslint-disable-next-line require-atomic-updates + // responseJson_ = await this.xmlToJson(responseText); + // if (!responseJson_) throw newError('Cannot parse XML response', response.status); + // return responseJson_; + // }; + // if (!response.ok) { + // // When using fetchBlob we only get a string (not xml or json) back + // if (options.target === 'file') throw newError('fetchBlob error', response.status); + // let json = null; + // try { + // json = await loadResponseJson(); + // } catch (error) { + // // Just send back the plain text in newErro() + // } + // if (json && json['d:error']) { + // const code = json['d:error']['s:exception'] ? json['d:error']['s:exception'].join(' ') : response.status; + // const message = json['d:error']['s:message'] ? json['d:error']['s:message'].join('\n') : 'Unknown error 1'; + // throw newError(`${message} (Exception ${code})`, response.status); + // } + // throw newError('Unknown error 2', response.status); + // } + // if (options.responseFormat === 'text') return responseText; + // // The following methods may have a response depending on the server but it's not + // // standard (some return a plain string, other XML, etc.) and we don't check the + // // response anyway since we rely on the HTTP status code so return null. + // if (['MKCOL', 'DELETE', 'PUT', 'MOVE'].indexOf(method) >= 0) return null; + // const output = await loadResponseJson(); + // this.handleNginxHack_(output, newError); + // // Check that we didn't get for example an HTML page (as an error) instead of the JSON response + // // null responses are possible, for example for DELETE calls + // if (output !== null && typeof output === 'object' && !('d:multistatus' in output)) throw newError('Not a valid WebDAV response'); + return [2 /*return*/, {}]; + } + }); + }); + }; + return JoplinServerApi; +}()); +exports.default = JoplinServerApi; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,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ZSI6IkpvcGxpblNlcnZlckFwaS5qcyIsInNvdXJjZXNDb250ZW50IjpbImNvbnN0IHsgTG9nZ2VyIH0gPSByZXF1aXJlKCdsaWIvbG9nZ2VyLmpzJyk7XG5jb25zdCB7IHJ0cmltU2xhc2hlcyB9ID0gcmVxdWlyZSgnbGliL3BhdGgtdXRpbHMuanMnKTtcbmNvbnN0IHsgc2hpbSB9ID0gcmVxdWlyZSgnbGliL3NoaW0nKTtcblxuZXhwb3J0IGludGVyZmFjZSBKb3BsaW5TZXJ2ZXJBcGlPcHRpb24ge1xuXHRiYXNlVXJsOiBGdW5jdGlvblxufVxuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBKb3BsaW5TZXJ2ZXJBcGkge1xuXG5cdGxvZ2dlcl86YW55ID0gbnVsbDtcblx0b3B0aW9uc186YW55ID0gbnVsbDtcblx0c2Vzc2lvbklkXzpzdHJpbmcgPSBudWxsO1xuXG5cdGNvbnN0cnVjdG9yKG9wdGlvbnM6YW55KSB7XG5cdFx0dGhpcy5sb2dnZXJfID0gbmV3IExvZ2dlcigpO1xuXHRcdHRoaXMub3B0aW9uc18gPSBvcHRpb25zO1xuXHR9XG5cblx0c2V0TG9nZ2VyKGw6YW55KSB7XG5cdFx0dGhpcy5sb2dnZXJfID0gbDtcblx0fVxuXG5cdGxvZ2dlcigpIHtcblx0XHRyZXR1cm4gdGhpcy5sb2dnZXJfO1xuXHR9XG5cblx0YmFzZVVybCgpIHtcblx0XHRyZXR1cm4gcnRyaW1TbGFzaGVzKHRoaXMub3B0aW9uc18uYmFzZVVybCgpKTtcblx0fVxuXG5cdHNldFNlc3Npb25JZCh2OnN0cmluZykge1xuXHRcdHRoaXMuc2Vzc2lvbklkXyA9IHY7XG5cdH1cblxuXHRzZXNzaW9uSWQoKSB7XG5cdFx0cmV0dXJuIHRoaXMuc2Vzc2lvbklkXztcblx0fVxuXG5cdHJlcXVlc3RUb0N1cmxfKHVybDpzdHJpbmcsIG9wdGlvbnM6YW55KSB7XG5cdFx0bGV0IG91dHB1dCA9IFtdO1xuXHRcdG91dHB1dC5wdXNoKCdjdXJsJyk7XG5cdFx0b3V0cHV0LnB1c2goJy12Jyk7XG5cdFx0aWYgKG9wdGlvbnMubWV0aG9kKSBvdXRwdXQucHVzaChgLVggJHtvcHRpb25zLm1ldGhvZH1gKTtcblx0XHRpZiAob3B0aW9ucy5oZWFkZXJzKSB7XG5cdFx0XHRmb3IgKGxldCBuIGluIG9wdGlvbnMuaGVhZGVycykge1xuXHRcdFx0XHRpZiAoIW9wdGlvbnMuaGVhZGVycy5oYXNPd25Qcm9wZXJ0eShuKSkgY29udGludWU7XG5cdFx0XHRcdG91dHB1dC5wdXNoKGAkeyctSCAnICsgJ1wiJ30ke259OiAke29wdGlvbnMuaGVhZGVyc1tuXX1cImApO1xuXHRcdFx0fVxuXHRcdH1cblx0XHRpZiAob3B0aW9ucy5ib2R5KSBvdXRwdXQucHVzaChgJHsnLS1kYXRhICcgKyAnXFwnJ30ke29wdGlvbnMuYm9keX0nYCk7XG5cdFx0b3V0cHV0LnB1c2godXJsKTtcblxuXHRcdHJldHVybiBvdXRwdXQuam9pbignICcpO1xuXHR9XG5cblx0YXN5bmMgZXhlYyhtZXRob2Q6c3RyaW5nLCBwYXRoOnN0cmluZyA9ICcnLCBib2R5OmFueSA9IG51bGwsIGhlYWRlcnM6YW55ID0gbnVsbCwgb3B0aW9uczphbnkgPSBudWxsKTpQcm9taXNlPGFueT4ge1xuXHRcdGlmIChoZWFkZXJzID09PSBudWxsKSBoZWFkZXJzID0ge307XG5cdFx0aWYgKG9wdGlvbnMgPT09IG51bGwpIG9wdGlvbnMgPSB7fTtcblx0XHRpZiAoIW9wdGlvbnMucmVzcG9uc2VGb3JtYXQpIG9wdGlvbnMucmVzcG9uc2VGb3JtYXQgPSAnanNvbic7XG5cdFx0aWYgKCFvcHRpb25zLnRhcmdldCkgb3B0aW9ucy50YXJnZXQgPSAnc3RyaW5nJztcblxuXHRcdGlmICh0aGlzLnNlc3Npb25JZCgpKSBoZWFkZXJzWydYLUFQSS1BVVRIJ10gPSB0aGlzLnNlc3Npb25JZCgpO1xuXG5cdFx0Y29uc3QgZmV0Y2hPcHRpb25zOmFueSA9IHt9O1xuXHRcdGZldGNoT3B0aW9ucy5oZWFkZXJzID0gaGVhZGVycztcblx0XHRmZXRjaE9wdGlvbnMubWV0aG9kID0gbWV0aG9kO1xuXHRcdGlmIChvcHRpb25zLnBhdGgpIGZldGNoT3B0aW9ucy5wYXRoID0gb3B0aW9ucy5wYXRoO1xuXHRcdGlmIChib2R5KSBmZXRjaE9wdGlvbnMuYm9keSA9IGJvZHk7XG5cblx0XHRjb25zdCB1cmw6c3RyaW5nID0gYCR7dGhpcy5iYXNlVXJsKCl9LyR7cGF0aH1gO1x0XHRcblxuXHRcdGxldCByZXNwb25zZTphbnkgPSBudWxsO1xuXG5cdFx0Y29uc29sZS5pbmZvKCdKb3BsaW4gQVBJIENhbGwnLCBtZXRob2QgKyAnICcgKyB1cmwsIGhlYWRlcnMsIG9wdGlvbnMpO1xuXHRcdGNvbnNvbGUuaW5mbyh0aGlzLnJlcXVlc3RUb0N1cmxfKHVybCwgZmV0Y2hPcHRpb25zKSk7XG5cblx0XHRpZiAob3B0aW9ucy5zb3VyY2UgPT0gJ2ZpbGUnICYmIChtZXRob2QgPT0gJ1BPU1QnIHx8IG1ldGhvZCA9PSAnUFVUJykpIHtcblx0XHRcdC8vIGlmIChmZXRjaE9wdGlvbnMucGF0aCkge1xuXHRcdFx0Ly8gXHRjb25zdCBmaWxlU3RhdCA9IGF3YWl0IHNoaW0uZnNEcml2ZXIoKS5zdGF0KGZldGNoT3B0aW9ucy5wYXRoKTtcblx0XHRcdC8vIFx0aWYgKGZpbGVTdGF0KSBmZXRjaE9wdGlvbnMuaGVhZGVyc1snQ29udGVudC1MZW5ndGgnXSA9IGAke2ZpbGVTdGF0LnNpemV9YDtcblx0XHRcdC8vIH1cblx0XHRcdC8vIHJlc3BvbnNlID0gYXdhaXQgc2hpbS51cGxvYWRCbG9iKHVybCwgZmV0Y2hPcHRpb25zKTtcblx0XHR9IGVsc2UgaWYgKG9wdGlvbnMudGFyZ2V0ID09ICdzdHJpbmcnKSB7XG5cdFx0XHRpZiAodHlwZW9mIGJvZHkgPT09ICdzdHJpbmcnKSBmZXRjaE9wdGlvbnMuaGVhZGVyc1snQ29udGVudC1MZW5ndGgnXSA9IGAke3NoaW0uc3RyaW5nQnl0ZUxlbmd0aChib2R5KX1gO1xuXHRcdFx0cmVzcG9uc2UgPSBhd2FpdCBzaGltLmZldGNoKHVybCwgZmV0Y2hPcHRpb25zKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0Ly8gZmlsZVxuXHRcdFx0cmVzcG9uc2UgPSBhd2FpdCBzaGltLmZldGNoQmxvYih1cmwsIGZldGNoT3B0aW9ucyk7XG5cdFx0fVxuXG5cdFx0Y29uc3QgcmVzcG9uc2VUZXh0ID0gYXdhaXQgcmVzcG9uc2UudGV4dCgpO1xuXG5cdFx0Y29uc29sZS5pbmZvKCdKb3BsaW4gQVBJIFJlc3BvbnNlJywgcmVzcG9uc2VUZXh0KTtcblxuXHRcdC8vIC8vIENyZWF0ZXMgYW4gZXJyb3Igb2JqZWN0IHdpdGggYXMgbXVjaCBkYXRhIGFzIHBvc3NpYmxlIGFzIGl0IHdpbGwgYXBwZWFyIGluIHRoZSBsb2csIHdoaWNoIHdpbGwgbWFrZSBkZWJ1Z2dpbmcgZWFzaWVyXG5cdFx0Ly8gY29uc3QgbmV3RXJyb3IgPSAobWVzc2FnZSwgY29kZSA9IDApID0+IHtcblx0XHQvLyBcdC8vIEdpdmVzIGEgc2hvcnRlciByZXNwb25zZSBmb3IgZXJyb3IgbWVzc2FnZXMuIFVzZWZ1bCBmb3IgY2FzZXMgd2hlcmUgYSBmdWxsIEhUTUwgcGFnZSBpcyBhY2NpZGVudGFsbHkgbG9hZGVkIGluc3RlYWQgb2Zcblx0XHQvLyBcdC8vIEpTT04uIFRoYXQgd2F5IHRoZSBlcnJvciBtZXNzYWdlIHdpbGwgc3RpbGwgc2hvdyB0aGVyZSdzIGEgcHJvYmxlbSBidXQgd2l0aG91dCBmaWxsaW5nIHVwIHRoZSBsb2cgb3Igc2NyZWVuLlxuXHRcdC8vIFx0Y29uc3Qgc2hvcnRSZXNwb25zZVRleHQgPSAoYCR7cmVzcG9uc2VUZXh0fWApLnN1YnN0cigwLCAxMDI0KTtcblx0XHQvLyBcdHJldHVybiBuZXcgSm9wbGluRXJyb3IoYCR7bWV0aG9kfSAke3BhdGh9OiAke21lc3NhZ2V9ICgke2NvZGV9KTogJHtzaG9ydFJlc3BvbnNlVGV4dH1gLCBjb2RlKTtcblx0XHQvLyB9O1xuXG5cdFx0Ly8gbGV0IHJlc3BvbnNlSnNvbl8gPSBudWxsO1xuXHRcdC8vIGNvbnN0IGxvYWRSZXNwb25zZUpzb24gPSBhc3luYyAoKSA9PiB7XG5cdFx0Ly8gXHRpZiAoIXJlc3BvbnNlVGV4dCkgcmV0dXJuIG51bGw7XG5cdFx0Ly8gXHRpZiAocmVzcG9uc2VKc29uXykgcmV0dXJuIHJlc3BvbnNlSnNvbl87XG5cdFx0Ly8gXHQvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgcmVxdWlyZS1hdG9taWMtdXBkYXRlc1xuXHRcdC8vIFx0cmVzcG9uc2VKc29uXyA9IGF3YWl0IHRoaXMueG1sVG9Kc29uKHJlc3BvbnNlVGV4dCk7XG5cdFx0Ly8gXHRpZiAoIXJlc3BvbnNlSnNvbl8pIHRocm93IG5ld0Vycm9yKCdDYW5ub3QgcGFyc2UgWE1MIHJlc3BvbnNlJywgcmVzcG9uc2Uuc3RhdHVzKTtcblx0XHQvLyBcdHJldHVybiByZXNwb25zZUpzb25fO1xuXHRcdC8vIH07XG5cblx0XHQvLyBpZiAoIXJlc3BvbnNlLm9rKSB7XG5cdFx0Ly8gXHQvLyBXaGVuIHVzaW5nIGZldGNoQmxvYiB3ZSBvbmx5IGdldCBhIHN0cmluZyAobm90IHhtbCBvciBqc29uKSBiYWNrXG5cdFx0Ly8gXHRpZiAob3B0aW9ucy50YXJnZXQgPT09ICdmaWxlJykgdGhyb3cgbmV3RXJyb3IoJ2ZldGNoQmxvYiBlcnJvcicsIHJlc3BvbnNlLnN0YXR1cyk7XG5cblx0XHQvLyBcdGxldCBqc29uID0gbnVsbDtcblx0XHQvLyBcdHRyeSB7XG5cdFx0Ly8gXHRcdGpzb24gPSBhd2FpdCBsb2FkUmVzcG9uc2VKc29uKCk7XG5cdFx0Ly8gXHR9IGNhdGNoIChlcnJvcikge1xuXHRcdC8vIFx0XHQvLyBKdXN0IHNlbmQgYmFjayB0aGUgcGxhaW4gdGV4dCBpbiBuZXdFcnJvKClcblx0XHQvLyBcdH1cblxuXHRcdC8vIFx0aWYgKGpzb24gJiYganNvblsnZDplcnJvciddKSB7XG5cdFx0Ly8gXHRcdGNvbnN0IGNvZGUgPSBqc29uWydkOmVycm9yJ11bJ3M6ZXhjZXB0aW9uJ10gPyBqc29uWydkOmVycm9yJ11bJ3M6ZXhjZXB0aW9uJ10uam9pbignICcpIDogcmVzcG9uc2Uuc3RhdHVzO1xuXHRcdC8vIFx0XHRjb25zdCBtZXNzYWdlID0ganNvblsnZDplcnJvciddWydzOm1lc3NhZ2UnXSA/IGpzb25bJ2Q6ZXJyb3InXVsnczptZXNzYWdlJ10uam9pbignXFxuJykgOiAnVW5rbm93biBlcnJvciAxJztcblx0XHQvLyBcdFx0dGhyb3cgbmV3RXJyb3IoYCR7bWVzc2FnZX0gKEV4Y2VwdGlvbiAke2NvZGV9KWAsIHJlc3BvbnNlLnN0YXR1cyk7XG5cdFx0Ly8gXHR9XG5cblx0XHQvLyBcdHRocm93IG5ld0Vycm9yKCdVbmtub3duIGVycm9yIDInLCByZXNwb25zZS5zdGF0dXMpO1xuXHRcdC8vIH1cblxuXHRcdC8vIGlmIChvcHRpb25zLnJlc3BvbnNlRm9ybWF0ID09PSAndGV4dCcpIHJldHVybiByZXNwb25zZVRleHQ7XG5cblx0XHQvLyAvLyBUaGUgZm9sbG93aW5nIG1ldGhvZHMgbWF5IGhhdmUgYSByZXNwb25zZSBkZXBlbmRpbmcgb24gdGhlIHNlcnZlciBidXQgaXQncyBub3Rcblx0XHQvLyAvLyBzdGFuZGFyZCAoc29tZSByZXR1cm4gYSBwbGFpbiBzdHJpbmcsIG90aGVyIFhNTCwgZXRjLikgYW5kIHdlIGRvbid0IGNoZWNrIHRoZVxuXHRcdC8vIC8vIHJlc3BvbnNlIGFueXdheSBzaW5jZSB3ZSByZWx5IG9uIHRoZSBIVFRQIHN0YXR1cyBjb2RlIHNvIHJldHVybiBudWxsLlxuXHRcdC8vIGlmIChbJ01LQ09MJywgJ0RFTEVURScsICdQVVQnLCAnTU9WRSddLmluZGV4T2YobWV0aG9kKSA+PSAwKSByZXR1cm4gbnVsbDtcblxuXHRcdC8vIGNvbnN0IG91dHB1dCA9IGF3YWl0IGxvYWRSZXNwb25zZUpzb24oKTtcblx0XHQvLyB0aGlzLmhhbmRsZU5naW54SGFja18ob3V0cHV0LCBuZXdFcnJvcik7XG5cblx0XHQvLyAvLyBDaGVjayB0aGF0IHdlIGRpZG4ndCBnZXQgZm9yIGV4YW1wbGUgYW4gSFRNTCBwYWdlIChhcyBhbiBlcnJvcikgaW5zdGVhZCBvZiB0aGUgSlNPTiByZXNwb25zZVxuXHRcdC8vIC8vIG51bGwgcmVzcG9uc2VzIGFyZSBwb3NzaWJsZSwgZm9yIGV4YW1wbGUgZm9yIERFTEVURSBjYWxsc1xuXHRcdC8vIGlmIChvdXRwdXQgIT09IG51bGwgJiYgdHlwZW9mIG91dHB1dCA9PT0gJ29iamVjdCcgJiYgISgnZDptdWx0aXN0YXR1cycgaW4gb3V0cHV0KSkgdGhyb3cgbmV3RXJyb3IoJ05vdCBhIHZhbGlkIFdlYkRBViByZXNwb25zZScpO1xuXG5cdFx0cmV0dXJuIHt9O1xuXHR9XG59XG4iXX0= diff --git a/Server/dist/lib/JoplinServerApi.ts b/Server/dist/lib/JoplinServerApi.ts new file mode 100644 index 0000000000..d5a8487626 --- /dev/null +++ b/Server/dist/lib/JoplinServerApi.ts @@ -0,0 +1,150 @@ +const { Logger } = require('lib/logger.js'); +const { rtrimSlashes } = require('lib/path-utils.js'); +const { shim } = require('lib/shim'); + +export interface JoplinServerApiOption { + baseUrl: Function +} + +export default class JoplinServerApi { + + logger_:any = null; + options_:any = null; + sessionId_:string = null; + + constructor(options:any) { + this.logger_ = new Logger(); + this.options_ = options; + } + + setLogger(l:any) { + this.logger_ = l; + } + + logger() { + return this.logger_; + } + + baseUrl() { + return rtrimSlashes(this.options_.baseUrl()); + } + + setSessionId(v:string) { + this.sessionId_ = v; + } + + sessionId() { + return this.sessionId_; + } + + requestToCurl_(url:string, options:any) { + let output = []; + output.push('curl'); + output.push('-v'); + if (options.method) output.push(`-X ${options.method}`); + if (options.headers) { + for (let n in options.headers) { + if (!options.headers.hasOwnProperty(n)) continue; + output.push(`${'-H ' + '"'}${n}: ${options.headers[n]}"`); + } + } + if (options.body) output.push(`${'--data ' + '\''}${options.body}'`); + output.push(url); + + return output.join(' '); + } + + async exec(method:string, path:string = '', body:any = null, headers:any = null, options:any = null):Promise { + if (headers === null) headers = {}; + if (options === null) options = {}; + if (!options.responseFormat) options.responseFormat = 'json'; + if (!options.target) options.target = 'string'; + + if (this.sessionId()) headers['X-API-AUTH'] = this.sessionId(); + + const fetchOptions:any = {}; + fetchOptions.headers = headers; + fetchOptions.method = method; + if (options.path) fetchOptions.path = options.path; + if (body) fetchOptions.body = body; + + const url:string = `${this.baseUrl()}/${path}`; + + let response:any = null; + + console.info('Joplin API Call', method + ' ' + url, headers, options); + console.info(this.requestToCurl_(url, fetchOptions)); + + if (options.source == 'file' && (method == 'POST' || method == 'PUT')) { + // if (fetchOptions.path) { + // const fileStat = await shim.fsDriver().stat(fetchOptions.path); + // if (fileStat) fetchOptions.headers['Content-Length'] = `${fileStat.size}`; + // } + // response = await shim.uploadBlob(url, fetchOptions); + } else if (options.target == 'string') { + if (typeof body === 'string') fetchOptions.headers['Content-Length'] = `${shim.stringByteLength(body)}`; + response = await shim.fetch(url, fetchOptions); + } else { + // file + response = await shim.fetchBlob(url, fetchOptions); + } + + const responseText = await response.text(); + + console.info('Joplin API Response', responseText); + + // // Creates an error object with as much data as possible as it will appear in the log, which will make debugging easier + // const newError = (message, code = 0) => { + // // Gives a shorter response for error messages. Useful for cases where a full HTML page is accidentally loaded instead of + // // JSON. That way the error message will still show there's a problem but without filling up the log or screen. + // const shortResponseText = (`${responseText}`).substr(0, 1024); + // return new JoplinError(`${method} ${path}: ${message} (${code}): ${shortResponseText}`, code); + // }; + + // let responseJson_ = null; + // const loadResponseJson = async () => { + // if (!responseText) return null; + // if (responseJson_) return responseJson_; + // // eslint-disable-next-line require-atomic-updates + // responseJson_ = await this.xmlToJson(responseText); + // if (!responseJson_) throw newError('Cannot parse XML response', response.status); + // return responseJson_; + // }; + + // if (!response.ok) { + // // When using fetchBlob we only get a string (not xml or json) back + // if (options.target === 'file') throw newError('fetchBlob error', response.status); + + // let json = null; + // try { + // json = await loadResponseJson(); + // } catch (error) { + // // Just send back the plain text in newErro() + // } + + // if (json && json['d:error']) { + // const code = json['d:error']['s:exception'] ? json['d:error']['s:exception'].join(' ') : response.status; + // const message = json['d:error']['s:message'] ? json['d:error']['s:message'].join('\n') : 'Unknown error 1'; + // throw newError(`${message} (Exception ${code})`, response.status); + // } + + // throw newError('Unknown error 2', response.status); + // } + + // if (options.responseFormat === 'text') return responseText; + + // // The following methods may have a response depending on the server but it's not + // // standard (some return a plain string, other XML, etc.) and we don't check the + // // response anyway since we rely on the HTTP status code so return null. + // if (['MKCOL', 'DELETE', 'PUT', 'MOVE'].indexOf(method) >= 0) return null; + + // const output = await loadResponseJson(); + // this.handleNginxHack_(output, newError); + + // // Check that we didn't get for example an HTML page (as an error) instead of the JSON response + // // null responses are possible, for example for DELETE calls + // if (output !== null && typeof output === 'object' && !('d:multistatus' in output)) throw newError('Not a valid WebDAV response'); + + return {}; + } +} diff --git a/Server/dist/lib/ModelCache.js b/Server/dist/lib/ModelCache.js new file mode 100644 index 0000000000..32fb765c8d --- /dev/null +++ b/Server/dist/lib/ModelCache.js @@ -0,0 +1,37 @@ +class ModelCache { + constructor(maxSize) { + this.cache_ = []; + this.maxSize_ = maxSize; + } + + fromCache(ModelClass, id) { + for (let i = 0; i < this.cache_.length; i++) { + const c = this.cache_[i]; + if (c.id === id && c.modelType === ModelClass.modelType()) return c; + } + return null; + } + + cache(ModelClass, id, model) { + if (this.fromCache(ModelClass, model.id)) return; + this.cache_.push({ + id: id, + model: model, + modelType: ModelClass.modelType(), + }); + + if (this.cache_.length > this.maxSize_) { + this.cache_.splice(0, 1); + } + } + + async load(ModelClass, id) { + const cached = this.fromCache(ModelClass, id); + if (cached) return cached.model; + const output = await ModelClass.load(id); + this.cache(ModelClass, id, output); + return output; + } +} + +module.exports = ModelCache; diff --git a/Server/dist/lib/ObjectUtils.js b/Server/dist/lib/ObjectUtils.js new file mode 100644 index 0000000000..3e28e705b4 --- /dev/null +++ b/Server/dist/lib/ObjectUtils.js @@ -0,0 +1,63 @@ +const ObjectUtils = {}; + +ObjectUtils.sortByValue = function(object) { + const temp = []; + for (let k in object) { + if (!object.hasOwnProperty(k)) continue; + temp.push({ + key: k, + value: object[k], + }); + } + + temp.sort(function(a, b) { + let v1 = a.value; + let v2 = b.value; + if (typeof v1 === 'string') v1 = v1.toLowerCase(); + if (typeof v2 === 'string') v2 = v2.toLowerCase(); + if (v1 === v2) return 0; + return v1 < v2 ? -1 : +1; + }); + + const output = {}; + for (let i = 0; i < temp.length; i++) { + const item = temp[i]; + output[item.key] = item.value; + } + + return output; +}; + +ObjectUtils.fieldsEqual = function(o1, o2) { + if ((!o1 || !o2) && o1 !== o2) return false; + + for (let k in o1) { + if (!o1.hasOwnProperty(k)) continue; + if (o1[k] !== o2[k]) return false; + } + + const c1 = Object.getOwnPropertyNames(o1); + const c2 = Object.getOwnPropertyNames(o2); + + if (c1.length !== c2.length) return false; + + return true; +}; + +ObjectUtils.convertValuesToFunctions = function(o) { + const output = {}; + for (let n in o) { + if (!o.hasOwnProperty(n)) continue; + output[n] = () => { + return typeof o[n] === 'function' ? o[n]() : o[n]; + }; + } + return output; +}; + +ObjectUtils.isEmpty = function(o) { + if (!o) return true; + return Object.keys(o).length === 0 && o.constructor === Object; +}; + +module.exports = ObjectUtils; diff --git a/Server/dist/lib/SyncTargetDropbox.js b/Server/dist/lib/SyncTargetDropbox.js new file mode 100644 index 0000000000..2b2f325dbb --- /dev/null +++ b/Server/dist/lib/SyncTargetDropbox.js @@ -0,0 +1,74 @@ +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const { _ } = require('lib/locale.js'); +const DropboxApi = require('lib/DropboxApi'); +const Setting = require('lib/models/Setting.js'); +const { parameters } = require('lib/parameters.js'); +const { FileApi } = require('lib/file-api.js'); +const { Synchronizer } = require('lib/synchronizer.js'); +const { FileApiDriverDropbox } = require('lib/file-api-driver-dropbox.js'); + +class SyncTargetDropbox extends BaseSyncTarget { + static id() { + return 7; + } + + constructor(db, options = null) { + super(db, options); + this.api_ = null; + } + + static targetName() { + return 'dropbox'; + } + + static label() { + return _('Dropbox'); + } + + authRouteName() { + return 'DropboxLogin'; + } + + async isAuthenticated() { + const f = await this.fileApi(); + return !!f + .driver() + .api() + .authToken(); + } + + async api() { + const fileApi = await this.fileApi(); + return fileApi.driver().api(); + } + + async initFileApi() { + const params = parameters().dropbox; + + const api = new DropboxApi({ + id: params.id, + secret: params.secret, + }); + + api.on('authRefreshed', auth => { + this.logger().info('Saving updated Dropbox auth.'); + Setting.setValue(`sync.${SyncTargetDropbox.id()}.auth`, auth ? auth : null); + }); + + const authToken = Setting.value(`sync.${SyncTargetDropbox.id()}.auth`); + api.setAuthToken(authToken); + + const appDir = ''; + const fileApi = new FileApi(appDir, new FileApiDriverDropbox(api)); + fileApi.setSyncTargetId(SyncTargetDropbox.id()); + fileApi.setLogger(this.logger()); + return fileApi; + } + + async initSynchronizer() { + if (!(await this.isAuthenticated())) throw new Error('User is not authentified'); + return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType')); + } +} + +module.exports = SyncTargetDropbox; diff --git a/Server/dist/lib/SyncTargetFilesystem.js b/Server/dist/lib/SyncTargetFilesystem.js new file mode 100644 index 0000000000..7d14391aab --- /dev/null +++ b/Server/dist/lib/SyncTargetFilesystem.js @@ -0,0 +1,40 @@ +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const { _ } = require('lib/locale.js'); +const Setting = require('lib/models/Setting.js'); +const { FileApi } = require('lib/file-api.js'); +const { FileApiDriverLocal } = require('lib/file-api-driver-local.js'); +const { Synchronizer } = require('lib/synchronizer.js'); + +class SyncTargetFilesystem extends BaseSyncTarget { + static id() { + return 2; + } + + static targetName() { + return 'filesystem'; + } + + static label() { + return _('File system'); + } + + async isAuthenticated() { + return true; + } + + async initFileApi() { + const syncPath = Setting.value('sync.2.path'); + const driver = new FileApiDriverLocal(); + const fileApi = new FileApi(syncPath, driver); + fileApi.setLogger(this.logger()); + fileApi.setSyncTargetId(SyncTargetFilesystem.id()); + await driver.mkdir(syncPath); + return fileApi; + } + + async initSynchronizer() { + return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType')); + } +} + +module.exports = SyncTargetFilesystem; diff --git a/Server/dist/lib/SyncTargetJoplinServer.js b/Server/dist/lib/SyncTargetJoplinServer.js new file mode 100644 index 0000000000..007a0e2a7e --- /dev/null +++ b/Server/dist/lib/SyncTargetJoplinServer.js @@ -0,0 +1,165 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var BaseSyncTarget = require('lib/BaseSyncTarget.js'); +var _ = require('lib/locale.js')._; +var Setting = require('lib/models/Setting.js'); +// const { FileApi } = require('lib/file-api.js'); +var Synchronizer = require('lib/synchronizer.js').Synchronizer; +// const WebDavApi = require('lib/WebDavApi'); +// const { FileApiDriverWebDav } = require('lib/file-api-driver-webdav'); +var SyncTargetJoplinServer = /** @class */ (function (_super) { + __extends(SyncTargetJoplinServer, _super); + function SyncTargetJoplinServer() { + return _super !== null && _super.apply(this, arguments) || this; + } + SyncTargetJoplinServer.id = function () { + return 8; + }; + SyncTargetJoplinServer.supportsConfigCheck = function () { + return true; + }; + SyncTargetJoplinServer.targetName = function () { + return 'joplinServer'; + }; + SyncTargetJoplinServer.label = function () { + return _('Joplin Server'); + }; + SyncTargetJoplinServer.prototype.isAuthenticated = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, true]; + }); + }); + }; + // static async newFileApi_(syncTargetId, options) { + // const apiOptions = { + // baseUrl: () => options.path(), + // username: () => options.username(), + // password: () => options.password(), + // }; + // return null; + // // const api = new WebDavApi(apiOptions); + // // const driver = new FileApiDriverWebDav(api); + // // const fileApi = new FileApi('', driver); + // // fileApi.setSyncTargetId(syncTargetId); + // // return fileApi; + // } + SyncTargetJoplinServer.checkConfig = function (options) { + return __awaiter(this, void 0, void 0, function () { + var fileApi, output, result, error_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, SyncTargetJoplinServer.newFileApi_(SyncTargetJoplinServer.id(), options)]; + case 1: + fileApi = _a.sent(); + fileApi.requestRepeatCount_ = 0; + output = { + ok: false, + errorMessage: '', + }; + _a.label = 2; + case 2: + _a.trys.push([2, 4, , 5]); + return [4 /*yield*/, fileApi.stat('')]; + case 3: + result = _a.sent(); + if (!result) + throw new Error("Sync directory not found: " + options.path()); + output.ok = true; + return [3 /*break*/, 5]; + case 4: + error_1 = _a.sent(); + output.errorMessage = error_1.message; + if (error_1.code) + output.errorMessage += " (Code " + error_1.code + ")"; + return [3 /*break*/, 5]; + case 5: return [2 /*return*/, output]; + } + }); + }); + }; + SyncTargetJoplinServer.prototype.initFileApi = function () { + return __awaiter(this, void 0, void 0, function () { + var fileApi; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, SyncTargetJoplinServer.newFileApi_(SyncTargetJoplinServer.id(), { + path: function () { return Setting.value('sync.8.path'); }, + username: function () { return Setting.value('sync.8.username'); }, + password: function () { return Setting.value('sync.8.password'); }, + })]; + case 1: + fileApi = _a.sent(); + fileApi.setLogger(this.logger()); + return [2 /*return*/, fileApi]; + } + }); + }); + }; + SyncTargetJoplinServer.prototype.initSynchronizer = function () { + return __awaiter(this, void 0, void 0, function () { + var _a, _b; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + _a = Synchronizer.bind; + _b = [void 0, this.db()]; + return [4 /*yield*/, this.fileApi()]; + case 1: return [2 /*return*/, new (_a.apply(Synchronizer, _b.concat([_c.sent(), Setting.value('appType')])))()]; + } + }); + }); + }; + return SyncTargetJoplinServer; +}(BaseSyncTarget)); +module.exports = SyncTargetJoplinServer; + +//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIlN5bmNUYXJnZXRKb3BsaW5TZXJ2ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxJQUFNLGNBQWMsR0FBRyxPQUFPLENBQUMsdUJBQXVCLENBQUMsQ0FBQztBQUNoRCxJQUFBLDhCQUFDLENBQThCO0FBQ3ZDLElBQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQyx1QkFBdUIsQ0FBQyxDQUFDO0FBQ2pELGtEQUFrRDtBQUMxQyxJQUFBLDBEQUFZLENBQW9DO0FBQ3hELDhDQUE4QztBQUM5Qyx5RUFBeUU7QUFFekU7SUFBcUMsMENBQWM7SUFBbkQ7O0lBMEVBLENBQUM7SUF4RU8seUJBQUUsR0FBVDtRQUNDLE9BQU8sQ0FBQyxDQUFDO0lBQ1YsQ0FBQztJQUVNLDBDQUFtQixHQUExQjtRQUNDLE9BQU8sSUFBSSxDQUFDO0lBQ2IsQ0FBQztJQUVNLGlDQUFVLEdBQWpCO1FBQ0MsT0FBTyxjQUFjLENBQUM7SUFDdkIsQ0FBQztJQUVNLDRCQUFLLEdBQVo7UUFDQyxPQUFPLENBQUMsQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUMzQixDQUFDO0lBRUssZ0RBQWUsR0FBckI7OztnQkFDQyxzQkFBTyxJQUFJLEVBQUM7OztLQUNaO0lBRUQsb0RBQW9EO0lBQ3BELHdCQUF3QjtJQUN4QixtQ0FBbUM7SUFDbkMsd0NBQXdDO0lBQ3hDLHdDQUF3QztJQUN4QyxNQUFNO0lBRU4sZ0JBQWdCO0lBRWhCLDZDQUE2QztJQUM3QyxtREFBbUQ7SUFDbkQsK0NBQStDO0lBQy9DLDZDQUE2QztJQUM3QyxzQkFBc0I7SUFDdEIsSUFBSTtJQUVTLGtDQUFXLEdBQXhCLFVBQXlCLE9BQU87Ozs7OzRCQUNmLHFCQUFNLHNCQUFzQixDQUFDLFdBQVcsQ0FBQyxzQkFBc0IsQ0FBQyxFQUFFLEVBQUUsRUFBRSxPQUFPLENBQUMsRUFBQTs7d0JBQXhGLE9BQU8sR0FBRyxTQUE4RTt3QkFDOUYsT0FBTyxDQUFDLG1CQUFtQixHQUFHLENBQUMsQ0FBQzt3QkFFMUIsTUFBTSxHQUFHOzRCQUNkLEVBQUUsRUFBRSxLQUFLOzRCQUNULFlBQVksRUFBRSxFQUFFO3lCQUNoQixDQUFDOzs7O3dCQUdjLHFCQUFNLE9BQU8sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLEVBQUE7O3dCQUEvQixNQUFNLEdBQUcsU0FBc0I7d0JBQ3JDLElBQUksQ0FBQyxNQUFNOzRCQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMsK0JBQTZCLE9BQU8sQ0FBQyxJQUFJLEVBQUksQ0FBQyxDQUFDO3dCQUM1RSxNQUFNLENBQUMsRUFBRSxHQUFHLElBQUksQ0FBQzs7Ozt3QkFFakIsTUFBTSxDQUFDLFlBQVksR0FBRyxPQUFLLENBQUMsT0FBTyxDQUFDO3dCQUNwQyxJQUFJLE9BQUssQ0FBQyxJQUFJOzRCQUFFLE1BQU0sQ0FBQyxZQUFZLElBQUksWUFBVSxPQUFLLENBQUMsSUFBSSxNQUFHLENBQUM7OzRCQUdoRSxzQkFBTyxNQUFNLEVBQUM7Ozs7S0FDZDtJQUVLLDRDQUFXLEdBQWpCOzs7Ozs0QkFDaUIscUJBQU0sc0JBQXNCLENBQUMsV0FBVyxDQUFDLHNCQUFzQixDQUFDLEVBQUUsRUFBRSxFQUFFOzRCQUNyRixJQUFJLEVBQUUsY0FBTSxPQUFBLE9BQU8sQ0FBQyxLQUFLLENBQUMsYUFBYSxDQUFDLEVBQTVCLENBQTRCOzRCQUN4QyxRQUFRLEVBQUUsY0FBTSxPQUFBLE9BQU8sQ0FBQyxLQUFLLENBQUMsaUJBQWlCLENBQUMsRUFBaEMsQ0FBZ0M7NEJBQ2hELFFBQVEsRUFBRSxjQUFNLE9BQUEsT0FBTyxDQUFDLEtBQUssQ0FBQyxpQkFBaUIsQ0FBQyxFQUFoQyxDQUFnQzt5QkFDaEQsQ0FBQyxFQUFBOzt3QkFKSSxPQUFPLEdBQUcsU0FJZDt3QkFFRixPQUFPLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO3dCQUVqQyxzQkFBTyxPQUFPLEVBQUM7Ozs7S0FDZjtJQUVLLGlEQUFnQixHQUF0Qjs7Ozs7OzZCQUNZLFlBQVk7c0NBQUMsSUFBSSxDQUFDLEVBQUUsRUFBRTt3QkFBRSxxQkFBTSxJQUFJLENBQUMsT0FBTyxFQUFFLEVBQUE7NEJBQXZELHNCQUFPLGNBQUksWUFBWSxhQUFZLFNBQW9CLEVBQUUsT0FBTyxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsTUFBQyxFQUFDOzs7O0tBQ25GO0lBQ0YsNkJBQUM7QUFBRCxDQTFFQSxBQTBFQyxDQTFFb0MsY0FBYyxHQTBFbEQ7QUFFRCxNQUFNLENBQUMsT0FBTyxHQUFHLHNCQUFzQixDQUFDIiwiZmlsZSI6IlN5bmNUYXJnZXRKb3BsaW5TZXJ2ZXIuanMiLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCBCYXNlU3luY1RhcmdldCA9IHJlcXVpcmUoJ2xpYi9CYXNlU3luY1RhcmdldC5qcycpO1xuY29uc3QgeyBfIH0gPSByZXF1aXJlKCdsaWIvbG9jYWxlLmpzJyk7XG5jb25zdCBTZXR0aW5nID0gcmVxdWlyZSgnbGliL21vZGVscy9TZXR0aW5nLmpzJyk7XG4vLyBjb25zdCB7IEZpbGVBcGkgfSA9IHJlcXVpcmUoJ2xpYi9maWxlLWFwaS5qcycpO1xuY29uc3QgeyBTeW5jaHJvbml6ZXIgfSA9IHJlcXVpcmUoJ2xpYi9zeW5jaHJvbml6ZXIuanMnKTtcbi8vIGNvbnN0IFdlYkRhdkFwaSA9IHJlcXVpcmUoJ2xpYi9XZWJEYXZBcGknKTtcbi8vIGNvbnN0IHsgRmlsZUFwaURyaXZlcldlYkRhdiB9ID0gcmVxdWlyZSgnbGliL2ZpbGUtYXBpLWRyaXZlci13ZWJkYXYnKTtcblxuY2xhc3MgU3luY1RhcmdldEpvcGxpblNlcnZlciBleHRlbmRzIEJhc2VTeW5jVGFyZ2V0IHtcblxuXHRzdGF0aWMgaWQoKSB7XG5cdFx0cmV0dXJuIDg7XG5cdH1cblxuXHRzdGF0aWMgc3VwcG9ydHNDb25maWdDaGVjaygpIHtcblx0XHRyZXR1cm4gdHJ1ZTtcblx0fVxuXG5cdHN0YXRpYyB0YXJnZXROYW1lKCkge1xuXHRcdHJldHVybiAnam9wbGluU2VydmVyJztcblx0fVxuXG5cdHN0YXRpYyBsYWJlbCgpIHtcblx0XHRyZXR1cm4gXygnSm9wbGluIFNlcnZlcicpO1xuXHR9XG5cblx0YXN5bmMgaXNBdXRoZW50aWNhdGVkKCkge1xuXHRcdHJldHVybiB0cnVlO1xuXHR9XG5cblx0Ly8gc3RhdGljIGFzeW5jIG5ld0ZpbGVBcGlfKHN5bmNUYXJnZXRJZCwgb3B0aW9ucykge1xuXHQvLyBcdGNvbnN0IGFwaU9wdGlvbnMgPSB7XG5cdC8vIFx0XHRiYXNlVXJsOiAoKSA9PiBvcHRpb25zLnBhdGgoKSxcblx0Ly8gXHRcdHVzZXJuYW1lOiAoKSA9PiBvcHRpb25zLnVzZXJuYW1lKCksXG5cdC8vIFx0XHRwYXNzd29yZDogKCkgPT4gb3B0aW9ucy5wYXNzd29yZCgpLFxuXHQvLyBcdH07XG5cblx0Ly8gXHRyZXR1cm4gbnVsbDtcblxuXHQvLyBcdC8vIGNvbnN0IGFwaSA9IG5ldyBXZWJEYXZBcGkoYXBpT3B0aW9ucyk7XG5cdC8vIFx0Ly8gY29uc3QgZHJpdmVyID0gbmV3IEZpbGVBcGlEcml2ZXJXZWJEYXYoYXBpKTtcblx0Ly8gXHQvLyBjb25zdCBmaWxlQXBpID0gbmV3IEZpbGVBcGkoJycsIGRyaXZlcik7XG5cdC8vIFx0Ly8gZmlsZUFwaS5zZXRTeW5jVGFyZ2V0SWQoc3luY1RhcmdldElkKTtcblx0Ly8gXHQvLyByZXR1cm4gZmlsZUFwaTtcblx0Ly8gfVxuXG5cdHN0YXRpYyBhc3luYyBjaGVja0NvbmZpZyhvcHRpb25zKSB7XG5cdFx0Y29uc3QgZmlsZUFwaSA9IGF3YWl0IFN5bmNUYXJnZXRKb3BsaW5TZXJ2ZXIubmV3RmlsZUFwaV8oU3luY1RhcmdldEpvcGxpblNlcnZlci5pZCgpLCBvcHRpb25zKTtcblx0XHRmaWxlQXBpLnJlcXVlc3RSZXBlYXRDb3VudF8gPSAwO1xuXG5cdFx0Y29uc3Qgb3V0cHV0ID0ge1xuXHRcdFx0b2s6IGZhbHNlLFxuXHRcdFx0ZXJyb3JNZXNzYWdlOiAnJyxcblx0XHR9O1xuXG5cdFx0dHJ5IHtcblx0XHRcdGNvbnN0IHJlc3VsdCA9IGF3YWl0IGZpbGVBcGkuc3RhdCgnJyk7XG5cdFx0XHRpZiAoIXJlc3VsdCkgdGhyb3cgbmV3IEVycm9yKGBTeW5jIGRpcmVjdG9yeSBub3QgZm91bmQ6ICR7b3B0aW9ucy5wYXRoKCl9YCk7XG5cdFx0XHRvdXRwdXQub2sgPSB0cnVlO1xuXHRcdH0gY2F0Y2ggKGVycm9yKSB7XG5cdFx0XHRvdXRwdXQuZXJyb3JNZXNzYWdlID0gZXJyb3IubWVzc2FnZTtcblx0XHRcdGlmIChlcnJvci5jb2RlKSBvdXRwdXQuZXJyb3JNZXNzYWdlICs9IGAgKENvZGUgJHtlcnJvci5jb2RlfSlgO1xuXHRcdH1cblxuXHRcdHJldHVybiBvdXRwdXQ7XG5cdH1cblxuXHRhc3luYyBpbml0RmlsZUFwaSgpIHtcblx0XHRjb25zdCBmaWxlQXBpID0gYXdhaXQgU3luY1RhcmdldEpvcGxpblNlcnZlci5uZXdGaWxlQXBpXyhTeW5jVGFyZ2V0Sm9wbGluU2VydmVyLmlkKCksIHtcblx0XHRcdHBhdGg6ICgpID0+IFNldHRpbmcudmFsdWUoJ3N5bmMuOC5wYXRoJyksXG5cdFx0XHR1c2VybmFtZTogKCkgPT4gU2V0dGluZy52YWx1ZSgnc3luYy44LnVzZXJuYW1lJyksXG5cdFx0XHRwYXNzd29yZDogKCkgPT4gU2V0dGluZy52YWx1ZSgnc3luYy44LnBhc3N3b3JkJyksXG5cdFx0fSk7XG5cblx0XHRmaWxlQXBpLnNldExvZ2dlcih0aGlzLmxvZ2dlcigpKTtcblxuXHRcdHJldHVybiBmaWxlQXBpO1xuXHR9XG5cblx0YXN5bmMgaW5pdFN5bmNocm9uaXplcigpIHtcblx0XHRyZXR1cm4gbmV3IFN5bmNocm9uaXplcih0aGlzLmRiKCksIGF3YWl0IHRoaXMuZmlsZUFwaSgpLCBTZXR0aW5nLnZhbHVlKCdhcHBUeXBlJykpO1xuXHR9XG59XG5cbm1vZHVsZS5leHBvcnRzID0gU3luY1RhcmdldEpvcGxpblNlcnZlcjtcbiJdfQ== diff --git a/Server/dist/lib/SyncTargetJoplinServer.ts b/Server/dist/lib/SyncTargetJoplinServer.ts new file mode 100644 index 0000000000..9cf802a099 --- /dev/null +++ b/Server/dist/lib/SyncTargetJoplinServer.ts @@ -0,0 +1,85 @@ +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const { _ } = require('lib/locale.js'); +const Setting = require('lib/models/Setting.js'); +// const { FileApi } = require('lib/file-api.js'); +const { Synchronizer } = require('lib/synchronizer.js'); +// const WebDavApi = require('lib/WebDavApi'); +// const { FileApiDriverWebDav } = require('lib/file-api-driver-webdav'); + +class SyncTargetJoplinServer extends BaseSyncTarget { + + static id() { + return 8; + } + + static supportsConfigCheck() { + return true; + } + + static targetName() { + return 'joplinServer'; + } + + static label() { + return _('Joplin Server'); + } + + async isAuthenticated() { + return true; + } + + // static async newFileApi_(syncTargetId, options) { + // const apiOptions = { + // baseUrl: () => options.path(), + // username: () => options.username(), + // password: () => options.password(), + // }; + + // return null; + + // // const api = new WebDavApi(apiOptions); + // // const driver = new FileApiDriverWebDav(api); + // // const fileApi = new FileApi('', driver); + // // fileApi.setSyncTargetId(syncTargetId); + // // return fileApi; + // } + + static async checkConfig(options) { + const fileApi = await SyncTargetJoplinServer.newFileApi_(SyncTargetJoplinServer.id(), options); + fileApi.requestRepeatCount_ = 0; + + const output = { + ok: false, + errorMessage: '', + }; + + try { + const result = await fileApi.stat(''); + if (!result) throw new Error(`Sync directory not found: ${options.path()}`); + output.ok = true; + } catch (error) { + output.errorMessage = error.message; + if (error.code) output.errorMessage += ` (Code ${error.code})`; + } + + return output; + } + + async initFileApi() { + const fileApi = await SyncTargetJoplinServer.newFileApi_(SyncTargetJoplinServer.id(), { + path: () => Setting.value('sync.8.path'), + username: () => Setting.value('sync.8.username'), + password: () => Setting.value('sync.8.password'), + }); + + fileApi.setLogger(this.logger()); + + return fileApi; + } + + async initSynchronizer() { + return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType')); + } +} + +module.exports = SyncTargetJoplinServer; diff --git a/Server/dist/lib/SyncTargetMemory.js b/Server/dist/lib/SyncTargetMemory.js new file mode 100644 index 0000000000..6b59a4cc39 --- /dev/null +++ b/Server/dist/lib/SyncTargetMemory.js @@ -0,0 +1,36 @@ +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const Setting = require('lib/models/Setting.js'); +const { FileApi } = require('lib/file-api.js'); +const { FileApiDriverMemory } = require('lib/file-api-driver-memory.js'); +const { Synchronizer } = require('lib/synchronizer.js'); + +class SyncTargetMemory extends BaseSyncTarget { + static id() { + return 1; + } + + static targetName() { + return 'memory'; + } + + static label() { + return 'Memory'; + } + + async isAuthenticated() { + return true; + } + + initFileApi() { + const fileApi = new FileApi('/root', new FileApiDriverMemory()); + fileApi.setLogger(this.logger()); + fileApi.setSyncTargetId(SyncTargetMemory.id()); + return fileApi; + } + + async initSynchronizer() { + return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType')); + } +} + +module.exports = SyncTargetMemory; diff --git a/Server/dist/lib/SyncTargetNextcloud.js b/Server/dist/lib/SyncTargetNextcloud.js new file mode 100644 index 0000000000..f63811593d --- /dev/null +++ b/Server/dist/lib/SyncTargetNextcloud.js @@ -0,0 +1,52 @@ +// The Nextcloud sync target is essentially a wrapper over the WebDAV sync target, +// thus all the calls to SyncTargetWebDAV to avoid duplicate code. + +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const { _ } = require('lib/locale.js'); +const Setting = require('lib/models/Setting.js'); +const { Synchronizer } = require('lib/synchronizer.js'); +const SyncTargetWebDAV = require('lib/SyncTargetWebDAV'); + +class SyncTargetNextcloud extends BaseSyncTarget { + static id() { + return 5; + } + + static supportsConfigCheck() { + return true; + } + + static targetName() { + return 'nextcloud'; + } + + static label() { + return _('Nextcloud'); + } + + async isAuthenticated() { + return true; + } + + static async checkConfig(options) { + return SyncTargetWebDAV.checkConfig(options); + } + + async initFileApi() { + const fileApi = await SyncTargetWebDAV.newFileApi_(SyncTargetNextcloud.id(), { + path: () => Setting.value('sync.5.path'), + username: () => Setting.value('sync.5.username'), + password: () => Setting.value('sync.5.password'), + }); + + fileApi.setLogger(this.logger()); + + return fileApi; + } + + async initSynchronizer() { + return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType')); + } +} + +module.exports = SyncTargetNextcloud; diff --git a/Server/dist/lib/SyncTargetOneDrive.js b/Server/dist/lib/SyncTargetOneDrive.js new file mode 100644 index 0000000000..a6fdc5b545 --- /dev/null +++ b/Server/dist/lib/SyncTargetOneDrive.js @@ -0,0 +1,87 @@ +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const { _ } = require('lib/locale.js'); +const { OneDriveApi } = require('lib/onedrive-api.js'); +const Setting = require('lib/models/Setting.js'); +const { parameters } = require('lib/parameters.js'); +const { FileApi } = require('lib/file-api.js'); +const { Synchronizer } = require('lib/synchronizer.js'); +const { FileApiDriverOneDrive } = require('lib/file-api-driver-onedrive.js'); + +class SyncTargetOneDrive extends BaseSyncTarget { + static id() { + return 3; + } + + constructor(db, options = null) { + super(db, options); + this.api_ = null; + } + + static targetName() { + return 'onedrive'; + } + + static label() { + return _('OneDrive'); + } + + async isAuthenticated() { + return this.api().auth(); + } + + syncTargetId() { + return SyncTargetOneDrive.id(); + } + + oneDriveParameters() { + return parameters().oneDrive; + } + + authRouteName() { + return 'OneDriveLogin'; + } + + api() { + if (this.api_) return this.api_; + + const isPublic = Setting.value('appType') != 'cli'; + + this.api_ = new OneDriveApi(this.oneDriveParameters().id, this.oneDriveParameters().secret, isPublic); + this.api_.setLogger(this.logger()); + + this.api_.on('authRefreshed', a => { + this.logger().info('Saving updated OneDrive auth.'); + Setting.setValue(`sync.${this.syncTargetId()}.auth`, a ? JSON.stringify(a) : null); + }); + + let auth = Setting.value(`sync.${this.syncTargetId()}.auth`); + if (auth) { + try { + auth = JSON.parse(auth); + } catch (error) { + this.logger().warn('Could not parse OneDrive auth token'); + this.logger().warn(error); + auth = null; + } + + this.api_.setAuth(auth); + } + + return this.api_; + } + + async initFileApi() { + const appDir = await this.api().appDirectory(); + const fileApi = new FileApi(appDir, new FileApiDriverOneDrive(this.api())); + fileApi.setSyncTargetId(this.syncTargetId()); + fileApi.setLogger(this.logger()); + return fileApi; + } + + async initSynchronizer() { + if (!(await this.isAuthenticated())) throw new Error('User is not authentified'); + return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType')); + } +} + +module.exports = SyncTargetOneDrive; diff --git a/Server/dist/lib/SyncTargetOneDriveDev.js b/Server/dist/lib/SyncTargetOneDriveDev.js new file mode 100644 index 0000000000..49e00e3042 --- /dev/null +++ b/Server/dist/lib/SyncTargetOneDriveDev.js @@ -0,0 +1,27 @@ +const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js'); +const { _ } = require('lib/locale.js'); +const { parameters } = require('lib/parameters.js'); + +class SyncTargetOneDriveDev extends SyncTargetOneDrive { + static id() { + return 4; + } + + static targetName() { + return 'onedrive_dev'; + } + + static label() { + return _('OneDrive Dev (For testing only)'); + } + + syncTargetId() { + return SyncTargetOneDriveDev.id(); + } + + oneDriveParameters() { + return parameters('dev').oneDrive; + } +} + +module.exports = SyncTargetOneDriveDev; diff --git a/Server/dist/lib/SyncTargetRegistry.js b/Server/dist/lib/SyncTargetRegistry.js new file mode 100644 index 0000000000..e85293d653 --- /dev/null +++ b/Server/dist/lib/SyncTargetRegistry.js @@ -0,0 +1,50 @@ +class SyncTargetRegistry { + static classById(syncTargetId) { + const info = SyncTargetRegistry.reg_[syncTargetId]; + if (!info) throw new Error(`Invalid id: ${syncTargetId}`); + return info.classRef; + } + + static addClass(SyncTargetClass) { + this.reg_[SyncTargetClass.id()] = { + id: SyncTargetClass.id(), + name: SyncTargetClass.targetName(), + label: SyncTargetClass.label(), + classRef: SyncTargetClass, + supportsConfigCheck: SyncTargetClass.supportsConfigCheck(), + }; + } + + static nameToId(name) { + for (let n in this.reg_) { + if (!this.reg_.hasOwnProperty(n)) continue; + if (this.reg_[n].name === name) return this.reg_[n].id; + } + throw new Error(`Name not found: ${name}`); + } + + static idToMetadata(id) { + for (let n in this.reg_) { + if (!this.reg_.hasOwnProperty(n)) continue; + if (this.reg_[n].id === id) return this.reg_[n]; + } + throw new Error(`ID not found: ${id}`); + } + + static idToName(id) { + return this.idToMetadata(id).name; + } + + static idAndLabelPlainObject() { + let output = {}; + for (let n in this.reg_) { + if (!this.reg_.hasOwnProperty(n)) continue; + output[n] = this.reg_[n].label; + } + return output; + } +} + +SyncTargetRegistry.reg_ = {}; + +module.exports = SyncTargetRegistry; diff --git a/Server/dist/lib/SyncTargetWebDAV.js b/Server/dist/lib/SyncTargetWebDAV.js new file mode 100644 index 0000000000..652ffffff2 --- /dev/null +++ b/Server/dist/lib/SyncTargetWebDAV.js @@ -0,0 +1,82 @@ +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const { _ } = require('lib/locale.js'); +const Setting = require('lib/models/Setting.js'); +const { FileApi } = require('lib/file-api.js'); +const { Synchronizer } = require('lib/synchronizer.js'); +const WebDavApi = require('lib/WebDavApi'); +const { FileApiDriverWebDav } = require('lib/file-api-driver-webdav'); + +class SyncTargetWebDAV extends BaseSyncTarget { + static id() { + return 6; + } + + static supportsConfigCheck() { + return true; + } + + static targetName() { + return 'webdav'; + } + + static label() { + return _('WebDAV'); + } + + async isAuthenticated() { + return true; + } + + static async newFileApi_(syncTargetId, options) { + const apiOptions = { + baseUrl: () => options.path(), + username: () => options.username(), + password: () => options.password(), + }; + + const api = new WebDavApi(apiOptions); + const driver = new FileApiDriverWebDav(api); + const fileApi = new FileApi('', driver); + fileApi.setSyncTargetId(syncTargetId); + return fileApi; + } + + static async checkConfig(options) { + const fileApi = await SyncTargetWebDAV.newFileApi_(SyncTargetWebDAV.id(), options); + fileApi.requestRepeatCount_ = 0; + + const output = { + ok: false, + errorMessage: '', + }; + + try { + const result = await fileApi.stat(''); + if (!result) throw new Error(`WebDAV directory not found: ${options.path()}`); + output.ok = true; + } catch (error) { + output.errorMessage = error.message; + if (error.code) output.errorMessage += ` (Code ${error.code})`; + } + + return output; + } + + async initFileApi() { + const fileApi = await SyncTargetWebDAV.newFileApi_(SyncTargetWebDAV.id(), { + path: () => Setting.value('sync.6.path'), + username: () => Setting.value('sync.6.username'), + password: () => Setting.value('sync.6.password'), + }); + + fileApi.setLogger(this.logger()); + + return fileApi; + } + + async initSynchronizer() { + return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType')); + } +} + +module.exports = SyncTargetWebDAV; diff --git a/Server/dist/lib/TaskQueue.js b/Server/dist/lib/TaskQueue.js new file mode 100644 index 0000000000..d1b1620429 --- /dev/null +++ b/Server/dist/lib/TaskQueue.js @@ -0,0 +1,126 @@ +const { time } = require('lib/time-utils.js'); +const Setting = require('lib/models/Setting'); +const { Logger } = require('lib/logger.js'); + +class TaskQueue { + constructor(name) { + this.waitingTasks_ = []; + this.processingTasks_ = {}; + this.processingQueue_ = false; + this.stopping_ = false; + this.results_ = {}; + this.name_ = name; + this.logger_ = new Logger(); + } + + concurrency() { + return Setting.value('sync.maxConcurrentConnections'); + } + + push(id, callback) { + if (this.stopping_) throw new Error('Cannot push task when queue is stopping'); + + this.waitingTasks_.push({ + id: id, + callback: callback, + }); + this.processQueue_(); + } + + processQueue_() { + if (this.processingQueue_ || this.stopping_) return; + + this.processingQueue_ = true; + + const completeTask = (task, result, error) => { + delete this.processingTasks_[task.id]; + + const r = { + id: task.id, + result: result, + }; + + if (error) r.error = error; + + this.results_[task.id] = r; + + this.processQueue_(); + }; + + while (this.waitingTasks_.length > 0 && Object.keys(this.processingTasks_).length < this.concurrency()) { + if (this.stopping_) break; + + const task = this.waitingTasks_.splice(0, 1)[0]; + this.processingTasks_[task.id] = task; + + task + .callback() + .then(result => { + completeTask(task, result, null); + }) + .catch(error => { + if (!error) error = new Error('Unknown error'); + completeTask(task, null, error); + }); + } + + this.processingQueue_ = false; + } + + isWaiting(taskId) { + return this.waitingTasks_.find(task => task.id === taskId); + } + + isProcessing(taskId) { + return taskId in this.processingTasks_; + } + + isDone(taskId) { + return taskId in this.results_; + } + + async waitForResult(taskId) { + if (!this.isWaiting(taskId) && !this.isProcessing(taskId) && !this.isDone(taskId)) throw new Error(`No such task: ${taskId}`); + + while (true) { + // if (this.stopping_) { + // return { + // id: taskId, + // error: new JoplinError('Queue has been destroyed', 'destroyedQueue'), + // }; + // } + + const task = this.results_[taskId]; + if (task) return task; + await time.sleep(0.1); + } + } + + async stop() { + this.stopping_ = true; + + this.logger_.info(`TaskQueue.stop: ${this.name_}: waiting for tasks to complete: ${Object.keys(this.processingTasks_).length}`); + + // In general it's not a big issue if some tasks are still running because + // it won't call anything unexpected in caller code, since the caller has + // to explicitely retrieve the results + const startTime = Date.now(); + while (Object.keys(this.processingTasks_).length) { + await time.sleep(0.1); + if (Date.now() - startTime >= 30000) { + this.logger_.warn(`TaskQueue.stop: ${this.name_}: timed out waiting for task to complete`); + break; + } + } + + this.logger_.info(`TaskQueue.stop: ${this.name_}: Done, waited for ${Date.now() - startTime}`); + } + + isStopping() { + return this.stopping_; + } +} + +TaskQueue.CONCURRENCY = 5; + +module.exports = TaskQueue; diff --git a/Server/dist/lib/TemplateUtils.js b/Server/dist/lib/TemplateUtils.js new file mode 100644 index 0000000000..2537c23069 --- /dev/null +++ b/Server/dist/lib/TemplateUtils.js @@ -0,0 +1,68 @@ +const { shim } = require('lib/shim.js'); +const { time } = require('lib/time-utils.js'); +const Mustache = require('mustache'); + +const TemplateUtils = {}; + + +// Mustache escapes strings (including /) with the html code by default +// This isn't useful for markdown so it's disabled +Mustache.escape = text => { + return text; +}; + +TemplateUtils.render = function(input) { + // new template variables can be added here + // If there are too many, this should be moved to a new file + // view needs to be set in this function so that the formats reflect settings + const view = { + date: time.formatMsToLocal(new Date().getTime(), time.dateFormat()), + time: time.formatMsToLocal(new Date().getTime(), time.timeFormat()), + datetime: time.formatMsToLocal(new Date().getTime()), + custom_datetime: () => { + return (text, render) => { + return render(time.formatMsToLocal(new Date().getTime(), text)); + }; + }, + }; + + return Mustache.render(input, view); +}; + +TemplateUtils.loadTemplates = async function(filePath) { + let templates = []; + let files = []; + + if (await shim.fsDriver().exists(filePath)) { + try { + files = await shim.fsDriver().readDirStats(filePath); + } catch (error) { + let msg = error.message ? error.message : ''; + msg = `Could not read template names from ${filePath}\n${msg}`; + error.message = msg; + throw error; + } + + // Make sure templates are always in the same order + // sensitivity ensures that the sort will ignore case + files.sort((a, b) => { return a.path.localeCompare(b.path, undefined, {sensitivity: 'accent'}); }); + + files.forEach(async file => { + if (file.path.endsWith('.md')) { + try { + let fileString = await shim.fsDriver().readFile(`${filePath}/${file.path}`, 'utf-8'); + templates.push({ label: file.path, value: fileString }); + } catch (error) { + let msg = error.message ? error.message : ''; + msg = `Could not load template ${file.path}\n${msg}`; + error.message = msg; + throw error; + } + } + }); + } + + return templates; +}; + +module.exports = TemplateUtils; diff --git a/Server/dist/lib/WebDavApi.js b/Server/dist/lib/WebDavApi.js new file mode 100644 index 0000000000..daa5a7b9a1 --- /dev/null +++ b/Server/dist/lib/WebDavApi.js @@ -0,0 +1,401 @@ +const { Logger } = require('lib/logger.js'); +const { shim } = require('lib/shim.js'); +const parseXmlString = require('xml2js').parseString; +const JoplinError = require('lib/JoplinError'); +const URL = require('url-parse'); +const { rtrimSlashes } = require('lib/path-utils.js'); +const base64 = require('base-64'); + +// Note that the d: namespace (the DAV namespace) is specific to Nextcloud. The RFC for example uses "D:" however +// we make all the tags and attributes lowercase so we handle both the Nextcloud style and RFC. Hopefully other +// implementations use the same namespaces. If not, extra processing can be done in `nameProcessor`, for +// example to convert a custom namespace to "d:" so that it can be used by the rest of the code. +// In general, we should only deal with things in "d:", which is the standard DAV namespace. + +class WebDavApi { + constructor(options) { + this.logger_ = new Logger(); + this.options_ = options; + } + + setLogger(l) { + this.logger_ = l; + } + + logger() { + return this.logger_; + } + + authToken() { + if (!this.options_.username() || !this.options_.password()) return null; + try { + // Note: Non-ASCII passwords will throw an error about Latin1 characters - https://github.com/laurent22/joplin/issues/246 + // Tried various things like the below, but it didn't work on React Native: + //return base64.encode(utf8.encode(this.options_.username() + ':' + this.options_.password())); + return base64.encode(`${this.options_.username()}:${this.options_.password()}`); + } catch (error) { + error.message = `Cannot encode username/password: ${error.message}`; + throw error; + } + } + + baseUrl() { + return rtrimSlashes(this.options_.baseUrl()); + } + + relativeBaseUrl() { + const url = new URL(this.baseUrl()); + return url.pathname + url.query; + } + + async xmlToJson(xml) { + let davNamespaces = []; // Yes, there can be more than one... xmlns:a="DAV:" xmlns:D="DAV:" + + const nameProcessor = name => { + if (name.indexOf('xmlns:') !== 0) { + // Check if the current name is within the DAV namespace. If it is, normalise it + // by moving it to the "d:" namespace, which is what all the functions are using. + const p = name.split(':'); + if (p.length == 2) { + const ns = p[0]; + if (davNamespaces.indexOf(ns) >= 0) { + name = `d:${p[1]}`; + } + } + } + return name.toLowerCase(); + }; + + const attrValueProcessor = (value, name) => { + if (value.toLowerCase() === 'dav:') { + const p = name.split(':'); + davNamespaces.push(p[p.length - 1]); + } + }; + + const options = { + tagNameProcessors: [nameProcessor], + attrNameProcessors: [nameProcessor], + attrValueProcessors: [attrValueProcessor], + }; + + return new Promise((resolve) => { + parseXmlString(xml, options, (error, result) => { + if (error) { + resolve(null); // Error handled by caller which will display the XML text (or plain text) if null is returned from this function + return; + } + resolve(result); + }); + }); + } + + valueFromJson(json, keys, type) { + let output = json; + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + + // console.info(key, typeof key, typeof output, typeof output === 'object' && (key in output), Array.isArray(output)); + + if (typeof key === 'number' && !Array.isArray(output)) return null; + if (typeof key === 'string' && (typeof output !== 'object' || !(key in output))) return null; + output = output[key]; + } + + if (type === 'string') { + // If the XML has not attribute the value is directly a string + // If the XML node has attributes, the value is under "_". + // Eg for this XML, the string will be under {"_":"Thu, 01 Feb 2018 17:24:05 GMT"}: + // Thu, 01 Feb 2018 17:24:05 GMT + // For this XML, the value will be "Thu, 01 Feb 2018 17:24:05 GMT" + // Thu, 01 Feb 2018 17:24:05 GMT + + if (typeof output === 'object' && '_' in output) output = output['_']; + if (typeof output !== 'string') return null; + return output; + } + + if (type === 'object') { + if (!Array.isArray(output) && typeof output === 'object') return output; + return null; + } + + if (type === 'array') { + return Array.isArray(output) ? output : null; + } + + return null; + } + + stringFromJson(json, keys) { + return this.valueFromJson(json, keys, 'string'); + } + + objectFromJson(json, keys) { + return this.valueFromJson(json, keys, 'object'); + } + + arrayFromJson(json, keys) { + return this.valueFromJson(json, keys, 'array'); + } + + resourcePropByName(resource, outputType, propName) { + const propStats = resource['d:propstat']; + let output = null; + if (!Array.isArray(propStats)) throw new Error('Missing d:propstat property'); + for (let i = 0; i < propStats.length; i++) { + const props = propStats[i]['d:prop']; + if (!Array.isArray(props) || !props.length) continue; + const prop = props[0]; + if (Array.isArray(prop[propName])) { + output = prop[propName]; + break; + } + } + + if (outputType === 'string') { + // If the XML has not attribute the value is directly a string + // If the XML node has attributes, the value is under "_". + // Eg for this XML, the string will be under {"_":"Thu, 01 Feb 2018 17:24:05 GMT"}: + // Thu, 01 Feb 2018 17:24:05 GMT + // For this XML, the value will be "Thu, 01 Feb 2018 17:24:05 GMT" + // Thu, 01 Feb 2018 17:24:05 GMT + + output = output[0]; + + if (typeof output === 'object' && '_' in output) output = output['_']; + if (typeof output !== 'string') return null; + return output; + } + + if (outputType === 'array') { + return output; + } + + throw new Error(`Invalid output type: ${outputType}`); + } + + async execPropFind(path, depth, fields = null, options = null) { + if (fields === null) fields = ['d:getlastmodified']; + + let fieldsXml = ''; + for (let i = 0; i < fields.length; i++) { + fieldsXml += `<${fields[i]}/>`; + } + + // To find all available properties: + // + // const body=` + // + // + // `; + + const body = + ` + + + ${fieldsXml} + + `; + + return this.exec('PROPFIND', path, body, { Depth: depth }, options); + } + + requestToCurl_(url, options) { + let output = []; + output.push('curl'); + output.push('-v'); + if (options.method) output.push(`-X ${options.method}`); + if (options.headers) { + for (let n in options.headers) { + if (!options.headers.hasOwnProperty(n)) continue; + output.push(`${'-H ' + '"'}${n}: ${options.headers[n]}"`); + } + } + if (options.body) output.push(`${'--data ' + '\''}${options.body}'`); + output.push(url); + + return output.join(' '); + } + + handleNginxHack_(jsonResponse, newErrorHandler) { + // Trying to fix 404 error issue with Nginx WebDAV server. + // https://github.com/laurent22/joplin/issues/624 + // https://github.com/laurent22/joplin/issues/808 + // Not tested but someone confirmed it worked - https://github.com/laurent22/joplin/issues/808#issuecomment-443552858 + // and fix is narrowly scoped so shouldn't affect anything outside this particular edge case. + // + // The issue is that instead of an HTTP 404 status code, Nginx returns 200 but with this response: + // + // + // + // + // /notes/ecd4027a5271483984b00317433e2c66.md + // + // + // HTTP/1.1 404 Not Found + // + // + // + // + // So we need to parse this and find that it is in fact a 404 error. + // + // HOWEVER, some implementations also return 404 for missing props, for example SeaFile: + // (indicates that the props "getlastmodified" is not present, but this call is only + // used when checking the conf, so we don't really need it) + // https://github.com/laurent22/joplin/issues/1137 + // + // + // + // + // /seafdav/joplin/ + // + // + // + // + // HTTP/1.1 404 Not Found + // + // + // + // + // + // + // + // HTTP/1.1 200 OK + // + // + // + // + // As a simple fix for now it's enough to check if ALL the statuses are 404 - in that case + // it really means that the file doesn't exist. Otherwise we can proceed as usual. + const responseArray = this.arrayFromJson(jsonResponse, ['d:multistatus', 'd:response']); + if (responseArray && responseArray.length === 1) { + const propStats = this.arrayFromJson(jsonResponse, ['d:multistatus', 'd:response', 0, 'd:propstat']); + if (!propStats.length) return; + let count404 = 0; + for (let i = 0; i < propStats.length; i++) { + const status = this.arrayFromJson(jsonResponse, ['d:multistatus', 'd:response', 0, 'd:propstat', i, 'd:status']); + if (status && status.length && status[0].indexOf('404') >= 0) count404++; + } + + if (count404 === propStats.length) throw newErrorHandler('Not found', 404); + } + } + + // curl -u admin:123456 'http://nextcloud.local/remote.php/dav/files/admin/' -X PROPFIND --data ' + // + // + // + // + // ' + + async exec(method, path = '', body = null, headers = null, options = null) { + if (headers === null) headers = {}; + if (options === null) options = {}; + if (!options.responseFormat) options.responseFormat = 'json'; + if (!options.target) options.target = 'string'; + + const authToken = this.authToken(); + + if (authToken) headers['Authorization'] = `Basic ${authToken}`; + + // On iOS, the network lib appends a If-None-Match header to PROPFIND calls, which is kind of correct because + // the call is idempotent and thus could be cached. According to RFC-7232 though only GET and HEAD should have + // this header for caching purposes. It makes no mention of PROPFIND. + // So possibly because of this, Seafile (and maybe other WebDAV implementations) responds with a "412 Precondition Failed" + // error when this header is present for PROPFIND call on existing resources. This is also kind of correct because there is a resource + // with this eTag and since this is neither a GET nor HEAD call, it is supposed to respond with 412 if the resource is present. + // The "solution", an ugly one, is to send a purposely invalid string as eTag, which will bypass the If-None-Match check - Seafile + // finds out that no resource has this ID and simply sends the requested data. + // Also add a random value to make sure the eTag is unique for each call. + if (['GET', 'HEAD'].indexOf(method) < 0) headers['If-None-Match'] = `JoplinIgnore-${Math.floor(Math.random() * 100000)}`; + + const fetchOptions = {}; + fetchOptions.headers = headers; + fetchOptions.method = method; + if (options.path) fetchOptions.path = options.path; + if (body) fetchOptions.body = body; + + const url = `${this.baseUrl()}/${path}`; + + let response = null; + + // console.info('WebDAV Call', method + ' ' + url, headers, options); + // console.info(this.requestToCurl_(url, fetchOptions)); + + if (options.source == 'file' && (method == 'POST' || method == 'PUT')) { + if (fetchOptions.path) { + const fileStat = await shim.fsDriver().stat(fetchOptions.path); + if (fileStat) fetchOptions.headers['Content-Length'] = `${fileStat.size}`; + } + response = await shim.uploadBlob(url, fetchOptions); + } else if (options.target == 'string') { + if (typeof body === 'string') fetchOptions.headers['Content-Length'] = `${shim.stringByteLength(body)}`; + response = await shim.fetch(url, fetchOptions); + } else { + // file + response = await shim.fetchBlob(url, fetchOptions); + } + + const responseText = await response.text(); + + // console.info('WebDAV Response', responseText); + + // Creates an error object with as much data as possible as it will appear in the log, which will make debugging easier + const newError = (message, code = 0) => { + // Gives a shorter response for error messages. Useful for cases where a full HTML page is accidentally loaded instead of + // JSON. That way the error message will still show there's a problem but without filling up the log or screen. + const shortResponseText = (`${responseText}`).substr(0, 1024); + return new JoplinError(`${method} ${path}: ${message} (${code}): ${shortResponseText}`, code); + }; + + let responseJson_ = null; + const loadResponseJson = async () => { + if (!responseText) return null; + if (responseJson_) return responseJson_; + // eslint-disable-next-line require-atomic-updates + responseJson_ = await this.xmlToJson(responseText); + if (!responseJson_) throw newError('Cannot parse XML response', response.status); + return responseJson_; + }; + + if (!response.ok) { + // When using fetchBlob we only get a string (not xml or json) back + if (options.target === 'file') throw newError('fetchBlob error', response.status); + + let json = null; + try { + json = await loadResponseJson(); + } catch (error) { + // Just send back the plain text in newErro() + } + + if (json && json['d:error']) { + const code = json['d:error']['s:exception'] ? json['d:error']['s:exception'].join(' ') : response.status; + const message = json['d:error']['s:message'] ? json['d:error']['s:message'].join('\n') : 'Unknown error 1'; + throw newError(`${message} (Exception ${code})`, response.status); + } + + throw newError('Unknown error 2', response.status); + } + + if (options.responseFormat === 'text') return responseText; + + // The following methods may have a response depending on the server but it's not + // standard (some return a plain string, other XML, etc.) and we don't check the + // response anyway since we rely on the HTTP status code so return null. + if (['MKCOL', 'DELETE', 'PUT', 'MOVE'].indexOf(method) >= 0) return null; + + const output = await loadResponseJson(); + this.handleNginxHack_(output, newError); + + // Check that we didn't get for example an HTML page (as an error) instead of the JSON response + // null responses are possible, for example for DELETE calls + if (output !== null && typeof output === 'object' && !('d:multistatus' in output)) throw newError('Not a valid WebDAV response'); + + return output; + } +} + +module.exports = WebDavApi; diff --git a/Server/dist/lib/WelcomeUtils.js b/Server/dist/lib/WelcomeUtils.js new file mode 100644 index 0000000000..51110b7804 --- /dev/null +++ b/Server/dist/lib/WelcomeUtils.js @@ -0,0 +1,80 @@ +const welcomeAssets = require('./welcomeAssets'); +const Note = require('lib/models/Note'); +const Setting = require('lib/models/Setting'); +const Folder = require('lib/models/Folder'); +const Tag = require('lib/models/Tag'); +const { shim } = require('lib/shim'); +const { uuid } = require('lib/uuid'); +const { fileExtension, basename } = require('lib/path-utils'); +const { pregQuote } = require('lib/string-utils'); + +class WelcomeUtils { + static async createWelcomeItems() { + const output = { + defaultFolderId: null, + }; + + const folderAssets = welcomeAssets.folders; + const tempDir = Setting.value('resourceDir'); + + for (let i = 0; i < folderAssets.length; i++) { + const folderAsset = folderAssets[i]; + const folder = await Folder.save({ title: `${folderAsset.title} (${Setting.appTypeToLabel(Setting.value('appType'))})` }); + if (!output.defaultFolderId) output.defaultFolderId = folder.id; + } + + const noteAssets = welcomeAssets.notes; + + for (let i = noteAssets.length - 1; i >= 0; i--) { + const noteAsset = noteAssets[i]; + + let noteBody = noteAsset.body; + + for (let resourceUrl in noteAsset.resources) { + if (!noteAsset.resources.hasOwnProperty(resourceUrl)) continue; + const resourceAsset = noteAsset.resources[resourceUrl]; + const ext = fileExtension(resourceUrl); + const tempFilePath = `${tempDir}/${uuid.create()}.tmp.${ext}`; + await shim.fsDriver().writeFile(tempFilePath, resourceAsset.body, 'base64'); + const resource = await shim.createResourceFromPath(tempFilePath, { + title: basename(resourceUrl), + }); + await shim.fsDriver().remove(tempFilePath); + + const regex = new RegExp(pregQuote(`(${resourceUrl})`), 'g'); + noteBody = noteBody.replace(regex, `(:/${resource.id})`); + } + + const note = await Note.save({ + parent_id: output.defaultFolderId, + title: noteAsset.title, + body: noteBody, + }); + + if (noteAsset.tags) await Tag.setNoteTagsByTitles(note.id, noteAsset.tags); + } + + return output; + } + + static async install(dispatch) { + if (!Setting.value('welcome.enabled')) { + Setting.setValue('welcome.wasBuilt', true); + return; + } + + if (!Setting.value('welcome.wasBuilt')) { + const result = await WelcomeUtils.createWelcomeItems(); + Setting.setValue('welcome.wasBuilt', true); + + dispatch({ + type: 'FOLDER_SELECT', + id: result.defaultFolderId, + }); + + Setting.setValue('activeFolderId', result.defaultFolderId); + } + } +} + +module.exports = WelcomeUtils; diff --git a/Server/dist/lib/components/CameraView.js b/Server/dist/lib/components/CameraView.js new file mode 100644 index 0000000000..cdcb2d57d5 --- /dev/null +++ b/Server/dist/lib/components/CameraView.js @@ -0,0 +1,93 @@ +const React = require('react'); +const Component = React.Component; +const { View, TouchableOpacity } = require('react-native'); +import { RNCamera } from 'react-native-camera'; +const Icon = require('react-native-vector-icons/Ionicons').default; +const { _ } = require('lib/locale.js'); + +class CameraView extends Component { + constructor() { + super(); + + this.state = { + snapping: false, + }; + + this.back_onPress = this.back_onPress.bind(this); + this.photo_onPress = this.photo_onPress.bind(this); + } + + back_onPress() { + if (this.props.onCancel) this.props.onCancel(); + } + + async photo_onPress() { + if (!this.camera || !this.props.onPhoto) return; + + this.setState({ snapping: true }); + + const result = await this.camera.takePictureAsync({ + quality: 0.8, + exif: true, + fixOrientation: true, + }); + + if (this.props.onPhoto) this.props.onPhoto(result); + + this.setState({ snapping: false }); + } + + render() { + const photoIcon = this.state.snapping ? 'md-checkmark' : 'md-camera'; + + return ( + + { + this.camera = ref; + }} + type={RNCamera.Constants.Type.back} + captureAudio={false} + androidCameraPermissionOptions={{ + title: _('Permission to use camera'), + message: _('Your permission to use your camera is required.'), + buttonPositive: _('OK'), + buttonNegative: _('Cancel'), + }} + > + + + + + + + + + + + + + + + + + + + ); + } +} + +module.exports = CameraView; diff --git a/Server/dist/lib/components/Dropdown.js b/Server/dist/lib/components/Dropdown.js new file mode 100644 index 0000000000..6fa90af612 --- /dev/null +++ b/Server/dist/lib/components/Dropdown.js @@ -0,0 +1,156 @@ +const React = require('react'); +const { TouchableOpacity, TouchableWithoutFeedback, Dimensions, Text, Modal, View } = require('react-native'); +const { ItemList } = require('lib/components/ItemList.js'); + +class Dropdown extends React.Component { + constructor() { + super(); + + this.headerRef_ = null; + } + + UNSAFE_componentWillMount() { + this.setState({ + headerSize: { x: 0, y: 0, width: 0, height: 0 }, + listVisible: false, + }); + } + + updateHeaderCoordinates() { + // https://stackoverflow.com/questions/30096038/react-native-getting-the-position-of-an-element + this.headerRef_.measure((fx, fy, width, height, px, py) => { + this.setState({ + headerSize: { x: px, y: py, width: width, height: height }, + }); + }); + } + + render() { + const items = this.props.items; + const itemHeight = 60; + const windowHeight = Dimensions.get('window').height - 50; + + // Dimensions doesn't return quite the right dimensions so leave an extra gap to make + // sure nothing is off screen. + const listMaxHeight = windowHeight; + const listHeight = Math.min(items.length * itemHeight, listMaxHeight); + const maxListTop = windowHeight - listHeight; + const listTop = Math.min(maxListTop, this.state.headerSize.y + this.state.headerSize.height); + + const wrapperStyle = { + width: this.state.headerSize.width, + height: listHeight + 2, // +2 for the border (otherwise it makes the scrollbar appear) + marginTop: listTop, + marginLeft: this.state.headerSize.x, + }; + + const itemListStyle = Object.assign({}, this.props.itemListStyle ? this.props.itemListStyle : {}, { + borderWidth: 1, + borderColor: '#ccc', + }); + + const itemWrapperStyle = Object.assign({}, this.props.itemWrapperStyle ? this.props.itemWrapperStyle : {}, { + flex: 1, + justifyContent: 'center', + height: itemHeight, + paddingLeft: 20, + paddingRight: 10, + }); + + const headerWrapperStyle = Object.assign({}, this.props.headerWrapperStyle ? this.props.headerWrapperStyle : {}, { + height: 35, + flex: 1, + flexDirection: 'row', + alignItems: 'center', + }); + + const headerStyle = Object.assign({}, this.props.headerStyle ? this.props.headerStyle : {}, { + flex: 1, + }); + + const headerArrowStyle = Object.assign({}, this.props.headerStyle ? this.props.headerStyle : {}, { + flex: 0, + marginRight: 10, + }); + + const itemStyle = Object.assign({}, this.props.itemStyle ? this.props.itemStyle : {}, {}); + + let headerLabel = '...'; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (item.value === this.props.selectedValue) { + headerLabel = item.label; + break; + } + } + + if (this.props.labelTransform && this.props.labelTransform === 'trim') headerLabel = headerLabel.trim(); + + const closeList = () => { + this.setState({ listVisible: false }); + }; + + const itemRenderer = item => { + return ( + { + closeList(); + if (this.props.onValueChange) this.props.onValueChange(item.value); + }} + > + + {item.label} + + + ); + }; + + return ( + + (this.headerRef_ = ref)} + onPress={() => { + this.updateHeaderCoordinates(); + this.setState({ listVisible: true }); + }} + > + + {headerLabel} + + {'▼'} + + { + closeList(); + }} + > + { + closeList(); + }} + > + + + { + return itemRenderer(item); + }} + /> + + + + + + ); + } +} + +module.exports = { Dropdown }; diff --git a/Server/dist/lib/components/ItemList.js b/Server/dist/lib/components/ItemList.js new file mode 100644 index 0000000000..9cb4260a69 --- /dev/null +++ b/Server/dist/lib/components/ItemList.js @@ -0,0 +1,110 @@ +const React = require('react'); +const { View, ScrollView } = require('react-native'); + +class ItemList extends React.Component { + constructor() { + super(); + + this.scrollTop_ = 0; + } + + itemCount(props = null) { + if (props === null) props = this.props; + return this.props.items ? this.props.items.length : this.props.itemComponents.length; + } + + updateStateItemIndexes(props = null, height = null) { + if (props === null) props = this.props; + + if (height === null) { + if (!this.state) return; + height = this.state.height; + } + + const topItemIndex = Math.max(0, Math.floor(this.scrollTop_ / props.itemHeight)); + const visibleItemCount = Math.ceil(height / props.itemHeight); + + let bottomItemIndex = topItemIndex + visibleItemCount - 1; + if (bottomItemIndex >= this.itemCount(props)) bottomItemIndex = this.itemCount(props) - 1; + + this.setState({ + topItemIndex: topItemIndex, + bottomItemIndex: bottomItemIndex, + }); + } + + UNSAFE_componentWillMount() { + this.setState({ + topItemIndex: 0, + bottomItemIndex: 0, + height: 0, + itemHeight: this.props.itemHeight ? this.props.itemHeight : 0, + }); + + this.updateStateItemIndexes(); + } + + UNSAFE_componentWillReceiveProps(newProps) { + if (newProps.itemHeight) { + this.setState({ + itemHeight: newProps.itemHeight, + }); + } + + this.updateStateItemIndexes(newProps); + } + + onScroll(event) { + this.scrollTop_ = Math.floor(event.nativeEvent.contentOffset.y); + this.updateStateItemIndexes(); + } + + onLayout(event) { + this.setState({ height: event.nativeEvent.layout.height }); + this.updateStateItemIndexes(null, event.nativeEvent.layout.height); + } + + render() { + const style = this.props.style ? this.props.style : {}; + + //if (!this.props.itemHeight) throw new Error('itemHeight is required'); + + let itemComps = []; + + if (this.props.items) { + const items = this.props.items; + + const blankItem = function(key, height) { + return ; + }; + + itemComps = [blankItem('top', this.state.topItemIndex * this.props.itemHeight)]; + + for (let i = this.state.topItemIndex; i <= this.state.bottomItemIndex; i++) { + const itemComp = this.props.itemRenderer(items[i]); + itemComps.push(itemComp); + } + + itemComps.push(blankItem('bottom', (items.length - this.state.bottomItemIndex - 1) * this.props.itemHeight)); + } else { + itemComps = this.props.itemComponents; + } + + return ( + { + this.onLayout(event); + }} + style={style} + onScroll={event => { + this.onScroll(event); + }} + > + {itemComps} + + ); + } +} + +module.exports = { ItemList }; diff --git a/Server/dist/lib/components/ModalDialog.js b/Server/dist/lib/components/ModalDialog.js new file mode 100644 index 0000000000..bb6a483f7d --- /dev/null +++ b/Server/dist/lib/components/ModalDialog.js @@ -0,0 +1,80 @@ +const React = require('react'); +const { Text, Modal, View, StyleSheet, Button } = require('react-native'); +const { themeStyle } = require('lib/components/global-style.js'); +const { _ } = require('lib/locale'); + +class ModalDialog extends React.Component { + constructor() { + super(); + this.styles_ = {}; + } + + styles() { + const themeId = this.props.theme; + const theme = themeStyle(themeId); + + if (this.styles_[themeId]) return this.styles_[themeId]; + this.styles_ = {}; + + let styles = { + modalWrapper: { + flex: 1, + justifyContent: 'center', + }, + modalContentWrapper: { + flex: 1, + flexDirection: 'column', + backgroundColor: theme.backgroundColor, + borderWidth: 1, + borderColor: theme.dividerColor, + margin: 20, + padding: 10, + borderRadius: 5, + }, + modalContentWrapper2: { + flex: 1, + }, + title: Object.assign({}, theme.normalText, { + borderBottomWidth: 1, + borderBottomColor: theme.dividerColor, + paddingBottom: 10, + fontWeight: 'bold', + }), + buttonRow: { + flexDirection: 'row', + borderTopWidth: 1, + borderTopColor: theme.dividerColor, + paddingTop: 10, + }, + }; + + this.styles_[themeId] = StyleSheet.create(styles); + return this.styles_[themeId]; + } + + render() { + const ContentComponent = this.props.ContentComponent; + const buttonBarEnabled = this.props.buttonBarEnabled !== false; + + return ( + + {}}> + + {this.props.title} + {ContentComponent} + + + + + + + + + + + + ); + } +} + +module.exports = ModalDialog; diff --git a/Server/dist/lib/components/SaveIcon.png b/Server/dist/lib/components/SaveIcon.png new file mode 100644 index 0000000000..f6c9ada749 Binary files /dev/null and b/Server/dist/lib/components/SaveIcon.png differ diff --git a/Server/dist/lib/components/action-button.js b/Server/dist/lib/components/action-button.js new file mode 100644 index 0000000000..dbe6984361 --- /dev/null +++ b/Server/dist/lib/components/action-button.js @@ -0,0 +1,129 @@ +const React = require('react'); + +const { StyleSheet } = require('react-native'); +const Icon = require('react-native-vector-icons/Ionicons').default; +const ReactNativeActionButton = require('react-native-action-button').default; +const { connect } = require('react-redux'); +const { _ } = require('lib/locale.js'); + +const styles = StyleSheet.create({ + actionButtonIcon: { + fontSize: 20, + height: 22, + color: 'white', + }, + itemText: { + // fontSize: 14, // Cannot currently set fontsize since the bow surrounding the label has a fixed size + }, +}); + +class ActionButtonComponent extends React.Component { + constructor() { + super(); + this.state = { + buttonIndex: 0, + }; + } + + UNSAFE_componentWillReceiveProps(newProps) { + if ('buttonIndex' in newProps) { + this.setState({ buttonIndex: newProps.buttonIndex }); + } + } + + newTodo_press() { + this.props.dispatch({ + type: 'NAV_GO', + routeName: 'Note', + noteId: null, + folderId: this.props.parentFolderId, + itemType: 'todo', + }); + } + + newNote_press() { + this.props.dispatch({ + type: 'NAV_GO', + routeName: 'Note', + noteId: null, + folderId: this.props.parentFolderId, + itemType: 'note', + }); + } + + render() { + let buttons = this.props.buttons ? this.props.buttons : []; + + if (this.props.addFolderNoteButtons) { + if (this.props.folders.length) { + buttons.push({ + title: _('New to-do'), + onPress: () => { + this.newTodo_press(); + }, + color: '#9b59b6', + icon: 'md-checkbox-outline', + }); + + buttons.push({ + title: _('New note'), + onPress: () => { + this.newNote_press(); + }, + color: '#9b59b6', + icon: 'md-document', + }); + } + } + + let buttonComps = []; + for (let i = 0; i < buttons.length; i++) { + let button = buttons[i]; + let buttonTitle = button.title ? button.title : ''; + let key = `${buttonTitle.replace(/\s/g, '_')}_${button.icon}`; + buttonComps.push( + + + + ); + } + + if (!buttonComps.length && !this.props.mainButton) { + return ; + } + + let mainButton = this.props.mainButton ? this.props.mainButton : {}; + let mainIcon = mainButton.icon ? : ; + + if (this.props.multiStates) { + if (!this.props.buttons || !this.props.buttons.length) throw new Error('Multi-state button requires at least one state'); + if (this.state.buttonIndex < 0 || this.state.buttonIndex >= this.props.buttons.length) throw new Error(`Button index out of bounds: ${this.state.buttonIndex}/${this.props.buttons.length}`); + let button = this.props.buttons[this.state.buttonIndex]; + let mainIcon = ; + return ( + { + button.onPress(); + }} + /> + ); + } else { + return ( + + {buttonComps} + + ); + } + } +} + +const ActionButton = connect(state => { + return { + folders: state.folders, + locale: state.settings.locale, + }; +})(ActionButtonComponent); + +module.exports = { ActionButton }; diff --git a/Server/dist/lib/components/app-nav.js b/Server/dist/lib/components/app-nav.js new file mode 100644 index 0000000000..8ea3836ce4 --- /dev/null +++ b/Server/dist/lib/components/app-nav.js @@ -0,0 +1,88 @@ +const React = require('react'); +const Component = React.Component; +const { connect } = require('react-redux'); +const { NotesScreen } = require('lib/components/screens/notes.js'); +const { SearchScreen } = require('lib/components/screens/search.js'); +const { KeyboardAvoidingView, Keyboard, Platform, View } = require('react-native'); +const { themeStyle } = require('lib/components/global-style.js'); + +class AppNavComponent extends Component { + constructor() { + super(); + this.previousRouteName_ = null; + this.state = { + autoCompletionBarExtraHeight: 0, // Extra padding for the auto completion bar at the top of the keyboard + }; + } + + UNSAFE_componentWillMount() { + if (Platform.OS === 'ios') { + this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow.bind(this)); + this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide.bind(this)); + } + } + + componentWillUnmount() { + if (this.keyboardDidShowListener) this.keyboardDidShowListener.remove(); + if (this.keyboardDidHideListener) this.keyboardDidHideListener.remove(); + this.keyboardDidShowListener = null; + this.keyboardDidHideListener = null; + } + + keyboardDidShow() { + this.setState({ autoCompletionBarExtraHeight: 30 }); + } + + keyboardDidHide() { + this.setState({ autoCompletionBarExtraHeight: 0 }); + } + + render() { + if (!this.props.route) throw new Error('Route must not be null'); + + // Note: certain screens are kept into memory, in particular Notes and Search + // so that the scroll position is not lost when the user navigate away from them. + + let route = this.props.route; + let Screen = null; + let notesScreenVisible = false; + let searchScreenVisible = false; + + if (route.routeName == 'Notes') { + notesScreenVisible = true; + } else if (route.routeName == 'Search') { + searchScreenVisible = true; + } else { + Screen = this.props.screens[route.routeName].screen; + } + + // Keep the search screen loaded if the user is viewing a note from that search screen + // so that if the back button is pressed, the screen is still loaded. However, unload + // it if navigating away. + let searchScreenLoaded = searchScreenVisible || (this.previousRouteName_ == 'Search' && route.routeName == 'Note'); + + this.previousRouteName_ = route.routeName; + + const theme = themeStyle(this.props.theme); + + const style = { flex: 1, backgroundColor: theme.backgroundColor }; + + return ( + + + {searchScreenLoaded && } + {!notesScreenVisible && !searchScreenVisible && } + + + ); + } +} + +const AppNav = connect(state => { + return { + route: state.route, + theme: state.settings.theme, + }; +})(AppNavComponent); + +module.exports = { AppNav }; diff --git a/Server/dist/lib/components/base-screen.js b/Server/dist/lib/components/base-screen.js new file mode 100644 index 0000000000..0a2db7de73 --- /dev/null +++ b/Server/dist/lib/components/base-screen.js @@ -0,0 +1,39 @@ +const React = require('react'); + +const { StyleSheet } = require('react-native'); +const { globalStyle, themeStyle } = require('lib/components/global-style.js'); + +const styleObject_ = { + screen: { + flex: 1, + backgroundColor: globalStyle.backgroundColor, + }, +}; + +const styles_ = StyleSheet.create(styleObject_); + +let rootStyles_ = {}; + +class BaseScreenComponent extends React.Component { + styles() { + return styles_; + } + + styleObject() { + return styleObject_; + } + + rootStyle(themeId) { + const theme = themeStyle(themeId); + if (rootStyles_[themeId]) return rootStyles_[themeId]; + rootStyles_[themeId] = StyleSheet.create({ + root: { + flex: 1, + backgroundColor: theme.backgroundColor, + }, + }); + return rootStyles_[themeId]; + } +} + +module.exports = { BaseScreenComponent }; diff --git a/Server/dist/lib/components/checkbox.js b/Server/dist/lib/components/checkbox.js new file mode 100644 index 0000000000..58803b3390 --- /dev/null +++ b/Server/dist/lib/components/checkbox.js @@ -0,0 +1,70 @@ +const React = require('react'); +const Component = React.Component; +const { View, TouchableHighlight } = require('react-native'); +const Icon = require('react-native-vector-icons/Ionicons').default; + +const styles = { + checkboxIcon: { + fontSize: 20, + height: 22, + //marginRight: 10, + }, +}; + +class Checkbox extends Component { + constructor() { + super(); + this.state = { + checked: false, + }; + } + + UNSAFE_componentWillMount() { + this.setState({ checked: this.props.checked }); + } + + UNSAFE_componentWillReceiveProps(newProps) { + if ('checked' in newProps) { + this.setState({ checked: newProps.checked }); + } + } + + onPress() { + let newChecked = !this.state.checked; + this.setState({ checked: newChecked }); + if (this.props.onChange) this.props.onChange(newChecked); + } + + render() { + const iconName = this.state.checked ? 'md-checkbox-outline' : 'md-square-outline'; + + let style = this.props.style ? Object.assign({}, this.props.style) : {}; + style.justifyContent = 'center'; + style.alignItems = 'center'; + + let checkboxIconStyle = Object.assign({}, styles.checkboxIcon); + if (style.color) checkboxIconStyle.color = style.color; + + if (style.paddingTop) checkboxIconStyle.marginTop = style.paddingTop; + if (style.paddingBottom) checkboxIconStyle.marginBottom = style.paddingBottom; + if (style.paddingLeft) checkboxIconStyle.marginLeft = style.paddingLeft; + if (style.paddingRight) checkboxIconStyle.marginRight = style.paddingRight; + + const thStyle = { + justifyContent: 'center', + alignItems: 'center', + }; + + if (style && style.display === 'none') return ; + + //if (style.display) thStyle.display = style.display; + + return ( + this.onPress()} style={thStyle}> + + + ); + } +} + +module.exports = { Checkbox }; diff --git a/Server/dist/lib/components/global-style.js b/Server/dist/lib/components/global-style.js new file mode 100644 index 0000000000..163fb17a99 --- /dev/null +++ b/Server/dist/lib/components/global-style.js @@ -0,0 +1,159 @@ +const Setting = require('lib/models/Setting.js'); +const { Platform } = require('react-native'); + +const globalStyle = { + fontSize: 16, + margin: 15, // No text and no interactive component should be within this margin + itemMarginTop: 10, + itemMarginBottom: 10, + backgroundColor: '#ffffff', + color: '#555555', // For regular text + colorError: 'red', + colorWarn: '#9A5B00', + colorFaded: '#777777', // For less important text + fontSizeSmaller: 14, + dividerColor: '#dddddd', + strongDividerColor: '#aaaaaa', + selectedColor: '#e5e5e5', + headerBackgroundColor: '#F0F0F0', + disabledOpacity: 0.2, + colorUrl: '#7B81FF', + textSelectionColor: '#0096FF', + + raisedBackgroundColor: '#0080EF', + raisedColor: '#003363', + raisedHighlightedColor: '#ffffff', + + warningBackgroundColor: '#FFD08D', + + // For WebView - must correspond to the properties above + htmlFontSize: '16px', + htmlColor: '#222222', + htmlBackgroundColor: 'white', + htmlDividerColor: 'rgb(230,230,230)', + htmlLinkColor: 'rgb(80,130,190)', + htmlLineHeight: '1.6em', + + htmlCodeBackgroundColor: 'rgb(243, 243, 243)', + htmlCodeBorderColor: 'rgb(220, 220, 220)', + htmlCodeColor: 'rgb(0,0,0)', + + codeThemeCss: 'hljs-atom-one-light.css', +}; + +globalStyle.marginRight = globalStyle.margin; +globalStyle.marginLeft = globalStyle.margin; +globalStyle.marginTop = globalStyle.margin; +globalStyle.marginBottom = globalStyle.margin; +globalStyle.htmlMarginLeft = `${((globalStyle.marginLeft / 10) * 0.6).toFixed(2)}em`; + +let themeCache_ = {}; + +function addExtraStyles(style) { + style.icon = { + color: style.color, + fontSize: 30, + }; + + style.lineInput = { + color: style.color, + backgroundColor: style.backgroundColor, + borderBottomWidth: 1, + borderColor: style.strongDividerColor, + paddingBottom: 0, + }; + + if (Platform.OS === 'ios') { + delete style.lineInput.borderBottomWidth; + delete style.lineInput.borderColor; + } + + style.buttonRow = { + flexDirection: 'row', + borderTopWidth: 1, + borderTopColor: style.dividerColor, + paddingTop: 10, + }; + + style.normalText = { + color: style.color, + fontSize: style.fontSize, + }; + + style.urlText = { + color: style.colorUrl, + fontSize: style.fontSize, + }; + + style.headerStyle = { + color: style.color, + fontSize: style.fontSize * 1.2, + fontWeight: 'bold', + }; + + style.headerWrapperStyle = { + backgroundColor: style.headerBackgroundColor, + }; + + return style; +} + +function editorFont(fontId) { + // IMPORTANT: The font mapping must match the one in Setting.js + const fonts = { + [Setting.FONT_DEFAULT]: null, + [Setting.FONT_MENLO]: 'Menlo', + [Setting.FONT_COURIER_NEW]: 'Courier New', + [Setting.FONT_AVENIR]: 'Avenir', + [Setting.FONT_MONOSPACE]: 'monospace', + }; + if (!fontId) { + console.warn('Editor font not set! Falling back to default font."'); + fontId = Setting.FONT_DEFAULT; + } + return fonts[fontId]; +} + +function themeStyle(theme) { + if (!theme) { + console.warn('Theme not set! Defaulting to Light theme.'); + theme = Setting.THEME_LIGHT; + } + + if (themeCache_[theme]) return themeCache_[theme]; + + let output = Object.assign({}, globalStyle); + if (theme == Setting.THEME_LIGHT) return addExtraStyles(output); + + output.backgroundColor = '#1D2024'; + output.color = '#dddddd'; + output.colorFaded = '#777777'; + output.dividerColor = '#555555'; + output.strongDividerColor = '#888888'; + output.selectedColor = '#333333'; + output.textSelectionColor = '#00AEFF'; + output.headerBackgroundColor = '#2D3136'; + + output.raisedBackgroundColor = '#0F2051'; + output.raisedColor = '#788BC3'; + output.raisedHighlightedColor = '#ffffff'; + + output.htmlColor = 'rgb(220,220,220)'; + output.htmlBackgroundColor = 'rgb(29,32,36)'; + output.htmlLinkColor = 'rgb(166,166,255)'; + + output.htmlDividerColor = '#3D444E'; + output.htmlLinkColor = 'rgb(166,166,255)'; + output.htmlCodeColor = '#ffffff'; + output.htmlCodeBackgroundColor = 'rgb(47, 48, 49)'; + output.htmlCodeBorderColor = 'rgb(70, 70, 70)'; + + output.codeThemeCss = 'hljs-atom-one-dark-reasonable.css'; + + output.colorUrl = '#7B81FF'; + + themeCache_[theme] = output; + return addExtraStyles(themeCache_[theme]); +} + +module.exports = { globalStyle, themeStyle, editorFont }; diff --git a/Server/dist/lib/components/note-body-viewer.js b/Server/dist/lib/components/note-body-viewer.js new file mode 100644 index 0000000000..1612ca59c9 --- /dev/null +++ b/Server/dist/lib/components/note-body-viewer.js @@ -0,0 +1,223 @@ +const React = require('react'); +const Component = React.Component; +const { Platform, View } = require('react-native'); +const { WebView } = require('react-native-webview'); +const { themeStyle } = require('lib/components/global-style.js'); +const Setting = require('lib/models/Setting.js'); +const { reg } = require('lib/registry.js'); +const { shim } = require('lib/shim'); +const MdToHtml = require('lib/renderers/MdToHtml.js'); +const shared = require('lib/components/shared/note-screen-shared.js'); + +class NoteBodyViewer extends Component { + constructor() { + super(); + this.state = { + resources: {}, + webViewLoaded: false, + }; + + this.isMounted_ = false; + } + + UNSAFE_componentWillMount() { + this.mdToHtml_ = new MdToHtml(); + this.isMounted_ = true; + } + + componentWillUnmount() { + this.mdToHtml_ = null; + this.isMounted_ = false; + } + + onLoadEnd() { + setTimeout(() => { + if (this.props.onLoadEnd) this.props.onLoadEnd(); + }, 100); + + if (this.state.webViewLoaded) return; + + // Need to display after a delay to avoid a white flash before + // the content is displayed. + setTimeout(() => { + if (!this.isMounted_) return; + this.setState({ webViewLoaded: true }); + }, 100); + } + + shouldComponentUpdate(nextProps, nextState) { + const safeGetNoteProp = (props, propName) => { + if (!props) return null; + if (!props.note) return null; + return props.note[propName]; + }; + + // To address https://github.com/laurent22/joplin/issues/433 + // If a checkbox in a note is ticked, the body changes, which normally would trigger a re-render + // of this component, which has the unfortunate side effect of making the view scroll back to the top. + // This re-rendering however is uncessary since the component is already visually updated via JS. + // So here, if the note has not changed, we prevent the component from updating. + // This fixes the above issue. A drawback of this is if the note is updated via sync, this change + // will not be displayed immediately. + const currentNoteId = safeGetNoteProp(this.props, 'id'); + const nextNoteId = safeGetNoteProp(nextProps, 'id'); + + if (currentNoteId !== nextNoteId || nextState.webViewLoaded !== this.state.webViewLoaded) return true; + + // If the length of the body has changed, then it's something other than a checkbox that has changed, + // for example a resource that has been attached to the note while in View mode. In that case, update. + return (`${safeGetNoteProp(this.props, 'body')}`).length !== (`${safeGetNoteProp(nextProps, 'body')}`).length; + } + + rebuildMd() { + // this.mdToHtml_.clearCache(); + this.forceUpdate(); + } + + render() { + const note = this.props.note; + const style = this.props.style; + + const theme = themeStyle(this.props.theme); + + const bodyToRender = note ? note.body : ''; + + const mdOptions = { + onResourceLoaded: () => { + if (this.resourceLoadedTimeoutId_) { + clearTimeout(this.resourceLoadedTimeoutId_); + this.resourceLoadedTimeoutId_ = null; + } + + this.resourceLoadedTimeoutId_ = setTimeout(() => { + this.resourceLoadedTimeoutId_ = null; + this.forceUpdate(); + }, 100); + }, + paddingBottom: '3.8em', // Extra bottom padding to make it possible to scroll past the action button (so that it doesn't overlap the text) + highlightedKeywords: this.props.highlightedKeywords, + resources: this.props.noteResources, //await shared.attachedResources(bodyToRender), + codeTheme: theme.codeThemeCss, + postMessageSyntax: 'window.ReactNativeWebView.postMessage', + }; + + let result = this.mdToHtml_.render(bodyToRender, this.props.webViewStyle, mdOptions); + let html = result.html; + + const resourceDownloadMode = Setting.value('sync.resourceDownloadMode'); + + const injectedJs = [this.mdToHtml_.injectedJavaScript()]; + injectedJs.push(shim.injectedJs('webviewLib')); + injectedJs.push('webviewLib.initialize({ postMessage: msg => { return window.ReactNativeWebView.postMessage(msg); } });'); + injectedJs.push(` + const readyStateCheckInterval = setInterval(function() { + if (document.readyState === "complete") { + clearInterval(readyStateCheckInterval); + if ("${resourceDownloadMode}" === "manual") webviewLib.setupResourceManualDownload(); + + const hash = "${this.props.noteHash}"; + // Gives it a bit of time before scrolling to the anchor + // so that images are loaded. + if (hash) { + setTimeout(() => { + const e = document.getElementById(hash); + if (!e) { + console.warn('Cannot find hash', hash); + return; + } + e.scrollIntoView(); + }, 500); + } + } + }, 10); + `); + + html = + ` + + + + + + + ${html} + + + `; + + let webViewStyle = { backgroundColor: this.props.webViewStyle.backgroundColor }; + // On iOS, the onLoadEnd() event is never fired so always + // display the webview (don't do the little trick + // to avoid the white flash). + if (Platform.OS !== 'ios') { + webViewStyle.opacity = this.state.webViewLoaded ? 1 : 0.01; + } + + // On iOS scalesPageToFit work like this: + // + // Find the widest image, resize it *and everything else* by x% so that + // the image fits within the viewport. The problem is that it means if there's + // a large image, everything is going to be scaled to a very small size, making + // the text unreadable. + // + // On Android: + // + // Find the widest elements and scale them (and them only) to fit within the viewport + // It means it's going to scale large images, but the text will remain at the normal + // size. + // + // That means we can use scalesPageToFix on Android but not on iOS. + // The weird thing is that on iOS, scalesPageToFix=false along with a CSS + // rule "img { max-width: 100% }", works like scalesPageToFix=true on Android. + // So we use scalesPageToFix=false on iOS along with that CSS rule. + + // `baseUrl` is where the images will be loaded from. So images must use a path relative to resourceDir. + const source = { + html: html, + baseUrl: `file://${Setting.value('resourceDir')}/`, + }; + + // Note: useWebKit={false} is needed to go around this bug: + // https://github.com/react-native-community/react-native-webview/issues/376 + // However, if we add the tag as described there, it is no longer necessary and WebKit can be used! + // https://github.com/react-native-community/react-native-webview/issues/312#issuecomment-501991406 + // + // However, on iOS, due to the bug below, we cannot use WebKit: + // https://github.com/react-native-community/react-native-webview/issues/312#issuecomment-503754654 + + return ( + + this.onLoadEnd()} + onError={() => reg.logger().error('WebView error')} + onMessage={event => { + // Since RN 58 (or 59) messages are now escaped twice??? + let msg = unescape(unescape(event.nativeEvent.data)); + + console.info('Got IPC message: ', msg); + + if (msg.indexOf('checkboxclick:') === 0) { + const newBody = shared.toggleCheckbox(msg, this.props.note.body); + if (this.props.onCheckboxChange) this.props.onCheckboxChange(newBody); + } else if (msg.indexOf('markForDownload:') === 0) { + msg = msg.split(':'); + const resourceId = msg[1]; + if (this.props.onMarkForDownload) this.props.onMarkForDownload({ resourceId: resourceId }); + } else { + this.props.onJoplinLinkClick(msg); + } + }} + /> + + ); + } +} + +module.exports = { NoteBodyViewer }; diff --git a/Server/dist/lib/components/note-item.js b/Server/dist/lib/components/note-item.js new file mode 100644 index 0000000000..8e0dac3314 --- /dev/null +++ b/Server/dist/lib/components/note-item.js @@ -0,0 +1,154 @@ +const React = require('react'); +const Component = React.Component; +const { connect } = require('react-redux'); +const { Text, TouchableOpacity, View, StyleSheet } = require('react-native'); +const { Checkbox } = require('lib/components/checkbox.js'); +const Note = require('lib/models/Note.js'); +const { time } = require('lib/time-utils.js'); +const { themeStyle } = require('lib/components/global-style.js'); + +class NoteItemComponent extends Component { + constructor() { + super(); + this.styles_ = {}; + } + + noteItem_press(noteId) { + this.props.dispatch({ + type: 'NAV_GO', + routeName: 'Note', + noteId: noteId, + }); + } + + styles() { + const theme = themeStyle(this.props.theme); + + if (this.styles_[this.props.theme]) return this.styles_[this.props.theme]; + this.styles_ = {}; + + let styles = { + listItem: { + flexDirection: 'row', + //height: 40, + borderBottomWidth: 1, + borderBottomColor: theme.dividerColor, + alignItems: 'flex-start', + paddingLeft: theme.marginLeft, + paddingRight: theme.marginRight, + paddingTop: theme.itemMarginTop, + paddingBottom: theme.itemMarginBottom, + //backgroundColor: theme.backgroundColor, + }, + listItemText: { + flex: 1, + color: theme.color, + fontSize: theme.fontSize, + }, + selectionWrapper: { + backgroundColor: theme.backgroundColor, + }, + }; + + styles.listItemWithCheckbox = Object.assign({}, styles.listItem); + delete styles.listItemWithCheckbox.paddingTop; + delete styles.listItemWithCheckbox.paddingBottom; + delete styles.listItemWithCheckbox.paddingLeft; + + styles.listItemTextWithCheckbox = Object.assign({}, styles.listItemText); + styles.listItemTextWithCheckbox.marginTop = styles.listItem.paddingTop - 1; + styles.listItemTextWithCheckbox.marginBottom = styles.listItem.paddingBottom; + + styles.selectionWrapperSelected = Object.assign({}, styles.selectionWrapper); + styles.selectionWrapperSelected.backgroundColor = theme.selectedColor; + + this.styles_[this.props.theme] = StyleSheet.create(styles); + return this.styles_[this.props.theme]; + } + + async todoCheckbox_change(checked) { + if (!this.props.note) return; + + const newNote = { + id: this.props.note.id, + todo_completed: checked ? time.unixMs() : 0, + }; + await Note.save(newNote); + } + + onPress() { + if (!this.props.note) return; + if (this.props.note.encryption_applied) return; + + if (this.props.noteSelectionEnabled) { + this.props.dispatch({ + type: 'NOTE_SELECTION_TOGGLE', + id: this.props.note.id, + }); + } else { + this.props.dispatch({ + type: 'NAV_GO', + routeName: 'Note', + noteId: this.props.note.id, + }); + } + } + + onLongPress() { + if (!this.props.note) return; + + this.props.dispatch({ + type: this.props.noteSelectionEnabled ? 'NOTE_SELECTION_TOGGLE' : 'NOTE_SELECTION_START', + id: this.props.note.id, + }); + } + + render() { + const note = this.props.note ? this.props.note : {}; + const isTodo = !!Number(note.is_todo); + + const theme = themeStyle(this.props.theme); + + // IOS: display: none crashes the app + let checkboxStyle = !isTodo ? { display: 'none' } : { color: theme.color }; + + if (isTodo) { + checkboxStyle.paddingRight = 10; + checkboxStyle.paddingTop = theme.itemMarginTop; + checkboxStyle.paddingBottom = theme.itemMarginBottom; + checkboxStyle.paddingLeft = theme.marginLeft; + } + + const checkboxChecked = !!Number(note.todo_completed); + + const listItemStyle = isTodo ? this.styles().listItemWithCheckbox : this.styles().listItem; + const listItemTextStyle = isTodo ? this.styles().listItemTextWithCheckbox : this.styles().listItemText; + const opacityStyle = isTodo && checkboxChecked ? { opacity: 0.4 } : {}; + const isSelected = this.props.noteSelectionEnabled && this.props.selectedNoteIds.indexOf(note.id) >= 0; + + const selectionWrapperStyle = isSelected ? this.styles().selectionWrapperSelected : this.styles().selectionWrapper; + + return ( + this.onPress()} onLongPress={() => this.onLongPress()} activeOpacity={0.5}> + + + + this.todoCheckbox_change(checked)} /> + {Note.displayTitle(note)} + + + + + ); + } +} + +const NoteItem = connect(state => { + return { + theme: state.settings.theme, + noteSelectionEnabled: state.noteSelectionEnabled, + selectedNoteIds: state.selectedNoteIds, + }; +})(NoteItemComponent); + +module.exports = { NoteItem }; diff --git a/Server/dist/lib/components/note-list.js b/Server/dist/lib/components/note-list.js new file mode 100644 index 0000000000..916dba7f5e --- /dev/null +++ b/Server/dist/lib/components/note-list.js @@ -0,0 +1,141 @@ +const React = require('react'); +const Component = React.Component; +const { connect } = require('react-redux'); +const { ListView, Text, StyleSheet, Button, View } = require('react-native'); +const { _ } = require('lib/locale.js'); +const { NoteItem } = require('lib/components/note-item.js'); +const { time } = require('lib/time-utils.js'); +const { themeStyle } = require('lib/components/global-style.js'); + +class NoteListComponent extends Component { + constructor() { + super(); + const ds = new ListView.DataSource({ + rowHasChanged: (r1, r2) => { + return r1 !== r2; + }, + }); + this.state = { + dataSource: ds, + items: [], + selectedItemIds: [], + }; + this.rootRef_ = null; + this.styles_ = {}; + + this.createNotebookButton_click = this.createNotebookButton_click.bind(this); + } + + styles() { + const themeId = this.props.theme; + const theme = themeStyle(themeId); + + if (this.styles_[themeId]) return this.styles_[themeId]; + this.styles_ = {}; + + let styles = { + noItemMessage: { + paddingLeft: theme.marginLeft, + paddingRight: theme.marginRight, + paddingTop: theme.marginTop, + paddingBottom: theme.marginBottom, + fontSize: theme.fontSize, + color: theme.color, + textAlign: 'center', + }, + noNotebookView: { + + }, + }; + + this.styles_[themeId] = StyleSheet.create(styles); + return this.styles_[themeId]; + } + + createNotebookButton_click() { + this.props.dispatch({ + type: 'NAV_GO', + routeName: 'Folder', + folderId: null, + }); + } + + filterNotes(notes) { + const todoFilter = 'all'; //Setting.value('todoFilter'); + if (todoFilter == 'all') return notes; + + const now = time.unixMs(); + const maxInterval = 1000 * 60 * 60 * 24; + const notRecentTime = now - maxInterval; + + let output = []; + for (let i = 0; i < notes.length; i++) { + const note = notes[i]; + if (note.is_todo) { + if (todoFilter == 'recent' && note.user_updated_time < notRecentTime && !!note.todo_completed) continue; + if (todoFilter == 'nonCompleted' && !!note.todo_completed) continue; + } + output.push(note); + } + return output; + } + + UNSAFE_componentWillMount() { + const newDataSource = this.state.dataSource.cloneWithRows(this.filterNotes(this.props.items)); + this.setState({ dataSource: newDataSource }); + } + + UNSAFE_componentWillReceiveProps(newProps) { + // https://stackoverflow.com/questions/38186114/react-native-redux-and-listview + this.setState({ + dataSource: this.state.dataSource.cloneWithRows(this.filterNotes(newProps.items)), + }); + + // Make sure scroll position is reset when switching from one folder to another or to a tag list. + if (this.rootRef_ && newProps.notesSource != this.props.notesSource) { + this.rootRef_.scrollTo({ x: 0, y: 0, animated: false }); + } + } + + render() { + // `enableEmptySections` is to fix this warning: https://github.com/FaridSafi/react-native-gifted-listview/issues/39 + + if (this.state.dataSource.getRowCount()) { + return ( + (this.rootRef_ = ref)} + dataSource={this.state.dataSource} + renderRow={note => { + return ; + }} + enableEmptySections={true} + /> + ); + } else { + if (!this.props.folders.length) { + const noItemMessage = _('You currently have no notebooks.'); + return ( + + {noItemMessage} + + + {/* Add this extra padding to make sure the view is scrollable when the keyboard is visible on small screens (iPhone SE) */} + + + + { + this.dialogbox = dialogbox; + }} + /> + + ); + } +} + +const DropboxLoginScreen = connect(state => { + return { + theme: state.settings.theme, + }; +})(DropboxLoginScreenComponent); + +module.exports = { DropboxLoginScreen }; diff --git a/Server/dist/lib/components/screens/encryption-config.js b/Server/dist/lib/components/screens/encryption-config.js new file mode 100644 index 0000000000..afca2966a9 --- /dev/null +++ b/Server/dist/lib/components/screens/encryption-config.js @@ -0,0 +1,287 @@ +const React = require('react'); + +const { TextInput, TouchableOpacity, Linking, View, StyleSheet, Text, Button, ScrollView } = require('react-native'); +const EncryptionService = require('lib/services/EncryptionService'); +const { connect } = require('react-redux'); +const { ScreenHeader } = require('lib/components/screen-header.js'); +const { _ } = require('lib/locale.js'); +const { BaseScreenComponent } = require('lib/components/base-screen.js'); +const { themeStyle } = require('lib/components/global-style.js'); +const { time } = require('lib/time-utils.js'); +const shared = require('lib/components/shared/encryption-config-shared.js'); +const { dialogs } = require('lib/dialogs.js'); +const DialogBox = require('react-native-dialogbox').default; + +class EncryptionConfigScreenComponent extends BaseScreenComponent { + static navigationOptions() { + return { header: null }; + } + + constructor() { + super(); + + this.state = { + passwordPromptShow: false, + passwordPromptAnswer: '', + }; + + shared.constructor(this); + + this.styles_ = {}; + } + + componentDidMount() { + this.isMounted_ = true; + } + + componentWillUnmount() { + this.isMounted_ = false; + } + + initState(props) { + return shared.initState(this, props); + } + + async refreshStats() { + return shared.refreshStats(this); + } + + UNSAFE_componentWillMount() { + this.initState(this.props); + } + + UNSAFE_componentWillReceiveProps(nextProps) { + this.initState(nextProps); + } + + async checkPasswords() { + return shared.checkPasswords(this); + } + + styles() { + const themeId = this.props.theme; + const theme = themeStyle(themeId); + + if (this.styles_[themeId]) return this.styles_[themeId]; + this.styles_ = {}; + + let styles = { + titleText: { + flex: 1, + fontWeight: 'bold', + flexDirection: 'column', + fontSize: theme.fontSize, + paddingTop: 5, + paddingBottom: 5, + marginTop: theme.marginTop, + marginBottom: 5, + color: theme.color, + }, + normalText: { + flex: 1, + fontSize: theme.fontSize, + color: theme.color, + }, + container: { + flex: 1, + padding: theme.margin, + }, + }; + + this.styles_[themeId] = StyleSheet.create(styles); + return this.styles_[themeId]; + } + + renderMasterKey(num, mk) { + const theme = themeStyle(this.props.theme); + + const onSaveClick = () => { + return shared.onSavePasswordClick(this, mk); + }; + + const onPasswordChange = text => { + return shared.onPasswordChange(this, mk, text); + }; + + const password = this.state.passwords[mk.id] ? this.state.passwords[mk.id] : ''; + const passwordOk = this.state.passwordChecks[mk.id] === true ? '✔' : '❌'; + + const inputStyle = { flex: 1, marginRight: 10, color: theme.color }; + inputStyle.borderBottomWidth = 1; + inputStyle.borderBottomColor = theme.strongDividerColor; + + return ( + + {_('Master Key %s', mk.id.substr(0, 6))} + {_('Created: %s', time.formatMsToLocal(mk.created_time))} + + {_('Password:')} + onPasswordChange(text)} style={inputStyle}> + {passwordOk} + + + + ); + } + + passwordPromptComponent() { + const theme = themeStyle(this.props.theme); + + const onEnableClick = async () => { + try { + const password = this.state.passwordPromptAnswer; + if (!password) throw new Error(_('Password cannot be empty')); + await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password); + this.setState({ passwordPromptShow: false }); + } catch (error) { + await dialogs.error(this, error.message); + } + }; + + return ( + + {_('Enabling encryption means *all* your notes and attachments are going to be re-synchronised and sent encrypted to the sync target. Do not lose the password as, for security purposes, this will be the *only* way to decrypt the data! To enable encryption, please enter your password below.')} + { + this.setState({ passwordPromptAnswer: text }); + }} + > + + + + + + + + + + ); + } + + render() { + const theme = themeStyle(this.props.theme); + const masterKeys = this.state.masterKeys; + const decryptedItemsInfo = this.props.encryptionEnabled ? {shared.decryptedStatText(this)} : null; + + const mkComps = []; + + let nonExistingMasterKeyIds = this.props.notLoadedMasterKeys.slice(); + + for (let i = 0; i < masterKeys.length; i++) { + const mk = masterKeys[i]; + mkComps.push(this.renderMasterKey(i + 1, mk)); + + const idx = nonExistingMasterKeyIds.indexOf(mk.id); + if (idx >= 0) nonExistingMasterKeyIds.splice(idx, 1); + } + + const onToggleButtonClick = async () => { + if (this.props.encryptionEnabled) { + const ok = await dialogs.confirm(this, _('Disabling encryption means *all* your notes and attachments are going to be re-synchronised and sent unencrypted to the sync target. Do you wish to continue?')); + if (!ok) return; + + try { + await EncryptionService.instance().disableEncryption(); + } catch (error) { + await dialogs.error(this, error.message); + } + } else { + this.setState({ + passwordPromptShow: true, + passwordPromptAnswer: '', + }); + return; + } + }; + + let nonExistingMasterKeySection = null; + + if (nonExistingMasterKeyIds.length) { + const rows = []; + for (let i = 0; i < nonExistingMasterKeyIds.length; i++) { + const id = nonExistingMasterKeyIds[i]; + rows.push( + + {id} + + ); + } + + nonExistingMasterKeySection = ( + + {_('Missing Master Keys')} + {_('The master keys with these IDs are used to encrypt some of your items, however the application does not currently have access to them. It is likely they will eventually be downloaded via synchronisation.')} + {rows} + + ); + } + + const passwordPromptComp = this.state.passwordPromptShow ? this.passwordPromptComponent() : null; + const toggleButton = !this.state.passwordPromptShow ? ( + + + + ) : null; + + return ( + + + + { + + {_('For more information about End-To-End Encryption (E2EE) and advice on how to enable it please check the documentation:')} + { + Linking.openURL('https://joplinapp.org/e2ee/'); + }} + > + https://joplinapp.org/e2ee/ + + + } + + {_('Status')} + {_('Encryption is: %s', this.props.encryptionEnabled ? _('Enabled') : _('Disabled'))} + {decryptedItemsInfo} + {toggleButton} + {passwordPromptComp} + {mkComps} + {nonExistingMasterKeySection} + + + { + this.dialogbox = dialogbox; + }} + /> + + ); + } +} + +const EncryptionConfigScreen = connect(state => { + return { + theme: state.settings.theme, + masterKeys: state.masterKeys, + passwords: state.settings['encryption.passwordCache'], + encryptionEnabled: state.settings['encryption.enabled'], + activeMasterKeyId: state.settings['encryption.activeMasterKeyId'], + notLoadedMasterKeys: state.notLoadedMasterKeys, + }; +})(EncryptionConfigScreenComponent); + +module.exports = { EncryptionConfigScreen }; diff --git a/Server/dist/lib/components/screens/folder.js b/Server/dist/lib/components/screens/folder.js new file mode 100644 index 0000000000..87f6111c09 --- /dev/null +++ b/Server/dist/lib/components/screens/folder.js @@ -0,0 +1,128 @@ +const React = require('react'); + +const { View, TextInput, StyleSheet } = require('react-native'); +const { connect } = require('react-redux'); +const Folder = require('lib/models/Folder.js'); +const BaseModel = require('lib/BaseModel.js'); +const { ScreenHeader } = require('lib/components/screen-header.js'); +const { BaseScreenComponent } = require('lib/components/base-screen.js'); +const { dialogs } = require('lib/dialogs.js'); +const { themeStyle } = require('lib/components/global-style.js'); +const { _ } = require('lib/locale.js'); + +class FolderScreenComponent extends BaseScreenComponent { + static navigationOptions() { + return { header: null }; + } + + constructor() { + super(); + this.state = { + folder: Folder.new(), + lastSavedFolder: null, + }; + this.styles_ = {}; + } + + styles() { + const theme = themeStyle(this.props.theme); + + if (this.styles_[this.props.theme]) return this.styles_[this.props.theme]; + this.styles_ = {}; + + let styles = { + textInput: { + color: theme.color, + paddingLeft: theme.marginLeft, + marginTop: theme.marginTop, + }, + }; + + this.styles_[this.props.theme] = StyleSheet.create(styles); + return this.styles_[this.props.theme]; + } + + UNSAFE_componentWillMount() { + if (!this.props.folderId) { + const folder = Folder.new(); + this.setState({ + folder: folder, + lastSavedFolder: Object.assign({}, folder), + }); + } else { + Folder.load(this.props.folderId).then(folder => { + this.setState({ + folder: folder, + lastSavedFolder: Object.assign({}, folder), + }); + }); + } + } + + isModified() { + if (!this.state.folder || !this.state.lastSavedFolder) return false; + let diff = BaseModel.diffObjects(this.state.folder, this.state.lastSavedFolder); + delete diff.type_; + return !!Object.getOwnPropertyNames(diff).length; + } + + folderComponent_change(propName, propValue) { + this.setState((prevState) => { + let folder = Object.assign({}, prevState.folder); + folder[propName] = propValue; + return { folder: folder }; + }); + } + + title_changeText(text) { + this.folderComponent_change('title', text); + } + + async saveFolderButton_press() { + let folder = Object.assign({}, this.state.folder); + + try { + folder = await Folder.save(folder, { userSideValidation: true }); + } catch (error) { + dialogs.error(this, _('The notebook could not be saved: %s', error.message)); + return; + } + + this.setState({ + lastSavedFolder: Object.assign({}, folder), + folder: folder, + }); + + this.props.dispatch({ + type: 'NAV_GO', + routeName: 'Notes', + folderId: folder.id, + }); + } + + render() { + let saveButtonDisabled = !this.isModified(); + const theme = themeStyle(this.props.theme); + + return ( + + this.saveFolderButton_press()} showSideMenuButton={false} showSearchButton={false} /> + this.title_changeText(text)} /> + { + this.dialogbox = dialogbox; + }} + /> + + ); + } +} + +const FolderScreen = connect(state => { + return { + folderId: state.selectedFolderId, + theme: state.settings.theme, + }; +})(FolderScreenComponent); + +module.exports = { FolderScreen }; diff --git a/Server/dist/lib/components/screens/log.js b/Server/dist/lib/components/screens/log.js new file mode 100644 index 0000000000..7b56713a95 --- /dev/null +++ b/Server/dist/lib/components/screens/log.js @@ -0,0 +1,139 @@ +const React = require('react'); + +const { ListView, View, Text, Button, StyleSheet, Platform } = require('react-native'); +const { connect } = require('react-redux'); +const { reg } = require('lib/registry.js'); +const { ScreenHeader } = require('lib/components/screen-header.js'); +const { time } = require('lib/time-utils'); +const { themeStyle } = require('lib/components/global-style.js'); +const { Logger } = require('lib/logger.js'); +const { BaseScreenComponent } = require('lib/components/base-screen.js'); +const { _ } = require('lib/locale.js'); + +class LogScreenComponent extends BaseScreenComponent { + static navigationOptions() { + return { header: null }; + } + + constructor() { + super(); + const ds = new ListView.DataSource({ + rowHasChanged: (r1, r2) => { + return r1 !== r2; + }, + }); + this.state = { + dataSource: ds, + showErrorsOnly: false, + }; + this.styles_ = {}; + } + + styles() { + const theme = themeStyle(this.props.theme); + + if (this.styles_[this.props.theme]) return this.styles_[this.props.theme]; + this.styles_ = {}; + + let styles = { + row: { + flexDirection: 'row', + paddingLeft: 1, + paddingRight: 1, + paddingTop: 0, + paddingBottom: 0, + }, + rowText: { + fontSize: 10, + color: theme.color, + }, + }; + + if (Platform.OS !== 'ios') { + // Crashes on iOS with error "Unrecognized font family 'monospace'" + styles.rowText.fontFamily = 'monospace'; + } + + styles.rowTextError = Object.assign({}, styles.rowText); + styles.rowTextError.color = theme.colorError; + + styles.rowTextWarn = Object.assign({}, styles.rowText); + styles.rowTextWarn.color = theme.colorWarn; + + this.styles_[this.props.theme] = StyleSheet.create(styles); + return this.styles_[this.props.theme]; + } + + UNSAFE_componentWillMount() { + this.resfreshLogEntries(); + } + + resfreshLogEntries(showErrorsOnly = null) { + if (showErrorsOnly === null) showErrorsOnly = this.state.showErrorsOnly; + + let levels = [Logger.LEVEL_DEBUG, Logger.LEVEL_INFO, Logger.LEVEL_WARN, Logger.LEVEL_ERROR]; + if (showErrorsOnly) levels = [Logger.LEVEL_WARN, Logger.LEVEL_ERROR]; + + reg + .logger() + .lastEntries(1000, { levels: levels }) + .then(entries => { + const newDataSource = this.state.dataSource.cloneWithRows(entries); + this.setState({ dataSource: newDataSource }); + }); + } + + toggleErrorsOnly() { + const showErrorsOnly = !this.state.showErrorsOnly; + this.setState({ showErrorsOnly: showErrorsOnly }); + this.resfreshLogEntries(showErrorsOnly); + } + + render() { + let renderRow = item => { + let textStyle = this.styles().rowText; + if (item.level == Logger.LEVEL_WARN) textStyle = this.styles().rowTextWarn; + if (item.level == Logger.LEVEL_ERROR) textStyle = this.styles().rowTextError; + + return ( + + {`${time.formatMsToLocal(item.timestamp, 'MM-DDTHH:mm:ss')}: ${item.message}`} + + ); + }; + + // `enableEmptySections` is to fix this warning: https://github.com/FaridSafi/react-native-gifted-listview/issues/39 + return ( + + + + + + + + ); + } +} + +const OneDriveLoginScreen = connect(() => { + return {}; +})(OneDriveLoginScreenComponent); + +module.exports = { OneDriveLoginScreen }; diff --git a/Server/dist/lib/components/screens/search.js b/Server/dist/lib/components/screens/search.js new file mode 100644 index 0000000000..a7793a49a0 --- /dev/null +++ b/Server/dist/lib/components/screens/search.js @@ -0,0 +1,209 @@ +const React = require('react'); + +const { StyleSheet, View, TextInput, FlatList, TouchableHighlight } = require('react-native'); +const { connect } = require('react-redux'); +const { ScreenHeader } = require('lib/components/screen-header.js'); +const Icon = require('react-native-vector-icons/Ionicons').default; +const { _ } = require('lib/locale.js'); +const Note = require('lib/models/Note.js'); +const { NoteItem } = require('lib/components/note-item.js'); +const { BaseScreenComponent } = require('lib/components/base-screen.js'); +const { themeStyle } = require('lib/components/global-style.js'); +const SearchEngineUtils = require('lib/services/SearchEngineUtils'); +const DialogBox = require('react-native-dialogbox').default; + +class SearchScreenComponent extends BaseScreenComponent { + static navigationOptions() { + return { header: null }; + } + + constructor() { + super(); + this.state = { + query: '', + notes: [], + }; + this.isMounted_ = false; + this.styles_ = {}; + } + + styles() { + const theme = themeStyle(this.props.theme); + + if (this.styles_[this.props.theme]) return this.styles_[this.props.theme]; + this.styles_ = {}; + + let styles = { + body: { + flex: 1, + }, + searchContainer: { + flexDirection: 'row', + alignItems: 'center', + borderWidth: 1, + borderColor: theme.dividerColor, + }, + }; + + styles.searchTextInput = Object.assign({}, theme.lineInput); + styles.searchTextInput.paddingLeft = theme.marginLeft; + styles.searchTextInput.flex = 1; + styles.searchTextInput.backgroundColor = theme.backgroundColor; + styles.searchTextInput.color = theme.color; + + styles.clearIcon = Object.assign({}, theme.icon); + styles.clearIcon.color = theme.colorFaded; + styles.clearIcon.paddingRight = theme.marginRight; + styles.clearIcon.backgroundColor = theme.backgroundColor; + + this.styles_[this.props.theme] = StyleSheet.create(styles); + return this.styles_[this.props.theme]; + } + + componentDidMount() { + this.setState({ query: this.props.query }); + this.refreshSearch(this.props.query); + this.isMounted_ = true; + } + + componentWillUnmount() { + this.isMounted_ = false; + } + + // UNSAFE_componentWillReceiveProps(newProps) { + // console.info('UNSAFE_componentWillReceiveProps', newProps); + + // let newState = {}; + // if ('query' in newProps && !this.state.query) newState.query = newProps.query; + + // if (Object.getOwnPropertyNames(newState).length) { + // this.setState(newState); + // this.refreshSearch(newState.query); + // } + // } + + searchTextInput_submit() { + const query = this.state.query.trim(); + if (!query) return; + + this.props.dispatch({ + type: 'SEARCH_QUERY', + query: query, + }); + + this.setState({ query: query }); + this.refreshSearch(query); + } + + clearButton_press() { + this.props.dispatch({ + type: 'SEARCH_QUERY', + query: '', + }); + + this.setState({ query: '' }); + this.refreshSearch(''); + } + + async refreshSearch(query = null) { + if (!this.props.visible) return; + + query = query === null ? this.state.query.trim : query.trim(); + + let notes = []; + + if (query) { + if (this.props.settings['db.ftsEnabled']) { + notes = await SearchEngineUtils.notesForQuery(query); + } else { + let p = query.split(' '); + let temp = []; + for (let i = 0; i < p.length; i++) { + let t = p[i].trim(); + if (!t) continue; + temp.push(t); + } + + notes = await Note.previews(null, { + anywherePattern: `*${temp.join('*')}*`, + }); + } + } + + if (!this.isMounted_) return; + + this.setState({ notes: notes }); + } + + searchTextInput_changeText(text) { + this.setState({ query: text }); + } + + render() { + if (!this.isMounted_) return null; + + const theme = themeStyle(this.props.theme); + + let rootStyle = { + flex: 1, + backgroundColor: theme.backgroundColor, + }; + + if (!this.props.visible) { + rootStyle.flex = 0.001; // This is a bit of a hack but it seems to work fine - it makes the component invisible but without unmounting it + } + + const thisComponent = this; + + return ( + + + + + { + this.searchTextInput_submit(); + }} + onChangeText={text => this.searchTextInput_changeText(text)} + value={this.state.query} + selectionColor={theme.textSelectionColor} + /> + this.clearButton_press()}> + + + + + item.id} renderItem={event => } /> + + { + this.dialogbox = dialogbox; + }} + /> + + ); + } +} + +const SearchScreen = connect(state => { + return { + query: state.searchQuery, + theme: state.settings.theme, + settings: state.settings, + noteSelectionEnabled: state.noteSelectionEnabled, + }; +})(SearchScreenComponent); + +module.exports = { SearchScreen }; diff --git a/Server/dist/lib/components/screens/status.js b/Server/dist/lib/components/screens/status.js new file mode 100644 index 0000000000..e1538132f7 --- /dev/null +++ b/Server/dist/lib/components/screens/status.js @@ -0,0 +1,143 @@ +const React = require('react'); + +const { StyleSheet, View, Text, Button, FlatList } = require('react-native'); +const Setting = require('lib/models/Setting.js'); +const { connect } = require('react-redux'); +const { ScreenHeader } = require('lib/components/screen-header.js'); +const { ReportService } = require('lib/services/report.js'); +const { _ } = require('lib/locale.js'); +const { BaseScreenComponent } = require('lib/components/base-screen.js'); +const { globalStyle, themeStyle } = require('lib/components/global-style.js'); + +const styles = StyleSheet.create({ + body: { + flex: 1, + margin: globalStyle.margin, + }, +}); + +class StatusScreenComponent extends BaseScreenComponent { + static navigationOptions() { + return { header: null }; + } + + constructor() { + super(); + this.state = { + report: [], + }; + } + + UNSAFE_componentWillMount() { + this.resfreshScreen(); + } + + async resfreshScreen() { + let service = new ReportService(); + let report = await service.status(Setting.value('sync.target')); + this.setState({ report: report }); + } + + render() { + const theme = themeStyle(this.props.theme); + + const renderBody = report => { + let baseStyle = { + paddingLeft: 6, + paddingRight: 6, + paddingTop: 2, + paddingBottom: 2, + flex: 0, + color: theme.color, + fontSize: theme.fontSize, + }; + + let lines = []; + + for (let i = 0; i < report.length; i++) { + let section = report[i]; + + let style = Object.assign({}, baseStyle); + style.fontWeight = 'bold'; + if (i > 0) style.paddingTop = 20; + lines.push({ key: `section_${i}`, isSection: true, text: section.title }); + + for (let n in section.body) { + if (!section.body.hasOwnProperty(n)) continue; + style = Object.assign({}, baseStyle); + const item = section.body[n]; + + let text = ''; + + let retryHandler = null; + if (typeof item === 'object') { + if (item.canRetry) { + retryHandler = async () => { + await item.retryHandler(); + this.resfreshScreen(); + }; + } + text = item.text; + } else { + text = item; + } + + lines.push({ key: `item_${i}_${n}`, text: text, retryHandler: retryHandler }); + } + + lines.push({ key: `divider2_${i}`, isDivider: true }); + } + + return ( + { + let style = Object.assign({}, baseStyle); + + if (item.isSection === true) { + style.fontWeight = 'bold'; + style.marginBottom = 5; + } + + style.flex = 1; + + const retryButton = item.retryHandler ? ( + +