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