@ -0,0 +1,283 @@ | |||
package Api | |||
import ( | |||
"encoding/json" | |||
"io/ioutil" | |||
"log" | |||
"net/http" | |||
"net/url" | |||
"strconv" | |||
"time" | |||
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/Auth" | |||
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/JsonSerialization" | |||
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Util" | |||
) | |||
func getPosts(w http.ResponseWriter, r *http.Request) { | |||
var ( | |||
posts []Models.Post | |||
returnJson []byte | |||
values url.Values | |||
page, pageSize int | |||
search string | |||
err error | |||
) | |||
_, err = Auth.CheckCookie(r) | |||
if err != nil { | |||
w.WriteHeader(http.StatusUnauthorized) | |||
return | |||
} | |||
values = r.URL.Query() | |||
page, err = strconv.Atoi(values.Get("page")) | |||
if err != nil { | |||
log.Println("Could not parse page url argument") | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
pageSize, err = strconv.Atoi(values.Get("pageSize")) | |||
if err != nil { | |||
log.Println("Could not parse pageSize url argument") | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
search = values.Get("search") | |||
posts, err = Database.GetPosts(page, pageSize, search) | |||
if err != nil { | |||
log.Printf("An error occured: %s\n", err.Error()) | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
if len(posts) == 0 { | |||
Util.JsonReturn(w, 404, "No more data") | |||
return | |||
} | |||
returnJson, err = json.MarshalIndent(posts, "", " ") | |||
if err != nil { | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
// Return updated json | |||
w.WriteHeader(http.StatusOK) | |||
w.Write(returnJson) | |||
} | |||
func getPost(w http.ResponseWriter, r *http.Request) { | |||
var ( | |||
postData Models.Post | |||
returnJson []byte | |||
err error | |||
) | |||
_, err = Auth.CheckCookie(r) | |||
if err != nil { | |||
w.WriteHeader(http.StatusUnauthorized) | |||
return | |||
} | |||
postData, err = Util.GetPostById(w, r) | |||
if err != nil { | |||
return | |||
} | |||
returnJson, err = json.MarshalIndent(postData, "", " ") | |||
if err != nil { | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
// Return updated json | |||
w.WriteHeader(http.StatusOK) | |||
w.Write(returnJson) | |||
} | |||
func createPost(w http.ResponseWriter, r *http.Request) { | |||
var ( | |||
requestBody []byte | |||
postData Models.Post | |||
returnJson []byte | |||
err error | |||
) | |||
_, err = Auth.CheckCookie(r) | |||
if err != nil { | |||
w.WriteHeader(http.StatusUnauthorized) | |||
return | |||
} | |||
requestBody, err = ioutil.ReadAll(r.Body) | |||
if err != nil { | |||
log.Printf("Error encountered reading POST body: %s\n", err.Error()) | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
postData, err = JsonSerialization.DeserializePost(requestBody, []string{ | |||
"id", | |||
"links", | |||
"images", | |||
"videos", | |||
"audios", | |||
}, false) | |||
if err != nil { | |||
log.Printf("Invalid data provided to posts API: %s\n", err.Error()) | |||
Util.JsonReturn(w, 405, "Invalid data") | |||
return | |||
} | |||
err = Database.CreatePost(&postData) | |||
if err != nil { | |||
Util.JsonReturn(w, 405, "Invalid data") | |||
} | |||
returnJson, err = json.MarshalIndent(postData, "", " ") | |||
if err != nil { | |||
log.Printf("An error occured: %s\n", err.Error()) | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
// Return updated json | |||
w.WriteHeader(http.StatusOK) | |||
w.Write(returnJson) | |||
} | |||
func updatePost(w http.ResponseWriter, r *http.Request) { | |||
var ( | |||
postData Models.Post | |||
requestBody []byte | |||
returnJson []byte | |||
id string | |||
err error | |||
) | |||
_, err = Auth.CheckCookie(r) | |||
if err != nil { | |||
w.WriteHeader(http.StatusUnauthorized) | |||
return | |||
} | |||
id, err = Util.GetPostId(r) | |||
if err != nil { | |||
log.Printf("Error encountered getting id\n") | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
requestBody, err = ioutil.ReadAll(r.Body) | |||
if err != nil { | |||
log.Printf("Error encountered reading POST body: %s\n", err.Error()) | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
postData, err = JsonSerialization.DeserializePost(requestBody, []string{}, true) | |||
if err != nil { | |||
log.Printf("Invalid data provided to posts API: %s\n", err.Error()) | |||
Util.JsonReturn(w, 405, "Invalid data") | |||
return | |||
} | |||
postData, err = Database.UpdatePost(id, &postData) | |||
if err != nil { | |||
log.Printf("An error occured: %s\n", err.Error()) | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
returnJson, err = json.MarshalIndent(postData, "", " ") | |||
if err != nil { | |||
log.Printf("An error occured: %s\n", err.Error()) | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
// Return updated json | |||
w.WriteHeader(http.StatusOK) | |||
w.Write(returnJson) | |||
} | |||
func publishPost(w http.ResponseWriter, r *http.Request) { | |||
var ( | |||
now time.Time = time.Now() | |||
postData Models.Post | |||
returnJson []byte | |||
err error | |||
) | |||
_, err = Auth.CheckCookie(r) | |||
if err != nil { | |||
w.WriteHeader(http.StatusUnauthorized) | |||
return | |||
} | |||
postData, err = Util.GetPostById(w, r) | |||
if err != nil { | |||
log.Printf("Error encountered getting id\n") | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
if postData.PublishedAt == nil { | |||
postData.PublishedAt = &now | |||
} else { | |||
postData.PublishedAt = nil | |||
} | |||
postData, err = Database.UpdatePost(postData.ID.String(), &postData) | |||
if err != nil { | |||
log.Printf("An error occured: %s\n", err.Error()) | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
returnJson, err = json.MarshalIndent(postData, "", " ") | |||
if err != nil { | |||
log.Printf("An error occured: %s\n", err.Error()) | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
// Return updated json | |||
w.WriteHeader(http.StatusOK) | |||
w.Write(returnJson) | |||
} | |||
func deletePost(w http.ResponseWriter, r *http.Request) { | |||
var ( | |||
postData Models.Post | |||
err error | |||
) | |||
_, err = Auth.CheckCookie(r) | |||
if err != nil { | |||
w.WriteHeader(http.StatusUnauthorized) | |||
return | |||
} | |||
postData, err = Util.GetPostById(w, r) | |||
if err != nil { | |||
return | |||
} | |||
err = Database.DeletePost(&postData) | |||
if err != nil { | |||
log.Printf("An error occured: %s\n", err.Error()) | |||
Util.JsonReturn(w, 500, "An error occured") | |||
return | |||
} | |||
// Return updated json | |||
w.WriteHeader(http.StatusOK) | |||
} |
@ -0,0 +1,97 @@ | |||
package Database | |||
import ( | |||
"fmt" | |||
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||
"gorm.io/gorm" | |||
"gorm.io/gorm/clause" | |||
) | |||
func GetPosts(page, pageSize int, search string) ([]Models.Post, error) { | |||
var ( | |||
posts []Models.Post | |||
query *gorm.DB | |||
offset int | |||
err error | |||
) | |||
switch { | |||
case pageSize > 100: | |||
pageSize = 100 | |||
case pageSize <= 0: | |||
pageSize = 10 | |||
} | |||
offset = page * pageSize | |||
search = fmt.Sprintf("%%%s%%", search) | |||
query = DB.Model(Models.Post{}). | |||
Preload(clause.Associations). | |||
Offset(offset). | |||
Limit(pageSize). | |||
Order("created_at desc") | |||
if search != "%%" { | |||
query = query. | |||
Where("title LIKE ?", search). | |||
Or("content LIKE ?", search) | |||
} | |||
err = query. | |||
Find(&posts). | |||
Error | |||
return posts, err | |||
} | |||
func GetPostById(id string) (Models.Post, error) { | |||
var ( | |||
postData Models.Post | |||
err error | |||
) | |||
err = DB.Preload(clause.Associations). | |||
First(&postData, "id = ?", id). | |||
Error | |||
return postData, err | |||
} | |||
func CreatePost(postData *Models.Post) error { | |||
return DB.Session(&gorm.Session{FullSaveAssociations: true}). | |||
Create(postData). | |||
Error | |||
} | |||
func UpdatePost(id string, postData *Models.Post) (Models.Post, error) { | |||
var ( | |||
err error | |||
) | |||
DB.Model(postData). | |||
Where("id = ?", id). | |||
Association("PostLinks"). | |||
Replace(postData.PostLinks) | |||
err = DB.Model(&Models.Post{}). | |||
Select("*"). | |||
Omit("id", "created_at", "updated_at", "deleted_at"). | |||
Where("id = ?", id). | |||
Updates(postData). | |||
Error | |||
if err != nil { | |||
return Models.Post{}, err | |||
} | |||
return GetPostById(id) | |||
} | |||
func DeletePost(postData *Models.Post) error { | |||
return DB.Session(&gorm.Session{FullSaveAssociations: true}). | |||
Delete(postData). | |||
Error | |||
} |
@ -0,0 +1,204 @@ | |||
<template> | |||
<div class="form-outline editor-form" v-if="editor"> | |||
<div class="mb-3"> | |||
<div class="input-group"> | |||
<button | |||
@click="editor.chain().focus().toggleBold().run()" | |||
:class="editor.isActive('bold') ? 'btn-dark' : 'btn-outline-dark'" | |||
type="button" | |||
class="btn" | |||
> | |||
<i class="fa-solid fa-bold"></i> | |||
</button> | |||
<button | |||
@click="editor.chain().focus().toggleItalic().run()" | |||
:class="editor.isActive('italic') ? 'btn-dark' : 'btn-outline-dark'" | |||
type="button" | |||
class="btn" | |||
> | |||
<i class="fa-solid fa-italic"></i> | |||
</button> | |||
<button | |||
@click="editor.chain().focus().toggleStrike().run()" | |||
:class="editor.isActive('strike') ? 'btn-dark' : 'btn-outline-dark'" | |||
type="button" | |||
class="btn" | |||
> | |||
<i class="fa-solid fa-strikethrough"></i> | |||
</button> | |||
<button | |||
@click="editor.chain().focus().toggleCode().run()" | |||
:class="editor.isActive('code') ? 'btn-dark' : 'btn-outline-dark'" | |||
type="button" | |||
class="btn" | |||
> | |||
<i class="fa-solid fa-code"></i> | |||
</button> | |||
<button | |||
@click="editor.chain().focus().setParagraph().run()" | |||
:class="editor.isActive('paragraph') ? 'btn-dark' : 'btn-outline-dark'" | |||
type="button" | |||
class="btn" | |||
> | |||
<i class="fa-solid fa-paragraph"></i> | |||
</button> | |||
</div> | |||
<div class="input-group"> | |||
<button | |||
@click="editor.chain().focus().toggleHeading({level: 1 }).run()" | |||
:class="editor.isActive('heading', { level: 1 }) ? 'btn-dark' : 'btn-outline-dark'" | |||
type="button" | |||
class="btn" | |||
> | |||
H1 | |||
</button> | |||
<button | |||
@click="editor.chain().focus().toggleHeading({level: 2 }).run()" | |||
:class="editor.isActive('heading', { level: 2 }) ? 'btn-dark' : 'btn-outline-dark'" | |||
type="button" | |||
class="btn" | |||
> | |||
H2 | |||
</button> | |||
<button | |||
@click="editor.chain().focus().toggleHeading({level: 3 }).run()" | |||
:class="editor.isActive('heading', { level: 3 }) ? 'btn-dark' : 'btn-outline-dark'" | |||
type="button" | |||
class="btn" | |||
> | |||
H3 | |||
</button> | |||
<button | |||
@click="editor.chain().focus().toggleHeading({level: 4 }).run()" | |||
:class="editor.isActive('heading', { level: 4 }) ? 'btn-dark' : 'btn-outline-dark'" | |||
type="button" | |||
class="btn" | |||
> | |||
H4 | |||
</button> | |||
<button | |||
@click="editor.chain().focus().toggleHeading({ level: 5 }).run()" | |||
:class="editor.isActive('heading', { level: 5 }) ? 'btn-dark' : 'btn-outline-dark'" | |||
type="button" | |||
class="btn" | |||
> | |||
H5 | |||
</button> | |||
<button | |||
@click="editor.chain().focus().toggleHeading({ level: 6 }).run()" | |||
:class="editor.isActive('heading', { level: 6 }) ? 'btn-dark' : 'btn-outline-dark'" | |||
type="button" | |||
class="btn" | |||
> | |||
H6 | |||
</button> | |||
</div> | |||
<div class="input-group"> | |||
<button | |||
@click="editor.chain().focus().toggleBulletList().run()" | |||
:class="editor.isActive('bulletList') ? 'btn-dark' : 'btn-outline-dark'" | |||
type="button" | |||
class="btn" | |||
> | |||
<i class="fa-solid fa-list"></i> | |||
</button> | |||
<button | |||
@click="editor.chain().focus().toggleOrderedList().run()" | |||
:class="editor.isActive('orderedList') ? 'btn-dark' : 'btn-outline-dark'" | |||
type="button" | |||
class="btn" | |||
> | |||
<i class="fa-solid fa-list-ol"></i> | |||
</button> | |||
<button | |||
@click="editor.chain().focus().toggleCodeBlock().run()" | |||
:class="editor.isActive('codeBlock') ? 'btn-dark' : 'btn-outline-dark'" | |||
type="button" | |||
class="btn" | |||
> | |||
<i class="fa-solid fa-file-code"></i> | |||
</button> | |||
<button | |||
@click="editor.chain().focus().setHorizontalRule().run()" | |||
type="button" | |||
class="btn btn-outline-dark" | |||
> | |||
<i class="fa-solid fa-ruler-horizontal"></i> | |||
</button> | |||
</div> | |||
<div class="input-group"> | |||
<button | |||
@click="editor.chain().focus().undo().run()" | |||
type="button" | |||
class="btn btn-outline-dark" | |||
> | |||
<i class="fa-solid fa-rotate-left"></i> | |||
</button> | |||
<button | |||
@click="editor.chain().focus().redo().run()" | |||
type="button" | |||
class="btn btn-outline-dark" | |||
> | |||
<i class="fa-solid fa-rotate-right"></i> | |||
</button> | |||
</div> | |||
</div> | |||
<editor-content :editor="editor" class="form-control"/> | |||
</div> | |||
</template> | |||
<script> | |||
import { Editor, EditorContent } from '@tiptap/vue-3' | |||
import StarterKit from '@tiptap/starter-kit' | |||
export default { | |||
props: { | |||
modelValue: { | |||
type: String, | |||
default: '', | |||
}, | |||
}, | |||
data () { | |||
return { | |||
editor: null, | |||
} | |||
}, | |||
components: { | |||
EditorContent, | |||
}, | |||
watch: { | |||
modelValue(value) { | |||
// HTML | |||
const isSame = this.editor.getHTML() === value | |||
// JSON | |||
// const isSame = JSON.stringify(this.editor.getJSON()) === JSON.stringify(value) | |||
if (isSame) { | |||
return | |||
} | |||
this.editor.commands.setContent(value, false) | |||
}, | |||
}, | |||
mounted() { | |||
this.editor = new Editor({ | |||
extensions: [ | |||
StarterKit, | |||
], | |||
content: this.modelValue, | |||
onUpdate: () => { | |||
// HTML | |||
this.$emit('update:modelValue', this.editor.getHTML()) | |||
// JSON | |||
// this.$emit('update:modelValue', this.editor.getJSON()) | |||
}, | |||
}) | |||
}, | |||
beforeUnmount() { | |||
this.editor.destroy() | |||
}, | |||
} | |||
</script> |