#2 feature/add-users

Merged
tovi merged 5 commits from feature/add-users into develop 3 years ago
  1. +66
    -0
      Api/Auth/Login.go
  2. +111
    -0
      Api/Auth/Login_test.go
  3. +34
    -0
      Api/Auth/Logout.go
  4. +90
    -0
      Api/Auth/Logout_test.go
  5. +22
    -0
      Api/Auth/Passwords.go
  6. +79
    -0
      Api/Auth/Session.go
  7. +53
    -0
      Api/Auth/UpdatePassword.go
  8. +100
    -0
      Api/Auth/UpdatePassword_test.go
  9. +76
    -0
      Api/JsonSerialization/DeserializeUserJson.go
  10. +36
    -15
      Api/JsonSerialization/VerifyJson.go
  11. +11
    -11
      Api/PostImages.go
  12. +1
    -16
      Api/PostImages_test.go
  13. +40
    -29
      Api/Posts.go
  14. +76
    -84
      Api/Posts_test.go
  15. +17
    -2
      Api/Routes.go
  16. +221
    -0
      Api/Users.go
  17. +372
    -0
      Api/Users_test.go
  18. +36
    -12
      Database/Init.go
  19. +127
    -0
      Database/Users.go
  20. +1
    -0
      Models/Posts.go
  21. +15
    -0
      Models/Users.go
  22. +4
    -4
      Util/PostHelper.go
  23. +4
    -4
      Util/PostImageHelper.go
  24. +1
    -1
      Util/ReturnJson.go
  25. +51
    -0
      Util/UserHelper.go

+ 66
- 0
Api/Auth/Login.go View File

@ -0,0 +1,66 @@
package Auth
import (
"encoding/json"
"net/http"
"time"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"github.com/gofrs/uuid"
)
type Credentials struct {
Email string `json:"email"`
Password string `json:"password"`
}
func Login(w http.ResponseWriter, r *http.Request) {
var (
creds Credentials
userData Models.User
sessionToken uuid.UUID
expiresAt time.Time
err error
)
err = json.NewDecoder(r.Body).Decode(&creds)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
userData, err = Database.GetUserByEmail(creds.Email)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
if !CheckPasswordHash(creds.Password, userData.Password) {
w.WriteHeader(http.StatusUnauthorized)
return
}
sessionToken, err = uuid.NewV4()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
expiresAt = time.Now().Add(1 * time.Hour)
Sessions[sessionToken.String()] = Session{
UserID: userData.ID.String(),
Email: userData.Email,
Expiry: expiresAt,
}
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: sessionToken.String(),
Expires: expiresAt,
})
w.WriteHeader(http.StatusOK)
}

+ 111
- 0
Api/Auth/Login_test.go View File

@ -0,0 +1,111 @@
package Auth
import (
"fmt"
"math/rand"
"net/http"
"net/http/httptest"
"os"
"path"
"runtime"
"strings"
"testing"
"time"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"github.com/gorilla/mux"
)
var (
r *mux.Router
letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
)
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)
}
Database.InitTest()
r = mux.NewRouter()
}
func randString(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
func createTestUser(random bool) (Models.User, error) {
now := time.Now()
email := "email@email.com"
if random {
email = fmt.Sprintf("%s@email.com", randString(16))
}
password, err := HashPassword("password")
if err != nil {
return Models.User{}, err
}
userData := Models.User{
Email: email,
Password: password,
LastLogin: &now,
FirstName: "Hugh",
LastName: "Mann",
}
err = Database.CreateUser(&userData)
return userData, err
}
func Test_Login(t *testing.T) {
t.Log("Testing Login...")
r.HandleFunc("/admin/login", Login).Methods("POST")
ts := httptest.NewServer(r)
defer ts.Close()
userData, err := createTestUser(true)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
postJson := `
{
"email": "%s",
"password": "password"
}
`
postJson = fmt.Sprintf(postJson, userData.Email)
res, err := http.Post(ts.URL+"/admin/login", "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
}
if len(res.Cookies()) != 1 {
t.Errorf("Expected cookies len 1, recieved %d", len(res.Cookies()))
return
}
}

+ 34
- 0
Api/Auth/Logout.go View File

@ -0,0 +1,34 @@
package Auth
import (
"net/http"
"time"
)
func Logout(w http.ResponseWriter, r *http.Request) {
var (
c *http.Cookie
sessionToken string
err error
)
c, err = r.Cookie("session_token")
if err != nil {
if err == http.ErrNoCookie {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusBadRequest)
return
}
sessionToken = c.Value
delete(Sessions, sessionToken)
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: "",
Expires: time.Now(),
})
}

+ 90
- 0
Api/Auth/Logout_test.go View File

@ -0,0 +1,90 @@
package Auth
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"path"
"runtime"
"strings"
"testing"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database"
"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)
}
Database.InitTest()
r = mux.NewRouter()
}
func Test_Logout(t *testing.T) {
t.Log("Testing Logout...")
r.HandleFunc("/admin/login", Logout).Methods("POST")
r.HandleFunc("/admin/logout", Logout).Methods("GET")
ts := httptest.NewServer(r)
defer ts.Close()
userData, err := createTestUser(true)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
postJson := `
{
"email": "%s",
"password": "password"
}
`
postJson = fmt.Sprintf(postJson, userData.Email)
res, err := http.Post(ts.URL+"/admin/login", "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
}
if len(res.Cookies()) != 1 {
t.Errorf("Expected cookies len 1, recieved %d", len(res.Cookies()))
return
}
req, err := http.NewRequest("GET", ts.URL+"/admin/logout", nil)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
return
}
req.AddCookie(res.Cookies()[0])
res, err = http.DefaultClient.Do(req)
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
}
}

