From 7c66aba3b93a5f7c961cf38e9e9a16b2c1f6c4fa Mon Sep 17 00:00:00 2001 From: Tovi Jaeschke-Rogers Date: Fri, 11 Mar 2022 16:54:07 +1030 Subject: [PATCH] Add api CRUD endpoints for posts --- Api/JsonSerialization/DeserializePostJson.go | 76 +++++ Api/JsonSerialization/VerifyJson.go | 87 ++++++ Api/Posts.go | 257 +++++++++++++++++ Api/Posts_test.go | 289 +++++++++++++++++++ Api/ReturnJson.go | 28 ++ Api/Routes.go | 29 ++ {database => Database}/Init.go | 20 +- Database/Posts.go | 90 ++++++ {models => Models}/Base.go | 10 +- Models/Posts.go | 53 ++++ {models => Models}/Subscriptions.go | 2 +- database/Posts.go | 1 - go.mod | 2 + go.sum | 4 + main.go | 19 +- models/Posts.go | 48 --- 16 files changed, 947 insertions(+), 68 deletions(-) create mode 100644 Api/JsonSerialization/DeserializePostJson.go create mode 100644 Api/JsonSerialization/VerifyJson.go create mode 100644 Api/Posts.go create mode 100644 Api/Posts_test.go create mode 100644 Api/ReturnJson.go create mode 100644 Api/Routes.go rename {database => Database}/Init.go (56%) create mode 100644 Database/Posts.go rename {models => Models}/Base.go (65%) create mode 100644 Models/Posts.go rename {models => Models}/Subscriptions.go (97%) delete mode 100644 database/Posts.go delete mode 100644 models/Posts.go diff --git a/Api/JsonSerialization/DeserializePostJson.go b/Api/JsonSerialization/DeserializePostJson.go new file mode 100644 index 0000000..d754e8e --- /dev/null +++ b/Api/JsonSerialization/DeserializePostJson.go @@ -0,0 +1,76 @@ +package JsonSerialization + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" + + schema "github.com/Kangaroux/go-map-schema" +) + +func DeserializePost(data []byte, allowMissing []string, allowAllMissing bool) (Models.Post, error) { + var ( + postData Models.Post = Models.Post{} + jsonStructureTest map[string]interface{} = make(map[string]interface{}) + jsonStructureTestResults *schema.CompareResults + field schema.FieldMissing + allowed string + missingFields []string + i int + err error + ) + + // Verify the JSON has the correct structure + json.Unmarshal(data, &jsonStructureTest) + jsonStructureTestResults, err = schema.CompareMapToStruct( + &postData, + jsonStructureTest, + &schema.CompareOpts{ + ConvertibleFunc: CanConvert, + TypeNameFunc: schema.DetailedTypeName, + }) + if err != nil { + return postData, err + } + + if len(jsonStructureTestResults.MismatchedFields) > 0 { + return postData, errors.New(fmt.Sprintf( + "MismatchedFields found when deserializing data: %s", + jsonStructureTestResults.Errors().Error(), + )) + } + + // Remove allowed missing fields from MissingFields + for _, allowed = range allowMissing { + for i, field = range jsonStructureTestResults.MissingFields { + if allowed == field.String() { + jsonStructureTestResults.MissingFields = append( + jsonStructureTestResults.MissingFields[:i], + jsonStructureTestResults.MissingFields[i+1:]..., + ) + } + } + } + + if !allowAllMissing && len(jsonStructureTestResults.MissingFields) > 0 { + for _, field = range jsonStructureTestResults.MissingFields { + missingFields = append(missingFields, field.String()) + } + + return postData, errors.New(fmt.Sprintf( + "MissingFields found when deserializing data: %s", + strings.Join(missingFields, ", "), + )) + } + + // Deserialize the JSON into the struct + err = json.Unmarshal(data, &postData) + if err != nil { + return postData, err + } + + return postData, err +} diff --git a/Api/JsonSerialization/VerifyJson.go b/Api/JsonSerialization/VerifyJson.go new file mode 100644 index 0000000..c0e1b17 --- /dev/null +++ b/Api/JsonSerialization/VerifyJson.go @@ -0,0 +1,87 @@ +package JsonSerialization + +import ( + "math" + "reflect" +) + +// isIntegerType returns whether the type is an integer and if it's unsigned. +// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L328 +func isIntegerType(t reflect.Type) (yes bool, unsigned bool) { + switch t.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + yes = true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + yes = true + unsigned = true + } + + return +} + +// isFloatType returns true if the type is a floating point. Note that this doesn't +// care about the value -- unmarshaling the number "0" gives a float, not an int. +// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L319 +func isFloatType(t reflect.Type) (yes bool) { + switch t.Kind() { + case reflect.Float32, reflect.Float64: + yes = true + } + + return +} + +// CanConvert returns whether value v is convertible to type t. +// +// If t is a pointer and v is not nil, it checks if v is convertible to the type that +// t points to. +// Modified due to not handling slices (DefaultCanConvert fails on PhotoUrls and Tags) +// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L191 +func CanConvert(t reflect.Type, v reflect.Value) bool { + isPtr := t.Kind() == reflect.Ptr + isStruct := t.Kind() == reflect.Struct + dstType := t + + // Check if v is a nil value. + if !v.IsValid() || (v.CanAddr() && v.IsNil()) { + return isPtr + } + + // If the dst is a pointer, check if we can convert to the type it's pointing to. + if isPtr { + dstType = t.Elem() + isStruct = t.Elem().Kind() == reflect.Struct + } + + // If the dst is a struct, we should check its nested fields. + if isStruct { + return v.Kind() == reflect.Map + } + + if t.Kind() == reflect.Slice { + return v.Kind() == reflect.Slice + } + + if !v.Type().ConvertibleTo(dstType) { + return false + } + + // Handle converting to an integer type. + if dstInt, unsigned := isIntegerType(dstType); dstInt { + if isFloatType(v.Type()) { + f := v.Float() + + if math.Trunc(f) != f { + return false + } else if unsigned && f < 0 { + return false + } + } else if srcInt, _ := isIntegerType(v.Type()); srcInt { + if unsigned && v.Int() < 0 { + return false + } + } + } + + return true +} diff --git a/Api/Posts.go b/Api/Posts.go new file mode 100644 index 0000000..c2752b9 --- /dev/null +++ b/Api/Posts.go @@ -0,0 +1,257 @@ +package Api + +import ( + "encoding/json" + "errors" + "io/ioutil" + "log" + "net/http" + "net/url" + "strconv" + + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/JsonSerialization" + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" + "github.com/gorilla/mux" +) + +func getPostById(w http.ResponseWriter, r *http.Request) (Models.Post, error) { + var ( + postData Models.Post + urlVars map[string]string + id string + ok bool + err error + ) + + urlVars = mux.Vars(r) + id, ok = urlVars["postID"] + if !ok { + log.Printf("Error encountered getting id\n") + JsonReturn(w, 500, "An error occured") + return postData, errors.New("Could not get id") + } + + postData, err = Database.GetPostById(id) + if err != nil { + log.Printf("Could not find pet with id %s\n", id) + JsonReturn(w, 404, "Not found") + return postData, err + } + + return postData, nil +} + +func getPosts(w http.ResponseWriter, r *http.Request) { + var ( + posts []Models.Post + returnJson []byte + values url.Values + page, pageSize int + err error + ) + + values = r.URL.Query() + + page, err = strconv.Atoi(values.Get("page")) + if err != nil { + log.Println("Could not parse page url argument") + JsonReturn(w, 500, "An error occured") + return + } + + page, err = strconv.Atoi(values.Get("pageSize")) + if err != nil { + log.Println("Could not parse pageSize url argument") + JsonReturn(w, 500, "An error occured") + return + } + + posts, err = Database.GetPosts(page, pageSize) + if err != nil { + log.Printf("An error occured: %s\n", err.Error()) + JsonReturn(w, 500, "An error occured") + return + } + + returnJson, err = json.MarshalIndent(posts, "", " ") + if err != nil { + JsonReturn(w, 500, "An error occured") + return + } + + // Return updated json + w.WriteHeader(http.StatusOK) + w.Write(returnJson) +} + +func getFrontPagePosts(w http.ResponseWriter, r *http.Request) { + var ( + posts []Models.Post + returnJson []byte + err error + ) + + posts, err = Database.GetFrontPagePosts() + if err != nil { + log.Printf("An error occured: %s\n", err.Error()) + JsonReturn(w, 500, "An error occured") + return + } + + returnJson, err = json.MarshalIndent(posts, "", " ") + if err != nil { + 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 + ) + + postData, err = getPostById(w, r) + if err != nil { + return + } + + returnJson, err = json.MarshalIndent(postData, "", " ") + if err != nil { + 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 + ) + + // TODO: Add auth + + log.Printf("Posts handler recieved %s request", r.Method) + + requestBody, err = ioutil.ReadAll(r.Body) + if err != nil { + log.Printf("Error encountered reading POST body: %s\n", err.Error()) + 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()) + JsonReturn(w, 405, "Invalid data") + return + } + + err = Database.CreatePost(&postData) + if err != nil { + JsonReturn(w, 405, "Invalid data") + } + + returnJson, err = json.MarshalIndent(postData, "", " ") + if err != nil { + log.Printf("An error occured: %s\n", err.Error()) + 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 + urlVars map[string]string + id string + ok bool + err error + ) + + urlVars = mux.Vars(r) + id, ok = urlVars["postID"] + if !ok { + log.Printf("Error encountered getting id\n") + 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()) + 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()) + JsonReturn(w, 405, "Invalid data") + return + } + + postData, err = Database.UpdatePost(id, &postData) + if err != nil { + log.Printf("An error occured: %s\n", err.Error()) + JsonReturn(w, 500, "An error occured") + return + } + + returnJson, err = json.MarshalIndent(postData, "", " ") + if err != nil { + log.Printf("An error occured: %s\n", err.Error()) + 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 + ) + + postData, err = getPostById(w, r) + if err != nil { + return + } + + err = Database.DeletePost(&postData) + if err != nil { + log.Printf("An error occured: %s\n", err.Error()) + JsonReturn(w, 500, "An error occured") + return + } + + // Return updated json + w.WriteHeader(http.StatusOK) +} diff --git a/Api/Posts_test.go b/Api/Posts_test.go new file mode 100644 index 0000000..a72e531 --- /dev/null +++ b/Api/Posts_test.go @@ -0,0 +1,289 @@ +package Api + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" + "github.com/gorilla/mux" +) + +func Test_getPosts(t *testing.T) { + log.SetOutput(ioutil.Discard) + Database.Init() + + r := mux.NewRouter() + r.HandleFunc("/post", getPosts).Methods("GET") + + ts := httptest.NewServer(r) + + defer ts.Close() + + for i := 0; i < 20; i++ { + postData := Models.Post{ + Title: "Test post", + Content: "Test content", + FrontPage: true, + Order: i, + PostLinks: []Models.PostLink{ + { + Type: "Facebook", + Link: "http://google.com/", + }, + }, + } + + Database.CreatePost(&postData) + + defer Database.DB.Unscoped().Delete(&postData) + } + + res, err := http.Get(ts.URL + "/post?page=1&pageSize=10") + + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + } + if res.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) + } + + getPostsData := new([]Models.Post) + err = json.NewDecoder(res.Body).Decode(getPostsData) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + } + + if len(*getPostsData) != 10 { + t.Errorf("Expected 10, recieved %d", len(*getPostsData)) + } +} + +func Test_getPost(t *testing.T) { + log.SetOutput(ioutil.Discard) + Database.Init() + + r := mux.NewRouter() + r.HandleFunc("/post/{postID}", getPost).Methods("GET") + + ts := httptest.NewServer(r) + + defer ts.Close() + + postData := Models.Post{ + Title: "Test post", + Content: "Test content", + FrontPage: true, + Order: 1, + PostLinks: []Models.PostLink{ + { + Type: "Facebook", + Link: "http://google.com/", + }, + }, + } + + Database.CreatePost(&postData) + + res, err := http.Get(fmt.Sprintf( + "%s/post/%s", + ts.URL, + postData.ID, + )) + + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + } + if res.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) + } + + getPostData := new(Models.Post) + err = json.NewDecoder(res.Body).Decode(getPostData) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + } + + Database.DB.Unscoped().Delete(&postData) +} + +func Test_createPost(t *testing.T) { + log.SetOutput(ioutil.Discard) + Database.Init() + + r := mux.NewRouter() + r.HandleFunc("/post", createPost).Methods("POST") + + ts := httptest.NewServer(r) + + defer ts.Close() + + postJson := ` +{ + "title": "Test post", + "content": "Test content", + "front_page": true, + "order": 1, + "links": [{ + "type": "Facebook", + "link": "http://google.com/" + }] +} +` + + res, err := http.Post(ts.URL+"/post", "application/json", strings.NewReader(postJson)) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + } + if res.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) + } + + postData := new(Models.Post) + err = json.NewDecoder(res.Body).Decode(postData) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + } + + if postData.Title != "Test post" { + t.Errorf("Expected title \"Test post\", recieved \"%s\"", postData.Title) + } + if postData.Content != "Test content" { + t.Errorf("Expected content \"Test content\", recieved \"%s\"", postData.Content) + } + + Database.DB.Unscoped().Delete(&postData) +} + +func Test_deletePost(t *testing.T) { + log.SetOutput(ioutil.Discard) + Database.Init() + + r := mux.NewRouter() + r.HandleFunc("/post/{postID}", deletePost).Methods("DELETE") + + ts := httptest.NewServer(r) + + defer ts.Close() + + postData := Models.Post{ + Title: "Test post", + Content: "Test content", + FrontPage: true, + Order: 1, + PostLinks: []Models.PostLink{ + { + Type: "Facebook", + Link: "http://google.com/", + }, + }, + } + + Database.CreatePost(&postData) + + req, err := http.NewRequest("DELETE", fmt.Sprintf( + "%s/post/%s", + ts.URL, + postData.ID, + ), nil) + + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + } + + // Fetch Request + res, err := http.DefaultClient.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) + } + + Database.DB.Unscoped().Delete(&postData) +} + +func Test_updatePost(t *testing.T) { + log.SetOutput(ioutil.Discard) + Database.Init() + + r := mux.NewRouter() + r.HandleFunc("/post/{postID}", updatePost).Methods("PUT") + + ts := httptest.NewServer(r) + + defer ts.Close() + + postData := Models.Post{ + Title: "Test post", + Content: "Test content", + FrontPage: true, + Order: 1, + PostLinks: []Models.PostLink{ + { + Type: "Facebook", + Link: "http://google.com/", + }, + }, + } + + Database.CreatePost(&postData) + + postJson := ` +{ + "content": "New test content", + "front_page": false, + "order": 2 +} +` + + req, err := http.NewRequest("PUT", fmt.Sprintf( + "%s/post/%s", + ts.URL, + postData.ID, + ), strings.NewReader(postJson)) + + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + } + + // Fetch Request + res, err := http.DefaultClient.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) + } + + updatePostData := new(Models.Post) + err = json.NewDecoder(res.Body).Decode(updatePostData) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + } + + if updatePostData.Content != "New test content" { + t.Errorf("Expected \"New test content\", recieved %s", updatePostData.Content) + } + + if updatePostData.FrontPage != false { + t.Errorf("Expected false, recieved %t", updatePostData.FrontPage) + } + + if updatePostData.Order != 2 { + t.Errorf("Expected 2, recieved %d", updatePostData.Order) + } + + Database.DB.Unscoped().Delete(&postData) +} diff --git a/Api/ReturnJson.go b/Api/ReturnJson.go new file mode 100644 index 0000000..002c091 --- /dev/null +++ b/Api/ReturnJson.go @@ -0,0 +1,28 @@ +package Api + +import ( + "encoding/json" + "log" + "net/http" +) + +func JsonReturn(w http.ResponseWriter, code int, msg string) { + var ( + responseJsonMap map[string]interface{} + responseJson []byte + err error + ) + + responseJsonMap = make(map[string]interface{}) + + w.WriteHeader(code) + responseJsonMap["code"] = code + responseJsonMap["message"] = msg + + responseJson, err = json.MarshalIndent(responseJsonMap, "", " ") + if err != nil { + log.Printf("Error occured creating response: %s\n", err.Error()) + } + + w.Write(responseJson) +} diff --git a/Api/Routes.go b/Api/Routes.go new file mode 100644 index 0000000..2e45bca --- /dev/null +++ b/Api/Routes.go @@ -0,0 +1,29 @@ +package Api + +import ( + "log" + + "github.com/gorilla/mux" +) + +func InitApiEndpoints() *mux.Router { + var ( + router *mux.Router + ) + + log.Println("Initializing API routes...") + + router = mux.NewRouter() + + // Define routes for posts api + router.HandleFunc("/post", getPosts).Methods("GET") + router.HandleFunc("/frontPagePosts", getFrontPagePosts).Methods("GET") + router.HandleFunc("/post", createPost).Methods("POST") + router.HandleFunc("/post/{postID}", createPost).Methods("GET") + router.HandleFunc("/post/{postID}", updatePost).Methods("PUT") + router.HandleFunc("/post/{postID}", deletePost).Methods("DELETE") + + //router.PathPrefix("/").Handler(http.StripPrefix("/images/", http.FileServer(http.Dir("./uploads")))) + + return router +} diff --git a/database/Init.go b/Database/Init.go similarity index 56% rename from database/Init.go rename to Database/Init.go index 2319074..a467b64 100644 --- a/database/Init.go +++ b/Database/Init.go @@ -1,9 +1,9 @@ -package database +package Database import ( "log" - "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/models" + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" "gorm.io/driver/postgres" "gorm.io/gorm" @@ -31,16 +31,16 @@ func Init() { log.Println("Running AutoMigrate on Post tables...") // Post tables - DB.AutoMigrate(&models.PostImage{}) - DB.AutoMigrate(&models.PostVideo{}) - DB.AutoMigrate(&models.PostAudio{}) - DB.AutoMigrate(&models.PostLink{}) - DB.AutoMigrate(&models.Post{}) + DB.AutoMigrate(&Models.PostImage{}) + DB.AutoMigrate(&Models.PostVideo{}) + DB.AutoMigrate(&Models.PostAudio{}) + DB.AutoMigrate(&Models.PostLink{}) + DB.AutoMigrate(&Models.Post{}) log.Println("Running AutoMigrate on Subscription tables...") // Email subscription tables - DB.AutoMigrate(&models.SubscriptionEmailAttachment{}) - DB.AutoMigrate(&models.SubscriptionEmail{}) - DB.AutoMigrate(&models.Subscription{}) + DB.AutoMigrate(&Models.SubscriptionEmailAttachment{}) + DB.AutoMigrate(&Models.SubscriptionEmail{}) + DB.AutoMigrate(&Models.Subscription{}) } diff --git a/Database/Posts.go b/Database/Posts.go new file mode 100644 index 0000000..c0b31e7 --- /dev/null +++ b/Database/Posts.go @@ -0,0 +1,90 @@ +package Database + +import ( + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" + + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +func GetPosts(page, pageSize int) ([]Models.Post, error) { + var ( + posts []Models.Post + err error + ) + + if page == 0 { + page = 1 + } + + switch { + case pageSize > 100: + pageSize = 100 + case pageSize <= 0: + pageSize = 10 + } + + err = DB.Offset(page). + Limit(pageSize). + Find(&posts). + Error + + return posts, err +} + +func GetFrontPagePosts() ([]Models.Post, error) { + var ( + posts []Models.Post + err error + ) + + err = DB.Where("front_page = ?", true). + Order("order asc"). + 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 + ) + + 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 +} diff --git a/models/Base.go b/Models/Base.go similarity index 65% rename from models/Base.go rename to Models/Base.go index ccc8adb..c4dac02 100644 --- a/models/Base.go +++ b/Models/Base.go @@ -1,4 +1,4 @@ -package models +package Models import ( "time" @@ -9,10 +9,10 @@ import ( // Base contains common columns for all tables. type Base struct { - ID uuid.UUID `gorm:"type:uuid;primary_key;"` - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt *time.Time `sql:"index"` + ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"` + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + DeletedAt *time.Time `sql:"index" json:"-"` } // BeforeCreate will set a UUID rather than numeric ID. diff --git a/Models/Posts.go b/Models/Posts.go new file mode 100644 index 0000000..5078189 --- /dev/null +++ b/Models/Posts.go @@ -0,0 +1,53 @@ +package Models + +import ( + "github.com/gofrs/uuid" +) + +type Post struct { + Base + Title string `gorm:"not null" json:"title"` + Content string `gorm:"not null" json:"content"` + FrontPage bool `gorm:"not null;type:boolean" json:"front_page"` + Order int `gorm:"not null" json:"order"` + + PostLinks []PostLink `json:"links"` + PostImages []PostImage `json:"images"` + PostVideos []PostVideo `json:"videos"` + PostAudios []PostAudio `json:"audios"` +} + +type PostLinkType string + +const ( + POST_LINK_FACEBOOK PostLinkType = "Facebook" + POST_LINK_INSTAGRAM PostLinkType = "Instagram" + POST_LINK_YOUTUBE PostLinkType = "YouTube" + POST_LINK_SPOTIFY PostLinkType = "Spotify" + POST_LINK_OTHER PostLinkType = "Other" +) + +type PostLink struct { + Base + PostID uuid.UUID `gorm:"type:uuid;column:post_foreign_key;not null;" json:"post_id"` + Link string `gorm:"not null" json:"link"` + Type PostLinkType `gorm:"not null" json:"type"` +} + +type PostImage struct { + Base + PostID uuid.UUID `gorm:"type:uuid;column:post_foreign_key;not null;" json:"post_id"` + Filepath string `gorm:"not null" json:"filepath"` +} + +type PostVideo struct { + Base + PostID uuid.UUID `gorm:"type:uuid;column:post_foreign_key;not null;" json:"post_id"` + Filepath string `gorm:"not null" json:"filepath"` +} + +type PostAudio struct { + Base + PostID uuid.UUID `gorm:"type:uuid;column:post_foreign_key;not null;" json:"post_id"` + Filepath string `gorm:"not null" json:"filepath"` +} diff --git a/models/Subscriptions.go b/Models/Subscriptions.go similarity index 97% rename from models/Subscriptions.go rename to Models/Subscriptions.go index 1932321..b86baf6 100644 --- a/models/Subscriptions.go +++ b/Models/Subscriptions.go @@ -1,4 +1,4 @@ -package models +package Models import ( "time" diff --git a/database/Posts.go b/database/Posts.go deleted file mode 100644 index 636bab8..0000000 --- a/database/Posts.go +++ /dev/null @@ -1 +0,0 @@ -package database diff --git a/go.mod b/go.mod index fe92b25..5a4c918 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,9 @@ module git.tovijaeschke.xyz/tovi/SuddenImpactRecords go 1.17 require ( + github.com/Kangaroux/go-map-schema v0.6.1 github.com/gofrs/uuid v4.2.0+incompatible + github.com/gorilla/mux v1.8.0 gorm.io/driver/postgres v1.3.1 gorm.io/gorm v1.23.2 ) diff --git a/go.sum b/go.sum index 2f0647d..55c7725 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Kangaroux/go-map-schema v0.6.1 h1:jXpOzi7kNFC6M8QSvJuI7xeDxObBrVHwA3D6vSrxuG4= +github.com/Kangaroux/go-map-schema v0.6.1/go.mod h1:56jN+6h/N8Pmn5D+JL9gREOvZTlVEAvXtXyLd/NRjh4= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= @@ -16,6 +18,8 @@ github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= diff --git a/main.go b/main.go index 0c224d5..5687a24 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,24 @@ package main import ( - "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/database" + "net/http" + + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api" + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" + + "github.com/gorilla/mux" ) func main() { - var () + var ( + router *mux.Router + ) + + Database.Init() + + router = Api.InitApiEndpoints() - database.Init() + // TODO: Run this within goroutine when running vue application + // Start and listen to requests + http.ListenAndServe(":8080", router) } diff --git a/models/Posts.go b/models/Posts.go deleted file mode 100644 index 36bfba6..0000000 --- a/models/Posts.go +++ /dev/null @@ -1,48 +0,0 @@ -package models - -import ( - "github.com/gofrs/uuid" -) - -type Post struct { - Base - Title string `gorm:"not null"` - Content string `gorm:"not null"` - FrontPage bool `gorm:"not null"` - Order int `gorm:"not null"` -} - -type PostLinkType string - -const ( - POST_LINK_FACEBOOK PostLinkType = "Facebook" - POST_LINK_INSTAGRAM PostLinkType = "Instagram" - POST_LINK_YOUTUBE PostLinkType = "YouTube" - POST_LINK_SPOTIFY PostLinkType = "Spotify" - POST_LINK_OTHER PostLinkType = "Other" -) - -type PostLink struct { - Base - PostId uuid.UUID `gorm:"type:uuid;column:post_foreign_key;not null;"` - Link string `gorm:"not null"` - Type PostLinkType `gorm:"not null"` -} - -type PostImage struct { - Base - PostId uuid.UUID `gorm:"type:uuid;column:post_foreign_key;not null;"` - Filepath string `gorm:"not null"` -} - -type PostVideo struct { - Base - PostId uuid.UUID `gorm:"type:uuid;column:post_foreign_key;not null;"` - Filepath string `gorm:"not null"` -} - -type PostAudio struct { - Base - PostId uuid.UUID `gorm:"type:uuid;column:post_foreign_key;not null;"` - Filepath string `gorm:"not null"` -}