| @ -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 DeserializeUser(data []byte, allowMissing []string, allowAllMissing bool) (Models.User, error) { | |||||
| var ( | |||||
| postData Models.User = Models.User{} | |||||
| 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,102 @@ | |||||
| package Api | |||||
| import ( | |||||
| "encoding/json" | |||||
| "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" | |||||
| ) | |||||
| func getUsers(w http.ResponseWriter, r *http.Request) { | |||||
| var ( | |||||
| users []Models.User | |||||
| 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 | |||||
| } | |||||
| users, err = Database.GetUsers(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(users, "", " ") | |||||
| if err != nil { | |||||
| JsonReturn(w, 500, "An error occured") | |||||
| return | |||||
| } | |||||
| // Return updated json | |||||
| w.WriteHeader(http.StatusOK) | |||||
| w.Write(returnJson) | |||||
| } | |||||
| func createUser(w http.ResponseWriter, r *http.Request) { | |||||
| var ( | |||||
| userData Models.User | |||||
| requestBody []byte | |||||
| err error | |||||
| ) | |||||
| 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 | |||||
| } | |||||
| userData, err = JsonSerialization.DeserializeUser(requestBody, []string{ | |||||
| "id", | |||||
| "last_login", | |||||
| }, false) | |||||
| if err != nil { | |||||
| log.Printf("Invalid data provided to user API: %s\n", err.Error()) | |||||
| JsonReturn(w, 405, "Invalid data") | |||||
| return | |||||
| } | |||||
| err = Database.CheckUniqueEmail(userData.Email) | |||||
| if err != nil { | |||||
| JsonReturn(w, 405, "invalid_email") | |||||
| return | |||||
| } | |||||
| if userData.Password != userData.ConfirmPassword { | |||||
| JsonReturn(w, 500, "invalid_password") | |||||
| return | |||||
| } | |||||
| err = Database.CreateUser(&userData) | |||||
| if err != nil { | |||||
| JsonReturn(w, 405, "Invalid data") | |||||
| return | |||||
| } | |||||
| // Return updated json | |||||
| w.WriteHeader(http.StatusOK) | |||||
| } | |||||
| @ -0,0 +1,132 @@ | |||||
| package Api | |||||
| import ( | |||||
| "encoding/json" | |||||
| "fmt" | |||||
| "io/ioutil" | |||||
| "log" | |||||
| "math/rand" | |||||
| "net/http" | |||||
| "net/http/httptest" | |||||
| "os" | |||||
| "path" | |||||
| "runtime" | |||||
| "strings" | |||||
| "testing" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||||
| "github.com/gorilla/mux" | |||||
| "gorm.io/gorm" | |||||
| ) | |||||
| 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() | |||||
| } | |||||
| var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") | |||||
| func RandStringRunes(n int) string { | |||||
| b := make([]rune, n) | |||||
| for i := range b { | |||||
| b[i] = letterRunes[rand.Intn(len(letterRunes))] | |||||
| } | |||||
| return string(b) | |||||
| } | |||||
| func Test_getUsers(t *testing.T) { | |||||
| t.Log("Testing getUsers...") | |||||
| r.HandleFunc("/user", getUsers).Methods("GET") | |||||
| ts := httptest.NewServer(r) | |||||
| defer ts.Close() | |||||
| var err error | |||||
| for i := 0; i < 20; i++ { | |||||
| userData := Models.User{ | |||||
| Email: fmt.Sprintf( | |||||
| "%s@email.com", | |||||
| RandStringRunes(16), | |||||
| ), | |||||
| Password: "password", | |||||
| ConfirmPassword: "password", | |||||
| } | |||||
| err = Database.CreateUser(&userData) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| } | |||||
| defer Database.DB. | |||||
| Session(&gorm.Session{FullSaveAssociations: true}). | |||||
| Unscoped(). | |||||
| Delete(&userData) | |||||
| } | |||||
| res, err := http.Get(ts.URL + "/user?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) | |||||
| } | |||||
| getUsersData := new([]Models.User) | |||||
| err = json.NewDecoder(res.Body).Decode(getUsersData) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| } | |||||
| if len(*getUsersData) != 10 { | |||||
| t.Errorf("Expected 10, recieved %d", len(*getUsersData)) | |||||
| } | |||||
| } | |||||
| func Test_createUser(t *testing.T) { | |||||
| t.Log("Testing createUser...") | |||||
| r.HandleFunc("/user", createUser).Methods("POST") | |||||
| ts := httptest.NewServer(r) | |||||
| defer ts.Close() | |||||
| postJson := ` | |||||
| { | |||||
| "email": "email@email.com", | |||||
| "password": "password", | |||||
| "confirm_password": "password", | |||||
| "first_name": "Hugh", | |||||
| "last_name": "Mann" | |||||
| } | |||||
| ` | |||||
| res, err := http.Post(ts.URL+"/user", "application/json", strings.NewReader(postJson)) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| return | |||||
| } | |||||
| if res.StatusCode != http.StatusOK { | |||||
| t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) | |||||
| return | |||||
| } | |||||
| Database.DB.Model(Models.User{}). | |||||
| Select("count(*) > 0"). | |||||
| Where("email = ?", "email@email.com"). | |||||
| Delete(Models.User{}) | |||||
| } | |||||
| @ -0,0 +1,63 @@ | |||||
| package Database | |||||
| import ( | |||||
| "errors" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||||
| "gorm.io/gorm" | |||||
| ) | |||||
| func GetUsers(page, pageSize int) ([]Models.User, error) { | |||||
| var ( | |||||
| users []Models.User | |||||
| 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(&users). | |||||
| Error | |||||
| return users, err | |||||
| } | |||||
| func CheckUniqueEmail(email string) error { | |||||
| var ( | |||||
| exists bool | |||||
| err error | |||||
| ) | |||||
| err = DB.Model(Models.User{}). | |||||
| Select("count(*) > 0"). | |||||
| Where("email = ?", email). | |||||
| Find(&exists). | |||||
| Error | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| if exists { | |||||
| return errors.New("Invalid email") | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func CreateUser(userData *Models.User) error { | |||||
| return DB.Session(&gorm.Session{FullSaveAssociations: true}). | |||||
| Create(userData). | |||||
| Error | |||||
| } | |||||
| @ -0,0 +1,15 @@ | |||||
| package Models | |||||
| import ( | |||||
| "time" | |||||
| ) | |||||
| type User struct { | |||||
| Base | |||||
| Email string `gorm:"not null;unique" json:"email"` | |||||
| Password string `gorm:"not null" json:"password"` | |||||
| ConfirmPassword string `gorm:"-" json:"confirm_password"` | |||||
| LastLogin *time.Time `json:"last_login"` | |||||
| FirstName string `gorm:"not null" json:"first_name"` | |||||
| LastName string `gorm:"not null" json:"last_name"` | |||||
| } | |||||