+ 22
- 0
Api/Auth/Passwords.go View File

@ -0,0 +1,22 @@
package Auth
import (
"golang.org/x/crypto/bcrypt"
)
func HashPassword(password string) (string, error) {
var (
bytes []byte
err error
)
bytes, err = bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err
}
func CheckPasswordHash(password, hash string) bool {
var (
err error
)
err = bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}

+ 79
- 0
Api/Auth/Session.go View File

@ -0,0 +1,79 @@
package Auth
import (
"errors"
"net/http"
"time"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Util"
)
var (
Sessions = map[string]Session{}
)
type Session struct {
UserID string
Email string
Expiry time.Time
}
func (s Session) IsExpired() bool {
return s.Expiry.Before(time.Now())
}
func CheckCookie(r *http.Request) (Session, error) {
var (
c *http.Cookie
sessionToken string
userSession Session
exists bool
err error
)
c, err = r.Cookie("session_token")
if err != nil {
return userSession, err
}
sessionToken = c.Value
// We then get the session from our session map
userSession, exists = Sessions[sessionToken]
if !exists {
return userSession, errors.New("Cookie not found")
}
// If the session is present, but has expired, we can delete the session, and return
// an unauthorized status
if userSession.IsExpired() {
delete(Sessions, sessionToken)
return userSession, errors.New("Cookie expired")
}
return userSession, nil
}
func CheckCookieCurrentUser(w http.ResponseWriter, r *http.Request) (Models.User, error) {
var (
userSession Session
userData Models.User
err error
)
userSession, err = CheckCookie(r)
if err != nil {
return userData, err
}
userData, err = Util.GetUserById(w, r)
if err != nil {
return userData, err
}
if userData.ID.String() != userSession.UserID {
return userData, errors.New("Is not current user")
}
return userData, nil
}

+ 53
- 0
Api/Auth/UpdatePassword.go View File

@ -0,0 +1,53 @@
package Auth
import (
"encoding/json"
"net/http"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
)
type ChangePassword struct {
Password string `json:"password"`
ConfirmPassword string `json:"confirm_password"`
}
func UpdatePassword(w http.ResponseWriter, r *http.Request) {
var (
changePasswd ChangePassword
userData Models.User
err error
)
userData, err = CheckCookieCurrentUser(w, r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
err = json.NewDecoder(r.Body).Decode(&changePasswd)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if changePasswd.Password != changePasswd.ConfirmPassword {
w.WriteHeader(http.StatusBadRequest)
return
}
userData.Password, err = HashPassword(changePasswd.Password)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
err = Database.UpdateUser(userData.ID.String(), &userData)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}

+ 100
- 0
Api/Auth/UpdatePassword_test.go View File

@ -0,0 +1,100 @@
package Auth
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"path"
"runtime"
"strings"
"testing"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database"
"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)
}
Database.InitTest()
r = mux.NewRouter()
}
func Test_UpdatePassword(t *testing.T) {
t.Log("Testing UpdatePassword...")
r.HandleFunc("/admin/login", Logout).Methods("POST")
r.HandleFunc("/admin/user/{userID}/update-password", UpdatePassword).Methods("PUT")
ts := httptest.NewServer(r)
defer ts.Close()
userData, err := createTestUser(true)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
postJson := `
{
"email": "%s",
"password": "password"
}
`
postJson = fmt.Sprintf(postJson, userData.Email)
res, err := http.Post(ts.URL+"/admin/login", "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
}
if len(res.Cookies()) != 1 {
t.Errorf("Expected cookies len 1, recieved %d", len(res.Cookies()))
return
}
postJson = `
{
"password": "new_password",
"confirm_password": "new_password"
}
`
req, err := http.NewRequest("PUT", fmt.Sprintf(
"%s/admin/user/%s/update-password",
ts.URL,
userData.ID,
), strings.NewReader(postJson))
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
return
}
req.AddCookie(res.Cookies()[0])
res, err = http.DefaultClient.Do(req)
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
}
}

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

+ 36
- 15
Api/JsonSerialization/VerifyJson.go View File

