1
0

initial commit

This commit is contained in:
konrad
2018-06-10 11:11:41 +02:00
committed by kolaente
commit 479cf54ada
595 changed files with 427508 additions and 0 deletions

57
models/config.go Normal file
View 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
View 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
View 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")
}

View 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
View File

@ -0,0 +1,7 @@
package models
import "testing"
func TestMain(m *testing.M) {
MainTest(m, "..")
}

6
models/message.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}