diff --git a/go/.env b/go/.env new file mode 100644 index 0000000..33a4c5f --- /dev/null +++ b/go/.env @@ -0,0 +1,10 @@ +APP_NAME="Kontor" +APP_VERSION="v0.1.0" + +# HTTP Response Content-Type Header - Success +HTTP_CONTENT_TYPE="application/vnd.api+json" + +# HTTP Response Content-Type Header - Error +HTTP_PROBLEM="application/problem+json" + +HTTP_PORT=":8086" diff --git a/go/.gitignore b/go/.gitignore new file mode 100644 index 0000000..0303555 --- /dev/null +++ b/go/.gitignore @@ -0,0 +1,9 @@ +.gradle/ +docs/build/ +bin/ +build/ +cover.out +coverage.html +coverage.xml +.settings/ +.project diff --git a/go/.gitlab-ci.yml b/go/.gitlab-ci.yml new file mode 100644 index 0000000..1580961 --- /dev/null +++ b/go/.gitlab-ci.yml @@ -0,0 +1,116 @@ +variables: + GRADLE_OPTS: "-Dorg.gradle.daemon=false" + +before_script: + - source "/home/gitlab-runner/.sdkman/bin/sdkman-init.sh" + - sdk update + - sdk d java 11.0.12-open + +stages: +- environment +- prepare +- build +- test +- analysis +- deploy + +environment: + stage: environment + script: + - echo "PATH=$PATH:/usr/local/go/bin:/home/gitlab-runner/go/bin" >> build.env + artifacts: + reports: + dotenv: build.env + +Prepare Go dependencies: + stage: prepare + script: + - go get -u github.com/spf13/cobra + - go get -u github.com/jstemmer/go-junit-report + - go get -u github.com/inconshreveable/mousetrap + - go get -u github.com/mitchellh/go-homedir + - go get github.com/boumenot/gocover-cobertura + - go get -u gotest.tools/gotestsum + - go get -u github.com/cryptix/wav + - go get -u golang.org/x/lint/golint + dependencies: + - environment + +Create Documentation: + stage: build + script: + - chmod +x docs/gradlew + - cd docs; ./gradlew publish + +Compile Go Application: + stage: build + script: make build + +Test Go Application: + stage: test + script: + - gotestsum --junitfile report.xml --format testname -- -coverprofile=coverage.txt -covermode count ./... + - go vet ./... 2> govet-report.out + - gocover-cobertura < coverage.txt > coverage.xml + - golint ./... > golint-report.out + dependencies: + - environment + artifacts: + when: always + paths: + - coverage.xml + - report.xml + reports: + junit: report.xml + coverage_report: + coverage_format: cobertura + path: coverage.xml + +Code analysis: + stage: analysis + script: + - go test -coverprofile cover.out ./... -json > report.json + - go vet ./... 2> govet-report.out + - golint ./... > golint-report.out + - echo "sonar.projectKey=kontor_kontor-go_AX-cQT62rXuu6JVRvr-z" >> sonar-project.properties + - echo "sonar.sources=." >> sonar-project.properties + - echo "sonar.exclusions=**/*_test.go" >> sonar-project.properties + - echo "sonar.tests=." >> sonar-project.properties + - echo "sonar.test.inclusions=**/*_test.go" >> sonar-project.properties + - echo "sonar.go.tests.reportPaths=report.json" >> sonar-project.properties + - echo "sonar.go.coverage.reportPaths=cover.out" >> sonar-project.properties + - echo "sonar.go.govet.reportPaths=govet-report.out" >> sonar-project.properties + - echo "sonar.go.golint.reportPaths=golint-report.out" >> sonar-project.properties + - /data/sonar-scanner/bin/sonar-scanner -Dsonar.projectVersion=$(git describe --abbrev=0) -Dsonar.host.url=https://sonar.thpeetz.de -Dsonar.login=319616a2761ac3e96a1c7aacc54976bfff4096a9 + dependencies: + - environment + +Deploy To Staging: + stage: deploy + script: + - make build + - ssh kontor /home/kontor/kontor-test_service stop + - rsync -av templates kontor:/home/kontor/staging + - rsync -av bin/kontor kontor:/home/kontor/staging + - ssh kontor /home/kontor/kontor-test_service start + environment: + name: staging + url: https://kontor-test.thpeetz.de + only: + - main + +Deploy to Production: + stage: deploy + script: + - make build + - sudo service kontor stop + - rsync -av templates kontor:/home/kontor/production + - rsync -av bin/kontor kontor:/home/kontor/production + - sudo service kontor start + environment: + name: production + url: https://kontor.thpeetz.de + + only: + - main + when: manual diff --git a/go/Makefile b/go/Makefile new file mode 100644 index 0000000..7f29013 --- /dev/null +++ b/go/Makefile @@ -0,0 +1,73 @@ +# Go parameters +GOPATH=$(HOME)/go +#GOBIN=$(shell pwd)/bin +GOBIN=/usr/local/go/bin +GOCMD=$(GOBIN)/go +GOGET=$(GOCMD) get +GOBUILD=$(GOCMD) build +GOCLEAN=$(GOCMD) clean +GOTEST=$(GOCMD) test +GOCOVER=$(GOCMD) tool cover +COBERTURA=$(HOME)/go/bin/gocover-cobertura +GOLINT=$(GOBIN)/golint +GOVET=$(GOCMD) vet + +# Project parameters +GONAME=kontor +GOFILE=cmd/kontor/main.go + +#.PHONY: all deps clean clean-bin clean-doc + +all: deps build + +deps: + $(GOGET) -u -v github.com/jstemmer/go-junit-report + $(GOGET) -u -v github.com/tebeka/go2xunit + $(GOGET) -u -v github.com/t-yuki/gocover-cobertura + $(GOGET) -u -v github.com/spf13/cobra/cobra + $(GOGET) -u -v github.com/inconshreveable/mousetrap + $(GOGET) -u -v github.com/mitchellh/go-homedir + $(GOGET) -u -v github.com/golang/protobuf/proto + $(GOGET) -u -v github.com/gin-gonic/gin + $(GOGET) -u -v github.com/gin-gonic/gin/binding + $(GOGET) -u -v github.com/gin-gonic/gin/render + $(GOGET) -u -v github.com/gin-contrib/sse + $(GOGET) -u -v github.com/mattn/go-isatty + $(GOGET) -u -v github.com/ugorji/go/codec + $(GOGET) -u -v golang.org/x/crypto/bcrypt + $(GOGET) -u -v golang.org/x/crypto/blowfish + G$(GOGET) -u -v gopkg.in/yaml.v2 + $(GOGET) -u -v gopkg.in/mgo.v2 + $(GOGET) -u -v gopkg.in/mgo.v2/bson + +build: bin/$(GONAME) + +bin/$(GONAME): $(GOFILE) + @echo "Building $(GOFILE) to ./bin" + $(GOBUILD) -v -ldflags="-X main.version=$(shell git describe --always --long --dirty)" -o bin/$(GONAME) $(GOFILE) + +install: + @echo using $(GOPATH) + $(GOCMD) install -v -ldflags="-X main.version=$(shell git describe --always --long --dirty)" ./... + +test: + $(GOTEST) -v ./... + $(GOTEST) -coverprofile=cover.out ./... + $(GOCOVER) -html=cover.out -o coverage.html + $(COBERTURA) < cover.out > coverage.xml + $(GOLINT) ./... + $(GOVET) -v ./... + +doc: + cd docs; ./gradlew build + +clean: clean-doc clean-bin + + +clean-doc: + @echo "Cleaning Gradle build" + cd docs; ./gradlew clean + +clean-bin: + @echo "Cleaning Go build" + rm -rf bin/ diff --git a/go/README.md b/go/README.md new file mode 100644 index 0000000..6814615 --- /dev/null +++ b/go/README.md @@ -0,0 +1,4 @@ +[](https://gitlab.thpeetz.de/kontor/kontor-go/commits/master) +[](https://gitlab.thpeetz.de/kontor/kontor-go/commits/master) + +# Kontor diff --git a/go/cmd/kontor/main.go b/go/cmd/kontor/main.go new file mode 100644 index 0000000..c2dbeeb --- /dev/null +++ b/go/cmd/kontor/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "database/sql" + "log" + "time" + + _ "github.com/go-sql-driver/mysql" +) + +func main() { + connectionString := "kontor:kontor@tcp(127.0.0.1:3306)/kontor?parseTime=true" + db, err := sql.Open("mysql", connectionString) + if err != nil { + log.Printf("setup database error: %v", err) + return + } + db.SetConnMaxLifetime(time.Minute * 3) + db.SetMaxOpenConns(10) + db.SetMaxIdleConns(10) + log.Println("Database connected") + + rows, err := db.Query("SELECT id, created_date, last_modified_date, version, url, title, review FROM media_file") + if err != nil { + log.Fatal((err.Error())) + } + defer rows.Close() + var rec MediaFile + for rows.Next() { + err = rows.Scan(&rec.ID, &rec.CreatedDate, &rec.LastModifiedDate, &rec.Version, &rec.Url, &rec.Title, &rec.Review) + if err != nil { + log.Fatal(err.Error()) + } + log.Println(rec, string(rec.Url), string(rec.Title)) + } +} diff --git a/go/cmd/kontor/model.go b/go/cmd/kontor/model.go new file mode 100644 index 0000000..b7634c5 --- /dev/null +++ b/go/cmd/kontor/model.go @@ -0,0 +1,25 @@ +package main + +import ( + "time" + + "github.com/google/uuid" +) + +type AbstractEntity struct { + ID uuid.UUID `gorm:"type:uuid;primary_key" json:"id"` + Version uint `json:"version"` + CreatedDate time.Time `json:"createdDate"` + LastModifiedDate time.Time `json:"lastModifiedDate"` +} + +type MediaFile struct { + AbstractEntity + Url []byte `json:"url"` + Review []uint8 `json:"review"` + ShouldDownload []uint8 `json:"shouldDownload"` + Title []byte `json:"title"` + CloudLink string `json:"cloudLink"` + FileName string `json:"fileName"` + Path string `json:"path"` +} diff --git a/go/cmd/root.go b/go/cmd/root.go new file mode 100644 index 0000000..9a1c5e2 --- /dev/null +++ b/go/cmd/root.go @@ -0,0 +1,89 @@ +package cmd + +import ( + "fmt" + "log" + "os" + + "gitlab.thpeetz.de/kontor/kontor-go/pkg/properties" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/setup" + + "github.com/gin-gonic/gin" + "github.com/spf13/cobra" +) + +var ( + // Verbose defines the parameter verbose. + Verbose bool + // Version defines the version of the web application. + Version string + // Debug defines the debug parameter. + Debug bool + // Port defines the parameter port. + Port int + // TemplatesDir defines the root directory of template files. + TemplatesDir string + router *gin.Engine +) + +var rootCmd = &cobra.Command{ + Use: "kontor", + Short: "kontor", + Long: `kontor`, + Run: func(cmd *cobra.Command, args []string) { + // Set Gin to production mode + if Debug { + gin.SetMode(gin.DebugMode) + } else { + gin.SetMode(gin.ReleaseMode) + } + log.SetOutput(gin.DefaultWriter) + + // Set the router as the default one provided by Gin + router = gin.Default() + + // Process the templates at the start so that they don't have to be loaded + // from the disk again. This makes serving HTML pages very fast. + templatesDir := fmt.Sprintf("%s/**/*", TemplatesDir) + log.Printf("load template files from %v", templatesDir) + router.LoadHTMLGlob(templatesDir) + + // Use Middleware logger + router.Use(gin.Logger()) + + // Initialize the routes + setup.InitializeRoutes(router) + + // Clean up SetSessionStatus + setup.CleanupSessions() + + // Check if at least one user configured + setup.CheckUserList() + + // Check if data for TradeYourSportsCards is available + setup.CheckTradeYourSportsCardsData() + + //properties.SetVersion(Version) + + // Start serving the application + server := fmt.Sprintf("127.0.0.1:%d", Port) + router.Run(server) + }, +} + +// Execute parses the commandline and calls Execute on rootCmd. +func Execute(version string) { + rootCmd.Version = version + properties.SetVersion(version) + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func init() { + rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") + rootCmd.PersistentFlags().BoolVarP(&Debug, "debug", "d", false, "debug modus") + rootCmd.PersistentFlags().IntVarP(&Port, "port", "p", 8500, "port number") + rootCmd.PersistentFlags().StringVarP(&TemplatesDir, "templates", "t", "templates", "path for template files") +} diff --git a/go/cmd/server.go b/go/cmd/server.go new file mode 100644 index 0000000..3b7ebd0 --- /dev/null +++ b/go/cmd/server.go @@ -0,0 +1,32 @@ +package main + +import ( + "github.com/joho/godotenv" + application "gitlab.com/tpeetz-kontor/kontor-go/pkg/infrastructure/app" + "gitlab.com/tpeetz-kontor/kontor-go/pkg/infrastructure/kernel" +) + +// init is invoked before main() +func init() { + // loads values from .env into the system + if err := godotenv.Load(); err != nil { + panic("No .env file found") + } +} + +func main() { + // Create our application + app := kernel.Boot() + + // Build our services + //ping.BuildPingService(app) + + // Run our Application in a coroutine + go func() { + app.Run() + }() + + // Wait for termination signals and shut down gracefully + application.WaitForShutdown(app) + +} diff --git a/go/comics.xml b/go/comics.xml new file mode 100644 index 0000000..730fdeb --- /dev/null +++ b/go/comics.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/go/docs/build.gradle b/go/docs/build.gradle new file mode 100644 index 0000000..9f27a44 --- /dev/null +++ b/go/docs/build.gradle @@ -0,0 +1,7 @@ +plugins { + alias(versionsLibs.plugins.asciidoctorConvention) +} + +wrapper { + gradleVersion = "7.5" +} diff --git a/go/docs/gradle.properties b/go/docs/gradle.properties new file mode 100644 index 0000000..aeb4e86 --- /dev/null +++ b/go/docs/gradle.properties @@ -0,0 +1,2 @@ +description='Documentation for application kontor-go' +version=1.0.0-SNAPSHOT diff --git a/go/docs/gradle/wrapper/gradle-wrapper.jar b/go/docs/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/go/docs/gradle/wrapper/gradle-wrapper.jar differ diff --git a/go/docs/gradle/wrapper/gradle-wrapper.properties b/go/docs/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..8049c68 --- /dev/null +++ b/go/docs/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/go/docs/gradlew b/go/docs/gradlew new file mode 100755 index 0000000..a69d9cb --- /dev/null +++ b/go/docs/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/go/docs/gradlew.bat b/go/docs/gradlew.bat new file mode 100644 index 0000000..53a6b23 --- /dev/null +++ b/go/docs/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/go/docs/settings.gradle b/go/docs/settings.gradle new file mode 100644 index 0000000..b956775 --- /dev/null +++ b/go/docs/settings.gradle @@ -0,0 +1 @@ +rootProject.name='kontor-go' diff --git a/go/docs/src/docs/asciidoc/kontor-go.adoc b/go/docs/src/docs/asciidoc/kontor-go.adoc new file mode 100644 index 0000000..5334cfb --- /dev/null +++ b/go/docs/src/docs/asciidoc/kontor-go.adoc @@ -0,0 +1,31 @@ += Projektbeschreibung Kontor +:author: Thomas Peetz +:email: +:doctype: article +:toc: left +:sectnums: + +== Einführung + +=== Zweck + +== Anforderungen + +== Implementierung + +=== Datenmodell + +== Betrieb + +[bibliography] +== Referenzen + +- [[[1]]] Thomas Peetz, Betriebshandbuch IBTP + +[index] +== Index + +== Tabellenverzeichnis + +[glossary] +== Glossar diff --git a/go/go.mod b/go/go.mod new file mode 100644 index 0000000..cc805de --- /dev/null +++ b/go/go.mod @@ -0,0 +1,28 @@ +module gitlab.com/tpeetz-kontor/kontor-go + +go 1.22.2 + +require github.com/joho/godotenv v1.5.1 + +require ( + github.com/felixge/httpsnoop v1.0.3 // indirect + go.uber.org/multierr v1.10.0 // indirect +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/google/uuid v1.6.0 + github.com/gorilla/handlers v1.5.2 + github.com/gorilla/mux v1.8.1 + github.com/jinzhu/gorm v1.9.16 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/satori/go.uuid v1.2.0 // indirect + go.uber.org/zap v1.27.0 + golang.org/x/text v0.19.0 // indirect + golang.org/x/tools v0.21.1-0.20240531212143-b6235391adb3 // indirect + gorm.io/driver/mysql v1.5.7 // indirect + gorm.io/gorm v1.25.12 // indirect +) diff --git a/go/go.sum b/go/go.sum new file mode 100644 index 0000000..3d35401 --- /dev/null +++ b/go/go.sum @@ -0,0 +1,62 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.21.1-0.20240531212143-b6235391adb3 h1:SHq4Rl+B7WvyM4XODon1LXtP7gcG49+7Jubt1gWWswY= +golang.org/x/tools v0.21.1-0.20240531212143-b6235391adb3/go.mod h1:bqv7PJ/TtlrzgJKhOAGdDUkUltQapRik/UEHubLVBWo= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= +honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= diff --git a/go/kontor-ansible.yml b/go/kontor-ansible.yml new file mode 100644 index 0000000..2e19328 --- /dev/null +++ b/go/kontor-ansible.yml @@ -0,0 +1,58 @@ +--- +# file: kontor-ansible.yml +- hosts: localhost + remote_user: root + tasks: + - name: create group kontor + group: + name: kontor + state: present + + - name: create user kontor + user: + name: kontor + home: /home/kontor + shell: /bin/bash + state: present + # generate_ssh_key: yes + # ssh_key_bits: 4096 + # ssh_key_file: .ssh/id_rsa + + - name: create log directory + file: + path: /var/log/kontor + state: directory + owner: kontor + group: kontor + mode: 0775 + recurse: true + + - name: create run directory + file: + path: /var/run/kontor + state: directory + owner: kontor + group: kontor + mode: 0775 + recurse: true + + - name: create directory + file: + path: /home/kontor/production + state: directory + owner: kontor + group: kontor + mode: 0775 + recurse: false + + - name: copy binary + copy: + src: bin/kontor + dest: /home/kontor/production/kontor + mode: 0775 + + - name: copy templates + copy: + src: templates + dest: /home/kontor/production/kontor/templates + mode: 0775 diff --git a/go/pkg/admin/routes.go b/go/pkg/admin/routes.go new file mode 100644 index 0000000..a4845cf --- /dev/null +++ b/go/pkg/admin/routes.go @@ -0,0 +1,23 @@ +package admin + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/auth" + + "github.com/gin-gonic/gin" +) + +// GetRoutes sets the routes for the administrative data urls. +func GetRoutes(router *gin.Engine) { + adminRoutes := router.Group("/admin") + { + adminRoutes.GET("/", auth.EnsureLoggedIn(), auth.EnsureAdminStatus(), showAdminIndex) + adminRoutes.GET("/user", auth.EnsureLoggedIn(), auth.EnsureAdminStatus(), showUserIndex) + adminRoutes.POST("/user", auth.EnsureLoggedIn(), auth.EnsureAdminStatus(), showUserIndex) + adminRoutes.GET("/user/view/:userid", auth.EnsureLoggedIn(), auth.EnsureAdminStatus(), showUserDetails) + adminRoutes.POST("/user/view", auth.EnsureLoggedIn(), auth.EnsureAdminStatus(), validateUserCreation) + adminRoutes.GET("/user/create", auth.EnsureLoggedIn(), auth.EnsureAdminStatus(), showUserCreation) + adminRoutes.POST("/user/create", auth.EnsureLoggedIn(), auth.EnsureAdminStatus(), validateUserCreation) + adminRoutes.GET("/data", auth.EnsureLoggedIn(), auth.EnsureAdminStatus(), showDataUpload) + adminRoutes.POST("/data", auth.EnsureLoggedIn(), auth.EnsureAdminStatus(), validateDataUpload) + } +} diff --git a/go/pkg/admin/user.go b/go/pkg/admin/user.go new file mode 100644 index 0000000..8d94a06 --- /dev/null +++ b/go/pkg/admin/user.go @@ -0,0 +1,18 @@ +package admin + +import ( + "gopkg.in/mgo.v2/bson" +) + +// User defines the data model for application users with id,email, user name, +// first and family name, password and admin status. +type User struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Email string `json:"email" bson:"email,omitempty"` + Username string `json:"username" bson:"username,omitempty"` + Firstname string `json:"firstname" bson:"firstname,omitempty"` + Lastname string `json:"lastname" bson:"lastname,omitempty"` + Password string `json:"password" bson:"password,omitempty"` + IsAdmin bool `json:"is_admin" bson:"is_admin,omitempty"` + Model string `json:"model" bson:"model,omitempty"` +} diff --git a/go/pkg/admin/user_dao.go b/go/pkg/admin/user_dao.go new file mode 100644 index 0000000..3f77fbd --- /dev/null +++ b/go/pkg/admin/user_dao.go @@ -0,0 +1,123 @@ +package admin + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "log" + + "github.com/gin-gonic/gin" + + "golang.org/x/crypto/bcrypt" + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// UserDAO extends the type BaseDAO. +type UserDAO struct { + Db dao.BaseDAO +} + +const ( + // USERCOLLECTION defines the collection name for storing application user data. + USERCOLLECTION = "user" + // USERMODEL defines the name of the user data model. + USERMODEL = "kontor.admin.user" +) + +// HashPassword returns the encrypted password from password string. +func HashPassword(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) + return string(bytes), err +} + +// CheckPasswordHash returns if password correlates with pasword hash. +func CheckPasswordHash(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} + +// FindAll retrieves the list of users from the database. +func (m *UserDAO) FindAll() ([]User, error) { + m.Db.Connect() + var users []User + err := m.Db.MongoDb.C(USERCOLLECTION).Find(bson.M{"model": USERMODEL}).All(&users) + return users, err +} + +// FindByID returns a user with given id or returns the error. +func (m *UserDAO) FindByID(id string) (User, error) { + m.Db.Connect() + var user User + err := m.Db.MongoDb.C(USERCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&user) + return user, err +} + +// FindByUsername returns a user with given name or returns the error. +func (m *UserDAO) FindByUsername(username string) (User, error) { + m.Db.Connect() + var user User + err := m.Db.MongoDb.C(USERCOLLECTION).Find(bson.M{"username": username, "model": USERMODEL}).One(&user) + return user, err +} + +// Insert a user into database. +func (m *UserDAO) Insert(user User) error { + m.Db.Connect() + user.Model = USERMODEL + err := m.Db.MongoDb.C(USERCOLLECTION).Insert(&user) + return err +} + +// Upsert a user into database. +func (m *UserDAO) Upsert(user User) (*mgo.ChangeInfo, error) { + m.Db.Connect() + user.Model = USERMODEL + info, err := m.Db.MongoDb.C(USERCOLLECTION).Upsert(bson.M{"username": user.Username}, &user) + return info, err +} + +// Update an existing user. +func (m *UserDAO) Update(user User) error { + m.Db.Connect() + err := m.Db.MongoDb.C(USERCOLLECTION).UpdateId(user.ID, &user) + return err +} + +// Delete an existing user. +func (m *UserDAO) Delete(user User) error { + m.Db.Connect() + err := m.Db.MongoDb.C(USERCOLLECTION).Remove(&user) + return err +} + +// IsUserValid checks if the username and password combination is valid +func (m *UserDAO) IsUserValid(username, password string) bool { + if gin.IsDebugging() { + log.Printf("UserDAO.IsUserValid(%s)", username) + } + user, err := m.FindByUsername(username) + if gin.IsDebugging() { + log.Printf("UserDAO.IsUserValid: %v, %v", user, err) + } + if &user == nil || err != nil { + return false + } + return CheckPasswordHash(password, user.Password) +} + +// IsUserAdmin checks if user identified by name has admin rights. +func (m *UserDAO) IsUserAdmin(username string) bool { + user, err := m.FindByUsername(username) + if &user == nil || err != nil { + return false + } + return user.IsAdmin +} + +// IsUsernameAvailable checks if the supplied username is available. +func (m *UserDAO) IsUsernameAvailable(username string) bool { + user, err := m.FindByUsername(username) + if &user == nil || err != nil { + return true + } + return false +} diff --git a/go/pkg/admin/user_test.go b/go/pkg/admin/user_test.go new file mode 100644 index 0000000..0668ae2 --- /dev/null +++ b/go/pkg/admin/user_test.go @@ -0,0 +1,105 @@ +package admin + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var userModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Email", "string"}, + {"Username", "string"}, + {"Firstname", "string"}, + {"Lastname", "string"}, + {"Password", "string"}, + {"IsAdmin", "bool"}, + {"Model", "string"}, +} + +func TestUserModel(t *testing.T) { + m := User{} + if reflect.TypeOf(m).NumField() != len(userModelTestTable) { + t.Fail() + } + for index, testData := range userModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListUsers(t *testing.T) { + var userDao = UserDAO{Db: dao.TestDb} + users, err := userDao.FindAll() + if err != nil { + t.Fail() + } + if users != nil { + t.Fail() + } +} + +func TestInsertUser(t *testing.T) { + var ( + userDao = UserDAO{Db: dao.TestDb} + user = User{} + users []User + ) + user.ID = bson.NewObjectId() + user.Username = "test" + err := userDao.Insert(user) + if err != nil { + t.Fail() + } + users, err = userDao.FindAll() + if err != nil { + t.Fail() + } + if len(users) != 1 { + t.Fail() + } +} + +func TestUpsertUser(t *testing.T) { + var ( + userDao = UserDAO{Db: dao.TestDb} + user = User{} + ) + user.ID = bson.NewObjectId() + user.Username = "test2" + userDao.Upsert(user) + users, err := userDao.FindAll() + if err != nil { + t.Fail() + } + if len(users) != 2 { + t.Fail() + } +} + +func TestDeleteUser(t *testing.T) { + var ( + userDao = UserDAO{Db: dao.TestDb} + ) + users, err := userDao.FindAll() + if err != nil { + t.Fail() + } + for _, user := range users { + userDao.Delete(user) + } + users, err = userDao.FindAll() + if err != nil { + t.Fail() + } + if len(users) != 0 { + t.Fail() + } +} diff --git a/go/pkg/admin/views.go b/go/pkg/admin/views.go new file mode 100644 index 0000000..f1b62cc --- /dev/null +++ b/go/pkg/admin/views.go @@ -0,0 +1,242 @@ +package admin + +import ( + "io/ioutil" + "log" + "net/http" + "path/filepath" + "strconv" + + "gitlab.thpeetz.de/kontor/kontor-go/pkg/auth" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/comics" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/util" + + "github.com/gin-gonic/gin" + "github.com/smallfish/simpleyaml" +) + +const ( + // KontorUserAdministrationTitle defines the text of the page title + KontorUserAdministrationTitle = "Kontor User Administration" + // DataUploadTemplate defines the name of the template file for the data upload + DataUploadTemplate = "kontor/data-upload.html" +) + +// ShowLoginPage renders login page. +func ShowLoginPage(c *gin.Context) { + // Call the render function with the name of the template to render + util.Render(c, gin.H{"title": "Login"}, "login.html") +} + +// PerformLogin reads data from login form and validates input. +func PerformLogin(c *gin.Context) { + // Obtain the POSTed username and password values + username := c.PostForm("username") + password := c.PostForm("password") + + var userDao = UserDAO{Db: dao.KontorDb} + + // Check if the username/password combination is valid + if userDao.IsUserValid(username, password) { + sessionInterface, _ := c.Get("session") + sessionID := sessionInterface.(string) + user, _ := userDao.FindByUsername(username) + sessionDao := auth.SessionDAO{Db: dao.KontorDb} + session, _ := sessionDao.FindByID(sessionID) + session.Username = username + session.IsAdmin = user.IsAdmin + sessionDao.Update(session) + util.Render(c, gin.H{"title": "Successful Login", "InfoMessage": "Login successfull"}, "kontor/index.html") + } else { + // If the username/password combination is invalid, + // show the error message on the login page + c.HTML(http.StatusBadRequest, "login.html", gin.H{ + "ErrorTitle": "Login Failed", + "ErrorMessage": "Invalid credentials provided"}) + } +} + +// Logout invalidates session. +func Logout(c *gin.Context) { + sessionInterface, _ := c.Get("session") + sessionID := sessionInterface.(string) + c.SetCookie("session", sessionID, -1, "", "", false, true) + + // Redirect to the home page + c.Redirect(http.StatusTemporaryRedirect, "/") +} + +func showAdminIndex(c *gin.Context) { + // Call the render function with the name of the template to render + util.Render(c, gin.H{"title": "Kontor", "payload": nil}, "kontor/admin.html") +} + +func showUserIndex(c *gin.Context) { + var dao = UserDAO{Db: dao.KontorDb} + if users, err := dao.FindAll(); err == nil && users != nil { + util.Render(c, gin.H{"title": KontorUserAdministrationTitle, "payload": users}, "kontor/users.html") + } else { + util.Render(c, gin.H{"title": KontorUserAdministrationTitle, "payload": users, "ErrorMessage": err}, "kontor/users.html") + } +} + +func showUserDetails(c *gin.Context) { + userID := c.Param("userid") + var userDao = UserDAO{Db: dao.KontorDb} + if user, err := userDao.FindByID(userID); err == nil && &user != nil { + util.Render(c, gin.H{"title": KontorUserAdministrationTitle, "payload": user, "action": util.SaveAction}, "kontor/user-detail.html") + } else { + c.AbortWithError(http.StatusNotFound, err) + } +} + +func showUserCreation(c *gin.Context) { + var user = User{} + util.Render(c, gin.H{"title": KontorUserAdministrationTitle, "payload": user, "action": util.AddAction}, "kontor/user-detail.html") +} + +func validateUserCreation(c *gin.Context) { + // Obtain the POSTed username and password values + username := c.PostForm("username") + firstname := c.PostForm("firstname") + lastname := c.PostForm("lastname") + password := c.PostForm("password") + adminFormVar := c.PostForm("admin") + action := c.PostForm("action") + userid := c.PostForm("userid") + isAdmin, _ := strconv.ParseBool(adminFormVar) + + var err error + var dao = UserDAO{Db: dao.KontorDb} + var user = User{} + + switch action { + case util.AddAction: + user.Username = username + user.Firstname = firstname + user.Lastname = lastname + user.IsAdmin = isAdmin + user.Password, _ = HashPassword(password) + _, err = dao.Upsert(user) + case util.SaveAction: + user, _ = dao.FindByID(userid) + user.Username = username + user.Firstname = firstname + user.Lastname = lastname + user.IsAdmin = isAdmin + user.Password, _ = HashPassword(password) + err = dao.Update(user) + case util.DeleteAction: + user, _ = dao.FindByID(userid) + err = dao.Delete(user) + } + if err == nil { + c.Redirect(http.StatusTemporaryRedirect, "/admin/user") + } else { + c.HTML(http.StatusBadRequest, "kontor/create-user.html", gin.H{ + "ErrorTitle": "User Creation Failed", + "ErrorMessage": err.Error()}) + } +} + +func showDataUpload(c *gin.Context) { + // Call the render function with the name of the template to render + //util.Render(c, gin.H{"title": "Kontor", "payload": nil}, "kontor/admin.html") + util.Render(c, gin.H{"title": "Kontor Data Upload", "payload": nil}, DataUploadTemplate) +} + +func validateDataUpload(c *gin.Context) { + // Call the render function with the name of the template to render + //util.Render(c, gin.H{"title": "Kontor", "payload": nil}, "kontor/admin.html") + datafile, err := c.FormFile("datafile") + if err != nil { + c.HTML(http.StatusBadRequest, DataUploadTemplate, gin.H{ + "ErrorTitle": "Data Upload Failed", + "ErrorMessage": err.Error()}) + return + } + log.Printf("Data File: %v", datafile.Filename) + filename := filepath.Base(datafile.Filename) + if err := c.SaveUploadedFile(datafile, filename); err != nil { + c.HTML(http.StatusBadRequest, DataUploadTemplate, gin.H{ + "ErrorTitle": "Data Upload Failed", + "ErrorMessage": err.Error()}) + return + } + source, err := ioutil.ReadFile(filename) + if err != nil { + panic(err) + } + yaml, err := simpleyaml.NewYaml(source) + if err != nil { + panic(err) + } + if yaml.IsArray() { + size, err := yaml.GetArraySize() + if err != nil { + panic(err) + } + log.Printf("Found %d entries.\n", size) + var publisherDao comics.PublisherDAO + publisherDao.Db = dao.KontorDb + var artistDao comics.ArtistDAO + artistDao.Db = dao.KontorDb + var comicDao comics.ComicDAO + comicDao.Db = dao.KontorDb + publisherMap := make(map[int]string) + for index := 0; index < size; index++ { + entry := yaml.GetIndex(index) + if entry.IsMap() { + model, err := entry.Get("model").String() + if err != nil { + panic(err) + } + pk, _ := entry.Get("pk").Int() + switch model { + case "comics.publisher": + name, err := entry.Get("fields").Get("name").String() + if err != nil { + panic(err) + } + log.Printf(" %v %v %v\n", pk, model, name) + publisherMap[pk] = name + publisher := comics.Publisher{} + publisher.Name = name + info, _ := publisherDao.Upsert(publisher) + log.Printf("Publisher records changed: %d", info.Updated) + case "comics.artist": + name, err := entry.Get("fields").Get("name").String() + if err != nil { + panic(err) + } + log.Printf(" %v %v %v\n", pk, model, name) + artist := comics.Artist{} + artist.Name = name + info, _ := artistDao.Upsert(artist) + log.Printf("Artist records changed: %d", info.Updated) + case "comics.comic": + title, err := entry.Get("fields").Get("title").String() + if err != nil { + panic(err) + } + publisherID, err := entry.Get("fields").Get("publisher").Int() + publisher, err := publisherDao.FindByName(publisherMap[publisherID]) + completed, err := entry.Get("fields").Get("completed").Bool() + if err != nil { + log.Printf("Error occured: %v", err) + } + log.Printf(" %v %v %v %v\n", pk, model, title, completed) + comic := comics.Comic{} + comic.Title = title + comic.Completed = completed + comic.Publisher = publisher.ID + info, _ := comicDao.Upsert(comic) + log.Printf("Comic records changed: %d", info.Updated) + } + //fmt.Printf("Entry %d: %v\n", index, entry) + } + } + } + util.Render(c, gin.H{"title": "Kontor Data Upload", "payload": nil}, DataUploadTemplate) +} diff --git a/go/pkg/application/registry/comic/registry.go b/go/pkg/application/registry/comic/registry.go new file mode 100644 index 0000000..7fb2f95 --- /dev/null +++ b/go/pkg/application/registry/comic/registry.go @@ -0,0 +1,19 @@ +package comic + +import ( + "net/http" + + "gitlab.com/tpeetz-kontor/kontor-go/cmd/pkg/context/comic/routing" + "gitlab.com/tpeetz-kontor/kontor-go/cmd/pkg/infrastructure/app" +) + +func BuildComicService(app *app.Application) { + // Create our Handler + handler := routing.NewHandler(app) + + // Create a sub router for this service + router := app.Router.Methods(http.MethodGet).Subrouter() + + // Register our service routes + router.HandleFunc("/comics/comic", handler.ComicList).Name("comics:comicList") +} diff --git a/go/pkg/auth/middleware.go b/go/pkg/auth/middleware.go new file mode 100644 index 0000000..a26a93d --- /dev/null +++ b/go/pkg/auth/middleware.go @@ -0,0 +1,87 @@ +package auth + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/properties" + "log" + "net/http" + + "github.com/gin-gonic/gin" + "gopkg.in/mgo.v2/bson" +) + +var sessionDao = SessionDAO{Db: dao.KontorDb} + +// EnsureLoggedIn ensures that a request will be aborted with an error +// if the user is not logged in +func EnsureLoggedIn() gin.HandlerFunc { + return func(c *gin.Context) { + // If there's an error or if the token is empty + // the user is not logged in + sessionInterface, _ := c.Get("session") + sessionID := sessionInterface.(string) + if session, err := sessionDao.GetSession(sessionID); err != nil || session.Username == "" { + c.Redirect(http.StatusTemporaryRedirect, "/") + //c.AbortWithStatus(http.StatusUnauthorized) + } + } +} + +// EnsureAdminStatus ensures that a request will be aborted with an error +// if the user is not logged in +func EnsureAdminStatus() gin.HandlerFunc { + return func(c *gin.Context) { + // If there's an error or if the token is empty + // the user is not logged in + sessionInterface, _ := c.Get("session") + sessionID := sessionInterface.(string) + if session, err := sessionDao.GetSession(sessionID); err != nil || !session.IsAdmin { + c.Redirect(http.StatusTemporaryRedirect, "/") + //c.AbortWithStatus(http.StatusUnauthorized) + } + } +} + +// EnsureNotLoggedIn ensures that a request will be aborted with an error +// if the user is already logged in +func EnsureNotLoggedIn() gin.HandlerFunc { + return func(c *gin.Context) { + // If there's no error or if the token is not empty + // the user is already logged in + sessionInterface, _ := c.Get("session") + sessionID := sessionInterface.(string) + if session, err := sessionDao.GetSession(sessionID); err != nil || session.Username != "" { + c.Redirect(http.StatusTemporaryRedirect, "/") + //c.AbortWithStatus(http.StatusUnauthorized) + } + } +} + +// SetSessionStatus reads sessionId from cookie if available or create new session object +// and sets cookie accordingly. +func SetSessionStatus() gin.HandlerFunc { + return func(c *gin.Context) { + if sessionID, err := c.Cookie("session"); err == nil || sessionID != "" { + c.Set("session", sessionID) + } else { + session, _ := sessionDao.GetSession(bson.NewObjectId().Hex()) + sessionID := session.ID.Hex() + c.Set("session", sessionID) + c.SetCookie("session", sessionID, 3600, "", "", false, true) + } + } +} + +// SetSessionData populates session information with username, admin status of user and +// application version. +func SetSessionData(c *gin.Context, data gin.H) { + sessionInterface, _ := c.Get("session") + sessionID := sessionInterface.(string) + session, _ := sessionDao.GetSession(sessionID) + if gin.IsDebugging() { + log.Printf("setSessionData(%v): %v", sessionID, session) + } + data["is_logged_in"] = (session.Username != "") + data["is_admin"] = session.IsAdmin + data["version"] = properties.Version +} diff --git a/go/pkg/auth/session.go b/go/pkg/auth/session.go new file mode 100644 index 0000000..dccf9fb --- /dev/null +++ b/go/pkg/auth/session.go @@ -0,0 +1,11 @@ +package auth + +import "gopkg.in/mgo.v2/bson" + +// Session defines the data model for sessions with id,user name and admin status. +type Session struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Username string `json:"username" bson:"username,omitempty"` + IsAdmin bool `json:"is_admin" bson:"is_admin,omitempty"` + Model string `json:"model" bson:"model,omitempty"` +} diff --git a/go/pkg/auth/session_dao.go b/go/pkg/auth/session_dao.go new file mode 100644 index 0000000..70f7adc --- /dev/null +++ b/go/pkg/auth/session_dao.go @@ -0,0 +1,78 @@ +package auth + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// SessionDAO extends the type BaseDAO. +type SessionDAO struct { + Db dao.BaseDAO +} + +const ( + // SESSIONCOLLECTION defines the collection name for storing session data. + SESSIONCOLLECTION = "session" + // SESSIONMODEL defines the name of the session data model. + SESSIONMODEL = "kontor.admin.session" +) + +// FindAll retrieves the list of sessions from the database. +func (m *SessionDAO) FindAll() ([]Session, error) { + m.Db.Connect() + var sessions []Session + err := m.Db.MongoDb.C(SESSIONCOLLECTION).Find(bson.M{"model": SESSIONMODEL}).All(&sessions) + return sessions, err +} + +// FindByID returns a session with given id or returns the error. +func (m *SessionDAO) FindByID(id string) (Session, error) { + m.Db.Connect() + var session Session + err := m.Db.MongoDb.C(SESSIONCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&session) + return session, err +} + +// Insert a session into database. +func (m *SessionDAO) Insert(session Session) error { + m.Db.Connect() + session.Model = SESSIONMODEL + err := m.Db.MongoDb.C(SESSIONCOLLECTION).Insert(&session) + //log.PrintDebug("Insert: %v, %v\n", session, err) + return err +} + +// Upsert a session into database. +func (m *SessionDAO) Upsert(session Session) (*mgo.ChangeInfo, error) { + m.Db.Connect() + session.Model = SESSIONMODEL + info, err := m.Db.MongoDb.C(SESSIONCOLLECTION).Upsert(bson.M{"_id": session.ID}, &session) + return info, err +} + +// Update an existing session. +func (m *SessionDAO) Update(session Session) error { + m.Db.Connect() + err := m.Db.MongoDb.C(SESSIONCOLLECTION).UpdateId(session.ID, &session) + return err +} + +// Delete an existing session. +func (m *SessionDAO) Delete(session Session) error { + m.Db.Connect() + err := m.Db.MongoDb.C(SESSIONCOLLECTION).Remove(&session) + return err +} + +// GetSession get a session by given id or create a new one, if nothing was found. +func (m *SessionDAO) GetSession(id string) (*Session, error) { + m.Db.Connect() + session, err := m.FindByID(id) + if err != nil { + session = Session{ID: bson.ObjectIdHex(id), Username: "", IsAdmin: false} + m.Insert(session) + } + return &session, nil +} diff --git a/go/pkg/auth/session_test.go b/go/pkg/auth/session_test.go new file mode 100644 index 0000000..a6769b8 --- /dev/null +++ b/go/pkg/auth/session_test.go @@ -0,0 +1,103 @@ +package auth + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var sessionModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Username", "string"}, + {"IsAdmin", "bool"}, + {"Model", "string"}, +} + +func TestSessionModel(t *testing.T) { + m := Session{} + if reflect.TypeOf(m).NumField() != len(sessionModelTestTable) { + t.Fail() + } + for index, testData := range sessionModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListSessions(t *testing.T) { + var ( + sessionDao = SessionDAO{Db: dao.TestDb} + ) + sessions, err := sessionDao.FindAll() + if err != nil { + t.Fail() + } + if sessions != nil { + t.Fail() + } +} + +func TestInsertSession(t *testing.T) { + var ( + sessionDao = SessionDAO{Db: dao.TestDb} + session = Session{} + sessions []Session + ) + session.ID = bson.NewObjectId() + session.Username = "test" + err := sessionDao.Insert(session) + if err != nil { + t.Fail() + } + sessions, err = sessionDao.FindAll() + if err != nil { + t.Fail() + } + if len(sessions) != 1 { + t.Fail() + } +} + +func TestUpsertSession(t *testing.T) { + var ( + sessionDao = SessionDAO{Db: dao.TestDb} + session = Session{} + ) + session.ID = bson.NewObjectId() + session.Username = "test2" + sessionDao.Upsert(session) + sessions, err := sessionDao.FindAll() + if err != nil { + t.Fail() + } + if len(sessions) != 2 { + t.Fail() + } +} + +func TestDeleteSession(t *testing.T) { + var ( + sessionDao = SessionDAO{Db: dao.TestDb} + ) + sessions, err := sessionDao.FindAll() + if err != nil { + t.Fail() + } + for _, session := range sessions { + sessionDao.Delete(session) + } + sessions, err = sessionDao.FindAll() + if err != nil { + t.Fail() + } + if len(sessions) != 0 { + t.Fail() + } +} diff --git a/go/pkg/comics/artist.go b/go/pkg/comics/artist.go new file mode 100644 index 0000000..8afd80a --- /dev/null +++ b/go/pkg/comics/artist.go @@ -0,0 +1,12 @@ +package comics + +import ( + "gopkg.in/mgo.v2/bson" +) + +// Artist defines the data model for comic artists with id and name. +type Artist struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Model string `json:"model" bson:"model"` +} diff --git a/go/pkg/comics/artist_dao.go b/go/pkg/comics/artist_dao.go new file mode 100644 index 0000000..b177c2b --- /dev/null +++ b/go/pkg/comics/artist_dao.go @@ -0,0 +1,78 @@ +package comics + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "log" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// ArtistDAO extends the type BaseDAO. +type ArtistDAO struct { + Db dao.BaseDAO +} + +const ( + // ARTISTCOLLECTION defines the collection name for storing comic artists. + ARTISTCOLLECTION = "artist" + // ARTISTMODEL defines the name of the artist data model. + ARTISTMODEL = "kontor.comics.artist" +) + +// FindAll retrieves the list of artists from the database. +func (m *ArtistDAO) FindAll() ([]Artist, error) { + m.Db.Connect() + var artists []Artist + err := m.Db.MongoDb.C(ARTISTCOLLECTION).Find(bson.M{"model": ARTISTMODEL}).All(&artists) + return artists, err +} + +// FindByID returns an artists with given id or returns the error. +func (m *ArtistDAO) FindByID(id string) (Artist, error) { + m.Db.Connect() + var artist Artist + err := m.Db.MongoDb.C(ARTISTCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&artist) + return artist, err +} + +// FindByName returns an artists with given name or returns the error. +func (m *ArtistDAO) FindByName(name string) (Artist, error) { + m.Db.Connect() + var artist Artist + err := m.Db.MongoDb.C(ARTISTCOLLECTION).Find(bson.M{"name": name, "model": ARTISTMODEL}).One(&artist) + return artist, err +} + +// Insert an artist into database. +func (m *ArtistDAO) Insert(artist Artist) error { + m.Db.Connect() + artist.Model = ARTISTMODEL + err := m.Db.MongoDb.C(ARTISTCOLLECTION).Insert(&artist) + return err +} + +// Upsert an artist into database. +func (m *ArtistDAO) Upsert(artist Artist) (*mgo.ChangeInfo, error) { + m.Db.Connect() + artist.Model = ARTISTMODEL + info, err := m.Db.MongoDb.C(ARTISTCOLLECTION).Upsert(bson.M{"name": artist.Name}, &artist) + return info, err +} + +// Delete an existing artist. +func (m *ArtistDAO) Delete(artist Artist) error { + m.Db.Connect() + err := m.Db.MongoDb.C(ARTISTCOLLECTION).Remove(&artist) + if err != nil { + log.Printf("ArtistDao.Delete: %v", err) + } + return err +} + +// Update an existing artist. +func (m *ArtistDAO) Update(artist Artist) error { + m.Db.Connect() + err := m.Db.MongoDb.C(ARTISTCOLLECTION).UpdateId(artist.ID, &artist) + return err +} diff --git a/go/pkg/comics/artist_test.go b/go/pkg/comics/artist_test.go new file mode 100644 index 0000000..96e23ca --- /dev/null +++ b/go/pkg/comics/artist_test.go @@ -0,0 +1,96 @@ +package comics + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var artistModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Name", "string"}, + {"Model", "string"}, +} + +func TestArtistModel(t *testing.T) { + m := Artist{} + if reflect.TypeOf(m).NumField() != len(artistModelTestTable) { + t.Fail() + } + for index, testData := range artistModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListArtists(t *testing.T) { + var ( + artistDao = ArtistDAO{Db: dao.TestDb} + ) + artists, err := artistDao.FindAll() + if err != nil { + t.Fail() + } + if artists != nil { + t.Fail() + } +} + +func TestInsertArtist(t *testing.T) { + var ( + artistDao = ArtistDAO{Db: dao.TestDb} + artist = Artist{} + artists []Artist + ) + artist.ID = bson.NewObjectId() + artist.Name = "Turner, Michael" + err := artistDao.Insert(artist) + if err != nil { + t.Fail() + } + artists, _ = artistDao.FindAll() + if len(artists) != 1 { + t.Fail() + } +} + +func TestUpsertArtist(t *testing.T) { + var ( + artistDao = ArtistDAO{Db: dao.TestDb} + artist = Artist{} + ) + artist.ID = bson.NewObjectId() + artist.Name = "Marz, Ron" + _, err := artistDao.Upsert(artist) + if err != nil { + t.Fail() + } + artists, _ := artistDao.FindAll() + if len(artists) != 2 { + t.Fail() + } +} + +func TestDeleteArtist(t *testing.T) { + var ( + artistDao = ArtistDAO{Db: dao.TestDb} + ) + artists, err := artistDao.FindAll() + if err != nil { + t.Fail() + } + for _, artist := range artists { + artistDao.Delete(artist) + } + artists, _ = artistDao.FindAll() + if len(artists) != 0 { + t.Fail() + } +} diff --git a/go/pkg/comics/artist_views.go b/go/pkg/comics/artist_views.go new file mode 100644 index 0000000..c685f1b --- /dev/null +++ b/go/pkg/comics/artist_views.go @@ -0,0 +1,23 @@ +package comics + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/util" +) + +const ( + // ArtistPublisherTemplate defines name of template file for comics publishers + ArtistPublisherTemplate = "comics/publishers.html" +) + +func showArtistList(c *gin.Context) { + var dao = ArtistDAO{Db: dao.KontorDb} + if artists, err := dao.FindAll(); err == nil { + util.Render(c, gin.H{"title": "Comic Artists", "payload": artists}, "artists.html") + } else { + c.AbortWithError(http.StatusNotFound, err) + } +} diff --git a/go/pkg/comics/comic.go b/go/pkg/comics/comic.go new file mode 100644 index 0000000..7332d65 --- /dev/null +++ b/go/pkg/comics/comic.go @@ -0,0 +1,15 @@ +package comics + +import ( + "gopkg.in/mgo.v2/bson" +) + +// Comic defines the data model for comic issues with id, title, publisher, order and completion status. +type Comic struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Title string `json:"title" bson:"title"` + Publisher bson.ObjectId `json:"publisher" bson:"publisher,omitempty"` + CurrentOrder bool `json:"current_order" bson:"current_order"` + Completed bool `json:"completed" bson:"completed"` + Model string `json:"model" bson:"model"` +} diff --git a/go/pkg/comics/comic_dao.go b/go/pkg/comics/comic_dao.go new file mode 100644 index 0000000..c9f9b02 --- /dev/null +++ b/go/pkg/comics/comic_dao.go @@ -0,0 +1,75 @@ +package comics + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// ComicDAO extends the type BaseDAO. +type ComicDAO struct { + Db dao.BaseDAO +} + +const ( + // COMICCOLLECTION defines the collection name for storing comics. + COMICCOLLECTION = "comic" + // COMICMODEL defines the name of the comic data model. + COMICMODEL = "kontor.comics.comic" +) + +// FindAll retrieves the list of comisc from the database. +func (m *ComicDAO) FindAll() ([]Comic, error) { + m.Db.Connect() + var comics []Comic + err := m.Db.MongoDb.C(COMICCOLLECTION).Find(bson.M{"model": COMICMODEL}).All(&comics) + return comics, err +} + +// FindByID returns an comic with given id or returns the error. +func (m *ComicDAO) FindByID(id string) (Comic, error) { + m.Db.Connect() + var comic Comic + err := m.Db.MongoDb.C(COMICCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&comic) + return comic, err +} + +// FindByName returns an comic with given name or returns the error. +func (m *ComicDAO) FindByName(name string) (Comic, error) { + m.Db.Connect() + var comic Comic + err := m.Db.MongoDb.C(COMICCOLLECTION).Find(bson.M{"name": name, "model": COMICMODEL}).One(&comic) + return comic, err +} + +// Insert a comic into database. +func (m *ComicDAO) Insert(comic Comic) error { + m.Db.Connect() + comic.Model = COMICMODEL + err := m.Db.MongoDb.C(COMICCOLLECTION).Insert(&comic) + //util.PrintDebug("ComicDAO.Insert: %v", comic) + return err +} + +// Upsert a comic into database. +func (m *ComicDAO) Upsert(comic Comic) (*mgo.ChangeInfo, error) { + m.Db.Connect() + comic.Model = COMICMODEL + info, err := m.Db.MongoDb.C(COMICCOLLECTION).Upsert(bson.M{"title": comic.Title}, &comic) + return info, err +} + +// Delete an existing comic. +func (m *ComicDAO) Delete(comic Comic) error { + m.Db.Connect() + err := m.Db.MongoDb.C(COMICCOLLECTION).Remove(&comic) + return err +} + +// Update an existing movie +func (m *ComicDAO) Update(comic Comic) error { + m.Db.Connect() + err := m.Db.MongoDb.C(COMICCOLLECTION).UpdateId(comic.ID, &comic) + return err +} diff --git a/go/pkg/comics/comic_test.go b/go/pkg/comics/comic_test.go new file mode 100644 index 0000000..baea792 --- /dev/null +++ b/go/pkg/comics/comic_test.go @@ -0,0 +1,88 @@ +package comics + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var comicModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Title", "string"}, + {"Publisher", "string"}, + {"CurrentOrder", "bool"}, + {"Completed", "bool"}, + {"Model", "string"}, +} + +func TestComicModel(t *testing.T) { + m := Comic{} + if reflect.TypeOf(m).NumField() != len(comicModelTestTable) { + t.Fail() + } + for index, testData := range comicModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListComics(t *testing.T) { + var ( + comicDao = ComicDAO{Db: dao.TestDb} + ) + comics, err := comicDao.FindAll() + if err != nil { + t.Fail() + } + if len(comics) != 0 { + t.Fail() + } +} + +func TestInsertComic(t *testing.T) { + var ( + comicDao = ComicDAO{Db: dao.TestDb} + comic = Comic{} + comics []Comic + ) + comic.ID = bson.NewObjectId() + comic.Title = "Simpsons" + comicDao.Insert(comic) + comics, _ = comicDao.FindAll() + if len(comics) != 1 { + t.Fail() + } +} +func TestUpsertComic(t *testing.T) { + var ( + comicDao = ComicDAO{Db: dao.TestDb} + comic = Comic{} + ) + comic.ID = bson.NewObjectId() + comic.Title = "1602" + comicDao.Upsert(comic) + comics, _ := comicDao.FindAll() + if len(comics) != 2 { + t.Fail() + } +} +func TestDeleteComic(t *testing.T) { + var ( + comicDao = ComicDAO{Db: dao.TestDb} + ) + comics, _ := comicDao.FindAll() + for _, comic := range comics { + comicDao.Delete(comic) + } + comics, _ = comicDao.FindAll() + if len(comics) != 0 { + t.Fail() + } +} diff --git a/go/pkg/comics/publisher.go b/go/pkg/comics/publisher.go new file mode 100644 index 0000000..7bef875 --- /dev/null +++ b/go/pkg/comics/publisher.go @@ -0,0 +1,12 @@ +package comics + +import ( + "gopkg.in/mgo.v2/bson" +) + +// Publisher defines the data model for comic publishers with id and name. +type Publisher struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Model string `json:"model" bson:"model"` +} diff --git a/go/pkg/comics/publisher_dao.go b/go/pkg/comics/publisher_dao.go new file mode 100644 index 0000000..f12cf22 --- /dev/null +++ b/go/pkg/comics/publisher_dao.go @@ -0,0 +1,75 @@ +package comics + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// PublisherDAO extends the type BaseDAO. +type PublisherDAO struct { + Db dao.BaseDAO +} + +const ( + // PUBLISHERCOLLECTION defines the collection name for storing publishers. + PUBLISHERCOLLECTION = "publisher" + // PUBLISHERMODEL defines the name of the publisher data model. + PUBLISHERMODEL = "kontor.comics.publisher" +) + +// FindAll retrieves the list of publishers from the database. +func (m *PublisherDAO) FindAll() ([]Publisher, error) { + m.Db.Connect() + var publishers []Publisher + err := m.Db.MongoDb.C(PUBLISHERCOLLECTION).Find(bson.M{"model": PUBLISHERMODEL}).All(&publishers) + return publishers, err +} + +// FindByID returns an publisher with given id or returns the error. +func (m *PublisherDAO) FindByID(id string) (Publisher, error) { + m.Db.Connect() + var publisher Publisher + err := m.Db.MongoDb.C(PUBLISHERCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&publisher) + return publisher, err +} + +// FindByName returns an comic with given name or returns the error. +func (m *PublisherDAO) FindByName(name string) (Publisher, error) { + m.Db.Connect() + var publisher Publisher + err := m.Db.MongoDb.C(PUBLISHERCOLLECTION).Find(bson.M{"name": name, "model": PUBLISHERMODEL}).One(&publisher) + return publisher, err +} + +// Insert a publisher into database. +func (m *PublisherDAO) Insert(publisher Publisher) error { + m.Db.Connect() + publisher.Model = PUBLISHERMODEL + err := m.Db.MongoDb.C(PUBLISHERCOLLECTION).Insert(&publisher) + //util.PrintDebug("PublisherDAO.Insert: %v", publisher) + return err +} + +// Upsert a publisher into database. +func (m *PublisherDAO) Upsert(publisher Publisher) (*mgo.ChangeInfo, error) { + m.Db.Connect() + publisher.Model = PUBLISHERMODEL + info, err := m.Db.MongoDb.C(PUBLISHERCOLLECTION).Upsert(bson.M{"name": publisher.Name}, &publisher) + return info, err +} + +// Delete an existing publisher. +func (m *PublisherDAO) Delete(publisher Publisher) error { + m.Db.Connect() + err := m.Db.MongoDb.C(PUBLISHERCOLLECTION).Remove(&publisher) + return err +} + +// Update an existing publisher. +func (m *PublisherDAO) Update(publisher Publisher) error { + m.Db.Connect() + err := m.Db.MongoDb.C(PUBLISHERCOLLECTION).UpdateId(publisher.ID, &publisher) + return err +} diff --git a/go/pkg/comics/publisher_test.go b/go/pkg/comics/publisher_test.go new file mode 100644 index 0000000..289c2cf --- /dev/null +++ b/go/pkg/comics/publisher_test.go @@ -0,0 +1,87 @@ +package comics + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var publisherModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Name", "string"}, + {"Model", "string"}, +} + +func TestPublisherModel(t *testing.T) { + m := Publisher{} + if reflect.TypeOf(m).NumField() != len(publisherModelTestTable) { + t.Fail() + } + for index, testData := range publisherModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListPublishers(t *testing.T) { + var ( + publisherDao = PublisherDAO{Db: dao.TestDb} + ) + publishers, err := publisherDao.FindAll() + if err != nil { + t.Fail() + } + if len(publishers) != 0 { + t.Fail() + } +} + +func TestInsertPublisher(t *testing.T) { + var ( + publisherDao = PublisherDAO{Db: dao.TestDb} + publisher = Publisher{} + publishers []Publisher + ) + publisher.ID = bson.NewObjectId() + publisher.Name = "CrossGen" + publisherDao.Insert(publisher) + publishers, _ = publisherDao.FindAll() + if len(publishers) != 1 { + t.Fail() + } +} + +func TestUpsertPublisher(t *testing.T) { + var ( + publisherDao = PublisherDAO{Db: dao.TestDb} + publisher = Publisher{} + ) + publisher.ID = bson.NewObjectId() + publisher.Name = "Marvel" + publisherDao.Upsert(publisher) + publishers, _ := publisherDao.FindAll() + if len(publishers) != 2 { + t.Fail() + } +} + +func TestDeletePublisher(t *testing.T) { + var ( + publisherDao = PublisherDAO{Db: dao.TestDb} + ) + publishers, _ := publisherDao.FindAll() + for _, publisher := range publishers { + publisherDao.Delete(publisher) + } + publishers, _ = publisherDao.FindAll() + if len(publishers) != 0 { + t.Fail() + } +} diff --git a/go/pkg/comics/publisher_views.go b/go/pkg/comics/publisher_views.go new file mode 100644 index 0000000..e6acfc9 --- /dev/null +++ b/go/pkg/comics/publisher_views.go @@ -0,0 +1,70 @@ +package comics + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/util" + "net/http" + + "github.com/gin-gonic/gin" +) + +const ( + // ComicsPublisherTemplate defines name of template file for comics publishers + ComicsPublisherTemplate = "comics/publishers.html" + // ComicsPublisherDetailsTemplate defines name of template file for comics publishers + ComicsPublisherDetailsTemplate = "comics/publisher.html" +) + +func showPublisherList(c *gin.Context) { + var dao = PublisherDAO{Db: dao.KontorDb} + if publishers, err := dao.FindAll(); err == nil { + util.Render(c, gin.H{"title": "Comics Publisher List", "payload": publishers}, ComicsPublisherTemplate) + } else { + c.AbortWithError(http.StatusNotFound, err) + } +} + +func showPublisherDetails(c *gin.Context) { + var dao = PublisherDAO{Db: dao.KontorDb} + publisherid := c.Param("publisher_id") + if publisher, err := dao.FindByID(publisherid); err == nil { + util.Render(c, gin.H{"title": "Comics Publisher", "payload": publisher, "action": util.SaveAction}, ComicsPublisherDetailsTemplate) + } else { + c.AbortWithError(http.StatusNotFound, err) + } +} + +func showPublisherCreation(c *gin.Context) { + var publisher = Publisher{} + util.Render(c, gin.H{"title": "Comics Publisher Creation", "payload": publisher, "action": util.AddAction}, ComicsPublisherTemplate) +} + +func validatePublisherDetails(c *gin.Context) { + name := c.PostForm("name") + action := c.PostForm("action") + publisherid := c.PostForm("publisherid") + + var err error + var dao = PublisherDAO{Db: dao.KontorDb} + var publisher = Publisher{} + + switch action { + case util.AddAction: + publisher.Name = name + _, err = dao.Upsert(publisher) + case util.SaveAction: + publisher, _ = dao.FindByID(publisherid) + publisher.Name = name + err = dao.Update(publisher) + case util.DeleteAction: + publisher, _ = dao.FindByID(publisherid) + err = dao.Delete(publisher) + } + if err == nil { + c.Redirect(http.StatusTemporaryRedirect, "/comics/publisher") + } else { + c.HTML(http.StatusBadRequest, "comics/publisher.html", gin.H{ + "ErrorTitle": "Publisher Creation Failed", + "ErrorMessage": err.Error()}) + } +} diff --git a/go/pkg/comics/routes.go b/go/pkg/comics/routes.go new file mode 100644 index 0000000..784704e --- /dev/null +++ b/go/pkg/comics/routes.go @@ -0,0 +1,28 @@ +package comics + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/auth" + + "github.com/gin-gonic/gin" +) + +// GetRoutes returns all routes for comic related data. +func GetRoutes(router *gin.Engine) { + comicRoutes := router.Group("/comics") + { + comicRoutes.GET("/", auth.EnsureLoggedIn(), showComicList) + comicRoutes.GET("/artist", auth.EnsureLoggedIn(), showArtistList) + comicRoutes.GET("/artist/view/:artist_id", auth.EnsureLoggedIn(), showArtistList) + comicRoutes.GET("/artist/create", auth.EnsureLoggedIn(), showArtistList) + comicRoutes.POST("/artist/create", auth.EnsureLoggedIn(), showArtistList) + comicRoutes.GET("/comic", auth.EnsureLoggedIn(), showComicList) + comicRoutes.GET("/comic/view/:comic_id", auth.EnsureLoggedIn(), showComic) + comicRoutes.GET("/comic/create", auth.EnsureLoggedIn(), showComicList) + comicRoutes.POST("/comic/create", auth.EnsureLoggedIn(), showComicList) + comicRoutes.GET("/publisher", auth.EnsureLoggedIn(), showPublisherList) + comicRoutes.POST("/publisher", auth.EnsureLoggedIn(), showPublisherList) + comicRoutes.GET("/publisher/view/:publisher_id", auth.EnsureLoggedIn(), showPublisherDetails) + comicRoutes.POST("/publisher/validate", auth.EnsureLoggedIn(), validatePublisherDetails) + comicRoutes.GET("/publisher/create", auth.EnsureLoggedIn(), showPublisherCreation) + } +} diff --git a/go/pkg/comics/views.go b/go/pkg/comics/views.go new file mode 100644 index 0000000..f1e9592 --- /dev/null +++ b/go/pkg/comics/views.go @@ -0,0 +1,28 @@ +package comics + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/util" + "net/http" + + "github.com/gin-gonic/gin" +) + +func showComicList(c *gin.Context) { + var dao = ComicDAO{Db: dao.KontorDb} + if comics, err := dao.FindAll(); err == nil { + util.Render(c, gin.H{"title": "Comics", "payload": comics}, "comics.html") + } else { + c.AbortWithError(http.StatusNotFound, err) + } +} + +func showComic(c *gin.Context) { + var dao = ComicDAO{Db: dao.KontorDb} + comicID := c.Param("comic_id") + if comic, err := dao.FindByID(comicID); err == nil { + util.Render(c, gin.H{"title": "Comic Details", "payload": comic, "action": util.SaveAction}, "comic.html") + } else { + c.AbortWithError(http.StatusNotFound, err) + } +} diff --git a/go/pkg/context/comic/responses/comic.go b/go/pkg/context/comic/responses/comic.go new file mode 100644 index 0000000..a8b6c06 --- /dev/null +++ b/go/pkg/context/comic/responses/comic.go @@ -0,0 +1,15 @@ +package responses + +// Response is the Ping Response +type Response struct { + Message string `json:"message"` +} + +type ComicList struct { + Comics []Comic `json:"comics"` +} + +type Comic struct { + ID string `json:"id"` + Title string `json:"title"` +} diff --git a/go/pkg/context/comic/routing/endpoints.go b/go/pkg/context/comic/routing/endpoints.go new file mode 100644 index 0000000..abe4187 --- /dev/null +++ b/go/pkg/context/comic/routing/endpoints.go @@ -0,0 +1,33 @@ +package routing + +import ( + "net/http" + + "gitlab.com/tpeetz-kontor/kontor-go/cmd/pkg/context/comic/responses" + "gitlab.com/tpeetz-kontor/kontor-go/cmd/pkg/infrastructure/app" + responseFactory "gitlab.com/tpeetz-kontor/kontor-go/cmd/pkg/infrastructure/response" +) + +// Handler is the http.Handler for this request +type Handler struct { + app *app.Application +} + +// NewHandler will create a new Handler to handle this request +func NewHandler(app *app.Application) *Handler { + return &Handler{app} +} + +// Handle will handle the incoming request +func (handler *Handler) ComicList(response http.ResponseWriter, request *http.Request) { + handler.app.Logger.Info("Ping Handler Dispatched.") + + responseFactory.Send( + response, + http.StatusOK, + &responses.ComicList{ + Comics: []responses.Comic{{ID: "123", Title: "Comic1"}, {ID: "123", Title: "Comic1"}}, + }, + handler.app.Config.HTTP.Content, + ) +} diff --git a/go/pkg/dao/database.go b/go/pkg/dao/database.go new file mode 100644 index 0000000..1a0f4c1 --- /dev/null +++ b/go/pkg/dao/database.go @@ -0,0 +1,31 @@ +package dao + +import ( + "log" + + mgo "gopkg.in/mgo.v2" +) + +// BaseDAO definess the connection parameters to a MongoDB instance. +type BaseDAO struct { + Server string + Database string + MongoDb *mgo.Database +} + +var ( + // KontorDb has the database connection to the productive MongoDB instance. + KontorDb = BaseDAO{Server: "localhost", Database: "kontor"} + // TestDb has the database connection to the test MongoDB instance. + TestDb = BaseDAO{Server: "localhost", Database: "kontor_test"} +) + +// Connect instantiates the database session. +func (m *BaseDAO) Connect() { + session, err := mgo.Dial(m.Server) + if err != nil { + //util.PrintDebug("Connect: %v", err) + log.Fatal(err) + } + m.MongoDb = session.DB(m.Database) +} diff --git a/go/pkg/dao/database_test.go b/go/pkg/dao/database_test.go new file mode 100644 index 0000000..52a204c --- /dev/null +++ b/go/pkg/dao/database_test.go @@ -0,0 +1,51 @@ +package dao + +import ( + "reflect" + "testing" +) + + +var baseDaoTestTable = []struct { + name string + typeName string +}{ + {"Server", "string"}, + {"Database", "string"}, + {"MongoDb", "ptr"}, +} + +func TestCheckBaseDao(t *testing.T) { + d := BaseDAO{} + for index, testData := range baseDaoTestTable { + givenType := reflect.TypeOf(d).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestConnectDb(t *testing.T) { + d := BaseDAO{} + d.Connect() + if d.MongoDb == nil { + t.Fail() + } +} + +func TestDatabasesConfig(t *testing.T) { + kontorDb := KontorDb + if kontorDb.Server != "localhost" { + t.Fail() + } + if kontorDb.Database != "kontor" { + t.Fail() + } + testDb := TestDb + if testDb.Server != "localhost" { + t.Fail() + } + if testDb.Database != "kontor_test" { + t.Fail() + } +} diff --git a/go/pkg/infrastructure/app/factory.go b/go/pkg/infrastructure/app/factory.go new file mode 100644 index 0000000..3bc8ce7 --- /dev/null +++ b/go/pkg/infrastructure/app/factory.go @@ -0,0 +1,50 @@ +package app + +import ( + "context" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/gorilla/mux" + "gitlab.com/tpeetz-kontor/kontor-go/pkg/infrastructure/config" + "go.uber.org/zap" +) + +// Application is our general purpose Application struct +type Application struct { + Server *http.Server + Router *mux.Router + Logger *zap.Logger + Config *config.Config +} + +// Run will run the Application server +func (app *Application) Run() { + app.Logger.Info("App started...") + err := app.Server.ListenAndServe() + + if err != nil { + fmt.Println(err) + } +} + +// WaitForShutdown is a graceful way to handle server shutdown events +func WaitForShutdown(application *Application) { + // Create a channel to listen for OS signals + interruptChan := make(chan os.Signal, 1) + signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + + // Block until we receive a signal to our channel + <-interruptChan + + application.Logger.Info("Received shutdown signal, gracefully terminating") + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + application.Server.Shutdown(ctx) + os.Exit(0) +} diff --git a/go/pkg/infrastructure/config/factory.go b/go/pkg/infrastructure/config/factory.go new file mode 100644 index 0000000..ec411f8 --- /dev/null +++ b/go/pkg/infrastructure/config/factory.go @@ -0,0 +1,48 @@ +package config + +import "os" + +// AppConfig is the Application configuration struct +type AppConfig struct { + Name string + Version string + Token string +} + +// HTTPConfig is the Application HTTP configuration +type HTTPConfig struct { + Content string + Problem string + Port string +} + +// Config is the Configuration struct +type Config struct { + App AppConfig + HTTP HTTPConfig +} + +// New returns a new Config Struct +func New() *Config { + return &Config{ + App: AppConfig{ + Name: env("APP_NAME", "Kontor"), + Version: env("APP_VERSION", "v1.0"), + Token: env("APP_TOKEN", ""), + }, + HTTP: HTTPConfig{ + Content: env("HTTP_CONTENT_TYPE", "application/json"), + Problem: env("HTTP_PROBLEM", "application/problem+json"), + Port: env("HTTP_PORT", ":8086"), + }, + } +} + +// env is a simple helper function to read an environment variable or return a default value +func env(key string, defaultValue string) string { + if value, exists := os.LookupEnv(key); exists { + return value + } + + return defaultValue +} diff --git a/go/pkg/infrastructure/kernel/app.go b/go/pkg/infrastructure/kernel/app.go new file mode 100644 index 0000000..39eb5b7 --- /dev/null +++ b/go/pkg/infrastructure/kernel/app.go @@ -0,0 +1,91 @@ +package kernel + +import ( + "context" + "net/http" + "time" + + "github.com/google/uuid" + gohandlers "github.com/gorilla/handlers" + "github.com/gorilla/mux" + "gitlab.com/tpeetz-kontor/kontor-go/pkg/infrastructure/app" + "gitlab.com/tpeetz-kontor/kontor-go/pkg/infrastructure/config" + "go.uber.org/zap" +) + +// Boot the Application +func Boot() *app.Application { + // Configuration + config := bootConfig() + + // Router + router := bootRouter() + + // CORS + corsHandler := gohandlers.CORS(gohandlers.AllowedOrigins([]string{"*"})) + + // Logger + logger := bootLogger() + defer logger.Sync() // flushes buffer, if any + + // Create and return and Application + return &app.Application{ + Server: &http.Server{ + Addr: config.HTTP.Port, + Handler: corsHandler(requestIDMiddleware(router)), + IdleTimeout: 120 * time.Second, + ReadTimeout: 1 * time.Second, + WriteTimeout: 1 * time.Second, + }, + Router: router, + Logger: logger, + Config: config, + } +} + +func bootConfig() *config.Config { + return config.New() +} + +func bootRouter() *mux.Router { + return mux.NewRouter() +} + +func bootLogger() *zap.Logger { + logger, logError := zap.NewProduction() + if logError != nil { + panic(logError) + } + + return logger +} + +// ContextKey is used for context.Context value. The value requires a key that is not primitive type. +type ContextKey string + +// ContextKeyRequestID is the ContextKey for RequestID +const ContextKeyRequestID ContextKey = "requestID" + +func requestIDMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + id := uuid.New() + + ctx = context.WithValue(ctx, ContextKeyRequestID, id.String()) + + r = r.WithContext(ctx) + + bootLogger().Debug("Incoming request", + zap.String("method", r.Method), + zap.String("uri", r.RequestURI), + zap.String("addr", r.RemoteAddr), zap.String("id", id.String()), + ) + + next.ServeHTTP(w, r) + + bootLogger().Debug("Finished handling http req. %s", + zap.String("id", id.String())) + }) +} diff --git a/go/pkg/infrastructure/response/response.go b/go/pkg/infrastructure/response/response.go new file mode 100644 index 0000000..f8def4a --- /dev/null +++ b/go/pkg/infrastructure/response/response.go @@ -0,0 +1,20 @@ +package response + +import ( + "encoding/json" + "net/http" +) + +// Response is a generic HTTP Response Struct +type Response struct { + Data string `json:"data"` +} + +// Send a HTTP Response +func Send(responseWriter http.ResponseWriter, code int, payload interface{}, contentType string) { + response, _ := json.Marshal(payload) + + responseWriter.Header().Set("Content-Type", contentType) + responseWriter.WriteHeader(code) + responseWriter.Write(response) +} diff --git a/go/pkg/library/author.go b/go/pkg/library/author.go new file mode 100644 index 0000000..2aafcd7 --- /dev/null +++ b/go/pkg/library/author.go @@ -0,0 +1,12 @@ +package library + +import ( + "gopkg.in/mgo.v2/bson" +) + +// Author defines the data model for library authors with id and name. +type Author struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Model string `json:"model" bson:"model,omitempty"` +} diff --git a/go/pkg/library/author_dao.go b/go/pkg/library/author_dao.go new file mode 100644 index 0000000..aa5f189 --- /dev/null +++ b/go/pkg/library/author_dao.go @@ -0,0 +1,74 @@ +package library + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// AuthorDAO extends the type BaseDAO. +type AuthorDAO struct { + Db dao.BaseDAO +} + +const ( + // AUTHORCOLLECTION defines the collection name for storing authors. + AUTHORCOLLECTION = "author" + // AUTHORMODEL defines the name of the author data model. + AUTHORMODEL = "kontor.library.author" +) + +// FindAll retrieves the list of authors from the database. +func (m *AuthorDAO) FindAll() ([]Author, error) { + m.Db.Connect() + var authors []Author + err := m.Db.MongoDb.C(AUTHORCOLLECTION).Find(bson.M{"model": AUTHORMODEL}).All(&authors) + return authors, err +} + +// FindByID returns an author with given id or returns the error. +func (m *AuthorDAO) FindByID(id string) (Author, error) { + m.Db.Connect() + var author Author + err := m.Db.MongoDb.C(AUTHORCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&author) + return author, err +} + +// FindByName returns an author with given name or returns the error. +func (m *AuthorDAO) FindByName(name string) (Author, error) { + m.Db.Connect() + var author Author + err := m.Db.MongoDb.C(AUTHORCOLLECTION).Find(bson.M{"name": name, "model": AUTHORMODEL}).One(&author) + return author, err +} + +// Insert a author into database. +func (m *AuthorDAO) Insert(author Author) error { + m.Db.Connect() + author.Model = AUTHORMODEL + err := m.Db.MongoDb.C(AUTHORCOLLECTION).Insert(&author) + return err +} + +// Upsert a author into database. +func (m *AuthorDAO) Upsert(author Author) (*mgo.ChangeInfo, error) { + m.Db.Connect() + author.Model = AUTHORMODEL + info, err := m.Db.MongoDb.C(AUTHORCOLLECTION).Upsert(bson.M{"name": author.Name}, &author) + return info, err +} + +// Delete an existing author. +func (m *AuthorDAO) Delete(author Author) error { + m.Db.Connect() + err := m.Db.MongoDb.C(AUTHORCOLLECTION).Remove(&author) + return err +} + +// Update an existing author. +func (m *AuthorDAO) Update(author Author) error { + m.Db.Connect() + err := m.Db.MongoDb.C(AUTHORCOLLECTION).UpdateId(author.ID, &author) + return err +} diff --git a/go/pkg/library/author_test.go b/go/pkg/library/author_test.go new file mode 100644 index 0000000..54a6537 --- /dev/null +++ b/go/pkg/library/author_test.go @@ -0,0 +1,99 @@ +package library + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var authorModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Name", "string"}, + {"Model", "string"}, +} + +func TestAuthorModel(t *testing.T) { + m := Author{} + if reflect.TypeOf(m).NumField() != len(authorModelTestTable) { + t.Fail() + } + for index, testData := range authorModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListAuthors(t *testing.T) { + var ( + authorDao = AuthorDAO{Db: dao.TestDb} + ) + authors, err := authorDao.FindAll() + if err != nil { + t.Fail() + } + if len(authors) != 0 { + t.Fail() + } +} + +func TestInsertAuthor(t *testing.T) { + var ( + authorDao = AuthorDAO{Db: dao.TestDb} + author = Author{} + authors []Author + ) + author.ID = bson.NewObjectId() + author.Name = "Packt Publishing" + err := authorDao.Insert(author) + if err != nil { + t.Fail() + } + authors, err = authorDao.FindAll() + if len(authors) != 1 { + t.Fail() + } +} + +func TestUpsertAuthor(t *testing.T) { + var ( + authorDao = AuthorDAO{Db: dao.TestDb} + ) + var author = Author{} + author.ID = bson.NewObjectId() + author.Name = "Hansa Verlag" + authorDao.Upsert(author) + authors, err := authorDao.FindAll() + if err != nil { + t.Fail() + } + if len(authors) != 2 { + t.Fail() + } +} + +func TestDeleteAuthor(t *testing.T) { + var ( + authorDao = AuthorDAO{Db: dao.TestDb} + ) + authors, err := authorDao.FindAll() + if err != nil { + t.Fail() + } + for _, author := range authors { + authorDao.Delete(author) + } + authors, err = authorDao.FindAll() + if err != nil { + t.Fail() + } + if len(authors) != 0 { + t.Fail() + } +} diff --git a/go/pkg/library/book.go b/go/pkg/library/book.go new file mode 100644 index 0000000..855f71c --- /dev/null +++ b/go/pkg/library/book.go @@ -0,0 +1,18 @@ +package library + +import ( + "gopkg.in/mgo.v2/bson" +) + +// Book defines the data model for library books with id, title, author, publisher, +// isbn, year and edition. +type Book struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Title string `json:"title" bson:"title"` + Author string `json:"author" bson:"author"` + Publisher bson.ObjectId `json:"publisher" bson:"publisher,omitempty"` + Isbn string `json:"isbn" bson:"isbn,omitempty"` + Year int `json:"year" bson:"year,omitempty"` + Edition string `json:"edition" bson:"edition,omitempty"` + Model string `json:"model" bson:"model,omitempty"` +} diff --git a/go/pkg/library/book_dao.go b/go/pkg/library/book_dao.go new file mode 100644 index 0000000..ddfea6a --- /dev/null +++ b/go/pkg/library/book_dao.go @@ -0,0 +1,74 @@ +package library + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// BookDAO extends the type BaseDAO. +type BookDAO struct { + Db dao.BaseDAO +} + +const ( + // BOOKCOLLECTION defines the collection name for storing books. + BOOKCOLLECTION = "book" + // BOOKMODEL defines the name of the book data model. + BOOKMODEL = "kontor.library.book" +) + +// FindAll retrieves the list of books from the database. +func (m *BookDAO) FindAll() ([]Book, error) { + m.Db.Connect() + var books []Book + err := m.Db.MongoDb.C(BOOKCOLLECTION).Find(bson.M{"model": BOOKMODEL}).All(&books) + return books, err +} + +// FindByID returns an book with given id or returns the error. +func (m *BookDAO) FindByID(id string) (Book, error) { + m.Db.Connect() + var book Book + err := m.Db.MongoDb.C(BOOKCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&book) + return book, err +} + +// FindByTitle returns a book with given title or returns the error. +func (m *BookDAO) FindByTitle(title string) (Book, error) { + m.Db.Connect() + var book Book + err := m.Db.MongoDb.C(BOOKCOLLECTION).Find(bson.M{"title": title, "model": BOOKMODEL}).One(&book) + return book, err +} + +// Insert a book into database. +func (m *BookDAO) Insert(book Book) error { + m.Db.Connect() + book.Model = BOOKMODEL + err := m.Db.MongoDb.C(BOOKCOLLECTION).Insert(&book) + return err +} + +// Upsert a book into database. +func (m *BookDAO) Upsert(book Book) (*mgo.ChangeInfo, error) { + m.Db.Connect() + book.Model = BOOKMODEL + info, err := m.Db.MongoDb.C(BOOKCOLLECTION).Upsert(bson.M{"title": book.Title}, &book) + return info, err +} + +// Delete an existing book. +func (m *BookDAO) Delete(book Book) error { + m.Db.Connect() + err := m.Db.MongoDb.C(BOOKCOLLECTION).Remove(&book) + return err +} + +// Update an existing book. +func (m *BookDAO) Update(book Book) error { + m.Db.Connect() + err := m.Db.MongoDb.C(BOOKCOLLECTION).UpdateId(book.ID, &book) + return err +} diff --git a/go/pkg/library/book_test.go b/go/pkg/library/book_test.go new file mode 100644 index 0000000..7a4f1f1 --- /dev/null +++ b/go/pkg/library/book_test.go @@ -0,0 +1,107 @@ +package library + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var bookModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Title", "string"}, + {"Author", "string"}, + {"Publisher", "string"}, + {"Isbn", "string"}, + {"Year", "int"}, + {"Edition", "string"}, + {"Model", "string"}, +} + +func TestBookModel(t *testing.T) { + m := Book{} + if reflect.TypeOf(m).NumField() != len(bookModelTestTable) { + t.Fail() + } + for index, testData := range bookModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListBooks(t *testing.T) { + var ( + bookDao = BookDAO{Db: dao.TestDb} + ) + books, err := bookDao.FindAll() + if err != nil { + t.Fail() + } + if len(books) != 0 { + t.Fail() + } +} + +func TestInsertBook(t *testing.T) { + var ( + bookDao = BookDAO{Db: dao.TestDb} + book = Book{} + books []Book + ) + book.ID = bson.NewObjectId() + book.Title = "Packt Publishing" + err := bookDao.Insert(book) + if err != nil { + t.Fail() + } + books, err = bookDao.FindAll() + if err != nil { + t.Fail() + } + if len(books) != 1 { + t.Fail() + } +} + +func TestUpsertBook(t *testing.T) { + var ( + bookDao = BookDAO{Db: dao.TestDb} + book = Book{} + ) + book.ID = bson.NewObjectId() + book.Title = "Hansa Verlag" + bookDao.Upsert(book) + books, err := bookDao.FindAll() + if err != nil { + t.Fail() + } + if len(books) != 2 { + t.Fail() + } +} + +func TestDeleteBook(t *testing.T) { + var ( + bookDao = BookDAO{Db: dao.TestDb} + ) + books, err := bookDao.FindAll() + if err != nil { + t.Fail() + } + for _, book := range books { + bookDao.Delete(book) + } + books, err = bookDao.FindAll() + if err != nil { + t.Fail() + } + if len(books) != 0 { + t.Fail() + } +} diff --git a/go/pkg/library/publisher.go b/go/pkg/library/publisher.go new file mode 100644 index 0000000..b7eb84d --- /dev/null +++ b/go/pkg/library/publisher.go @@ -0,0 +1,12 @@ +package library + +import ( + "gopkg.in/mgo.v2/bson" +) + +// Publisher defines the data model for library publishers with id and name. +type Publisher struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Model string `json:"model" bson:"model"` +} diff --git a/go/pkg/library/publisher_dao.go b/go/pkg/library/publisher_dao.go new file mode 100644 index 0000000..0e48cf5 --- /dev/null +++ b/go/pkg/library/publisher_dao.go @@ -0,0 +1,74 @@ +package library + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// PublisherDAO extends the type BaseDAO. +type PublisherDAO struct { + Db dao.BaseDAO +} + +const ( + // PUBLISHERCOLLECTION defines the collection name for storing publishers. + PUBLISHERCOLLECTION = "publisher" + // PUBLISHERMODEL defines the name of the publisher data model. + PUBLISHERMODEL = "kontor.library.publisher" +) + +// FindAll retrieves the list of publishers from the database. +func (m *PublisherDAO) FindAll() ([]Publisher, error) { + m.Db.Connect() + var publishers []Publisher + err := m.Db.MongoDb.C(PUBLISHERCOLLECTION).Find(bson.M{"model": PUBLISHERMODEL}).All(&publishers) + return publishers, err +} + +// FindByID returns an publisher with given id or returns the error. +func (m *PublisherDAO) FindByID(id string) (Publisher, error) { + m.Db.Connect() + var publisher Publisher + err := m.Db.MongoDb.C(PUBLISHERCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&publisher) + return publisher, err +} + +// FindByName returns an comic with given name or returns the error. +func (m *PublisherDAO) FindByName(name string) (Publisher, error) { + m.Db.Connect() + var publisher Publisher + err := m.Db.MongoDb.C(PUBLISHERCOLLECTION).Find(bson.M{"name": name, "model": PUBLISHERMODEL}).One(&publisher) + return publisher, err +} + +// Insert a publisher into database. +func (m *PublisherDAO) Insert(publisher Publisher) error { + m.Db.Connect() + publisher.Model = PUBLISHERMODEL + err := m.Db.MongoDb.C(PUBLISHERCOLLECTION).Insert(&publisher) + return err +} + +// Upsert a publisher into database. +func (m *PublisherDAO) Upsert(publisher Publisher) (*mgo.ChangeInfo, error) { + m.Db.Connect() + publisher.Model = PUBLISHERMODEL + info, err := m.Db.MongoDb.C(PUBLISHERCOLLECTION).Upsert(bson.M{"name": publisher.Name}, &publisher) + return info, err +} + +// Delete an existing publisher. +func (m *PublisherDAO) Delete(publisher Publisher) error { + m.Db.Connect() + err := m.Db.MongoDb.C(PUBLISHERCOLLECTION).Remove(&publisher) + return err +} + +// Update an existing publisher. +func (m *PublisherDAO) Update(publisher Publisher) error { + m.Db.Connect() + err := m.Db.MongoDb.C(PUBLISHERCOLLECTION).UpdateId(publisher.ID, &publisher) + return err +} diff --git a/go/pkg/library/publisher_test.go b/go/pkg/library/publisher_test.go new file mode 100644 index 0000000..1c680f3 --- /dev/null +++ b/go/pkg/library/publisher_test.go @@ -0,0 +1,103 @@ +package library + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var publisherModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Name", "string"}, + {"Model", "string"}, +} + +func TestPublisherModel(t *testing.T) { + m := Publisher{} + if reflect.TypeOf(m).NumField() != len(publisherModelTestTable) { + t.Fail() + } + for index, testData := range publisherModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + + } +} + +func TestListPublishers(t *testing.T) { + var ( + publisherDao = PublisherDAO{Db: dao.TestDb} + ) + publishers, err := publisherDao.FindAll() + if err != nil { + t.Fail() + } + if len(publishers) != 0 { + t.Fail() + } +} + +func TestInsertPublisher(t *testing.T) { + var ( + publisherDao = PublisherDAO{Db: dao.TestDb} + publisher = Publisher{} + publishers []Publisher + ) + publisher.ID = bson.NewObjectId() + publisher.Name = "Packt Publishing" + err := publisherDao.Insert(publisher) + if err != nil { + t.Fail() + } + publishers, err = publisherDao.FindAll() + if err != nil { + t.Fail() + } + if len(publishers) != 1 { + t.Fail() + } +} + +func TestUpsertPublisher(t *testing.T) { + var ( + publisherDao = PublisherDAO{Db: dao.TestDb} + publisher = Publisher{} + ) + publisher.ID = bson.NewObjectId() + publisher.Name = "Hansa Verlag" + publisherDao.Upsert(publisher) + publishers, err := publisherDao.FindAll() + if err != nil { + t.Fail() + } + if len(publishers) != 2 { + t.Fail() + } +} + +func TestDeletePublisher(t *testing.T) { + var ( + publisherDao = PublisherDAO{Db: dao.TestDb} + ) + publishers, err := publisherDao.FindAll() + if err != nil { + t.Fail() + } + for _, publisher := range publishers { + publisherDao.Delete(publisher) + } + publishers, err = publisherDao.FindAll() + if err != nil { + t.Fail() + } + if len(publishers) != 0 { + t.Fail() + } +} diff --git a/go/pkg/library/publisher_views.go b/go/pkg/library/publisher_views.go new file mode 100644 index 0000000..233169a --- /dev/null +++ b/go/pkg/library/publisher_views.go @@ -0,0 +1,68 @@ +package library + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/util" + "net/http" + + "github.com/gin-gonic/gin" +) + +const ( + // LibraryPublisherTemplate defines name of template file for comics publishers + LibraryPublisherTemplate = "library/publishers.html" +) + +func showPublisherList(c *gin.Context) { + var dao = PublisherDAO{Db: dao.KontorDb} + if publishers, err := dao.FindAll(); err == nil { + util.Render(c, gin.H{"title": "Library Publisher List", "payload": publishers}, LibraryPublisherTemplate) + } else { + c.AbortWithError(http.StatusNotFound, err) + } +} + +func showPublisherDetails(c *gin.Context) { + var dao = PublisherDAO{Db: dao.KontorDb} + publisherid := c.Param("publisher_id") + if publisher, err := dao.FindByID(publisherid); err == nil { + util.Render(c, gin.H{"title": "Library Publisher", "payload": publisher, "action": util.SaveAction}, LibraryPublisherTemplate) + } else { + c.AbortWithError(http.StatusNotFound, err) + } +} + +func showPublisherCreation(c *gin.Context) { + var publisher = Publisher{} + util.Render(c, gin.H{"title": "Library Publisher Creation", "payload": publisher, "action": util.AddAction}, LibraryPublisherTemplate) +} + +func validatePublisherDetails(c *gin.Context) { + name := c.PostForm("name") + action := c.PostForm("action") + publisherid := c.PostForm("publisherid") + + var err error + var dao = PublisherDAO{Db: dao.KontorDb} + var publisher = Publisher{} + + switch action { + case util.AddAction: + publisher.Name = name + _, err = dao.Upsert(publisher) + case util.SaveAction: + publisher, _ = dao.FindByID(publisherid) + publisher.Name = name + err = dao.Update(publisher) + case util.DeleteAction: + publisher, _ = dao.FindByID(publisherid) + err = dao.Delete(publisher) + } + if err == nil { + c.Redirect(http.StatusTemporaryRedirect, "/library/publisher") + } else { + c.HTML(http.StatusBadRequest, "library/publisher.html", gin.H{ + "ErrorTitle": "Publisher Creation Failed", + "ErrorMessage": err.Error()}) + } +} diff --git a/go/pkg/library/routes.go b/go/pkg/library/routes.go new file mode 100644 index 0000000..ea93fd5 --- /dev/null +++ b/go/pkg/library/routes.go @@ -0,0 +1,22 @@ +package library + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/auth" + + "github.com/gin-gonic/gin" +) + +// GetRoutes returns all routes for library related data. +func GetRoutes(router *gin.Engine) { + libraryRoutes := router.Group("/library") + { + libraryRoutes.GET("/", auth.EnsureLoggedIn(), showBookList) + libraryRoutes.GET("/book", auth.EnsureLoggedIn(), showBookList) + libraryRoutes.GET("/author", auth.EnsureLoggedIn(), showAuthorList) + libraryRoutes.GET("/publisher", auth.EnsureLoggedIn(), showPublisherList) + libraryRoutes.POST("/publisher", auth.EnsureLoggedIn(), showPublisherList) + libraryRoutes.GET("/publisher/view/:publisher_id", auth.EnsureLoggedIn(), showPublisherDetails) + libraryRoutes.POST("/publisher/validate", auth.EnsureLoggedIn(), validatePublisherDetails) + libraryRoutes.GET("/publisher/create", auth.EnsureLoggedIn(), showPublisherCreation) + } +} diff --git a/go/pkg/library/views.go b/go/pkg/library/views.go new file mode 100644 index 0000000..4b4bcc6 --- /dev/null +++ b/go/pkg/library/views.go @@ -0,0 +1,24 @@ +package library + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/util" + + "github.com/gin-gonic/gin" +) + +func showAuthorList(c *gin.Context) { + var authorDao = AuthorDAO{Db: dao.KontorDb} + if authors, err := authorDao.FindAll(); err == nil { + util.Render(c, gin.H{"title": "Author List", "payload": authors}, "library/authors.html") + } +} + +func showBookList(c *gin.Context) { + var bookDao = BookDAO{Db: dao.KontorDb} + if books, err := bookDao.FindAll(); err == nil { + util.Render(c, gin.H{"title": "Book List", "payload": books}, "library/books.html") + } else { + util.Render(c, gin.H{"title": "Kontor", "payload": nil}, "kontor/index.html") + } +} diff --git a/go/pkg/office/routes.go b/go/pkg/office/routes.go new file mode 100644 index 0000000..8674071 --- /dev/null +++ b/go/pkg/office/routes.go @@ -0,0 +1,13 @@ +package office + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/auth" + + "github.com/gin-gonic/gin" +) + +// GetRoutes returns all routes for office related data. +func GetRoutes(router *gin.Engine) { + officeRoutes := router.Group("/office") + officeRoutes.GET("/", auth.EnsureLoggedIn(), showIndexPage) +} diff --git a/go/pkg/office/views.go b/go/pkg/office/views.go new file mode 100644 index 0000000..2fd1504 --- /dev/null +++ b/go/pkg/office/views.go @@ -0,0 +1,11 @@ +package office + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/util" + + "github.com/gin-gonic/gin" +) + +func showIndexPage(c *gin.Context) { + util.Render(c, gin.H{"title": "Home Office", "payload": nil}, "office/index.html") +} diff --git a/go/pkg/properties/root.go b/go/pkg/properties/root.go new file mode 100644 index 0000000..c80e136 --- /dev/null +++ b/go/pkg/properties/root.go @@ -0,0 +1,25 @@ +package properties + +var ( + // Version defines the version of the web application kontor. + Version = "undefined" + // Debug defines the property debug to be used for more verbose output. + Debug = false + // Port defines port number under the web application is reachable. + Port = 8500 +) + +// SetVersion sets Version with given value. +func SetVersion(value string) { + Version = value +} + +// SetDebug sets Debug with given value. +func SetDebug(value bool) { + Debug = value +} + +// SetPort sets Port with given value. +func SetPort(value int) { + Port = value +} diff --git a/go/pkg/setup/data.go b/go/pkg/setup/data.go new file mode 100644 index 0000000..e45e813 --- /dev/null +++ b/go/pkg/setup/data.go @@ -0,0 +1,20 @@ +package setup + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/tysc" + "log" +) + +// CheckTradeYourSportsCardsData checks if the TYSC releated data is available. +func CheckTradeYourSportsCardsData() { + log.Printf("Check data for TradeYourSportsCards module") + var sport = tysc.SportDAO{Db: dao.KontorDb} + sport.Upsert(tysc.Sport{Name: "Football"}) + football, _ := sport.FindByName("Football") + sport.Upsert(tysc.Sport{Name: "Baseball"}) + sport.Upsert(tysc.Sport{Name: "Basketball"}) + sport.Upsert(tysc.Sport{Name: "Hockey"}) + var position = tysc.PositionDAO{Db: dao.KontorDb} + position.Upsert(tysc.Position{Name: "WR", Description: "Wide Receiver", Sport: football.ID}) +} diff --git a/go/pkg/setup/routes.go b/go/pkg/setup/routes.go new file mode 100644 index 0000000..3b1a9b6 --- /dev/null +++ b/go/pkg/setup/routes.go @@ -0,0 +1,38 @@ +package setup + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/admin" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/auth" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/comics" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/library" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/office" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/tradingcards" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/tysc" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/util" + + "github.com/gin-gonic/gin" +) + +// InitializeRoutes setup the routes for Kontor web application. +func InitializeRoutes(router *gin.Engine) { + + // Use the setUserStatus middleware for every route to set a flag + // indicating whether the request was from an authenticated user or not + router.Use(auth.SetSessionStatus()) + + // Handle the index route + router.GET("/", util.ShowIndexPage) + + userRoutes := router.Group("/user") + { + userRoutes.GET("/login", auth.EnsureNotLoggedIn(), admin.ShowLoginPage) + userRoutes.POST("/login", auth.EnsureNotLoggedIn(), admin.PerformLogin) + userRoutes.GET("/logout", auth.EnsureLoggedIn(), admin.Logout) + } + admin.GetRoutes(router) + comics.GetRoutes(router) + library.GetRoutes(router) + office.GetRoutes(router) + tradingcards.GetRoutes(router) + tysc.GetRoutes(router) +} diff --git a/go/pkg/setup/session.go b/go/pkg/setup/session.go new file mode 100644 index 0000000..5fe184a --- /dev/null +++ b/go/pkg/setup/session.go @@ -0,0 +1,15 @@ +package setup + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/auth" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" +) + +// CleanupSessions removes all sessions from database. +func CleanupSessions() { + sessionDao := auth.SessionDAO{Db: dao.KontorDb} + sessions, _ := sessionDao.FindAll() + for _, session := range sessions { + sessionDao.Delete(session) + } +} diff --git a/go/pkg/setup/user.go b/go/pkg/setup/user.go new file mode 100644 index 0000000..5272ed3 --- /dev/null +++ b/go/pkg/setup/user.go @@ -0,0 +1,20 @@ +package setup + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/admin" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + "gopkg.in/mgo.v2/bson" +) + +// CheckUserList ensures that at least the admin user is available. +func CheckUserList() { + var userDao = admin.UserDAO{Db: dao.KontorDb} + users, err := userDao.FindAll() + if err == nil && len(users) == 0 { + password, _ := admin.HashPassword("admin") + id := bson.NewObjectId() + user := admin.User{ID: id, Username: "admin", Password: password, Firstname: "Administrator", IsAdmin: true} + userDao.Insert(user) + } +} diff --git a/go/pkg/tradingcards/routes.go b/go/pkg/tradingcards/routes.go new file mode 100644 index 0000000..d5d3aad --- /dev/null +++ b/go/pkg/tradingcards/routes.go @@ -0,0 +1,13 @@ +package tradingcards + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/auth" + + "github.com/gin-gonic/gin" +) + +// GetRoutes returns all routes for tradingcards related data. +func GetRoutes(router *gin.Engine) { + tradingCardsRoutes := router.Group("/tradingcards") + tradingCardsRoutes.GET("/", auth.EnsureLoggedIn(), showIndexPage) +} diff --git a/go/pkg/tradingcards/views.go b/go/pkg/tradingcards/views.go new file mode 100644 index 0000000..1d4f104 --- /dev/null +++ b/go/pkg/tradingcards/views.go @@ -0,0 +1,11 @@ +package tradingcards + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/util" + + "github.com/gin-gonic/gin" +) + +func showIndexPage(c *gin.Context) { + util.Render(c, gin.H{"title": "Trading Cards", "payload": nil}, "tradingcards/index.html") +} diff --git a/go/pkg/tysc/card.go b/go/pkg/tysc/card.go new file mode 100644 index 0000000..e2555b5 --- /dev/null +++ b/go/pkg/tysc/card.go @@ -0,0 +1,20 @@ +package tysc + +import ( + "gopkg.in/mgo.v2/bson" +) + +// Card defines the data model for TYSC cards. +type Card struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Player bson.ObjectId `json:"player" bson:"player,omitempty"` + Team bson.ObjectId `json:"team" bson:"team,omitempty"` + Manufacturer bson.ObjectId `json:"manufacturer" bson:"manufacturer,omitempty"` + CardSet bson.ObjectId `json:"cardset" bson:"cardset,omitempty"` + ParallelSet bson.ObjectId `json:"parallelset" bson:"paralelset,omitempty"` + InsertSet bson.ObjectId `json:"insertset" bson:"insertset,omitempty"` + Rookie bool `json:"rookie" bson:"rookie,omitempty"` + Year int `json:"year" bson:"year,omitempty"` + Number int `json:"number" bson:"number,omitempty"` + Model string `json:"model" bson:"model,omitempty"` +} diff --git a/go/pkg/tysc/card_dao.go b/go/pkg/tysc/card_dao.go new file mode 100644 index 0000000..8fa4d4b --- /dev/null +++ b/go/pkg/tysc/card_dao.go @@ -0,0 +1,66 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// CardDAO extends the type BaseDAO. +type CardDAO struct { + Db dao.BaseDAO +} + +const ( + // CARDCOLLECTION defines the collection name for storing cards. + CARDCOLLECTION = "card" + // CARDMODEL defines the name of the card data model. + CARDMODEL = "kontor.tysc.card" +) + +// FindAll retrieves the list of cards from the database. +func (m *CardDAO) FindAll() ([]Card, error) { + m.Db.Connect() + var cards []Card + err := m.Db.MongoDb.C(CARDCOLLECTION).Find(bson.M{"model": CARDMODEL}).All(&cards) + return cards, err +} + +// FindByID returns an card with given id or returns the error. +func (m *CardDAO) FindByID(id string) (Card, error) { + m.Db.Connect() + var card Card + err := m.Db.MongoDb.C(CARDCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&card) + return card, err +} + +// Insert a card into database. +func (m *CardDAO) Insert(card Card) error { + m.Db.Connect() + card.Model = CARDMODEL + err := m.Db.MongoDb.C(CARDCOLLECTION).Insert(&card) + return err +} + +// Upsert a card into database. +func (m *CardDAO) Upsert(card Card) (*mgo.ChangeInfo, error) { + m.Db.Connect() + card.Model = CARDMODEL + info, err := m.Db.MongoDb.C(CARDCOLLECTION).Upsert(bson.M{"number": card.Number}, &card) + return info, err +} + +// Update an existing card. +func (m *CardDAO) Update(card Card) error { + m.Db.Connect() + err := m.Db.MongoDb.C(CARDCOLLECTION).UpdateId(card.ID, &card) + return err +} + +// Delete an existing card. +func (m *CardDAO) Delete(card Card) error { + m.Db.Connect() + err := m.Db.MongoDb.C(CARDCOLLECTION).Remove(&card) + return err +} diff --git a/go/pkg/tysc/card_test.go b/go/pkg/tysc/card_test.go new file mode 100644 index 0000000..e5c0afb --- /dev/null +++ b/go/pkg/tysc/card_test.go @@ -0,0 +1,110 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var cardModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Player", "string"}, + {"Team", "string"}, + {"Manufacturer", "string"}, + {"CardSet", "string"}, + {"ParallelSet", "string"}, + {"InsertSet", "string"}, + {"Rookie", "bool"}, + {"Year", "int"}, + {"Number", "int"}, + {"Model", "string"}, +} + +func TestCardModel(t *testing.T) { + m := Card{} + if reflect.TypeOf(m).NumField() != len(cardModelTestTable) { + t.Fail() + } + for index, testData := range cardModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListCards(t *testing.T) { + var ( + cardDao = CardDAO{Db: dao.TestDb} + ) + cards, err := cardDao.FindAll() + if err != nil { + t.Fail() + } + if len(cards) != 0 { + t.Fail() + } +} + +func TestInsertCard(t *testing.T) { + var ( + cardDao = CardDAO{Db: dao.TestDb} + card = Card{} + cards []Card + ) + card.ID = bson.NewObjectId() + card.Number = 1 + err := cardDao.Insert(card) + if err != nil { + t.Fail() + } + cards, err = cardDao.FindAll() + if err != nil { + t.Fail() + } + if len(cards) != 1 { + t.Fail() + } +} + +func TestUpsertCard(t *testing.T) { + var ( + cardDao = CardDAO{Db: dao.TestDb} + card = Card{} + ) + card.ID = bson.NewObjectId() + card.Number = 2 + cardDao.Upsert(card) + cards, err := cardDao.FindAll() + if err != nil { + t.Fail() + } + if len(cards) != 2 { + t.Fail() + } +} + +func TestDeleteCard(t *testing.T) { + var ( + cardDao = CardDAO{Db: dao.TestDb} + ) + cards, err := cardDao.FindAll() + if err != nil { + t.Fail() + } + for _, card := range cards { + cardDao.Delete(card) + } + cards, err = cardDao.FindAll() + if err != nil { + t.Fail() + } + if len(cards) != 0 { + t.Fail() + } +} diff --git a/go/pkg/tysc/cardset.go b/go/pkg/tysc/cardset.go new file mode 100644 index 0000000..6184a11 --- /dev/null +++ b/go/pkg/tysc/cardset.go @@ -0,0 +1,13 @@ +package tysc + +import ( + "gopkg.in/mgo.v2/bson" +) + +// CardSet defines the data model for TYSC card sets with id, name and manufacturer. +type CardSet struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Manufacturer bson.ObjectId `json:"manufacturer" bson:"manufacturer,omitempty"` + Model string `json:"model" bson:"model,omitempty"` +} diff --git a/go/pkg/tysc/cardset_dao.go b/go/pkg/tysc/cardset_dao.go new file mode 100644 index 0000000..4158860 --- /dev/null +++ b/go/pkg/tysc/cardset_dao.go @@ -0,0 +1,82 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// CardSetDAO extends the type BaseDAO. +type CardSetDAO struct { + Db dao.BaseDAO +} + +const ( + // CARDSETCOLLECTION defines the collection name for storing cards sets. + CARDSETCOLLECTION = "cardSet" + // CARDSETMODEL defines the name of the card set data model. + CARDSETMODEL = "kontor.tysc.cardSet" +) + +// FindAll retrieves the list of card sets from the database. +func (m *CardSetDAO) FindAll() ([]CardSet, error) { + m.Db.Connect() + var cardSets []CardSet + err := m.Db.MongoDb.C(CARDSETCOLLECTION).Find(bson.M{"model": CARDSETMODEL}).All(&cardSets) + return cardSets, err +} + +// FindByID returns a card set with given id or returns the error. +func (m *CardSetDAO) FindByID(id string) (CardSet, error) { + m.Db.Connect() + var cardSet CardSet + err := m.Db.MongoDb.C(CARDSETCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&cardSet) + return cardSet, err +} + +// FindByManufacturer returns a card set with given manufacturer or returns the error. +func (m *CardSetDAO) FindByManufacturer(manufacturer string) ([]CardSet, error) { + m.Db.Connect() + var cardSets []CardSet + err := m.Db.MongoDb.C(CARDSETCOLLECTION).Find(bson.M{"model": CARDSETMODEL, "manufacturer": bson.ObjectIdHex(manufacturer)}).All(&cardSets) + return cardSets, err +} + +// FindByName returns a card set with given name or returns the error. +func (m *CardSetDAO) FindByName(name string) (CardSet, error) { + m.Db.Connect() + var cardSet CardSet + err := m.Db.MongoDb.C(CARDSETCOLLECTION).Find(bson.M{"name": name, "model": CARDSETMODEL}).One(&cardSet) + return cardSet, err +} + +// Insert an card set into database. +func (m *CardSetDAO) Insert(cardSet CardSet) error { + m.Db.Connect() + cardSet.Model = CARDSETMODEL + err := m.Db.MongoDb.C(CARDSETCOLLECTION).Insert(&cardSet) + return err +} + +// Upsert an card set into database. +func (m *CardSetDAO) Upsert(cardSet CardSet) (*mgo.ChangeInfo, error) { + m.Db.Connect() + cardSet.Model = CARDSETMODEL + info, err := m.Db.MongoDb.C(CARDSETCOLLECTION).Upsert(bson.M{"name": cardSet.Name}, &cardSet) + return info, err +} + +// Update an existing card set. +func (m *CardSetDAO) Update(cardSet CardSet) error { + m.Db.Connect() + err := m.Db.MongoDb.C(CARDSETCOLLECTION).UpdateId(cardSet.ID, &cardSet) + return err +} + +// Delete an existing card set. +func (m *CardSetDAO) Delete(cardSet CardSet) error { + m.Db.Connect() + err := m.Db.MongoDb.C(CARDSETCOLLECTION).Remove(&cardSet) + return err +} diff --git a/go/pkg/tysc/cardset_test.go b/go/pkg/tysc/cardset_test.go new file mode 100644 index 0000000..82d7ebc --- /dev/null +++ b/go/pkg/tysc/cardset_test.go @@ -0,0 +1,103 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var cardsetModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Name", "string"}, + {"Manufacturer", "string"}, + {"Model", "string"}, +} + +func TestCardSetModel(t *testing.T) { + m := CardSet{} + if reflect.TypeOf(m).NumField() != len(cardsetModelTestTable) { + t.Fail() + } + for index, testData := range cardsetModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListCardSets(t *testing.T) { + var ( + cardsetDao = CardSetDAO{Db: dao.TestDb} + ) + cardSets, err := cardsetDao.FindAll() + if err != nil { + t.Fail() + } + if len(cardSets) != 0 { + t.Fail() + } +} + +func TestInsertCardSet(t *testing.T) { + var ( + cardsetDao = CardSetDAO{Db: dao.TestDb} + cardSet = CardSet{} + cardSets []CardSet + ) + cardSet.ID = bson.NewObjectId() + cardSet.Name = "test" + err := cardsetDao.Insert(cardSet) + if err != nil { + t.Fail() + } + cardSets, err = cardsetDao.FindAll() + if err != nil { + t.Fail() + } + if len(cardSets) != 1 { + t.Fail() + } +} + +func TestUpsertCardSet(t *testing.T) { + var ( + cardsetDao = CardSetDAO{Db: dao.TestDb} + cardSet = CardSet{} + ) + cardSet.ID = bson.NewObjectId() + cardSet.Name = "test2" + cardsetDao.Upsert(cardSet) + cardSets, err := cardsetDao.FindAll() + if err != nil { + t.Fail() + } + if len(cardSets) != 2 { + t.Fail() + } +} + +func TestDeleteCardSet(t *testing.T) { + var ( + cardsetDao = CardSetDAO{Db: dao.TestDb} + ) + cardSets, err := cardsetDao.FindAll() + if err != nil { + t.Fail() + } + for _, cardSet := range cardSets { + cardsetDao.Delete(cardSet) + } + cardSets, err = cardsetDao.FindAll() + if err != nil { + t.Fail() + } + if len(cardSets) != 0 { + t.Fail() + } +} diff --git a/go/pkg/tysc/insertset.go b/go/pkg/tysc/insertset.go new file mode 100644 index 0000000..8655d35 --- /dev/null +++ b/go/pkg/tysc/insertset.go @@ -0,0 +1,13 @@ +package tysc + +import ( + "gopkg.in/mgo.v2/bson" +) + +// InsertSet defines the data model for TYSC inserts sets with id, name and manufacturer. +type InsertSet struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Manufacturer bson.ObjectId `json:"manufacturer" bson:"manufacturer,omitempty"` + Model string `json:"model" bson:"model,omitempty"` +} diff --git a/go/pkg/tysc/insertset_dao.go b/go/pkg/tysc/insertset_dao.go new file mode 100644 index 0000000..dc87ddf --- /dev/null +++ b/go/pkg/tysc/insertset_dao.go @@ -0,0 +1,82 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// InsertSetDAO extends the type BaseDAO. +type InsertSetDAO struct { + Db dao.BaseDAO +} + +const ( + // INSERTSETCOLLECTION defines the collection name for storing insert sets. + INSERTSETCOLLECTION = "insertSet" + // INSERTSETMODEL defines the name of the insert set data model. + INSERTSETMODEL = "kontor.tysc.insertSet" +) + +// FindAll retrieves the list of cards from the database. +func (m *InsertSetDAO) FindAll() ([]InsertSet, error) { + m.Db.Connect() + var insertSets []InsertSet + err := m.Db.MongoDb.C(INSERTSETCOLLECTION).Find(bson.M{"model": INSERTSETMODEL}).All(&insertSets) + return insertSets, err +} + +// FindByID returns an card with given id or returns the error. +func (m *InsertSetDAO) FindByID(id string) (InsertSet, error) { + m.Db.Connect() + var insertSet InsertSet + err := m.Db.MongoDb.C(INSERTSETCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&insertSet) + return insertSet, err +} + +// FindByManufacturer returns a insert set with given manufacturer or returns the error. +func (m *InsertSetDAO) FindByManufacturer(manufacturer string) ([]InsertSet, error) { + m.Db.Connect() + var insertSets []InsertSet + err := m.Db.MongoDb.C(INSERTSETCOLLECTION).Find(bson.M{"model": INSERTSETMODEL, "manufacturer": bson.ObjectIdHex(manufacturer)}).All(&insertSets) + return insertSets, err +} + +// FindByName returns a insert set with given name or returns the error. +func (m *InsertSetDAO) FindByName(name string) (InsertSet, error) { + m.Db.Connect() + var insertSet InsertSet + err := m.Db.MongoDb.C(INSERTSETCOLLECTION).Find(bson.M{"name": name, "model": INSERTSETMODEL}).One(&insertSet) + return insertSet, err +} + +// Insert an insert set into database. +func (m *InsertSetDAO) Insert(insertSet InsertSet) error { + m.Db.Connect() + insertSet.Model = INSERTSETMODEL + err := m.Db.MongoDb.C(INSERTSETCOLLECTION).Insert(&insertSet) + return err +} + +// Upsert an insert set into database. +func (m *InsertSetDAO) Upsert(insertSet InsertSet) (*mgo.ChangeInfo, error) { + m.Db.Connect() + insertSet.Model = INSERTSETMODEL + info, err := m.Db.MongoDb.C(INSERTSETCOLLECTION).Upsert(bson.M{"name": insertSet.Name}, &insertSet) + return info, err +} + +// Update an existing insert set. +func (m *InsertSetDAO) Update(insertSet InsertSet) error { + m.Db.Connect() + err := m.Db.MongoDb.C(INSERTSETCOLLECTION).UpdateId(insertSet.ID, &insertSet) + return err +} + +// Delete an existing insert set. +func (m *InsertSetDAO) Delete(insertSet InsertSet) error { + m.Db.Connect() + err := m.Db.MongoDb.C(INSERTSETCOLLECTION).Remove(&insertSet) + return err +} diff --git a/go/pkg/tysc/insertset_test.go b/go/pkg/tysc/insertset_test.go new file mode 100644 index 0000000..b4d4152 --- /dev/null +++ b/go/pkg/tysc/insertset_test.go @@ -0,0 +1,103 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var insertsetModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Name", "string"}, + {"Manufacturer", "string"}, + {"Model", "string"}, +} + +func TestInsertSetModel(t *testing.T) { + m := InsertSet{} + if reflect.TypeOf(m).NumField() != len(insertsetModelTestTable) { + t.Fail() + } + for index, testData := range insertsetModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListInsertSets(t *testing.T) { + var ( + insertsetDao = InsertSetDAO{Db: dao.TestDb} + ) + insertSets, err := insertsetDao.FindAll() + if err != nil { + t.Fail() + } + if len(insertSets) != 0 { + t.Fail() + } +} + +func TestInsertInsertSet(t *testing.T) { + var ( + insertsetDao = InsertSetDAO{Db: dao.TestDb} + insertSet = InsertSet{} + insertSets []InsertSet + ) + insertSet.ID = bson.NewObjectId() + insertSet.Name = "test" + err := insertsetDao.Insert(insertSet) + if err != nil { + t.Fail() + } + insertSets, err = insertsetDao.FindAll() + if err != nil { + t.Fail() + } + if len(insertSets) != 1 { + t.Fail() + } +} + +func TestUpsertInsertSet(t *testing.T) { + var ( + insertsetDao = InsertSetDAO{Db: dao.TestDb} + insertSet = InsertSet{} + ) + insertSet.ID = bson.NewObjectId() + insertSet.Name = "test2" + insertsetDao.Upsert(insertSet) + insertSets, err := insertsetDao.FindAll() + if err != nil { + t.Fail() + } + if len(insertSets) != 2 { + t.Fail() + } +} + +func TestDeleteInsertSet(t *testing.T) { + var ( + insertsetDao = InsertSetDAO{Db: dao.TestDb} + ) + insertSets, err := insertsetDao.FindAll() + if err != nil { + t.Fail() + } + for _, insertSet := range insertSets { + insertsetDao.Delete(insertSet) + } + insertSets, err = insertsetDao.FindAll() + if err != nil { + t.Fail() + } + if len(insertSets) != 0 { + t.Fail() + } +} diff --git a/go/pkg/tysc/manufacturer.go b/go/pkg/tysc/manufacturer.go new file mode 100644 index 0000000..e4ccdc0 --- /dev/null +++ b/go/pkg/tysc/manufacturer.go @@ -0,0 +1,12 @@ +package tysc + +import ( + "gopkg.in/mgo.v2/bson" +) + +// Manufacturer defines the data model for TYSC manufacturers with id, and name. +type Manufacturer struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Model string `json:"model" bson:"model,omitempty"` +} diff --git a/go/pkg/tysc/manufacturer_dao.go b/go/pkg/tysc/manufacturer_dao.go new file mode 100644 index 0000000..b23be11 --- /dev/null +++ b/go/pkg/tysc/manufacturer_dao.go @@ -0,0 +1,74 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// ManufacturerDAO extends the type BaseDAO. +type ManufacturerDAO struct { + Db dao.BaseDAO +} + +const ( + // MANUFACTURERCOLLECTION defines the collection name for storing manufacturers. + MANUFACTURERCOLLECTION = "manufacturer" + // MANUFACTURERMODEL defines the name of the manufacturer data model. + MANUFACTURERMODEL = "kontor.tysc.manufacturer" +) + +// FindAll retrieves the list of manufacturers from the database. +func (m *ManufacturerDAO) FindAll() ([]Manufacturer, error) { + m.Db.Connect() + var manufacturers []Manufacturer + err := m.Db.MongoDb.C(MANUFACTURERCOLLECTION).Find(bson.M{"model": MANUFACTURERMODEL}).All(&manufacturers) + return manufacturers, err +} + +// FindByID returns a manufacturer with given id or returns the error. +func (m *ManufacturerDAO) FindByID(id string) (Manufacturer, error) { + m.Db.Connect() + var manufacturer Manufacturer + err := m.Db.MongoDb.C(MANUFACTURERCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&manufacturer) + return manufacturer, err +} + +// FindByName returns a manufacturer with given name or returns the error. +func (m *ManufacturerDAO) FindByName(name string) (Manufacturer, error) { + m.Db.Connect() + var manufacturer Manufacturer + err := m.Db.MongoDb.C(MANUFACTURERCOLLECTION).Find(bson.M{"name": name, "model": MANUFACTURERMODEL}).One(&manufacturer) + return manufacturer, err +} + +// Insert a manufacturer into database. +func (m *ManufacturerDAO) Insert(manufacturer Manufacturer) error { + m.Db.Connect() + manufacturer.Model = MANUFACTURERMODEL + err := m.Db.MongoDb.C(MANUFACTURERCOLLECTION).Insert(&manufacturer) + return err +} + +// Upsert a manufacturer into database. +func (m *ManufacturerDAO) Upsert(manufacturer Manufacturer) (*mgo.ChangeInfo, error) { + m.Db.Connect() + manufacturer.Model = MANUFACTURERMODEL + info, err := m.Db.MongoDb.C(MANUFACTURERCOLLECTION).Upsert(bson.M{"name": manufacturer.Name}, &manufacturer) + return info, err +} + +// Update an existing manufacturer. +func (m *ManufacturerDAO) Update(manufacturer Manufacturer) error { + m.Db.Connect() + err := m.Db.MongoDb.C(MANUFACTURERCOLLECTION).UpdateId(manufacturer.ID, &manufacturer) + return err +} + +// Delete an existing manufacturer. +func (m *ManufacturerDAO) Delete(manufacturer Manufacturer) error { + m.Db.Connect() + err := m.Db.MongoDb.C(MANUFACTURERCOLLECTION).Remove(&manufacturer) + return err +} diff --git a/go/pkg/tysc/manufacturer_test.go b/go/pkg/tysc/manufacturer_test.go new file mode 100644 index 0000000..9973beb --- /dev/null +++ b/go/pkg/tysc/manufacturer_test.go @@ -0,0 +1,102 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var manufacturerModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Name", "string"}, + {"Model", "string"}, +} + +func TestManufacturerModel(t *testing.T) { + m := Manufacturer{} + if reflect.TypeOf(m).NumField() != len(manufacturerModelTestTable) { + t.Fail() + } + for index, testData := range manufacturerModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListManufacturers(t *testing.T) { + var ( + manufacturerDao = ManufacturerDAO{Db: dao.TestDb} + ) + manufacturers, err := manufacturerDao.FindAll() + if err != nil { + t.Fail() + } + if len(manufacturers) != 0 { + t.Fail() + } +} + +func TestInsertManufacturer(t *testing.T) { + var ( + manufacturerDao = ManufacturerDAO{Db: dao.TestDb} + manufacturer = Manufacturer{} + manufacturers []Manufacturer + ) + manufacturer.ID = bson.NewObjectId() + manufacturer.Name = "test" + err := manufacturerDao.Insert(manufacturer) + if err != nil { + t.Fail() + } + manufacturers, err = manufacturerDao.FindAll() + if err != nil { + t.Fail() + } + if len(manufacturers) != 1 { + t.Fail() + } +} + +func TestUpsertManufacturer(t *testing.T) { + var ( + manufacturerDao = ManufacturerDAO{Db: dao.TestDb} + manufacturer = Manufacturer{} + ) + manufacturer.ID = bson.NewObjectId() + manufacturer.Name = "test2" + manufacturerDao.Upsert(manufacturer) + manufacturers, err := manufacturerDao.FindAll() + if err != nil { + t.Fail() + } + if len(manufacturers) != 2 { + t.Fail() + } +} + +func TestDeleteManufacturer(t *testing.T) { + var ( + manufacturerDao = ManufacturerDAO{Db: dao.TestDb} + ) + manufacturers, err := manufacturerDao.FindAll() + if err != nil { + t.Fail() + } + for _, manufacturer := range manufacturers { + manufacturerDao.Delete(manufacturer) + } + manufacturers, err = manufacturerDao.FindAll() + if err != nil { + t.Fail() + } + if len(manufacturers) != 0 { + t.Fail() + } +} diff --git a/go/pkg/tysc/parallelset.go b/go/pkg/tysc/parallelset.go new file mode 100644 index 0000000..78a93e2 --- /dev/null +++ b/go/pkg/tysc/parallelset.go @@ -0,0 +1,13 @@ +package tysc + +import ( + "gopkg.in/mgo.v2/bson" +) + +// ParallelSet defines the data model for TYSC parallel sets with id, name and manufacturer. +type ParallelSet struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Manufacturer bson.ObjectId `json:"manufacturer" bson:"manufacturer,omitempty"` + Model string `json:"model" bson:"model,omitempty"` +} diff --git a/go/pkg/tysc/parallelset_dao.go b/go/pkg/tysc/parallelset_dao.go new file mode 100644 index 0000000..0cadac3 --- /dev/null +++ b/go/pkg/tysc/parallelset_dao.go @@ -0,0 +1,82 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// ParallelSetDAO extends the type BaseDAO. +type ParallelSetDAO struct { + Db dao.BaseDAO +} + +const ( + // PARALLELSETCOLLECTION defines the collection name for storing parallel sets. + PARALLELSETCOLLECTION = "parallelSet" + // PARALLELSETMODEL defines the name of the parallel set data model. + PARALLELSETMODEL = "kontor.tysc.parallelSet" +) + +// FindAll retrieves the list of parallel sets from the database. +func (m *ParallelSetDAO) FindAll() ([]ParallelSet, error) { + m.Db.Connect() + var parallelSets []ParallelSet + err := m.Db.MongoDb.C(PARALLELSETCOLLECTION).Find(bson.M{"model": PARALLELSETMODEL}).All(¶llelSets) + return parallelSets, err +} + +// FindByID returns a parallel set with given id or returns the error. +func (m *ParallelSetDAO) FindByID(id string) (ParallelSet, error) { + m.Db.Connect() + var parallelSet ParallelSet + err := m.Db.MongoDb.C(PARALLELSETCOLLECTION).FindId(bson.ObjectIdHex(id)).One(¶llelSet) + return parallelSet, err +} + +// FindByManufacturer returns a paralle set with given manufacturer or returns the error. +func (m *ParallelSetDAO) FindByManufacturer(manufacturer string) ([]ParallelSet, error) { + m.Db.Connect() + var parallelSets []ParallelSet + err := m.Db.MongoDb.C(PARALLELSETCOLLECTION).Find(bson.M{"model": PARALLELSETMODEL, "manufacturer": bson.ObjectIdHex(manufacturer)}).All(¶llelSets) + return parallelSets, err +} + +// FindByName returns a parallel set with given name or returns the error. +func (m *ParallelSetDAO) FindByName(name string) (ParallelSet, error) { + m.Db.Connect() + var parallelSet ParallelSet + err := m.Db.MongoDb.C(PARALLELSETCOLLECTION).Find(bson.M{"name": name, "model": PARALLELSETMODEL}).One(¶llelSet) + return parallelSet, err +} + +// Insert a parallel set into database. +func (m *ParallelSetDAO) Insert(parallelSet ParallelSet) error { + m.Db.Connect() + parallelSet.Model = PARALLELSETMODEL + err := m.Db.MongoDb.C(PARALLELSETCOLLECTION).Insert(¶llelSet) + return err +} + +// Upsert a parallel set into database. +func (m *ParallelSetDAO) Upsert(parallelSet ParallelSet) (*mgo.ChangeInfo, error) { + m.Db.Connect() + parallelSet.Model = PARALLELSETMODEL + info, err := m.Db.MongoDb.C(PARALLELSETCOLLECTION).Upsert(bson.M{"name": parallelSet.Name}, ¶llelSet) + return info, err +} + +// Update an existing parallel set. +func (m *ParallelSetDAO) Update(parallelSet ParallelSet) error { + m.Db.Connect() + err := m.Db.MongoDb.C(PARALLELSETCOLLECTION).UpdateId(parallelSet.ID, ¶llelSet) + return err +} + +// Delete an existing parallel set. +func (m *ParallelSetDAO) Delete(parallelSet ParallelSet) error { + m.Db.Connect() + err := m.Db.MongoDb.C(PARALLELSETCOLLECTION).Remove(¶llelSet) + return err +} diff --git a/go/pkg/tysc/parallelset_test.go b/go/pkg/tysc/parallelset_test.go new file mode 100644 index 0000000..e0a9c2b --- /dev/null +++ b/go/pkg/tysc/parallelset_test.go @@ -0,0 +1,103 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var parallelsetModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Name", "string"}, + {"Manufacturer", "string"}, + {"Model", "string"}, +} + +func TestParallelSetModel(t *testing.T) { + m := ParallelSet{} + if reflect.TypeOf(m).NumField() != len(parallelsetModelTestTable) { + t.Fail() + } + for index, testData := range parallelsetModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListParallelSets(t *testing.T) { + var ( + parallelsetDao = ParallelSetDAO{Db: dao.TestDb} + ) + parallelSets, err := parallelsetDao.FindAll() + if err != nil { + t.Fail() + } + if len(parallelSets) != 0 { + t.Fail() + } +} + +func TestInsertParallelSet(t *testing.T) { + var ( + parallelsetDao = ParallelSetDAO{Db: dao.TestDb} + parallelSet = ParallelSet{} + parallelSets []ParallelSet + ) + parallelSet.ID = bson.NewObjectId() + parallelSet.Name = "test" + err := parallelsetDao.Insert(parallelSet) + if err != nil { + t.Fail() + } + parallelSets, err = parallelsetDao.FindAll() + if err != nil { + t.Fail() + } + if len(parallelSets) != 1 { + t.Fail() + } +} + +func TestUpsertParallelSet(t *testing.T) { + var ( + parallelsetDao = ParallelSetDAO{Db: dao.TestDb} + parallelSet = ParallelSet{} + ) + parallelSet.ID = bson.NewObjectId() + parallelSet.Name = "test2" + parallelsetDao.Upsert(parallelSet) + parallelSets, err := parallelsetDao.FindAll() + if err != nil { + t.Fail() + } + if len(parallelSets) != 2 { + t.Fail() + } +} + +func TestDeleteParallelSet(t *testing.T) { + var ( + parallelsetDao = ParallelSetDAO{Db: dao.TestDb} + ) + parallelSets, err := parallelsetDao.FindAll() + if err != nil { + t.Fail() + } + for _, parallelSet := range parallelSets { + parallelsetDao.Delete(parallelSet) + } + parallelSets, err = parallelsetDao.FindAll() + if err != nil { + t.Fail() + } + if len(parallelSets) != 0 { + t.Fail() + } +} diff --git a/go/pkg/tysc/player.go b/go/pkg/tysc/player.go new file mode 100644 index 0000000..055efe6 --- /dev/null +++ b/go/pkg/tysc/player.go @@ -0,0 +1,13 @@ +package tysc + +import ( + "gopkg.in/mgo.v2/bson" +) + +// Player defines the data model for TYSC players with id, first and lastname. +type Player struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Firstname string `json:"firstname" bson:"firstname"` + Lastname string `json:"lastname" bson:"lastname"` + Model string `json:"model" bson:"model,omitempty"` +} diff --git a/go/pkg/tysc/player_dao.go b/go/pkg/tysc/player_dao.go new file mode 100644 index 0000000..ff60c37 --- /dev/null +++ b/go/pkg/tysc/player_dao.go @@ -0,0 +1,74 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// PlayerDAO extends the type BaseDAO. +type PlayerDAO struct { + Db dao.BaseDAO +} + +const ( + // PLAYERCOLLECTION defines the collection name for storing players. + PLAYERCOLLECTION = "player" + // PLAYERMODEL defines the name of the player data model. + PLAYERMODEL = "kontor.tysc.player" +) + +// FindAll retrieves the list of players from the database. +func (m *PlayerDAO) FindAll() ([]Player, error) { + m.Db.Connect() + var players []Player + err := m.Db.MongoDb.C(PLAYERCOLLECTION).Find(bson.M{"model": PLAYERMODEL}).All(&players) + return players, err +} + +// FindByID returns a player with given id or returns the error. +func (m *PlayerDAO) FindByID(id string) (Player, error) { + m.Db.Connect() + var player Player + err := m.Db.MongoDb.C(PLAYERCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&player) + return player, err +} + +// FindByLastName returns a player with given last name or returns the error. +func (m *PlayerDAO) FindByLastName(lastname string) (Player, error) { + m.Db.Connect() + var player Player + err := m.Db.MongoDb.C(PLAYERCOLLECTION).Find(bson.M{"lastname": lastname, "model": PLAYERMODEL}).One(&player) + return player, err +} + +// Insert a player into database. +func (m *PlayerDAO) Insert(player Player) error { + m.Db.Connect() + player.Model = PLAYERMODEL + err := m.Db.MongoDb.C(PLAYERCOLLECTION).Insert(&player) + return err +} + +// Upsert a player into database. +func (m *PlayerDAO) Upsert(player Player) (*mgo.ChangeInfo, error) { + m.Db.Connect() + player.Model = PLAYERMODEL + info, err := m.Db.MongoDb.C(PLAYERCOLLECTION).Upsert(bson.M{"lastname": player.Lastname}, &player) + return info, err +} + +// Update an existing player. +func (m *PlayerDAO) Update(player Player) error { + m.Db.Connect() + err := m.Db.MongoDb.C(PLAYERCOLLECTION).UpdateId(player.ID, &player) + return err +} + +// Delete an existing player. +func (m *PlayerDAO) Delete(player Player) error { + m.Db.Connect() + err := m.Db.MongoDb.C(PLAYERCOLLECTION).Remove(&player) + return err +} diff --git a/go/pkg/tysc/player_test.go b/go/pkg/tysc/player_test.go new file mode 100644 index 0000000..e839ee9 --- /dev/null +++ b/go/pkg/tysc/player_test.go @@ -0,0 +1,103 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var playerModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Firstname", "string"}, + {"Lastname", "string"}, + {"Model", "string"}, +} + +func TestPlayerModel(t *testing.T) { + m := Player{} + if reflect.TypeOf(m).NumField() != len(playerModelTestTable) { + t.Fail() + } + for index, testData := range playerModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListPlayers(t *testing.T) { + var ( + playerDao = PlayerDAO{Db: dao.TestDb} + ) + players, err := playerDao.FindAll() + if err != nil { + t.Fail() + } + if players != nil { + t.Fail() + } +} + +func TestInsertPlayer(t *testing.T) { + var ( + playerDao = PlayerDAO{Db: dao.TestDb} + player = Player{} + players []Player + ) + player.ID = bson.NewObjectId() + player.Lastname = "test" + err := playerDao.Insert(player) + if err != nil { + t.Fail() + } + players, err = playerDao.FindAll() + if err != nil { + t.Fail() + } + if len(players) != 1 { + t.Fail() + } +} + +func TestUpsertPlayer(t *testing.T) { + var ( + playerDao = PlayerDAO{Db: dao.TestDb} + player = Player{} + ) + player.ID = bson.NewObjectId() + player.Lastname = "test2" + playerDao.Upsert(player) + players, err := playerDao.FindAll() + if err != nil { + t.Fail() + } + if len(players) != 2 { + t.Fail() + } +} + +func TestDeletePlayer(t *testing.T) { + var ( + playerDao = PlayerDAO{Db: dao.TestDb} + ) + players, err := playerDao.FindAll() + if err != nil { + t.Fail() + } + for _, player := range players { + playerDao.Delete(player) + } + players, err = playerDao.FindAll() + if err != nil { + t.Fail() + } + if len(players) != 0 { + t.Fail() + } +} diff --git a/go/pkg/tysc/position.go b/go/pkg/tysc/position.go new file mode 100644 index 0000000..b52703a --- /dev/null +++ b/go/pkg/tysc/position.go @@ -0,0 +1,14 @@ +package tysc + +import ( + "gopkg.in/mgo.v2/bson" +) + +// Position defines the data model for TYSC positions with id, name, description and sport. +type Position struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Description string `json:"description" bson:"description"` + Sport bson.ObjectId `json:"sport" bson:"sport,omitempty"` + Model string `json:"model" bson:"model,omitempty"` +} diff --git a/go/pkg/tysc/position_dao.go b/go/pkg/tysc/position_dao.go new file mode 100644 index 0000000..4166ee0 --- /dev/null +++ b/go/pkg/tysc/position_dao.go @@ -0,0 +1,74 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// PositionDAO extends the type BaseDAO. +type PositionDAO struct { + Db dao.BaseDAO +} + +const ( + // POSITIONCOLLECTION defines the collection name for storing positions. + POSITIONCOLLECTION = "position" + // POSITIONMODEL defines the name of the position data model. + POSITIONMODEL = "kontor.tysc.position" +) + +// FindAll retrieves the list of positions from the database. +func (m *PositionDAO) FindAll() ([]Position, error) { + m.Db.Connect() + var positions []Position + err := m.Db.MongoDb.C(POSITIONCOLLECTION).Find(bson.M{"model": POSITIONMODEL}).All(&positions) + return positions, err +} + +// FindByID returns a position with given id or returns the error. +func (m *PositionDAO) FindByID(id string) (Position, error) { + m.Db.Connect() + var position Position + err := m.Db.MongoDb.C(POSITIONCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&position) + return position, err +} + +// FindByName returns a position with given name or returns the error. +func (m *PositionDAO) FindByName(name string) (Position, error) { + m.Db.Connect() + var position Position + err := m.Db.MongoDb.C(POSITIONCOLLECTION).Find(bson.M{"name": name, "model": POSITIONMODEL}).One(&position) + return position, err +} + +// Insert a position into database. +func (m *PositionDAO) Insert(position Position) error { + m.Db.Connect() + position.Model = POSITIONMODEL + err := m.Db.MongoDb.C(POSITIONCOLLECTION).Insert(&position) + return err +} + +// Upsert a position into database. +func (m *PositionDAO) Upsert(position Position) (*mgo.ChangeInfo, error) { + m.Db.Connect() + position.Model = POSITIONMODEL + info, err := m.Db.MongoDb.C(POSITIONCOLLECTION).Upsert(bson.M{"name": position.Name}, &position) + return info, err +} + +// Update an existing position. +func (m *PositionDAO) Update(position Position) error { + m.Db.Connect() + err := m.Db.MongoDb.C(POSITIONCOLLECTION).UpdateId(position.ID, &position) + return err +} + +// Delete an existing position. +func (m *PositionDAO) Delete(position Position) error { + m.Db.Connect() + err := m.Db.MongoDb.C(POSITIONCOLLECTION).Remove(&position) + return err +} diff --git a/go/pkg/tysc/position_test.go b/go/pkg/tysc/position_test.go new file mode 100644 index 0000000..2b97105 --- /dev/null +++ b/go/pkg/tysc/position_test.go @@ -0,0 +1,104 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var positionModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Name", "string"}, + {"Description", "string"}, + {"Sport", "string"}, + {"Model", "string"}, +} + +func TestPositionModel(t *testing.T) { + m := Position{} + if reflect.TypeOf(m).NumField() != len(positionModelTestTable) { + t.Fail() + } + for index, testData := range positionModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListPositions(t *testing.T) { + var ( + positionDao = PositionDAO{Db: dao.TestDb} + ) + positions, err := positionDao.FindAll() + if err != nil { + t.Fail() + } + if len(positions) != 0 { + t.Fail() + } +} + +func TestInsertPosition(t *testing.T) { + var ( + positionDao = PositionDAO{Db: dao.TestDb} + position = Position{} + positions []Position + ) + position.ID = bson.NewObjectId() + position.Name = "test" + err := positionDao.Insert(position) + if err != nil { + t.Fail() + } + positions, err = positionDao.FindAll() + if err != nil { + t.Fail() + } + if len(positions) != 1 { + t.Fail() + } +} + +func TestUpsertPosition(t *testing.T) { + var ( + positionDao = PositionDAO{Db: dao.TestDb} + position = Position{} + ) + position.ID = bson.NewObjectId() + position.Name = "test2" + positionDao.Upsert(position) + positions, err := positionDao.FindAll() + if err != nil { + t.Fail() + } + if len(positions) != 2 { + t.Fail() + } +} + +func TestDeletePosition(t *testing.T) { + var ( + positionDao = PositionDAO{Db: dao.TestDb} + ) + positions, err := positionDao.FindAll() + if err != nil { + t.Fail() + } + for _, position := range positions { + positionDao.Delete(position) + } + positions, err = positionDao.FindAll() + if err != nil { + t.Fail() + } + if len(positions) != 0 { + t.Fail() + } +} diff --git a/go/pkg/tysc/routes.go b/go/pkg/tysc/routes.go new file mode 100644 index 0000000..e581db7 --- /dev/null +++ b/go/pkg/tysc/routes.go @@ -0,0 +1,22 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/auth" + + "github.com/gin-gonic/gin" +) + +// GetRoutes returns all routes for TYSC related data. +func GetRoutes(router *gin.Engine) { + tyscRoutes := router.Group("/tysc") + tyscRoutes.GET("/", auth.EnsureLoggedIn(), showIndexPage) + tyscRoutes.GET("/sport", auth.EnsureLoggedIn(), showSportList) + tyscRoutes.GET("/position", auth.EnsureLoggedIn(), showIndexPage) + tyscRoutes.GET("/team", auth.EnsureLoggedIn(), showIndexPage) + tyscRoutes.GET("/player", auth.EnsureLoggedIn(), showIndexPage) + tyscRoutes.GET("/manufacturer", auth.EnsureLoggedIn(), showIndexPage) + tyscRoutes.GET("/cardset", auth.EnsureLoggedIn(), showIndexPage) + tyscRoutes.GET("/parallelset", auth.EnsureLoggedIn(), showIndexPage) + tyscRoutes.GET("/insertset", auth.EnsureLoggedIn(), showIndexPage) + tyscRoutes.GET("/card", auth.EnsureLoggedIn(), showIndexPage) +} diff --git a/go/pkg/tysc/sport.go b/go/pkg/tysc/sport.go new file mode 100644 index 0000000..ed5b8ef --- /dev/null +++ b/go/pkg/tysc/sport.go @@ -0,0 +1,12 @@ +package tysc + +import ( + "gopkg.in/mgo.v2/bson" +) + +// Sport defines the data model for TYSC sports with id, and name. +type Sport struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Model string `json:"model" bson:"model,omitempty"` +} diff --git a/go/pkg/tysc/sport_dao.go b/go/pkg/tysc/sport_dao.go new file mode 100644 index 0000000..e931f98 --- /dev/null +++ b/go/pkg/tysc/sport_dao.go @@ -0,0 +1,74 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// SportDAO extends the type BaseDAO. +type SportDAO struct { + Db dao.BaseDAO +} + +const ( + // SPORTCOLLECTION defines the collection name for storing sports. + SPORTCOLLECTION = "sport" + // SPORTMODEL defines the name of the sport data model. + SPORTMODEL = "kontor.tysc.sport" +) + +// FindAll retrieves the list of sports from the database. +func (m *SportDAO) FindAll() ([]Sport, error) { + m.Db.Connect() + var sports []Sport + err := m.Db.MongoDb.C(SPORTCOLLECTION).Find(bson.M{"model": SPORTMODEL}).All(&sports) + return sports, err +} + +// FindByID returns a sport with given id or returns the error. +func (m *SportDAO) FindByID(id string) (Sport, error) { + m.Db.Connect() + var sport Sport + err := m.Db.MongoDb.C(SPORTCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&sport) + return sport, err +} + +// FindByName returns a sport with given name or returns the error. +func (m *SportDAO) FindByName(name string) (Sport, error) { + m.Db.Connect() + var sport Sport + err := m.Db.MongoDb.C(SPORTCOLLECTION).Find(bson.M{"name": name, "model": SPORTMODEL}).One(&sport) + return sport, err +} + +// Insert a sport into database. +func (m *SportDAO) Insert(sport Sport) error { + m.Db.Connect() + sport.Model = SPORTMODEL + err := m.Db.MongoDb.C(SPORTCOLLECTION).Insert(&sport) + return err +} + +// Upsert a sport into database. +func (m *SportDAO) Upsert(sport Sport) (*mgo.ChangeInfo, error) { + m.Db.Connect() + sport.Model = SPORTMODEL + info, err := m.Db.MongoDb.C(SPORTCOLLECTION).Upsert(bson.M{"name": sport.Name}, &sport) + return info, err +} + +// Update an existing sport. +func (m *SportDAO) Update(sport Sport) error { + m.Db.Connect() + err := m.Db.MongoDb.C(SPORTCOLLECTION).UpdateId(sport.ID, &sport) + return err +} + +// Delete an existing sport. +func (m *SportDAO) Delete(sport Sport) error { + m.Db.Connect() + err := m.Db.MongoDb.C(SPORTCOLLECTION).Remove(&sport) + return err +} diff --git a/go/pkg/tysc/sport_test.go b/go/pkg/tysc/sport_test.go new file mode 100644 index 0000000..0aaf1c1 --- /dev/null +++ b/go/pkg/tysc/sport_test.go @@ -0,0 +1,102 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var sportModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Name", "string"}, + {"Model", "string"}, +} + +func TestSport(t *testing.T) { + m := Sport{} + if reflect.TypeOf(m).NumField() != len(sportModelTestTable) { + t.Fail() + } + for index, testData := range sportModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListSports(t *testing.T) { + var ( + sportDao = SportDAO{Db: dao.TestDb} + ) + sports, err := sportDao.FindAll() + if err != nil { + t.Fail() + } + if len(sports) != 0 { + t.Fail() + } +} + +func TestInsertSport(t *testing.T) { + var ( + sportDao = SportDAO{Db: dao.TestDb} + sport = Sport{} + sports []Sport + ) + sport.ID = bson.NewObjectId() + sport.Name = "test" + err := sportDao.Insert(sport) + if err != nil { + t.Fail() + } + sports, err = sportDao.FindAll() + if err != nil { + t.Fail() + } + if len(sports) != 1 { + t.Fail() + } +} + +func TestUpsertSport(t *testing.T) { + var ( + sportDao = SportDAO{Db: dao.TestDb} + sport = Sport{} + ) + sport.ID = bson.NewObjectId() + sport.Name = "test2" + sportDao.Upsert(sport) + sports, err := sportDao.FindAll() + if err != nil { + t.Fail() + } + if len(sports) != 2 { + t.Fail() + } +} + +func TestDeleteSport(t *testing.T) { + var ( + sportDao = SportDAO{Db: dao.TestDb} + ) + sports, err := sportDao.FindAll() + if err != nil { + t.Fail() + } + for _, sport := range sports { + sportDao.Delete(sport) + } + sports, err = sportDao.FindAll() + if err != nil { + t.Fail() + } + if len(sports) != 0 { + t.Fail() + } +} diff --git a/go/pkg/tysc/team.go b/go/pkg/tysc/team.go new file mode 100644 index 0000000..167a2e5 --- /dev/null +++ b/go/pkg/tysc/team.go @@ -0,0 +1,14 @@ +package tysc + +import ( + "gopkg.in/mgo.v2/bson" +) + +// Team defines the data model for TYSC teams with id, name and sport. +type Team struct { + ID bson.ObjectId `json:"_id" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Shortname string `json:"shortname" bson:"shortname"` + Sport bson.ObjectId `json:"sport" bson:"sport,omitempty"` + Model string `json:"model" bson:"model,omitempty"` +} diff --git a/go/pkg/tysc/team_dao.go b/go/pkg/tysc/team_dao.go new file mode 100644 index 0000000..911763e --- /dev/null +++ b/go/pkg/tysc/team_dao.go @@ -0,0 +1,74 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +// TeamDAO extends the type BaseDAO. +type TeamDAO struct { + Db dao.BaseDAO +} + +const ( + // TEAMCOLLECTION defines the collection name for storing teams. + TEAMCOLLECTION = "team" + // TEAMMODEL defines the name of the team data model. + TEAMMODEL = "kontor.tysc.team" +) + +// FindAll retrieves the list of cards from the database. +func (m *TeamDAO) FindAll() ([]Team, error) { + m.Db.Connect() + var teams []Team + err := m.Db.MongoDb.C(TEAMCOLLECTION).Find(bson.M{"model": TEAMMODEL}).All(&teams) + return teams, err +} + +// FindByID returns a team with given id or returns the error. +func (m *TeamDAO) FindByID(id string) (Team, error) { + m.Db.Connect() + var team Team + err := m.Db.MongoDb.C(TEAMCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&team) + return team, err +} + +// FindByName returns a team with given name or returns the error. +func (m *TeamDAO) FindByName(name string) (Team, error) { + m.Db.Connect() + var team Team + err := m.Db.MongoDb.C(TEAMCOLLECTION).Find(bson.M{"name": name, "model": TEAMMODEL}).One(&team) + return team, err +} + +// Insert a team into database +func (m *TeamDAO) Insert(team Team) error { + m.Db.Connect() + team.Model = TEAMMODEL + err := m.Db.MongoDb.C(TEAMCOLLECTION).Insert(&team) + return err +} + +// Upsert a team into database +func (m *TeamDAO) Upsert(team Team) (*mgo.ChangeInfo, error) { + m.Db.Connect() + team.Model = TEAMMODEL + info, err := m.Db.MongoDb.C(TEAMCOLLECTION).Upsert(bson.M{"name": team.Name}, &team) + return info, err +} + +// Update an existing team. +func (m *TeamDAO) Update(team Team) error { + m.Db.Connect() + err := m.Db.MongoDb.C(TEAMCOLLECTION).UpdateId(team.ID, &team) + return err +} + +// Delete an existing team. +func (m *TeamDAO) Delete(team Team) error { + m.Db.Connect() + err := m.Db.MongoDb.C(TEAMCOLLECTION).Remove(&team) + return err +} diff --git a/go/pkg/tysc/team_test.go b/go/pkg/tysc/team_test.go new file mode 100644 index 0000000..79dd539 --- /dev/null +++ b/go/pkg/tysc/team_test.go @@ -0,0 +1,103 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +var teamModelTestTable = []struct { + name string + typeName string +}{ + {"Id", "string"}, + {"Name", "string"}, + {"Shortname", "string"}, + {"Sport", "string"}, + {"Model", "string"}, +} + +func TestTeamModel(t *testing.T) { + m := Team{} + if reflect.TypeOf(m).NumField() != len(teamModelTestTable) { + t.Fail() + } + for index, testData := range teamModelTestTable { + givenType := reflect.TypeOf(m).Field(index).Type.Kind().String() + if givenType != testData.typeName { + t.Fail() + } + } +} + +func TestListTeams(t *testing.T) { + var ( + teamDao = TeamDAO{Db: dao.TestDb} + ) + teams, err := teamDao.FindAll() + if err != nil { + t.Fail() + } + if teams != nil { + t.Fail() + } +} + +func TestInsertTeam(t *testing.T) { + var ( + teamDao = TeamDAO{Db: dao.TestDb} + team = Team{} + teams []Team + ) + team.ID = bson.NewObjectId() + err := teamDao.Insert(team) + if err != nil { + t.Fail() + } + teams, err = teamDao.FindAll() + if err != nil { + t.Fail() + } + if len(teams) != 1 { + t.Fail() + } +} + +func TestUpsertTeam(t *testing.T) { + var ( + teamDao = TeamDAO{Db: dao.TestDb} + team = Team{} + ) + team.ID = bson.NewObjectId() + team.Name = "test2" + teamDao.Upsert(team) + teams, err := teamDao.FindAll() + if err != nil { + t.Fail() + } + if len(teams) != 2 { + t.Fail() + } +} + +func TestDeleteTeam(t *testing.T) { + var ( + teamDao = TeamDAO{Db: dao.TestDb} + ) + teams, err := teamDao.FindAll() + if err != nil { + t.Fail() + } + for _, team := range teams { + teamDao.Delete(team) + } + teams, err = teamDao.FindAll() + if err != nil { + t.Fail() + } + if len(teams) != 0 { + t.Fail() + } +} diff --git a/go/pkg/tysc/views.go b/go/pkg/tysc/views.go new file mode 100644 index 0000000..0d9f233 --- /dev/null +++ b/go/pkg/tysc/views.go @@ -0,0 +1,22 @@ +package tysc + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/dao" + "gitlab.thpeetz.de/kontor/kontor-go/pkg/util" + "net/http" + + "github.com/gin-gonic/gin" +) + +func showIndexPage(c *gin.Context) { + util.Render(c, gin.H{"title": "TradeYourSportsCards", "payload": nil}, "tysc/index.html") +} + +func showSportList(c *gin.Context) { + var dao = SportDAO{Db: dao.KontorDb} + if sports, err := dao.FindAll(); err == nil { + util.Render(c, gin.H{"title": "TYSC Sport List", "payload": sports}, "tysc/sports.html") + } else { + c.AbortWithError(http.StatusNotFound, err) + } +} diff --git a/go/pkg/util/render.go b/go/pkg/util/render.go new file mode 100644 index 0000000..b786b4c --- /dev/null +++ b/go/pkg/util/render.go @@ -0,0 +1,39 @@ +package util + +import ( + "gitlab.thpeetz.de/kontor/kontor-go/pkg/auth" + "net/http" + + "github.com/gin-gonic/gin" +) + +const ( + // SaveAction defines label of button. + SaveAction = "Save" + // AddAction defines label of button. + AddAction = "Add" + // DeleteAction defines label of button. + DeleteAction = "Delete" +) + +// Render one of HTML, JSON or CSV based on the 'Accept' header of the request +// If the header doesn't specify this, HTML is rendered, provided that +// the template name is present +func Render(c *gin.Context, data gin.H, templateName string) { + auth.SetSessionData(c, data) + switch c.Request.Header.Get("Accept") { + case "application/json": + c.JSON(http.StatusOK, data["payload"]) + case "application/xml": + c.XML(http.StatusOK, data["payload"]) + default: + c.HTML(http.StatusOK, templateName, data) + } +} + +// ShowIndexPage render the index page of Kontor web application. +func ShowIndexPage(c *gin.Context) { + // Call the render function with the name of the template to render + //log.Printf("Context: %v", c) + Render(c, gin.H{"title": "Kontor", "payload": nil}, "kontor/index.html") +} diff --git a/go/sonar-project.properties b/go/sonar-project.properties new file mode 100644 index 0000000..b06f378 --- /dev/null +++ b/go/sonar-project.properties @@ -0,0 +1,2 @@ +sonar.projectKey=kontor_kontor-go_AX-cQT62rXuu6JVRvr-z +sonar.qualitygate.wait=true diff --git a/go/templates/comics/artists.html b/go/templates/comics/artists.html new file mode 100644 index 0000000..f49ebbc --- /dev/null +++ b/go/templates/comics/artists.html @@ -0,0 +1,24 @@ + +{{ template "header.html" .}} +{{ template "comics/menu.html" .}} + + Comics + Publishers + Artists + + + + + Comic Artists + Name + {{range .payload }} + {{.Name}} + {{end}} + + + Add entry + + + + +{{ template "footer.html" .}} diff --git a/go/templates/comics/comic.html b/go/templates/comics/comic.html new file mode 100644 index 0000000..8184605 --- /dev/null +++ b/go/templates/comics/comic.html @@ -0,0 +1,34 @@ +{{ template "header.html" .}} +{{ template "comics/menu.html" .}} + + Comics + Publishers + Artists + + + {{ if .ErrorTitle}} + + {{.ErrorTitle}}: {{.ErrorMessage}} + + {{end}} + + + Title + {{ if .payload.Title }} + + {{ else }} + + {{ end }} + + + + Completed + + + {{.action}} + {{ if eq .action "Save" }} + Delete + {{ end }} + + +{{ template "footer.html" .}} diff --git a/go/templates/comics/comics.html b/go/templates/comics/comics.html new file mode 100644 index 0000000..aeca37f --- /dev/null +++ b/go/templates/comics/comics.html @@ -0,0 +1,24 @@ + +{{ template "header.html" .}} +{{ template "comics/menu.html" .}} + + Comics + Publishers + Artists + + + + + List of Comics + Name + {{range .payload }} + {{.Title}} + {{end}} + + + Add entry + + + + +{{ template "footer.html" .}} diff --git a/go/templates/comics/menu.html b/go/templates/comics/menu.html new file mode 100644 index 0000000..6fec416 --- /dev/null +++ b/go/templates/comics/menu.html @@ -0,0 +1,38 @@ +{{ define "comics/menu.html" }} + + + + + + Toggle navigation + + + + + Kontor + + + + + + {{ if .is_logged_in }} + Comics (current) + Library + HomeOffice + Trading Cards + TradeYourSportsCards + {{end}} + {{ if not .is_logged_in }} + + {{end}} + {{ if .is_admin }} + Admin + {{ end }} + {{ if .is_logged_in }} + + {{end}} + + + + +{{ end }} diff --git a/go/templates/comics/publisher.html b/go/templates/comics/publisher.html new file mode 100644 index 0000000..fe38dcd --- /dev/null +++ b/go/templates/comics/publisher.html @@ -0,0 +1,32 @@ +{{ define "comics/publisher.html" }} +{{ template "header.html" .}} +{{ template "comics/menu.html" .}} + + Comics + Publishers + Artists + + + {{ if .ErrorTitle}} + + {{.ErrorTitle}}: {{.ErrorMessage}} + + {{end}} + + + Name + {{ if .payload.Name }} + + {{ else }} + + {{ end }} + + + {{.action}} + {{ if eq .action "Save" }} + Delete + {{ end }} + + +{{ template "footer.html" .}} +{{ end }} diff --git a/go/templates/comics/publishers.html b/go/templates/comics/publishers.html new file mode 100644 index 0000000..d691cdc --- /dev/null +++ b/go/templates/comics/publishers.html @@ -0,0 +1,25 @@ + +{{ define "comics/publishers.html" }} +{{ template "header.html" .}} +{{ template "comics/menu.html" .}} + + Comics + Publishers + Artists + + + + + Comic Publishers + Name + {{range .payload }} + {{.Name}} + {{end}} + + + Add entry + + + +{{ template "footer.html" .}} +{{ end }} diff --git a/go/templates/kontor/admin-menu.html b/go/templates/kontor/admin-menu.html new file mode 100644 index 0000000..d5738a2 --- /dev/null +++ b/go/templates/kontor/admin-menu.html @@ -0,0 +1,34 @@ + + + + + + Toggle navigation + + + + + Kontor + + + + + + {{ if .is_logged_in }} + Comics + Library + HomeOffice + Trading Cards + TradeYourSportsCards + {{end}} + {{ if not .is_logged_in }} + + {{end}} + {{ if .is_logged_in }} + Admin(current) + + {{end}} + + + + diff --git a/go/templates/kontor/admin.html b/go/templates/kontor/admin.html new file mode 100644 index 0000000..9dd19ad --- /dev/null +++ b/go/templates/kontor/admin.html @@ -0,0 +1,11 @@ + +{{ define "kontor/admin.html" }} +{{ template "header.html" .}} +{{ template "admin-menu.html" .}} + + User + Data + + +{{ template "footer.html" .}} +{{ end }} diff --git a/go/templates/kontor/create-user.html b/go/templates/kontor/create-user.html new file mode 100644 index 0000000..1a67785 --- /dev/null +++ b/go/templates/kontor/create-user.html @@ -0,0 +1,40 @@ +{{ define "kontor/create-user.html" }} +{{ template "header.html" .}} +{{ template "admin-menu.html" .}} + + User + Data + + + + + + {{ if .ErrorTitle}} + + {{.ErrorTitle}}: {{.ErrorMessage}} + + {{end}} + + + + Username + + + + First Name + + + + Password + + + + + Administrator + + Add + + + +{{ template "footer.html" .}} +{{end}} diff --git a/go/templates/kontor/data-upload.html b/go/templates/kontor/data-upload.html new file mode 100644 index 0000000..b2f77f2 --- /dev/null +++ b/go/templates/kontor/data-upload.html @@ -0,0 +1,29 @@ +{{ define "kontor/data-upload.html" }} +{{ template "header.html" .}} +{{ template "admin-menu.html" .}} + + User + Data + + + + + + {{ if .ErrorTitle}} + + {{.ErrorTitle}}: {{.ErrorMessage}} + + {{end}} + + + + Data File + + + Upload + + + + +{{ template "footer.html" .}} +{{end}} diff --git a/go/templates/kontor/footer.html b/go/templates/kontor/footer.html new file mode 100644 index 0000000..3760315 --- /dev/null +++ b/go/templates/kontor/footer.html @@ -0,0 +1,26 @@ + + + + + + + + + + Kontor + â‹… + {{ if not .is_logged_in }} + Login + {{end}} + {{ if .is_logged_in }} + Logout + {{end}} + + Version {{ .version }} Copyright © 2018. All Rights Reserved + + + + +
+ {{.ErrorTitle}}: {{.ErrorMessage}} +
+
Version {{ .version }} Copyright © 2018. All Rights Reserved