595 changed files with 427508 additions and 0 deletions
@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE |
||||
Version 3, 29 June 2007 |
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |
||||
Everyone is permitted to copy and distribute verbatim copies |
||||
of this license document, but changing it is not allowed. |
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates |
||||
the terms and conditions of version 3 of the GNU General Public |
||||
License, supplemented by the additional permissions listed below. |
||||
|
||||
0. Additional Definitions. |
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser |
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU |
||||
General Public License. |
||||
|
||||
"The Library" refers to a covered work governed by this License, |
||||
other than an Application or a Combined Work as defined below. |
||||
|
||||
An "Application" is any work that makes use of an interface provided |
||||
by the Library, but which is not otherwise based on the Library. |
||||
Defining a subclass of a class defined by the Library is deemed a mode |
||||
of using an interface provided by the Library. |
||||
|
||||
A "Combined Work" is a work produced by combining or linking an |
||||
Application with the Library. The particular version of the Library |
||||
with which the Combined Work was made is also called the "Linked |
||||
Version". |
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the |
||||
Corresponding Source for the Combined Work, excluding any source code |
||||
for portions of the Combined Work that, considered in isolation, are |
||||
based on the Application, and not on the Linked Version. |
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the |
||||
object code and/or source code for the Application, including any data |
||||
and utility programs needed for reproducing the Combined Work from the |
||||
Application, but excluding the System Libraries of the Combined Work. |
||||
|
||||
1. Exception to Section 3 of the GNU GPL. |
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License |
||||
without being bound by section 3 of the GNU GPL. |
||||
|
||||
2. Conveying Modified Versions. |
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a |
||||
facility refers to a function or data to be supplied by an Application |
||||
that uses the facility (other than as an argument passed when the |
||||
facility is invoked), then you may convey a copy of the modified |
||||
version: |
||||
|
||||
a) under this License, provided that you make a good faith effort to |
||||
ensure that, in the event an Application does not supply the |
||||
function or data, the facility still operates, and performs |
||||
whatever part of its purpose remains meaningful, or |
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of |
||||
this License applicable to that copy. |
||||
|
||||
3. Object Code Incorporating Material from Library Header Files. |
||||
|
||||
The object code form of an Application may incorporate material from |
||||
a header file that is part of the Library. You may convey such object |
||||
code under terms of your choice, provided that, if the incorporated |
||||
material is not limited to numerical parameters, data structure |
||||
layouts and accessors, or small macros, inline functions and templates |
||||
(ten or fewer lines in length), you do both of the following: |
||||
|
||||
a) Give prominent notice with each copy of the object code that the |
||||
Library is used in it and that the Library and its use are |
||||
covered by this License. |
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license |
||||
document. |
||||
|
||||
4. Combined Works. |
||||
|
||||
You may convey a Combined Work under terms of your choice that, |
||||
taken together, effectively do not restrict modification of the |
||||
portions of the Library contained in the Combined Work and reverse |
||||
engineering for debugging such modifications, if you also do each of |
||||
the following: |
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that |
||||
the Library is used in it and that the Library and its use are |
||||
covered by this License. |
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license |
||||
document. |
||||
|
||||
c) For a Combined Work that displays copyright notices during |
||||
execution, include the copyright notice for the Library among |
||||
these notices, as well as a reference directing the user to the |
||||
copies of the GNU GPL and this license document. |
||||
|
||||
d) Do one of the following: |
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this |
||||
License, and the Corresponding Application Code in a form |
||||
suitable for, and under terms that permit, the user to |
||||
recombine or relink the Application with a modified version of |
||||
the Linked Version to produce a modified Combined Work, in the |
||||
manner specified by section 6 of the GNU GPL for conveying |
||||
Corresponding Source. |
||||
|
||||
1) Use a suitable shared library mechanism for linking with the |
||||
Library. A suitable mechanism is one that (a) uses at run time |
||||
a copy of the Library already present on the user's computer |
||||
system, and (b) will operate properly with a modified version |
||||
of the Library that is interface-compatible with the Linked |
||||
Version. |
||||
|
||||
e) Provide Installation Information, but only if you would otherwise |
||||
be required to provide such information under section 6 of the |
||||
GNU GPL, and only to the extent that such information is |
||||
necessary to install and execute a modified version of the |
||||
Combined Work produced by recombining or relinking the |
||||
Application with a modified version of the Linked Version. (If |
||||
you use option 4d0, the Installation Information must accompany |
||||
the Minimal Corresponding Source and Corresponding Application |
||||
Code. If you use option 4d1, you must provide the Installation |
||||
Information in the manner specified by section 6 of the GNU GPL |
||||
for conveying Corresponding Source.) |
||||
|
||||
5. Combined Libraries. |
||||
|
||||
You may place library facilities that are a work based on the |
||||
Library side by side in a single library together with other library |
||||
facilities that are not Applications and are not covered by this |
||||
License, and convey such a combined library under terms of your |
||||
choice, if you do both of the following: |
||||
|
||||
a) Accompany the combined library with a copy of the same work based |
||||
on the Library, uncombined with any other library facilities, |
||||
conveyed under the terms of this License. |
||||
|
||||
b) Give prominent notice with the combined library that part of it |
||||
is a work based on the Library, and explaining where to find the |
||||
accompanying uncombined form of the same work. |
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License. |
||||
|
||||
The Free Software Foundation may publish revised and/or new versions |
||||
of the GNU Lesser General Public License from time to time. Such new |
||||
versions will be similar in spirit to the present version, but may |
||||
differ in detail to address new problems or concerns. |
||||
|
||||
Each version is given a distinguishing version number. If the |
||||
Library as you received it specifies that a certain numbered version |
||||
of the GNU Lesser General Public License "or any later version" |
||||
applies to it, you have the option of following the terms and |
||||
conditions either of that published version or of any later version |
||||
published by the Free Software Foundation. If the Library as you |
||||
received it does not specify a version number of the GNU Lesser |
||||
General Public License, you may choose any version of the GNU Lesser |
||||
General Public License ever published by the Free Software Foundation. |
||||
|
||||
If the Library as you received it specifies that a proxy can decide |
||||
whether future versions of the GNU Lesser General Public License shall |
||||
apply, that proxy's public statement of acceptance of any version is |
||||
permanent authorization for you to choose that version for the |
||||
Library. |
Binary file not shown.
@ -0,0 +1,156 @@
|
||||
DIST := dist
|
||||
IMPORT := git.kolaente.de/konrad/list
|
||||
|
||||
SED_INPLACE := sed -i
|
||||
|
||||
ifeq ($(OS), Windows_NT) |
||||
EXECUTABLE := Library.exe
|
||||
else |
||||
EXECUTABLE := Library
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
SED_INPLACE := sed -i ''
|
||||
endif
|
||||
endif |
||||
|
||||
GOFILES := $(shell find . -name "*.go" -type f ! -path "./vendor/*" ! -path "*/bindata.go")
|
||||
GOFMT ?= gofmt -s
|
||||
|
||||
GOFLAGS := -i -v
|
||||
EXTRA_GOFLAGS ?=
|
||||
|
||||
LDFLAGS := -X "main.Version=$(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')" -X "main.Tags=$(TAGS)"
|
||||
|
||||
PACKAGES ?= $(filter-out git.kolaente.de/konrad/list/integrations,$(shell go list ./... | grep -v /vendor/))
|
||||
SOURCES ?= $(shell find . -name "*.go" -type f)
|
||||
|
||||
TAGS ?=
|
||||
|
||||
TMPDIR := $(shell mktemp -d 2>/dev/null || mktemp -d -t 'kasino-temp')
|
||||
|
||||
ifeq ($(OS), Windows_NT) |
||||
EXECUTABLE := Library.exe
|
||||
else |
||||
EXECUTABLE := Library
|
||||
endif |
||||
|
||||
ifneq ($(DRONE_TAG),) |
||||
VERSION ?= $(subst v,,$(DRONE_TAG))
|
||||
else |
||||
ifneq ($(DRONE_BRANCH),)
|
||||
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
|
||||
else
|
||||
VERSION ?= master
|
||||
endif
|
||||
endif |
||||
|
||||
.PHONY: all |
||||
all: build |
||||
|
||||
.PHONY: clean |
||||
clean: |
||||
go clean -i ./...
|
||||
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA)
|
||||
|
||||
.PHONY: test |
||||
test: |
||||
go test -cover $(PACKAGES)
|
||||
|
||||
required-gofmt-version: |
||||
@go version | grep -q '\(1.7\|1.8\|1.9\|1.10\)' || { echo "We require go version 1.7, 1.8, 1.9 or 1.10 to format code" >&2 && exit 1; }
|
||||
|
||||
.PHONY: lint |
||||
lint: |
||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/golang/lint/golint; \
|
||||
fi
|
||||
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
||||
|
||||
.PHONY: fmt |
||||
fmt: required-gofmt-version |
||||
$(GOFMT) -w $(GOFILES)
|
||||
|
||||
.PHONY: fmt-check |
||||
fmt-check: required-gofmt-version |
||||
# get all go files and run go fmt on them
|
||||
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make fmt' and commit the result:"; \
|
||||
echo "$${diff}"; \
|
||||
exit 1; \
|
||||
fi;
|
||||
|
||||
.PHONY: install |
||||
install: $(wildcard *.go) |
||||
go install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
|
||||
|
||||
.PHONY: build |
||||
build: $(EXECUTABLE) |
||||
|
||||
$(EXECUTABLE): $(SOURCES) |
||||
go build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
|
||||
|
||||
.PHONY: release |
||||
release: release-dirs release-windows release-linux release-darwin release-frontend release-copy release-check release-os-package |
||||
|
||||
.PHONY: release-dirs |
||||
release-dirs: |
||||
mkdir -p $(DIST)/binaries $(DIST)/release $(DIST)/zip
|
||||
|
||||
.PHONY: release-frontend |
||||
release-frontend: |
||||
mv frontend/siteconfig.json frontend/siteconfig.json.old; \
|
||||
echo '{"API_URL": "/api/v1/"}' > frontend/siteconfig.json; \
|
||||
npm --prefix frontend run build; \
|
||||
rm frontend/dist/siteconfig.json; \
|
||||
mv frontend/siteconfig.json.old frontend/siteconfig.json;
|
||||
|
||||
.PHONY: release-windows |
||||
release-windows: |
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/karalabe/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out Library-$(VERSION) .
|
||||
ifeq ($(CI),drone) |
||||
mv /build/* $(DIST)/binaries
|
||||
endif |
||||
|
||||
.PHONY: release-linux |
||||
release-linux: |
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/karalabe/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/*' -out Library-$(VERSION) .
|
||||
ifeq ($(CI),drone) |
||||
mv /build/* $(DIST)/binaries
|
||||
endif |
||||
|
||||
.PHONY: release-darwin |
||||
release-darwin: |
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/karalabe/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out Library-$(VERSION) .
|
||||
ifeq ($(CI),drone) |
||||
mv /build/* $(DIST)/binaries
|
||||
endif |
||||
|
||||
.PHONY: release-copy |
||||
release-copy: |
||||
$(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),cp $(file) $(DIST)/release/$(notdir $(file));)
|
||||
mkdir $(DIST)/release/frontend
|
||||
cp frontend/dist $(DIST)/release/frontend/ -R
|
||||
|
||||
.PHONY: release-check |
||||
release-check: |
||||
cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;)
|
||||
|
||||
|
||||
.PHONY: release-os-package |
||||
release-os-package: |
||||
$(foreach file,$(filter-out %.sha256,$(wildcard $(DIST)/release/$(EXECUTABLE)-*)),mkdir $(file)-full;mv $(file) $(file)-full/; mv $(file).sha256 $(file)-full/; cp config.ini.sample $(file)-full/config.ini; cp $(DIST)/release/frontend $(file)-full/ -R; cp LICENSE $(file)-full/; )
|
||||
rm $(DIST)/release/frontend -rf
|
||||
|
||||
.PHONY: release-zip |
||||
release-zip: |
||||
$(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),cd $(file); zip -r ../../zip/$(shell basename $(file)).zip *; cd ../../../; )
|
@ -0,0 +1,14 @@
|
||||
[General] |
||||
JWTSecret = blablaGEHEMINยง)!ยง |
||||
; The interface on which to run the webserver |
||||
Interface = :8080 |
||||
|
||||
[Database] |
||||
Type = mysql |
||||
User = root |
||||
Password = supersecret |
||||
Host = 127.0.0.1 |
||||
Database = library |
||||
ShowQueries = false |
||||
; When using sqlite, this is the path where to store the data |
||||
; Path = ./library.db |
@ -0,0 +1,63 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"git.kolaente.de/konrad/list/models" |
||||
"git.kolaente.de/konrad/list/routes" |
||||
|
||||
"context" |
||||
"fmt" |
||||
"os" |
||||
"os/signal" |
||||
"time" |
||||
) |
||||
|
||||
// UserLogin Object to recive user credentials in JSON format
|
||||
type UserLogin struct { |
||||
Username string `json:"username" form:"username"` |
||||
Password string `json:"password" form:"password"` |
||||
} |
||||
|
||||
// Version sets the version to be printed to the user. Gets overwritten by "make release" or "make build" with last git commit or tag.
|
||||
var Version = "1.0" |
||||
|
||||
func main() { |
||||
|
||||
// Init Config
|
||||
err := models.SetConfig() |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
os.Exit(1) |
||||
} |
||||
|
||||
// Set Engine
|
||||
err = models.SetEngine() |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
os.Exit(1) |
||||
} |
||||
|
||||
// Version notification
|
||||
fmt.Println("Library version", Version) |
||||
|
||||
// Start the webserver
|
||||
e := routes.NewEcho() |
||||
routes.RegisterRoutes(e) |
||||
// Start server
|
||||
go func() { |
||||
if err := e.Start(models.Config.Interface); err != nil { |
||||
e.Logger.Info("shutting down the server") |
||||
} |
||||
}() |
||||
|
||||
// Wait for interrupt signal to gracefully shutdown the server with
|
||||
// a timeout of 10 seconds.
|
||||
quit := make(chan os.Signal) |
||||
signal.Notify(quit, os.Interrupt) |
||||
<-quit |
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) |
||||
defer cancel() |
||||
fmt.Println("Shutting down...") |
||||
if err := e.Shutdown(ctx); err != nil { |
||||
e.Logger.Fatal(err) |
||||
} |
||||
} |
@ -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 |
||||
} |
@ -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) |
||||
} |
@ -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") |
||||
} |
@ -0,0 +1,7 @@
|
||||
- |
||||
id: 1 |
||||
name: 'John Doe' |
||||
username: 'user1' |
||||
password: '1234' |
||||
email: 'johndoe@example.com' |
||||
is_admin: true |
@ -0,0 +1,7 @@
|
||||
package models |
||||
|
||||
import "testing" |
||||
|
||||
func TestMain(m *testing.M) { |
||||
MainTest(m, "..") |
||||
} |
@ -0,0 +1,6 @@
|
||||
package models |
||||
|
||||
// Message is a standard message
|
||||
type Message struct { |
||||
Message string `json:"message"` |
||||
} |
@ -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 |
||||
} |
@ -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) |
||||
} |
@ -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() |
||||
} |
@ -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() |
||||
} |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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)) |
||||
} |
@ -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 |
||||
} |
@ -0,0 +1,18 @@
|
||||
package v1 |
||||
|
||||
import ( |
||||
"fmt" |
||||
"git.kolaente.de/konrad/list/models" |
||||
"github.com/dgrijalva/jwt-go" |
||||
"github.com/labstack/echo" |
||||
) |
||||
|
||||
// CheckToken checks prints a message if the token is valid or not. Currently only used for testing pourposes.
|
||||
func CheckToken(c echo.Context) error { |
||||
|
||||
user := c.Get("user").(*jwt.Token) |
||||
|
||||
fmt.Println(user.Valid) |
||||
|
||||
return c.JSON(418, models.Message{"๐ต"}) |
||||
} |
@ -0,0 +1,101 @@
|
||||
package v1 |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"git.kolaente.de/konrad/list/models" |
||||
"github.com/labstack/echo" |
||||
"net/http" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
// UserAddOrUpdate is the handler to add a user
|
||||
func UserAddOrUpdate(c echo.Context) error { |
||||
|
||||
// TODO: prevent everyone from updating users
|
||||
|
||||
// Check for Request Content
|
||||
userFromString := c.FormValue("user") |
||||
var datUser *models.User |
||||
|
||||
if userFromString == "" { |
||||
// b := new(models.User)
|
||||
if err := c.Bind(&datUser); err != nil { |
||||
return c.JSON(http.StatusBadRequest, models.Message{"No user model provided."}) |
||||
} |
||||
} else { |
||||
// Decode the JSON
|
||||
dec := json.NewDecoder(strings.NewReader(userFromString)) |
||||
err := dec.Decode(&datUser) |
||||
|
||||
if err != nil { |
||||
return c.JSON(http.StatusBadRequest, models.Message{"Error decoding user: " + err.Error()}) |
||||
} |
||||
} |
||||
|
||||
// Check if we have an ID other than the one in the struct
|
||||
id := c.Param("id") |
||||
if id != "" { |
||||
// Make int
|
||||
userID, err := strconv.ParseInt(id, 10, 64) |
||||
|
||||
if err != nil { |
||||
return c.JSON(http.StatusBadRequest, models.Message{"Invalid ID."}) |
||||
} |
||||
datUser.ID = userID |
||||
} |
||||
|
||||
// Check if the user exists
|
||||
_, exists, err := models.GetUserByID(datUser.ID) |
||||
if err != nil { |
||||
return c.JSON(http.StatusInternalServerError, models.Message{"Could not check if the user exists."}) |
||||
} |
||||
|
||||
// Get the doer options
|
||||
doer, err := models.GetCurrentUser(c) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Insert or update the user
|
||||
var newUser models.User |
||||
if exists { |
||||
newUser, err = models.UpdateUser(*datUser, &doer) |
||||
} else { |
||||
newUser, err = models.CreateUser(*datUser, &doer) |
||||
} |
||||
|
||||
if err != nil { |
||||
// Check for user already exists
|
||||
if models.IsErrUsernameExists(err) { |
||||
return c.JSON(http.StatusBadRequest, models.Message{"A user with this username already exists."}) |
||||
} |
||||
|
||||
// Check for user with that email already exists
|
||||
if models.IsErrUserEmailExists(err) { |
||||
return c.JSON(http.StatusBadRequest, models.Message{"A user with this email address already exists."}) |
||||
} |
||||
|
||||
// Check for no username provided
|
||||
if models.IsErrNoUsername(err) { |
||||
return c.JSON(http.StatusBadRequest, models.Message{"Please specify a username."}) |
||||
} |
||||
|
||||
// Check for no username or password provided
|
||||
if models.IsErrNoUsernamePassword(err) { |
||||
return c.JSON(http.StatusBadRequest, models.Message{"Please specify a username and a password."}) |
||||
} |
||||
|
||||
// Check for user does not exist
|
||||
if models.IsErrUserDoesNotExist(err) { |
||||
return c.JSON(http.StatusBadRequest, models.Message{"The user does not exist."}) |
||||
} |
||||
|
||||
return c.JSON(http.StatusInternalServerError, models.Message{"Error"}) |
||||
} |
||||
|
||||
// Obfuscate his password
|
||||
newUser.Password = "" |
||||
|
||||
return c.JSON(http.StatusOK, newUser) |
||||
} |