From 4c4bfeb340936135a823063466436f1dd274d8bb Mon Sep 17 00:00:00 2001 From: Tovi Jaeschke-Rogers Date: Sat, 9 Apr 2022 13:23:47 +0930 Subject: [PATCH] Add file upload and delete capabilities to admin post form --- Api/PostImages.go | 9 +- Api/Routes.go | 2 +- Frontend/Routes.go | 7 +- Frontend/vue/package-lock.json | 20 +++ Frontend/vue/package.json | 1 + Frontend/vue/src/assets/css/admin.css | 22 +++ .../admin/views/posts/AdminPostsForm.vue | 143 +++++++++++++++++- Frontend/vue/src/main.js | 6 +- Models/Posts.go | 9 +- Util/Files.go | 14 +- 10 files changed, 211 insertions(+), 22 deletions(-) diff --git a/Api/PostImages.go b/Api/PostImages.go index dea0442..ea1145b 100644 --- a/Api/PostImages.go +++ b/Api/PostImages.go @@ -74,10 +74,11 @@ func createPostImage(w http.ResponseWriter, r *http.Request) { } postImage = Models.PostImage{ - PostID: postUUID, - Filepath: fileObject.Filepath, - Mimetype: fileObject.Mimetype, - Size: fileObject.Size, + PostID: postUUID, + Filepath: fileObject.Filepath, + PublicFilepath: fileObject.PublicFilepath, + Mimetype: fileObject.Mimetype, + Size: fileObject.Size, } err = Database.CreatePostImage(&postImage) diff --git a/Api/Routes.go b/Api/Routes.go index edee089..761ad2e 100644 --- a/Api/Routes.go +++ b/Api/Routes.go @@ -46,5 +46,5 @@ func InitApiEndpoints(router *mux.Router) { api.HandleFunc("/admin/logout", Auth.Logout).Methods("GET") api.HandleFunc("/admin/me", Auth.Me).Methods("GET") - //router.PathPrefix("/").Handler(http.StripPrefix("/images/", http.FileServer(http.Dir("./uploads")))) + // router.PathPrefix("/").Handler(http.StripPrefix("/images/", http.FileServer(http.Dir("./uploads")))) } diff --git a/Frontend/Routes.go b/Frontend/Routes.go index 3ce773a..9124c2f 100644 --- a/Frontend/Routes.go +++ b/Frontend/Routes.go @@ -46,6 +46,11 @@ func InitFrontendRoutes(router *mux.Router) { HandlerFunc(indexHandler(indexPath)) } - router.PathPrefix("/").Handler(frontendFS) + router.PathPrefix("/public/"). + Handler(http.StripPrefix( + "/public/", + http.FileServer(http.Dir("./Frontend/public/")), + )) + router.PathPrefix("/").Handler(frontendFS) } diff --git a/Frontend/vue/package-lock.json b/Frontend/vue/package-lock.json index 55af1f8..c769c9f 100644 --- a/Frontend/vue/package-lock.json +++ b/Frontend/vue/package-lock.json @@ -20,6 +20,7 @@ "@vuepic/vue-datepicker": "^3.0.0", "axios": "^0.26.1", "bootstrap": "^5.1.3", + "bootstrap-vue-3": "^0.1.10", "core-js": "^3.8.3", "vee-validate": "^4.5.10", "vue": "^3.2.13", @@ -4131,6 +4132,17 @@ "@popperjs/core": "^2.10.2" } }, + "node_modules/bootstrap-vue-3": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/bootstrap-vue-3/-/bootstrap-vue-3-0.1.10.tgz", + "integrity": "sha512-r5zd5DIzclFpR16s6nwFRkZlrLoTANbZ9OWFFGoKLGcHOnL+WFuR8HULUB5QEyKdH5mf9ltTBCEsX/mFRq2S1w==", + "dependencies": { + "core-js": "3.x.x" + }, + "peerDependencies": { + "bootstrap": "5.x.x" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -15455,6 +15467,14 @@ "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==", "requires": {} }, + "bootstrap-vue-3": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/bootstrap-vue-3/-/bootstrap-vue-3-0.1.10.tgz", + "integrity": "sha512-r5zd5DIzclFpR16s6nwFRkZlrLoTANbZ9OWFFGoKLGcHOnL+WFuR8HULUB5QEyKdH5mf9ltTBCEsX/mFRq2S1w==", + "requires": { + "core-js": "3.x.x" + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", diff --git a/Frontend/vue/package.json b/Frontend/vue/package.json index 83b5906..2a0a3ec 100644 --- a/Frontend/vue/package.json +++ b/Frontend/vue/package.json @@ -21,6 +21,7 @@ "@vuepic/vue-datepicker": "^3.0.0", "axios": "^0.26.1", "bootstrap": "^5.1.3", + "bootstrap-vue-3": "^0.1.10", "core-js": "^3.8.3", "vee-validate": "^4.5.10", "vue": "^3.2.13", diff --git a/Frontend/vue/src/assets/css/admin.css b/Frontend/vue/src/assets/css/admin.css index 674c6e3..3824e01 100644 --- a/Frontend/vue/src/assets/css/admin.css +++ b/Frontend/vue/src/assets/css/admin.css @@ -133,3 +133,25 @@ label[role=alert] { .ProseMirror p:last-child { margin-bottom: 0; } + +.image-button-overlay { + position: relative; + top: 3rem; + z-index: 3; + width: 100%; + text-align: right; + padding-right: 1rem; +} + +.image-delete { + color: var(--bs-danger); + z-index: 3; + font-size: 1.6rem; + height: 2rem; + width: 2rem; + border-radius: 50%; +} + +.image-delete:hover { + background-color: white; +} diff --git a/Frontend/vue/src/components/admin/views/posts/AdminPostsForm.vue b/Frontend/vue/src/components/admin/views/posts/AdminPostsForm.vue index 13eb3e7..a3a5dd2 100644 --- a/Frontend/vue/src/components/admin/views/posts/AdminPostsForm.vue +++ b/Frontend/vue/src/components/admin/views/posts/AdminPostsForm.vue @@ -14,18 +14,26 @@ > Post Details + -
-
+
+

Update Post

-
+
-
+
- +
+ +
+
+ + +
+
+
@@ -117,6 +138,64 @@
+ +
+
+

Upload Images

+
+ +
+ +
+ +
+
+
+ + +
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+
+
+ +
+

Empty

+
+ +
+ +
+ +
+
+
@@ -131,6 +210,8 @@ export default { return { tab: 'details', post: {}, + images: [], + imageLabel: 'Choose File', } }, @@ -203,6 +284,58 @@ export default { } catch (error) { this.$toast.error('An error occured'); } + }, + + async onUpload(event) { + let fd = new FormData() + + let photos = event.target[0].files + + if (photos.length === 0) { + alert('No Files') + return + } + + for (let i = 0; i < photos.length; i++) { + fd.append('files', photos[i]) + } + + let response = await this.axios.post( + `/admin/post/${this.$route.params.id}/image`, + fd, + { + headers: { + 'Content-Type': 'multipart/form-data', + } + } + ) + + if (response.status === 200) { + this.post = response.data + this.clearFiles() + } + + }, + + clearFiles () { + document.getElementById("image-input").value=null; + }, + + async deleteImage(id) { + let response = await this.axios.delete( + `/admin/post/${this.$route.params.id}/image/${id}`, + ) + + if (response.status === 200) { + this.clearFiles() + + + const indexOfObject = this.post.images.findIndex(object => { + return object.id === id; + }); + + this.post.images.splice(indexOfObject, 1); + } } } } diff --git a/Frontend/vue/src/main.js b/Frontend/vue/src/main.js index 6a3d7c3..66c6a54 100644 --- a/Frontend/vue/src/main.js +++ b/Frontend/vue/src/main.js @@ -6,6 +6,7 @@ import { defineRule } from 'vee-validate'; import AllRules from '@vee-validate/rules'; import Toaster from "@meforma/vue-toaster"; import Datepicker from '@vuepic/vue-datepicker'; +import BootstrapVue from 'bootstrap-vue-3' import { library } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; @@ -25,7 +26,9 @@ import admin from './store/admin/index.js' import 'bootstrap/dist/css/bootstrap.min.css' import 'bootstrap/dist/js/bootstrap.bundle.js' -// Import the CSS or use your own! +import "bootstrap-vue-3/dist/bootstrap-vue-3.css" +import "bootstrap-vue-3/dist/bootstrap-vue-3.es.js" +import "bootstrap-vue-3/dist/bootstrap-vue-3.umd.js" import '@vuepic/vue-datepicker/dist/main.css' import './assets/css/admin.css' @@ -38,6 +41,7 @@ app.use(VueAxios, axios) app.use(VueCookies) app.use(admin) app.use(Toaster, { position: 'top-right' }) +app.use(BootstrapVue) Object.keys(AllRules).forEach(rule => { defineRule(rule, AllRules[rule]); diff --git a/Models/Posts.go b/Models/Posts.go index ca16879..c33ea40 100644 --- a/Models/Posts.go +++ b/Models/Posts.go @@ -40,10 +40,11 @@ type PostLink struct { type PostImage struct { Base - PostID uuid.UUID `gorm:"type:uuid;column:post_id;not null;" json:"post_id"` - Filepath string `gorm:"not null" json:"filepath"` - Mimetype string `gorm:"not null" json:"mimetype"` - Size int64 `gorm:"not null"` + PostID uuid.UUID `gorm:"type:uuid;column:post_id;not null;" json:"post_id"` + Filepath string `gorm:"not null" json:"-"` + PublicFilepath string `gorm:"not null" json:"filepath"` + Mimetype string `gorm:"not null" json:"mimetype"` + Size int64 `gorm:"not null"` } type PostVideo struct { diff --git a/Util/Files.go b/Util/Files.go index b0f208e..871b101 100644 --- a/Util/Files.go +++ b/Util/Files.go @@ -11,9 +11,10 @@ import ( ) type FileObject struct { - Filepath string - Mimetype string - Size int64 + Filepath string + PublicFilepath string + Mimetype string + Size int64 } func WriteFile(fileBytes []byte, acceptedMime string) (FileObject, error) { @@ -58,9 +59,10 @@ func WriteFile(fileBytes []byte, acceptedMime string) (FileObject, error) { } fileObject = FileObject{ - Filepath: file.Name(), - Mimetype: mime.String(), - Size: fi.Size(), + Filepath: file.Name(), + PublicFilepath: strings.ReplaceAll(file.Name(), "./Frontend", ""), + Mimetype: mime.String(), + Size: fi.Size(), } return fileObject, err