1
0

api.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  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. module.exports = { register: app => {
  20. app.router.post("/api/database/reload", (req, res) => {
  21. app.routerUtils.onApiRequest(req, res);
  22. if (!req.sessionObj?.accessList?.isAdmin)
  23. return app.routerUtils.onBadRequest(res);
  24. app.libraryManager.updateLibraries(app.databaseHelper).finally(x => { require('../src/autotagBuilder').rebuildPathTags(app); });
  25. app.routerUtils.jsonResponse(res, {});
  26. });
  27. app.router.get("/api/access/list", (req, res) => {
  28. app.routerUtils.onApiRequest(req, res);
  29. app.routerUtils.jsonResponse(res, req.sessionObj?.accessList || {});
  30. });
  31. app.router.post("/api/access/link", async (req, res) => { // /api/access/link, post: { linkIds: [string] (JSON) }
  32. app.routerUtils.onApiRequest(req, res);
  33. if (!req.post?.linkIds?.length)
  34. return app.routerUtils.httpResponse(res, 400, "Missing argument");
  35. try {
  36. for (let i of JSON.parse(req.post.linkIds)) {
  37. const access = await app.databaseHelper.findOne(AccessModel, { type: ACCESS_TYPE.link, typeData: i });
  38. if (access) {
  39. Security.addLinkToSession(req, access.id, i);
  40. if (access.accessTo == ACCESS_TO.admin)
  41. Security.setAdmin(req, true);
  42. }
  43. }
  44. }
  45. catch (err) {
  46. console.error(err);
  47. return app.routerUtils.onBadRequest(res);
  48. }
  49. app.routerUtils.jsonResponse(res, req.sessionObj.accessList);
  50. });
  51. app.router.del("/api/access/:id", async (req, res) => {
  52. app.routerUtils.onApiRequest(req, res);
  53. Security.removeFromSession(req, req.params.id);
  54. 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 });
  55. const result = Security.setAdmin(req, !!(access?.length || 0));
  56. app.routerUtils.jsonResponse(res, result);
  57. });
  58. app.router.get("/api/accessAdmin/list", async (req, res) => {
  59. app.routerUtils.onApiRequest(req, res);
  60. if (!req.sessionObj?.accessList?.isAdmin)
  61. return app.routerUtils.onBadRequest(res);
  62. const access = await app.databaseHelper.fetch(AccessModel);
  63. app.routerUtils.jsonResponse(res, access.map(i => {
  64. const typeStr = [ "unknown", "ldapAccount", "email", "link", "every one" ][i.type];
  65. const accessToStr = [ "unknown", "item", "tag", "meta", "everything", "admin"][i.accessTo];
  66. const grantStr = [ "none", "read", "write", "read without meta"][i.grant];
  67. return {
  68. id: i.id,
  69. type: typeStr,
  70. typeData: i.typeData,
  71. accessTo: accessToStr,
  72. accessToData: i.accessToData,
  73. grant: grantStr
  74. };
  75. }));
  76. });
  77. app.router.post("/api/media/:id/tag/del/:tag", async (req, res) => {
  78. app.routerUtils.onApiRequest(req, res);
  79. if (!req.params.id ||!req.params.tag)
  80. return app.routerUtils.onBadRequest(res);
  81. let checksum = [ req.params.id ];
  82. if (req.params.id === "list") {
  83. if (!req.body?.['list[]'])
  84. return app.routerUtils.onBadRequest(res);
  85. checksum = req.body['list[]'];
  86. }
  87. let data = await MediaService.fetchMultiple(app, checksum, req.sessionObj?.accessList, 0);
  88. data = Object.keys(data).map(x => data[x]).filter(x => x.ACCESS_TYPE != ACCESS_GRANT.write);
  89. await Promise.all(data.map(x => MediaService.updateVersionInDb(app, x.fixedSum)));
  90. await app.databaseHelper.remove(MediaFileTagModel, { md5sum: data.map(x => x.fixedSum), tag: decodeURIComponent(req.params.tag), fromMeta: 0 });
  91. const allMedias = await MediaService.fetchMultiple(app, checksum, req.sessionObj?.accessList, 0);
  92. app.routerUtils.jsonResponse(res, Object.keys(allMedias).map(x => allMedias[x]).map(x => MediaToJson(x)));
  93. });
  94. app.router.put("/api/media/:id/tag", async (req, res) => {
  95. app.routerUtils.onApiRequest(req, res);
  96. const requestedTag = req.body?.tag;
  97. if (!req.params.id ||!requestedTag)
  98. return app.routerUtils.onBadRequest(res);
  99. let checksum = [ req.params.id ];
  100. if (req.params.id === "list") {
  101. if (!req.body?.['list[]'])
  102. return app.routerUtils.onBadRequest(res);
  103. checksum = req.body['list[]'];
  104. }
  105. let data = await MediaService.fetchMultiple(app, checksum, req.sessionObj?.accessList, 0);
  106. data = Object.keys(data)
  107. .map(x => data[x])
  108. .filter(x => {
  109. if (x.ACCESS_TYPE != ACCESS_GRANT.write)
  110. return true;
  111. for (let existingTag of [...x.tags, ...x.fixedTags]) {
  112. if (existingTag === requestedTag || existingTag.startsWith(`${requestedTag}/`)) {
  113. return true;
  114. }
  115. }
  116. });
  117. await Promise.all(data.map(x => MediaService.updateVersionInDb(app, x.fixedSum)));
  118. let tag = data.map(x => new MediaFileTagModel(x.fixedSum, requestedTag, false));
  119. await app.databaseHelper.insertMultipleSameTable(tag);
  120. const allMedias = await MediaService.fetchMultiple(app, checksum, req.sessionObj?.accessList, 0);
  121. app.routerUtils.jsonResponse(res, Object.keys(allMedias).map(x => allMedias[x]).map(x => MediaToJson(x)));
  122. });
  123. app.router.patch("/api/media/:id/meta/:key", async (req, res) => {
  124. app.routerUtils.onApiRequest(req, res);
  125. if (!req.params.id ||!req.params.key || !Number.isInteger(req.body?.value?.length))
  126. return app.routerUtils.onBadRequest(res);
  127. let checksum = [ req.params.id ];
  128. if (req.params.id === "list") {
  129. if (!req.body?.['list[]'])
  130. return app.routerUtils.onBadRequest(res);
  131. checksum = req.body['list[]'];
  132. }
  133. let data = await MediaService.fetchMultiple(app, checksum, req.sessionObj?.accessList, 0);
  134. data = Object.keys(data)
  135. .map(x => data[x])
  136. .filter(x => x.ACCESS_TYPE != ACCESS_GRANT.write);
  137. await Promise.all(data.map(x => MediaService.updateVersionInDb(app, x.fixedSum)));
  138. if (!req.body.value) {
  139. await app.databaseHelper.remove(MediaFileMetaModel, { md5sum: data.map(x => x.fixedSum), key: req.params.key, fromFile: 0 });
  140. } else {
  141. let newMediaItemMedia = data.map(x => new MediaFileMetaModel(x.fixedSum, req.params.key, req.body.value, false));
  142. await app.databaseHelper.remove(MediaFileMetaModel, { md5sum: data.map(x => x.fixedSum), key: req.params.key, fromFile: 0 });
  143. await app.databaseHelper.insertMultipleSameTable(newMediaItemMedia);
  144. }
  145. const allMedias = await MediaService.fetchMultiple(app, checksum, req.sessionObj?.accessList);
  146. app.routerUtils.jsonResponse(res, Object.keys(allMedias).map(x => allMedias[x]).map(x => MediaToJson(x)));
  147. });
  148. app.router.get("/api/media/list", async (req, res) => {
  149. app.routerUtils.onApiRequest(req, res);
  150. let first = undefined,
  151. last = undefined,
  152. maxVersion = undefined;
  153. if (req.body?.chronology !== undefined) {
  154. let range = await MediaService.getMediaRange(app);
  155. first = range.min;
  156. last = range.max;
  157. maxVersion = range.maxVersion;
  158. }
  159. let fromDate = parseInt(req.body?.from);
  160. let count = parseInt(req.body?.count);
  161. app.routerUtils.jsonResponse(res, {
  162. data: (await MediaService.fetchMediasWithAccess(
  163. app,
  164. isNaN(fromDate) ? 0 : fromDate,
  165. isNaN(count) ? 25 : Math.min(350, count),
  166. req.sessionObj?.accessList,
  167. req.body?.version || 0)).map(MediaToJson),
  168. first: first,
  169. last: last,
  170. maxVersion: maxVersion
  171. });
  172. });
  173. app.router.get("/api/media/sumlist", async (req, res) => {
  174. app.routerUtils.onApiRequest(req, res);
  175. app.routerUtils.jsonResponse(res, {
  176. data: await MediaService.fetchMediasSumWithAccess(
  177. app,
  178. req.sessionObj?.accessList)
  179. });
  180. });
  181. app.router.get("/api/media/:md5sum", async (req, res) => {
  182. app.routerUtils.onApiRequest(req, res);
  183. let data = MediaToJson(await MediaService.fetchOne(app, req.params.md5sum, req.sessionObj?.accessList, 0));
  184. if (!data)
  185. return app.routerUtils.onPageNotFound(res);
  186. app.routerUtils.jsonResponse(res, data);
  187. });
  188. app.router.get("/api/media/thumbnail/:md5sum.jpg", async (req, res) => {
  189. app.routerUtils.onApiRequest(req, res);
  190. let data = await MediaService.fetchOne(app, req.params.md5sum, req.sessionObj?.accessList, 0);
  191. if (!data)
  192. return app.routerUtils.onPageNotFound(res);
  193. try {
  194. let thumbnail = null;
  195. req.body = req.body || {};
  196. req.body.w = parseInt(req.body.w || 0);
  197. req.body.h = parseInt(req.body.h || 0);
  198. req.body.q = parseInt(req.body.q || 6);
  199. try {
  200. thumbnail = await (await app.libraryManager.findMedia(data.path))?.createThumbnail(req.body.w, req.body.h, req.body.q);
  201. } catch (err) {
  202. return app.routerUtils.apiError(res);
  203. }
  204. if (!thumbnail)
  205. return app.routerUtils.onPageNotFound(res);
  206. res.setHeader("Content-Type", "image/jpeg");
  207. res.setHeader("Content-Length", fs.statSync(thumbnail.name)?.size || undefined);
  208. res.setHeader("Cache-Control", "private, max-age=2630000"); // 1 month cache
  209. let rd = fs.createReadStream(thumbnail.name);
  210. rd.once('end', () => thumbnail.removeCallback());
  211. rd.pipe(res);
  212. }
  213. catch (err) {
  214. console.error(err);
  215. app.routerUtils.onPageNotFound(res);
  216. }
  217. });
  218. app.router.get("/api/media/original/:md5sum", async (req, res) => {
  219. app.routerUtils.onApiRequest(req, res);
  220. let data = await MediaService.fetchOne(app, req.params.md5sum, req.sessionObj?.accessList, 0);
  221. if (!data)
  222. return app.routerUtils.onPageNotFound(res);
  223. const fileName = Path.basename(data.path);
  224. res.setHeader("Cache-Control", "private, max-age=2630000"); // 1 month cache
  225. if (data.accessType === ACCESS_GRANT.readNoMeta || req.body?.trim !== undefined) {
  226. console.log("remove meta");//-> trim metadata
  227. }
  228. res.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
  229. res.setHeader("Content-Type", mime.lookup(data.path));
  230. res.setHeader("Content-Length", fs.statSync(data.path)?.size || undefined);
  231. fs.createReadStream(data.path).pipe(res);
  232. });
  233. }};