From 448c16650764cfc15ec20c6706ce04ecc634dbfc Mon Sep 17 00:00:00 2001 From: jedi Date: Sat, 31 Aug 2024 02:50:08 +0200 Subject: [PATCH] add item images --- backend/.env.dist | 4 +- backend/backend/settings.py | 2 + backend/files/media_urls.py | 22 +- deploy/dev/instance_a/a.env | 2 + deploy/dev/instance_a/nginx-a.dev.conf | 10 + deploy/dev/instance_b/b.env | 2 + .../src/components/AuthenticatedImage.vue | 53 +++++ frontend/src/components/CameraFileSource.vue | 76 +++++++ frontend/src/components/CombinedFileField.vue | 193 +++++++++++++++++ frontend/src/components/DeletableWrapper.vue | 36 ++++ .../src/components/DragDropFileSource.vue | 88 ++++++++ frontend/src/components/FsFileSource.vue | 59 +++++ frontend/src/components/WebcamFileSource.vue | 201 ++++++++++++++++++ frontend/src/store.js | 37 ++++ frontend/src/views/Inventory.vue | 26 ++- frontend/src/views/InventoryDetail.vue | 13 ++ frontend/src/views/InventoryEdit.vue | 10 + frontend/src/views/InventoryNew.vue | 7 + 18 files changed, 826 insertions(+), 15 deletions(-) create mode 100644 frontend/src/components/AuthenticatedImage.vue create mode 100644 frontend/src/components/CameraFileSource.vue create mode 100644 frontend/src/components/CombinedFileField.vue create mode 100644 frontend/src/components/DeletableWrapper.vue create mode 100644 frontend/src/components/DragDropFileSource.vue create mode 100644 frontend/src/components/FsFileSource.vue create mode 100644 frontend/src/components/WebcamFileSource.vue diff --git a/backend/.env.dist b/backend/.env.dist index 24a8a42..4cf4790 100644 --- a/backend/.env.dist +++ b/backend/.env.dist @@ -1,2 +1,4 @@ -ALLOWED_HOSTS="localhost,127.0.0.1" \ No newline at end of file +ALLOWED_HOSTS="localhost,127.0.0.1" + +SERVE_X_ACCEL_REDIRECT=True \ No newline at end of file diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 82f2e67..1d09378 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -86,6 +86,8 @@ CORS_ALLOW_ALL_ORIGINS = True USE_X_FORWARDED_HOST = True SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +SERVE_X_ACCEL_REDIRECT = os.environ.get('SERVE_X_ACCEL_REDIRECT', 'False').lower() == 'true' + ROOT_URLCONF = 'backend.urls' TEMPLATES = [ diff --git a/backend/files/media_urls.py b/backend/files/media_urls.py index aca590d..b87ab00 100644 --- a/backend/files/media_urls.py +++ b/backend/files/media_urls.py @@ -7,6 +7,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from authentication.signature_auth import SignatureAuthentication +from backend import settings from files.models import File @@ -19,12 +20,21 @@ def media_urls(request, hash_path): file = File.objects.filter(connected_items__owner__in=request.user.friends_or_self()).distinct().get( file=hash_path) - return HttpResponse(status=status.HTTP_200_OK, - content_type=file.mime_type, - headers={ - 'X-Accel-Redirect': f'/redirect_media/{hash_path}', - 'Access-Control-Allow-Origin': '*', - }) # TODO Expires and Cache-Control + if settings.SERVE_X_ACCEL_REDIRECT: + return HttpResponse(status=status.HTTP_200_OK, + content_type=file.mime_type, + headers={ + 'X-Accel-Redirect': f'/redirect_media/{hash_path}', + 'Access-Control-Allow-Origin': '*', + }) # TODO Expires and Cache-Control + else: + return HttpResponse(status=status.HTTP_200_OK, + content_type=file.mime_type, + headers={ + 'Access-Control-Allow-Origin': '*', + }, + content=open(file.file.path, 'rb').read()) + except File.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) diff --git a/deploy/dev/instance_a/a.env b/deploy/dev/instance_a/a.env index 33c5b7e..5816f2a 100644 --- a/deploy/dev/instance_a/a.env +++ b/deploy/dev/instance_a/a.env @@ -2,6 +2,8 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG=True +SERVE_X_ACCEL_REDIRECT=False + # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY='e*lm&*!j0_stqaiod$1zob(vs@aq6+n-i$1%!rek)_v9n^ue$3' diff --git a/deploy/dev/instance_a/nginx-a.dev.conf b/deploy/dev/instance_a/nginx-a.dev.conf index b77f550..37f4f1b 100644 --- a/deploy/dev/instance_a/nginx-a.dev.conf +++ b/deploy/dev/instance_a/nginx-a.dev.conf @@ -45,6 +45,16 @@ http { proxy_pass http://backend; } + location /media { + proxy_set_header Host $host:$server_port; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host:$server_port; + proxy_set_header X-Forwarded-Port $server_port; + proxy_pass http://backend; + } + location /docs { proxy_pass http://backend/docs; } diff --git a/deploy/dev/instance_b/b.env b/deploy/dev/instance_b/b.env index c0118ca..76e8087 100644 --- a/deploy/dev/instance_b/b.env +++ b/deploy/dev/instance_b/b.env @@ -1,6 +1,8 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG=True +SERVE_X_ACCEL_REDIRECT=True + # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY='7ccxjje%q@@0*z+r&-$fy3(rj9n)%$!sk-k++-&rb=_u(wpjbe' diff --git a/frontend/src/components/AuthenticatedImage.vue b/frontend/src/components/AuthenticatedImage.vue new file mode 100644 index 0000000..5f3f0f3 --- /dev/null +++ b/frontend/src/components/AuthenticatedImage.vue @@ -0,0 +1,53 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/CameraFileSource.vue b/frontend/src/components/CameraFileSource.vue new file mode 100644 index 0000000..5a93a89 --- /dev/null +++ b/frontend/src/components/CameraFileSource.vue @@ -0,0 +1,76 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/CombinedFileField.vue b/frontend/src/components/CombinedFileField.vue new file mode 100644 index 0000000..c5430b0 --- /dev/null +++ b/frontend/src/components/CombinedFileField.vue @@ -0,0 +1,193 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/DeletableWrapper.vue b/frontend/src/components/DeletableWrapper.vue new file mode 100644 index 0000000..d357a01 --- /dev/null +++ b/frontend/src/components/DeletableWrapper.vue @@ -0,0 +1,36 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/DragDropFileSource.vue b/frontend/src/components/DragDropFileSource.vue new file mode 100644 index 0000000..6646ef5 --- /dev/null +++ b/frontend/src/components/DragDropFileSource.vue @@ -0,0 +1,88 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/FsFileSource.vue b/frontend/src/components/FsFileSource.vue new file mode 100644 index 0000000..29bff9a --- /dev/null +++ b/frontend/src/components/FsFileSource.vue @@ -0,0 +1,59 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/WebcamFileSource.vue b/frontend/src/components/WebcamFileSource.vue new file mode 100644 index 0000000..3605a79 --- /dev/null +++ b/frontend/src/components/WebcamFileSource.vue @@ -0,0 +1,201 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/store.js b/frontend/src/store.js index 0cadaff..b0f43e5 100644 --- a/frontend/src/store.js +++ b/frontend/src/store.js @@ -77,6 +77,9 @@ export default createStore({ setDomains(state, domains) { state.domains = domains; }, + setFiles(state, files) { + state.files = files; + }, logout(state) { state.user = null; state.token = null; @@ -232,6 +235,40 @@ export default createStore({ const servers = await dispatch('getHomeServers') return await servers.delete(getters.signAuth, '/api/friends/' + id + '/') }, + async fetchFiles({state, commit, dispatch, getters}) { + if (state.last_load.files > Date.now() - 1000 * 60 * 60 * 24) { + return state.files + } + const servers = await dispatch('getHomeServers') + const data = await servers.get(getters.signAuth, '/api/files/') + data.map(file => file.owner = state.user) + commit('setFiles', data) + state.last_load.files = Date.now() + return data + }, + async fetchFilesByItem({state, commit, dispatch, getters}, {id}) { + const servers = await dispatch('getHomeServers') + return await servers.get(getters.signAuth, '/api/item_files/' + id + '/') + }, + async pushFile({state, dispatch, getters}, {item_id, file}) { + const servers = await dispatch('getHomeServers') + const data = await servers.post(getters.signAuth, '/api/item_files/' + item_id + '/', file) + if (data.name) { + data.owner = state.user + state.files.push(data) + return data + } + }, + async deleteFile({state, dispatch, getters}, {id}) { + const servers = await dispatch('getHomeServers') + await servers.delete(getters.signAuth, '/api/files/' + id + '/') + state.files = state.files.filter(file => file.id !== id) + }, + async deleteItemFile({state, dispatch, getters}, {item_id, file_id}) { + const servers = await dispatch('getHomeServers') + await servers.delete(getters.signAuth, '/api/item_files/' + item_id + '/' + file_id + '/') + state.files = state.files.filter(file => file.id !== file_id) + }, async fetchTags({state, commit, dispatch, getters}) { if (state.last_load.tags > Date.now() - 1000 * 60 * 60 * 24) { return state.tags diff --git a/frontend/src/views/Inventory.vue b/frontend/src/views/Inventory.vue index 8687fa6..da488b9 100644 --- a/frontend/src/views/Inventory.vue +++ b/frontend/src/views/Inventory.vue @@ -48,24 +48,27 @@
- + +
-
{{ item.name }}
+
+ + {{ item.name }} +
{{ item.availability_policy }} {{ item.owned_quantity }}
- +
@@ -89,6 +92,7 @@ import {mapActions, mapGetters, mapMutations, mapState} from "vuex"; import * as BIcons from "bootstrap-icons-vue"; import BaseLayout from "@/components/BaseLayout.vue"; +import AuthenticatedImage from "../components/AuthenticatedImage.vue"; export default { name: "Inventory", @@ -98,6 +102,7 @@ export default { } }, components: { + AuthenticatedImage, BaseLayout, ...BIcons }, @@ -107,6 +112,9 @@ export default { }, methods: { ...mapActions(["fetchInventoryItems", "deleteInventoryItem"]), + only_images(files) { + return files.filter(file => file.mime_type.startsWith("image/")); + }, }, async mounted() { await this.fetchInventoryItems() @@ -115,5 +123,7 @@ export default { \ No newline at end of file diff --git a/frontend/src/views/InventoryDetail.vue b/frontend/src/views/InventoryDetail.vue index ab8910b..9c55894 100644 --- a/frontend/src/views/InventoryDetail.vue +++ b/frontend/src/views/InventoryDetail.vue @@ -26,6 +26,14 @@ {{ item.owned_quantity }}
+
+ +
+ +
+ +
@@ -50,10 +58,15 @@ import * as BIcons from "bootstrap-icons-vue"; import BaseLayout from "@/components/BaseLayout.vue"; import {mapActions, mapGetters} from "vuex"; +import AuthenticatedImage from "@/components/AuthenticatedImage.vue"; +import PropertyField from "../components/PropertyField.vue"; +import TagField from "../components/TagField.vue"; export default { name: "InventoryDetail", components: { + AuthenticatedImage, + PropertyField, TagField, BaseLayout, ...BIcons }, diff --git a/frontend/src/views/InventoryEdit.vue b/frontend/src/views/InventoryEdit.vue index 4e7220a..06163b1 100644 --- a/frontend/src/views/InventoryEdit.vue +++ b/frontend/src/views/InventoryEdit.vue @@ -39,6 +39,11 @@
+
+ + +
+
+ + +