Browse Source

Add createUser and getUsers API endpoints

pull/2/head
Tovi Jaeschke-Rogers 3 years ago
parent
commit
fe4b31be06
8 changed files with 394 additions and 2 deletions
  1. +76
    -0
      Api/JsonSerialization/DeserializeUserJson.go
  2. +0
    -2
      Api/Posts.go
  3. +3
    -0
      Api/Routes.go
  4. +102
    -0
      Api/Users.go
  5. +132
    -0
      Api/Users_test.go
  6. +3
    -0
      Database/Init.go
  7. +63
    -0
      Database/Users.go
  8. +15
    -0
      Models/Users.go

+ 76
- 0
Api/JsonSerialization/DeserializeUserJson.go View File

@ -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
- 2
Api/Posts.go View File

@ -116,8 +116,6 @@ func createPost(w http.ResponseWriter, r *http.Request) {
// TODO: Add auth // TODO: Add auth
log.Printf("Posts handler recieved %s request", r.Method)
requestBody, err = ioutil.ReadAll(r.Body) requestBody, err = ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
log.Printf("Error encountered reading POST body: %s\n", err.Error()) log.Printf("Error encountered reading POST body: %s\n", err.Error())


+ 3
- 0
Api/Routes.go View File

@ -26,6 +26,9 @@ func InitApiEndpoints() *mux.Router {
router.HandleFunc("/post/{postID}/image", createPostImage).Methods("POST") router.HandleFunc("/post/{postID}/image", createPostImage).Methods("POST")
router.HandleFunc("/post/{postID}/image/{imageID}", deletePostImage).Methods("DELETE") router.HandleFunc("/post/{postID}/image/{imageID}", deletePostImage).Methods("DELETE")
// Define routes for users api
router.HandleFunc("/user", createUser).Methods("POST")
//router.PathPrefix("/").Handler(http.StripPrefix("/images/", http.FileServer(http.Dir("./uploads")))) //router.PathPrefix("/").Handler(http.StripPrefix("/images/", http.FileServer(http.Dir("./uploads"))))
return router return router


+ 102
- 0
Api/Users.go View File

@ -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)
}

+ 132
- 0
Api/Users_test.go View File

@ -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{})
}

+ 3
- 0
Database/Init.go View File

@ -43,4 +43,7 @@ func Init() {
DB.AutoMigrate(&Models.SubscriptionEmailAttachment{}) DB.AutoMigrate(&Models.SubscriptionEmailAttachment{})
DB.AutoMigrate(&Models.SubscriptionEmail{}) DB.AutoMigrate(&Models.SubscriptionEmail{})
DB.AutoMigrate(&Models.Subscription{}) DB.AutoMigrate(&Models.Subscription{})
log.Println("Running AutoMigrate on User tables...")
DB.AutoMigrate(&Models.User{})
} }

+ 63
- 0
Database/Users.go View File

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

+ 15
- 0
Models/Users.go View File

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

Loading…
Cancel
Save