1
0

api.js 14 KB

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