initial commit
This commit is contained in:
57
models/config.go
Normal file
57
models/config.go
Normal file
@ -0,0 +1,57 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/go-ini/ini"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ConfigStruct holds the config struct
|
||||
type ConfigStruct struct {
|
||||
Database struct {
|
||||
Type string
|
||||
Host string
|
||||
User string
|
||||
Password string
|
||||
Database string
|
||||
Path string
|
||||
ShowQueries bool
|
||||
}
|
||||
|
||||
JWTLoginSecret []byte
|
||||
Interface string
|
||||
}
|
||||
|
||||
// Config holds the configuration for the program
|
||||
var Config = new(ConfigStruct)
|
||||
|
||||
// SetConfig initianlises the config and publishes it for other functions to use
|
||||
func SetConfig() (err error) {
|
||||
|
||||
// File Checks
|
||||
if _, err := os.Stat("config.ini"); os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load the config
|
||||
cfg, err := ini.Load("config.ini")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Map the config to our struct
|
||||
err = cfg.MapTo(Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set default value for interface to listen on
|
||||
Config.Interface = cfg.Section("General").Key("Interface").String()
|
||||
if Config.Interface == "" {
|
||||
Config.Interface = ":8080"
|
||||
}
|
||||
|
||||
// JWT secret
|
||||
Config.JWTLoginSecret = []byte(cfg.Section("General").Key("JWTSecret").String())
|
||||
|
||||
return nil
|
||||
}
|
61
models/config_test.go
Normal file
61
models/config_test.go
Normal file
@ -0,0 +1,61 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSetConfig(t *testing.T) {
|
||||
// Create test database
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
// This should fail as it is looking for a nonexistent config
|
||||
err := SetConfig()
|
||||
assert.Error(t, err)
|
||||
|
||||
// Write an invalid config
|
||||
configString := `[General
|
||||
JWTSecret = Supersecret
|
||||
Interface = ; This should make it automatically to :8080
|
||||
|
||||
[Database
|
||||
Type = sqlite
|
||||
Path = ./library.db`
|
||||
err = ioutil.WriteFile("config.ini", []byte(configString), 0644)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test setConfig (should fail as we're trying to parse an invalid config)
|
||||
err = SetConfig()
|
||||
assert.Error(t, err)
|
||||
|
||||
// Delete the invalid file
|
||||
err = os.Remove("config.ini")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Write a fake config
|
||||
configString = `[General]
|
||||
JWTSecret = Supersecret
|
||||
Interface = ; This should make it automatically to :8080
|
||||
|
||||
[Database]
|
||||
Type = sqlite
|
||||
Path = ./library.db`
|
||||
err = ioutil.WriteFile("config.ini", []byte(configString), 0644)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test setConfig
|
||||
err = SetConfig()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check for the values
|
||||
assert.Equal(t, []byte("Supersecret"), Config.JWTLoginSecret)
|
||||
assert.Equal(t, string(":8080"), Config.Interface)
|
||||
assert.Equal(t, string("sqlite"), Config.Database.Type)
|
||||
assert.Equal(t, string("./library.db"), Config.Database.Path)
|
||||
|
||||
// Remove the dummy config
|
||||
err = os.Remove("config.ini")
|
||||
assert.NoError(t, err)
|
||||
}
|
177
models/error.go
Normal file
177
models/error.go
Normal file
@ -0,0 +1,177 @@
|
||||
package models
|
||||
|
||||
import "fmt"
|
||||
|
||||
// =====================
|
||||
// User Operation Errors
|
||||
// =====================
|
||||
|
||||
// ErrUsernameExists represents a "UsernameAlreadyExists" kind of error.
|
||||
type ErrUsernameExists struct {
|
||||
UserID int64
|
||||
Username string
|
||||
}
|
||||
|
||||
// IsErrUsernameExists checks if an error is a ErrUsernameExists.
|
||||
func IsErrUsernameExists(err error) bool {
|
||||
_, ok := err.(ErrUsernameExists)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUsernameExists) Error() string {
|
||||
return fmt.Sprintf("a user with this username does already exist [user id: %d, username: %s]", err.UserID, err.Username)
|
||||
}
|
||||
|
||||
// ErrUserEmailExists represents a "UserEmailExists" kind of error.
|
||||
type ErrUserEmailExists struct {
|
||||
UserID int64
|
||||
Email string
|
||||
}
|
||||
|
||||
// IsErrUserEmailExists checks if an error is a ErrUserEmailExists.
|
||||
func IsErrUserEmailExists(err error) bool {
|
||||
_, ok := err.(ErrUserEmailExists)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserEmailExists) Error() string {
|
||||
return fmt.Sprintf("a user with this email does already exist [user id: %d, email: %s]", err.UserID, err.Email)
|
||||
}
|
||||
|
||||
// ErrNoUsername represents a "UsernameAlreadyExists" kind of error.
|
||||
type ErrNoUsername struct {
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// IsErrNoUsername checks if an error is a ErrUsernameExists.
|
||||
func IsErrNoUsername(err error) bool {
|
||||
_, ok := err.(ErrNoUsername)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrNoUsername) Error() string {
|
||||
return fmt.Sprintf("you need to specify a username [user id: %d]", err.UserID)
|
||||
}
|
||||
|
||||
// ErrNoUsernamePassword represents a "NoUsernamePassword" kind of error.
|
||||
type ErrNoUsernamePassword struct{}
|
||||
|
||||
// IsErrNoUsernamePassword checks if an error is a ErrNoUsernamePassword.
|
||||
func IsErrNoUsernamePassword(err error) bool {
|
||||
_, ok := err.(ErrNoUsernamePassword)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrNoUsernamePassword) Error() string {
|
||||
return fmt.Sprintf("you need to specify a username and a password")
|
||||
}
|
||||
|
||||
// ErrUserDoesNotExist represents a "UserDoesNotExist" kind of error.
|
||||
type ErrUserDoesNotExist struct {
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// IsErrUserDoesNotExist checks if an error is a ErrUserDoesNotExist.
|
||||
func IsErrUserDoesNotExist(err error) bool {
|
||||
_, ok := err.(ErrUserDoesNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserDoesNotExist) Error() string {
|
||||
return fmt.Sprintf("this user does not exist [user id: %d]", err.UserID)
|
||||
}
|
||||
|
||||
// ErrCouldNotGetUserID represents a "ErrCouldNotGetUserID" kind of error.
|
||||
type ErrCouldNotGetUserID struct{}
|
||||
|
||||
// IsErrCouldNotGetUserID checks if an error is a ErrCouldNotGetUserID.
|
||||
func IsErrCouldNotGetUserID(err error) bool {
|
||||
_, ok := err.(ErrCouldNotGetUserID)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrCouldNotGetUserID) Error() string {
|
||||
return fmt.Sprintf("could not get user ID")
|
||||
}
|
||||
|
||||
// ErrCannotDeleteLastUser represents a "ErrCannotDeleteLastUser" kind of error.
|
||||
type ErrCannotDeleteLastUser struct{}
|
||||
|
||||
// IsErrCannotDeleteLastUser checks if an error is a ErrCannotDeleteLastUser.
|
||||
func IsErrCannotDeleteLastUser(err error) bool {
|
||||
_, ok := err.(ErrCannotDeleteLastUser)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrCannotDeleteLastUser) Error() string {
|
||||
return fmt.Sprintf("cannot delete last user")
|
||||
}
|
||||
|
||||
// ===================
|
||||
// Empty things errors
|
||||
// ===================
|
||||
|
||||
// ErrIDCannotBeZero represents a "IDCannotBeZero" kind of error. Used if an ID (of something, not defined) is 0 where it should not.
|
||||
type ErrIDCannotBeZero struct{}
|
||||
|
||||
// IsErrIDCannotBeZero checks if an error is a ErrIDCannotBeZero.
|
||||
func IsErrIDCannotBeZero(err error) bool {
|
||||
_, ok := err.(ErrIDCannotBeZero)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrIDCannotBeZero) Error() string {
|
||||
return fmt.Sprintf("ID cannot be 0")
|
||||
}
|
||||
|
||||
// ErrAuthorCannotBeEmpty represents a "AuthorCannotBeEmpty" kind of error.
|
||||
type ErrAuthorCannotBeEmpty struct{}
|
||||
|
||||
// IsErrAuthorCannotBeEmpty checks if an error is a ErrAuthorCannotBeEmpty.
|
||||
func IsErrAuthorCannotBeEmpty(err error) bool {
|
||||
_, ok := err.(ErrAuthorCannotBeEmpty)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrAuthorCannotBeEmpty) Error() string {
|
||||
return fmt.Sprintf("author cannot be empty")
|
||||
}
|
||||
|
||||
// ErrItemTitleCannotBeEmpty represents a "ErrItemTitleCannotBeEmpty" kind of error.
|
||||
type ErrItemTitleCannotBeEmpty struct{}
|
||||
|
||||
// IsErrItemTitleCannotBeEmpty checks if an error is a ErrItemTitleCannotBeEmpty.
|
||||
func IsErrItemTitleCannotBeEmpty(err error) bool {
|
||||
_, ok := err.(ErrItemTitleCannotBeEmpty)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrItemTitleCannotBeEmpty) Error() string {
|
||||
return fmt.Sprintf("title cannot be empty")
|
||||
}
|
||||
|
||||
// ErrBookTitleCannotBeEmpty represents a "ErrBookTitleCannotBeEmpty" kind of error.
|
||||
type ErrBookTitleCannotBeEmpty struct{}
|
||||
|
||||
// IsErrBookTitleCannotBeEmpty checks if an error is a ErrBookTitleCannotBeEmpty.
|
||||
func IsErrBookTitleCannotBeEmpty(err error) bool {
|
||||
_, ok := err.(ErrBookTitleCannotBeEmpty)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrBookTitleCannotBeEmpty) Error() string {
|
||||
return fmt.Sprintf("the book should at least have a title")
|
||||
}
|
||||
|
||||
// ErrNoPublisherName represents a "ErrNoPublisherName" kind of error.
|
||||
type ErrNoPublisherName struct{}
|
||||
|
||||
// IsErrNoPublisherName checks if an error is a ErrNoPublisherName.
|
||||
func IsErrNoPublisherName(err error) bool {
|
||||
_, ok := err.(ErrNoPublisherName)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrNoPublisherName) Error() string {
|
||||
return fmt.Sprintf("you need at least a name to insert a new publisher")
|
||||
}
|
7
models/fixtures/users.yml
Normal file
7
models/fixtures/users.yml
Normal file
@ -0,0 +1,7 @@
|
||||
-
|
||||
id: 1
|
||||
name: 'John Doe'
|
||||
username: 'user1'
|
||||
password: '1234'
|
||||
email: 'johndoe@example.com'
|
||||
is_admin: true
|
7
models/main_test.go
Normal file
7
models/main_test.go
Normal file
@ -0,0 +1,7 @@
|
||||
package models
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
MainTest(m, "..")
|
||||
}
|
6
models/message.go
Normal file
6
models/message.go
Normal file
@ -0,0 +1,6 @@
|
||||
package models
|
||||
|
||||
// Message is a standard message
|
||||
type Message struct {
|
||||
Message string `json:"message"`
|
||||
}
|
48
models/models.go
Normal file
48
models/models.go
Normal file
@ -0,0 +1,48 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "github.com/go-sql-driver/mysql" // Because.
|
||||
"github.com/go-xorm/core"
|
||||
"github.com/go-xorm/xorm"
|
||||
_ "github.com/mattn/go-sqlite3" // Because.
|
||||
)
|
||||
|
||||
var x *xorm.Engine
|
||||
|
||||
func getEngine() (*xorm.Engine, error) {
|
||||
// Use Mysql if set
|
||||
if Config.Database.Type == "mysql" {
|
||||
connStr := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=true",
|
||||
Config.Database.User, Config.Database.Password, Config.Database.Host, Config.Database.Database)
|
||||
return xorm.NewEngine("mysql", connStr)
|
||||
}
|
||||
|
||||
// Otherwise use sqlite
|
||||
path := Config.Database.Path
|
||||
if path == "" {
|
||||
path = "./db.db"
|
||||
}
|
||||
return xorm.NewEngine("sqlite3", path)
|
||||
}
|
||||
|
||||
// SetEngine sets the xorm.Engine
|
||||
func SetEngine() (err error) {
|
||||
x, err = getEngine()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to connect to database: %v", err)
|
||||
}
|
||||
|
||||
// Cache
|
||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
|
||||
x.SetDefaultCacher(cacher)
|
||||
|
||||
x.SetMapper(core.GonicMapper{})
|
||||
|
||||
// Sync dat shit
|
||||
x.Sync(&User{})
|
||||
|
||||
x.ShowSQL(Config.Database.ShowQueries)
|
||||
|
||||
return nil
|
||||
}
|
12
models/models_test.go
Normal file
12
models/models_test.go
Normal file
@ -0,0 +1,12 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSetEngine(t *testing.T) {
|
||||
Config.Database.Path = "file::memory:?cache=shared"
|
||||
err := SetEngine()
|
||||
assert.NoError(t, err)
|
||||
}
|
19
models/test_fixtures.go
Normal file
19
models/test_fixtures.go
Normal file
@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"gopkg.in/testfixtures.v2"
|
||||
)
|
||||
|
||||
var fixtures *testfixtures.Context
|
||||
|
||||
// InitFixtures initialize test fixtures for a test database
|
||||
func InitFixtures(helper testfixtures.Helper, dir string) (err error) {
|
||||
testfixtures.SkipDatabaseNameCheck(true)
|
||||
fixtures, err = testfixtures.NewFolder(x.DB().DB, helper, dir)
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadFixtures load fixtures for a test database
|
||||
func LoadFixtures() error {
|
||||
return fixtures.Load()
|
||||
}
|
48
models/unit_tests.go
Normal file
48
models/unit_tests.go
Normal file
@ -0,0 +1,48 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-xorm/core"
|
||||
"github.com/go-xorm/xorm"
|
||||
"gopkg.in/testfixtures.v2"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// MainTest creates the test engine
|
||||
func MainTest(m *testing.M, pathToRoot string) {
|
||||
var err error
|
||||
fixturesDir := filepath.Join(pathToRoot, "models", "fixtures")
|
||||
if err = createTestEngine(fixturesDir); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating test engine: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func createTestEngine(fixturesDir string) error {
|
||||
var err error
|
||||
x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
|
||||
//x, err = xorm.NewEngine("sqlite3", "db.db")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
x.SetMapper(core.GonicMapper{})
|
||||
|
||||
// Sync dat shit
|
||||
x.Sync(&User{})
|
||||
|
||||
// Show SQL-Queries if nessecary
|
||||
if os.Getenv("UNIT_TESTS_VERBOSE") == "1" {
|
||||
x.ShowSQL(true)
|
||||
}
|
||||
|
||||
return InitFixtures(&testfixtures.SQLite{}, fixturesDir)
|
||||
}
|
||||
|
||||
// PrepareTestDatabase load test fixtures into test database
|
||||
func PrepareTestDatabase() error {
|
||||
return LoadFixtures()
|
||||
}
|
93
models/user.go
Normal file
93
models/user.go
Normal file
@ -0,0 +1,93 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/labstack/echo"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// UserLogin Object to recive user credentials in JSON format
|
||||
type UserLogin struct {
|
||||
Username string `json:"username" form:"username"`
|
||||
Password string `json:"password" form:"password"`
|
||||
}
|
||||
|
||||
// User holds information about an user
|
||||
type User struct {
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"`
|
||||
Name string `xorm:"varchar(250)" json:"name"`
|
||||
Username string `xorm:"varchar(250) not null unique" json:"username"`
|
||||
Password string `xorm:"varchar(250) not null" json:"password"`
|
||||
Email string `xorm:"varchar(250)" json:"email"`
|
||||
IsAdmin bool `xorm:"tinyint(1) not null" json:"isAdmin"`
|
||||
Created int64 `xorm:"created" json:"created"`
|
||||
Updated int64 `xorm:"updated" json:"updated"`
|
||||
}
|
||||
|
||||
// TableName returns the table name for users
|
||||
func (User) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
// GetUserByID gets informations about a user by its ID
|
||||
func GetUserByID(id int64) (user User, exists bool, err error) {
|
||||
// Apparently xorm does otherwise look for all users but return only one, which leads to returing one even if the ID is 0
|
||||
if id == 0 {
|
||||
return User{}, false, nil
|
||||
}
|
||||
|
||||
return GetUser(User{ID: id})
|
||||
}
|
||||
|
||||
// GetUser gets a user object
|
||||
func GetUser(user User) (userOut User, exists bool, err error) {
|
||||
userOut = user
|
||||
exists, err = x.Get(&userOut)
|
||||
|
||||
if !exists {
|
||||
return User{}, false, ErrUserDoesNotExist{}
|
||||
}
|
||||
|
||||
return userOut, exists, err
|
||||
}
|
||||
|
||||
// CheckUserCredentials checks user credentials
|
||||
func CheckUserCredentials(u *UserLogin) (User, error) {
|
||||
|
||||
// Check if the user exists
|
||||
user, exists, err := GetUser(User{Username: u.Username})
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return User{}, ErrUserDoesNotExist{}
|
||||
}
|
||||
|
||||
// Check the users password
|
||||
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(u.Password))
|
||||
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetCurrentUser returns the current user based on its jwt token
|
||||
func GetCurrentUser(c echo.Context) (user User, err error) {
|
||||
jwtinf := c.Get("user").(*jwt.Token)
|
||||
claims := jwtinf.Claims.(jwt.MapClaims)
|
||||
userID, ok := claims["id"].(float64)
|
||||
if !ok {
|
||||
return user, ErrCouldNotGetUserID{}
|
||||
}
|
||||
user = User{
|
||||
ID: int64(userID),
|
||||
Name: claims["name"].(string),
|
||||
Email: claims["email"].(string),
|
||||
Username: claims["username"].(string),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
125
models/user_add_update.go
Normal file
125
models/user_add_update.go
Normal file
@ -0,0 +1,125 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// CreateUser creates a new user and inserts it into the database
|
||||
func CreateUser(user User, doer *User) (newUser User, err error) {
|
||||
|
||||
newUser = user
|
||||
|
||||
// Check if we have all needed informations
|
||||
if newUser.Password == "" || newUser.Username == "" {
|
||||
return User{}, ErrNoUsernamePassword{}
|
||||
}
|
||||
|
||||
// Check if the user already existst with that username
|
||||
existingUser, exists, err := GetUser(User{Username: newUser.Username})
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
if exists {
|
||||
return User{}, ErrUsernameExists{existingUser.ID, existingUser.Username}
|
||||
}
|
||||
|
||||
// Check if the user already existst with that email
|
||||
existingUser, exists, err = GetUser(User{Email: newUser.Email})
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
if exists {
|
||||
return User{}, ErrUserEmailExists{existingUser.ID, existingUser.Email}
|
||||
}
|
||||
|
||||
// Hash the password
|
||||
newUser.Password, err = hashPassword(user.Password)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
// Insert it
|
||||
_, err = x.Insert(newUser)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
// Get the full new User
|
||||
newUserOut, _, err := GetUser(newUser)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
return newUserOut, err
|
||||
}
|
||||
|
||||
// HashPassword hashes a password
|
||||
func hashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
// UpdateUser updates a user
|
||||
func UpdateUser(user User, doer *User) (updatedUser User, err error) {
|
||||
|
||||
// Check if it exists
|
||||
theUser, exists, err := GetUserByID(user.ID)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
if exists {
|
||||
// Check if we have at least a username
|
||||
if user.Username == "" {
|
||||
//return User{}, ErrNoUsername{user.ID}
|
||||
user.Username = theUser.Username // Dont change the username if we dont have one
|
||||
}
|
||||
|
||||
user.Password = theUser.Password // set the password to the one in the database to not accedently resetting it
|
||||
|
||||
// Update it
|
||||
_, err = x.Id(user.ID).Update(user)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
// Get the newly updated user
|
||||
updatedUser, _, err = GetUserByID(user.ID)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
return updatedUser, err
|
||||
}
|
||||
|
||||
return User{}, ErrUserDoesNotExist{user.ID}
|
||||
}
|
||||
|
||||
// UpdateUserPassword updates the password of a user
|
||||
func UpdateUserPassword(userID int64, newPassword string, doer *User) (err error) {
|
||||
|
||||
// Get all user details
|
||||
user, exists, err := GetUserByID(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return ErrUserDoesNotExist{userID}
|
||||
}
|
||||
|
||||
// Hash the new password and set it
|
||||
hashed, err := hashPassword(newPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.Password = hashed
|
||||
|
||||
// Update it
|
||||
_, err = x.Id(user.ID).Update(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
28
models/user_delete.go
Normal file
28
models/user_delete.go
Normal file
@ -0,0 +1,28 @@
|
||||
package models
|
||||
|
||||
// DeleteUserByID deletes a user by its ID
|
||||
func DeleteUserByID(id int64, doer *User) error {
|
||||
// Check if the id is 0
|
||||
if id == 0 {
|
||||
return ErrIDCannotBeZero{}
|
||||
}
|
||||
|
||||
// Check if there is > 1 user
|
||||
total, err := x.Count(User{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if total < 2 {
|
||||
return ErrCannotDeleteLastUser{}
|
||||
}
|
||||
|
||||
// Delete the user
|
||||
_, err = x.Id(id).Delete(&User{})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
147
models/user_test.go
Normal file
147
models/user_test.go
Normal file
@ -0,0 +1,147 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
// Create test database
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
// Get our doer
|
||||
doer, _, err := GetUserByID(1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Our dummy user for testing
|
||||
dummyuser := User{
|
||||
Name: "noooem, dief",
|
||||
Username: "testuu",
|
||||
Password: "1234",
|
||||
Email: "noone@example.com",
|
||||
IsAdmin: true,
|
||||
}
|
||||
|
||||
// Delete every preexisting user to have a fresh start
|
||||
_, err = x.Where("1 = 1").Delete(&User{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
allusers, err := ListUsers("")
|
||||
assert.NoError(t, err)
|
||||
for _, user := range allusers {
|
||||
// Delete it
|
||||
err := DeleteUserByID(user.ID, &doer)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// Create a new user
|
||||
createdUser, err := CreateUser(dummyuser, &doer)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a second new user
|
||||
createdUser2, err := CreateUser(User{Username: dummyuser.Username + "2", Email: dummyuser.Email + "m", Password: dummyuser.Password}, &doer)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check if it fails to create the same user again
|
||||
_, err = CreateUser(dummyuser, &doer)
|
||||
assert.Error(t, err)
|
||||
|
||||
// Check if it fails to create a user with just the same username
|
||||
_, err = CreateUser(User{Username: dummyuser.Username, Password: "fsdf"}, &doer)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrUsernameExists(err))
|
||||
|
||||
// Check if it fails to create one with the same email
|
||||
_, err = CreateUser(User{Username: "noone", Password: "1234", Email: dummyuser.Email}, &doer)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrUserEmailExists(err))
|
||||
|
||||
// Check if it fails to create a user without password and username
|
||||
_, err = CreateUser(User{}, &doer)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrNoUsernamePassword(err))
|
||||
|
||||
_, err = CreateUser(User{Name: "blub"}, &doer)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrNoUsernamePassword(err))
|
||||
|
||||
// Check if he exists
|
||||
theuser, exists, err := GetUser(createdUser)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
|
||||
// Get by his ID
|
||||
_, exists, err = GetUserByID(theuser.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
|
||||
// Passing 0 as ID should return an empty user
|
||||
_, exists, err = GetUserByID(0)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exists)
|
||||
|
||||
// Check the user credentials
|
||||
user, err := CheckUserCredentials(&UserLogin{"testuu", "1234"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, dummyuser.Name, user.Name)
|
||||
|
||||
// Check wrong password (should also fail)
|
||||
_, err = CheckUserCredentials(&UserLogin{"testuu", "12345"})
|
||||
assert.Error(t, err)
|
||||
|
||||
// Check usercredentials for a nonexistent user (should fail)
|
||||
_, err = CheckUserCredentials(&UserLogin{"dfstestuu", "1234"})
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrUserDoesNotExist(err))
|
||||
|
||||
// Update the user
|
||||
newname := "Test_te"
|
||||
uuser, err := UpdateUser(User{ID: theuser.ID, Name: newname, Password: "444444"}, &doer)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, newname, uuser.Name)
|
||||
assert.Equal(t, theuser.Password, uuser.Password) // Password should not change
|
||||
assert.Equal(t, theuser.Username, uuser.Username) // Username should not change either
|
||||
|
||||
// Try updating one which does not exist
|
||||
_, err = UpdateUser(User{ID: 99999, Username: "dg"}, &doer)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrUserDoesNotExist(err))
|
||||
|
||||
// Update a users password
|
||||
newpassword := "55555"
|
||||
err = UpdateUserPassword(theuser.ID, newpassword, &doer)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check if it was changed
|
||||
user, err = CheckUserCredentials(&UserLogin{theuser.Username, newpassword})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, newname, user.Name)
|
||||
|
||||
// Check if the searchterm works
|
||||
all, err := ListUsers("test")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, len(all) > 0)
|
||||
|
||||
all, err = ListUsers("")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, len(all) > 0)
|
||||
|
||||
// Try updating the password of a nonexistent user (should fail)
|
||||
err = UpdateUserPassword(9999, newpassword, &doer)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrUserDoesNotExist(err))
|
||||
|
||||
// Delete it
|
||||
err = DeleteUserByID(theuser.ID, &doer)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Try deleting one with ID = 0
|
||||
err = DeleteUserByID(0, &doer)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrIDCannotBeZero(err))
|
||||
|
||||
// Try delete the last user (Should fail)
|
||||
err = DeleteUserByID(createdUser2.ID, &doer)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrCannotDeleteLastUser(err))
|
||||
}
|
25
models/users_list.go
Normal file
25
models/users_list.go
Normal file
@ -0,0 +1,25 @@
|
||||
package models
|
||||
|
||||
// ListUsers returns a list with all users, filtered by an optional searchstring
|
||||
func ListUsers(searchterm string) (users []User, err error) {
|
||||
|
||||
if searchterm == "" {
|
||||
err = x.Find(&users)
|
||||
} else {
|
||||
err = x.
|
||||
Where("username LIKE ?", "%"+searchterm+"%").
|
||||
Or("name LIKE ?", "%"+searchterm+"%").
|
||||
Find(&users)
|
||||
}
|
||||
|
||||
// Obfuscate the password. Selecting everything except the password didn't work.
|
||||
for i := range users {
|
||||
users[i].Password = ""
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return []User{}, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
Reference in New Issue
Block a user