@ -7,7 +7,11 @@ import (
// isIntegerType returns whether the type is an integer and if it's unsigned. // isIntegerType returns whether the type is an integer and if it's unsigned.
// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L328 // See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L328
func isIntegerType(t reflect.Type) (yes bool, unsigned bool) {
func isIntegerType(t reflect.Type) (bool, bool) {
var (
yes bool
unsigned bool
)
switch t.Kind() { switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
yes = true yes = true
@ -16,19 +20,22 @@ func isIntegerType(t reflect.Type) (yes bool, unsigned bool) {
unsigned = true unsigned = true
} }
return
return yes, unsigned
} }
// isFloatType returns true if the type is a floating point. Note that this doesn't // isFloatType returns true if the type is a floating point. Note that this doesn't
// care about the value -- unmarshaling the number "0" gives a float, not an int. // care about the value -- unmarshaling the number "0" gives a float, not an int.
// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L319 // See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L319
func isFloatType(t reflect.Type) (yes bool) {
func isFloatType(t reflect.Type) bool {
var (
yes bool
)
switch t.Kind() { switch t.Kind() {
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
yes = true yes = true
} }
return
return yes
} }
// CanConvert returns whether value v is convertible to type t. // CanConvert returns whether value v is convertible to type t.
@ -38,9 +45,20 @@ func isFloatType(t reflect.Type) (yes bool) {
// Modified due to not handling slices (DefaultCanConvert fails on PhotoUrls and Tags) // Modified due to not handling slices (DefaultCanConvert fails on PhotoUrls and Tags)
// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L191 // See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L191
func CanConvert(t reflect.Type, v reflect.Value) bool { func CanConvert(t reflect.Type, v reflect.Value) bool {
isPtr := t.Kind() == reflect.Ptr
isStruct := t.Kind() == reflect.Struct
dstType := t
var (
isPtr bool
isStruct bool
isArray bool
dstType reflect.Type
dstInt bool
unsigned bool
f float64
srcInt bool
)
isPtr = t.Kind() == reflect.Ptr
isStruct = t.Kind() == reflect.Struct
isArray = t.Kind() == reflect.Array
dstType = t
// Check if v is a nil value. // Check if v is a nil value.
if !v.IsValid() || (v.CanAddr() && v.IsNil()) { if !v.IsValid() || (v.CanAddr() && v.IsNil()) {
@ -58,6 +76,10 @@ func CanConvert(t reflect.Type, v reflect.Value) bool {
return v.Kind() == reflect.Map return v.Kind() == reflect.Map
} }
if isArray {
return v.Kind() == reflect.String
}
if t.Kind() == reflect.Slice { if t.Kind() == reflect.Slice {
return v.Kind() == reflect.Slice return v.Kind() == reflect.Slice
} }
@ -67,19 +89,18 @@ func CanConvert(t reflect.Type, v reflect.Value) bool {
} }
// Handle converting to an integer type. // Handle converting to an integer type.
if dstInt, unsigned := isIntegerType(dstType); dstInt {
dstInt, unsigned = isIntegerType(dstType)
if dstInt {
if isFloatType(v.Type()) { if isFloatType(v.Type()) {
f := v.Float()
f = v.Float()
if math.Trunc(f) != f {
return false
} else if unsigned && f < 0 {
if math.Trunc(f) != f || unsigned && f < 0 {
return false return false
} }
} else if srcInt, _ := isIntegerType(v.Type()); srcInt {
if unsigned && v.Int() < 0 {
return false
} }
srcInt, _ = isIntegerType(v.Type())
if srcInt && unsigned && v.Int() < 0 {
return false
} }
} }


+ 11
- 11
Api/PostImages.go View File

@ -30,10 +30,10 @@ func createPostImage(w http.ResponseWriter, r *http.Request) {
err error err error
) )
postID, err = getPostId(r)
postID, err = Util.GetPostId(r)
if err != nil { if err != nil {
log.Printf("Error encountered getting id\n") log.Printf("Error encountered getting id\n")
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
@ -42,7 +42,7 @@ func createPostImage(w http.ResponseWriter, r *http.Request) {
err = r.ParseMultipartForm(20 << 20) err = r.ParseMultipartForm(20 << 20)
if err != nil { if err != nil {
log.Printf("Error encountered parsing multipart form: %s\n", err.Error()) log.Printf("Error encountered parsing multipart form: %s\n", err.Error())
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
@ -54,7 +54,7 @@ func createPostImage(w http.ResponseWriter, r *http.Request) {
file, err = fileHeader.Open() file, err = fileHeader.Open()
if err != nil { if err != nil {
log.Printf("Error encountered while post image upload: %s\n", err.Error()) log.Printf("Error encountered while post image upload: %s\n", err.Error())
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
defer file.Close() defer file.Close()
@ -62,14 +62,14 @@ func createPostImage(w http.ResponseWriter, r *http.Request) {
fileBytes, err = ioutil.ReadAll(file) fileBytes, err = ioutil.ReadAll(file)
if err != nil { if err != nil {
log.Printf("Error encountered while post image upload: %s\n", err.Error()) log.Printf("Error encountered while post image upload: %s\n", err.Error())
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
fileObject, err = Util.WriteFile(fileBytes, "image") fileObject, err = Util.WriteFile(fileBytes, "image")
if err != nil { if err != nil {
log.Printf("Error encountered while post image upload: %s\n", err.Error()) log.Printf("Error encountered while post image upload: %s\n", err.Error())
JsonReturn(w, 415, "Invalid filetype")
Util.JsonReturn(w, 415, "Invalid filetype")
return return
} }
@ -83,19 +83,19 @@ func createPostImage(w http.ResponseWriter, r *http.Request) {
err = Database.CreatePostImage(&postImage) err = Database.CreatePostImage(&postImage)
if err != nil { if err != nil {
log.Printf("Error encountered while creating post_image record: %s\n", err.Error()) log.Printf("Error encountered while creating post_image record: %s\n", err.Error())
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
} }
postData, err = getPostById(w, r)
postData, err = Util.GetPostById(w, r)
if err != nil { if err != nil {
return return
} }
returnJson, err = json.MarshalIndent(postData, "", " ") returnJson, err = json.MarshalIndent(postData, "", " ")
if err != nil { if err != nil {
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
@ -110,7 +110,7 @@ func deletePostImage(w http.ResponseWriter, r *http.Request) {
err error err error
) )
postImageData, err = getPostImageById(w, r)
postImageData, err = Util.GetPostImageById(w, r)
if err != nil { if err != nil {
return return
} }
@ -118,7 +118,7 @@ func deletePostImage(w http.ResponseWriter, r *http.Request) {
err = Database.DeletePostImage(&postImageData) err = Database.DeletePostImage(&postImageData)
if err != nil { if err != nil {
log.Printf("An error occured: %s\n", err.Error()) log.Printf("An error occured: %s\n", err.Error())
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }


+ 1
- 16
Api/PostImages_test.go View File

@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -22,7 +21,6 @@ import (
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"gorm.io/gorm"
) )
func init() { func init() {
@ -34,8 +32,7 @@ func init() {
panic(err) panic(err)
} }
log.SetOutput(ioutil.Discard)
Database.Init()
Database.InitTest()
r = mux.NewRouter() r = mux.NewRouter()
} }
@ -181,12 +178,6 @@ func Test_createPostImages(t *testing.T) {
t.Errorf("Expected nil, recieved %s", err.Error()) 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 { if len(updatePostData.PostImages) != 1 {
t.Errorf("Expected len(updatePostData.PostImages) == 1, recieved %d", len(updatePostData.PostImages)) t.Errorf("Expected len(updatePostData.PostImages) == 1, recieved %d", len(updatePostData.PostImages))
} }
@ -243,12 +234,6 @@ func Test_deletePostImages(t *testing.T) {
t.Errorf("Expected nil, recieved %s", err.Error()) 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( req, err := http.NewRequest("DELETE", fmt.Sprintf(
"%s/post/%s/image/%s", "%s/post/%s/image/%s",
ts.URL, ts.URL,


+ 40
- 29
Api/Posts.go View File

@ -8,11 +8,11 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/Auth"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/JsonSerialization" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/JsonSerialization"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"github.com/gorilla/mux"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Util"
) )
func getPosts(w http.ResponseWriter, r *http.Request) { func getPosts(w http.ResponseWriter, r *http.Request) {
@ -29,27 +29,27 @@ func getPosts(w http.ResponseWriter, r *http.Request) {
page, err = strconv.Atoi(values.Get("page")) page, err = strconv.Atoi(values.Get("page"))
if err != nil { if err != nil {
log.Println("Could not parse page url argument") log.Println("Could not parse page url argument")
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
page, err = strconv.Atoi(values.Get("pageSize")) page, err = strconv.Atoi(values.Get("pageSize"))
if err != nil { if err != nil {
log.Println("Could not parse pageSize url argument") log.Println("Could not parse pageSize url argument")
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
posts, err = Database.GetPosts(page, pageSize) posts, err = Database.GetPosts(page, pageSize)
if err != nil { if err != nil {
log.Printf("An error occured: %s\n", err.Error()) log.Printf("An error occured: %s\n", err.Error())
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
returnJson, err = json.MarshalIndent(posts, "", " ") returnJson, err = json.MarshalIndent(posts, "", " ")
if err != nil { if err != nil {
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
@ -68,13 +68,13 @@ func getFrontPagePosts(w http.ResponseWriter, r *http.Request) {
posts, err = Database.GetFrontPagePosts() posts, err = Database.GetFrontPagePosts()
if err != nil { if err != nil {
log.Printf("An error occured: %s\n", err.Error()) log.Printf("An error occured: %s\n", err.Error())
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
returnJson, err = json.MarshalIndent(posts, "", " ") returnJson, err = json.MarshalIndent(posts, "", " ")
if err != nil { if err != nil {
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
@ -90,14 +90,14 @@ func getPost(w http.ResponseWriter, r *http.Request) {
err error err error
) )
postData, err = getPostById(w, r)
postData, err = Util.GetPostById(w, r)
if err != nil { if err != nil {
return return
} }
returnJson, err = json.MarshalIndent(postData, "", " ") returnJson, err = json.MarshalIndent(postData, "", " ")
if err != nil { if err != nil {
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
@ -114,14 +114,16 @@ func createPost(w http.ResponseWriter, r *http.Request) {
err error err error
) )
// TODO: Add auth
log.Printf("Posts handler recieved %s request", r.Method)
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
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())
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
@ -134,19 +136,19 @@ func createPost(w http.ResponseWriter, r *http.Request) {
}, false) }, false)
if err != nil { if err != nil {
log.Printf("Invalid data provided to posts API: %s\n", err.Error()) log.Printf("Invalid data provided to posts API: %s\n", err.Error())
JsonReturn(w, 405, "Invalid data")
Util.JsonReturn(w, 405, "Invalid data")
return return
} }
err = Database.CreatePost(&postData) err = Database.CreatePost(&postData)
if err != nil { if err != nil {
JsonReturn(w, 405, "Invalid data")
Util.JsonReturn(w, 405, "Invalid data")
} }
returnJson, err = json.MarshalIndent(postData, "", " ") returnJson, err = json.MarshalIndent(postData, "", " ")
if err != nil { if err != nil {
log.Printf("An error occured: %s\n", err.Error()) log.Printf("An error occured: %s\n", err.Error())
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
@ -160,45 +162,48 @@ func updatePost(w http.ResponseWriter, r *http.Request) {
postData Models.Post postData Models.Post
requestBody []byte requestBody []byte
returnJson []byte returnJson []byte
urlVars map[string]string
id string id string
ok bool
err error err error
) )
urlVars = mux.Vars(r)
id, ok = urlVars["postID"]
if !ok {
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
id, err = Util.GetPostId(r)
if err != nil {
log.Printf("Error encountered getting id\n") log.Printf("Error encountered getting id\n")
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
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())
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
postData, err = JsonSerialization.DeserializePost(requestBody, []string{}, true) postData, err = JsonSerialization.DeserializePost(requestBody, []string{}, true)
if err != nil { if err != nil {
log.Printf("Invalid data provided to posts API: %s\n", err.Error()) log.Printf("Invalid data provided to posts API: %s\n", err.Error())
JsonReturn(w, 405, "Invalid data")
Util.JsonReturn(w, 405, "Invalid data")
return return
} }
postData, err = Database.UpdatePost(id, &postData) postData, err = Database.UpdatePost(id, &postData)
if err != nil { if err != nil {
log.Printf("An error occured: %s\n", err.Error()) log.Printf("An error occured: %s\n", err.Error())
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
returnJson, err = json.MarshalIndent(postData, "", " ") returnJson, err = json.MarshalIndent(postData, "", " ")
if err != nil { if err != nil {
log.Printf("An error occured: %s\n", err.Error()) log.Printf("An error occured: %s\n", err.Error())
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }
@ -213,7 +218,13 @@ func deletePost(w http.ResponseWriter, r *http.Request) {
err error err error
) )
postData, err = getPostById(w, r)
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
postData, err = Util.GetPostById(w, r)
if err != nil { if err != nil {
return return
} }
@ -221,7 +232,7 @@ func deletePost(w http.ResponseWriter, r *http.Request) {
err = Database.DeletePost(&postData) err = Database.DeletePost(&postData)
if err != nil { if err != nil {
log.Printf("An error occured: %s\n", err.Error()) log.Printf("An error occured: %s\n", err.Error())
JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 500, "An error occured")
return return
} }


+ 76
- 84
Api/Posts_test.go View File

@ -3,8 +3,6 @@ package Api
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"log"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
@ -17,7 +15,6 @@ import (
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"gorm.io/gorm"
) )
var ( var (
@ -33,27 +30,21 @@ func init() {
panic(err) panic(err)
} }
log.SetOutput(ioutil.Discard)
Database.Init()
Database.InitTest()
r = mux.NewRouter() r = mux.NewRouter()
} }
func Test_getPosts(t *testing.T) {
t.Log("Testing getPosts...")
r.HandleFunc("/post", getPosts).Methods("GET")
func createTestPost() (Models.Post, error) {
ts := httptest.NewServer(r)
defer ts.Close()
userData, err := createTestUser(true)
var err error
for i := 0; i < 20; i++ {
postData := Models.Post{ postData := Models.Post{
UserID: userData.ID,
Title: "Test post", Title: "Test post",
Content: "Test content", Content: "Test content",
FrontPage: true, FrontPage: true,
Order: i,
Order: 1,
PostLinks: []Models.PostLink{ PostLinks: []Models.PostLink{
{ {
Type: "Facebook", Type: "Facebook",
@ -63,15 +54,21 @@ func Test_getPosts(t *testing.T) {
} }
err = Database.CreatePost(&postData) err = Database.CreatePost(&postData)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
return postData, err
} }
defer Database.DB.
Session(&gorm.Session{FullSaveAssociations: true}).
Unscoped().
Select("PostLinks").
Delete(&postData)
func Test_getPosts(t *testing.T) {
t.Log("Testing getPosts...")
r.HandleFunc("/post", getPosts).Methods("GET")
ts := httptest.NewServer(r)
defer ts.Close()
var err error
for i := 0; i < 20; i++ {
createTestPost()
} }
res, err := http.Get(ts.URL + "/post?page=1&pageSize=10") res, err := http.Get(ts.URL + "/post?page=1&pageSize=10")
@ -103,30 +100,12 @@ func Test_getPost(t *testing.T) {
defer ts.Close() 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)
postData, err := createTestPost()
if err != nil { if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error()) t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
} }
defer Database.DB.
Session(&gorm.Session{FullSaveAssociations: true}).
Unscoped().
Select("PostLinks").
Delete(&postData)
res, err := http.Get(fmt.Sprintf( res, err := http.Get(fmt.Sprintf(
"%s/post/%s", "%s/post/%s",
ts.URL, ts.URL,
@ -135,16 +114,36 @@ func Test_getPost(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error()) t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
} }
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode)
t.FailNow()
} }
getPostData := new(Models.Post) getPostData := new(Models.Post)
err = json.NewDecoder(res.Body).Decode(getPostData) err = json.NewDecoder(res.Body).Decode(getPostData)
if err != nil { if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error()) t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
if getPostData.Title != "Test post" {
t.Errorf("Expected title \"Test post\", recieved %s", getPostData.Title)
t.FailNow()
} }
if getPostData.Content != "Test content" {
t.Errorf("Expected content \"Test content\", recieved %s", getPostData.Content)
t.FailNow()
}
if len(getPostData.PostLinks) != 1 {
t.Errorf("Expected len(PostLinks) == 1, recieved %d", len(getPostData.PostLinks))
t.FailNow()
}
} }
func Test_createPost(t *testing.T) { func Test_createPost(t *testing.T) {
@ -156,8 +155,15 @@ func Test_createPost(t *testing.T) {
defer ts.Close() defer ts.Close()
c, u, err := login()
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
return
}
postJson := ` postJson := `
{ {
"user_id": "%s",
"title": "Test post", "title": "Test post",
"content": "Test content", "content": "Test content",
"front_page": true, "front_page": true,
@ -169,12 +175,25 @@ func Test_createPost(t *testing.T) {
} }
` `
res, err := http.Post(ts.URL+"/post", "application/json", strings.NewReader(postJson))
postJson = fmt.Sprintf(postJson, u.ID.String())
req, err := http.NewRequest("POST", ts.URL+"/post", strings.NewReader(postJson))
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
return
}
req.AddCookie(c)
res, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error()) t.Errorf("Expected nil, recieved %s", err.Error())
return
} }
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode)
return
} }
postData := new(Models.Post) postData := new(Models.Post)
@ -183,12 +202,6 @@ func Test_createPost(t *testing.T) {
t.Errorf("Expected nil, recieved %s", err.Error()) t.Errorf("Expected nil, recieved %s", err.Error())
} }
defer Database.DB.
Session(&gorm.Session{FullSaveAssociations: true}).
Unscoped().
Select("PostLinks").
Delete(&postData)
if postData.Title != "Test post" { if postData.Title != "Test post" {
t.Errorf("Expected title \"Test post\", recieved \"%s\"", postData.Title) t.Errorf("Expected title \"Test post\", recieved \"%s\"", postData.Title)
} }
@ -206,30 +219,18 @@ func Test_deletePost(t *testing.T) {
defer ts.Close() 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/",
},
},
c, _, err := login()
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
return
} }
err := Database.CreatePost(&postData)
postData, err := createTestPost()
if err != nil { if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error()) t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
} }
defer Database.DB.
Session(&gorm.Session{FullSaveAssociations: true}).
Unscoped().
Select("PostLinks").
Delete(&postData)
req, err := http.NewRequest("DELETE", fmt.Sprintf( req, err := http.NewRequest("DELETE", fmt.Sprintf(
"%s/post/%s", "%s/post/%s",
ts.URL, ts.URL,
@ -240,6 +241,8 @@ func Test_deletePost(t *testing.T) {
t.Errorf("Expected nil, recieved %s", err.Error()) t.Errorf("Expected nil, recieved %s", err.Error())
} }
req.AddCookie(c)
// Fetch Request // Fetch Request
res, err := http.DefaultClient.Do(req) res, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
@ -262,20 +265,13 @@ func Test_updatePost(t *testing.T) {
defer ts.Close() 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/",
},
},
c, _, err := login()
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
return
} }
err := Database.CreatePost(&postData)
postData, err := createTestPost()
if err != nil { if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error()) t.Errorf("Expected nil, recieved %s", err.Error())
} }
@ -298,6 +294,8 @@ func Test_updatePost(t *testing.T) {
t.Errorf("Expected nil, recieved %s", err.Error()) t.Errorf("Expected nil, recieved %s", err.Error())
} }
req.AddCookie(c)
// Fetch Request // Fetch Request
res, err := http.DefaultClient.Do(req) res, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
@ -315,12 +313,6 @@ func Test_updatePost(t *testing.T) {
t.Errorf("Expected nil, recieved %s", err.Error()) t.Errorf("Expected nil, recieved %s", err.Error())
} }
defer Database.DB.
Session(&gorm.Session{FullSaveAssociations: true}).
Unscoped().
Select("PostLinks").
Delete(&postData)
if updatePostData.Content != "New test content" { if updatePostData.Content != "New test content" {
t.Errorf("Expected \"New test content\", recieved %s", updatePostData.Content) t.Errorf("Expected \"New test content\", recieved %s", updatePostData.Content)
} }


+ 17
- 2
Api/Routes.go View File

@ -3,6 +3,8 @@ package Api
import ( import (
"log" "log"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/Auth"
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
@ -17,15 +19,28 @@ func InitApiEndpoints() *mux.Router {
// Define routes for posts api // Define routes for posts api
router.HandleFunc("/post", getPosts).Methods("GET") router.HandleFunc("/post", getPosts).Methods("GET")
router.HandleFunc("/frontPagePosts", getFrontPagePosts).Methods("GET")
router.HandleFunc("/post", createPost).Methods("POST") router.HandleFunc("/post", createPost).Methods("POST")
router.HandleFunc("/post/{postID}", createPost).Methods("GET")
router.HandleFunc("/post/{postID}", getPost).Methods("GET")
router.HandleFunc("/post/{postID}", updatePost).Methods("PUT") router.HandleFunc("/post/{postID}", updatePost).Methods("PUT")
router.HandleFunc("/post/{postID}", deletePost).Methods("DELETE") router.HandleFunc("/post/{postID}", deletePost).Methods("DELETE")
router.HandleFunc("/frontPagePosts", getFrontPagePosts).Methods("GET")
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("/admin/user", getUsers).Methods("GET")
router.HandleFunc("/admin/user", createUser).Methods("POST")
router.HandleFunc("/admin/user/{userID}", getUser).Methods("GET")
router.HandleFunc("/admin/user/{userID}", updatePost).Methods("PUT")
router.HandleFunc("/admin/user/{userID}", deletePost).Methods("DELETE")
router.HandleFunc("/admin/user/{userID}/update-password", Auth.UpdatePassword).Methods("PUT")
// Define routes for authentication
router.HandleFunc("/admin/login", Auth.Login).Methods("POST")
router.HandleFunc("/admin/logout", Auth.Logout).Methods("GET")
//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


+ 221
- 0
Api/Users.go View File

@ -0,0 +1,221 @@
package Api
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"net/url"
"strconv"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/Auth"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/JsonSerialization"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Util"
)
func getUsers(w http.ResponseWriter, r *http.Request) {
var (
users []Models.User
returnJson []byte
values url.Values
page, pageSize int
err error
)
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
values = r.URL.Query()
page, err = strconv.Atoi(values.Get("page"))
if err != nil {
log.Println("Could not parse page url argument")
Util.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")
Util.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())
Util.JsonReturn(w, 500, "An error occured")
return
}
returnJson, err = json.MarshalIndent(users, "", " ")
if err != nil {
Util.JsonReturn(w, 500, "An error occured")
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
w.Write(returnJson)
}
func getUser(w http.ResponseWriter, r *http.Request) {
var (
userData Models.User
returnJson []byte
err error
)
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
userData, err = Util.GetUserById(w, r)
if err != nil {
return
}
returnJson, err = json.MarshalIndent(userData, "", " ")
if err != nil {
Util.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())
Util.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())
Util.JsonReturn(w, 405, "Invalid data")
return
}
err = Database.CheckUniqueEmail(userData.Email)
if err != nil {
Util.JsonReturn(w, 405, "invalid_email")
return
}
if userData.Password != userData.ConfirmPassword {
Util.JsonReturn(w, 405, "invalid_password")
return
}
userData.Password, err = Auth.HashPassword(userData.Password)
if err != nil {
Util.JsonReturn(w, 500, "An error occured")
return
}
err = Database.CreateUser(&userData)
if err != nil {
Util.JsonReturn(w, 500, "An error occured")
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
}
func updateUser(w http.ResponseWriter, r *http.Request) {
var (
currentUserData Models.User
userData Models.User
requestBody []byte
returnJson []byte
err error
)
currentUserData, err = Auth.CheckCookieCurrentUser(w, r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
requestBody, err = ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error encountered reading POST body: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
userData, err = JsonSerialization.DeserializeUser(requestBody, []string{}, true)
if err != nil {
log.Printf("Invalid data provided to users API: %s\n", err.Error())
Util.JsonReturn(w, 405, "Invalid data")
return
}
err = Database.UpdateUser(currentUserData.ID.String(), &userData)
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
returnJson, err = json.MarshalIndent(userData, "", " ")
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
w.Write(returnJson)
}
func deleteUser(w http.ResponseWriter, r *http.Request) {
var (
userData Models.User
err error
)
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
userData, err = Util.GetUserById(w, r)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
err = Database.DeleteUser(&userData)
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
}

+ 372
- 0
Api/Users_test.go View File

@ -0,0 +1,372 @@
package Api
import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/http"
"net/http/httptest"
"os"
"path"
"runtime"
"strings"
"testing"
"time"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/Auth"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"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)
}
Database.InitTest()
r = mux.NewRouter()
}
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func randString(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
func createTestUser(random bool) (Models.User, error) {
now := time.Now()
email := "email@email.com"
if random {
email = fmt.Sprintf("%s@email.com", randString(16))
}
password, err := Auth.HashPassword("password")
if err != nil {
return Models.User{}, err
}
userData := Models.User{
Email: email,
Password: password,
LastLogin: &now,
FirstName: "Hugh",
LastName: "Mann",
}
err = Database.CreateUser(&userData)
return userData, err
}
func login() (*http.Cookie, Models.User, error) {
var (
c *http.Cookie
u Models.User
)
r.HandleFunc("/admin/login", Auth.Login).Methods("POST")
ts := httptest.NewServer(r)
defer ts.Close()
u, err := createTestUser(true)
if err != nil {
return c, u, err
}
postJson := `
{
"email": "%s",
"password": "password"
}
`
postJson = fmt.Sprintf(postJson, u.Email)
res, err := http.Post(ts.URL+"/admin/login", "application/json", strings.NewReader(postJson))
if err != nil {
return c, u, err
}
if res.StatusCode != http.StatusOK {
return c, u, errors.New("Invalid res.StatusCode")
}
if len(res.Cookies()) != 1 {
return c, u, errors.New("Invalid cookies length")
}
return res.Cookies()[0], u, nil
}
func Test_getUser(t *testing.T) {
t.Log("Testing getUser...")
r.HandleFunc("/user/{userID}", getUser).Methods("GET")
ts := httptest.NewServer(r)
defer ts.Close()
c, u, err := login()
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
req, err := http.NewRequest("GET", fmt.Sprintf(
"%s/user/%s",
ts.URL,
u.ID,
), nil)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
req.AddCookie(c)
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
if res.StatusCode != http.StatusOK {
t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode)
t.FailNow()
}
getUserData := new(Models.User)
err = json.NewDecoder(res.Body).Decode(getUserData)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
if getUserData.Email != u.Email {
t.Errorf("Expected email \"%s\", recieved %s", u.Email, getUserData.Email)
t.FailNow()
}
if getUserData.FirstName != u.FirstName {
t.Errorf("Expected email \"%s\", recieved %s", u.FirstName, getUserData.FirstName)
t.FailNow()
}
if getUserData.LastName != u.LastName {
t.Errorf("Expected email \"%s\", recieved %s", u.LastName, getUserData.LastName)
t.FailNow()
}
}
func Test_getUsers(t *testing.T) {
t.Log("Testing getUsers...")
r.HandleFunc("/user", getUsers).Methods("GET")
ts := httptest.NewServer(r)
defer ts.Close()
c, _, err := login()
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
for i := 0; i < 20; i++ {
createTestUser(true)
}
req, err := http.NewRequest("GET", ts.URL+"/user?page=1&pageSize=10", nil)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
req.AddCookie(c)
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
if res.StatusCode != http.StatusOK {
t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode)
t.FailNow()
}
getUsersData := new([]Models.User)
err = json.NewDecoder(res.Body).Decode(getUsersData)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
if len(*getUsersData) != 10 {
t.Errorf("Expected 10, recieved %d", len(*getUsersData))
t.FailNow()
}
}
func Test_createUser(t *testing.T) {
t.Log("Testing createUser...")
r.HandleFunc("/user", createUser).Methods("POST")
ts := httptest.NewServer(r)
defer ts.Close()
email := fmt.Sprintf("%s@email.com", randString(16))
postJson := `
{
"email": "%s",
"password": "password",
"confirm_password": "password",
"first_name": "Hugh",
"last_name": "Mann"
}
`
postJson = fmt.Sprintf(postJson, email)
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
}
}
func Test_updateUser(t *testing.T) {
t.Log("Testing updateUser...")
r.HandleFunc("/user/{userID}", updateUser).Methods("PUT")
ts := httptest.NewServer(r)
defer ts.Close()
c, u, err := login()
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
email := fmt.Sprintf("%s@email.com", randString(16))
postJson := `
{
"email": "%s",
"first_name": "first",
"last_name": "last"
}
`
postJson = fmt.Sprintf(postJson, email)
req, err := http.NewRequest("PUT", fmt.Sprintf(
"%s/user/%s",
ts.URL,
u.ID,
), strings.NewReader(postJson))
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
}
req.AddCookie(c)
// Fetch Request
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode)
}
updateUserData := new(Models.User)
err = json.NewDecoder(res.Body).Decode(updateUserData)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
}
if updateUserData.Email != email {
t.Errorf("Expected email \"%s\", recieved %s", email, updateUserData.Email)
}
if updateUserData.FirstName != "first" {
t.Errorf("Expected FirstName \"first\", recieved %s", updateUserData.FirstName)
}
if updateUserData.LastName != "last" {
t.Errorf("Expected LastName \"last\", recieved %s", updateUserData.LastName)
}
}
func Test_deleteUser(t *testing.T) {
t.Log("Testing deleteUser...")
r.HandleFunc("/user/{userID}", deleteUser).Methods("DELETE")
ts := httptest.NewServer(r)
defer ts.Close()
c, _, err := login()
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
userData, err := createTestUser(true)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
t.FailNow()
}
req, err := http.NewRequest("DELETE", fmt.Sprintf(
"%s/user/%s",
ts.URL,
userData.ID,
), nil)
if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error())
}
req.AddCookie(c)
// Fetch Request
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)
}
}

+ 36
- 12
Database/Init.go View File

@ -10,13 +10,29 @@ import (
) )
const dbUrl = "postgres://postgres:@localhost:5432/sudden_impact_records" const dbUrl = "postgres://postgres:@localhost:5432/sudden_impact_records"
const dbTestUrl = "postgres://postgres:@localhost:5432/sudden_impact_records_test"
var ( var (
DB *gorm.DB DB *gorm.DB
) )
func GetModels() []interface{} {
return []interface{}{
&Models.User{},
&Models.PostImage{},
&Models.PostVideo{},
&Models.PostAudio{},
&Models.PostLink{},
&Models.Post{},
&Models.SubscriptionEmailAttachment{},
&Models.SubscriptionEmail{},
&Models.Subscription{},
}
}
func Init() { func Init() {
var ( var (
model interface{}
err error err error
) )
@ -28,19 +44,27 @@ func Init() {
log.Fatalln(err) log.Fatalln(err)
} }
log.Println("Running AutoMigrate on Post tables...")
log.Println("Running AutoMigrate...")
for _, model = range GetModels() {
DB.AutoMigrate(model)
}
}
func InitTest() {
var (
model interface{}
err error
)
// Post tables
DB.AutoMigrate(&Models.PostImage{})
DB.AutoMigrate(&Models.PostVideo{})
DB.AutoMigrate(&Models.PostAudio{})
DB.AutoMigrate(&Models.PostLink{})
DB.AutoMigrate(&Models.Post{})
DB, err = gorm.Open(postgres.Open(dbTestUrl), &gorm.Config{})
log.Println("Running AutoMigrate on Subscription tables...")
if err != nil {
log.Fatalln(err)
}
// Email subscription tables
DB.AutoMigrate(&Models.SubscriptionEmailAttachment{})
DB.AutoMigrate(&Models.SubscriptionEmail{})
DB.AutoMigrate(&Models.Subscription{})
for _, model = range GetModels() {
DB.Migrator().DropTable(model)
DB.AutoMigrate(model)
}
} }

+ 127
- 0
Database/Users.go View File

@ -0,0 +1,127 @@
package Database
import (
"errors"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func GetUserById(id string) (Models.User, error) {
var (
userData Models.User
err error
)
err = DB.Preload(clause.Associations).
First(&userData, "id = ?", id).
Error
userData.Password = ""
return userData, err
}
func GetUserByEmail(email string) (Models.User, error) {
var (
userData Models.User
err error
)
err = DB.Preload(clause.Associations).
First(&userData, "email = ?", email).
Error
return userData, err
}
func GetUsers(page, pageSize int) ([]Models.User, error) {
var (
users []Models.User
i int
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
for i, _ = range users {
users[i].Password = ""
}
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 {
var (
err error
)
err = DB.Session(&gorm.Session{FullSaveAssociations: true}).
Create(userData).
Error
userData.Password = ""
return err
}
func UpdateUser(id string, userData *Models.User) error {
var (
err error
)
err = DB.Model(&Models.User{}).
Select("*").
Omit("id", "created_at", "updated_at", "deleted_at").
Where("id = ?", id).
Updates(userData).
Error
userData.Password = ""
return err
}
func DeleteUser(userData *Models.User) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Delete(userData).
Error
}

+ 1
- 0
Models/Posts.go View File

@ -6,6 +6,7 @@ import (
type Post struct { type Post struct {
Base Base
UserID uuid.UUID `gorm:"type:uuid;column:user_id;not null;" json:"user_id"`
Title string `gorm:"not null" json:"title"` Title string `gorm:"not null" json:"title"`
Content string `gorm:"not null" json:"content"` Content string `gorm:"not null" json:"content"`
FrontPage bool `gorm:"not null;type:boolean" json:"front_page"` FrontPage bool `gorm:"not null;type:boolean" json:"front_page"`


+ 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,omitempty"`
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"`
}

Api/PostHelper.go → Util/PostHelper.go View File


Api/PostImageHelper.go → Util/PostImageHelper.go View File


Api/ReturnJson.go → Util/ReturnJson.go View File


+ 51
- 0
Util/UserHelper.go View File

@ -0,0 +1,51 @@
package Util
import (
"errors"
"log"
"net/http"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"github.com/gorilla/mux"
)
func GetUserId(r *http.Request) (string, error) {
var (
urlVars map[string]string
id string
ok bool
)
urlVars = mux.Vars(r)
id, ok = urlVars["userID"]
if !ok {
return id, errors.New("Could not get id")
}
return id, nil
}
func GetUserById(w http.ResponseWriter, r *http.Request) (Models.User, error) {
var (
postData Models.User
id string
err error
)
id, err = GetUserId(r)
if err != nil {
log.Printf("Error encountered getting id\n")
JsonReturn(w, 500, "An error occured")
return postData, err
}
postData, err = Database.GetUserById(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
}

Loading…
Cancel
Save