api.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. const mime = require("mime-types");
  2. const fs = require('fs');
  3. const Path = require('path');
  4. const Security = require('../src/security.js');
  5. const MediaService = require('../model/mediaService.js');
  6. const MediaFileMetaModel = require('../model/mediaItemMeta.js').MediaFileMetaModel;
  7. const MediaFileTagModel = require('../model/mediaItemTag.js').MediaFileTagModel;
  8. const { AccessModel, ACCESS_TYPE, ACCESS_GRANT, ACCESS_TO } = require('../model/access.js');
  9. function MediaToJson(mediaData) {
  10. if (!mediaData)
  11. return null;
  12. if (mediaData.accessType === ACCESS_GRANT.readNoMeta)
  13. mediaData.meta = {
  14. height: mediaData.meta?.height,
  15. width: mediaData.meta?.width
  16. };
  17. return mediaData;
  18. }
  19. function accessToJson(access) {
  20. const typeStr = [ "unknown", "ldapAccount", "email", "link", "every one" ][access.type];
  21. const accessToStr = [ "unknown", "item", "tag", "meta", "everything", "admin"][access.accessTo];
  22. const grantStr = [ "none", "read", "write", "read without meta"][access.grant];
  23. return {
  24. id: access.id,
  25. type: typeStr,
  26. typeLabel: access.typeLabel,
  27. typeData: access.typeData,
  28. accessTo: accessToStr,
  29. accessToData: access.accessToData,
  30. grant: grantStr
  31. };
  32. }
  33. async function accessListToJson(app, req) {
  34. let result = {
  35. ...(req.sessionObj?.accessList || {})
  36. };
  37. result.isAdmin = await req.sessionObj?.accessList?.isAdmin?.(app, result) || false;
  38. delete result.isAdmin_;
  39. return result;
  40. }
  41. module.exports = { register: app => {
  42. app.router.post("/api/server/reboot", async (req, res) => {
  43. app.routerUtils.onApiRequest(req, res);
  44. if (!await req.sessionObj?.accessList?.isAdmin(app, req.sessionObj?.accessList))
  45. return app.routerUtils.onBadRequest(res);
  46. console.log("Starting reboot process, initiaed from ", res.sessionObj);
  47. app.server.close(() => {
  48. require("child_process").spawn(process.argv.shift(), process.argv, {
  49. cwd: process.cwd(),
  50. detached : true,
  51. stdio: "inherit"
  52. }).unref();
  53. setTimeout(() => process.exit(), 500);
  54. });
  55. app.routerUtils.jsonResponse(res, {});
  56. });
  57. app.router.post("/api/database/reload", async (req, res) => {
  58. app.routerUtils.onApiRequest(req, res);
  59. if (!await req.sessionObj?.accessList?.isAdmin(app, req.sessionObj?.accessList))
  60. return app.routerUtils.onBadRequest(res);
  61. app.libraryManager.forceReload(app);
  62. app.routerUtils.jsonResponse(res, {});
  63. });
  64. app.router.post("/api/database/scan", async (req, res) => {
  65. app.routerUtils.onApiRequest(req, res);
  66. if (!await req.sessionObj?.accessList?.isAdmin(app, req.sessionObj?.accessList))
  67. return app.routerUtils.onBadRequest(res);
  68. app.libraryManager.updateLibraries(app);
  69. app.routerUtils.jsonResponse(res, {});
  70. });
  71. app.router.get("/api/access/list", async (req, res) => {
  72. app.routerUtils.onApiRequest(req, res);
  73. app.routerUtils.jsonResponse(res, await accessListToJson(app, req));
  74. });
  75. app.router.post("/api/access/login", async (req, res) => { // /api/access/login, post: { username: string, password: string }
  76. app.routerUtils.onApiRequest(req, res);
  77. if (!req.post?.username?.length || !req.post?.password?.length)
  78. return app.routerUtils.httpResponse(res, 400, "Missing argument");
  79. try {
  80. const access = await app.databaseHelper.fetch(AccessModel, { type: ACCESS_TYPE.ldapAccount, typeData: req.post.username });
  81. if (access?.length) {
  82. await Security.tryLoginIntoSession(req, access, req.post.username, req.post.password);
  83. if (access.find(x => x.accessTo == ACCESS_TO.admin))
  84. Security.setAdmin(req, true);
  85. } else {
  86. return app.routerUtils.onBadRequest(res, "Invalid Credentials");
  87. }
  88. }
  89. catch (err) {
  90. return app.routerUtils.onBadRequest(res, err);
  91. }
  92. app.routerUtils.jsonResponse(res, await accessListToJson(app, req));
  93. });
  94. app.router.post("/api/access/link", async (req, res) => { // /api/access/link, post: { linkIds: [string] (JSON) }
  95. app.routerUtils.onApiRequest(req, res);
  96. if (!req.post?.linkIds?.length)
  97. return app.routerUtils.httpResponse(res, 400, "Missing argument");
  98. try {
  99. for (let i of JSON.parse(req.post.linkIds)) {
  100. const access = await app.databaseHelper.findOne(AccessModel, { type: ACCESS_TYPE.link, typeData: i });
  101. if (access) {
  102. Security.addLinkToSession(req, access.id, i, access.typeLabel);
  103. if (access.accessTo == ACCESS_TO.admin)
  104. Security.setAdmin(req, true);
  105. }
  106. }
  107. }
  108. catch (err) {
  109. console.error(err);
  110. return app.routerUtils.onBadRequest(res);
  111. }
  112. app.routerUtils.jsonResponse(res, await accessListToJson(app, req));
  113. });
  114. app.router.del("/api/access/:id", async (req, res) => {
  115. app.routerUtils.onApiRequest(req, res);
  116. Security.removeFromSession(req, req.params.id);
  117. const access = await app.databaseHelper.fetch(AccessModel, { id: Object.keys(req.sessionObj.accessList).map(i => req.sessionObj.accessList[i]).filter(x => x.dbId).map(x => x.dbId), accessTo: ACCESS_TO.admin });
  118. const result = Security.setAdmin(req, !!(access?.length || 0));
  119. app.routerUtils.jsonResponse(res, result);
  120. });
  121. app.router.post("/api/accessAdmin/create", async (req, res) => {
  122. app.routerUtils.onApiRequest(req, res);
  123. if (!await req.sessionObj?.accessList?.isAdmin(app, req.sessionObj?.accessList) || !req.body)
  124. return app.routerUtils.onBadRequest(res);
  125. let access = new AccessModel();
  126. access.type = parseInt(req.body.typeId);
  127. access.typeData = req.body.typeData;
  128. access.typeLabel = req.body.typeLabel;
  129. access.accessTo = parseInt(req.body.accessToId);
  130. access.accessToData = req.body.accessToData;
  131. access.grant = parseInt(req.body.grant);
  132. try {
  133. await app.databaseHelper.insertOne(access);
  134. }
  135. catch (err) {
  136. console.error(err);
  137. return app.routerUtils.onBadRequest(res);
  138. }
  139. app.routerUtils.jsonResponse(res, accessToJson(access));
  140. });
  141. app.router.del("/api/accessAdmin/:id", async (req, res) => {
  142. app.routerUtils.onApiRequest(req, res);
  143. if (!await req.sessionObj?.accessList?.isAdmin(app, req.sessionObj?.accessList) || !req.params.id)
  144. return app.routerUtils.onBadRequest(res);
  145. app.databaseHelper.remove(AccessModel, { id: parseInt(req.params.id) });
  146. app.routerUtils.jsonResponse(res, {});
  147. });
  148. app.router.post("/api/accessAdmin/:id", async (req, res) => {
  149. app.routerUtils.onApiRequest(req, res);
  150. if (!await req.sessionObj?.accessList?.isAdmin(app, req.sessionObj?.accessList) || !req.params.id || !req.body)
  151. return app.routerUtils.onBadRequest(res);
  152. const access = (await app.databaseHelper.fetch(AccessModel, { id: parseInt(req.params.id) }))?.[0];
  153. if (!access)
  154. return app.routerUtils.onBadRequest(res);
  155. access.typeLabel = req.body.typeLabel;
  156. access.typeData = req.body.typeData;
  157. access.accessTo = parseInt(req.body.accessToId);
  158. access.accessToData = req.body.accessToData;
  159. access.grant = parseInt(req.body.grant);
  160. try {
  161. app.databaseHelper.upsertOne(access);
  162. }
  163. catch (err) {
  164. console.error(err);
  165. return app.routerUtils.onBadRequest(res);
  166. }
  167. app.routerUtils.jsonResponse(res, accessToJson(access));
  168. });
  169. app.router.get("/api/accessAdmin/list", async (req, res) => {
  170. app.routerUtils.onApiRequest(req, res);
  171. if (!await req.sessionObj?.accessList?.isAdmin(app, req.sessionObj?.accessList))
  172. return app.routerUtils.onBadRequest(res);
  173. app.routerUtils.jsonResponse(res, (await app.databaseHelper.fetch(AccessModel)).map(accessToJson));
  174. });
  175. app.router.post("/api/media/:id/tag/del/:tag", async (req, res) => {
  176. app.routerUtils.onApiRequest(req, res);
  177. if (!req.params.id ||!req.params.tag)
  178. return app.routerUtils.onBadRequest(res);
  179. let checksum = [ req.params.id ];
  180. if (req.params.id === "list") {
  181. if (!req.body?.['list[]'])
  182. return app.routerUtils.onBadRequest(res);
  183. checksum = req.body['list[]'];
  184. }
  185. let data = await MediaService.fetchMultiple(app, checksum, req.sessionObj?.accessList, 0);
  186. data = Object.keys(data).map(x => data[x]).filter(x => x.ACCESS_TYPE != ACCESS_GRANT.write);
  187. await Promise.all(data.map(x => MediaService.updateVersionInDb(app, x.fixedSum)));
  188. await app.databaseHelper.remove(MediaFileTagModel, { md5sum: data.map(x => x.fixedSum), tag: decodeURIComponent(req.params.tag), fromMeta: 0 });
  189. const allMedias = await MediaService.fetchMultiple(app, checksum, req.sessionObj?.accessList, 0);
  190. app.routerUtils.jsonResponse(res, Object.keys(allMedias).map(x => allMedias[x]).map(x => MediaToJson(x)));
  191. });
  192. app.router.put("/api/media/:id/tag", async (req, res) => {
  193. app.routerUtils.onApiRequest(req, res);
  194. const requestedTag = req.body?.tag;
  195. if (!req.params.id ||!requestedTag)
  196. return app.routerUtils.onBadRequest(res);
  197. let checksum = [ req.params.id ];
  198. if (req.params.id === "list") {
  199. if (!req.body?.['list[]'])
  200. return app.routerUtils.onBadRequest(res);
  201. checksum = req.body['list[]'];
  202. }
  203. let data = await MediaService.fetchMultiple(app, checksum, req.sessionObj?.accessList, 0);
  204. data = Object.keys(data)
  205. .map(x => data[x])
  206. .filter(x => {
  207. if (x.ACCESS_TYPE != ACCESS_GRANT.write)
  208. return true;
  209. for (let existingTag of [...x.tags, ...x.fixedTags]) {
  210. if (existingTag === requestedTag || existingTag.startsWith(`${requestedTag}/`)) {
  211. return true;
  212. }
  213. }
  214. });
  215. await Promise.all(data.map(x => MediaService.updateVersionInDb(app, x.fixedSum)));
  216. let tag = data.map(x => new MediaFileTagModel(x.fixedSum, requestedTag, false));
  217. try {
  218. await app.databaseHelper.insertMultipleSameTable(tag);
  219. }
  220. catch (err) {
  221. console.error(err);
  222. return app.routerUtils.onBadRequest(res);
  223. }
  224. const allMedias = await MediaService.fetchMultiple(app, checksum, req.sessionObj?.accessList, 0);
  225. app.routerUtils.jsonResponse(res, Object.keys(allMedias).map(x => allMedias[x]).map(x => MediaToJson(x)));
  226. });
  227. app.router.patch("/api/media/:id/meta/:key", async (req, res) => {
  228. app.routerUtils.onApiRequest(req, res);
  229. if (!req.params.id ||!req.params.key || !Number.isInteger(req.body?.value?.length))
  230. return app.routerUtils.onBadRequest(res);
  231. let checksum = [ req.params.id ];
  232. if (req.params.id === "list") {
  233. if (!req.body?.['list[]'])
  234. return app.routerUtils.onBadRequest(res);
  235. checksum = req.body['list[]'];
  236. }
  237. let data = await MediaService.fetchMultiple(app, checksum, req.sessionObj?.accessList, 0);
  238. data = Object.keys(data)
  239. .map(x => data[x])
  240. .filter(x => x.ACCESS_TYPE != ACCESS_GRANT.write);
  241. if (!(await MediaService.updateMeta(app, data.map(x => x.fixedSum), req.params.key, req.body.value)))
  242. return app.routerUtils.onBadRequest(res);
  243. const allMedias = await MediaService.fetchMultiple(app, checksum, req.sessionObj?.accessList, 0);
  244. app.routerUtils.jsonResponse(res, Object.keys(allMedias).map(x => allMedias[x]).map(x => MediaToJson(x)));
  245. });
  246. app.router.get("/api/media/list", async (req, res) => {
  247. app.routerUtils.onApiRequest(req, res);
  248. let first = undefined,
  249. last = undefined,
  250. maxVersion = undefined;
  251. if (req.body?.chronology !== undefined) {
  252. let range = await MediaService.getMediaRange(app);
  253. first = range.min;
  254. last = range.max;
  255. maxVersion = range.maxVersion;
  256. }
  257. let fromDate = parseInt(req.body?.from);
  258. let count = parseInt(req.body?.count);
  259. app.routerUtils.jsonResponse(res, {
  260. data: (await MediaService.fetchMediasWithAccess(
  261. app,
  262. isNaN(fromDate) ? 0 : fromDate,
  263. isNaN(count) ? 25 : Math.min(350, count),
  264. req.sessionObj?.accessList,
  265. req.body?.version || 0)).map(MediaToJson),
  266. first: first,
  267. last: last,
  268. maxVersion: maxVersion
  269. });
  270. });
  271. app.router.get("/api/media/sumlist", async (req, res) => {
  272. app.routerUtils.onApiRequest(req, res);
  273. app.routerUtils.jsonResponse(res, {
  274. data: await MediaService.fetchMediasSumWithAccess(
  275. app,
  276. req.sessionObj?.accessList)
  277. });
  278. });
  279. app.router.del("/api/media/:md5sum", async (req, res) => {
  280. app.routerUtils.onApiRequest(req, res);
  281. let data = MediaToJson(await MediaService.fetchOne(app, req.params.md5sum, req.sessionObj?.accessList, 0));
  282. if (!data)
  283. return app.routerUtils.onPageNotFound(res);
  284. await MediaService.removeMedia(app, data);
  285. app.routerUtils.jsonResponse(res, {});
  286. });
  287. app.router.get("/api/media/:md5sum", async (req, res) => {
  288. app.routerUtils.onApiRequest(req, res);
  289. let data = MediaToJson(await MediaService.fetchOne(app, req.params.md5sum, req.sessionObj?.accessList, 0));
  290. if (!data)
  291. return app.routerUtils.onPageNotFound(res);
  292. app.routerUtils.jsonResponse(res, data);
  293. });
  294. app.router.get("/api/media/thumbnail/:md5sum.jpg", async (req, res) => {
  295. app.routerUtils.onApiRequest(req, res);
  296. let data = await MediaService.fetchOne(app, req.params.md5sum, req.sessionObj?.accessList, 0);
  297. if (!data)
  298. return app.routerUtils.onPageNotFound(res);
  299. try {
  300. let thumbnail = null;
  301. req.body = req.body || {};
  302. req.body.w = parseInt(req.body.w || 0);
  303. req.body.h = parseInt(req.body.h || 0);
  304. req.body.q = parseInt(req.body.q || 6);
  305. try {
  306. thumbnail = await (await app.libraryManager.findMedia(data.path))?.createThumbnail(req.body.w, req.body.h, req.body.q);
  307. } catch (err) {
  308. return app.routerUtils.apiError(res);
  309. }
  310. if (!thumbnail)
  311. return app.routerUtils.onPageNotFound(res);
  312. res.setHeader("Content-Type", "image/jpeg");
  313. res.setHeader("Content-Length", fs.statSync(thumbnail.name)?.size || undefined);
  314. res.setHeader("Cache-Control", "private, max-age=2630000"); // 1 month cache
  315. let rd = fs.createReadStream(thumbnail.name);
  316. rd.once('end', () => thumbnail.removeCallback());
  317. rd.pipe(res);
  318. }
  319. catch (err) {
  320. console.error(err);
  321. app.routerUtils.onPageNotFound(res);
  322. }
  323. });
  324. app.router.get("/api/media/original/:md5sum", async (req, res) => {
  325. app.routerUtils.onApiRequest(req, res);
  326. let data = await MediaService.fetchOne(app, req.params.md5sum, req.sessionObj?.accessList, 0);
  327. if (!data)
  328. return app.routerUtils.onPageNotFound(res);
  329. const fileName = Path.basename(data.path);
  330. res.setHeader("Cache-Control", "private, max-age=2630000"); // 1 month cache
  331. if (data.accessType === ACCESS_GRANT.readNoMeta || req.body?.trim !== undefined) {
  332. console.log("remove meta");//-> trim metadata
  333. }
  334. res.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
  335. res.setHeader("Content-Type", mime.lookup(data.path));
  336. res.setHeader("Content-Length", fs.statSync(data.path)?.size || undefined);
  337. fs.createReadStream(data.path).pipe(res);
  338. });
  339. }};