| @ -0,0 +1,55 @@ | |||||
| package Seeder | |||||
| import ( | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||||
| ) | |||||
| func createPost(userData Models.User) (Models.Post, error) { | |||||
| var ( | |||||
| postData Models.Post | |||||
| err error | |||||
| ) | |||||
| postData = Models.Post{ | |||||
| UserID: userData.ID, | |||||
| Title: "Test post", | |||||
| Content: "Test content", | |||||
| FrontPage: true, | |||||
| Order: 1, | |||||
| PostLinks: []Models.PostLink{ | |||||
| { | |||||
| Type: "Facebook", | |||||
| Link: "http://facebook.com/", | |||||
| }, | |||||
| }, | |||||
| } | |||||
| err = Database.CreatePost(&postData) | |||||
| return postData, err | |||||
| } | |||||
| func SeedPosts() { | |||||
| var ( | |||||
| userData Models.User | |||||
| i int | |||||
| err error | |||||
| ) | |||||
| err = Database.DB. | |||||
| Model(Models.User{}). | |||||
| First(&userData). | |||||
| Error | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | |||||
| for i = 0; i <= 20; i++ { | |||||
| _, err = createPost(userData) | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,61 @@ | |||||
| <template> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-12"> | |||||
| <div class="page-nav-container"> | |||||
| <div class="row"> | |||||
| <div class="col-md-6 col-10"> | |||||
| <div class="input-group"> | |||||
| <input | |||||
| type="text" | |||||
| class="form-control" | |||||
| placeholder="Search..." | |||||
| ref="search" | |||||
| > | |||||
| <div class="input-group-append"> | |||||
| <button | |||||
| class="btn btn-dark" | |||||
| type="button" | |||||
| @click="searchFunction" | |||||
| > | |||||
| Search | |||||
| </button> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-md-6 float-right col-2"> | |||||
| <div class="btn-group float-right" role="group"> | |||||
| <router-link :to="{ name: addNewTo }"> | |||||
| <button | |||||
| type="button" | |||||
| class="btn btn-rounded btn-dark d-none d-md-inline-block" | |||||
| > | |||||
| <i class="fa-solid fa-plus"></i> | |||||
| {{ addNewLabel }} | |||||
| </button> | |||||
| <button | |||||
| type="button" | |||||
| class="btn btn-rounded btn-dark d-inline-block d-md-none" | |||||
| > | |||||
| <i class="fa-solid fa-plus"></i> | |||||
| </button> | |||||
| </router-link> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| export default { | |||||
| props: { | |||||
| addNewTo: { type: String }, | |||||
| addNewLabel: { type: String }, | |||||
| searchFunction: { type: Function }, | |||||
| } | |||||
| } | |||||
| </script> | |||||
| @ -0,0 +1,156 @@ | |||||
| <template> | |||||
| <div id="admin-page-container"> | |||||
| <admin-navbar/> | |||||
| <section class="container mt-5"> | |||||
| <div class="row mb-3"> | |||||
| <div class="col-12"> | |||||
| <div class="page-nav-container"> | |||||
| <div class="btn-group" role="group"> | |||||
| <button | |||||
| type="button" | |||||
| class="btn btn-rounded" | |||||
| :class="tab === 'details' ? 'btn-dark' : 'btn-outline-dark'" | |||||
| @click="tab = 'details'" | |||||
| > | |||||
| Post Details | |||||
| </button> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="card shadow-2-strong card-registration"> | |||||
| <div class="card-body p-4 p-md-5" v-if="tab === 'details'"> | |||||
| <h3 class="mb-4 pb-2 pb-md-0 mb-md-5">Update Post</h3> | |||||
| <Form @submit="updatePost" v-slot="{ meta, errors }"> | |||||
| <div class="row"> | |||||
| <div class="col-md-8 mb-4"> | |||||
| <div class="form-outline"> | |||||
| <Field | |||||
| v-model="post.title" | |||||
| type="text" | |||||
| id="title" | |||||
| name="Title" | |||||
| class="form-control form-control-lg" | |||||
| :class="errors['Title'] ? 'invalid' : ''" | |||||
| rules="required"/> | |||||
| <label v-if="!errors['Title']" class="form-label" for="title">Title</label> | |||||
| <ErrorMessage name="Title" as="label" class="form-label" for="title"/> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-md-4 mb-4"> | |||||
| <div class="form-outline"> | |||||
| <select | |||||
| v-model="post.front_page" | |||||
| id="front_page" | |||||
| name="Front Page" | |||||
| class="form-control form-control-lg form-select"> | |||||
| <option :value="true">Yes</option> | |||||
| <option :value="false">No</option> | |||||
| </select> | |||||
| <label v-if="!errors['Front Page']" class="form-label" for="front_page">Front Page</label> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col-md-4 mb-4 pb-2"> | |||||
| <div class="form-outline"> | |||||
| <date-picker | |||||
| v-model="post.created_at" | |||||
| format="dd/MM/yyyy, HH:mm" | |||||
| disabled="disabled" | |||||
| id="created_at"/> | |||||
| <label class="form-label" for="created_at">Created At</label> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-md-4 mb-4 pb-2"> | |||||
| <div class="form-outline"> | |||||
| <date-picker | |||||
| v-model="post.updated_at" | |||||
| format="dd/MM/yyyy, HH:mm" | |||||
| disabled="disabled" | |||||
| id="updated_at"/> | |||||
| <label class="form-label" for="updated_at">Updated At</label> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="mt-2 pt-2 right-align"> | |||||
| <button class="btn btn-danger btn-md" type="button"> | |||||
| Delete | |||||
| </button> | |||||
| <button :disabled="!meta.touched || !meta.valid" class="btn btn-primary btn-md" type="submit"> | |||||
| Update | |||||
| </button> | |||||
| </div> | |||||
| </Form> | |||||
| </div> | |||||
| </div> | |||||
| </section> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import AdminNavbar from '@/components/admin/components/navbar/AdminNavbar' | |||||
| import { Form, Field, ErrorMessage } from 'vee-validate' | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| tab: 'details', | |||||
| post: {}, | |||||
| } | |||||
| }, | |||||
| components: { | |||||
| AdminNavbar, | |||||
| Form, | |||||
| Field, | |||||
| ErrorMessage, | |||||
| }, | |||||
| mounted () { | |||||
| this.getPost() | |||||
| }, | |||||
| methods: { | |||||
| async getPost () { | |||||
| try { | |||||
| const response = await this.axios.get(`/post/${this.$route.params.id}`) | |||||
| if (response.status === 200) { | |||||
| this.post = response.data | |||||
| } | |||||
| } catch (error) { | |||||
| this.$toast.error('An error occurred.') | |||||
| } | |||||
| }, | |||||
| async updatePost () { | |||||
| try { | |||||
| let response = await this.axios.put( | |||||
| `/user/${this.$route.params.id}`, | |||||
| { | |||||
| first_name: this.user.first_name, | |||||
| last_name: this.user.last_name, | |||||
| email: this.user.email, | |||||
| }, | |||||
| ) | |||||
| if (response.status === 200) { | |||||
| this.$toast.success('Successfully updated user details.'); | |||||
| this.setPostFromResponse(response) | |||||
| } | |||||
| } catch (error) { | |||||
| this.$toast.error('An error occured'); | |||||
| } | |||||
| }, | |||||
| } | |||||
| } | |||||
| </script> | |||||
| @ -0,0 +1,139 @@ | |||||
| <template> | |||||
| <div id="admin-page-container"> | |||||
| <admin-navbar/> | |||||
| <div class="container table-responsive mt-5 pb-5"> | |||||
| <admin-list-header | |||||
| addNewTo="AdminUsersCreate" | |||||
| addNewLabel="Add Post" | |||||
| :searchFunction="searchPosts" | |||||
| ref="listHeader" | |||||
| /> | |||||
| <div class="card shadow-2-strong card-registration"> | |||||
| <table class="table table-striped"> | |||||
| <thead class="thead-dark"> | |||||
| <tr> | |||||
| <th scope="col">Title</th> | |||||
| <th scope="col">Front Page</th> | |||||
| <th scope="col" class="d-none d-sm-table-cell">Created At</th> | |||||
| <th scope="col" class="d-none d-sm-table-cell">Published At</th> | |||||
| <th scope="col"></th> | |||||
| </tr> | |||||
| </thead> | |||||
| <tbody> | |||||
| <tr v-for="post in posts" :key="post.id"> | |||||
| <td class="align-middle">{{ post.title }}</td> | |||||
| <td class="align-middle">{{ post.front_page }}</td> | |||||
| <td class="align-middle d-none d-sm-table-cell">{{ formatDate(post.created_at) }}</td> | |||||
| <td v-if="post.published_at" class="align-middle d-none d-sm-table-cell">{{ formatDate(post.published_at) }}</td> | |||||
| <td v-if="!post.published_at" class="align-middle d-none d-sm-table-cell">-</td> | |||||
| <td class="align-middle"> | |||||
| <router-link | |||||
| :to="{ name: 'AdminPostsForm', params: { id: post.id } }" | |||||
| > | |||||
| <button | |||||
| class="btn btn-outline-dark" | |||||
| > | |||||
| Open | |||||
| </button> | |||||
| </router-link> | |||||
| </td> | |||||
| </tr> | |||||
| </tbody> | |||||
| </table> | |||||
| <p v-if="dataEnd" class="py-2 center-align text-muted">No more data</p> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import AdminNavbar from '@/components/admin/components/navbar/AdminNavbar' | |||||
| import AdminListHeader from '@/components/admin/components/list/AdminListHeader.vue' | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| posts: [], | |||||
| page: 0, | |||||
| pageSize: 15, | |||||
| search: '', | |||||
| dataEnd: false, | |||||
| } | |||||
| }, | |||||
| components: { | |||||
| AdminNavbar, | |||||
| AdminListHeader, | |||||
| }, | |||||
| beforeMount () { | |||||
| this.getInitialPosts() | |||||
| }, | |||||
| mounted () { | |||||
| this.axios.get('/admin/me') | |||||
| this.getNextPosts() | |||||
| }, | |||||
| methods: { | |||||
| formatDate (dateString) { | |||||
| const d = new Date(dateString) | |||||
| let hours = d.getHours(); | |||||
| let minutes = d.getMinutes(); | |||||
| const ampm = hours >= 12 ? 'pm' : 'am'; | |||||
| hours = hours % 12; | |||||
| hours = hours ? hours : 12; | |||||
| minutes = minutes < 10 ? '0'+minutes : minutes; | |||||
| const strTime = hours + ':' + minutes + ' ' + ampm; | |||||
| return d.getDate() + "/" + (d.getMonth()+1) + "/" + d.getFullYear() + " " + strTime; | |||||
| }, | |||||
| async getInitialPosts () { | |||||
| try { | |||||
| const response = await this.axios.get( | |||||
| `/post?page=${this.page}&pageSize=${this.pageSize}&search=${this.search}` | |||||
| ) | |||||
| if (response.status === 200) { | |||||
| this.posts = response.data | |||||
| } | |||||
| } catch (error) { | |||||
| if (error.response.status === 404) { | |||||
| this.posts = {} | |||||
| this.dataEnd = true | |||||
| } | |||||
| } | |||||
| }, | |||||
| async getNextPosts () { | |||||
| window.onscroll = async () => { | |||||
| let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight === document.documentElement.offsetHeight; | |||||
| if (bottomOfWindow) { | |||||
| try { | |||||
| this.page += 1 | |||||
| const response = await this.axios.get( | |||||
| `/post?page=${this.page}&pageSize=${this.pageSize}&search=${this.search}` | |||||
| ) | |||||
| if (response.status === 200) { | |||||
| this.posts.push(...response.data) | |||||
| } | |||||
| } catch (error) { | |||||
| console.log(error) | |||||
| if (error.response.status === 404) { | |||||
| this.dataEnd = true | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| searchPosts () { | |||||
| this.search = this.$refs.listHeader.$refs.search.value | |||||
| this.getInitialPosts() | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||