| @ -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 | |||||
| } | |||||
| @ -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 | |||||
| } | |||||
| @ -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) | |||||
| } | |||||
| @ -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) | |||||
| } | |||||
| @ -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) | |||||
| } | |||||
| @ -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 | |||||
| } | |||||
| @ -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 | |||||
| } | |||||
| @ -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"` | |||||
| } | |||||
| @ -1 +0,0 @@ | |||||
| package database | |||||
| @ -1,11 +1,24 @@ | |||||
| package main | package main | ||||
| import ( | 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() { | 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) | |||||
| } | } | ||||
| @ -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"` | |||||
| } | |||||