| @ -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"` | |||
| } | |||