genofire/hs_monolith
genofire
/
hs_monolith
Archived
1
0
Fork 0

[Task]: add comments + comment quality saving

This commit is contained in:
mlabusch 2017-06-21 15:25:18 +02:00
parent d38839c4f9
commit 99d62fcb47
47 changed files with 2435 additions and 2476 deletions

View File

@ -1,5 +1,5 @@
# Stock-Microservice # Stock-Microservice
This is a microservice cutted out of a [Monolith](https://gitlab.com/matthiasstock/monolith). This microservice is cut out of a [Monolith](https://gitlab.com/matthiasstock/monolith).
[![Build Status](https://travis-ci.org/genofire/hs_master-kss-monolith.svg?branch=master)](https://travis-ci.org/genofire/hs_master-kss-monolith) [![CircleCI](https://circleci.com/gh/genofire/hs_master-kss-monolith/tree/master.svg?style=svg)](https://circleci.com/gh/genofire/hs_master-kss-monolith/tree/master) [![Coverage Status](https://coveralls.io/repos/github/genofire/hs_master-kss-monolith/badge.svg?branch=master)](https://coveralls.io/github/genofire/hs_master-kss-monolith?branch=master) [![GoDoc](https://godoc.org/github.com/genofire/hs_master-kss-monolith?status.svg)](https://godoc.org/github.com/genofire/hs_master-kss-monolith) [![Build Status](https://travis-ci.org/genofire/hs_master-kss-monolith.svg?branch=master)](https://travis-ci.org/genofire/hs_master-kss-monolith) [![CircleCI](https://circleci.com/gh/genofire/hs_master-kss-monolith/tree/master.svg?style=svg)](https://circleci.com/gh/genofire/hs_master-kss-monolith/tree/master) [![Coverage Status](https://coveralls.io/repos/github/genofire/hs_master-kss-monolith/badge.svg?branch=master)](https://coveralls.io/github/genofire/hs_master-kss-monolith?branch=master) [![GoDoc](https://godoc.org/github.com/genofire/hs_master-kss-monolith?status.svg)](https://godoc.org/github.com/genofire/hs_master-kss-monolith)
@ -9,14 +9,14 @@ This is a microservice cutted out of a [Monolith](https://gitlab.com/matthiassto
* [Easy dummy Shop-Cart in browser-cache](https://stock.pub.warehost.de/dummy_cart/) * [Easy dummy Shop-Cart in browser-cache](https://stock.pub.warehost.de/dummy_cart/)
## Features of this stock mircoservice ## Features of this stock mircoservice
* The main functionality of the microservice is to store the goods with their storage location and the date, when they are too old to be sell. * The main functionality of the microservice is to store goods with their storage location and the date, when they are too old to sell.
* Functionality of the admin frontend * Functionality of the admin frontend
* Add new goods to the stock * Add new goods to the stock
* Manually remove a single goods from the stock, for example when they are rancid * Manually remove a single good from the stock, for example when it is fouled
* Remove single goods from the stock, when they are send to a costumer * Remove a single good from the stock, when it is send to a costumer
* Block goods from the stock, when a costumer adds them to his cart * Block goods from the stock, when a costumer adds them to his shop-cart
* Functionality of the costumer frontend * Functionality of the costumer frontend
* Show the store with a traffic light food labelling * Show the stock of a product with a traffic light food labelling system
* Optional Features * Optional Features
* Admin frontend: display of a statistic on the amount and average of goods in the stock * Admin frontend: display of a statistic of the current and average amount of goods in the stock
* Admin frontend: display a traffic light food labelling for each good, which indicates whether the good is too old * Admin frontend: display a traffic light food labelling system for each good, which indicates whether the good is too old

View File

@ -1,4 +1,4 @@
// Package that containts the cmd binary of the microservice to run it // Package that contains the cmd binary of the microservice to run it
package main package main
import ( import (
@ -50,13 +50,15 @@ func main() {
} }
grw := runtime.NewGoodReleaseWorker(config.GoodRelease) grw := runtime.NewGoodReleaseWorker(config.GoodRelease)
cw := runtime.NewCacheWorker() cw := runtime.NewCacheWorker()
fw := worker.NewWorker(config.FouledDeleter.Duration, func() { runtime.GoodFouled() }) fw := worker.NewWorker(config.FouledDeleter.Duration, func() {
runtime.GoodFouled()
})
go grw.Start() go grw.Start()
go cw.Start() go cw.Start()
if config.FouledDeleter.Duration != time.Duration(0) { if config.FouledDeleter.Duration != time.Duration(0) {
go fw.Start() go fw.Start()
} }
// Startwebsrver // Start webserver
router := goji.NewMux() router := goji.NewMux()
web.BindAPI(router) web.BindAPI(router)

View File

@ -1,24 +0,0 @@
# bind the webserver to a dynamic ports
webserver_bind = ":65000"
webroot = "webroot"
good_availablity_template = "contrib/good_availablity.svg"
[database]
type = "sqlite3"
# logging = true
connection = "file::memory:?mode=memory&cache=shared"
# For Master-Slave cluster
# read_connection = ""
[good_release]
every = "5m"
after = "30m"
[cache_clean]
every = "5m"
after = "30m"
[microservice_dependencies]
product = "http://localhost:65000/api-test/product/%d/"
permission = "http://localhost:65000/api-test/session/%s/%d/"

View File

@ -1,8 +1,7 @@
<!-- SVG to show the current stock with a traffic light food labeling system --> <!-- SVG to show the current stock with a traffic light food labeling system -->
<!-- Used functions --> <!-- Used functions -->
<!-- process_radius ANZAHL MAXANZAHL RADIUS --> <!-- process_radius ANZAHL MAXANZAHL RADIUS -->
<!-- procent ANZAHL MAXANZAHL --> <!-- procent ANZAHL MAXANZAHL ex. {procent .Count 10}%-->
<!-- ex. {procent .Count 10}%-->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g class="arcs"> <g class="arcs">
{{if eq .Count 0}} {{if eq .Count 0}}

Before

Width:  |  Height:  |  Size: 854 B

After

Width:  |  Height:  |  Size: 840 B

View File

@ -58,8 +58,9 @@ Die Packages und Go-Files des Application Layers umfassen die Logik des Microser
\paragraph{http:} Go-Files, die die Anwendungslogik (Funktionen) und die API-Routen beinhalten \paragraph{http:} Go-Files, die die Anwendungslogik (Funktionen) und die API-Routen beinhalten
\begin{itemize} \begin{itemize}
\item \texttt{bindapi.go}: Funktionen, die für das Binden der URL-Pfade notwendig sind \item \texttt{bindapi.go}: Funktionen, die für das Binden der URL-Pfade notwendig sind
\item \texttt{good.go}: Funktionen für die Verwaltung der Waren im Warenbestand \item \texttt{good.go}: Funktionen für das Hinzufügen von Waren zum Warenbestand und die Anzeige, ob eine Ware abgelaufen ist
\item \texttt{good\_show.go}: Funktionen für die Auflistung und Zählung der vorhandenen Waren sowie die Feststellung ihrer Verfügbarkeit \item \texttt{good\_lock.go}: Funktionen für das Blockieren von Waren, die sich im Warenkorb befinden
\item \texttt{good\_show.go}: Funktionen für die Auflistung und Zählung der vorhandenen Waren sowie die Feststellung ihrer Verfügbarkeit
\item \texttt{good\_temp.go}: Hilfsfunktionen, die für die Darstellung des Warenbestandes als Ampel im Kunden-Frontend benötigt werden \item \texttt{good\_temp.go}: Hilfsfunktionen, die für die Darstellung des Warenbestandes als Ampel im Kunden-Frontend benötigt werden
\item \texttt{status.go}: Funktion, die den Status des Microservice abfragt \item \texttt{status.go}: Funktion, die den Status des Microservice abfragt
\end{itemize} \end{itemize}
@ -77,9 +78,9 @@ Die Packages und Go-Files des Application Layers umfassen die Logik des Microser
\begin{itemize} \begin{itemize}
\item \texttt{auth.go}: Hilfsfunktionen zur Prüfung, ob eine Berechtigung für den Zugriff vorliegt \item \texttt{auth.go}: Hilfsfunktionen zur Prüfung, ob eine Berechtigung für den Zugriff vorliegt
\item \texttt{cache\_worker.go}: Hilfsfunktionen für das Löschen und Anlegen von Cache-Workers \item \texttt{cache\_worker.go}: Hilfsfunktionen für das Löschen und Anlegen von Cache-Workers
\item \texttt{good\_fouled.go}: Hilfsfunktion, um abgelaufene Waren automatisch aus dem Warenbestand zu entfernen
\item \texttt{good\_release.go}: Hilfsfunktionen zum Blockieren und Entsperren von Waren \item \texttt{good\_release.go}: Hilfsfunktionen zum Blockieren und Entsperren von Waren
\item \texttt{productcache.go}: Hilfsfunktionen zum Anlegen eines Caches für Produkte \item \texttt{productcache.go}: Hilfsfunktionen zum Anlegen eines Caches für Produkte
\item \texttt{runtime.go}: Übergreifende Hintergrundfunktionalitäten
\end{itemize} \end{itemize}
@ -122,7 +123,7 @@ connection = "file::memory:?mode=memory&cache=shared"
\newpage \newpage
\subsection{Integrierte Tests} \subsection{Integrierte Tests}
\label{subsec: Integrierte Test} \label{subsec: Integrierte Test}
Neben den bisherigen Packages, die bereits Whitebox-Tests umfassen, ist in dem Package \textbf{\texttt{test}} ein weiteres Go-File (\texttt{testRest.go}) enthalten. Dieses setzt einen Test des Webservers um, bei dem auf Testdaten eines Produktkataloges zurückgegriffen wird. Mit Hilfe der integrierten Tests wird in der hier beschriebenen Version eine Code-Coverage von 100\% erreicht, das heißt jedes Stück Code wird mindestens einmal zur Ausführung gebracht. Neben den bisherigen Packages, die bereits Whitebox-Tests umfassen, ist in dem Package \textbf{\texttt{test}} ein weiteres Go-File (\texttt{testrest.go}) enthalten. Dieses setzt einen Test des Webservers um, bei dem auf Testdaten eines Produktkataloges zurückgegriffen wird. Mit Hilfe der integrierten Tests wird in der hier beschriebenen Version eine Code-Coverage von 100\% erreicht, das heißt jedes Stück Code wird mindestens einmal zur Ausführung gebracht.
\subsection{Anpassung des Monolithen} \subsection{Anpassung des Monolithen}
\label{subsec: Anpassung des Monolithen} \label{subsec: Anpassung des Monolithen}

View File

@ -28,8 +28,8 @@ func addGood(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(pat.Param(r, "productid"), 10, 64) id, err := strconv.ParseInt(pat.Param(r, "productid"), 10, 64)
if err != nil { if err != nil {
log.Warn("false productid format") log.Warn("false product id format")
http.Error(w, "the product id is false", http.StatusNotAcceptable) http.Error(w, "the product id has a false format", http.StatusNotAcceptable)
return return
} }
log = log.WithField("productid", id) log = log.WithField("productid", id)
@ -40,7 +40,7 @@ func addGood(w http.ResponseWriter, r *http.Request) {
return return
} }
if !ok { if !ok {
log.Warn("false product, product not found") log.Warn("product not found")
http.Error(w, "the product was not found", http.StatusNotFound) http.Error(w, "the product was not found", http.StatusNotFound)
return return
} }
@ -66,7 +66,7 @@ func addGood(w http.ResponseWriter, r *http.Request) {
} }
if db.Error != nil { if db.Error != nil {
log.Error("database not able to write", db.Error) log.Error("database unable to write", db.Error)
http.Error(w, "the product could not be written into the database", http.StatusInternalServerError) http.Error(w, "the product could not be written into the database", http.StatusInternalServerError)
} }
lib.Write(w, &obj) lib.Write(w, &obj)
@ -79,7 +79,7 @@ func delGood(w http.ResponseWriter, r *http.Request) {
log := logger.HTTP(r) log := logger.HTTP(r)
id, err := strconv.ParseInt(pat.Param(r, "goodid"), 10, 64) id, err := strconv.ParseInt(pat.Param(r, "goodid"), 10, 64)
if err != nil { if err != nil {
log.Warn("wrong goodid format") log.Warn("wrong good id format")
http.Error(w, "the good id has a false format", http.StatusNotAcceptable) http.Error(w, "the good id has a false format", http.StatusNotAcceptable)
return return
} }
@ -90,8 +90,8 @@ func delGood(w http.ResponseWriter, r *http.Request) {
good.ID = id good.ID = id
db := good.FilterAvailable(database.Read).First(&good) db := good.FilterAvailable(database.Read).First(&good)
if db.RecordNotFound() { if db.RecordNotFound() {
log.Warnf("good could not found: %s", db.Error) log.Warnf("could not find good: %s", db.Error)
http.Error(w, "the good could not found", http.StatusNotFound) http.Error(w, "the good was not found", http.StatusNotFound)
return return
} }
good.ManuelleDelete = true good.ManuelleDelete = true
@ -99,8 +99,8 @@ func delGood(w http.ResponseWriter, r *http.Request) {
db = database.Write.Save(&good) db = database.Write.Save(&good)
if db.Error != nil { if db.Error != nil {
log.Warnf("good could not delete: %s", db.Error) log.Warnf("could not delete good: %s", db.Error)
http.Error(w, "the good could not delete", http.StatusInternalServerError) http.Error(w, "the good could not be deleted", http.StatusInternalServerError)
return return
} }
log.Info("done") log.Info("done")

View File

@ -1,3 +1,4 @@
// Package that contains all api routes of this microservice
package http package http
import ( import (
@ -14,6 +15,7 @@ type LockGood struct {
Count int `json:"count"` Count int `json:"count"`
} }
// Function to lock goods
func lockGoods(w http.ResponseWriter, r *http.Request) { func lockGoods(w http.ResponseWriter, r *http.Request) {
log := logger.HTTP(r) log := logger.HTTP(r)
secret := r.Header.Get("secret") secret := r.Header.Get("secret")
@ -37,8 +39,8 @@ func lockGoods(w http.ResponseWriter, r *http.Request) {
} }
if len(goods) <= 0 { if len(goods) <= 0 {
log.Warn("try to log nothing") log.Warn("tried to log nothing")
http.Error(w, "try to log nothing", http.StatusBadRequest) http.Error(w, "tried to log nothing", http.StatusBadRequest)
return return
} }
@ -47,9 +49,9 @@ func lockGoods(w http.ResponseWriter, r *http.Request) {
for _, good := range goods { for _, good := range goods {
if good.ProductID <= 0 { if good.ProductID <= 0 {
log.Warn("try to log nothing") log.Warn("tried to log nothing")
tx.Rollback() tx.Rollback()
http.Error(w, "try to log nothing", http.StatusBadRequest) http.Error(w, "tried to log nothing", http.StatusBadRequest)
return return
} }
for i := 0; i < good.Count; i++ { for i := 0; i < good.Count; i++ {
@ -68,7 +70,7 @@ func lockGoods(w http.ResponseWriter, r *http.Request) {
if db.Error != nil || db.RowsAffected != 1 { if db.Error != nil || db.RowsAffected != 1 {
http.Error(w, "the good was not found in database", http.StatusInternalServerError) http.Error(w, "the good was not found in database", http.StatusInternalServerError)
tx.Rollback() tx.Rollback()
log.Panic("more then one good locked: ", db.Error) log.Panic("there is more than one good locked: ", db.Error)
return return
} }
count += 1 count += 1
@ -81,6 +83,7 @@ func lockGoods(w http.ResponseWriter, r *http.Request) {
} }
// Function to release locked goods
func releaseGoods(w http.ResponseWriter, r *http.Request) { func releaseGoods(w http.ResponseWriter, r *http.Request) {
log := logger.HTTP(r) log := logger.HTTP(r)
secret := r.Header.Get("secret") secret := r.Header.Get("secret")
@ -91,14 +94,14 @@ func releaseGoods(w http.ResponseWriter, r *http.Request) {
result := db.RowsAffected result := db.RowsAffected
if err != nil { if err != nil {
log.Warn("database error during release goods: ", err) log.Warn("database error during the release of goods: ", err)
http.Error(w, "secret could not validate", http.StatusInternalServerError) http.Error(w, "the secret could not be validated", http.StatusInternalServerError)
return return
} }
if result <= 0 { if result <= 0 {
log.Warn("no goods found") log.Warn("no goods found")
http.Error(w, "no goods found to release", http.StatusNotFound) http.Error(w, "there are no goods to release", http.StatusNotFound)
return return
} }

View File

@ -11,6 +11,7 @@ import (
"github.com/genofire/hs_master-kss-monolith/test" "github.com/genofire/hs_master-kss-monolith/test"
) )
// Function to test lockGoods()
func TestLockGoods(t *testing.T) { func TestLockGoods(t *testing.T) {
assertion, router := test.Init(t) assertion, router := test.Init(t)
good := &models.Good{ good := &models.Good{
@ -58,6 +59,7 @@ func TestLockGoods(t *testing.T) {
test.Close() test.Close()
} }
// Function to test releaseGoods()
func TestReleaseGoods(t *testing.T) { func TestReleaseGoods(t *testing.T) {
now := time.Now() now := time.Now()
assertion, router := test.Init(t) assertion, router := test.Init(t)

View File

@ -38,7 +38,7 @@ func TestListGood(t *testing.T) {
test.Close() test.Close()
} }
// Function to getGoodAvailability() and getGoodAvailabilityCount() // Function to test getGoodAvailability() and getGoodAvailabilityCount()
func TestGetGoodAvailable(t *testing.T) { func TestGetGoodAvailable(t *testing.T) {
now := time.Now() now := time.Now()
assertion, router := test.Init(t) assertion, router := test.Init(t)
@ -99,7 +99,7 @@ func TestGetGoodAvailable(t *testing.T) {
} }
// Function to getGoodFreshness() // Function to test getGoodFreshness()
func TestGetGoodFreshness(t *testing.T) { func TestGetGoodFreshness(t *testing.T) {
now := time.Now().Add(36 * time.Hour) now := time.Now().Add(36 * time.Hour)
assertion, router := test.Init(t) assertion, router := test.Init(t)

View File

@ -9,19 +9,19 @@ import (
"text/template" "text/template"
) )
// Path to the svg image template, that shows the availablity or freshness of a given good // Path to the svg image template, that shows the availability or freshness of a given good
// with a traffic light food labeling system // with a traffic light food labeling system
var GoodAvailabilityTemplate string var GoodAvailabilityTemplate string
var GoodFreshnessTemplate string var GoodFreshnessTemplate string
// Function to calculate a percent value from a given value and an maximum value // Function to calculate a percent value from a given value and a maximum value
func tempPercent(value, max int) int { func tempPercent(value, max int) int {
return value * 100 / max return value * 100 / max
} }
// Function to calculate a partial radius, depending on a percentage value // Function to calculate a partial radius, depending on a percentage value
func tempProcessRadius(value, max, radius int) float64 { func tempProcessRadius(value, max, radius int) float64 {
return (1 - float64(value)/float64(max)) * float64(radius) * 2 * 3.14 return (1 - float64(value) / float64(max)) * float64(radius) * 2 * 3.14
} }
// Function to get the SVG, that shows the availability with a traffic light food labeling system for a given good // Function to get the SVG, that shows the availability with a traffic light food labeling system for a given good

View File

@ -73,14 +73,14 @@ func TestAddGood(t *testing.T) {
runtime.HasPermission("testsessionkey", runtime.PermissionCreateGood) runtime.HasPermission("testsessionkey", runtime.PermissionCreateGood)
runtime.CleanCache() runtime.CleanCache()
// Test gatewaytimeout on product exists // Test the gatewaytimeout on product exists
_, w = session.JSONRequest("POST", "/api/good/1", good) _, w = session.JSONRequest("POST", "/api/good/1", good)
assertion.Equal(http.StatusGatewayTimeout, w.StatusCode) assertion.Equal(http.StatusGatewayTimeout, w.StatusCode)
time.Sleep(time.Duration(10) * time.Millisecond) time.Sleep(time.Duration(10) * time.Millisecond)
runtime.CleanCache() runtime.CleanCache()
// Test gatewaytimeout on permission exists // Test the gatewaytimeout on permission exists
_, w = session.JSONRequest("POST", "/api/good/1", good) _, w = session.JSONRequest("POST", "/api/good/1", good)
assertion.Equal(http.StatusGatewayTimeout, w.StatusCode) assertion.Equal(http.StatusGatewayTimeout, w.StatusCode)

View File

@ -23,14 +23,14 @@ var (
// Configuration of the database connection // Configuration of the database connection
type Config struct { type Config struct {
// type of the database, currently supports sqlite and postgres // Type of the database (currently supports sqlite and postgres)
Type string Type string
// connection configuration // Connection configuration
Connection string Connection string
// create another connection for reading only // Create another connection for reading only
ReadConnection string ReadConnection string
// enable logging of the generated sql string // Enable logging of the generated sql string
Logging bool Logging bool
} }
// Function to open a database and set the given configuration // Function to open a database and set the given configuration

View File

@ -22,7 +22,7 @@ func Read(r *http.Request, to interface{}) (err error) {
func Write(w http.ResponseWriter, data interface{}) { func Write(w http.ResponseWriter, data interface{}) {
js, err := json.Marshal(data) js, err := json.Marshal(data)
if err != nil { if err != nil {
http.Error(w, "failed to encode response: "+err.Error(), http.StatusInternalServerError) http.Error(w, "failed to encode response: " + err.Error(), http.StatusInternalServerError)
return return
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")

View File

@ -1,4 +1,4 @@
// Package with a lib for cronjobs to run in background // Package with a lib for cronjobs to run in the background
package worker package worker
import "time" import "time"

View File

@ -1,4 +1,4 @@
// Package with a lib for cronjobs to run in background // Package with a lib for cronjobs to run in the background
package worker package worker
import ( import (
@ -14,7 +14,7 @@ func TestWorker(t *testing.T) {
runtime := 0 runtime := 0
w := NewWorker(time.Duration(5)*time.Millisecond, func() { w := NewWorker(time.Duration(5) * time.Millisecond, func() {
runtime = runtime + 1 runtime = runtime + 1
}) })
go w.Start() go w.Start()

View File

@ -13,27 +13,27 @@ import (
// Config file for this daemon (more information at the config_example.conf in this git repository) // Config file for this daemon (more information at the config_example.conf in this git repository)
type Config struct { type Config struct {
// address under which the api and static content of the webserver runs // address under which the api and static content of the webserver runs
WebserverBind string `toml:"webserver_bind"` WebserverBind string `toml:"webserver_bind"`
// path to deliver static content // path to deliver static content
Webroot string `toml:"webroot"` Webroot string `toml:"webroot"`
Database database.Config `toml:"database"` Database database.Config `toml:"database"`
GoodRelease GoodReleaseConfig `toml:"good_release"` GoodRelease GoodReleaseConfig `toml:"good_release"`
CacheClean CacheWorkerConfig `toml:"cache_clean"` CacheClean CacheWorkerConfig `toml:"cache_clean"`
// path to the svg image templates to show the availablity and freshness // path to the SVG image templates to show the availability and freshness
// of a given good with a traffic light food labeling system // of a given good with a traffic light food labeling system
GoodAvailabilityTemplate string `toml:"good_availablity_template"` GoodAvailabilityTemplate string `toml:"good_availablity_template"`
GoodFreshnessTemplate string `toml:"good_freshness_template"` GoodFreshnessTemplate string `toml:"good_freshness_template"`
FouledDeleter Duration `toml:"fouled_deleted"` FouledDeleter Duration `toml:"fouled_deleted"`
// URLs to other microservices that this services uses // URLs to other microservices, which this service uses
MicroserviceDependencies struct { MicroserviceDependencies struct {
Product string `toml:"product"` Product string `toml:"product"`
Permission string `toml:"permission"` Permission string `toml:"permission"`
} `toml:"microservice_dependencies"` } `toml:"microservice_dependencies"`
} }
// Configuration of the Worker to clean the cache from values of other microservice // Configuration of the Worker to clean the cache from values of other microservice
@ -48,7 +48,7 @@ type CacheWorkerConfig struct {
type GoodReleaseConfig struct { type GoodReleaseConfig struct {
// Run worker every Duration // Run worker every Duration
Every Duration `toml:"every"` Every Duration `toml:"every"`
// Unlock those which are not used since Duration // Unlock those, which are not used since Duration
After Duration `toml:"after"` After Duration `toml:"after"`
} }

View File

@ -8,7 +8,7 @@ import (
) )
// Duration is a TOML datatype // Duration is a TOML datatype
// A duration string is a possibly signed sequence of decimal numbers and a unit suffix, // A duration string is a possibly signed sequence of decimal numbers and a unit suffix,
// such as "300s", "1.5h" or "5d". Valid time units are "s", "m", "h", "d", "w". // such as "300s", "1.5h" or "5d". Valid time units are "s", "m", "h", "d", "w".
type Duration struct { type Duration struct {
time.Duration time.Duration
@ -28,8 +28,8 @@ func (d *Duration) UnmarshalTOML(dataInterface interface{}) error {
return fmt.Errorf("invalid duration: \"%s\"", data) return fmt.Errorf("invalid duration: \"%s\"", data)
} }
unit := data[len(data)-1] unit := data[len(data) - 1]
value, err := strconv.Atoi(string(data[:len(data)-1])) value, err := strconv.Atoi(string(data[:len(data) - 1]))
if err != nil { if err != nil {
return fmt.Errorf("unable to parse duration %s: %s", data, err) return fmt.Errorf("unable to parse duration %s: %s", data, err)
} }

View File

@ -10,18 +10,18 @@ import (
"github.com/genofire/hs_master-kss-monolith/lib/database" "github.com/genofire/hs_master-kss-monolith/lib/database"
) )
// Goods managed in this stock microservice // Type of goods managed in this stock microservice
type Good struct { type Good struct {
ID int64 `json:"id"` ID int64 `json:"id"`
ProductID int64 `json:"product_id"` ProductID int64 `json:"product_id"`
Position string `json:"position"` Position string `json:"position"`
Comment string `json:"comment"` Comment string `json:"comment"`
FouledAt *time.Time `json:"fouled_at"` FouledAt *time.Time `json:"fouled_at"`
RecievedAt *time.Time `sql:"default:current_timestamp" json:"recieved_at"` RecievedAt *time.Time `sql:"default:current_timestamp" json:"recieved_at"`
// Make it temporary unusable // Make it temporary unusable
LockedAt *time.Time `json:"-"` LockedAt *time.Time `json:"-"`
LockedSecret string `json:"-"` LockedSecret string `json:"-"`
// Make it unusable // Make it unusable
DeletedAt *time.Time `json:"-"` DeletedAt *time.Time `json:"-"`
ManuelleDelete bool `json:"-"` ManuelleDelete bool `json:"-"`

View File

@ -1,4 +0,0 @@
// Package with the mostly static content (models) of this microservice
package models
// Store all the structs

View File

@ -16,108 +16,109 @@ import de.mstock.monolith.web.ReviewDTO;
public class DataTransferObjectFactory { public class DataTransferObjectFactory {
/** /**
* Creates a Data Transfer Object (DTO). * Creates a Data Transfer Object (DTO).
* *
* @param category database entity * @param category database entity
* @param locale the requested locale * @param locale the requested locale
* @return DTO * @return DTO
*/ */
public CategoryDTO createCategoryDTO(Category category, Locale locale) { public CategoryDTO createCategoryDTO(Category category, Locale locale) {
CategoryI18n i18n = category.getI18n().get(locale.getLanguage()); CategoryI18n i18n = category.getI18n().get(locale.getLanguage());
CategoryDTO categoryDTO = new CategoryDTO(); CategoryDTO categoryDTO = new CategoryDTO();
categoryDTO.setName(i18n.getName()); categoryDTO.setName(i18n.getName());
categoryDTO.setPrettyUrlFragment(i18n.getPrettyUrlFragment()); categoryDTO.setPrettyUrlFragment(i18n.getPrettyUrlFragment());
return categoryDTO; return categoryDTO;
}
/**
* Creates a Data Transfer Object (DTO).
*
* @param product database entity
* @param locale the requested locale
* @return DTO
*/
public ProductDTO createProductDTO(Product product, Locale locale) {
return createProductDTO(product, locale, NumberFormat.getCurrencyInstance(locale));
}
private ProductDTO createProductDTO(Product product, Locale locale, NumberFormat numberFormat) {
ProductDTO productDTO = createProductWithoutReviewsDTO(product, locale, numberFormat);
ProductI18n i18n = product.getI18n().get(locale.getLanguage());
productDTO.setReviews(createReviewDTOs(i18n.getReviews()));
return productDTO;
}
/**
* Creates Data Transfer Objects (DTOs).
*
* @param products database entities
* @param locale the requested locale
* @return DTOs
*/
public List<ProductDTO> createProductDTOs(List<Product> products, Locale locale) {
List<ProductDTO> productDTOs = new ArrayList<>(products.size());
NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale);
for (Product product : products) {
productDTOs.add(createProductDTO(product, locale, numberFormat));
} }
return productDTOs;
}
/** /**
* Creates Data Transfer Objects (DTOs) without loading their reviews. * Creates a Data Transfer Object (DTO).
* *
* @param products database entities * @param product database entity
* @param locale the requested locale * @param locale the requested locale
* @return DTOs * @return DTO
*/ */
public List<ProductDTO> createProductWithoutReviewsDTOs(List<Product> products, Locale locale) { public ProductDTO createProductDTO(Product product, Locale locale) {
List<ProductDTO> productDTOs = new ArrayList<>(products.size()); return createProductDTO(product, locale, NumberFormat.getCurrencyInstance(locale));
NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale);
for (Product product : products) {
productDTOs.add(createProductWithoutReviewsDTO(product, locale, numberFormat));
} }
return productDTOs;
}
private ProductDTO createProductWithoutReviewsDTO(Product product, Locale locale, private ProductDTO createProductDTO(Product product, Locale locale, NumberFormat numberFormat) {
NumberFormat numberFormat) { ProductDTO productDTO = createProductWithoutReviewsDTO(product, locale, numberFormat);
ProductI18n i18n = product.getI18n().get(locale.getLanguage()); ProductI18n i18n = product.getI18n().get(locale.getLanguage());
String price = numberFormat.format(i18n.getPrice()); productDTO.setReviews(createReviewDTOs(i18n.getReviews()));
ProductDTO productDTO = new ProductDTO(); return productDTO;
productDTO.setId(product.getId()); }
productDTO.setItemNumber(product.getItemNumber());
productDTO.setUnit(product.getUnit()); /**
productDTO.setName(i18n.getName()); * Creates Data Transfer Objects (DTOs).
productDTO.setPrettyUrlFragment(i18n.getPrettyUrlFragment()); *
productDTO.setPrice(price); * @param products database entities
productDTO.setDescription(i18n.getDescription()); * @param locale the requested locale
return productDTO; * @return DTOs
} */
public List<ProductDTO> createProductDTOs(List<Product> products, Locale locale) {
/** List<ProductDTO> productDTOs = new ArrayList<>(products.size());
* Creates a Data Transfer Object (DTO). NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale);
* for (Product product : products) {
* @param review database entity productDTOs.add(createProductDTO(product, locale, numberFormat));
* @return DTO }
*/ return productDTOs;
public ReviewDTO createReviewDTO(Review review) { }
ReviewDTO dto = new ReviewDTO();
dto.setLanguage(review.getLocaleLanguage()); /**
dto.setRatingStars(review.getRatingStars()); * Creates Data Transfer Objects (DTOs) without loading their reviews.
dto.setFirstName(review.getFirstName()); *
dto.setLastName(review.getLastName()); * @param products database entities
dto.setText(review.getText()); * @param locale the requested locale
return dto; * @return DTOs
} */
public List<ProductDTO> createProductWithoutReviewsDTOs(List<Product> products, Locale locale) {
private List<ReviewDTO> createReviewDTOs(List<Review> reviews) { List<ProductDTO> productDTOs = new ArrayList<>(products.size());
List<ReviewDTO> ratingDTOs = new ArrayList<>(reviews.size()); NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale);
for (Review review : reviews) { for (Product product : products) {
ratingDTOs.add(createReviewDTO(review)); productDTOs.add(createProductWithoutReviewsDTO(product, locale, numberFormat));
}
return productDTOs;
}
private ProductDTO createProductWithoutReviewsDTO(Product product, Locale locale,
NumberFormat numberFormat) {
ProductI18n i18n = product.getI18n().get(locale.getLanguage());
String price = numberFormat.format(i18n.getPrice());
ProductDTO productDTO = new ProductDTO();
// Addition: productDTO.setID()
productDTO.setId(product.getId());
productDTO.setItemNumber(product.getItemNumber());
productDTO.setUnit(product.getUnit());
productDTO.setName(i18n.getName());
productDTO.setPrettyUrlFragment(i18n.getPrettyUrlFragment());
productDTO.setPrice(price);
productDTO.setDescription(i18n.getDescription());
return productDTO;
}
/**
* Creates a Data Transfer Object (DTO).
*
* @param review database entity
* @return DTO
*/
public ReviewDTO createReviewDTO(Review review) {
ReviewDTO dto = new ReviewDTO();
dto.setLanguage(review.getLocaleLanguage());
dto.setRatingStars(review.getRatingStars());
dto.setFirstName(review.getFirstName());
dto.setLastName(review.getLastName());
dto.setText(review.getText());
return dto;
}
private List<ReviewDTO> createReviewDTOs(List<Review> reviews) {
List<ReviewDTO> ratingDTOs = new ArrayList<>(reviews.size());
for (Review review : reviews) {
ratingDTOs.add(createReviewDTO(review));
}
return Collections.unmodifiableList(ratingDTOs);
} }
return Collections.unmodifiableList(ratingDTOs);
}
} }

View File

@ -17,16 +17,18 @@ public class HomepageController {
@Autowired @Autowired
private ShopService shopService; private ShopService shopService;
// Addition: contant with the address of the stock microservice adminfrontend
private final String STOCKADMINFRONTENDTEMPLATE = "https://stock.pub.warehost.de/index.html"; private final String STOCKADMINFRONTENDTEMPLATE = "https://stock.pub.warehost.de/index.html";
/** /**
* Redirect * Redirect to stock admin frontend
* *
* @param model Template model * @param model Template model
* @return The constant template name for the stock admin frontend. * @return The constant template name for the stock admin frontend.
*/ */
@RequestMapping(value = "/stockadmin", method = RequestMethod.GET) @RequestMapping(value = "/stockadmin", method = RequestMethod.GET)
public String redirect(Model model) {return "redirect:"+ this.STOCKADMINFRONTENDTEMPLATE; public String redirect(Model model) {
return "redirect:" + this.STOCKADMINFRONTENDTEMPLATE;
} }
/** /**

View File

@ -15,6 +15,7 @@ public class ProductDTO {
private String description; private String description;
private List<ReviewDTO> reviews; private List<ReviewDTO> reviews;
// Addition: int id, getId() and setID()
public int getId() { public int getId() {
return id; return id;
} }

View File

@ -31,7 +31,10 @@
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
<h2 th:text="${product.name}">Product Name</h2> <h2 th:text="${product.name}">Product Name</h2>
<!-- Addition: traffic light food labeling system of the stock microservice -->
<img width="10%" class="icon" th:src="${'https://stock.pub.warehost.de/api/good/availablity/'+product.id}"/> <img width="10%" class="icon" th:src="${'https://stock.pub.warehost.de/api/good/availablity/'+product.id}"/>
<p class="text-info text-uppercase" th:text="${product.price}">0,00 Euro</p> <p class="text-info text-uppercase" th:text="${product.price}">0,00 Euro</p>
<p class="lead" th:text="${product.description}">Description.</p> <p class="lead" th:text="${product.description}">Description.</p>
<div th:replace="fragments/reviews :: reviews"></div> <div th:replace="fragments/reviews :: reviews"></div>

View File

@ -1,32 +0,0 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/skeleton :: head">
<meta charset="utf-8" />
<meta http-equiv="refresh" content="5; URL=http://localhost:65000/"/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="../static/css/bootstrap.min.css" rel="stylesheet" />
<link href="../static/css/mosh.css" rel="stylesheet" />
</head>
<body>
<div th:replace="fragments/skeleton :: navigation">
<div class="container">
<nav>Navigation</nav>
</div>
</div>
<div class="container product">
<div class="row info">
<p align="center"><a href="http://localhost:65000/">If the automatic redirection to the stock management admin
front end does not work, click here.</a></p>
</div>
<footer th:replace="fragments/skeleton :: footer">
<p>&copy; 2017</p>
</footer>
</div>
<script src="../static/js/jquery-3.1.1.min.js"
th:src="@{/js/jquery-3.1.1.min.js}"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="../static/js/bootstrap.min.js"
th:src="@{/js/bootstrap.min.js}"></script>
</body>
</html>

View File

@ -10,20 +10,20 @@ import (
"sync" "sync"
) )
// URL to the microservice which manages permissions // URL to the microservice, which manages permissions
var PermissionURL string var PermissionURL string
// Type of permission // Type of permission
type Permission int type Permission int
// Some permissions (the real permissions need to come from the permission microservice) // Some permissions (the real permissions need to come from a permission microservice)
const ( const (
// permission to add goods to the stock // permission to add goods to the stock
// e.g. if a good is received and now available to sell // e.g. if a good is received and now available for selling
PermissionCreateGood = 1 PermissionCreateGood = 1
// permission to delete goods from the stock // permission to delete goods from the stock
// e.g. if a good becomes fouled and has to be removed // e.g. if a good becomes fouled and has to be removed manually
PermissionDeleteGood = 2 PermissionDeleteGood = 2
) )

View File

@ -8,7 +8,7 @@ import (
"github.com/genofire/hs_master-kss-monolith/models" "github.com/genofire/hs_master-kss-monolith/models"
) )
// Function to test the cache Worker // Function to test the cacheWorker
func TestCacheWorker(t *testing.T) { func TestCacheWorker(t *testing.T) {
productExistCache[2] = boolMicroServiceCache{LastCheck: time.Now(), Value: true} productExistCache[2] = boolMicroServiceCache{LastCheck: time.Now(), Value: true}

View File

@ -8,7 +8,7 @@ import (
"github.com/genofire/hs_master-kss-monolith/models" "github.com/genofire/hs_master-kss-monolith/models"
) )
// Function to remove automaticle goods after the are fouled // Function to automatically remove goods, if they are fouled
func GoodFouled() int { func GoodFouled() int {
var goods []*models.Good var goods []*models.Good
var g models.Good var g models.Good

View File

@ -11,7 +11,7 @@ import (
"github.com/genofire/hs_master-kss-monolith/models" "github.com/genofire/hs_master-kss-monolith/models"
) )
// Function to test the unlocking of goods // Function to test fouledDelete()
func TestFouledDelete(t *testing.T) { func TestFouledDelete(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
database.Open(database.Config{ database.Open(database.Config{

View File

@ -11,7 +11,7 @@ import (
"github.com/genofire/hs_master-kss-monolith/models" "github.com/genofire/hs_master-kss-monolith/models"
) )
// Function to test the unlocking of goods // Function to test goodRelease()
func TestGoodRelease(t *testing.T) { func TestGoodRelease(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
database.Open(database.Config{ database.Open(database.Config{

View File

@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
// Function to test, if and which products exist (get information from the products catalogue) // Function to test, if and which products exist (get information from the product catalogue)
func TestProductExists(t *testing.T) { func TestProductExists(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)

View File

@ -1,5 +0,0 @@
// Package with supporting functionality to run the microservice
package runtime
// some mingled functionality to handle in background

View File

@ -25,6 +25,7 @@ type MockTransport struct {
running bool running bool
} }
// Function to use the http handler
func (t *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) { func (t *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if !t.running { if !t.running {
return nil, errors.New("mock a error") return nil, errors.New("mock a error")
@ -33,9 +34,13 @@ func (t *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
t.Handler.ServeHTTP(w, req) t.Handler.ServeHTTP(w, req)
return w.Result(), nil return w.Result(), nil
} }
// Function to start the http handler
func (t *MockTransport) Start() { func (t *MockTransport) Start() {
t.running = true t.running = true
} }
// Function to close/stop the http handler
func (t *MockTransport) Close() { func (t *MockTransport) Close() {
t.running = false t.running = false
} }
@ -73,7 +78,7 @@ func Close() {
mock.Close() mock.Close()
} }
// Handle a test session with cookies // Struct to dandle a test session with cookies
type Request struct { type Request struct {
req *http.Request req *http.Request
cookies []*http.Cookie cookies []*http.Cookie

View File

@ -1,4 +1,4 @@
{ {
"id": 1, "id": 1,
"title": "Kiwi" "title": "Kiwi"
} }

View File

@ -1,4 +1,4 @@
{ {
"id": 2, "id": 2,
"title": "Blueberries" "title": "Blueberries"
} }

View File

@ -1,4 +1,4 @@
{ {
"id": 3, "id": 3,
"title": "Cherries" "title": "Cherries"
} }

View File

@ -1,4 +1,4 @@
{ {
"id": 4, "id": 4,
"title": "Potatoes" "title": "Potatoes"
} }

View File

@ -1,4 +1,4 @@
{ {
"id": 5, "id": 5,
"title": "Tomatoes" "title": "Tomatoes"
} }

View File

@ -1,4 +1,4 @@
{ {
"id": 6, "id": 6,
"title": "Rhubarb" "title": "Rhubarb"
} }

View File

@ -1,8 +1,8 @@
[ [
{"id":1, "title": "Kiwi"}, {"id":1, "title": "Kiwi"},
{"id":2, "title": "Blueberries"}, {"id":2, "title": "Blueberries"},
{"id":3, "title": "Cherries"}, {"id":3, "title": "Cherries"},
{"id":4, "title": "Potatoes"}, {"id":4, "title": "Potatoes"},
{"id":5, "title": "Tomatoes"}, {"id":5, "title": "Tomatoes"},
{"id":6, "title": "Rhubarb"} {"id":6, "title": "Rhubarb"}
] ]

View File

@ -1,79 +1,81 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1"/>
<link href="/node_modules/semantic-ui-css/semantic.min.css" rel="stylesheet" /> <link href="/node_modules/semantic-ui-css/semantic.min.css" rel="stylesheet"/>
<link href="/static/css/main.css" rel="stylesheet" /> <link href="/static/css/main.css" rel="stylesheet"/>
<title>microStock Dummy Cart</title> <title>microStock Dummy Cart</title>
</head> </head>
<body ng-app="microStockDummieCare" ng-controller="MainCtrl"> <body ng-app="microStockDummieCare" ng-controller="MainCtrl">
<nav class="ui stackable inverted menu"> <nav class="ui stackable inverted menu">
<div class="ui container"> <div class="ui container">
<div class="header item">Dummy Cart</div> <div class="header item">Dummy Cart</div>
<div class="right menu"> <div class="right menu">
<a class="ui item" ng-click="reset()"> <a class="ui item" ng-click="reset()">
<i class="undo icon"></i> <i class="undo icon"></i>
Reset Reset
</a> </a>
</div> </div>
</div>
</nav>
<div class="ui container">
<form class="ui form" ng-submit="add()">
<div class="three fields">
<div class="field">
<div class="ui fluid search selection dropdown">
<input name="country" type="hidden">
<i class="dropdown icon"></i>
<div class="default text">Select Product</div>
<div class="menu">
<div class="item" ng-repeat="item in products" data-value="{{item.id}}"><img class="icon" ng-src="{{'/api/good/availablity/'+item.id| reloadSrc}}"/>{{item.title}}</div>
</div>
</div>
</div>
<div class="field">
<input placeholder="Count" type="number" min="1" max="50" ng-model="goods.count">
</div>
<div class="field">
<button type="submit" class="ui button" tabindex="0">Add</button>
</div>
</div>
</form>
<table class="ui table">
<thead>
<tr>
<th>Count</th>
<th>Product</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in cart">
<td>{{item.count}}</td>
<td>{{getProduct(item.product_id).title}}</td>
<td>
<div class="ui button icon" ng-click="del(item)"><i class="icon trash"></i></div>
</td>
</tr>
</tbody>
</table>
</div>
<footer class="ui vertical footer segment">
<div class="ui center aligned container">
<p>&copy; 2017 MM / Go - Team</p>
</div> </div>
</footer> </nav>
<script src="/node_modules/jquery/dist/jquery.min.js"></script>
<script src="/node_modules/semantic-ui-css/semantic.min.js"></script> <div class="ui container">
<script src="/node_modules/angular/angular.min.js"></script> <form class="ui form" ng-submit="add()">
<script src="/node_modules/angular-animate/angular-animate.min.js"></script> <div class="three fields">
<script src="/node_modules/angular-ui-router/release/angular-ui-router.min.js"></script> <div class="field">
<script src="/node_modules/angular-loading-bar/build/loading-bar.min.js"></script> <div class="ui fluid search selection dropdown">
<script> <input name="country" type="hidden">
<i class="dropdown icon"></i>
<div class="default text">Select Product</div>
<div class="menu">
<div class="item" ng-repeat="item in products" data-value="{{item.id}}"><img class="icon"
ng-src="{{'/api/good/availablity/'+item.id| reloadSrc}}"/>{{item.title}}
</div>
</div>
</div>
</div>
<div class="field">
<input placeholder="Count" type="number" min="1" max="50" ng-model="goods.count">
</div>
<div class="field">
<button type="submit" class="ui button" tabindex="0">Add</button>
</div>
</div>
</form>
<table class="ui table">
<thead>
<tr>
<th>Count</th>
<th>Product</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in cart">
<td>{{item.count}}</td>
<td>{{getProduct(item.product_id).title}}</td>
<td>
<div class="ui button icon" ng-click="del(item)"><i class="icon trash"></i></div>
</td>
</tr>
</tbody>
</table>
</div>
<footer class="ui vertical footer segment">
<div class="ui center aligned container">
<p>&copy; 2017 MM / Go - Team</p>
</div>
</footer>
<script src="/node_modules/jquery/dist/jquery.min.js"></script>
<script src="/node_modules/semantic-ui-css/semantic.min.js"></script>
<script src="/node_modules/angular/angular.min.js"></script>
<script src="/node_modules/angular-animate/angular-animate.min.js"></script>
<script src="/node_modules/angular-ui-router/release/angular-ui-router.min.js"></script>
<script src="/node_modules/angular-loading-bar/build/loading-bar.min.js"></script>
<script>
var config = { var config = {
'microservice_dependencies': { 'microservice_dependencies': {
'products': '/api-test/product/', 'products': '/api-test/product/',
@ -192,6 +194,7 @@
}; };
}]); }]);
</script>
</script>
</body> </body>
</html> </html>

View File

@ -1,48 +1,49 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1"/>
<link href="/node_modules/semantic-ui-css/semantic.min.css" rel="stylesheet" /> <link href="/node_modules/semantic-ui-css/semantic.min.css" rel="stylesheet"/>
<link href="/static/css/main.css" rel="stylesheet" /> <link href="/static/css/main.css" rel="stylesheet"/>
<title>Stock Admin</title> <title>Stock Admin</title>
</head> </head>
<body ng-app="microStock"> <body ng-app="microStock">
<nav class="ui stackable inverted menu" ng-controller="GlobalCtrl"> <nav class="ui stackable inverted menu" ng-controller="GlobalCtrl">
<div class="ui container"> <div class="ui container">
<div class="header item">Stock Admin</div> <div class="header item">Stock Admin</div>
<a class="item" ui-sref="list" ui-active="active">List</a> <a class="item" ui-sref="list" ui-active="active">List</a>
<a class="item" ui-sref="statistics" ui-active="active">Statistics</a>< <a class="item" ui-sref="statistics" ui-active="active">Statistics</a><
<div class="right menu"> <div class="right menu">
<a class="ui item" ng-click="login()"> <a class="ui item" ng-click="login()">
<i class="icon" ng-class="{'unlock':!loggedIn,'lock':loggedIn}"></i> <i class="icon" ng-class="{'unlock':!loggedIn,'lock':loggedIn}"></i>
</a> </a>
</div> </div>
</div> </div>
</nav> </nav>
<div class="ui container" ui-view=""></div> <div class="ui container" ui-view=""></div>
<footer class="ui vertical footer segment"> <footer class="ui vertical footer segment">
<div class="ui center aligned container"> <div class="ui center aligned container">
<p>&copy; 2017 MM / Go - Team</p> <p>&copy; 2017 MM / Go - Team</p>
</div> </div>
</footer> </footer>
<script src="//cdn.jsdelivr.net/webshim/1.14.5/polyfiller.js"></script> <script src="//cdn.jsdelivr.net/webshim/1.14.5/polyfiller.js"></script>
<script> <script>
webshims.setOptions('forms-ext', {types: 'date'}); webshims.setOptions('forms-ext', {types: 'date'});
webshims.polyfill('forms forms-ext'); webshims.polyfill('forms forms-ext');
</script>
<script src="/node_modules/angular/angular.min.js"></script> </script>
<script src="/node_modules/angular-animate/angular-animate.min.js"></script> <script src="/node_modules/angular/angular.min.js"></script>
<script src="/node_modules/angular-ui-router/release/angular-ui-router.min.js"></script> <script src="/node_modules/angular-animate/angular-animate.min.js"></script>
<script src="/node_modules/angular-loading-bar/build/loading-bar.min.js"></script> <script src="/node_modules/angular-ui-router/release/angular-ui-router.min.js"></script>
<script src="/static/js/main.js"></script> <script src="/node_modules/angular-loading-bar/build/loading-bar.min.js"></script>
<script src="/static/js/item.controller.js"></script> <script src="/static/js/main.js"></script>
<script src="/static/js/item-add.controller.js"></script> <script src="/static/js/item.controller.js"></script>
<script src="/static/js/global.js"></script> <script src="/static/js/item-add.controller.js"></script>
<script src="/static/js/list.controller.js"></script> <script src="/static/js/global.js"></script>
<script src="/static/js/statistics.controller.js"></script> <script src="/static/js/list.controller.js"></script>
<script src="/static/js/statistics.controller.js"></script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 434 KiB

After

Width:  |  Height:  |  Size: 451 KiB

View File

@ -1,31 +1,31 @@
<h1> <h1>
{{product.title}} {{product.title}}
<a ui-sref="item({productid:product.id})"> <a ui-sref="item({productid:product.id})">
<i class="icon linkify"></i> <i class="icon linkify"></i>
</a> </a>
</h1> </h1>
<form class="ui form segment" ng-submit="submit()" ng-class="{'top attached':msg.type}"> <form class="ui form segment" ng-submit="submit()" ng-class="{'top attached':msg.type}">
<div class="field"> <div class="field">
<label>Expiration Date</label> <label>Expiration Date</label>
<input type="date" name="fouled_at" placeholder="Fouled at date (e.g. 2017-06-30)" ng-model="obj.fouled_at" <input type="date" name="fouled_at" placeholder="Fouled at date (e.g. 2017-06-30)" ng-model="obj.fouled_at"
required="" pattern="((?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]| required="" pattern="((?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]|
1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31)))?"> 1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31)))?">
</div> </div>
<div class="field"> <div class="field">
<label>Position</label> <label>Position</label>
<input type="text" name="position" placeholder="Location in Store", ng-model="obj.position"> <input type="text" name="position" placeholder="Location in Store" , ng-model="obj.position">
</div> </div>
<div class="field"> <div class="field">
<label>Comment</label> <label>Comment</label>
<input type="text" name="comment" placeholder="Comment for this good", ng-model="obj.comment"> <input type="text" name="comment" placeholder="Comment for this good" , ng-model="obj.comment">
</div> </div>
<div class="field"> <div class="field">
<label>Count</label> <label>Count</label>
<input type="number" name="count" ng-model="count" min="1"> <input type="number" name="count" ng-model="count" min="1">
</div> </div>
<button class="ui button" type="submit">Submit</button> <button class="ui button" type="submit">Submit</button>
</form> </form>
<div class="ui bottom attached message {{msg.type}}" ng-show="msg.type"> <div class="ui bottom attached message {{msg.type}}" ng-show="msg.type">
{{msg.text}} {{msg.text}}
</div> </div>

View File

@ -1,37 +1,37 @@
<h1> <h1>
{{obj.title}} {{obj.title}}
<img class="icon" src="/api/good/availablity/{{obj.id}}"/> <img class="icon" src="/api/good/availablity/{{obj.id}}"/>
<a class="ui icon button mini right floated" ui-sref="item-add({productid: obj.id})"><i class="icon plus"></i></a> <a class="ui icon button mini right floated" ui-sref="item-add({productid: obj.id})"><i class="icon plus"></i></a>
</h1> </h1>
<table class="ui table very basic"> <table class="ui table very basic">
<tr> <tr>
<td>Product ID</td> <td>Product ID</td>
<td>{{obj.id}}</td> <td>{{obj.id}}</td>
</tr> </tr>
<tr> <tr>
<td>Count</td> <td>Count</td>
<td>{{list.length}}</td> <td>{{list.length}}</td>
</tr> </tr>
</table> </table>
<h2>Goods of this product</h2> <h2>Goods of this product</h2>
<div class="ui warning message" ng-if="list.length <= 0"> <div class="ui warning message" ng-if="list.length <= 0">
<p>There are no goods for this product.</p> <p>There are no goods for this product.</p>
</div> </div>
<table class="ui table list" ng-if="list.length > 0"> <table class="ui table list" ng-if="list.length > 0">
<thead> <thead>
<tr> <tr>
<th>Status of Freshness</th> <th>Status of Freshness</th>
<th>Location</th> <th>Location</th>
<th>Comment</th> <th>Comment</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="item in list"> <tr ng-repeat="item in list">
<td><img class="icon" ng-src="{{'/api/good/freshness/'+item.id| reloadSrc}}"/></td> <td><img class="icon" ng-src="{{'/api/good/freshness/'+item.id| reloadSrc}}"/></td>
<td>{{item.position}}</td> <td>{{item.position}}</td>
<td>{{item.comment}}</td> <td>{{item.comment}}</td>
<td><a class="ui icon button mini" ng-click="delete(item.id)"><i class="trash icon"></i></a></td> <td><a class="ui icon button mini" ng-click="delete(item.id)"><i class="trash icon"></i></a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -1,23 +1,23 @@
<h1>List of Products</h1> <h1>List of Products</h1>
<table class="ui very compact table"> <table class="ui very compact table">
<thead> <thead>
<tr> <tr>
<th class="two wide">#</th> <th class="two wide">#</th>
<th class="twelve wide">Productname</th> <th class="twelve wide">Productname</th>
<th class="one wide">Amount</th> <th class="one wide">Amount</th>
<th class="one wide"></th> <th class="one wide"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="item in list"> <tr ng-repeat="item in list">
<td>{{item.id}}</td> <td>{{item.id}}</td>
<td><a ui-sref="item({productid: item.id})">{{item.title}}</a></td> <td><a ui-sref="item({productid: item.id})">{{item.title}}</a></td>
<td> <td>
<img class="icon" ng-src="{{'/api/good/availablity/'+item.id| reloadSrc}}"/> <img class="icon" ng-src="{{'/api/good/availablity/'+item.id| reloadSrc}}"/>
</td> </td>
<td> <td>
<a class="ui icon button" ui-sref="item-add({productid: item.id})"><i class="icon plus"></i></a> <a class="ui icon button" ui-sref="item-add({productid: item.id})"><i class="icon plus"></i></a>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -1,21 +1,21 @@
<h1>Statistics</h1> <h1>Statistics</h1>
<div class="ui statistics two"> <div class="ui statistics two">
<div class="statistic"> <div class="statistic">
<div class="value"> <div class="value">
<i class="cube icon"></i> <i class="cube icon"></i>
{{obj.good.count}} {{obj.good.count}}
</div>
<div class="label">
Total Count of Goods
</div>
</div> </div>
<div class="label"> <div class="statistic">
Total Count of Goods <div class="value">
<i class="cubes icon"></i>
{{obj.good.avg}}
</div>
<div class="label">
Avg Goods per Product
</div>
</div> </div>
</div>
<div class="statistic">
<div class="value">
<i class="cubes icon"></i>
{{obj.good.avg}}
</div>
<div class="label">
Avg Goods per Product
</div>
</div>
</div> </div>