| @ -0,0 +1 @@ | |||
| /Frontend/public/images/* | |||
| @ -0,0 +1,50 @@ | |||
| package Api | |||
| import ( | |||
| "errors" | |||
| "log" | |||
| "net/http" | |||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||
| "github.com/gorilla/mux" | |||
| ) | |||
| func getPostId(r *http.Request) (string, error) { | |||
| var ( | |||
| urlVars map[string]string | |||
| id string | |||
| ok bool | |||
| ) | |||
| urlVars = mux.Vars(r) | |||
| id, ok = urlVars["postID"] | |||
| if !ok { | |||
| return id, errors.New("Could not get id") | |||
| } | |||
| return id, nil | |||
| } | |||
| func getPostById(w http.ResponseWriter, r *http.Request) (Models.Post, error) { | |||
| var ( | |||
| postData Models.Post | |||
| id string | |||
| err error | |||
| ) | |||
| id, err = getPostId(r) | |||
| if err != nil { | |||
| log.Printf("Error encountered getting id\n") | |||
| JsonReturn(w, 500, "An error occured") | |||
| return postData, err | |||
| } | |||
| 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 | |||
| } | |||
| @ -0,0 +1,50 @@ | |||
| package Api | |||
| import ( | |||
| "errors" | |||
| "log" | |||
| "net/http" | |||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||
| "github.com/gorilla/mux" | |||
| ) | |||
| func getPostImageId(r *http.Request) (string, error) { | |||
| var ( | |||
| urlVars map[string]string | |||
| id string | |||
| ok bool | |||
| ) | |||
| urlVars = mux.Vars(r) | |||
| id, ok = urlVars["imageID"] | |||
| if !ok { | |||
| return id, errors.New("Could not get id") | |||
| } | |||
| return id, nil | |||
| } | |||
| func getPostImageById(w http.ResponseWriter, r *http.Request) (Models.PostImage, error) { | |||
| var ( | |||
| postImageData Models.PostImage | |||
| id string | |||
| err error | |||
| ) | |||
| id, err = getPostImageId(r) | |||
| if err != nil { | |||
| log.Printf("Error encountered getting id\n") | |||
| JsonReturn(w, 500, "An error occured") | |||
| return postImageData, err | |||
| } | |||
| postImageData, err = Database.GetPostImageById(id) | |||
| if err != nil { | |||
| log.Printf("Could not find pet with id %s\n", id) | |||
| JsonReturn(w, 404, "Not found") | |||
| return postImageData, err | |||
| } | |||
| return postImageData, nil | |||
| } | |||
| @ -0,0 +1,127 @@ | |||
| package Api | |||
| import ( | |||
| "encoding/json" | |||
| "io/ioutil" | |||
| "log" | |||
| "mime/multipart" | |||
| "net/http" | |||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Util" | |||
| "github.com/gofrs/uuid" | |||
| ) | |||
| func createPostImage(w http.ResponseWriter, r *http.Request) { | |||
| var ( | |||
| postData Models.Post | |||
| postID string | |||
| postUUID uuid.UUID | |||
| postImage Models.PostImage | |||
| formData *multipart.Form | |||
| fileHeaders []*multipart.FileHeader | |||
| fileHeader *multipart.FileHeader | |||
| file multipart.File | |||
| fileBytes []byte | |||
| fileObject Util.FileObject | |||
| returnJson []byte | |||
| err error | |||
| ) | |||
| postID, err = getPostId(r) | |||
| if err != nil { | |||
| log.Printf("Error encountered getting id\n") | |||
| JsonReturn(w, 500, "An error occured") | |||
| return | |||
| } | |||
| postUUID = uuid.FromStringOrNil(postID) | |||
| // TODO: Change arg to bitwise op | |||
| err = r.ParseMultipartForm(200000) | |||
| if err != nil { | |||
| log.Printf("Error encountered parsing multipart form: %s\n", err.Error()) | |||
| JsonReturn(w, 500, "An error occured") | |||
| return | |||
| } | |||
| formData = r.MultipartForm | |||
| fileHeaders = formData.File["files"] | |||
| for _, fileHeader = range fileHeaders { | |||
| file, err = fileHeader.Open() | |||
| if err != nil { | |||
| log.Printf("Error encountered while post image upload: %s\n", err.Error()) | |||
| JsonReturn(w, 500, "An error occured") | |||
| return | |||
| } | |||
| defer file.Close() | |||
| fileBytes, err = ioutil.ReadAll(file) | |||
| if err != nil { | |||
| log.Printf("Error encountered while post image upload: %s\n", err.Error()) | |||
| JsonReturn(w, 500, "An error occured") | |||
| return | |||
| } | |||
| fileObject, err = Util.WriteFile(fileBytes, "image") | |||
| if err != nil { | |||
| log.Printf("Error encountered while post image upload: %s\n", err.Error()) | |||
| JsonReturn(w, 415, "Invalid filetype") | |||
| return | |||
| } | |||
| postImage = Models.PostImage{ | |||
| PostID: postUUID, | |||
| Filepath: fileObject.Filepath, | |||
| Mimetype: fileObject.Mimetype, | |||
| Size: fileObject.Size, | |||
| } | |||
| err = Database.CreatePostImage(&postImage) | |||
| if err != nil { | |||
| log.Printf("Error encountered while creating post_image record: %s\n", err.Error()) | |||
| JsonReturn(w, 500, "An error occured") | |||
| return | |||
| } | |||
| } | |||
| 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 deletePostImage(w http.ResponseWriter, r *http.Request) { | |||
| var ( | |||
| postImageData Models.PostImage | |||
| err error | |||
| ) | |||
| postImageData, err = getPostImageById(w, r) | |||
| if err != nil { | |||
| return | |||
| } | |||
| err = Database.DeletePostImage(&postImageData) | |||
| 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,273 @@ | |||
| package Api | |||
| import ( | |||
| "bytes" | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| "io" | |||
| "io/ioutil" | |||
| "log" | |||
| "mime/multipart" | |||
| "net/http" | |||
| "net/http/httptest" | |||
| "os" | |||
| "path" | |||
| "path/filepath" | |||
| "runtime" | |||
| "strconv" | |||
| "testing" | |||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||
| "gorm.io/gorm" | |||
| "github.com/gorilla/mux" | |||
| ) | |||
| func init() { | |||
| // Fix working directory for tests | |||
| _, filename, _, _ := runtime.Caller(0) | |||
| dir := path.Join(path.Dir(filename), "..") | |||
| err := os.Chdir(dir) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| log.SetOutput(ioutil.Discard) | |||
| Database.Init() | |||
| r = mux.NewRouter() | |||
| } | |||
| func get(url string) (resp *http.Response, err error) { | |||
| for i := 0; i < 5; i++ { | |||
| resp, err = http.Get(url) | |||
| if err == nil { | |||
| return resp, err | |||
| } | |||
| } | |||
| return resp, err | |||
| } | |||
| // Image generates a *os.File with a random image using the loremflickr.com service | |||
| func fakeImage(width, height int, categories []string, prefix string, categoriesStrict bool) *os.File { | |||
| url := "https://loremflickr.com" | |||
| switch prefix { | |||
| case "g": | |||
| url += "/g" | |||
| case "p": | |||
| url += "/p" | |||
| case "red": | |||
| url += "/red" | |||
| case "green": | |||
| url += "/green" | |||
| case "blue": | |||
| url += "/blue" | |||
| } | |||
| url += string('/') + strconv.Itoa(width) + string('/') + strconv.Itoa(height) | |||
| if len(categories) > 0 { | |||
| url += string('/') | |||
| for _, category := range categories { | |||
| url += category + string(',') | |||
| } | |||
| if categoriesStrict { | |||
| url += "/all" | |||
| } | |||
| } | |||
| resp, err := get(url) | |||
| defer resp.Body.Close() | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| f, err := ioutil.TempFile(os.TempDir(), "loremflickr-img-*.jpg") | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| io.Copy(f, resp.Body) | |||
| err = f.Close() | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| return f | |||
| } | |||
| func Test_createPostImages(t *testing.T) { | |||
| t.Log("Testing createPostImages...") | |||
| r.HandleFunc("/post/{postID}/image", createPostImage).Methods("POST") | |||
| 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://facebook.com/", | |||
| }, | |||
| }, | |||
| } | |||
| err := Database.CreatePost(&postData) | |||
| if err != nil { | |||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||
| } | |||
| image := fakeImage(100, 100, []string{}, "", false) | |||
| image, err = os.Open(image.Name()) | |||
| if err != nil { | |||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||
| } | |||
| body := &bytes.Buffer{} | |||
| writer := multipart.NewWriter(body) | |||
| part, err := writer.CreateFormFile("files", filepath.Base(image.Name())) | |||
| if err != nil { | |||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||
| } | |||
| io.Copy(part, image) | |||
| writer.Close() | |||
| request, err := http.NewRequest( | |||
| "POST", | |||
| fmt.Sprintf( | |||
| "%s/post/%s/image", | |||
| ts.URL, | |||
| postData.ID, | |||
| ), | |||
| body, | |||
| ) | |||
| if err != nil { | |||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||
| } | |||
| request.Header.Add("Content-Type", writer.FormDataContentType()) | |||
| client := &http.Client{} | |||
| res, err := client.Do(request) | |||
| 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) | |||
| } | |||
| defer res.Body.Close() | |||
| updatePostData := new(Models.Post) | |||
| err = json.NewDecoder(res.Body).Decode(updatePostData) | |||
| if err != nil { | |||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||
| } | |||
| defer Database.DB. | |||
| Session(&gorm.Session{FullSaveAssociations: true}). | |||
| Unscoped(). | |||
| Select("PostLinks", "PostImages"). | |||
| Delete(&postData) | |||
| if len(updatePostData.PostImages) != 1 { | |||
| t.Errorf("Expected len(updatePostData.PostImages) == 1, recieved %d", len(updatePostData.PostImages)) | |||
| } | |||
| for _, f := range updatePostData.PostImages { | |||
| if _, err := os.Stat("./" + f.Filepath); errors.Is(err, os.ErrNotExist) { | |||
| t.Errorf( | |||
| "File ./%s does not exist", | |||
| f.Filepath, | |||
| ) | |||
| } else { | |||
| os.Remove("./" + f.Filepath) | |||
| } | |||
| } | |||
| } | |||
| func Test_deletePostImages(t *testing.T) { | |||
| t.Log("Testing createPostImages...") | |||
| r.HandleFunc("/post/{postID}/image/{imageID}", deletePostImage).Methods("DELETE") | |||
| ts := httptest.NewServer(r) | |||
| defer ts.Close() | |||
| image := fakeImage(100, 100, []string{}, "", false) | |||
| image, err := os.Open(image.Name()) | |||
| if err != nil { | |||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||
| } | |||
| defer image.Close() | |||
| postData := Models.Post{ | |||
| Title: "Test post", | |||
| Content: "Test content", | |||
| FrontPage: true, | |||
| Order: 1, | |||
| PostLinks: []Models.PostLink{ | |||
| { | |||
| Type: "Facebook", | |||
| Link: "http://facebook.com/", | |||
| }, | |||
| }, | |||
| PostImages: []Models.PostImage{ | |||
| { | |||
| Filepath: image.Name(), | |||
| }, | |||
| }, | |||
| } | |||
| err = Database.CreatePost(&postData) | |||
| if err != nil { | |||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||
| } | |||
| defer Database.DB. | |||
| Session(&gorm.Session{FullSaveAssociations: true}). | |||
| Unscoped(). | |||
| Select("PostLinks", "PostImages"). | |||
| Delete(&postData) | |||
| req, err := http.NewRequest("DELETE", fmt.Sprintf( | |||
| "%s/post/%s/image/%s", | |||
| ts.URL, | |||
| postData.ID, | |||
| postData.PostImages[0].ID, | |||
| ), nil) | |||
| if err != nil { | |||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||
| } | |||
| 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) | |||
| } | |||
| } | |||
| @ -0,0 +1,32 @@ | |||
| package Database | |||
| import ( | |||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||
| "gorm.io/gorm" | |||
| "gorm.io/gorm/clause" | |||
| ) | |||
| func CreatePostImage(postImageData *Models.PostImage) error { | |||
| return DB.Session(&gorm.Session{FullSaveAssociations: true}). | |||
| Create(postImageData). | |||
| Error | |||
| } | |||
| func GetPostImageById(id string) (Models.PostImage, error) { | |||
| var ( | |||
| postImageData Models.PostImage | |||
| err error | |||
| ) | |||
| err = DB.Preload(clause.Associations). | |||
| First(&postImageData, "id = ?", id). | |||
| Error | |||
| return postImageData, err | |||
| } | |||
| func DeletePostImage(postImageData *Models.PostImage) error { | |||
| return DB.Session(&gorm.Session{FullSaveAssociations: true}). | |||
| Delete(postImageData). | |||
| Error | |||
| } | |||
| @ -0,0 +1,12 @@ | |||
| FLAC_FILES = $(shell find . -type d -not -path '*/\.git/*' -not -path '*\/.git' -not -path '.') | |||
| default: test build | |||
| build: | |||
| go build -o SuddenImpactRecords main | |||
| test: | |||
| for dir in ${FLAC_FILES}; do \ | |||
| go test $$dir; \ | |||
| done | |||
| @ -0,0 +1,67 @@ | |||
| package Util | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| "io/ioutil" | |||
| "os" | |||
| "strings" | |||
| "github.com/gabriel-vasile/mimetype" | |||
| ) | |||
| type FileObject struct { | |||
| Filepath string | |||
| Mimetype string | |||
| Size int64 | |||
| } | |||
| func WriteFile(fileBytes []byte, acceptedMime string) (FileObject, error) { | |||
| var ( | |||
| mime *mimetype.MIME | |||
| mimeSplit []string | |||
| file *os.File | |||
| fi os.FileInfo | |||
| fileObject FileObject | |||
| err error | |||
| ) | |||
| mime = mimetype.Detect(fileBytes) | |||
| mimeSplit = strings.Split(mime.String(), "/") | |||
| if mimeSplit[0] != acceptedMime { | |||
| return fileObject, errors.New("Invalid filetype provided") | |||
| } | |||
| file, err = ioutil.TempFile( | |||
| fmt.Sprintf( | |||
| "./Frontend/public/%ss/", | |||
| mimeSplit[0], | |||
| ), | |||
| fmt.Sprintf( | |||
| "%ss-*%s", | |||
| mimeSplit[0], | |||
| mime.Extension(), | |||
| ), | |||
| ) | |||
| if err != nil { | |||
| return fileObject, err | |||
| } | |||
| defer file.Close() | |||
| _, err = file.Write(fileBytes) | |||
| fi, err = file.Stat() | |||
| if err != nil { | |||
| return fileObject, err | |||
| } | |||
| fileObject = FileObject{ | |||
| Filepath: file.Name(), | |||
| Mimetype: mime.String(), | |||
| Size: fi.Size(), | |||
| } | |||
| return fileObject, err | |||
| } | |||