From 09f70b6e03604ba68f4d7587f2954a5437c55eed Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Fri, 13 Sep 2024 15:30:09 -0700 Subject: [PATCH] Adding photos from sites and better rendering --- src/api/routes/photo-router.ts | 26 +++ src/api/routes/place-router.ts | 35 ++- src/web/src/apis/places-api.js | 13 ++ .../components/Sites/site-forms/Photos.vue | 205 +++++++++++------- src/web/src/store/places.js | 40 ++++ 5 files changed, 240 insertions(+), 79 deletions(-) diff --git a/src/api/routes/photo-router.ts b/src/api/routes/photo-router.ts index d249069b..b4fb1ddc 100644 --- a/src/api/routes/photo-router.ts +++ b/src/api/routes/photo-router.ts @@ -129,6 +129,32 @@ photoRouter.get( } ); +photoRouter.get( + '/:id/file/download', + [check('id').notEmpty().isUUID()], + async (req: Request, res: Response) => { + const errors = validationResult(req); + + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + + await photoService + .getFileById(req.params.id) + .then((photo) => { + if (photo && photo.file) { + return res.contentType('image/jpg').send(photo.file); + } + + return res.status(404).send('Photo not found'); + }) + .catch((err) => { + console.error(err); + return res.status(404).send('Photo not found'); + }); + } +); + photoRouter.get( '/:id/thumbfile', [check('id').notEmpty().isUUID()], diff --git a/src/api/routes/place-router.ts b/src/api/routes/place-router.ts index 386f536b..cd851d4a 100644 --- a/src/api/routes/place-router.ts +++ b/src/api/routes/place-router.ts @@ -8,17 +8,19 @@ import { matchedData, } from 'express-validator'; import fs from 'fs'; +import multer from 'multer'; import { create } from 'handlebars'; import handlebarsHelpers from '../utils/handlebars-helpers'; import { API_PORT, DB_CONFIG } from '../config'; -import { PlaceService } from '../services'; +import { PhotoService, PlaceService } from '../services'; import { ReturnValidationErrors } from '../middleware'; import { authorize } from '../middleware/authorization'; import { Place, User, UserRoles } from '../models'; import PlacesController from '../controllers/places-controller'; import { PlacePolicy } from '../policies'; import { generatePDF } from '../utils/pdf-generator'; +import { createThumbnail } from '../utils/image'; const placeService = new PlaceService(DB_CONFIG); const PAGE_SIZE = 10; @@ -218,6 +220,37 @@ placeRouter.post( } ); +placeRouter.post( + '/:id/photo', + authorize([ + UserRoles.SITE_ADMIN, + UserRoles.SITE_EDITOR, + UserRoles.ADMINISTRATOR, + ]), + multer().single('file'), + async (req: Request, res: Response) => { + try { + const { id } = req.params; + + const ThumbFile = await createThumbnail(req.file.buffer); + const body = { + File: req.file.buffer, + ThumbFile, + ...req.body, + placeId: id, + dateCreated: new Date(), + }; + + const photoService = new PhotoService(DB_CONFIG); + photoService.addPhoto(body); + + return res.json({ data: 'success' }); + } catch (err) { + return res.json({ data: 'failuer', error: err }); + } + } +); + placeRouter.patch( '/:id', authorize([UserRoles.SITE_ADMIN, UserRoles.ADMINISTRATOR]), diff --git a/src/web/src/apis/places-api.js b/src/web/src/apis/places-api.js index ecb2fafb..da5d24b9 100644 --- a/src/web/src/apis/places-api.js +++ b/src/web/src/apis/places-api.js @@ -48,4 +48,17 @@ export default { return Promise.reject(error); }); }, + uploadPhoto(id, data) { + return http + .post(`${placeUrl}/${id}/photo`, data, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + .then((response) => response.data) + .catch((error) => { + console.error(error); + return Promise.reject(error); + }); + }, }; diff --git a/src/web/src/components/Sites/site-forms/Photos.vue b/src/web/src/components/Sites/site-forms/Photos.vue index 175de5a3..8070410e 100644 --- a/src/web/src/components/Sites/site-forms/Photos.vue +++ b/src/web/src/components/Sites/site-forms/Photos.vue @@ -39,84 +39,120 @@ - - - +
+ + - - - + + + + + + + + + + + - mdi-close - - - - - - - - - - - - - + /> + + + +
+ + mdi-download + + + mdi-open-in-new + + + + + +
+ + @@ -151,6 +187,7 @@ import axios from 'axios'; import store from '@/store'; import { PLACE_URL, PHOTO_URL } from '@/urls'; +import { mapActions } from 'vuex'; /* Important**, field data that was not found on the swaggerhub api docs provided was assumed to be in development, hence, some placeholder variables were created. */ export default { @@ -185,6 +222,7 @@ export default { .catch((error) => console.error(error)); }, methods: { + ...mapActions('places', ['savePhotos']), addPhoto() { this.photos.push({}); }, @@ -194,13 +232,13 @@ export default { onFileSelection(event, i) { if (event) { //this.fields.photos[i].img = URL.createObjectURL(event.target.files[0]); - this.fields.photos[i].img = URL.createObjectURL(event); + this.photos[i].img = URL.createObjectURL(event); } else { - this.fields.photos[i].img = null; + this.photos[i].img = null; } }, - save() { - console.error('Not implemented'); + async save() { + await this.savePhotos(this.photos); }, makeThumbnailUrl(photo) { return `${PHOTO_URL}/${photo.rowId}/thumbfile`; @@ -208,6 +246,17 @@ export default { makePhotoUrl(photo) { return `${PHOTO_URL}/${photo.rowId}/thumbfile`; }, + + downloadPhoto(item) { + console.log(item); + window.open(`${PHOTO_URL}/${item.rowId}/file/download`); + }, + + openPhotoPage(item) { + localStorage.setItem('currentRowId', item.rowId); + this.$store.commit('photos/setRowId', item.rowId); + window.open(`/photos/view`); + }, }, }; diff --git a/src/web/src/store/places.js b/src/web/src/store/places.js index 0e2d308a..affd2a9a 100644 --- a/src/web/src/store/places.js +++ b/src/web/src/store/places.js @@ -114,5 +114,45 @@ export default { commit('setLoading', false); }); }, + + async savePhotos({ commit, state }, data) { + commit('setLoading', true); + + for (const photo of data) { + if (photo.rowId) continue; + + const formData = new FormData(); + formData.append('caption', photo.caption || ''); + formData.append('comments', photo.comments || ''); + formData.append('creditLine', photo.creditLine || ''); + formData.append('featureName', photo.featureName || ''); + formData.append('yhsiRecord', state.place.yHSIId); + formData.append('ntsMapNumber', state.place.nTSMapSheet); + formData.append('file', photo.file); + formData.append('location', photo.location || ''); + formData.append('communityId', state.place.communityId); + formData.append('isOtherRecord', false); + formData.append('originalMediaId', 1); // ditital + formData.append('mediaStorage', 4); // database + formData.append('copyright', 6); // incomplete + formData.append('ownerId', 1); // historic sites + formData.append('photoProjectId', 79); // none selected + formData.append('program', 4); // YHSI + formData.append('isComplete', false); + formData.append('rating', 1); + formData.append('isSiteDefault', false); + + await api + .uploadPhoto(state.place.id, formData) + .then((resp) => { + console.log(resp); + }) + .catch((err) => { + console.log('ERRROR', err); + }); + } + + commit('setLoading', false); + }, }, };