Compare commits
100 Commits
2c5e84d836
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
c3be0b8e54
|
|||
|
8ca7858069
|
|||
|
5e030a5bc3
|
|||
|
b47abbfc0b
|
|||
|
4905d18222
|
|||
|
eb8838ab75
|
|||
|
6ad0112683
|
|||
|
b9cea2347c
|
|||
|
e420bf303a
|
|||
|
92ddd9e0fe
|
|||
|
8c17aa9d6d
|
|||
|
d37c53c60b
|
|||
|
2465b0a73a
|
|||
| 12515c2a1e | |||
|
6dd91f5a1b
|
|||
|
249fabbd7a
|
|||
|
c9675b1573
|
|||
|
63a9e2ff58
|
|||
|
28325c8d7b
|
|||
|
93a5e9c1ba
|
|||
|
4d093ed99a
|
|||
|
77e6e6bc04
|
|||
|
c28939d2b8
|
|||
|
cfe5d89b22
|
|||
|
f84c9b6ea2
|
|||
|
42d808165b
|
|||
|
96b22a2254
|
|||
|
c1ae0706f9
|
|||
|
8eff1115c5
|
|||
|
4708a2b8ff
|
|||
|
4989848335
|
|||
|
d02d48e7c8
|
|||
|
2a3d789292
|
|||
|
169afed5c9
|
|||
|
3648d5a1cc
|
|||
|
1c3555d8b7
|
|||
|
d8604dc3cc
|
|||
|
417a7cf982
|
|||
|
ab1dab6619
|
|||
|
262321a1e2
|
|||
|
c8b8f87f6c
|
|||
|
f59593e9e8
|
|||
|
c8e33884c0
|
|||
|
55c8c739b0
|
|||
|
1d45635530
|
|||
|
3ea8603368
|
|||
|
c0175fc9bc
|
|||
| 7e7558dd5e | |||
|
86a4a28aee
|
|||
|
a5528be456
|
|||
| 0faef20441 | |||
| 395231e1bf | |||
| bc68115ce1 | |||
| dfb5ead740 | |||
| 2c630aff95 | |||
| dc18440821 | |||
| 4b4070df98 | |||
| 325aa46cf2 | |||
|
ebee8a304b
|
|||
|
3f13118312
|
|||
|
27cdde132d
|
|||
|
f8b1949c28
|
|||
|
9b276bd644
|
|||
|
70ddd91d6b
|
|||
|
95d945dda7
|
|||
|
bbb0cf3f42
|
|||
|
cd7f919eb2
|
|||
|
e77d3c4d5e
|
|||
|
0e16bb5361
|
|||
|
2bda056ca7
|
|||
|
6d104dc72a
|
|||
|
72e3fbe05b
|
|||
|
acf793ae41
|
|||
|
e6158e680f
|
|||
|
9f5f5413d1
|
|||
|
9c56ea56e2
|
|||
|
b6a6c9375f
|
|||
|
0c6f686ce5
|
|||
|
ba18d011bd
|
|||
|
ad47895597
|
|||
|
50bbcdc71d
|
|||
|
7ae7f294da
|
|||
|
c0d5a543f0
|
|||
|
8f78b80043
|
|||
|
506e7f64b6
|
|||
|
52646fbfb7
|
|||
|
240f3f5aae
|
|||
|
ea20a8cc3c
|
|||
|
4801411b1d
|
|||
|
0a6caeffc5
|
|||
|
538e23708a
|
|||
|
8cc2de5c2a
|
|||
|
da50161a92
|
|||
|
3977d395f3
|
|||
|
6a3cdb1877
|
|||
|
ff0a8b97be
|
|||
|
403295b2b4
|
|||
|
1c00a06c10
|
|||
|
4b8fd5d78a
|
|||
|
f947dd2912
|
41
Jenkinsfile
vendored
41
Jenkinsfile
vendored
@@ -6,11 +6,21 @@ pipeline {
|
||||
environment {
|
||||
GO115MODULE = 'on'
|
||||
CGO_ENABLED = 0
|
||||
GOPATH = '/tmp/gopath'
|
||||
}
|
||||
stages {
|
||||
stage('Pre Test') {
|
||||
steps {
|
||||
echo 'Installing dependencies'
|
||||
sh 'go version'
|
||||
sh 'go get -u golang.org/x/lint/golint'
|
||||
echo 'Setting permission for build script'
|
||||
sh "chmod +x do.sh"
|
||||
}
|
||||
}
|
||||
stage('Compile') {
|
||||
steps {
|
||||
sh 'go build'
|
||||
sh './do.sh build'
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
@@ -18,13 +28,35 @@ pipeline {
|
||||
stage('go vet') {
|
||||
steps {
|
||||
echo 'Running vetting'
|
||||
sh 'go vet .'
|
||||
sh './do.sh _vet'
|
||||
}
|
||||
}
|
||||
stage('test') {
|
||||
environment {
|
||||
PATH="${env.PATH}:${env.GOPATH}/bin"
|
||||
}
|
||||
steps {
|
||||
echo 'Running test'
|
||||
sh 'go test -v ./...'
|
||||
|
||||
sh './do.sh _test'
|
||||
|
||||
sh "go get github.com/tebeka/go2xunit"
|
||||
sh "go2xunit < tests.out -output tests.xml"
|
||||
junit "tests.xml"
|
||||
|
||||
// convert coverage
|
||||
sh "go get github.com/t-yuki/gocover-cobertura"
|
||||
sh "gocover-cobertura < coverage.out > coverage.xml"
|
||||
cobertura coberturaReportFile: 'coverage.xml'
|
||||
|
||||
}
|
||||
}
|
||||
stage('lint') {
|
||||
environment {
|
||||
PATH="${env.PATH}:${env.GOPATH}/bin"
|
||||
}
|
||||
steps {
|
||||
sh './do.sh _lint'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +67,6 @@ pipeline {
|
||||
always {
|
||||
mail (bcc: '',
|
||||
body: "Project: ${env.JOB_NAME} <br>Build Number: ${env.BUILD_NUMBER} <br> Build URL: ${env.BUILD_URL}", cc: '', charset: 'UTF-8', from: 'jenkins@jenkins.deepak.science', mimeType: 'text/html', replyTo: 'dmallubhotla+jenkins@gmail.com', subject: "${env.JOB_NAME} #${env.BUILD_NUMBER}: Build ${currentBuild.currentResult}", to: "dmallubhotla+ci@gmail.com")
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
config/config-missing-fields.yaml
Normal file
5
config/config-missing-fields.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
app:
|
||||
environment: "missingfield"
|
||||
|
||||
db:
|
||||
type: "typical"
|
||||
12
config/config-sample.yaml
Normal file
12
config/config-sample.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
app:
|
||||
environment: "devel"
|
||||
port: 5151
|
||||
timezone: Africa/Abidjan
|
||||
db:
|
||||
type: "aoeu"
|
||||
host: "aeihn"
|
||||
port: 1234
|
||||
user: USER
|
||||
password: PASSWORD
|
||||
database: g2
|
||||
droponstart: true # don't use this in production!
|
||||
@@ -2,44 +2,71 @@ package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Key string
|
||||
|
||||
const (
|
||||
Environment Key = `app.environment`
|
||||
Port Key = `app.port`
|
||||
DBType Key = `db.type`
|
||||
DBHost Key = `db.host`
|
||||
DBPort Key = `db.port`
|
||||
DBUser Key = `db.user`
|
||||
DBPassword Key = `db.password`
|
||||
DBDatabase Key = `db.database`
|
||||
)
|
||||
|
||||
func (k Key) GetString() string {
|
||||
return viper.GetString(string(k))
|
||||
// AppConfig represents the config flags for the base application.
|
||||
type AppConfig struct {
|
||||
Environment string
|
||||
Port string
|
||||
Timezone string
|
||||
}
|
||||
|
||||
func InitDefault() {
|
||||
|
||||
viper.SetDefault(string(Environment), "local")
|
||||
viper.SetDefault(string(Port), "8080")
|
||||
|
||||
// DBConfig is the config for the DB connection.
|
||||
type DBConfig struct {
|
||||
Type string
|
||||
Host string
|
||||
Port string
|
||||
User string
|
||||
Password string
|
||||
Database string
|
||||
DropOnStart bool
|
||||
}
|
||||
|
||||
func Init() {
|
||||
log.Print("Initialising config...")
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath(".")
|
||||
// Conf represents the overall configuration of the application.
|
||||
type Conf struct {
|
||||
App AppConfig
|
||||
Db DBConfig
|
||||
}
|
||||
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
log.Fatal("Could not read config file: \n", err)
|
||||
os.Exit(1)
|
||||
func createDefaultConf() *Conf {
|
||||
cnf := &Conf{
|
||||
App: AppConfig{
|
||||
Environment: "local",
|
||||
Port: "8080",
|
||||
Timezone: "America/New_York",
|
||||
},
|
||||
Db: DBConfig{
|
||||
Type: "postgres",
|
||||
Host: "localhost",
|
||||
Port: "5432",
|
||||
User: "<user>",
|
||||
Password: "<password>",
|
||||
Database: "gogmagog",
|
||||
DropOnStart: false,
|
||||
},
|
||||
}
|
||||
|
||||
return cnf
|
||||
}
|
||||
|
||||
// GetConf returns config values
|
||||
func GetConf(filename string) (*Conf, error) {
|
||||
|
||||
cnf := createDefaultConf()
|
||||
|
||||
vv := viper.New()
|
||||
vv.SetConfigName(filename)
|
||||
vv.SetConfigType("yaml")
|
||||
vv.AddConfigPath(".")
|
||||
|
||||
err := vv.ReadInConfig()
|
||||
if err != nil {
|
||||
log.Print("Could not load config file: \n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vv.Unmarshal(&cnf)
|
||||
return cnf, nil
|
||||
}
|
||||
|
||||
58
config/config_test.go
Normal file
58
config/config_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
|
||||
"gitea.deepak.science/deepak/gogmagog/config"
|
||||
)
|
||||
|
||||
func TestSample(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
conf, err := config.GetConf("config-sample")
|
||||
assert.Nil(err)
|
||||
|
||||
appConf := conf.App
|
||||
assert.Equal("devel", appConf.Environment)
|
||||
assert.Equal("5151", appConf.Port)
|
||||
assert.Equal("Africa/Abidjan", appConf.Timezone)
|
||||
|
||||
dbConf := conf.Db
|
||||
assert.Equal("aoeu", dbConf.Type)
|
||||
assert.Equal("aeihn", dbConf.Host)
|
||||
assert.Equal("1234", dbConf.Port)
|
||||
assert.Equal("USER", dbConf.User)
|
||||
assert.Equal("PASSWORD", dbConf.Password)
|
||||
assert.Equal("g2", dbConf.Database)
|
||||
assert.True(dbConf.DropOnStart)
|
||||
}
|
||||
|
||||
func TestDefault(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
conf, err := config.GetConf("config-missing-fields")
|
||||
assert.Nil(err)
|
||||
|
||||
appConf := conf.App
|
||||
assert.Equal("missingfield", appConf.Environment)
|
||||
assert.Equal("8080", appConf.Port)
|
||||
assert.Equal("America/New_York", appConf.Timezone)
|
||||
|
||||
dbConf := conf.Db
|
||||
assert.Equal("typical", dbConf.Type)
|
||||
assert.Equal("localhost", dbConf.Host)
|
||||
assert.Equal("5432", dbConf.Port)
|
||||
assert.Equal("<user>", dbConf.User)
|
||||
assert.Equal("<password>", dbConf.Password)
|
||||
assert.Equal("gogmagog", dbConf.Database)
|
||||
assert.False(dbConf.DropOnStart)
|
||||
}
|
||||
|
||||
func TestMissingFile(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
conf, err := config.GetConf("non-existent")
|
||||
assert.Nil(conf)
|
||||
assert.NotNil(err)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
DROP TABLE IF EXISTS actions;
|
||||
|
||||
DROP FUNCTION IF EXISTS trigger_set_timestamp;
|
||||
@@ -1,20 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS actions(
|
||||
id serial PRIMARY KEY,
|
||||
description VARCHAR (500),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION trigger_set_timestamp()
|
||||
RETURNS TRIGGER AS $set_updated$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$set_updated$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER set_updated
|
||||
BEFORE UPDATE ON actions
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE trigger_set_timestamp();
|
||||
@@ -1,69 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gitea.deepak.science/deepak/gogmagog/config"
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
_ "github.com/jackc/pgx/v4/stdlib"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
)
|
||||
|
||||
type postgresStore struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func GetStore() *postgresStore {
|
||||
|
||||
if config.DBType.GetString() != "postgres" {
|
||||
log.Fatalf("Unsupported database type: " + config.DBType.GetString())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
connStr := fmt.Sprintf("user=%s password=%s host=%s port=%s database=%s sslmode=disable",
|
||||
config.DBUser.GetString(),
|
||||
config.DBPassword.GetString(),
|
||||
config.DBHost.GetString(),
|
||||
config.DBPort.GetString(),
|
||||
config.DBDatabase.GetString())
|
||||
|
||||
tmp, err := sql.Open("pgx", connStr)
|
||||
if err != nil {
|
||||
log.Fatal("Could not connect to database: \n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
db := sqlx.NewDb(tmp, "pgx")
|
||||
if err := db.Ping(); err != nil {
|
||||
log.Fatal("database ping failed\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
driver, err := postgres.WithInstance(tmp, &postgres.Config{})
|
||||
if err != nil {
|
||||
log.Fatal("Could not create driver for db migration", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
m, err := migrate.NewWithDatabaseInstance("file://db/migrations", "postgres", driver)
|
||||
if err != nil {
|
||||
log.Fatal("Could not perform migration", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
|
||||
log.Fatalf("An error occurred while syncing the database.. %v", err)
|
||||
}
|
||||
return &postgresStore{db: db}
|
||||
}
|
||||
|
||||
func (store *postgresStore) SelectActions() ([]*models.Action, error) {
|
||||
actions := make([]*models.Action, 0)
|
||||
err := store.db.Select(&actions, "SELECT id, description, created_at AS createdat, updated_at AS updatedat FROM actions")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return actions, nil
|
||||
}
|
||||
50
do.sh
Normal file
50
do.sh
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# Do - The Simplest Build Tool on Earth.
|
||||
# Documentation and examples see https://github.com/8gears/do
|
||||
|
||||
set -Eeuo pipefail # -e "Automatic exit from bash shell script on error" -u "Treat unset variables and parameters as errors"
|
||||
|
||||
build() {
|
||||
echo "I am ${FUNCNAME[0]}ing"
|
||||
go version
|
||||
go build
|
||||
}
|
||||
|
||||
test() {
|
||||
echo "I am ${FUNCNAME[0]}ing"
|
||||
fmt && _lint && _vet && _test
|
||||
}
|
||||
|
||||
run() {
|
||||
echo "I am ${FUNCNAME[0]}ing"
|
||||
fmt && test && go run main.go
|
||||
}
|
||||
|
||||
fmt() {
|
||||
echo "I am ${FUNCNAME[0]}ing"
|
||||
go fmt ./...
|
||||
}
|
||||
|
||||
_test() {
|
||||
go test -v -coverprofile=coverage.out -covermode count ./... | tee tests.out
|
||||
}
|
||||
|
||||
_testhtml() {
|
||||
_test && go tool cover -html=coverage.out
|
||||
}
|
||||
|
||||
_lint() {
|
||||
golint -set_exit_status ./...
|
||||
}
|
||||
|
||||
_vet() {
|
||||
go vet ./...
|
||||
}
|
||||
|
||||
all() {
|
||||
fmt && test && build
|
||||
}
|
||||
|
||||
"$@" # <- execute the task
|
||||
|
||||
[ "$#" -gt 0 ] || printf "Usage:\n\t./do.sh %s\n" "($(compgen -A function | grep '^[^_]' | paste -sd '|' -))"
|
||||
6
go.mod
6
go.mod
@@ -3,8 +3,14 @@ module gitea.deepak.science/deepak/gogmagog
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/go-chi/jwtauth v1.1.1
|
||||
github.com/golang-migrate/migrate/v4 v4.14.1
|
||||
github.com/jackc/pgx/v4 v4.10.1
|
||||
github.com/jmoiron/sqlx v1.2.0
|
||||
github.com/lestrrat-go/jwx v1.0.6-0.20201127121120-26218808f029
|
||||
github.com/spf13/viper v1.7.1
|
||||
github.com/stretchr/testify v1.5.1
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
|
||||
)
|
||||
|
||||
47
go.sum
47
go.sum
@@ -34,11 +34,15 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/ClickHouse/clickhouse-go v1.3.12/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -64,9 +68,11 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
|
||||
github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.4.1 h1:pASeJT3R3YyVn+94qEPk0SnU1OQ20Jd/T+SPKy9xehY=
|
||||
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
@@ -82,10 +88,15 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dhui/dktest v0.3.3 h1:DBuH/9GFaWbDRa42qsut/hbQu+srAQ0rPWnUoiGX7CA=
|
||||
github.com/dhui/dktest v0.3.3/go.mod h1:EML9sP4sqJELHn4jV7B0TY8oF6077nk83/tz7M56jcQ=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible h1:iWPIG7pWIsCwT6ZtHnTUpoVMnete7O/pzd9HFE3+tn8=
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -97,6 +108,11 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k=
|
||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/jwtauth v1.1.1 h1:CtUHwzvXUfZeZSbASLgzaTZQ8mL7p+vitX59NBTL1vY=
|
||||
github.com/go-chi/jwtauth v1.1.1/go.mod h1:znOWz9e5/GfBOKiZlOUoEfjSjUF+cLZO3GcpkoGXvFI=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
@@ -104,16 +120,17 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
|
||||
github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang-migrate/migrate v1.3.2 h1:QAlFV1QF9zdkzy/jujlBVkVu+L/+k18cg8tuY1/4JDY=
|
||||
github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA=
|
||||
github.com/golang-migrate/migrate/v4 v4.14.1 h1:qmRd/rNGjM1r3Ve5gHd5ZplytrD02UcItYNxJ3iUHHE=
|
||||
github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
@@ -143,6 +160,7 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
@@ -155,6 +173,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
@@ -178,6 +197,7 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51
|
||||
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
@@ -226,6 +246,7 @@ github.com/jackc/pgconn v1.8.0 h1:FmjZ0rOyXTr1wfWs45i4a9vjnjWUAGpMuQLD9OSs+lw=
|
||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
@@ -249,7 +270,6 @@ github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkAL
|
||||
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
|
||||
github.com/jackc/pgtype v1.6.2 h1:b3pDeuhbbzBYcg5kwNmNDun4pFUD/0AAr1kLXZLeNt8=
|
||||
github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
|
||||
github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
@@ -290,6 +310,11 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4=
|
||||
github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911 h1:FvnrqecqX4zT0wOIbYK1gNgTm0677INEWiFY8UEYggY=
|
||||
github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||
github.com/lestrrat-go/jwx v1.0.6-0.20201127121120-26218808f029 h1:+HTAqhgKkKqizghOYb4uEpZ7wK8tl3Y48ZbUTHF521c=
|
||||
github.com/lestrrat-go/jwx v1.0.6-0.20201127121120-26218808f029/go.mod h1:TPF17WiSFegZo+c20fdpw49QD+/7n4/IsGvEmCSWwT0=
|
||||
github.com/lestrrat-go/pdebug v0.0.0-20200204225717-4d6bd78da58d/go.mod h1:B06CSso/AWxiPejj+fheUINGeBKeeEZNt8w+EoU7+L8=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
@@ -310,6 +335,7 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
@@ -324,6 +350,7 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
@@ -334,7 +361,9 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
||||
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
@@ -343,6 +372,7 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
|
||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -367,11 +397,13 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY=
|
||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
@@ -399,6 +431,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
@@ -510,6 +543,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d h1:dOiJ2n2cMwGLce/74I/QHMbnpk5GfY7InR8rczoMqRM=
|
||||
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -570,6 +604,7 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=
|
||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -581,6 +616,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -623,6 +659,7 @@ golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapK
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
@@ -696,6 +733,7 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200911024640-645f7a48b24f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3 h1:sg8vLDNIxFPHTchfhH1E3AI32BL3f23oie38xUWnJM8=
|
||||
google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
@@ -710,6 +748,7 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
@@ -720,6 +759,7 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -741,6 +781,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
56
main.go
56
main.go
@@ -2,54 +2,48 @@ package main
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/config"
|
||||
"gitea.deepak.science/deepak/gogmagog/db"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"gitea.deepak.science/deepak/gogmagog/store"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Config
|
||||
config.Init()
|
||||
port := config.Port.GetString()
|
||||
env := config.Environment.GetString()
|
||||
conf, err := config.GetConf("config")
|
||||
if err != nil {
|
||||
log.Println("Could not get config", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.Print("Running server on " + port)
|
||||
log.Print("App environment is " + env)
|
||||
appConf := conf.App
|
||||
port := appConf.Port
|
||||
env := appConf.Environment
|
||||
|
||||
// DB
|
||||
store := db.GetStore()
|
||||
store, err := store.GetStore(&conf.Db)
|
||||
if err != nil {
|
||||
log.Println("Could not get store", err)
|
||||
os.Exit(1)
|
||||
|
||||
}
|
||||
|
||||
if store != nil {
|
||||
log.Print("Got DB connection")
|
||||
log.Println("Got DB connection")
|
||||
}
|
||||
m := models.New(store)
|
||||
if m != nil {
|
||||
log.Print("created model")
|
||||
log.Println("created model")
|
||||
}
|
||||
|
||||
http.Handle("/static/", http.FileServer(http.Dir("public")))
|
||||
router := routes.NewRouter(m, tokens.New("my secret"))
|
||||
|
||||
http.HandleFunc("/actions/", HandleAction)
|
||||
log.Println("Running server on " + port)
|
||||
|
||||
log.Fatal(http.ListenAndServe(":"+port, nil))
|
||||
}
|
||||
|
||||
func ShowCompleteActions(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(r.URL.Path))
|
||||
}
|
||||
|
||||
func HandleAction(w http.ResponseWriter, r *http.Request) {
|
||||
var message string
|
||||
id := r.URL.Path[len("/actions/"):]
|
||||
if r.Method == "GET" {
|
||||
message = "Get the action " + id
|
||||
} else {
|
||||
message = r.Method + " the action " + id
|
||||
}
|
||||
w.Write([]byte(message))
|
||||
}
|
||||
|
||||
func Hello() string {
|
||||
return "Hello, world!\n"
|
||||
http.ListenAndServe(":"+port, router)
|
||||
|
||||
log.Println("App environment is " + env)
|
||||
}
|
||||
|
||||
10
main_test.go
10
main_test.go
@@ -1,10 +0,0 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestHello(t *testing.T) {
|
||||
want := "Hello, world!\n"
|
||||
if got := Hello(); got != want {
|
||||
t.Errorf("Hello() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,36 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Action represents a single action item.
|
||||
type Action struct {
|
||||
ID int64
|
||||
Description string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
ActionID int64 `json:"action_id"`
|
||||
ActionDescription string `json:"action_description"`
|
||||
UserID int64 `json:"user_id"`
|
||||
EstimatedChunks int `json:"estimated_chunks"`
|
||||
CompletedChunks int `json:"completed_chunks"`
|
||||
CompletedOn *time.Time `json:"completed_on,omitempty"`
|
||||
PlanID int `json:"plan_id"`
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Model) Actions() ([]*Action, error) {
|
||||
return m.SelectActions()
|
||||
// Actions returns all actions from the model.
|
||||
func (m *Model) Actions(userID int) ([]*Action, error) {
|
||||
return m.SelectActions(userID)
|
||||
}
|
||||
|
||||
// Action returns a single action from its ID
|
||||
func (m *Model) Action(id int, userID int) (*Action, error) {
|
||||
act, err := m.SelectActionByID(id, userID)
|
||||
return act, wrapNotFound(err)
|
||||
}
|
||||
|
||||
// AddAction inserts a given action into the store, returning the generated ActionID. The provided ActionID is ignored.
|
||||
func (m *Model) AddAction(action *Action, userID int) (int, error) {
|
||||
return m.InsertAction(action, userID)
|
||||
}
|
||||
|
||||
// SaveAction saves and updates an action.
|
||||
func (m *Model) SaveAction(action *Action, userID int) error {
|
||||
return m.UpdateAction(action, userID)
|
||||
}
|
||||
|
||||
23
models/current_plan.go
Normal file
23
models/current_plan.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package models
|
||||
|
||||
// CurrentPlan represents the primary plan ID for a particular user ID.
|
||||
type CurrentPlan struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
PlanID int64 `json:"plan_id"`
|
||||
}
|
||||
|
||||
// CurrentPlan returns the primary plan for a provided user ID in the given model.
|
||||
func (m *Model) CurrentPlan(userID int) (*CurrentPlan, error) {
|
||||
pp, err := m.SelectCurrentPlan(userID)
|
||||
return pp, wrapNotFound(err)
|
||||
}
|
||||
|
||||
// AddCurrentPlan inserts a given primary plan into the store, returning nothing.
|
||||
func (m *Model) AddCurrentPlan(pp *CurrentPlan, userID int) error {
|
||||
return m.InsertCurrentPlan(pp, userID)
|
||||
}
|
||||
|
||||
// SaveCurrentPlan saves and updates a primary plan.
|
||||
func (m *Model) SaveCurrentPlan(pp *CurrentPlan, userID int) error {
|
||||
return m.UpdateCurrentPlan(pp, userID)
|
||||
}
|
||||
45
models/current_plan_test.go
Normal file
45
models/current_plan_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/store"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestModelCurrentPlan(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
a1 := &models.Action{ActionID: 3}
|
||||
userID := 3
|
||||
p := &models.Plan{PlanID: 6}
|
||||
|
||||
str, _ := store.GetInMemoryStore()
|
||||
str.InsertAction(a1, userID)
|
||||
str.InsertPlan(p, userID)
|
||||
str.InsertPlan(p, userID)
|
||||
m := models.New(str)
|
||||
|
||||
_, err := m.CurrentPlan(userID)
|
||||
assert.NotNil(err)
|
||||
assert.True(models.IsNotFoundError(err))
|
||||
|
||||
err = m.AddCurrentPlan(&models.CurrentPlan{PlanID: 1}, userID)
|
||||
assert.Nil(err)
|
||||
|
||||
pp, err := m.CurrentPlan(userID)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(1, pp.PlanID)
|
||||
assert.EqualValues(userID, pp.UserID)
|
||||
|
||||
err = m.AddCurrentPlan(&models.CurrentPlan{PlanID: 2}, userID)
|
||||
assert.NotNil(err)
|
||||
|
||||
err = m.SaveCurrentPlan(&models.CurrentPlan{PlanID: 2}, userID)
|
||||
assert.Nil(err)
|
||||
|
||||
pp, err = m.CurrentPlan(userID)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(2, pp.PlanID)
|
||||
assert.EqualValues(userID, pp.UserID)
|
||||
|
||||
}
|
||||
11
models/err_model_test.go
Normal file
11
models/err_model_test.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/store"
|
||||
)
|
||||
|
||||
func getErrorModel(err error) *models.Model {
|
||||
str := store.GetErrorStoreForError(err, true)
|
||||
return models.New(str)
|
||||
}
|
||||
54
models/errors.go
Normal file
54
models/errors.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type notFoundError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e *notFoundError) NotFound() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsNotFoundError returns true if the model deems it a not found error.
|
||||
func IsNotFoundError(err error) bool {
|
||||
type notFound interface {
|
||||
NotFound() bool
|
||||
}
|
||||
te, ok := err.(notFound)
|
||||
return ok && te.NotFound()
|
||||
}
|
||||
|
||||
func wrapNotFound(err error) error {
|
||||
if err == sql.ErrNoRows {
|
||||
return ¬FoundError{error: err}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type invalidLoginError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e *invalidLoginError) InvalidLogin() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsInvalidLoginError returns true if the model deems it an invalid login error.
|
||||
func IsInvalidLoginError(err error) bool {
|
||||
type invalidLogin interface {
|
||||
InvalidLogin() bool
|
||||
}
|
||||
te, ok := err.(invalidLogin)
|
||||
return ok && te.InvalidLogin()
|
||||
}
|
||||
|
||||
func wrapInvalidLogin(err error) error {
|
||||
if err == sql.ErrNoRows || err == bcrypt.ErrMismatchedHashAndPassword {
|
||||
return &invalidLoginError{error: err}
|
||||
}
|
||||
return err
|
||||
}
|
||||
49
models/errors_test.go
Normal file
49
models/errors_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRandomErrNotNotFound(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
err := fmt.Errorf("example")
|
||||
|
||||
assert.False(models.IsNotFoundError(err))
|
||||
}
|
||||
|
||||
type MyError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e *MyError) NotFound() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func TestCustomInterface(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
err := &MyError{fmt.Errorf("example")}
|
||||
|
||||
assert.True(models.IsNotFoundError(err))
|
||||
}
|
||||
func TestErrorModelWrapping(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
m := getErrorModel(sql.ErrNoRows)
|
||||
|
||||
_, err := m.Plan(0, 0)
|
||||
assert.True(models.IsNotFoundError(err))
|
||||
_, err = m.Action(0, 0)
|
||||
assert.True(models.IsNotFoundError(err))
|
||||
}
|
||||
func TestErrorModelInvalidLogin(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
m := getErrorModel(sql.ErrNoRows)
|
||||
|
||||
_, err := m.VerifyUserByUsernamePassword("duck", "duck")
|
||||
assert.True(models.IsInvalidLoginError(err))
|
||||
}
|
||||
@@ -1,13 +1,43 @@
|
||||
package models
|
||||
|
||||
type store interface {
|
||||
SelectActions() ([]*Action, error)
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Store represents the backing store.
|
||||
type Store interface {
|
||||
ConnectionLive() error
|
||||
SelectActions(userID int) ([]*Action, error)
|
||||
SelectActionByID(id int, userID int) (*Action, error)
|
||||
InsertAction(action *Action, userID int) (int, error)
|
||||
UpdateAction(action *Action, userID int) error
|
||||
SelectPlans(userID int) ([]*Plan, error)
|
||||
SelectPlanByID(id int, userID int) (*Plan, error)
|
||||
InsertPlan(plan *Plan, userID int) (int, error)
|
||||
UpdatePlan(plan *Plan, userID int) error
|
||||
SelectActionsByPlanID(plan *Plan, userID int) ([]*Action, error)
|
||||
SelectUserByUsername(username string) (*User, error)
|
||||
InsertUser(user *User) (int, error)
|
||||
SelectCurrentPlan(userID int) (*CurrentPlan, error)
|
||||
InsertCurrentPlan(currentPlan *CurrentPlan, userID int) error
|
||||
UpdateCurrentPlan(currentPlan *CurrentPlan, userID int) error
|
||||
}
|
||||
|
||||
// Model represents a current model item.
|
||||
type Model struct {
|
||||
store
|
||||
Store
|
||||
}
|
||||
|
||||
// New creates an instance of the model using the passed in store.
|
||||
func New(store Store) *Model {
|
||||
return &Model{Store: store}
|
||||
}
|
||||
|
||||
// Healthy returns an error if the connection is healthy.
|
||||
// Wrapper over db.Ping()
|
||||
func (m *Model) Healthy() error {
|
||||
if m.Store == nil {
|
||||
return fmt.Errorf("No store available")
|
||||
}
|
||||
return m.ConnectionLive()
|
||||
}
|
||||
|
||||
func New(store store) *Model {
|
||||
return &Model{store: store}
|
||||
}
|
||||
|
||||
91
models/models_test.go
Normal file
91
models/models_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/store"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestModelActions(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
a1 := &models.Action{ActionID: 3}
|
||||
a2 := &models.Action{ActionID: 4}
|
||||
userID := 3
|
||||
p := &models.Plan{PlanID: 6}
|
||||
|
||||
str, _ := store.GetInMemoryStore()
|
||||
str.InsertAction(a1, userID)
|
||||
str.InsertPlan(p, userID)
|
||||
m := models.New(str)
|
||||
|
||||
actions, err := m.Actions(userID)
|
||||
assert.Nil(err)
|
||||
assert.Equal(1, len(actions))
|
||||
|
||||
firstAction, err := m.Action(1, userID)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(1, firstAction.ActionID)
|
||||
|
||||
actionID, err := m.AddAction(a2, userID)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(2, actionID)
|
||||
|
||||
err = m.SaveAction(a1, userID)
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestModelPlanMethods(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
userID := 3
|
||||
a1 := &models.Action{ActionID: 3, PlanID: 1}
|
||||
a2 := &models.Action{ActionID: 4}
|
||||
p := &models.Plan{}
|
||||
|
||||
str, _ := store.GetInMemoryStore()
|
||||
str.InsertPlan(p, userID)
|
||||
str.InsertAction(a1, userID)
|
||||
str.InsertAction(a2, userID)
|
||||
m := models.New(str)
|
||||
|
||||
plans, err := m.Plans(userID)
|
||||
assert.Nil(err)
|
||||
assert.Equal(1, len(plans))
|
||||
|
||||
firstPlan, err := m.Plan(1, userID)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(1, firstPlan.PlanID)
|
||||
|
||||
p2 := &models.Plan{PlanDescription: "testing", PlanID: 1}
|
||||
m.SavePlan(p2, userID)
|
||||
p2, err = m.Plan(1, userID)
|
||||
assert.Nil(err)
|
||||
assert.Equal("testing", p2.PlanDescription)
|
||||
|
||||
actions, err := m.GetActions(firstPlan, userID)
|
||||
assert.Nil(err)
|
||||
assert.Equal(1, len(actions))
|
||||
|
||||
planId, err := m.AddPlan(&models.Plan{}, userID)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(2, planId)
|
||||
}
|
||||
|
||||
func TestModelHealthy(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
str, _ := store.GetInMemoryStore()
|
||||
m := models.New(str)
|
||||
|
||||
err := m.Healthy()
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestNilModelUnhealthy(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
m := models.New(nil)
|
||||
|
||||
err := m.Healthy()
|
||||
assert.NotNil(err)
|
||||
}
|
||||
34
models/plan.go
Normal file
34
models/plan.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package models
|
||||
|
||||
// Plan represents a single day's agenda of actions.
|
||||
type Plan struct {
|
||||
PlanID int64 `json:"plan_id"`
|
||||
PlanDescription string `json:"plan_description"`
|
||||
UserID int64 `json:"user_id"`
|
||||
}
|
||||
|
||||
// Plans returns all plans in the model.
|
||||
func (m *Model) Plans(userID int) ([]*Plan, error) {
|
||||
return m.SelectPlans(userID)
|
||||
}
|
||||
|
||||
// Plan returns a single plan from the store by plan_id.
|
||||
func (m *Model) Plan(id int, userID int) (*Plan, error) {
|
||||
plan, err := m.SelectPlanByID(id, userID)
|
||||
return plan, wrapNotFound(err)
|
||||
}
|
||||
|
||||
// AddPlan inserts a given plan into the store, returning the generated PlanID. The provided PlanID is ignored.
|
||||
func (m *Model) AddPlan(plan *Plan, userID int) (int, error) {
|
||||
return m.InsertPlan(plan, userID)
|
||||
}
|
||||
|
||||
// SavePlan saves and updates a plan.
|
||||
func (m *Model) SavePlan(plan *Plan, userID int) error {
|
||||
return m.UpdatePlan(plan, userID)
|
||||
}
|
||||
|
||||
// GetActions returns the actions associated with a particular plan.
|
||||
func (m *Model) GetActions(plan *Plan, userID int) ([]*Action, error) {
|
||||
return m.SelectActionsByPlanID(plan, userID)
|
||||
}
|
||||
96
models/user.go
Normal file
96
models/user.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// User represents the full DB user field, for inserts and compares.
|
||||
// No reason to return the hashed pw on the route though.
|
||||
type User struct {
|
||||
UserID int64
|
||||
Username string
|
||||
DisplayName string
|
||||
Password []byte
|
||||
}
|
||||
|
||||
// UserNoPassword contains the non password user fields.
|
||||
// This is preferred outside of the model / store.
|
||||
type UserNoPassword struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
DisplayName string `json:"display_name"`
|
||||
}
|
||||
|
||||
// VerifyUserByUsernamePassword returns a single user by the unique username, if the provided password is correct.
|
||||
func (m *Model) VerifyUserByUsernamePassword(username string, password string) (*UserNoPassword, error) {
|
||||
user, err := m.SelectUserByUsername(username)
|
||||
if err != nil {
|
||||
// throwaway to pad time
|
||||
hashPassword(username)
|
||||
return nil, wrapInvalidLogin(err)
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword(user.Password, []byte(password))
|
||||
if err != nil {
|
||||
return nil, wrapInvalidLogin(err)
|
||||
}
|
||||
|
||||
return user.NoPassword(), nil
|
||||
}
|
||||
|
||||
// NoPassword strips the user of password.
|
||||
func (u *User) NoPassword() *UserNoPassword {
|
||||
return &UserNoPassword{
|
||||
UserID: u.UserID,
|
||||
Username: u.Username,
|
||||
DisplayName: u.DisplayName,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateUserRequest represents a desired user creation.
|
||||
type CreateUserRequest struct {
|
||||
Username string `json:"username"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// CreateUser takes in a create user request and returns the ID of the newly created user.
|
||||
func (m *Model) CreateUser(req *CreateUserRequest) (int, error) {
|
||||
if req.Username == "" {
|
||||
return -1, fmt.Errorf("No username provided")
|
||||
}
|
||||
if req.Password == "" {
|
||||
return -1, fmt.Errorf("No password provided")
|
||||
}
|
||||
hash, err := hashPassword(req.Password)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
desiredUser := &User{
|
||||
Username: req.Username,
|
||||
DisplayName: req.DisplayName,
|
||||
Password: hash,
|
||||
}
|
||||
|
||||
return m.InsertUser(desiredUser)
|
||||
}
|
||||
|
||||
// UserByUsername retrieves a single username from the store, verifying the passed in userID.
|
||||
func (m *Model) UserByUsername(username string, userID int) (*UserNoPassword, error) {
|
||||
user, err := m.SelectUserByUsername(username)
|
||||
if user == nil {
|
||||
return nil, wrapNotFound(err)
|
||||
}
|
||||
if int(user.UserID) != userID {
|
||||
return nil, ¬FoundError{error: fmt.Errorf("provided userID does not match the retrieved user")}
|
||||
}
|
||||
return user.NoPassword(), wrapNotFound(err)
|
||||
}
|
||||
|
||||
// hashPassword hashes a password
|
||||
func hashPassword(password string) ([]byte, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 11)
|
||||
return bytes, err
|
||||
}
|
||||
104
models/user_test.go
Normal file
104
models/user_test.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/store"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestModelUsers(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
a1 := &models.Action{ActionID: 3}
|
||||
a2 := &models.Action{ActionID: 4}
|
||||
p := &models.Plan{PlanID: 6}
|
||||
|
||||
username := "test1"
|
||||
// password := password
|
||||
user1 := &models.User{Username: username, DisplayName: "Ted Est", Password: []byte("$2y$05$6SVV35GX4cB4PDPhRaDD/exsL.HV8QtMMr60YL6dLyqtX4l58q.cy")}
|
||||
str, _ := store.GetInMemoryStore()
|
||||
str.InsertPlan(p, 3)
|
||||
str.InsertAction(a1, 3)
|
||||
str.InsertAction(a2, 3)
|
||||
str.InsertUser(user1)
|
||||
m := models.New(str)
|
||||
|
||||
userNoPass, err := m.UserByUsername("test1", 1)
|
||||
assert.Nil(err)
|
||||
assert.NotNil(userNoPass)
|
||||
|
||||
userNoPass, err = m.UserByUsername("test1", 2)
|
||||
assert.NotNil(err)
|
||||
assert.True(models.IsNotFoundError(err))
|
||||
assert.Nil(userNoPass)
|
||||
|
||||
userNoPass, err = m.UserByUsername("test2", 2)
|
||||
assert.NotNil(err)
|
||||
assert.True(models.IsNotFoundError(err))
|
||||
assert.Nil(userNoPass)
|
||||
|
||||
user, err := m.VerifyUserByUsernamePassword("test1", "password")
|
||||
assert.Nil(err)
|
||||
assert.NotNil(user)
|
||||
|
||||
user, err = m.VerifyUserByUsernamePassword("test1", "wrong_password")
|
||||
assert.NotNil(err)
|
||||
assert.Nil(user)
|
||||
|
||||
user, err = m.VerifyUserByUsernamePassword("test2", "password")
|
||||
assert.NotNil(err)
|
||||
assert.Nil(user)
|
||||
}
|
||||
|
||||
func TestErrorUsers(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
m := getErrorModel(fmt.Errorf("err"))
|
||||
|
||||
user, err := m.VerifyUserByUsernamePassword("snth", "aoeu")
|
||||
assert.Nil(user)
|
||||
assert.NotNil(err)
|
||||
}
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
username := "test"
|
||||
displayName := "Ted Est"
|
||||
pass := "abc"
|
||||
u := &models.CreateUserRequest{Username: username, DisplayName: displayName, Password: pass}
|
||||
|
||||
str, _ := store.GetInMemoryStore()
|
||||
m := models.New(str)
|
||||
|
||||
id, err := m.CreateUser(u)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(1, id)
|
||||
}
|
||||
func TestCreateUserFailValidation(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
username := ""
|
||||
displayName := "Ted Est"
|
||||
pass := "abc"
|
||||
u := &models.CreateUserRequest{Username: username, DisplayName: displayName, Password: pass}
|
||||
|
||||
str, _ := store.GetInMemoryStore()
|
||||
m := models.New(str)
|
||||
|
||||
_, err := m.CreateUser(u)
|
||||
assert.NotNil(err)
|
||||
}
|
||||
|
||||
func TestCreateUserFailValidationPassword(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
username := "aoeu"
|
||||
displayName := "Ted Est"
|
||||
pass := ""
|
||||
u := &models.CreateUserRequest{Username: username, DisplayName: displayName, Password: pass}
|
||||
|
||||
str, _ := store.GetInMemoryStore()
|
||||
m := models.New(str)
|
||||
|
||||
_, err := m.CreateUser(u)
|
||||
assert.NotNil(err)
|
||||
}
|
||||
2
public/static/css/bootstrap-glyphicons.css
vendored
2
public/static/css/bootstrap-glyphicons.css
vendored
File diff suppressed because one or more lines are too long
4281
public/static/css/bootstrap.min.css
vendored
4281
public/static/css/bootstrap.min.css
vendored
File diff suppressed because it is too large
Load Diff
2104
public/static/css/font-awesome.min.css
vendored
2104
public/static/css/font-awesome.min.css
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,52 +0,0 @@
|
||||
.navbar-fixed-top + .sidebar-trigger .sidebar-toggle,
|
||||
.navbar-fixed-bottom + .sidebar-trigger .sidebar-toggle,
|
||||
.navbar-fixed-top + .sidebar-trigger + .sidebar-trigger .sidebar-toggle,
|
||||
.navbar-fixed-bottom + .sidebar-trigger + .sidebar-trigger .sidebar-toggle {
|
||||
position: fixed;
|
||||
z-index: 1032;
|
||||
}
|
||||
.navbar-fixed-top + .sidebar-trigger .sidebar-wrapper,
|
||||
.navbar-fixed-bottom + .sidebar-trigger .sidebar-wrapper,
|
||||
.navbar-fixed-top + .sidebar-trigger + .sidebar-trigger .sidebar-wrapper,
|
||||
.navbar-fixed-bottom + .sidebar-trigger + .sidebar-trigger .sidebar-wrapper {
|
||||
z-index: 1033;
|
||||
}
|
||||
.navbar-fixed-bottom + .sidebar-trigger .sidebar-toggle,
|
||||
.navbar-fixed-bottom + .sidebar-trigger + .sidebar-trigger .sidebar-toggle {
|
||||
top: inherit;
|
||||
bottom: 0;
|
||||
}
|
||||
.navbar-fixed-top + .container,
|
||||
.navbar-fixed-top + .container-fluid,
|
||||
.navbar-fixed-top + .sidebar-trigger + .container,
|
||||
.navbar-fixed-top + .sidebar-trigger + .container-fluid,
|
||||
.navbar-fixed-top + .sidebar-trigger + .sidebar-trigger + .container,
|
||||
.navbar-fixed-top + .sidebar-trigger + .sidebar-trigger + .container-fluid {
|
||||
margin-top: 70px;
|
||||
}
|
||||
.navbar-fixed-bottom + .container,
|
||||
.navbar-fixed-bottom + .container-fluid,
|
||||
.navbar-fixed-bottom + .sidebar-trigger + .container,
|
||||
.navbar-fixed-bottom + .sidebar-trigger + .container-fluid,
|
||||
.navbar-fixed-bottom + .sidebar-trigger + .sidebar-trigger + .container,
|
||||
.navbar-fixed-bottom + .sidebar-trigger + .sidebar-trigger + .container-fluid {
|
||||
margin-bottom: 70px;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.sidebar-force-open:not(.sidebar-right) + .container,
|
||||
.sidebar-force-open:not(.sidebar-right) + .sidebar-trigger + .container {
|
||||
padding-left: 225px;
|
||||
}
|
||||
.sidebar-force-open:not(.sidebar-right) + .container-fluid,
|
||||
.sidebar-force-open:not(.sidebar-right) + .sidebar-trigger + .container-fluid {
|
||||
margin-left: 210px;
|
||||
}
|
||||
.sidebar-force-open.sidebar-right + .container,
|
||||
.sidebar-force-open.sidebar-right + .sidebar-trigger + .container {
|
||||
padding-right: 225px;
|
||||
}
|
||||
.sidebar-force-open.sidebar-right + .container-fluid,
|
||||
.sidebar-force-open.sidebar-right + .sidebar-trigger + .container-fluid {
|
||||
margin-right: 210px;
|
||||
}
|
||||
}
|
||||
@@ -1,336 +0,0 @@
|
||||
.sidebar-toggle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 51px;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 17px 20px 17px 0;
|
||||
}
|
||||
.sidebar-toggle .glyphicon{
|
||||
color:white;
|
||||
}
|
||||
.sidebar-toggle > i {
|
||||
font-size: 18px;
|
||||
margin: 0 0 0 -5px;
|
||||
-webkit-transition: all 0.1s ease-in-out;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
.sidebar-toggle > .fa-sidebar-toggle:before {
|
||||
content: "\f0c9";
|
||||
}
|
||||
.sidebar-toggle:hover > i,
|
||||
.sidebar-toggle.sidebar-toggle-opened > i {
|
||||
margin-left: -9px;
|
||||
}
|
||||
.sidebar-toggle + .navbar-brand > img {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.sidebar-togglable .sidebar-toggle {
|
||||
display: block;
|
||||
}
|
||||
.sidebar-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
overflow: hidden;
|
||||
bottom: 0;
|
||||
width: 210px;
|
||||
cursor: default;
|
||||
-moz-user-select: -moz-none;
|
||||
user-select: none;
|
||||
-webkit-transform: translate3d(-210px, 0px, 0px);
|
||||
transform: translate3d(-210px, 0px, 0px);
|
||||
}
|
||||
.sidebar-wrapper.sidebar-ready {
|
||||
-webkit-transition: -webkit-transform 0.2s;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-open {
|
||||
-webkit-transform: translate3d(0px, 0px, 0px);
|
||||
transform: translate3d(0px, 0px, 0px);
|
||||
}
|
||||
.sidebar-wrapper .sidebar-scroller {
|
||||
position: absolute;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
left: 0;
|
||||
right: -18px;
|
||||
height: 100%;
|
||||
}
|
||||
.sidebar-wrapper .sidebar-menu {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-x: hidden;
|
||||
list-style: none;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
}
|
||||
.sidebar-wrapper .sidebar-menu ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.sidebar-wrapper .sidebar-menu li {
|
||||
display: block;
|
||||
}
|
||||
.sidebar-wrapper .sidebar-group > span,
|
||||
.sidebar-wrapper .sidebar-item > a {
|
||||
display: block;
|
||||
height: 100%;
|
||||
padding: 12px 15px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.sidebar-wrapper .sidebar-item > a.active {
|
||||
border-left: 5px solid;
|
||||
padding: 12px 15px 12px 10px;
|
||||
}
|
||||
.sidebar-wrapper .sidebar-item.sidebar-item-mini {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
.sidebar-wrapper .sidebar-item.sidebar-item-mini > a {
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
.sidebar-wrapper .sidebar-group {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.sidebar-wrapper .sidebar-group:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.sidebar-wrapper .sidebar-group > span {
|
||||
font-family: inherit;
|
||||
font-size: 24px;
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
.sidebar-wrapper .sidebar-group.sticky-header {
|
||||
position: absolute;
|
||||
height: auto;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
margin-top: 0;
|
||||
}
|
||||
.sidebar-wrapper .sidebar-group + .sidebar-item {
|
||||
margin-top: 32px;
|
||||
border-top: 1px solid transparent;
|
||||
}
|
||||
.sidebar-wrapper .sidebar-item > a {
|
||||
cursor: pointer;
|
||||
}
|
||||
.sidebar-swipe {
|
||||
position: fixed;
|
||||
z-index: 1001;
|
||||
width: 20px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
-moz-user-select: -moz-none;
|
||||
user-select: none;
|
||||
}
|
||||
.sidebar-open + .sidebar-swipe,
|
||||
.sidebar-force-open .sidebar-swipe {
|
||||
left: 210px;
|
||||
}
|
||||
.sidebar-trigger .sidebar-toggle {
|
||||
z-index: 1002;
|
||||
}
|
||||
.sidebar-trigger .sidebar-wrapper {
|
||||
z-index: 1003;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-default {
|
||||
color: #222222;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-default.sidebar-open,
|
||||
.sidebar-wrapper.sidebar-default.sidebar-dragging {
|
||||
-webkit-box-shadow: 3px 0 4px rgba(0, 0, 0, 0.18);
|
||||
box-shadow: 3px 0 4px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
.sidebar-wrapper.sidebar-default .sidebar-group > span {
|
||||
color: #dbdbdb;
|
||||
border-bottom-color: #eeeeee;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-default .sidebar-group.sticky-header > span {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-default .sidebar-group + .sidebar-item {
|
||||
border-top-color: #eeeeee;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-default .sidebar-item > a {
|
||||
color: #222222;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-default .sidebar-item > a:hover,
|
||||
.sidebar-wrapper.sidebar-default .sidebar-item > a:focus {
|
||||
color: #333333;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-default .sidebar-item > a.active {
|
||||
color: #337ab7;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-default .sidebar-item.sidebar-item-mini > a {
|
||||
color: #747474;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-default .hammer-scrollbar {
|
||||
background-color: #555555;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-inverse {
|
||||
color: #9d9d9d;
|
||||
background-color: #2a3542;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-inverse.sidebar-open,
|
||||
.sidebar-wrapper.sidebar-inverse.sidebar-dragging {
|
||||
-webkit-box-shadow: 3px 0 4px rgba(0, 0, 0, 0.32);
|
||||
box-shadow: 3px 0 4px rgba(0, 0, 0, 0.32);
|
||||
}
|
||||
.sidebar-wrapper.sidebar-inverse .sidebar-group > span {
|
||||
color: #46586e;
|
||||
border-bottom-color: #344252;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-inverse .sidebar-group.sticky-header > span {
|
||||
background-color: #2a3542;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-inverse .sidebar-group + .sidebar-item {
|
||||
border-top-color: #344252;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-inverse .sidebar-item > a {
|
||||
color: #9d9d9d;
|
||||
background-color: #2a3542;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-inverse .sidebar-item > a:hover,
|
||||
.sidebar-wrapper.sidebar-inverse .sidebar-item > a:focus {
|
||||
color: #ffffff;
|
||||
background-color: #344252;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-inverse .sidebar-item > a.active {
|
||||
color: #dfecf6;
|
||||
background-color: #2a3542;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-inverse .sidebar-item.sidebar-item-mini > a {
|
||||
color: #6d85a2;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-inverse .hammer-scrollbar {
|
||||
background-color: #e4e8ed;
|
||||
}
|
||||
.navbar-default + .sidebar-trigger .sidebar-toggle > i,
|
||||
.navbar-default + .sidebar-trigger + .sidebar-trigger .sidebar-toggle > i {
|
||||
color: #888888;
|
||||
}
|
||||
.navbar-default + .sidebar-trigger .sidebar-toggle:hover > i,
|
||||
.navbar-default + .sidebar-trigger + .sidebar-trigger .sidebar-toggle:hover > i {
|
||||
color: #333333;
|
||||
}
|
||||
.navbar-inverse + .sidebar-trigger .sidebar-toggle > i,
|
||||
.navbar-inverse + .sidebar-trigger + .sidebar-trigger .sidebar-toggle > i {
|
||||
color: #ffffff;
|
||||
}
|
||||
.navbar-inverse + .sidebar-trigger .sidebar-toggle:hover > i,
|
||||
.navbar-inverse + .sidebar-trigger + .sidebar-trigger .sidebar-toggle:hover > i {
|
||||
color: #ffffff;
|
||||
}
|
||||
.sidebar-right .sidebar-toggle {
|
||||
left: auto;
|
||||
right: 0;
|
||||
padding-left: 20px;
|
||||
padding-right: 0;
|
||||
}
|
||||
.sidebar-right .sidebar-toggle > i {
|
||||
margin-left: 0;
|
||||
margin-right: -5px;
|
||||
}
|
||||
.sidebar-right .sidebar-toggle:hover > i,
|
||||
.sidebar-right .sidebar-toggle.sidebar-toggle-opened > i {
|
||||
margin-right: -9px;
|
||||
}
|
||||
.sidebar-right .sidebar-wrapper {
|
||||
right: 0;
|
||||
-webkit-transform: translate3d(210px, 0px, 0px);
|
||||
transform: translate3d(210px, 0px, 0px);
|
||||
}
|
||||
.sidebar-right .sidebar-wrapper.sidebar-open {
|
||||
-webkit-transform: translate3d(0px, 0px, 0px);
|
||||
transform: translate3d(0px, 0px, 0px);
|
||||
}
|
||||
.sidebar-right .sidebar-wrapper.sidebar-default.sidebar-open,
|
||||
.sidebar-right .sidebar-wrapper.sidebar-default.sidebar-dragging {
|
||||
-webkit-box-shadow: -3px 0 4px rgba(0, 0, 0, 0.18);
|
||||
box-shadow: -3px 0 4px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
.sidebar-right .sidebar-wrapper.sidebar-inverse.sidebar-open,
|
||||
.sidebar-right .sidebar-wrapper.sidebar-inverse.sidebar-dragging {
|
||||
-webkit-box-shadow: -3px 0 4px rgba(0, 0, 0, 0.32);
|
||||
box-shadow: -3px 0 4px rgba(0, 0, 0, 0.32);
|
||||
}
|
||||
.sidebar-right .sidebar-wrapper .sidebar-item > a.active {
|
||||
border-left: inherit;
|
||||
border-right: 5px solid;
|
||||
padding: 12px 10px 12px 15px;
|
||||
}
|
||||
.sidebar-right .sidebar-swipe {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
.sidebar-right .sidebar-open + .sidebar-swipe,
|
||||
.sidebar-right .sidebar-force-open .sidebar-swipe {
|
||||
right: 210px;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.sidebar-wrapper {
|
||||
width: 80%;
|
||||
-webkit-transform: translate3d(-100%, 0px, 0px);
|
||||
transform: translate3d(-100%, 0px, 0px);
|
||||
}
|
||||
.sidebar-open + .sidebar-swipe,
|
||||
.sidebar-force-open .sidebar-open + .sidebar-swipe {
|
||||
left: 80%;
|
||||
}
|
||||
.sidebar-force-open .sidebar-swipe {
|
||||
left: 0;
|
||||
}
|
||||
.sidebar-right .sidebar-toggle {
|
||||
padding-left: 7px;
|
||||
}
|
||||
.sidebar-right .sidebar-wrapper {
|
||||
-webkit-transform: translate3d(100%, 0px, 0px);
|
||||
transform: translate3d(100%, 0px, 0px);
|
||||
}
|
||||
.sidebar-right .sidebar-open + .sidebar-swipe {
|
||||
left: auto;
|
||||
right: 80%;
|
||||
}
|
||||
.sidebar-right .sidebar-force-open .sidebar-swipe {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
.sidebar-force-open .sidebar-wrapper:not(.sidebar-open) + .sidebar-swipe {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.sidebar-trigger.sidebar-locked .sidebar-toggle {
|
||||
display: none;
|
||||
}
|
||||
.sidebar-trigger.sidebar-locked .sidebar-wrapper {
|
||||
margin-top: 51px;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-open-init {
|
||||
-webkit-transform: translate3d(0px, 0px, 0px);
|
||||
transform: translate3d(0px, 0px, 0px);
|
||||
}
|
||||
.sidebar-force-open .sidebar-wrapper.sidebar-open,
|
||||
.sidebar-force-open.sidebar-right .sidebar-wrapper.sidebar-open,
|
||||
.sidebar-force-open .sidebar-wrapper.sidebar-dragging,
|
||||
.sidebar-force-open.sidebar-right .sidebar-wrapper.sidebar-dragging {
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
.sidebar-wrapper {
|
||||
-ms-touch-action: none;
|
||||
}
|
||||
.sidebar-swipe {
|
||||
-ms-touch-action: none;
|
||||
}
|
||||
@@ -1,412 +0,0 @@
|
||||
/*
|
||||
|
||||
omninotesweb main stylesheet
|
||||
=============
|
||||
|
||||
Author: Suraj Patil
|
||||
Updated: January 13, 2014
|
||||
Notes: omninotesweb
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/*--------------------------------------
|
||||
Layout
|
||||
-------------------------------------- */
|
||||
|
||||
.commentslist {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.comment {
|
||||
padding-left: 25px;
|
||||
padding-right: 25px;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
ul{
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.timestamp{
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.badge{
|
||||
background-color: #1a78c9;
|
||||
margin-right:10px;
|
||||
float:right;
|
||||
}
|
||||
|
||||
.center{
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.navbar-brand:hover{
|
||||
color:white;
|
||||
}
|
||||
|
||||
.footer{
|
||||
height:30px;
|
||||
width:auto;
|
||||
text-align:center;
|
||||
|
||||
}
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.floating-action-icon{
|
||||
position: fixed;
|
||||
bottom:40px;
|
||||
right:30px;
|
||||
z-index:101;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
font-size: 20px;
|
||||
border-radius: 35px;
|
||||
box-shadow:0 0 4px #111;
|
||||
padding-top:10px !important;
|
||||
}
|
||||
|
||||
.notification{
|
||||
width: -moz-fit-content;
|
||||
position: fixed;
|
||||
right: 10px;
|
||||
max-width: 400px;
|
||||
padding: 12px;
|
||||
background-color: #F2FCFF;
|
||||
box-shadow: 1px 0px 9px 0px rgba(27, 123, 216, 0.4);
|
||||
border: 1px solid #5596CE;
|
||||
border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
z-index: 1100;
|
||||
max-height: 90px;
|
||||
margin-top: 20px;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
#btnMessage {
|
||||
padding: 1px 12px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
float: right;
|
||||
}
|
||||
|
||||
/*--------------------------------------
|
||||
Navbar
|
||||
-------------------------------------- */
|
||||
|
||||
.mainHeader{
|
||||
background-color: #3f51b5;
|
||||
border-radius:0px;
|
||||
|
||||
}
|
||||
|
||||
.navbar-brand{
|
||||
margin-left: 25px;
|
||||
font-size:1.5em;
|
||||
color:white;
|
||||
max-width: 2000px;
|
||||
}
|
||||
|
||||
.btn-action{
|
||||
background-color: #3f51b5;
|
||||
}
|
||||
|
||||
#icons{
|
||||
text-align:right;
|
||||
float: right;
|
||||
height: 50%;
|
||||
line-height: 52px;
|
||||
float:right;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
nav .glyphicon{
|
||||
color:white;
|
||||
}
|
||||
|
||||
nav .glyphicon:hover{
|
||||
color:black;
|
||||
}
|
||||
|
||||
.sr-only{
|
||||
display:block;
|
||||
width:20px;
|
||||
height:3px;
|
||||
background-color: white;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.hidden{
|
||||
display:none;
|
||||
}
|
||||
|
||||
/*--------------------------------------
|
||||
NotesFeed
|
||||
-------------------------------------- */
|
||||
.overdue {
|
||||
color: red;
|
||||
margin-top: 1px;
|
||||
font-size: 12px;
|
||||
padding-top: -100px;
|
||||
position: absolute;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.noteHeading {
|
||||
font-weight:900;
|
||||
font-size:17px;
|
||||
color:#666666;
|
||||
margin-bottom:0px;
|
||||
padding-bottom:5px;
|
||||
}
|
||||
hr{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
}
|
||||
|
||||
.noteHeading a {
|
||||
font-weight:400;
|
||||
font-size:11px;
|
||||
}
|
||||
|
||||
.noteContent {
|
||||
padding-top: 7px;
|
||||
font-size: 0.9em;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.noteContent img {
|
||||
width: 90%;
|
||||
padding-left: 10%;
|
||||
}
|
||||
|
||||
.toggle{
|
||||
cursor: pointer;
|
||||
margin-top:-30px;
|
||||
float:right;
|
||||
}
|
||||
|
||||
.notefooter{
|
||||
color:#aaa;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* These are the classes that are going to be applied: */
|
||||
.column {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.note{
|
||||
display: block;
|
||||
margin: 4px;
|
||||
margin-bottom: 6px;
|
||||
padding: 15px;
|
||||
background-color: white;
|
||||
box-shadow:1px 1px 2px 1px #7D7470;
|
||||
border-radius:4px 4px 4px 4px;
|
||||
height: auto;
|
||||
|
||||
}
|
||||
|
||||
.note:hover{
|
||||
border-bottom: 2px solid black;
|
||||
}
|
||||
.menu a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.menu li{
|
||||
margin-top: -10px;
|
||||
list-style: none;
|
||||
float: right;
|
||||
padding: 5px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
/*--------------------------------------
|
||||
Navigation drawer
|
||||
-------------------------------------- */
|
||||
|
||||
.list-group-item{
|
||||
border:none;
|
||||
}
|
||||
|
||||
.sidebar-item h5{
|
||||
margin-left:15px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar-item .glyphicon:hover{
|
||||
color:black;
|
||||
}
|
||||
|
||||
|
||||
.col-sm-2 a.list-group-item:hover,a.list-group-item:focus{
|
||||
text-decoration:none;
|
||||
background-color: transparent;
|
||||
|
||||
}
|
||||
|
||||
body{
|
||||
background-color: #f5f5f5;
|
||||
overflow-x:hidden;
|
||||
overflow-y:auto;
|
||||
margin-top:70px;
|
||||
}
|
||||
|
||||
.nav-item{
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.col-sm-2{
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.badge{
|
||||
background-color: #7D8EF0;
|
||||
margin-right:10px;
|
||||
float:right;
|
||||
}
|
||||
|
||||
/* --------------------------------------
|
||||
Global Styles
|
||||
-------------------------------------- */
|
||||
body{
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.main-content{
|
||||
color:black;
|
||||
}
|
||||
|
||||
.highlight{
|
||||
background-color: #72CBFF;
|
||||
}
|
||||
/* --------------------------------------
|
||||
Media Queries
|
||||
-------------------------------------- */
|
||||
|
||||
/* Portrait & landscape phone */
|
||||
@media (max-width: 480px) {
|
||||
body{
|
||||
margin-top:105px;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#icons{
|
||||
text-align:center;
|
||||
padding:0;
|
||||
height: 50%;
|
||||
line-height: 52px;
|
||||
float:right;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.mainHeader h3{
|
||||
padding:0px;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
|
||||
.note-sm{
|
||||
height:150px;
|
||||
}
|
||||
|
||||
.floating-action-icon{
|
||||
position: fixed;
|
||||
bottom:35px;
|
||||
right:20px;
|
||||
z-index:101;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
padding: 10px 10px;
|
||||
font-size: 24px;
|
||||
border-radius: 25px;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.modal-dialog{
|
||||
width:240px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Landscape phone to portrait tablet */
|
||||
@media (max-width: 768px) and (min-width:481px) {
|
||||
#icons{
|
||||
text-align:center;
|
||||
padding:0;
|
||||
height: 50%;
|
||||
line-height: 52px;
|
||||
float:right;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
body{
|
||||
margin-top:105px;
|
||||
}
|
||||
.timeline {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.mainHeader h3{
|
||||
padding:0px;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.note-lg{
|
||||
height:360px;
|
||||
/*change later*/
|
||||
}
|
||||
.modal-dialog{
|
||||
width:250px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Large desktop */
|
||||
@media (max-width: 1400px) and (min-width:769px) {
|
||||
.timeline {
|
||||
overflow: hidden;
|
||||
width: 55%;
|
||||
margin-left:20%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*--------------------------------------
|
||||
Sidebar
|
||||
-------------------------------------- */
|
||||
.sidebar-toggle{
|
||||
left:10px !important;
|
||||
}
|
||||
.sidebar-wrapper.sidebar-default.sidebar-open, .sidebar-wrapper.sidebar-default.sidebar-dragging{
|
||||
width:260px;
|
||||
}
|
||||
|
||||
/*Modal dialog*/
|
||||
|
||||
.modal-footer {
|
||||
padding: 10px 15px 15px;
|
||||
margin:0;
|
||||
border:none;
|
||||
}
|
||||
|
||||
.modal-body{
|
||||
padding: 20px 20px 10px 20px;
|
||||
}
|
||||
|
||||
.modal{
|
||||
overflow: hidden;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,175 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata></metadata>
|
||||
<defs>
|
||||
<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
|
||||
<font-face units-per-em="1200" ascent="960" descent="-240" />
|
||||
<missing-glyph horiz-adv-x="500" />
|
||||
<glyph />
|
||||
<glyph />
|
||||
<glyph unicode="
" />
|
||||
<glyph unicode=" " />
|
||||
<glyph unicode="*" d="M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z" />
|
||||
<glyph unicode="+" d="M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z" />
|
||||
<glyph unicode="€" d="M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-63 -32t-65.5 -67t-50 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-207 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z" />
|
||||
<glyph unicode="−" d="M200 400h900v300h-900v-300z" />
|
||||
<glyph unicode="✉" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" />
|
||||
<glyph unicode="✏" d="M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z" />
|
||||
<glyph unicode="" horiz-adv-x="500" d="M0 0z" />
|
||||
<glyph unicode="" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" />
|
||||
<glyph unicode="" d="M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q18 -55 86 -75.5t147 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z" />
|
||||
<glyph unicode="" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" />
|
||||
<glyph unicode="" d="M100 784q0 64 28 123t73 100.5t104.5 64t119 20.5t120 -38.5t104.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5 t-94 124.5t-33.5 117.5z" />
|
||||
<glyph unicode="" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447z" />
|
||||
<glyph unicode="" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM237 700l196 -142l-73 -226l192 140l195 -141l-74 229l193 140h-235l-77 211l-78 -211h-239z" />
|
||||
<glyph unicode="" d="M0 0v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100l400 -257v-143h-1200z" />
|
||||
<glyph unicode="" d="M0 0v1100h1200v-1100h-1200zM100 100h100v100h-100v-100zM100 300h100v100h-100v-100zM100 500h100v100h-100v-100zM100 700h100v100h-100v-100zM100 900h100v100h-100v-100zM300 100h600v400h-600v-400zM300 600h600v400h-600v-400zM1000 100h100v100h-100v-100z M1000 300h100v100h-100v-100zM1000 500h100v100h-100v-100zM1000 700h100v100h-100v-100zM1000 900h100v100h-100v-100z" />
|
||||
<glyph unicode="" d="M0 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM0 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5zM600 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM600 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 450v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5 t-14.5 -35.5v-200zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z" />
|
||||
<glyph unicode="" d="M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z" />
|
||||
<glyph unicode="" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233zM300 600v200h100v100h200v-100h100v-200h-100v-100h-200v100h-100z" />
|
||||
<glyph unicode="" d="M23 694q0 200 142 342t342 142t342 -142t142 -342q0 -141 -78 -262l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 299q-120 -77 -261 -77q-200 0 -342 142t-142 342zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 601h400v200h-400v-200z" />
|
||||
<glyph unicode="" d="M23 600q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5 zM500 750q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400z" />
|
||||
<glyph unicode="" d="M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z" />
|
||||
<glyph unicode="" d="M26 601q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39l5 -2l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38 l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73zM385 601 q0 88 63 151t152 63t152 -63t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152z" />
|
||||
<glyph unicode="" d="M100 1025v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18zM200 100v800h900v-800q0 -41 -29.5 -71t-70.5 -30h-700q-41 0 -70.5 30 t-29.5 71zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM500 1100h300v100h-300v-100zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" />
|
||||
<glyph unicode="" d="M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z" />
|
||||
<glyph unicode="" d="M100 25v1150q0 11 7 18t18 7h475v-500h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18zM700 800v300l300 -300h-300z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM173 600q0 -176 125 -301.5t302 -125.5t302 125.5t125 301.5 q0 177 -125 302t-302 125t-302 -125t-125 -302zM500 500v400h100v-300h200v-100h-300z" />
|
||||
<glyph unicode="" d="M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z" />
|
||||
<glyph unicode="" d="M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM173 600q0 -176 125 -301.5t302 -125.5t302 125.5t125 301.5 q0 177 -125 302t-302 125t-302 -125t-125 -302zM350 600h150v300h200v-300h150l-250 -300z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM173 600q0 -176 125 -301.5t302 -125.5t302 125.5t125 301.5 q0 177 -125 302t-302 125t-302 -125t-125 -302zM350 600h150v-300h200v300h150l-250 300z" />
|
||||
<glyph unicode="" d="M0 25v475l200 700h800q199 -700 200 -700v-475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18zM200 500h200l50 -200h300l50 200h200l-97 500h-606z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM173 601q0 -176 125 -301.5t302 -125.5t302 125.5t125 301.5 q0 177 -125 302t-302 125t-302 -125t-125 -302zM500 397v401l297 -200z" />
|
||||
<glyph unicode="" d="M23 600q0 -118 45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123t123 184t45.5 224.5h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123 t-123 -184t-45.5 -224.5z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150zM100 0v400h400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122z" />
|
||||
<glyph unicode="" d="M100 0h1100v1200h-1100v-1200zM200 100v900h900v-900h-900zM300 200v100h100v-100h-100zM300 400v100h100v-100h-100zM300 600v100h100v-100h-100zM300 800v100h100v-100h-100zM500 200h500v100h-500v-100zM500 400v100h500v-100h-500zM500 600v100h500v-100h-500z M500 800v100h500v-100h-500z" />
|
||||
<glyph unicode="" d="M0 100v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" />
|
||||
<glyph unicode="" d="M100 0v1100h100v-1100h-100zM300 400q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500z" />
|
||||
<glyph unicode="" d="M0 275q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v275v25q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5 t-49.5 -227v-300zM200 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14zM800 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14z" />
|
||||
<glyph unicode="" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM688 459l141 141l-141 141l71 71l141 -141l141 141l71 -71l-141 -141l141 -141l-71 -71l-141 141l-141 -141z" />
|
||||
<glyph unicode="" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" />
|
||||
<glyph unicode="" d="M0 401v400h300l300 200v-800l-300 200h-300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 108 86 248q0 145 -93 257zM889 951l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8l81 -66l6 8q142 178 142 405q0 230 -144 408l-6 8z" />
|
||||
<glyph unicode="" d="M0 0h500v500h-200v100h-100v-100h-200v-500zM0 600h100v100h400v100h100v100h-100v300h-500v-600zM100 100v300h300v-300h-300zM100 800v300h300v-300h-300zM200 200v100h100v-100h-100zM200 900h100v100h-100v-100zM500 500v100h300v-300h200v-100h-100v-100h-200v100 h-100v100h100v200h-200zM600 0v100h100v-100h-100zM600 1000h100v-300h200v-300h300v200h-200v100h200v500h-600v-200zM800 800v300h300v-300h-300zM900 0v100h300v-100h-300zM900 900v100h100v-100h-100zM1100 200v100h100v-100h-100z" />
|
||||
<glyph unicode="" d="M0 200h100v1000h-100v-1000zM100 0v100h300v-100h-300zM200 200v1000h100v-1000h-100zM500 0v91h100v-91h-100zM500 200v1000h200v-1000h-200zM700 0v91h100v-91h-100zM800 200v1000h100v-1000h-100zM900 0v91h200v-91h-200zM1000 200v1000h200v-1000h-200z" />
|
||||
<glyph unicode="" d="M1 700v475q0 10 7.5 17.5t17.5 7.5h474l700 -700l-500 -500zM148 953q0 -42 29 -71q30 -30 71.5 -30t71.5 30q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71z" />
|
||||
<glyph unicode="" d="M2 700v475q0 11 7 18t18 7h474l700 -700l-500 -500zM148 953q0 -42 30 -71q29 -30 71 -30t71 30q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71zM701 1200h100l700 -700l-500 -500l-50 50l450 450z" />
|
||||
<glyph unicode="" d="M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z" />
|
||||
<glyph unicode="" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" />
|
||||
<glyph unicode="" d="M0 100v700h200l100 -200h600l100 200h200v-700h-200v200h-800v-200h-200zM253 829l40 -124h592l62 124l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18zM281 24l38 152q2 10 11.5 17t19.5 7h500q10 0 19.5 -7t11.5 -17l38 -152q2 -10 -3.5 -17t-15.5 -7h-600 q-10 0 -15.5 7t-3.5 17z" />
|
||||
<glyph unicode="" d="M0 200q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600z M356 500q0 100 72 172t172 72t172 -72t72 -172t-72 -172t-172 -72t-172 72t-72 172zM494 500q0 -44 31 -75t75 -31t75 31t31 75t-31 75t-75 31t-75 -31t-31 -75zM900 700v100h100v-100h-100z" />
|
||||
<glyph unicode="" d="M53 0h365v66q-41 0 -72 11t-49 38t1 71l92 234h391q67 -181 82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66zM416 521l178 457l46 -140 l116 -317h-340z" />
|
||||
<glyph unicode="" d="M100 0v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21t-29 13.5t-49 14v71h471q76 0 145.5 -37.5t115 -111.5t45.5 -167q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129 q0 -84 -59 -156.5t-142 -111t-162 -38.5h-500zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400zM400 700h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5v-379z" />
|
||||
<glyph unicode="" d="M200 0v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500z" />
|
||||
<glyph unicode="" d="M-75 200h75v800h-75l125 167l125 -167h-75v-800h75l-125 -167zM300 900v300h150h700h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49z " />
|
||||
<glyph unicode="" d="M33 51l167 125v-75h800v75l167 -125l-167 -125v75h-800v-75zM100 901v300h150h700h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50z" />
|
||||
<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 350q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM0 650q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 950q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
|
||||
<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 650q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM200 350q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM200 950q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
|
||||
<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600 q-21 0 -35.5 15t-14.5 35z" />
|
||||
<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" />
|
||||
<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" />
|
||||
<glyph unicode="" d="M-101 500v100h201v75l166 -125l-166 -125v75h-201zM300 0h100v1100h-100v-1100zM500 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35 v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 650q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100 q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100z" />
|
||||
<glyph unicode="" d="M1 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 650 q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM801 0v1100h100v-1100 h-100zM934 550l167 -125v75h200v100h-200v75z" />
|
||||
<glyph unicode="" d="M0 275v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53zM900 600l300 300v-600z" />
|
||||
<glyph unicode="" d="M0 44v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31zM100 263l247 182l298 -131l-74 156l293 318l236 -288v500h-1000v-737zM208 750q0 56 39 95t95 39t95 -39t39 -95t-39 -95t-95 -39t-95 39t-39 95z " />
|
||||
<glyph unicode="" d="M219 725q0 -116 60 -249q65 -114 158.5 -231.5t154.5 -178.5l61 -61q22 25 59.5 69t132 167t163.5 231q70 142 70 258q0 117 -57.5 218.5t-156.5 161t-216 59.5q-116 0 -215 -61t-156.5 -163.5t-57.5 -219.5zM431 752q0 92 64.5 157t156.5 65t157 -65t65 -157t-65 -156.5 t-157 -64.5t-156.5 64.5t-64.5 156.5z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM173 600q0 -176 125 -301.5t302 -125.5v854q-177 0 -302 -125t-125 -302z " />
|
||||
<glyph unicode="" d="M117 406q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 138.5t-64 210.5zM243 414q14 -82 59.5 -136 t136.5 -80l16 98q-7 6 -18 17t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125l200 200v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM436 341l161 50l412 412l-114 113l-405 -405zM994 1015l114 -113l113 113l-21 85l-92 28z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5 zM423 524q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM342 632l283 -284l566 567l-136 137l-430 -431l-147 147z" />
|
||||
<glyph unicode="" d="M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z" />
|
||||
<glyph unicode="" d="M200 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M0 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M136 550l564 550v-487l500 487v-1100l-500 488v-488z" />
|
||||
<glyph unicode="" d="M200 0l900 550l-900 550v-1100z" />
|
||||
<glyph unicode="" d="M200 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800zM600 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
|
||||
<glyph unicode="" d="M200 150q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
|
||||
<glyph unicode="" d="M0 0v1100l500 -487v487l564 -550l-564 -550v488z" />
|
||||
<glyph unicode="" d="M0 0v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488z" />
|
||||
<glyph unicode="" d="M300 0v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438z" />
|
||||
<glyph unicode="" d="M100 250v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5zM100 500h1100l-550 564z" />
|
||||
<glyph unicode="" d="M136 550v1l551 550l198 -197l-352 -353l352 -353l-198 -198z" />
|
||||
<glyph unicode="" d="M315 198l198 -198l552 550l-1 1l-551 550l-198 -197l353 -353z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM300 500h200v-200h200v200h200v200h-200v200h-200v-200h-200v-200z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM300 500h600v200h-600v-200z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM247 459l212 -212l141 141l141 -141l213 212l-142 141l142 142l-213 212 l-141 -142l-141 142l-212 -212l141 -142z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM270 551l276 -277l411 411l-175 174l-236 -236l-102 102z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM364 700h143q4 0 11.5 -1t11 -0.5t6.5 3t3 8.5t1 11t3.5 8.5t3.5 6t5.5 4 t6.5 2.5t9 1.5t9 0.5h11.5h12.5q19 0 30 -10t11 -26q0 -21 -4.5 -27.5t-26.5 -21.5q-5 -1 -12.5 -3.5t-27 -13.5t-34 -27t-26.5 -46.5t-11 -68.5h200q5 3 14 8t31.5 25.5t39.5 46t31 69t14 93.5q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3q-50 0 -90.5 -12t-75 -38.5 t-53.5 -74.5t-19 -114zM500 300h200v100h-200v-100z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM400 300h400v100h-100v300h-300v-100h100v-200h-100v-100zM500 800h200 v100h-200v-100z" />
|
||||
<glyph unicode="" d="M0 500v200h194q15 60 36 104.5t55.5 86t88 69t126.5 40.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h6h165h32v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194zM290 500q24 -73 79.5 -127.5t130.5 -78.5v206 h200v-206q149 48 201 206h-201v200h200q-25 74 -76 127.5t-124 76.5v-204h-200v203q-75 -24 -130 -77.5t-79 -125.5h209v-200h-210z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM173 600q0 -176 125 -301.5t302 -125.5t302 125.5t125 301.5 q0 177 -125 302t-302 125t-302 -125t-125 -302zM384 465l135 135l-135 135l81 81l135 -135l135 135l81 -81l-135 -135l135 -135l-81 -81l-135 136l-135 -136z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM173 600q0 -176 125 -301.5t302 -125.5t302 125.5t125 301.5 q0 177 -125 302t-302 125t-302 -125t-125 -302zM350 537l113 113l87 -87l204 204l113 -113l-317 -317z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM173 600q0 -119 66 -225l586 587q-105 65 -225 65q-177 0 -302 -125 t-125 -302zM381 235q104 -62 219 -62q177 0 302 125.5t125 301.5q0 117 -62 219z" />
|
||||
<glyph unicode="" d="M0 547l600 453v-300h600v-300h-600v-301z" />
|
||||
<glyph unicode="" d="M0 400v300h600v300l600 -453l-600 -448v301h-600z" />
|
||||
<glyph unicode="" d="M204 600l450 600l444 -600h-298v-600h-300v600h-296z" />
|
||||
<glyph unicode="" d="M104 600h296v600h300v-600h298l-449 -600z" />
|
||||
<glyph unicode="" d="M0 200q5 105 27 193t68 167t113 135t166.5 91.5t225.5 42.5v271l600 -453l-600 -448v301q-94 -2 -182.5 -20t-170.5 -52.5t-147 -92.5t-100 -135z" />
|
||||
<glyph unicode="" d="M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z" />
|
||||
<glyph unicode="" d="M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -142l-295 -294l129 -130h-400z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM456 851l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5 t21.5 34.5l58 302q4 20 -8 34.5t-33 14.5h-207q-20 0 -32 -14.5t-8 -34.5zM500 300h200v100h-200v-100z" />
|
||||
<glyph unicode="" d="M0 800h100v-200h400v300h200v-300h400v200h100v100h-111v6t-1 15t-3 18l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6h-111v-100z M100 0h400v400h-400v-400zM200 900q-3 0 14 48t35 96l18 47l214 -191h-281zM700 0v400h400v-400h-400zM731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269z" />
|
||||
<glyph unicode="" d="M0 -22v143l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55z M238.5 300.5q19.5 -6.5 86.5 76.5q55 66 367 234q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5z" />
|
||||
<glyph unicode="" d="M111 408q0 -33 5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-4 -23 -14 -51.5t-20 -49t-24 -49.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5 t30.5 2.5t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5q2 -12 8 -41.5 t8 -43t6 -39.5t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85z" />
|
||||
<glyph unicode="" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30l26 -40l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5 t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30zM120 600q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5t123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54 q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l105 105q-37 24 -75 72t-57 84l-20 36z" />
|
||||
<glyph unicode="" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43zM119 600q37 -48 65.5 -82.5t84 -93t118.5 -100t126 -60.5l37 141 q-107 18 -178.5 101.5t-71.5 193.5q0 85 46 158q-97 -83 -227 -258zM377 656q49 -124 154 -191l47 47l23 87q-30 28 -59.5 69t-43.5 68l-15 26zM780 161l38 145q22 15 45 34t45.5 43.5t40.5 44.5t40.5 49.5t34 44.5t32 44t24.5 34q-83 113 -139 175l38 146 q68 -54 132.5 -125.5t87.5 -103.5t36 -52l26 -40l-26 -40q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5t-124 -100t-146.5 -79z" />
|
||||
<glyph unicode="" d="M-97.5 34q13.5 -34 50.5 -34h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 33 -48 36t-48 -29l-642 -1066q-21 -32 -7.5 -66zM155 200l445 723l445 -723h-345v100h-200v-100h-345zM500 600l100 -300l100 300v100h-200v-100z" />
|
||||
<glyph unicode="" d="M100 262v41q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -20 -13 -28.5t-32 0.5l-94 78h-222l-94 -78q-19 -9 -32 -0.5t-13 28.5 v64q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5z" />
|
||||
<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100v-750zM0 900h1100v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 100v100h100v-100h-100zM100 300v100h100v-100h-100z M100 500v100h100v-100h-100zM300 100v100h100v-100h-100zM300 300v100h100v-100h-100zM300 500v100h100v-100h-100zM500 100v100h100v-100h-100zM500 300v100h100v-100h-100zM500 500v100h100v-100h-100zM700 100v100h100v-100h-100zM700 300v100h100v-100h-100zM700 500 v100h100v-100h-100zM900 100v100h100v-100h-100zM900 300v100h100v-100h-100zM900 500v100h100v-100h-100z" />
|
||||
<glyph unicode="" d="M0 200v200h259l600 600h241v198l300 -295l-300 -300v197h-159l-600 -600h-341zM0 800h259l122 -122l141 142l-181 180h-341v-200zM678 381l141 142l122 -123h159v198l300 -295l-300 -300v197h-241z" />
|
||||
<glyph unicode="" d="M0 400v600q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5z" />
|
||||
<glyph unicode="" d="M100 600v200h300v-250v-26v-55.5t3.5 -50t11 -47.5t22 -37t35.5 -31.5t53.5 -18t74.5 -7.5t74.5 8t53.5 18.5t35.5 32t22 38t11 48t3.5 49.5v54v25v250h300v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5 t-89 96.5t-58 109.5t-31.5 116t-15 104t-3 83zM100 900v300h300v-300h-300zM800 900v300h300v-300h-300z" />
|
||||
<glyph unicode="" d="M-1 410l198 -198l353 353l353 -353l198 198l-550 552z" />
|
||||
<glyph unicode="" d="M99 797l551 -551l550 551l-198 198l-353 -352l-352 352z" />
|
||||
<glyph unicode="" d="M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-199l300 -283l299 283h-200v600h-796z" />
|
||||
<glyph unicode="" d="M18 939q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-300v-50q0 -21 -14.5 -35.5 t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43z" />
|
||||
<glyph unicode="" d="M0 0v800h1200v-800h-1200zM0 900v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-100h-1200z" />
|
||||
<glyph unicode="" d="M1 0l300 700h1200l-300 -700h-1200zM1 400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000z" />
|
||||
<glyph unicode="" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" />
|
||||
<glyph unicode="" d="M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z" />
|
||||
<glyph unicode="" d="M0 100v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM800 100h100v100h-100v-100z M1000 100h100v100h-100v-100z" />
|
||||
<glyph unicode="" d="M-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5zM100 500v250v8v8v7t0.5 7t1.5 5.5t2 5t3 4t4.5 3.5t6 1.5t7.5 0.5h200l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35 q-55 337 -55 351zM1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35z" />
|
||||
<glyph unicode="" d="M74 350q0 21 13.5 35.5t33.5 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94q20 0 29 -10.5t3 -29.5q-18 -36 -18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-22 -9 -63 -23 t-167.5 -37t-251.5 -23t-245.5 20.5t-178.5 41.5l-58 20q-18 7 -31 27.5t-13 40.5zM497 110q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6t-103 6z" />
|
||||
<glyph unicode="" d="M21 445l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180l-155 180l-45 -233l-224 78l78 -225l-233 -44l179 -156z" />
|
||||
<glyph unicode="" d="M0 200v600h200v-600h-200zM300 275v400q0 37 20 63l145 196l96 198q14 28 38 48t51 20h50q39 0 69.5 -40.5t30.5 -84.5v-150l-28 -125h328q39 0 69.5 -40.5t30.5 -84.5v-100q0 -43 -29 -74l-238 -344q-37 -57 -83 -57h-250q-7 0 -41.5 25t-66.5 50l-31 25h-61 q-100 0 -100 75z" />
|
||||
<glyph unicode="" d="M0 400v600h200v-600h-200zM300 525v400q0 75 100 75h61q123 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5h-50q-27 0 -51 20t-38 48l-96 198l-145 196 q-20 26 -20 63z" />
|
||||
<glyph unicode="" d="M8 200v600h200v-600h-200zM308 275q0 -13 83 -94t90 -81h341q15 0 28.5 19.5t20.5 41.5l130 339h107q84 0 138.5 39t54.5 111t-53.5 110t-138.5 38h-302l85 121q11 15 10.5 34t-13.5 32l-110 112q-22 22 -53 6l-362 -230q-6 -4 -15.5 -10.5t-25 -26t-15.5 -36.5v-525z M408 289v503l339 236l86 -83l-147 -183q-17 -23 -5 -47q2 -3 4 -5.5t4 -4t5.5 -2.5t5 -1.5t6 -1t6.5 -0.5h7.5h6.5h457q22 0 30.5 -25t-0.5 -50t-30 -25h-203q-15 0 -28.5 -20t-19.5 -41l-131 -339h-293z" />
|
||||
<glyph unicode="" d="M-101 651q0 -72 55 -111t139 -39h107l130 -339q6 -21 19.5 -41t29.5 -20h341q8 0 94 80.5t86 93.5v526q0 17 -15 35.5t-30 27.5l-15 10l-365 230q-32 14 -54 -6l-109 -113q-13 -13 -13.5 -32t10.5 -34l85 -121q-101 1 -302 1q-85 0 -139 -38t-54 -110zM-1 601v100h476 h6.5h7.5t6.5 0.5t6.5 1t5.5 1.5t5 2.5l4 4t3.5 5.5q13 24 -5 46l-145 184l87 83l343 -237v-502l-107 -89h-293l-131 339q-6 20 -19.5 40.5t-28.5 20.5h-222zM1000 201v600h200v-600h-200z" />
|
||||
<glyph unicode="" d="M97 719l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53zM172 739l83 86l183 -146 q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6v7.5v7v456q0 22 25 31t50 -0.5t25 -30.5v-203q0 -15 20 -28.5t41 -19.5l339 -131v-293l-89 -100h-503zM400 0v200h600v-200h-600z" />
|
||||
<glyph unicode="" d="M1 585q-15 -31 7 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85q0 -51 -0.5 -153.5t-0.5 -148.5q0 -84 38.5 -138t110.5 -54t111 55t39 139v106l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15zM76 565l237 339h503 l89 -100v-294l-340 -130q-21 -7 -40.5 -20.5t-19.5 -28.5v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146zM305 1104v200h600v-200h-600z" />
|
||||
<glyph unicode="" d="M22 600q0 157 77.5 290.5t210.5 210.5t290 77t290 -77t210.5 -210.5t77.5 -290.5t-77.5 -290t-210.5 -210.5t-290 -77.5t-290 77.5t-210.5 210.5t-77.5 290zM298 500h300v-194l402 294l-402 299v-198h-300v-201z" />
|
||||
<glyph unicode="" d="M22 600q0 157 77.5 290.5t210.5 210.5t290 77t290 -77t210.5 -210.5t77.5 -290.5t-77.5 -290t-210.5 -210.5t-290 -77.5t-290 77.5t-210.5 210.5t-77.5 290zM200 600l400 -294v194h300v201h-298v198z" />
|
||||
<glyph unicode="" d="M22 600q0 157 77.5 290.5t210.5 210.5t290 77t290 -77t210.5 -210.5t77.5 -290.5t-77.5 -290t-210.5 -210.5t-290 -77.5t-290 77.5t-210.5 210.5t-77.5 290zM302 599h197v-300h201v300h194l-294 401z" />
|
||||
<glyph unicode="" d="M22 600q0 157 77.5 290.5t210.5 210.5t290 77t290 -77t210.5 -210.5t77.5 -290.5t-77.5 -290t-210.5 -210.5t-290 -77.5t-290 77.5t-210.5 210.5t-77.5 290zM306 602l294 -402l298 402h-197v300h-201v-300h-194z" />
|
||||
<glyph unicode="" d="M24 600q0 154 78 287t211 211t287 78t287 -78t211 -211t78 -287t-78 -287t-211 -211t-287 -78t-287 78t-211 211t-78 287zM254 780q-8 -33 5.5 -92.5t7.5 -87.5q0 -9 17 -44t16 -60q12 0 23 -5.5t23 -15t20 -13.5q16 -8 34 -15t40 -14.5t34 -12.5q22 -8 53 -31.5 t59.5 -38.5t57.5 -11q8 -18 -15 -55t-20 -57q42 -71 87 -80q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q104 -3 221 112q30 29 47 47t34.5 49.5t20.5 62.5q-14 9 -37.5 9t-35.5 7q-14 8 -49 15.5t-52 18.5q-9 0 -39.5 -0.5t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12 q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5t5.5 57.5q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5 t34 22.5q-6 17 10 36q8 0 23.5 -1.5t24.5 -1.5t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 41 1 44q31 -13 58.5 -14.5t39.5 3.5l11 4q6 36 -17 53.5t-64 28.5t-56 23q-20 -3 -37 1q-16 -13 -37.5 -21.5t-34 -12t-44 -8.5t-38.5 -6 q-15 -3 -45.5 0.5t-45.5 -2.5q-22 -8 -52.5 -27t-33.5 -34q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -90.5t-29.5 -79.5zM518 916q3 12 16 30t16 25q10 -10 18.5 -10t14 6t14.5 14.5t16 12.5q0 -17 8 -41.5t16.5 -44.5t9.5 -24q-9 2 -39.5 6t-52 10t-37.5 16z" />
|
||||
<glyph unicode="" d="M0 164.5q0 21.5 15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138l145 -232l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5z" />
|
||||
<glyph unicode="" horiz-adv-x="1220" d="M0 196v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 596v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5zM0 996v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM600 596h500v100h-500v-100zM800 196h300v100h-300v-100zM900 996h200v100h-200v-100z" />
|
||||
<glyph unicode="" d="M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z" />
|
||||
<glyph unicode="" d="M0 200v200h1200v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500z M500 1000h200v100h-200v-100z" />
|
||||
<glyph unicode="" d="M0 0v400l129 -129l200 200l142 -142l-200 -200l129 -129h-400zM0 800l129 129l200 -200l142 142l-200 200l129 129h-400v-400zM729 329l142 142l200 -200l129 129v-400h-400l129 129zM729 871l200 200l-129 129h400v-400l-129 129l-200 -200z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM173 600q0 -176 125.5 -301.5t301.5 -125.5q177 0 302 125.5t125 301.5 q0 177 -125 302t-302 125q-176 0 -301.5 -125t-125.5 -302zM291 655q0 23 16 39t38 16q23 0 39 -16t16 -39t-16 -39t-39 -16q-22 0 -38 16t-16 39zM400 850q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5zM513 609q0 32 21 56.5 t52 29.5l122 126l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-16 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-62 25.5t-26 61.5zM800 655q0 23 16 39t39 16q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16t-16 39z " />
|
||||
<glyph unicode="" d="M-40 375q-13 -95 35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -79.5 -17t-67.5 -51l-388 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23q38 0 53 -36 q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256l7 -7l69 -60l517 511 q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-39 -23 -111 -95l-512 -512q-68 -68 -81 -163z" />
|
||||
<glyph unicode="" d="M99 785q0 64 28 122.5t73 100t104.5 64t119 20.5t120 -38.5t105.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100t27 -122.5q0 -70 -55.5 -151.5t-129.5 -151t-182.5 -181t-182.5 -212.5q-62 85 -145.5 174t-150 152.5t-127 127t-94 124.5t-33.5 118z M229 785q0 -31 29.5 -75t64.5 -80.5t97 -97.5q17 -16 25 -24q101 -98 204 -217q85 97 209 219q127 125 163 171q48 62 48 104q0 78 -53.5 132.5t-120.5 54.5q-85 0 -147 -91l-102 -147l-97 150q-58 88 -141 88q-68 0 -123.5 -55.5t-55.5 -131.5z" />
|
||||
<glyph unicode="" d="M57 353q0 -95 66 -159l141 -142q66 -66 159 -66t159 66l283 283q66 66 66 159t-66 159l-141 141q-3 4 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159zM269 706q0 -93 66 -159l141 -141q3 -3 9.5 -9t8.5 -8 l106 105l-212 212l389 389l247 -247l-95 -96l17 -17q47 -47 78 -100l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159z" />
|
||||
<glyph unicode="" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM300 300h600v700h-600v-700zM496 150q0 -43 30.5 -73.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5 t-73.5 -30.5t-30.5 -73.5z" />
|
||||
<glyph unicode="" d="M0 0l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207z" />
|
||||
<glyph unicode="" d="M295 433h139q5 -77 48.5 -126.5t117.5 -64.5v335q-6 1 -15.5 4t-11.5 3q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5 v-307l64 -14q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5zM466 889q0 -36 9 -60t31 -38t36 -19.5t47 -13.5q7 -2 11 -3v274q-61 -8 -97.5 -37.5t-36.5 -102.5zM700 237 q170 18 170 151q0 64 -44 99.5t-126 60.5v-311z" />
|
||||
<glyph unicode="" d="M100 600v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -28 16.5 -69.5t28 -62.5t41.5 -72h241v-100h-197q8 -50 -2.5 -115 t-31.5 -94q-41 -59 -99 -113q35 11 84 18t70 7q32 1 102 -16t104 -17q76 0 136 30l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z" />
|
||||
<glyph unicode="" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v200h100v-100h200v-100h-300zM700 400v100h300v-200h-99v-100h-100v100h99v100h-200zM700 700v500h300v-500h-100v100h-100v-100h-100zM801 900h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v500h300v-500h-100v100h-100v-100h-100zM700 700v200h100v-100h200v-100h-300zM700 1100v100h300v-200h-99v-100h-100v100h99v100h-200zM801 200h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500z" />
|
||||
<glyph unicode="" d="M0 400v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-163 0 -281.5 117.5t-118.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM400 300l333 250l-333 250v-500z" />
|
||||
<glyph unicode="" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 700l250 -333l250 333h-500z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 400h500l-250 333z" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
|
Before Width: | Height: | Size: 50 KiB |
Binary file not shown.
Binary file not shown.
6
public/static/js/bootstrap.min.js
vendored
6
public/static/js/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
9
public/static/js/hammer.min.js
vendored
9
public/static/js/hammer.min.js
vendored
File diff suppressed because one or more lines are too long
4
public/static/js/jquery.min.js
vendored
4
public/static/js/jquery.min.js
vendored
File diff suppressed because one or more lines are too long
4
public/static/js/modernizr-2.6.2.min.js
vendored
4
public/static/js/modernizr-2.6.2.min.js
vendored
File diff suppressed because one or more lines are too long
7
public/static/js/salvattore.min.js
vendored
7
public/static/js/salvattore.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
|
||||
This is a javascript file for omninotesweb
|
||||
============
|
||||
|
||||
Author: Suraj patil
|
||||
Updated: January 2015
|
||||
keyCode: n-110
|
||||
*/
|
||||
|
||||
$(document).ready(function(){
|
||||
|
||||
/*on() is used instead of click because click can be used only on static elements, and on() is to be used when you add
|
||||
elements dynamically*/
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
//
|
||||
// $('.items').openOnHover(function(){
|
||||
// alert();
|
||||
// });
|
||||
|
||||
$('.floating-action-icon-add').click(function(){
|
||||
$('#addNoteModal').modal('show');
|
||||
});
|
||||
|
||||
$('#editCatFrmBtn').click(function(){
|
||||
$('#EditForm').toggleClass('hidden')
|
||||
});
|
||||
|
||||
$('#searchFormBtn').click(function(){
|
||||
$('#SearchForm').toggleClass('hidden')
|
||||
});
|
||||
|
||||
$('#toggleAddFileGrp').click(function(){
|
||||
$('#file-group').toggleClass('hidden');
|
||||
$('#toggleAddFileGrp').addClass('hidden') ;
|
||||
});
|
||||
|
||||
$("#noti").click(
|
||||
function(){
|
||||
this.fadeOut();
|
||||
}
|
||||
);
|
||||
if ($('#actlMsg').html()==''){
|
||||
$('.notification').addClass('hidden');
|
||||
} else {
|
||||
$('.notification').fadeOut(9000);
|
||||
}
|
||||
$('.btnMessage').click(function(){$('.notification').fadeOut()})
|
||||
|
||||
/*$( document ).keypress(
|
||||
function(event){
|
||||
if ( event.which == 110 ) { //bind the 'n' key to add note
|
||||
$('#addNoteModal').modal('show');
|
||||
}
|
||||
|
||||
if (event.which==109 ) { //binds the 'm' key to show the navigation drawer
|
||||
$('.sidebar-toggle').click();
|
||||
}
|
||||
}
|
||||
);*/
|
||||
|
||||
$("#addNoteBtn").on("click", function() {
|
||||
this.preventDefaults();
|
||||
var task_id = $("#task-id").val();
|
||||
$.ajax({
|
||||
url: "/tasks/" + task_id,
|
||||
type: "POST",
|
||||
data: {'title':'randome note', 'content':'this and that'}
|
||||
}).done(function(res, status) {
|
||||
console.log(status, res);
|
||||
var response = res
|
||||
$("#timeline").append(response)
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$('.toggle').click(function(){
|
||||
$(this).next().toggle();
|
||||
});
|
||||
});
|
||||
@@ -1,732 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Sonatra package.
|
||||
*
|
||||
* (c) François Pluchino <francois.pluchino@sonatra.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/*global jQuery*/
|
||||
/*global window*/
|
||||
/*global navigator*/
|
||||
/*global document*/
|
||||
/*global CSSMatrix*/
|
||||
/*global WebKitCSSMatrix*/
|
||||
/*global MSCSSMatrix*/
|
||||
/*global Hammer*/
|
||||
/*global Sidebar*/
|
||||
|
||||
/**
|
||||
* @param {jQuery} $
|
||||
*
|
||||
* @typedef {Sidebar} Sidebar
|
||||
*/
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Check if is a mobile device.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function mobileCheck() {
|
||||
return Boolean(navigator.userAgent.match(/Android|iPhone|iPad|iPod|IEMobile|BlackBerry|Opera Mini/i));
|
||||
}
|
||||
|
||||
/**
|
||||
* Binding actions of keyboard.
|
||||
*
|
||||
* @param {jQuery.Event|Event} event
|
||||
*
|
||||
* @typedef {Sidebar} Event.data The sidebar instance
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function keyboardAction(event) {
|
||||
if (!event instanceof jQuery.Event || event.data.options.disabledKeyboard) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = event.data,
|
||||
kbe = self.options.keyboardEvent;
|
||||
|
||||
if (event.shiftKey === kbe.shiftKey
|
||||
&& event.ctrlKey === kbe.ctrlKey
|
||||
&& event.altKey === kbe.altKey
|
||||
&& event.keyCode === kbe.keyCode) {
|
||||
self.toggle(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the window width is wider than the minimum width defined in
|
||||
* options.
|
||||
*
|
||||
* @param {Sidebar} self The sidebar instance
|
||||
*
|
||||
* @returns {boolean}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function isOverMinWidth(self) {
|
||||
var scrollbarWidth = 'scrollbarWidth',
|
||||
isOver = false,
|
||||
$window = $(window),
|
||||
windowWidth = $window.innerWidth(),
|
||||
widthNoScroll,
|
||||
inner,
|
||||
outer;
|
||||
|
||||
if ($('body').height() > $window.innerHeight()) {
|
||||
if (null === self.scrollbarWidth) {
|
||||
inner = document.createElement('div');
|
||||
outer = document.createElement('div');
|
||||
outer.style.visibility = 'hidden';
|
||||
outer.style.width = '100px';
|
||||
|
||||
document.body.appendChild(outer);
|
||||
|
||||
widthNoScroll = outer.offsetWidth;
|
||||
outer.style.overflow = 'scroll';
|
||||
inner.style.width = '100%';
|
||||
outer.appendChild(inner);
|
||||
|
||||
self[scrollbarWidth] = widthNoScroll - inner.offsetWidth;
|
||||
|
||||
outer.parentNode.removeChild(outer);
|
||||
}
|
||||
|
||||
windowWidth += self[scrollbarWidth];
|
||||
}
|
||||
|
||||
if (windowWidth >= self.options.minLockWidth) {
|
||||
isOver = true;
|
||||
}
|
||||
|
||||
return isOver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the sidebar since external action.
|
||||
*
|
||||
* @param {Event} event The event
|
||||
*
|
||||
* @typedef {Sidebar} Event.data The sidebar instance
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function closeExternal(event) {
|
||||
var self = event.data,
|
||||
$target = $(event.currentTarget.activeElement);
|
||||
|
||||
if ((self.isLocked() && isOverMinWidth(self)) || $(event.target).parents('.' + self.options.classWrapper).size() > 0 || $target.parents('.' + self.options.classWrapper).size() > 0 || $target.hasClass('sidebar-swipe')) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
if (isOverMinWidth(self)) {
|
||||
self.close();
|
||||
|
||||
} else {
|
||||
self.forceClose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the sidebar or reopen the locked sidebar on window resize event.
|
||||
*
|
||||
* @param {Event} event The event
|
||||
*
|
||||
* @typedef {Sidebar} Event.data The sidebar instance
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function onResizeWindow(event) {
|
||||
var self = event.data;
|
||||
|
||||
if (isOverMinWidth(self) && self.isLocked()) {
|
||||
self.forceOpen();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
closeExternal(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sidebar wrapper position.
|
||||
*
|
||||
* @param {jQuery} $target
|
||||
*
|
||||
* @returns {number} The Y axis position
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function getWrapperPosition($target) {
|
||||
var transformCss = $target.css('transform'),
|
||||
transform = {e: 0, f: 0},
|
||||
reMatrix,
|
||||
match;
|
||||
|
||||
if (transformCss) {
|
||||
if ('function' === typeof CSSMatrix) {
|
||||
transform = new CSSMatrix(transformCss);
|
||||
|
||||
} else if ('function' === typeof WebKitCSSMatrix) {
|
||||
transform = new WebKitCSSMatrix(transformCss);
|
||||
|
||||
} else if ('function' === typeof MSCSSMatrix) {
|
||||
transform = new MSCSSMatrix(transformCss);
|
||||
|
||||
} else {
|
||||
reMatrix = /matrix\(\s*-?\d+(?:\.\d+)?\s*,\s*-?\d+(?:\.\d+)?\s*,\s*-?\d+(?:\.\d+)?\s*,\s*-?\d+(?:\.\d+)?\s*,\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/;
|
||||
match = transformCss.match(reMatrix);
|
||||
|
||||
if (match) {
|
||||
transform.e = parseInt(match[1], 10);
|
||||
transform.f = parseInt(match[2], 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return transform.e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans the hammer configuration on the wrapper element.
|
||||
*
|
||||
* @param {Sidebar} self The sidebar instance
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function cleanHammer(self) {
|
||||
self.$wrapper.removeData('drap-start-position');
|
||||
self.$wrapper.css('-webkit-transition', '');
|
||||
self.$wrapper.css('transition', '');
|
||||
self.$wrapper.css('-webkit-transform', '');
|
||||
self.$wrapper.css('transform', '');
|
||||
self.$wrapper.removeClass(self.options.classOnDragging);
|
||||
delete self.dragStartPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action of "on drag" hammer event.
|
||||
*
|
||||
* @param {Sidebar} self The sidebar instance
|
||||
* @param {Event} event The hammer event
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function onDrag(self, event) {
|
||||
var dragStartPosition = 'dragStartPosition',
|
||||
horizontal;
|
||||
|
||||
if (undefined !== self.hammerScroll) {
|
||||
self.hammerScroll.onDrag(event);
|
||||
}
|
||||
|
||||
if ((Hammer.DIRECTION_LEFT !== event.gesture.direction && Hammer.DIRECTION_RIGHT !== event.gesture.direction)
|
||||
|| (self.options.locked && isOverMinWidth(self))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (undefined === self.dragStartPosition) {
|
||||
self[dragStartPosition] = getWrapperPosition(self.$wrapper);
|
||||
}
|
||||
|
||||
horizontal = Math.round(self.dragStartPosition + event.gesture.deltaX);
|
||||
|
||||
if ((Sidebar.POSITION_LEFT === self.getPosition() && horizontal > 0) || (Sidebar.POSITION_RIGHT === self.getPosition() && horizontal < 0)) {
|
||||
horizontal = 0;
|
||||
}
|
||||
|
||||
self.$wrapper.addClass(self.options.classOnDragging);
|
||||
self.$wrapper.css('-webkit-transition', 'none');
|
||||
self.$wrapper.css('transition', 'none');
|
||||
self.$wrapper.css('-webkit-transform', 'translate3d(' + horizontal + 'px, 0px, 0px)');
|
||||
self.$wrapper.css('transform', 'translate3d(' + horizontal + 'px, 0px, 0px)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Action of "on drag end" hammer event.
|
||||
*
|
||||
* @param {Sidebar} self The sidebar instance
|
||||
* @param {Event} event The hammer event
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function onDragEnd(self, event) {
|
||||
var closeGesture = Sidebar.POSITION_LEFT,
|
||||
openGesture = Sidebar.POSITION_RIGHT;
|
||||
|
||||
if (undefined !== self.hammerScroll) {
|
||||
self.hammerScroll.onDragEnd(event);
|
||||
}
|
||||
|
||||
cleanHammer(self);
|
||||
|
||||
if (Math.abs(event.gesture.deltaX) <= (self.$wrapper.innerWidth() / 4)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Sidebar.POSITION_RIGHT === self.getPosition()) {
|
||||
closeGesture = Sidebar.POSITION_RIGHT;
|
||||
openGesture = Sidebar.POSITION_LEFT;
|
||||
}
|
||||
|
||||
if (self.isOpen() && closeGesture === event.gesture.direction) {
|
||||
self.forceClose();
|
||||
|
||||
} else if (openGesture === event.gesture.direction) {
|
||||
if (self.isOpen() && isOverMinWidth(self) && $.inArray(self.options.forceToggle, [true, 'always']) >= 0) {
|
||||
self.forceOpen();
|
||||
|
||||
} else if (isOverMinWidth(self) && 'always' === self.options.forceToggle) {
|
||||
self.forceOpen();
|
||||
|
||||
} else {
|
||||
self.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the hammer instance.
|
||||
*
|
||||
* @param {Sidebar} self The sidebar instance
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function initHammer(self) {
|
||||
if (!Hammer) {
|
||||
return;
|
||||
}
|
||||
|
||||
var swipe = '$swipe',
|
||||
hammer = 'hammer',
|
||||
hammerScroll = 'hammerScroll';
|
||||
|
||||
if ($.fn.hammerScroll) {
|
||||
self[hammerScroll] = $('.sidebar-scroller', self.$wrapper).hammerScroll({
|
||||
contentWrapperClass: 'sidebar-scroller-content',
|
||||
eventDelegated: true,
|
||||
hammerStickyHeader: self.options.sidebarStickyHeader,
|
||||
scrollbar: self.options.hammerScrollbar,
|
||||
scrollbarInverse: Sidebar.POSITION_RIGHT === self.options.position
|
||||
}).data('st.hammerscroll');
|
||||
}
|
||||
|
||||
self[swipe] = $('<div id="sidebar-swipe' + self.guid + '" class="sidebar-swipe"></div>').appendTo(self.$element);
|
||||
self[swipe].on('mouseover', function (event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
self[hammer] = new Hammer(self.$element[0], {
|
||||
tap: false,
|
||||
transform: false,
|
||||
release: false,
|
||||
hold: false,
|
||||
swipe: false,
|
||||
drag_block_horizontal: true,
|
||||
drag_block_vertical: $.fn.hammerScroll,
|
||||
drag_lock_to_axis: false,
|
||||
drag_min_distance: 5
|
||||
})
|
||||
|
||||
.on('drag', function (event) {
|
||||
onDrag(self, event);
|
||||
})
|
||||
.on('dragend', function (event) {
|
||||
onDragEnd(self, event);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the hammer configuration.
|
||||
*
|
||||
* @param {Sidebar} self The sidebar instance
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function destroyHammer(self) {
|
||||
if (!Hammer) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.$swipe.off('mouseover');
|
||||
self.$element.remove(self.$swipe);
|
||||
}
|
||||
|
||||
// SIDEBAR CLASS DEFINITION
|
||||
// ========================
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {string|elements|object|jQuery} element
|
||||
* @param {object} options
|
||||
*
|
||||
* @this Sidebar
|
||||
*/
|
||||
var Sidebar = function (element, options) {
|
||||
this.guid = jQuery.guid;
|
||||
this.options = $.extend({}, Sidebar.DEFAULTS, options);
|
||||
this.$element = $(element);
|
||||
this.$toggle = $('.' + this.options.classToggle, this.$element);
|
||||
this.$wrapper = $('.' + this.options.classWrapper, this.$element);
|
||||
this.eventType = mobileCheck() ? 'touchstart' : 'click';
|
||||
this.scrollbarWidth = null;
|
||||
this.hammer = undefined;
|
||||
this.hammerScroll = undefined;
|
||||
this.$swipe = undefined;
|
||||
this.$element.attr('data-sidebar', 'true');
|
||||
|
||||
var $findToggle;
|
||||
|
||||
if (Sidebar.POSITION_RIGHT !== this.options.position) {
|
||||
this.options.position = Sidebar.POSITION_LEFT;
|
||||
|
||||
} else {
|
||||
this.$element.addClass('sidebar-right');
|
||||
}
|
||||
|
||||
if (this.$element.hasClass('sidebar-right')) {
|
||||
this.options.position = Sidebar.POSITION_RIGHT;
|
||||
}
|
||||
|
||||
if (this.options.locked) {
|
||||
this.options.forceToggle = 'always';
|
||||
this.$element.css('-webkit-transition', 'none');
|
||||
this.$element.css('transition', 'none');
|
||||
this.$wrapper.css('-webkit-transition', 'none');
|
||||
this.$wrapper.css('transition', 'none');
|
||||
this.$element.addClass(this.options.classLocked);
|
||||
this.$element.addClass(this.options.classForceOpen);
|
||||
this.$wrapper.addClass(this.options.classOpen + '-init');
|
||||
}
|
||||
|
||||
if (!mobileCheck() && this.options.openOnHover && null === this.options.toggleId) {
|
||||
this.$element.on('mouseover.st.sidebar' + this.guid, $.proxy(Sidebar.prototype.open, this));
|
||||
this.$element.on('mouseout.st.sidebar' + this.guid, $.proxy(Sidebar.prototype.close, this));
|
||||
}
|
||||
|
||||
if (null !== this.options.toggleId) {
|
||||
$findToggle = $('#' + this.options.toggleId);
|
||||
|
||||
if (1 === $findToggle.size()) {
|
||||
this.$toggle.remove();
|
||||
this.$toggle = $findToggle;
|
||||
}
|
||||
|
||||
} else {
|
||||
this.$element.addClass('sidebar-togglable');
|
||||
}
|
||||
|
||||
this.$toggle.on(this.eventType + '.st.sidebar' + this.guid, null, this, Sidebar.prototype.toggle);
|
||||
$(window).on('keyup.st.sidebar' + this.guid, null, this, keyboardAction);
|
||||
$(window).on('resize.st.sidebar' + this.guid, null, this, onResizeWindow);
|
||||
|
||||
if (this.$wrapper.hasClass(this.options.classOpen + '-init')) {
|
||||
if (isOverMinWidth(this)) {
|
||||
this.$wrapper.addClass(this.options.classOpen);
|
||||
|
||||
} else {
|
||||
this.$wrapper.removeClass(this.options.classOpen);
|
||||
}
|
||||
|
||||
this.$wrapper.removeClass(this.options.classOpen + '-init');
|
||||
}
|
||||
|
||||
if (this.$wrapper.hasClass(this.options.classOpen)) {
|
||||
$(document).on(this.eventType + '.st.sidebar' + this.guid, null, this, closeExternal);
|
||||
}
|
||||
|
||||
if (this.options.sidebarStickyHeader && $.fn.stickyHeader && !$.fn.hammerScroll) {
|
||||
this.stickyHeader = $('.sidebar-scroller', this.$wrapper).stickyHeader().data('st.stickyheader');
|
||||
}
|
||||
|
||||
initHammer(this);
|
||||
|
||||
this.$element.css('-webkit-transition', '');
|
||||
this.$element.css('transition', '');
|
||||
this.$wrapper.css('-webkit-transition', '');
|
||||
this.$wrapper.css('transition', '');
|
||||
this.$wrapper.addClass('sidebar-ready');
|
||||
},
|
||||
old;
|
||||
|
||||
/**
|
||||
* Defaults options.
|
||||
*
|
||||
* @type {object}
|
||||
*/
|
||||
Sidebar.DEFAULTS = {
|
||||
classToggle: 'sidebar-toggle',
|
||||
classWrapper: 'sidebar-wrapper',
|
||||
classOpen: 'sidebar-open',
|
||||
classLocked: 'sidebar-locked',
|
||||
classForceOpen: 'sidebar-force-open',
|
||||
classOnDragging: 'sidebar-dragging',
|
||||
openOnHover: false,
|
||||
forceToggle: false,//false, true, 'always'
|
||||
locked: false,
|
||||
position: Sidebar.POSITION_LEFT,//left, right
|
||||
minLockWidth: 992,
|
||||
toggleId: null,
|
||||
sidebarStickyHeader: false,
|
||||
hammerScrollbar: true,
|
||||
disabledKeyboard: false,
|
||||
keyboardEvent: {
|
||||
ctrlKey: true,
|
||||
shiftKey: false,
|
||||
altKey: true,
|
||||
keyCode: 'S'.charCodeAt(0)
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Left position.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
Sidebar.POSITION_LEFT = 'left';
|
||||
|
||||
/**
|
||||
* Right position.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
Sidebar.POSITION_RIGHT = 'right';
|
||||
|
||||
/**
|
||||
* Get sidebar position.
|
||||
*
|
||||
* @returns {string} The position (left or right)
|
||||
*
|
||||
* @this Sidebar
|
||||
*/
|
||||
Sidebar.prototype.getPosition = function () {
|
||||
return this.options.position;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if sidebar is locked (always open).
|
||||
*
|
||||
* @returns {boolean}
|
||||
*
|
||||
* @this Sidebar
|
||||
*/
|
||||
Sidebar.prototype.isLocked = function () {
|
||||
return this.options.locked;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if sidebar is locked (always open).
|
||||
*
|
||||
* @returns {boolean}
|
||||
*
|
||||
* @this Sidebar
|
||||
*/
|
||||
Sidebar.prototype.isOpen = function () {
|
||||
return this.$wrapper.hasClass(this.options.classOpen);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if sidebar is fully opened.
|
||||
*
|
||||
* @return {boolean}
|
||||
*
|
||||
* @this Sidebar
|
||||
*/
|
||||
Sidebar.prototype.isFullyOpened = function () {
|
||||
return this.$element.hasClass(this.options.classForceOpen);
|
||||
};
|
||||
|
||||
/**
|
||||
* Force open the sidebar.
|
||||
*
|
||||
* @this Sidebar
|
||||
*/
|
||||
Sidebar.prototype.forceOpen = function () {
|
||||
if (this.isOpen() && this.isFullyOpened()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$element.addClass(this.options.classForceOpen);
|
||||
this.open();
|
||||
this.$toggle.removeClass(this.options.classToggle + '-opened');
|
||||
};
|
||||
|
||||
/**
|
||||
* Force close the sidebar.
|
||||
*
|
||||
* @this Sidebar
|
||||
*/
|
||||
Sidebar.prototype.forceClose = function () {
|
||||
if (!this.isOpen() || (this.isLocked() && isOverMinWidth(this))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$element.removeClass(this.options.classForceOpen);
|
||||
this.close();
|
||||
};
|
||||
|
||||
/**
|
||||
* Open the sidebar.
|
||||
*
|
||||
* @this Sidebar
|
||||
*/
|
||||
Sidebar.prototype.open = function () {
|
||||
if (this.isOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('[data-sidebar=true]').sidebar('forceClose');
|
||||
this.$wrapper.addClass(this.options.classOpen);
|
||||
this.$toggle.addClass(this.options.classToggle + '-opened');
|
||||
$(document).on(this.eventType + '.st.sidebar' + this.guid, null, this, closeExternal);
|
||||
};
|
||||
|
||||
/**
|
||||
* Close open the sidebar.
|
||||
*
|
||||
* @this Sidebar
|
||||
*/
|
||||
Sidebar.prototype.close = function () {
|
||||
if (!this.isOpen() || (this.isFullyOpened() && isOverMinWidth(this))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$wrapper.removeClass(this.options.classOpen);
|
||||
this.$toggle.removeClass(this.options.classToggle + '-opened');
|
||||
$(document).off(this.eventType + '.st.sidebar' + this.guid, closeExternal);
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle the sidebar ("close, "open", "force open").
|
||||
*
|
||||
* @param {jQuery.Event|Event} [event]
|
||||
*
|
||||
* @typedef {Sidebar} Event.data The sidebar instance
|
||||
*
|
||||
* @this Sidebar
|
||||
*/
|
||||
Sidebar.prototype.toggle = function (event) {
|
||||
var self = (undefined !== event) ? event.data : this,
|
||||
$target,
|
||||
$parents;
|
||||
|
||||
if (event) {
|
||||
$target = $(event.target);
|
||||
$parents = $target.parents('.' + self.options.classWrapper);
|
||||
event.stopPropagation();
|
||||
|
||||
if ($target.hasClass(self.options.classToggle) || $target.parents('.' + self.options.classToggle).size() > 0) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if ($parents.size() > 0 || $target.hasClass('sidebar-swipe')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (self.isOpen()) {
|
||||
if (self.isFullyOpened()) {
|
||||
self.forceClose();
|
||||
|
||||
} else if (isOverMinWidth(self) && $.inArray(self.options.forceToggle, [true, 'always']) >= 0) {
|
||||
self.forceOpen();
|
||||
|
||||
} else {
|
||||
self.close();
|
||||
}
|
||||
|
||||
} else if (isOverMinWidth(self) && 'always' === self.options.forceToggle) {
|
||||
self.forceOpen();
|
||||
|
||||
} else {
|
||||
self.open();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy instance.
|
||||
*
|
||||
* @this Sidebar
|
||||
*/
|
||||
Sidebar.prototype.destroy = function () {
|
||||
if (!mobileCheck()) {
|
||||
this.$element.off('mouseover.st.sidebar' + this.guid, $.proxy(Sidebar.prototype.open, this));
|
||||
this.$element.off('mouseout.st.sidebar' + this.guid, $.proxy(Sidebar.prototype.close, this));
|
||||
}
|
||||
|
||||
$(document).off(this.eventType + '.st.sidebar' + this.guid, closeExternal);
|
||||
$(window).off('resize.st.sidebar' + this.guid, onResizeWindow);
|
||||
this.$toggle.off(this.eventType + '.st.sidebar' + this.guid, Sidebar.prototype.toggle);
|
||||
$(window).off('keyup.st.sidebar' + this.guid, keyboardAction);
|
||||
destroyHammer(this);
|
||||
|
||||
if (undefined !== this.stickyHeader) {
|
||||
this.stickyHeader.destroy();
|
||||
}
|
||||
|
||||
this.$element.removeData('st.sidebar');
|
||||
};
|
||||
|
||||
|
||||
// SIDEBAR PLUGIN DEFINITION
|
||||
// =========================
|
||||
|
||||
function Plugin(option, value) {
|
||||
return this.each(function () {
|
||||
var $this = $(this),
|
||||
data = $this.data('st.sidebar'),
|
||||
options = typeof option === 'object' && option;
|
||||
|
||||
if (!data && option === 'destroy') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
$this.data('st.sidebar', (data = new Sidebar(this, options)));
|
||||
}
|
||||
|
||||
if (typeof option === 'string') {
|
||||
data[option](value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
old = $.fn.sidebar;
|
||||
|
||||
$.fn.sidebar = Plugin;
|
||||
$.fn.sidebar.Constructor = Sidebar;
|
||||
|
||||
|
||||
// SIDEBAR NO CONFLICT
|
||||
// ===================
|
||||
|
||||
$.fn.sidebar.noConflict = function () {
|
||||
$.fn.sidebar = old;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
// SIDEBAR DATA-API
|
||||
// ================
|
||||
|
||||
$(window).on('load', function () {
|
||||
$('[data-sidebar="true"]').each(function () {
|
||||
var $this = $(this);
|
||||
Plugin.call($this, $this.data());
|
||||
});
|
||||
});
|
||||
|
||||
}(jQuery));
|
||||
215
routes/actions.go
Normal file
215
routes/actions.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/go-chi/chi"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// NewActionRouter returns a new action router
|
||||
func NewActionRouter(m *models.Model) http.Handler {
|
||||
router := chi.NewRouter()
|
||||
router.Get("/", getActionsFunc(m))
|
||||
router.Post("/", postActionFunc(m))
|
||||
router.Get("/{actionid}", getActionByIDFunc(m))
|
||||
router.Put("/{actionid}", putActionFunc(m))
|
||||
return router
|
||||
}
|
||||
|
||||
func getActionsFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, userErr := tokens.GetUserID(r.Context())
|
||||
if userErr != nil {
|
||||
unauthorizedHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
actions []*models.Action
|
||||
err error
|
||||
)
|
||||
planIDString := r.URL.Query().Get("plan_id")
|
||||
if planIDString == "" {
|
||||
actions, err = m.Actions(userID)
|
||||
} else {
|
||||
planID, convErr := strconv.ParseInt(planIDString, 10, 64)
|
||||
if convErr != nil {
|
||||
actions = []*models.Action{}
|
||||
err = nil
|
||||
} else {
|
||||
plan := &models.Plan{PlanID: planID}
|
||||
actions, err = m.GetActions(plan, userID)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(actions); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getActionByIDFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
userID, userErr := tokens.GetUserID(r.Context())
|
||||
if userErr != nil {
|
||||
unauthorizedHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(chi.URLParam(r, "actionid"))
|
||||
if err != nil {
|
||||
notFoundHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
action, err := m.Action(id, userID)
|
||||
if err != nil {
|
||||
if models.IsNotFoundError(err) {
|
||||
notFoundHandler(w, r)
|
||||
return
|
||||
}
|
||||
serverError(w, err)
|
||||
return
|
||||
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(action); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type createActionResponse struct {
|
||||
CreatedAction *models.Action `json:"created_action"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func postActionFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
userID, err := tokens.GetUserID(r.Context())
|
||||
if err != nil {
|
||||
unauthorizedHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 1024)
|
||||
dec := json.NewDecoder(r.Body)
|
||||
dec.DisallowUnknownFields()
|
||||
var a models.Action
|
||||
err = dec.Decode(&a)
|
||||
if err != nil {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
err = dec.Decode(&struct{}{})
|
||||
if err != io.EOF {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
action := &models.Action{
|
||||
ActionDescription: a.ActionDescription,
|
||||
EstimatedChunks: a.EstimatedChunks,
|
||||
CompletedChunks: a.CompletedChunks,
|
||||
CompletedOn: a.CompletedOn,
|
||||
PlanID: a.PlanID,
|
||||
}
|
||||
id, err := m.AddAction(action, userID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
action, err = m.Action(id, userID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
response := &createActionResponse{
|
||||
CreatedAction: action,
|
||||
ID: int64(id),
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
type updateActionResponse struct {
|
||||
UpdatedAction *models.Action `json:"updated_action"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func putActionFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
userID, err := tokens.GetUserID(r.Context())
|
||||
if err != nil {
|
||||
unauthorizedHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(chi.URLParam(r, "actionid"))
|
||||
if err != nil {
|
||||
notFoundHandler(w, r)
|
||||
return
|
||||
}
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 1024)
|
||||
dec := json.NewDecoder(r.Body)
|
||||
dec.DisallowUnknownFields()
|
||||
var a models.Action
|
||||
err = dec.Decode(&a)
|
||||
if err != nil {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
err = dec.Decode(&struct{}{})
|
||||
if err != io.EOF {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
action := &models.Action{
|
||||
ActionDescription: a.ActionDescription,
|
||||
EstimatedChunks: a.EstimatedChunks,
|
||||
CompletedChunks: a.CompletedChunks,
|
||||
CompletedOn: a.CompletedOn,
|
||||
PlanID: a.PlanID,
|
||||
ActionID: int64(id),
|
||||
}
|
||||
err = m.SaveAction(action, userID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
action, err = m.Action(id, userID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
response := &updateActionResponse{
|
||||
UpdatedAction: action,
|
||||
ID: int64(id),
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
297
routes/actions_test.go
Normal file
297
routes/actions_test.go
Normal file
@@ -0,0 +1,297 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEmptyActions(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewActionRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
expected := `[]`
|
||||
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestOneAction(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
createdDate, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
updatedDate, _ := time.Parse("2006-01-02", "2021-01-02")
|
||||
completedDate, _ := time.Parse("2006-01-02", "2021-01-03")
|
||||
a1 := &models.Action{ActionID: 3, ActionDescription: "testing", CompletedChunks: 1, CompletedOn: &completedDate, CreatedAt: &createdDate, UpdatedAt: &updatedDate, EstimatedChunks: 3, PlanID: 0}
|
||||
m := getEmptyModel()
|
||||
m.AddAction(a1, 3)
|
||||
|
||||
router := routes.NewActionRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `[
|
||||
{
|
||||
"action_id": 1,
|
||||
"action_description": "testing",
|
||||
"user_id": 3,
|
||||
"estimated_chunks": 3,
|
||||
"completed_chunks": 1,
|
||||
"completed_on": "2021-01-03T00:00:00Z",
|
||||
"updated_at": "2021-01-02T00:00:00Z",
|
||||
"created_at": "2021-01-01T00:00:00Z",
|
||||
"plan_id": 0
|
||||
}
|
||||
]`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestErrorAction(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorModel("Model always errors")
|
||||
|
||||
router := routes.NewActionRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestEmptyActionErrorWriter(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getEmptyModel()
|
||||
|
||||
router := routes.NewActionRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
|
||||
func TestOneActionByID(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
createdDate, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
updatedDate, _ := time.Parse("2006-01-02", "2021-01-02")
|
||||
a := &models.Action{ActionID: 6, ActionDescription: "howdy", CompletedOn: nil, CreatedAt: &createdDate, UpdatedAt: &updatedDate, CompletedChunks: 0, EstimatedChunks: 54, PlanID: 3}
|
||||
m := getEmptyModel()
|
||||
m.InsertAction(a, 3)
|
||||
router := routes.NewActionRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/1", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `{
|
||||
"action_id": 1,
|
||||
"action_description": "howdy",
|
||||
"user_id": 3,
|
||||
"estimated_chunks": 54,
|
||||
"completed_chunks": 0,
|
||||
"updated_at": "2021-01-02T00:00:00Z",
|
||||
"created_at": "2021-01-01T00:00:00Z",
|
||||
"plan_id": 3
|
||||
}`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestErrorActionByID(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorModel("Model always errors")
|
||||
|
||||
router := routes.NewActionRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/5", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestEmptyActionErrorWriterByID(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
a := &models.Action{ActionID: 6}
|
||||
m := getEmptyModel()
|
||||
m.AddAction(a, 3)
|
||||
|
||||
router := routes.NewActionRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/1", nil)
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
|
||||
func TestNotFoundActionByIDText(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getEmptyModel()
|
||||
|
||||
router := routes.NewActionRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/wo", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusNotFound, status)
|
||||
|
||||
}
|
||||
func TestNotFoundActionByIDEmpty(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getEmptyModel()
|
||||
|
||||
router := routes.NewActionRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/1", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusNotFound, status)
|
||||
|
||||
}
|
||||
|
||||
func TestActionsByPlanID(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
createdDate, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
updatedDate, _ := time.Parse("2006-01-02", "2021-01-02")
|
||||
a := &models.Action{ActionID: 1, ActionDescription: "howdy", CompletedOn: nil, CreatedAt: &createdDate, UpdatedAt: &updatedDate, CompletedChunks: 0, EstimatedChunks: 54, PlanID: 6}
|
||||
m := getEmptyModel()
|
||||
m.AddAction(a, 3)
|
||||
router := routes.NewActionRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/?plan_id=6", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `[
|
||||
{
|
||||
"action_id": 1,
|
||||
"action_description": "howdy",
|
||||
"user_id": 3,
|
||||
"estimated_chunks": 54,
|
||||
"completed_chunks": 0,
|
||||
"updated_at": "2021-01-02T00:00:00Z",
|
||||
"created_at": "2021-01-01T00:00:00Z",
|
||||
"plan_id": 6
|
||||
}
|
||||
]`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestActionsByPlanIDInvalidID(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
createdDate, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
updatedDate, _ := time.Parse("2006-01-02", "2021-01-02")
|
||||
a := &models.Action{ActionID: 6, ActionDescription: "howdy", CompletedOn: nil, CreatedAt: &createdDate, UpdatedAt: &updatedDate, CompletedChunks: 0, EstimatedChunks: 54, PlanID: 3}
|
||||
m := getEmptyModel()
|
||||
m.AddAction(a, 3)
|
||||
router := routes.NewActionRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/?plan_id=aoeu", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `[]`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
119
routes/actions_unauthorized_test.go
Normal file
119
routes/actions_unauthorized_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEmptyActionEmptyContext(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewActionRouter(m)
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
|
||||
}
|
||||
|
||||
func TestOneActionEmptyContext(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2006-01-02"
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription, UserID: 3}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
router := routes.NewActionRouter(m)
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
|
||||
}
|
||||
|
||||
func TestOneActionByIDEmptyContext(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription, UserID: 3}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
router := routes.NewActionRouter(m)
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/1", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
|
||||
}
|
||||
|
||||
func TestPureJSONActionEmptyContext(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewActionRouter(m)
|
||||
data := []byte(`{
|
||||
"plan_description": "2021-01-01T00:00:00Z",
|
||||
"plan_id": 1,
|
||||
"user_id": 3
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
}
|
||||
|
||||
func TestPutActionEmptyContext(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
a := &models.Action{PlanID: 6}
|
||||
m := getEmptyModel()
|
||||
m.AddAction(a, 3)
|
||||
|
||||
router := routes.NewActionRouter(m)
|
||||
data, _ := json.Marshal(a)
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
|
||||
}
|
||||
103
routes/auth.go
Normal file
103
routes/auth.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/go-chi/chi"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewAuthRouter returns a new auth router.
|
||||
func NewAuthRouter(m *models.Model, tok tokens.Toker) http.Handler {
|
||||
router := chi.NewRouter()
|
||||
router.Post("/register", postUserFunc(m))
|
||||
router.Post("/tokens", createTokenFunc(m, tok))
|
||||
return router
|
||||
}
|
||||
|
||||
type createUserResponse struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
func postUserFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 1024)
|
||||
dec := json.NewDecoder(r.Body)
|
||||
dec.DisallowUnknownFields()
|
||||
var req models.CreateUserRequest
|
||||
err := dec.Decode(&req)
|
||||
if err != nil {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
err = dec.Decode(&struct{}{})
|
||||
if err != io.EOF {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = m.CreateUser(&req)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
response := &createUserResponse{
|
||||
Username: req.Username,
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
type loginCreds struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
type createdToken struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func createTokenFunc(m *models.Model, tok tokens.Toker) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 1024)
|
||||
dec := json.NewDecoder(r.Body)
|
||||
dec.DisallowUnknownFields()
|
||||
var creds loginCreds
|
||||
err := dec.Decode(&creds)
|
||||
if err != nil {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
err = dec.Decode(&struct{}{})
|
||||
if err != io.EOF {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := m.VerifyUserByUsernamePassword(creds.Username, creds.Password)
|
||||
if err != nil {
|
||||
if models.IsInvalidLoginError(err) {
|
||||
unauthorizedHandler(w, r)
|
||||
return
|
||||
}
|
||||
serverError(w, err)
|
||||
return
|
||||
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
response := &createdToken{Token: tok.EncodeUser(user)}
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
202
routes/auth_login_test.go
Normal file
202
routes/auth_login_test.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoginAuth(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getEmptyModel()
|
||||
username := "testing_username"
|
||||
displayName := "testing_name"
|
||||
password := "pass"
|
||||
m.CreateUser(&models.CreateUserRequest{Username: username, DisplayName: displayName, Password: password})
|
||||
|
||||
toker := tokens.GetDeterministicToker()
|
||||
data := []byte(`{
|
||||
"username": "testing_username",
|
||||
"password": "pass"
|
||||
}`)
|
||||
req, _ := http.NewRequest("POST", "/tokens", bytes.NewBuffer(data))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router := routes.NewAuthRouter(m, toker)
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
expected := `{
|
||||
"token": "{\"ID\":1,\"Username\":\"testing_username\"}"
|
||||
}`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
|
||||
}
|
||||
|
||||
func TestLoginBadCreds(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
toker := tokens.GetDeterministicToker()
|
||||
data := []byte(`{
|
||||
"username": "testing_use
|
||||
}`)
|
||||
req, _ := http.NewRequest("POST", "/tokens", bytes.NewBuffer(data))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router := routes.NewAuthRouter(m, toker)
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
|
||||
}
|
||||
|
||||
func TestLoginBadRequestTwoBodies(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
toker := tokens.GetDeterministicToker()
|
||||
data := []byte(`{
|
||||
"username": "testing_username",
|
||||
"password": "pass"
|
||||
}{
|
||||
"username": "testing_username",
|
||||
"password": "pass"
|
||||
}`)
|
||||
req, _ := http.NewRequest("POST", "/tokens", bytes.NewBuffer(data))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router := routes.NewAuthRouter(m, toker)
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
}
|
||||
|
||||
func TestLoginAuthWrongPass(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getEmptyModel()
|
||||
username := "testing_username"
|
||||
displayName := "testing_name"
|
||||
password := "pass"
|
||||
m.CreateUser(&models.CreateUserRequest{Username: username, DisplayName: displayName, Password: password})
|
||||
|
||||
toker := tokens.GetDeterministicToker()
|
||||
data := []byte(`{
|
||||
"username": "testing_username",
|
||||
"password": "badpass"
|
||||
}`)
|
||||
req, _ := http.NewRequest("POST", "/tokens", bytes.NewBuffer(data))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router := routes.NewAuthRouter(m, toker)
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
|
||||
}
|
||||
|
||||
func TestLoginErrorModel(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorModel("error")
|
||||
|
||||
toker := tokens.GetDeterministicToker()
|
||||
data := []byte(`{
|
||||
"username": "testing_username",
|
||||
"password": "badpass"
|
||||
}`)
|
||||
req, _ := http.NewRequest("POST", "/tokens", bytes.NewBuffer(data))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router := routes.NewAuthRouter(m, toker)
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
|
||||
func TestLoginBadWriter(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getEmptyModel()
|
||||
username := "testing_username"
|
||||
displayName := "testing_name"
|
||||
password := "pass"
|
||||
m.CreateUser(&models.CreateUserRequest{Username: username, DisplayName: displayName, Password: password})
|
||||
|
||||
toker := tokens.GetDeterministicToker()
|
||||
data := []byte(`{
|
||||
"username": "testing_username",
|
||||
"password": "pass"
|
||||
}`)
|
||||
req, _ := http.NewRequest("POST", "/tokens", bytes.NewBuffer(data))
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router := routes.NewAuthRouter(m, toker)
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// func TestRegisterBadWriter(t *testing.T) {
|
||||
// // set up
|
||||
// assert := assert.New(t)
|
||||
// m := getEmptyModel()
|
||||
// toker := tokens.New("secret")
|
||||
// data := []byte(`{
|
||||
// "username": "test",
|
||||
// "password": "pass",
|
||||
// "display_name": "My Display Name"
|
||||
// }`)
|
||||
// req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(data))
|
||||
//
|
||||
// rr := NewBadWriter()
|
||||
//
|
||||
// // function under test
|
||||
// router := routes.NewAuthRouter(m, toker)
|
||||
// router.ServeHTTP(rr, req)
|
||||
//
|
||||
// // check results
|
||||
// status := rr.Code
|
||||
// assert.Equal(http.StatusInternalServerError, status)
|
||||
//
|
||||
// }
|
||||
139
routes/auth_register_test.go
Normal file
139
routes/auth_register_test.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegisterAuth(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
toker := tokens.New("secret")
|
||||
data := []byte(`{
|
||||
"username": "test",
|
||||
"password": "pass",
|
||||
"display_name": "My Display Name"
|
||||
}`)
|
||||
req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(data))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router := routes.NewAuthRouter(m, toker)
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusCreated, status)
|
||||
expected := `{
|
||||
"username": "test"
|
||||
}`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
|
||||
}
|
||||
|
||||
func TestRegisterBadRequestAuth(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
toker := tokens.New("secret")
|
||||
data := []byte(`{
|
||||
"username": y Display Name"
|
||||
}`)
|
||||
req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(data))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router := routes.NewAuthRouter(m, toker)
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
|
||||
}
|
||||
|
||||
func TestRegisterBadRequestTwoBodies(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
toker := tokens.New("secret")
|
||||
data := []byte(`{
|
||||
"username": "test",
|
||||
"password": "pass",
|
||||
"display_name": "My Display Name"
|
||||
}, {
|
||||
"username": "test",
|
||||
"password": "pass",
|
||||
"display_name": "My Display Name"
|
||||
}`)
|
||||
req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(data))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router := routes.NewAuthRouter(m, toker)
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
|
||||
}
|
||||
|
||||
func TestRegisterErrorModel(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getErrorModel("here's an error")
|
||||
toker := tokens.New("secret")
|
||||
data := []byte(`{
|
||||
"username": "test",
|
||||
"password": "pass",
|
||||
"display_name": "My Display Name"
|
||||
}`)
|
||||
req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(data))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router := routes.NewAuthRouter(m, toker)
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
|
||||
func TestRegisterBadWriter(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
toker := tokens.New("secret")
|
||||
data := []byte(`{
|
||||
"username": "test",
|
||||
"password": "pass",
|
||||
"display_name": "My Display Name"
|
||||
}`)
|
||||
req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(data))
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router := routes.NewAuthRouter(m, toker)
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
48
routes/currentUser.go
Normal file
48
routes/currentUser.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/go-chi/chi"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewCurrentUserRouter returns a new router for getting the current user.
|
||||
func NewCurrentUserRouter(m *models.Model) http.Handler {
|
||||
router := chi.NewRouter()
|
||||
router.Get("/", getMeFunc(m))
|
||||
return router
|
||||
}
|
||||
|
||||
func getMeFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, err := tokens.GetUserID(r.Context())
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
unauthorizedHandler(w, r)
|
||||
return
|
||||
}
|
||||
username, err := tokens.GetUsername(r.Context())
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
unauthorizedHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := m.UserByUsername(username, userID)
|
||||
if err != nil {
|
||||
if models.IsNotFoundError(err) {
|
||||
notFoundHandler(w, r)
|
||||
return
|
||||
}
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(user); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
158
routes/currentUser_test.go
Normal file
158
routes/currentUser_test.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEmptyCurrentUser(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewCurrentUserRouter(m)
|
||||
req, _ := http.NewRequestWithContext(tokens.GetContextForUserValues(3, "testing"), "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusNotFound, status)
|
||||
|
||||
}
|
||||
|
||||
func TestSingleUser(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
|
||||
idToUse := 1
|
||||
username := "testing_username"
|
||||
displayName := "testing_name"
|
||||
password := "pass"
|
||||
m.CreateUser(&models.CreateUserRequest{Username: username, DisplayName: displayName, Password: password})
|
||||
|
||||
router := routes.NewCurrentUserRouter(m)
|
||||
req, _ := http.NewRequestWithContext(tokens.GetContextForUserValues(idToUse, username), "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
expected := `{
|
||||
"user_id": 1,
|
||||
"username": "testing_username",
|
||||
"display_name": "testing_name"
|
||||
}`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
|
||||
}
|
||||
|
||||
func TestSingleUserEmptyContext(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
|
||||
username := "testing_username"
|
||||
displayName := "testing_name"
|
||||
password := "pass"
|
||||
m.CreateUser(&models.CreateUserRequest{Username: username, DisplayName: displayName, Password: password})
|
||||
|
||||
router := routes.NewCurrentUserRouter(m)
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
|
||||
}
|
||||
|
||||
func TestSingleUserContextNoUserID(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
|
||||
idToUse := 1
|
||||
username := "testing_username"
|
||||
displayName := "testing_name"
|
||||
password := "pass"
|
||||
m.CreateUser(&models.CreateUserRequest{Username: username, DisplayName: displayName, Password: password})
|
||||
|
||||
router := routes.NewCurrentUserRouter(m)
|
||||
req, _ := http.NewRequestWithContext(tokens.SetUserID(context.Background(), idToUse), "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
|
||||
}
|
||||
|
||||
func TestErrorUserContextNoUserID(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getErrorModel("Here's an error.")
|
||||
|
||||
idToUse := 1
|
||||
|
||||
router := routes.NewCurrentUserRouter(m)
|
||||
req, _ := http.NewRequestWithContext(tokens.GetContextForUserValues(idToUse, "username"), "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
|
||||
func TestSingleUserErrorWriter(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
|
||||
idToUse := 1
|
||||
username := "testing_username"
|
||||
displayName := "testing_name"
|
||||
password := "pass"
|
||||
m.CreateUser(&models.CreateUserRequest{Username: username, DisplayName: displayName, Password: password})
|
||||
|
||||
router := routes.NewCurrentUserRouter(m)
|
||||
req, _ := http.NewRequestWithContext(tokens.GetContextForUserValues(idToUse, username), "GET", "/", nil)
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
160
routes/current_plan.go
Normal file
160
routes/current_plan.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/go-chi/chi"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewCurrentPlanRouter returns a new primary plan router
|
||||
func NewCurrentPlanRouter(m *models.Model) http.Handler {
|
||||
router := chi.NewRouter()
|
||||
router.Get("/", getCurrentPlanFunc(m))
|
||||
router.Post("/", postCurrentPlanFunc(m))
|
||||
router.Put("/", putCurrentPlanFunc(m))
|
||||
return router
|
||||
}
|
||||
|
||||
func getCurrentPlanFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, userErr := tokens.GetUserID(r.Context())
|
||||
if userErr != nil {
|
||||
unauthorizedHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
pp, err := m.CurrentPlan(userID)
|
||||
if err != nil {
|
||||
if models.IsNotFoundError(err) {
|
||||
notFoundHandler(w, r)
|
||||
return
|
||||
}
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(pp); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type createCurrentPlanResponse struct {
|
||||
CreatedCurrentPlan *models.CurrentPlan `json:"created_current_plan"`
|
||||
}
|
||||
|
||||
func postCurrentPlanFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
userID, err := tokens.GetUserID(r.Context())
|
||||
if err != nil {
|
||||
unauthorizedHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 1024)
|
||||
dec := json.NewDecoder(r.Body)
|
||||
dec.DisallowUnknownFields()
|
||||
var pp models.CurrentPlan
|
||||
err = dec.Decode(&pp)
|
||||
if err != nil {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
err = dec.Decode(&struct{}{})
|
||||
if err != io.EOF {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
newPP := &models.CurrentPlan{
|
||||
PlanID: pp.PlanID,
|
||||
UserID: int64(userID),
|
||||
}
|
||||
err = m.AddCurrentPlan(newPP, userID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
finishedPP, err := m.CurrentPlan(userID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
response := &createCurrentPlanResponse{
|
||||
CreatedCurrentPlan: finishedPP,
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
type updateCurrentPlanResponse struct {
|
||||
UpdatedCurrentPlan *models.CurrentPlan `json:"updated_current_plan"`
|
||||
}
|
||||
|
||||
func putCurrentPlanFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
userID, err := tokens.GetUserID(r.Context())
|
||||
if err != nil {
|
||||
unauthorizedHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = m.CurrentPlan(userID)
|
||||
if models.IsNotFoundError(err) {
|
||||
notFoundHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 1024)
|
||||
dec := json.NewDecoder(r.Body)
|
||||
dec.DisallowUnknownFields()
|
||||
var pp models.CurrentPlan
|
||||
err = dec.Decode(&pp)
|
||||
if err != nil {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
err = dec.Decode(&struct{}{})
|
||||
if err != io.EOF {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
newPP := &models.CurrentPlan{
|
||||
PlanID: pp.PlanID,
|
||||
UserID: int64(userID),
|
||||
}
|
||||
err = m.SaveCurrentPlan(newPP, userID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
newPP, err = m.CurrentPlan(userID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
response := &updateCurrentPlanResponse{
|
||||
UpdatedCurrentPlan: newPP,
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
228
routes/current_plan_post_test.go
Normal file
228
routes/current_plan_post_test.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPostSinglePlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
|
||||
pp := &models.CurrentPlan{PlanID: 1}
|
||||
data, _ := json.Marshal(pp)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusCreated, status)
|
||||
}
|
||||
|
||||
func TestPostCurrentPlanUnauth(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
|
||||
pp := &models.CurrentPlan{PlanID: 1}
|
||||
data, _ := json.Marshal(pp)
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
}
|
||||
|
||||
func TestExtraFieldJSONCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
|
||||
data := []byte(`{
|
||||
"plan_id": 5,
|
||||
"sabotage": "omg"
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestEmptyBodyJSONCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
|
||||
data := []byte(``)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestTwoBodyCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
|
||||
data := []byte(`{
|
||||
"plan_id": 5
|
||||
}, {
|
||||
"plan_id": 7
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorCreateCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getErrorModel("error model")
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
|
||||
data := []byte(`{
|
||||
"plan_id": 5
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorOnRetrieveCreateCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getErrorOnGetModel("error model")
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
|
||||
data := []byte(`{
|
||||
"plan_id": 5
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorWriterCreateCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
|
||||
data := []byte(`{
|
||||
"plan_id": 5
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := NewBadWriter()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
}
|
||||
269
routes/current_plan_put_test.go
Normal file
269
routes/current_plan_put_test.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPutSinglePlanNotFound(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddPlan(plan, 3)
|
||||
|
||||
pp := &models.CurrentPlan{PlanID: 1}
|
||||
data, _ := json.Marshal(pp)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusNotFound, status)
|
||||
}
|
||||
|
||||
func TestPutSinglePlanNonDup(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddCurrentPlan(&models.CurrentPlan{PlanID: 1}, 3)
|
||||
|
||||
pp := &models.CurrentPlan{PlanID: 2}
|
||||
data, _ := json.Marshal(pp)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
}
|
||||
|
||||
func TestPutCurrentPlanUnauth(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddCurrentPlan(&models.CurrentPlan{PlanID: 1}, 3)
|
||||
|
||||
pp := &models.CurrentPlan{PlanID: 1}
|
||||
data, _ := json.Marshal(pp)
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "PUT", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
}
|
||||
|
||||
func TestExtraFieldJSONPutCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddCurrentPlan(&models.CurrentPlan{PlanID: 1}, 3)
|
||||
|
||||
data := []byte(`{
|
||||
"plan_id": 5,
|
||||
"sabotage": "omg"
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestEmptyBodyJSONPutCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddCurrentPlan(&models.CurrentPlan{PlanID: 1}, 3)
|
||||
|
||||
data := []byte(``)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestTwoBodyPutCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddCurrentPlan(&models.CurrentPlan{PlanID: 1}, 3)
|
||||
|
||||
data := []byte(`{
|
||||
"plan_id": 5
|
||||
}, {
|
||||
"plan_id": 7
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorPutCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getErrorModel("error model")
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddCurrentPlan(&models.CurrentPlan{PlanID: 1}, 3)
|
||||
|
||||
data := []byte(`{
|
||||
"plan_id": 5
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorOnRetrievePutCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getErrorOnGetModel("error model")
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddCurrentPlan(&models.CurrentPlan{PlanID: 1}, 3)
|
||||
|
||||
data := []byte(`{
|
||||
"plan_id": 5
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorWriterPutCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
|
||||
plan := &models.Plan{}
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddPlan(plan, 3)
|
||||
m.AddCurrentPlan(&models.CurrentPlan{PlanID: 1}, 3)
|
||||
|
||||
data := []byte(`{
|
||||
"plan_id": 5
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := NewBadWriter()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
}
|
||||
117
routes/current_plan_test.go
Normal file
117
routes/current_plan_test.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
// "gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEmptyCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusNotFound, status)
|
||||
}
|
||||
|
||||
func TestEmptyCurrentPlanUnauth(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
}
|
||||
|
||||
func TestErrorCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorModel("Model always errors")
|
||||
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestEmptyCurrentPlanErrorWriter(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getEmptyModel()
|
||||
m.AddCurrentPlan(&models.CurrentPlan{PlanID: 3}, 3)
|
||||
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
|
||||
func TestSingleCurrentPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
m.AddCurrentPlan(&models.CurrentPlan{PlanID: 3}, 3)
|
||||
|
||||
router := routes.NewCurrentPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `{
|
||||
"plan_id": 3,
|
||||
"user_id": 3
|
||||
}`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
32
routes/errors.go
Normal file
32
routes/errors.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func serverError(w http.ResponseWriter, err error) {
|
||||
code := http.StatusInternalServerError
|
||||
log.Printf("received error: {%v}", err)
|
||||
http.Error(w, http.StatusText(code), code)
|
||||
}
|
||||
|
||||
func badRequestError(w http.ResponseWriter, err error) {
|
||||
code := http.StatusBadRequest
|
||||
log.Printf("received error: {%v}", err)
|
||||
http.Error(w, http.StatusText(code), code)
|
||||
}
|
||||
|
||||
func methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) {
|
||||
code := http.StatusMethodNotAllowed
|
||||
http.Error(w, http.StatusText(code), code)
|
||||
}
|
||||
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||
code := http.StatusNotFound
|
||||
http.Error(w, http.StatusText(code), code)
|
||||
}
|
||||
|
||||
func unauthorizedHandler(w http.ResponseWriter, r *http.Request) {
|
||||
code := http.StatusUnauthorized
|
||||
http.Error(w, http.StatusText(code), code)
|
||||
}
|
||||
58
routes/health.go
Normal file
58
routes/health.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"github.com/go-chi/chi"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func newHealthRouter(m *models.Model) http.Handler {
|
||||
router := chi.NewRouter()
|
||||
router.Get("/", getHealthFunc(m))
|
||||
return router
|
||||
}
|
||||
|
||||
type healthCheck struct {
|
||||
Name string `json:"name"`
|
||||
Healthy bool `json:"healthy"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func getHealthFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var healths []*healthCheck
|
||||
|
||||
healths = append(healths, dbHealth(m))
|
||||
|
||||
code := http.StatusOK
|
||||
for _, h := range healths {
|
||||
if !h.Healthy {
|
||||
code = http.StatusInternalServerError
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(code)
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(healths); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dbHealth(m *models.Model) *healthCheck {
|
||||
errMessage := ""
|
||||
health := true
|
||||
name := "Store"
|
||||
err := m.Healthy()
|
||||
if err != nil {
|
||||
errMessage = err.Error()
|
||||
health = false
|
||||
}
|
||||
return &healthCheck{
|
||||
Name: name,
|
||||
Healthy: health,
|
||||
Message: errMessage,
|
||||
}
|
||||
}
|
||||
88
routes/health_test.go
Normal file
88
routes/health_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEmptyHeatlhErrorWriter(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getEmptyModel()
|
||||
|
||||
router := routes.NewRouter(m, tokens.New("whatever"))
|
||||
req, _ := http.NewRequest("GET", "/health", nil)
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
|
||||
func TestEmptyHealth(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewRouter(m, tokens.New("whatever"))
|
||||
req, _ := http.NewRequest("GET", "/health", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
expected := `[
|
||||
{
|
||||
"name": "Store",
|
||||
"healthy": true,
|
||||
"message": ""
|
||||
}
|
||||
]`
|
||||
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestUnhealthyDB(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
errorMsg := "error"
|
||||
m := getErrorModel(errorMsg)
|
||||
router := routes.NewRouter(m, tokens.New("whatever"))
|
||||
req, _ := http.NewRequest("GET", "/health", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
expected := fmt.Sprintf(`[
|
||||
{
|
||||
"name": "Store",
|
||||
"healthy": false,
|
||||
"message": "%s"
|
||||
}
|
||||
]`, errorMsg)
|
||||
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
29
routes/http_util_test.go
Normal file
29
routes/http_util_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type BadResponseWriter struct {
|
||||
Code int
|
||||
header http.Header
|
||||
}
|
||||
|
||||
func NewBadWriter() *BadResponseWriter {
|
||||
return &BadResponseWriter{
|
||||
header: http.Header{},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *BadResponseWriter) Header() http.Header {
|
||||
return w.header
|
||||
}
|
||||
|
||||
func (w *BadResponseWriter) Write(b []byte) (int, error) {
|
||||
return 0, fmt.Errorf("always an error")
|
||||
}
|
||||
|
||||
func (w *BadResponseWriter) WriteHeader(statusCode int) {
|
||||
w.Code = statusCode
|
||||
}
|
||||
218
routes/plan_put_test.go
Normal file
218
routes/plan_put_test.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPureJSONPutPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
p := &models.Plan{
|
||||
PlanID: 1,
|
||||
PlanDescription: "hn",
|
||||
}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
router := routes.NewPlanRouter(m)
|
||||
data := []byte(`{
|
||||
"plan_id": 1,
|
||||
"plan_description": "testing"
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
expected := `{
|
||||
"updated_plan": {
|
||||
"plan_description": "testing",
|
||||
"plan_id": 1,
|
||||
"user_id": 3
|
||||
},
|
||||
"id": 1
|
||||
}`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestExtraFieldPlanPutJSON(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
p := &models.Plan{
|
||||
PlanID: 1,
|
||||
PlanDescription: "hn",
|
||||
}
|
||||
m.AddPlan(p, 3)
|
||||
router := routes.NewPlanRouter(m)
|
||||
data := []byte(`{
|
||||
"plan_id": 1,
|
||||
"plan_description": "testing",
|
||||
"sabotage": "omg"
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestEmptyBodyPlanPut(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewPlanRouter(m)
|
||||
data := []byte(``)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestTwoBodyPlanPut(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewPlanRouter(m)
|
||||
data := []byte(`{
|
||||
"plan_id": 5
|
||||
}, {
|
||||
"plan_id": 6
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestBadPlanIDPut(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewPlanRouter(m)
|
||||
data := []byte(`{
|
||||
"plan_id": 5
|
||||
}, {
|
||||
"plan_id": 6
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/text", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusNotFound, status)
|
||||
expected := `Not Found`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorUpdatePlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorModel("Model always errors")
|
||||
|
||||
router := routes.NewPlanRouter(m)
|
||||
p := &models.Plan{PlanID: 6}
|
||||
data, _ := json.Marshal(p)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorOnRetrieveUpdatePlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorOnGetModel("Model always errors")
|
||||
|
||||
router := routes.NewPlanRouter(m)
|
||||
p := &models.Plan{PlanID: 6}
|
||||
data, _ := json.Marshal(p)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorWriterUpdatePlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
p := &models.Plan{PlanID: 6}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
|
||||
router := routes.NewPlanRouter(m)
|
||||
data, _ := json.Marshal(p)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
188
routes/plans.go
Normal file
188
routes/plans.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/go-chi/chi"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// NewPlanRouter returns the http.Handler for the passed in model to route plan methods.
|
||||
func NewPlanRouter(m *models.Model) http.Handler {
|
||||
router := chi.NewRouter()
|
||||
router.Get("/", getAllPlansFunc(m))
|
||||
router.Post("/", postPlanFunc(m))
|
||||
router.Get("/{planid}", getPlanByIDFunc(m))
|
||||
router.Put("/{planid}", putPlanFunc(m))
|
||||
return router
|
||||
}
|
||||
|
||||
func getAllPlansFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, err := tokens.GetUserID(r.Context())
|
||||
if err != nil {
|
||||
unauthorizedHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
plans, err := m.Plans(userID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(plans); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getPlanByIDFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, err := tokens.GetUserID(r.Context())
|
||||
if err != nil {
|
||||
unauthorizedHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(chi.URLParam(r, "planid"))
|
||||
if err != nil {
|
||||
notFoundHandler(w, r)
|
||||
return
|
||||
}
|
||||
// todo get real user id
|
||||
plan, err := m.Plan(id, userID)
|
||||
if err != nil {
|
||||
if models.IsNotFoundError(err) {
|
||||
notFoundHandler(w, r)
|
||||
return
|
||||
}
|
||||
serverError(w, err)
|
||||
return
|
||||
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(plan); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type createPlanResponse struct {
|
||||
CreatedPlan *models.Plan `json:"created_plan"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func postPlanFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, err := tokens.GetUserID(r.Context())
|
||||
if err != nil {
|
||||
unauthorizedHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 1024)
|
||||
dec := json.NewDecoder(r.Body)
|
||||
dec.DisallowUnknownFields()
|
||||
var p models.Plan
|
||||
err = dec.Decode(&p)
|
||||
if err != nil {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
err = dec.Decode(&struct{}{})
|
||||
if err != io.EOF {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Map the fields we allow to be set to the plan to be created.
|
||||
plan := &models.Plan{PlanDescription: p.PlanDescription, UserID: p.UserID}
|
||||
id, err := m.AddPlan(plan, userID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
plan, err = m.Plan(id, userID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
response := &createPlanResponse{
|
||||
CreatedPlan: plan,
|
||||
ID: int64(id),
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
type updatePlanResponse struct {
|
||||
UpdatedPlan *models.Plan `json:"updated_plan"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func putPlanFunc(m *models.Model) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
userID, err := tokens.GetUserID(r.Context())
|
||||
if err != nil {
|
||||
unauthorizedHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(chi.URLParam(r, "planid"))
|
||||
if err != nil {
|
||||
notFoundHandler(w, r)
|
||||
return
|
||||
}
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 1024)
|
||||
dec := json.NewDecoder(r.Body)
|
||||
dec.DisallowUnknownFields()
|
||||
var p models.Plan
|
||||
err = dec.Decode(&p)
|
||||
if err != nil {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
err = dec.Decode(&struct{}{})
|
||||
if err != io.EOF {
|
||||
badRequestError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
plan := &models.Plan{
|
||||
PlanDescription: p.PlanDescription,
|
||||
PlanID: int64(id),
|
||||
}
|
||||
err = m.SavePlan(plan, userID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
plan, err = m.Plan(id, userID)
|
||||
if err != nil {
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
response := &updatePlanResponse{
|
||||
UpdatedPlan: plan,
|
||||
ID: int64(id),
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
220
routes/plans_test.go
Normal file
220
routes/plans_test.go
Normal file
@@ -0,0 +1,220 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var sampleContext = tokens.GetContextForUserValues(3, "testing")
|
||||
|
||||
func TestEmptyPlans(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
expected := `[]`
|
||||
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestOnePlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription, UserID: 3}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
router := routes.NewPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `[
|
||||
{
|
||||
"plan_id": 1,
|
||||
"plan_description": "2021-01-01",
|
||||
"user_id": 3
|
||||
}
|
||||
]`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestErrorPlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorModel("Model always errors")
|
||||
|
||||
router := routes.NewPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestEmptyPlanErrorWriter(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getEmptyModel()
|
||||
|
||||
router := routes.NewPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
|
||||
func TestOnePlanByID(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription, UserID: 3}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
router := routes.NewPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/1", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
|
||||
expected := `{
|
||||
"plan_id": 1,
|
||||
"plan_description": "2021-01-01",
|
||||
"user_id": 3
|
||||
}`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestErrorPlanByID(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorModel("Model always errors")
|
||||
|
||||
router := routes.NewPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/5", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestEmptyPlanErrorWriterByID(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
p := &models.Plan{PlanID: 1, PlanDescription: planDescription}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
|
||||
router := routes.NewPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/1", nil)
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
|
||||
func TestNotFoundPlanByIDText(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getEmptyModel()
|
||||
|
||||
router := routes.NewPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/wo", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusNotFound, status)
|
||||
|
||||
}
|
||||
func TestNotFoundPlanByIDEmpty(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getEmptyModel()
|
||||
|
||||
router := routes.NewPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/1", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusNotFound, status)
|
||||
|
||||
}
|
||||
119
routes/plans_unauthorized_test.go
Normal file
119
routes/plans_unauthorized_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEmptyPlanEmptyContext(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
|
||||
}
|
||||
|
||||
func TestOnePlanEmptyContext(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription, UserID: 3}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
router := routes.NewPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
|
||||
}
|
||||
|
||||
func TestOnePlanByIDEmptyContext(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription, UserID: 3}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
router := routes.NewPlanRouter(m)
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "GET", "/1", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
|
||||
}
|
||||
|
||||
func TestPureJSONEmptyContext(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewPlanRouter(m)
|
||||
data := []byte(`{
|
||||
"plan_description": "2021-01-01T00:00:00Z",
|
||||
"plan_id": 1,
|
||||
"user_id": 3
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
}
|
||||
|
||||
func TestPutPlanEmptyContext(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: "sth"}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
|
||||
router := routes.NewPlanRouter(m)
|
||||
data, _ := json.Marshal(p)
|
||||
req, _ := http.NewRequestWithContext(context.Background(), "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
|
||||
}
|
||||
215
routes/post_action_test.go
Normal file
215
routes/post_action_test.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPureJSONPostAction(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
compOn, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
a := &models.Action{
|
||||
PlanID: 5,
|
||||
CompletedOn: &compOn,
|
||||
EstimatedChunks: 3,
|
||||
CompletedChunks: 2,
|
||||
ActionDescription: "here's an action",
|
||||
}
|
||||
m := getEmptyModel()
|
||||
m.AddAction(a, 3)
|
||||
router := routes.NewActionRouter(m)
|
||||
data := []byte(`{
|
||||
"action_description": "here's an action",
|
||||
"estimated_chunks": 3,
|
||||
"completed_chunks": 2,
|
||||
"completed_on": "2021-01-01T00:00:00Z",
|
||||
"plan_id": 5
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusCreated, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `{
|
||||
"created_action": {
|
||||
"action_description": "here's an action",
|
||||
"estimated_chunks": 3,
|
||||
"completed_chunks": 2,
|
||||
"completed_on": "2021-01-01T00:00:00Z",
|
||||
"plan_id": 5,
|
||||
"action_id": 2,
|
||||
"user_id": 3
|
||||
},
|
||||
"id": 2
|
||||
}`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestExtraFieldActionPostJSON(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
router := routes.NewActionRouter(m)
|
||||
data := []byte(`{
|
||||
"completed_on": "2021-01-01T00:00:00Z",
|
||||
"plan_id": 5,
|
||||
"sabotage": "omg"
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
func TestEmptyBodyActionPost(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
router := routes.NewActionRouter(m)
|
||||
data := []byte(``)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestTwoBodyActionPost(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
router := routes.NewActionRouter(m)
|
||||
data := []byte(`{
|
||||
"plan_id": 5
|
||||
}, {
|
||||
"plan_id": 6
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorCreateAction(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorModel("Model always errors")
|
||||
|
||||
router := routes.NewActionRouter(m)
|
||||
a := &models.Action{PlanID: 6}
|
||||
data, _ := json.Marshal(a)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorOnRetrieveCreateAction(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorOnGetModel("Model always errors")
|
||||
|
||||
router := routes.NewActionRouter(m)
|
||||
a := &models.Action{PlanID: 6}
|
||||
data, _ := json.Marshal(a)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorWriterCreateAction(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
a := &models.Action{PlanID: 6}
|
||||
m := getEmptyModel()
|
||||
m.AddAction(a, 3)
|
||||
|
||||
router := routes.NewActionRouter(m)
|
||||
data, _ := json.Marshal(a)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
243
routes/post_plan_test.go
Normal file
243
routes/post_plan_test.go
Normal file
@@ -0,0 +1,243 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreatePlanRoute(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
userID := 3
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription, UserID: int64(userID)}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, userID)
|
||||
router := routes.NewPlanRouter(m)
|
||||
data, _ := json.Marshal(p)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusCreated, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `{
|
||||
"created_plan": {
|
||||
"plan_id": 2,
|
||||
"plan_description": "2021-01-01",
|
||||
"user_id": 3
|
||||
},
|
||||
"id": 2
|
||||
}`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestPureJSON(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
userID := 3
|
||||
p := &models.Plan{PlanID: 1, PlanDescription: planDescription, UserID: int64(userID)}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, userID)
|
||||
router := routes.NewPlanRouter(m)
|
||||
data := []byte(`{
|
||||
"plan_description": "2021-01-01",
|
||||
"plan_id": 1,
|
||||
"user_id": 3
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusCreated, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `{
|
||||
"created_plan": {
|
||||
"plan_id": 2,
|
||||
"user_id": 3,
|
||||
"plan_description": "2021-01-01"
|
||||
},
|
||||
"id": 2
|
||||
}`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestExtraFieldJSON(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
userID := 3
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription, UserID: int64(userID)}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, userID)
|
||||
router := routes.NewPlanRouter(m)
|
||||
data := []byte(`{
|
||||
"plan_description": "2021-01-01",
|
||||
"plan_id": 5,
|
||||
"plan_sabotage": "omg"
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
func TestEmptyBody(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
userID := 3
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, userID)
|
||||
router := routes.NewPlanRouter(m)
|
||||
data := []byte(``)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestTwoBody(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
router := routes.NewPlanRouter(m)
|
||||
data := []byte(`{
|
||||
"plan_description": "2021-01-01",
|
||||
"plan_id": 5
|
||||
}, {
|
||||
"plan_description": "2021-01-01",
|
||||
"plan_id": 6
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorCreatePlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorModel("Model always errors")
|
||||
|
||||
router := routes.NewPlanRouter(m)
|
||||
planDescription := "2021-01-01"
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription}
|
||||
data, _ := json.Marshal(p)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorOnRetrieveCreatePlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorOnGetModel("Model always errors")
|
||||
|
||||
router := routes.NewPlanRouter(m)
|
||||
planDescription := "2021-01-01"
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription}
|
||||
data, _ := json.Marshal(p)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorWriterCreatePlan(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
planDescription := "2021-01-01"
|
||||
|
||||
p := &models.Plan{PlanID: 6, PlanDescription: planDescription}
|
||||
m := getEmptyModel()
|
||||
m.AddPlan(p, 3)
|
||||
router := routes.NewPlanRouter(m)
|
||||
data, _ := json.Marshal(p)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
231
routes/put_action_test.go
Normal file
231
routes/put_action_test.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPureJSONPutAction(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
compOn, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
a := &models.Action{
|
||||
PlanID: 5,
|
||||
CompletedOn: &compOn,
|
||||
EstimatedChunks: 1,
|
||||
CompletedChunks: 1,
|
||||
ActionDescription: "hn",
|
||||
}
|
||||
m := getEmptyModel()
|
||||
m.AddAction(a, 3)
|
||||
router := routes.NewActionRouter(m)
|
||||
data := []byte(`{
|
||||
"action_description": "here's an action",
|
||||
"estimated_chunks": 3,
|
||||
"completed_chunks": 2,
|
||||
"completed_on": "2021-01-01T00:00:00Z",
|
||||
"plan_id": 5
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `{
|
||||
"updated_action": {
|
||||
"action_description": "here's an action",
|
||||
"estimated_chunks": 3,
|
||||
"completed_chunks": 2,
|
||||
"completed_on": "2021-01-01T00:00:00Z",
|
||||
"plan_id": 5,
|
||||
"action_id": 1,
|
||||
"user_id": 3
|
||||
},
|
||||
"id": 1
|
||||
}`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestExtraFieldActionPutJSON(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewActionRouter(m)
|
||||
data := []byte(`{
|
||||
"completed_on": "2021-01-01T00:00:00Z",
|
||||
"plan_id": 5,
|
||||
"sabotage": "omg"
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
func TestEmptyBodyActionPut(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewActionRouter(m)
|
||||
data := []byte(``)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestTwoBodyActionPut(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewActionRouter(m)
|
||||
data := []byte(`{
|
||||
"plan_id": 5
|
||||
}, {
|
||||
"plan_id": 6
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusBadRequest, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Bad Request`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestBadActionIDPut(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewActionRouter(m)
|
||||
data := []byte(`{
|
||||
"plan_id": 5
|
||||
}, {
|
||||
"plan_id": 6
|
||||
}`)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/text", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusNotFound, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Not Found`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorUpdateAction(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorModel("Model always errors")
|
||||
|
||||
router := routes.NewActionRouter(m)
|
||||
a := &models.Action{PlanID: 6}
|
||||
data, _ := json.Marshal(a)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorOnRetrieveUpdateAction(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
m := getErrorOnGetModel("Model always errors")
|
||||
|
||||
router := routes.NewActionRouter(m)
|
||||
a := &models.Action{PlanID: 6}
|
||||
data, _ := json.Marshal(a)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
// We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp.
|
||||
expected := `Internal Server Error`
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestErrorWriterUpdateAction(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
|
||||
a := &models.Action{PlanID: 6}
|
||||
m := getEmptyModel()
|
||||
m.AddAction(a, 3)
|
||||
|
||||
router := routes.NewActionRouter(m)
|
||||
data, _ := json.Marshal(a)
|
||||
req, _ := http.NewRequestWithContext(sampleContext, "PUT", "/1", bytes.NewBuffer(data))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
|
||||
}
|
||||
22
routes/route_model_test.go
Normal file
22
routes/route_model_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/store"
|
||||
)
|
||||
|
||||
func getEmptyModel() *models.Model {
|
||||
str, _ := store.GetInMemoryStore()
|
||||
m := models.New(str)
|
||||
return m
|
||||
}
|
||||
|
||||
func getErrorModel(message string) *models.Model {
|
||||
str := store.GetErrorStore(message, true)
|
||||
return models.New(str)
|
||||
}
|
||||
|
||||
func getErrorOnGetModel(errorMsg string) *models.Model {
|
||||
str := store.GetErrorStore(errorMsg, false)
|
||||
return models.New(str)
|
||||
}
|
||||
36
routes/routes.go
Normal file
36
routes/routes.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/go-chi/chi"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewRouter returns a router powered by the provided model.
|
||||
func NewRouter(m *models.Model, tok tokens.Toker) http.Handler {
|
||||
router := chi.NewRouter()
|
||||
router.MethodNotAllowed(methodNotAllowedHandler)
|
||||
router.NotFound(notFoundHandler)
|
||||
router.Group(func(r chi.Router) {
|
||||
r.Use(tok.Authenticator)
|
||||
r.Mount("/actions", NewActionRouter(m))
|
||||
r.Mount("/plans", NewPlanRouter(m))
|
||||
r.Mount("/me", NewCurrentUserRouter(m))
|
||||
r.Mount("/currentPlan", NewCurrentPlanRouter(m))
|
||||
})
|
||||
router.Mount("/auth", NewAuthRouter(m, tok))
|
||||
router.Mount("/health", newHealthRouter(m))
|
||||
router.Get("/ping", ping)
|
||||
return router
|
||||
}
|
||||
|
||||
func ping(w http.ResponseWriter, r *http.Request) {
|
||||
// A very simple health check.
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(map[string]string{"ping": "pong"}); err != nil {
|
||||
serverError(w, err)
|
||||
}
|
||||
}
|
||||
87
routes/routes_test.go
Normal file
87
routes/routes_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/routes"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPingHandler(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewRouter(m, tokens.New("whatever"))
|
||||
req, _ := http.NewRequest("GET", "/ping", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
expected := `{"ping": "pong"}`
|
||||
assert.JSONEq(expected, rr.Body.String())
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
assert.Equal("application/json", contentType)
|
||||
}
|
||||
|
||||
func TestPingPostHandler(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewRouter(m, tokens.New("whatever"))
|
||||
req, _ := http.NewRequest("POST", "/ping", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusMethodNotAllowed, status)
|
||||
expected := http.StatusText(http.StatusMethodNotAllowed)
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestNotFoundHandler(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewRouter(m, tokens.New("whatever"))
|
||||
req, _ := http.NewRequest("POST", "/null", nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusNotFound, status)
|
||||
expected := http.StatusText(http.StatusNotFound)
|
||||
assert.Equal(expected, strings.TrimSpace(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestPingError(t *testing.T) {
|
||||
// set up
|
||||
assert := assert.New(t)
|
||||
m := getEmptyModel()
|
||||
router := routes.NewRouter(m, tokens.New("whatever"))
|
||||
req, _ := http.NewRequest("GET", "/ping", nil)
|
||||
|
||||
rr := NewBadWriter()
|
||||
|
||||
// function under test
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// check results
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusInternalServerError, status)
|
||||
}
|
||||
104
store/errorStore.go
Normal file
104
store/errorStore.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
)
|
||||
|
||||
func (e *errorStore) SelectActions(userID int) ([]*models.Action, error) {
|
||||
return nil, e.error
|
||||
}
|
||||
|
||||
func (e *errorStore) SelectActionByID(id int, userID int) (*models.Action, error) {
|
||||
return nil, e.error
|
||||
}
|
||||
|
||||
func (e *errorStore) InsertAction(action *models.Action, userID int) (int, error) {
|
||||
if e.errorOnInsert {
|
||||
return 0, e.error
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (e *errorStore) UpdateAction(action *models.Action, userID int) error {
|
||||
if e.errorOnInsert {
|
||||
return e.error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *errorStore) SelectPlans(userID int) ([]*models.Plan, error) {
|
||||
return nil, e.error
|
||||
}
|
||||
|
||||
func (e *errorStore) SelectPlanByID(id int, userID int) (*models.Plan, error) {
|
||||
return nil, e.error
|
||||
}
|
||||
|
||||
func (e *errorStore) InsertPlan(plan *models.Plan, userID int) (int, error) {
|
||||
if e.errorOnInsert {
|
||||
return 0, e.error
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (e *errorStore) UpdatePlan(plan *models.Plan, userID int) error {
|
||||
if e.errorOnInsert {
|
||||
return e.error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *errorStore) SelectActionsByPlanID(plan *models.Plan, userID int) ([]*models.Action, error) {
|
||||
return nil, e.error
|
||||
}
|
||||
|
||||
func (e *errorStore) SelectUserByUsername(name string) (*models.User, error) {
|
||||
return nil, e.error
|
||||
}
|
||||
|
||||
func (e *errorStore) InsertUser(user *models.User) (int, error) {
|
||||
if e.errorOnInsert {
|
||||
return 0, e.error
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (e *errorStore) SelectCurrentPlan(userID int) (*models.CurrentPlan, error) {
|
||||
return nil, e.error
|
||||
}
|
||||
|
||||
func (e *errorStore) InsertCurrentPlan(currentPlan *models.CurrentPlan, userID int) error {
|
||||
if e.errorOnInsert {
|
||||
return e.error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *errorStore) UpdateCurrentPlan(currentPlan *models.CurrentPlan, userID int) error {
|
||||
if e.errorOnInsert {
|
||||
return e.error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *errorStore) ConnectionLive() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
type errorStore struct {
|
||||
error error
|
||||
errorOnInsert bool
|
||||
}
|
||||
|
||||
// GetErrorStore returns a models.Store that always errors. This is useful for testing purposes.
|
||||
func GetErrorStore(errorMsg string, errorOnInsert bool) models.Store {
|
||||
e := &errorStore{error: fmt.Errorf(errorMsg), errorOnInsert: errorOnInsert}
|
||||
return e
|
||||
}
|
||||
|
||||
// GetErrorStoreForError returns a models.Store that always errors with the provided error.
|
||||
func GetErrorStoreForError(err error, errorOnInsert bool) models.Store {
|
||||
e := &errorStore{error: err, errorOnInsert: errorOnInsert}
|
||||
return e
|
||||
}
|
||||
112
store/errorStore_test.go
Normal file
112
store/errorStore_test.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/store"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestErrorActionMethods(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
userID := 2
|
||||
str := store.GetErrorStore("error message sample", true)
|
||||
str2 := store.GetErrorStore("error message sample", false)
|
||||
str3 := store.GetErrorStoreForError(fmt.Errorf("test error"), false)
|
||||
|
||||
_, err := str.InsertAction(&models.Action{}, userID)
|
||||
assert.NotNil(err)
|
||||
_, err = str2.InsertAction(&models.Action{}, userID)
|
||||
assert.Nil(err)
|
||||
_, err = str3.InsertAction(&models.Action{}, userID)
|
||||
assert.Nil(err)
|
||||
|
||||
_, err = str.SelectActionByID(8, userID)
|
||||
assert.NotNil(err)
|
||||
_, err = str2.SelectActionByID(8, userID)
|
||||
assert.NotNil(err)
|
||||
|
||||
_, err = str.SelectActions(userID)
|
||||
assert.NotNil(err)
|
||||
|
||||
_, err = str.SelectActionsByPlanID(&models.Plan{}, userID)
|
||||
assert.NotNil(err)
|
||||
|
||||
replacementAction := &models.Action{}
|
||||
err = str.UpdateAction(replacementAction, userID)
|
||||
assert.NotNil(err)
|
||||
err = str2.UpdateAction(replacementAction, userID)
|
||||
assert.Nil(err)
|
||||
|
||||
}
|
||||
|
||||
func TestErrorPlanMethods(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
str := store.GetErrorStore("sntahoeu", true)
|
||||
str2 := store.GetErrorStore("sntahoeu", false)
|
||||
|
||||
_, err := str.SelectPlans(3)
|
||||
assert.NotNil(err)
|
||||
|
||||
_, err = str.InsertPlan(&models.Plan{}, 3)
|
||||
assert.NotNil(err)
|
||||
_, err = str2.InsertPlan(&models.Plan{}, 3)
|
||||
assert.Nil(err)
|
||||
|
||||
replacementPlan := &models.Plan{}
|
||||
err = str.UpdatePlan(replacementPlan, 3)
|
||||
assert.NotNil(err)
|
||||
err = str2.UpdatePlan(replacementPlan, 3)
|
||||
assert.Nil(err)
|
||||
|
||||
_, err = str.SelectPlanByID(5, 3)
|
||||
assert.NotNil(err)
|
||||
|
||||
}
|
||||
|
||||
func TestErrorLive(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
str := store.GetErrorStore("error", true)
|
||||
|
||||
err := str.ConnectionLive()
|
||||
assert.NotNil(err)
|
||||
}
|
||||
|
||||
func TestErrorUserMethods(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
str := store.GetErrorStore("error", true)
|
||||
str2 := store.GetErrorStore("error", false)
|
||||
|
||||
u := &models.User{}
|
||||
|
||||
_, err := str.InsertUser(u)
|
||||
assert.NotNil(err)
|
||||
_, err = str2.InsertUser(u)
|
||||
assert.Nil(err)
|
||||
|
||||
_, err = str.SelectUserByUsername("snth")
|
||||
assert.NotNil(err)
|
||||
}
|
||||
|
||||
func TestErrorCurrentPlanMethods(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
str := store.GetErrorStore("error", true)
|
||||
str2 := store.GetErrorStore("error", false)
|
||||
|
||||
cp := &models.CurrentPlan{}
|
||||
|
||||
_, err := str.SelectCurrentPlan(1)
|
||||
assert.NotNil(err)
|
||||
|
||||
err = str.InsertCurrentPlan(cp, 1)
|
||||
assert.NotNil(err)
|
||||
err = str2.InsertCurrentPlan(cp, 1)
|
||||
assert.Nil(err)
|
||||
|
||||
replace := &models.CurrentPlan{}
|
||||
err = str.UpdateCurrentPlan(replace, 1)
|
||||
assert.NotNil(err)
|
||||
err = str2.UpdateCurrentPlan(replace, 1)
|
||||
assert.Nil(err)
|
||||
}
|
||||
164
store/inmemory.go
Normal file
164
store/inmemory.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
)
|
||||
|
||||
type inMemoryStore struct {
|
||||
actions []*models.Action
|
||||
plans []*models.Plan
|
||||
users []*models.User
|
||||
currentPlans []*models.CurrentPlan
|
||||
}
|
||||
|
||||
// GetInMemoryStore provides a purely in memory store, for testing purposes only, with no persistence.
|
||||
func GetInMemoryStore() (models.Store, error) {
|
||||
return &inMemoryStore{
|
||||
actions: make([]*models.Action, 0),
|
||||
plans: make([]*models.Plan, 0),
|
||||
users: make([]*models.User, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (store *inMemoryStore) SelectActions(userID int) ([]*models.Action, error) {
|
||||
ret := make([]*models.Action, 0)
|
||||
for _, action := range store.actions {
|
||||
if int(action.UserID) == userID {
|
||||
ret = append(ret, action)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (store *inMemoryStore) SelectActionsByPlanID(plan *models.Plan, userID int) ([]*models.Action, error) {
|
||||
ret := make([]*models.Action, 0)
|
||||
for _, action := range store.actions {
|
||||
if (int(plan.PlanID) == int(action.PlanID)) && (int(action.UserID) == userID) {
|
||||
ret = append(ret, action)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (store *inMemoryStore) SelectActionByID(id int, userID int) (*models.Action, error) {
|
||||
for _, action := range store.actions {
|
||||
if id == int(action.ActionID) && (int(action.UserID) == userID) {
|
||||
return action, nil
|
||||
}
|
||||
}
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (store *inMemoryStore) InsertAction(action *models.Action, userID int) (int, error) {
|
||||
id := len(store.actions) + 1
|
||||
action.ActionID = int64(id)
|
||||
action.UserID = int64(userID)
|
||||
store.actions = append(store.actions, action)
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (store *inMemoryStore) UpdateAction(action *models.Action, userID int) error {
|
||||
currentAction, err := store.SelectActionByID(int(action.ActionID), userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentAction.ActionDescription = action.ActionDescription
|
||||
currentAction.EstimatedChunks = action.EstimatedChunks
|
||||
currentAction.CompletedChunks = action.CompletedChunks
|
||||
currentAction.PlanID = action.PlanID
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (store *inMemoryStore) SelectPlans(userID int) ([]*models.Plan, error) {
|
||||
ret := make([]*models.Plan, 0)
|
||||
for _, plan := range store.plans {
|
||||
if int(plan.UserID) == userID {
|
||||
ret = append(ret, plan)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (store *inMemoryStore) SelectPlanByID(id int, userID int) (*models.Plan, error) {
|
||||
for _, plan := range store.plans {
|
||||
if id == int(plan.PlanID) && (userID == int(plan.UserID)) {
|
||||
return plan, nil
|
||||
}
|
||||
}
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (store *inMemoryStore) InsertPlan(plan *models.Plan, userID int) (int, error) {
|
||||
id := len(store.plans) + 1
|
||||
plan.PlanID = int64(id)
|
||||
plan.UserID = int64(userID)
|
||||
store.plans = append(store.plans, plan)
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (store *inMemoryStore) UpdatePlan(plan *models.Plan, userID int) error {
|
||||
currPlan, err := store.SelectPlanByID(int(plan.PlanID), userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currPlan.PlanDescription = plan.PlanDescription
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *inMemoryStore) SelectCurrentPlan(userID int) (*models.CurrentPlan, error) {
|
||||
for _, currentPlan := range store.currentPlans {
|
||||
if userID == int(currentPlan.UserID) {
|
||||
return currentPlan, nil
|
||||
}
|
||||
}
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (store *inMemoryStore) InsertCurrentPlan(currentPlan *models.CurrentPlan, userID int) error {
|
||||
_, err := store.SelectCurrentPlan(userID)
|
||||
if err == nil {
|
||||
return fmt.Errorf("Can't insert primary plan")
|
||||
}
|
||||
// actually impossible, but at this point it must be a not found error.
|
||||
// if err != sql.ErrNoRows {
|
||||
// return err
|
||||
// }
|
||||
|
||||
store.currentPlans = append(store.currentPlans, &models.CurrentPlan{PlanID: int64(currentPlan.PlanID), UserID: int64(userID)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *inMemoryStore) UpdateCurrentPlan(currentPlan *models.CurrentPlan, userID int) error {
|
||||
current, err := store.SelectCurrentPlan(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
current.PlanID = currentPlan.PlanID
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *inMemoryStore) ConnectionLive() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *inMemoryStore) SelectUserByUsername(username string) (*models.User, error) {
|
||||
for _, user := range store.users {
|
||||
if username == user.Username {
|
||||
return user, nil
|
||||
}
|
||||
}
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
// inMemoryStore.InsertUser will not enforce unique usernames, which is ok.
|
||||
func (store *inMemoryStore) InsertUser(user *models.User) (int, error) {
|
||||
id := len(store.users) + 1
|
||||
user.UserID = int64(id)
|
||||
store.users = append(store.users, user)
|
||||
return id, nil
|
||||
}
|
||||
151
store/inmemory_test.go
Normal file
151
store/inmemory_test.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/store"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInMemoryActionMethods(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
str, _ := store.GetInMemoryStore()
|
||||
|
||||
sampleplanid := 8
|
||||
userID := 10
|
||||
|
||||
act := &models.Action{}
|
||||
a2 := &models.Action{PlanID: sampleplanid}
|
||||
|
||||
id, _ := str.InsertAction(act, userID)
|
||||
assert.EqualValues(1, id)
|
||||
|
||||
receivedAction, err := str.SelectActionByID(id, userID)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(act, receivedAction)
|
||||
_, err = str.SelectActionByID(id, userID+1)
|
||||
assert.NotNil(err)
|
||||
|
||||
allactions, err := str.SelectActions(userID)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(1, len(allactions))
|
||||
|
||||
str.InsertAction(a2, userID)
|
||||
allactions, err = str.SelectActions(userID)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(2, len(allactions))
|
||||
|
||||
planactions, err := str.SelectActionsByPlanID(&models.Plan{PlanID: int64(sampleplanid)}, userID)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(1, len(planactions))
|
||||
assert.Equal(a2, planactions[0])
|
||||
|
||||
_, err = str.SelectActionByID(151, userID)
|
||||
assert.NotNil(err)
|
||||
|
||||
sampleDescription := "snth"
|
||||
replacementAction := &models.Action{ActionID: 1, ActionDescription: sampleDescription}
|
||||
err = str.UpdateAction(replacementAction, userID)
|
||||
assert.Nil(err)
|
||||
assert.Equal(sampleDescription, act.ActionDescription)
|
||||
|
||||
replacementAction = &models.Action{ActionID: 1235122, ActionDescription: sampleDescription}
|
||||
err = str.UpdateAction(replacementAction, userID)
|
||||
assert.NotNil(err)
|
||||
|
||||
}
|
||||
|
||||
func TestInMemoryPlanMethods(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
str, _ := store.GetInMemoryStore()
|
||||
userID := 1
|
||||
p := &models.Plan{}
|
||||
plans, err := str.SelectPlans(userID)
|
||||
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(0, len(plans))
|
||||
|
||||
id, err := str.InsertPlan(p, userID)
|
||||
plans, err = str.SelectPlans(userID)
|
||||
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(1, len(plans))
|
||||
|
||||
retrievedPlan, err := str.SelectPlanByID(id, userID)
|
||||
assert.Nil(err)
|
||||
assert.Equal(retrievedPlan, p)
|
||||
|
||||
_, err = str.SelectPlanByID(135135, userID)
|
||||
assert.NotNil(err)
|
||||
|
||||
sampleDescription := "snth"
|
||||
replacePlan := &models.Plan{PlanID: 1, PlanDescription: sampleDescription}
|
||||
err = str.UpdatePlan(replacePlan, userID)
|
||||
assert.Nil(err)
|
||||
assert.Equal(sampleDescription, p.PlanDescription)
|
||||
|
||||
replacePlan = &models.Plan{PlanID: 1235122, PlanDescription: sampleDescription}
|
||||
err = str.UpdatePlan(replacePlan, userID)
|
||||
assert.NotNil(err)
|
||||
}
|
||||
|
||||
func TestLive(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
str, _ := store.GetInMemoryStore()
|
||||
|
||||
err := str.ConnectionLive()
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestInMemoryUserMethods(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
str, _ := store.GetInMemoryStore()
|
||||
|
||||
uname := "hiimauser"
|
||||
|
||||
u := &models.User{Username: uname}
|
||||
|
||||
id, err := str.InsertUser(u)
|
||||
assert.Nil(err)
|
||||
|
||||
retrievedUser, err := str.SelectUserByUsername(uname)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(id, retrievedUser.UserID)
|
||||
|
||||
_, err = str.SelectUserByUsername("bad username")
|
||||
assert.NotNil(err)
|
||||
}
|
||||
|
||||
func TestInMemoryCurrentPlanMethods(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
str, _ := store.GetInMemoryStore()
|
||||
|
||||
userID := 10
|
||||
|
||||
cp1 := &models.CurrentPlan{PlanID: 1}
|
||||
cp2 := &models.CurrentPlan{PlanID: 2}
|
||||
|
||||
err := str.UpdateCurrentPlan(cp1, userID)
|
||||
assert.NotNil(err)
|
||||
|
||||
err = str.InsertCurrentPlan(cp1, userID)
|
||||
assert.Nil(err)
|
||||
|
||||
receivedCp, err := str.SelectCurrentPlan(userID)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(1, receivedCp.PlanID)
|
||||
|
||||
_, err = str.SelectCurrentPlan(userID + 1)
|
||||
assert.NotNil(err)
|
||||
|
||||
str.InsertCurrentPlan(cp2, userID)
|
||||
assert.NotNil(err)
|
||||
|
||||
err = str.UpdateCurrentPlan(cp2, userID)
|
||||
assert.Nil(err)
|
||||
|
||||
receivedCp, err = str.SelectCurrentPlan(userID)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(2, receivedCp.PlanID)
|
||||
|
||||
}
|
||||
6
store/migrations/000001_create_action_table.down.sql
Normal file
6
store/migrations/000001_create_action_table.down.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
DROP TABLE IF EXISTS actions;
|
||||
DROP TABLE IF EXISTS user_current_plan;
|
||||
DROP TABLE IF EXISTS plans;
|
||||
DROP TABLE IF EXISTS users;
|
||||
|
||||
DROP FUNCTION IF EXISTS trigger_set_timestamp;
|
||||
58
store/migrations/000001_create_action_table.up.sql
Normal file
58
store/migrations/000001_create_action_table.up.sql
Normal file
@@ -0,0 +1,58 @@
|
||||
CREATE TABLE IF NOT EXISTS users(
|
||||
user_id serial PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
display_name VARCHAR (100) NOT NULL,
|
||||
password bytea,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS plans(
|
||||
plan_id serial PRIMARY KEY,
|
||||
plan_description VARCHAR (500) NOT NULL,
|
||||
user_id int REFERENCES users(user_id),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
UNIQUE (user_id, plan_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_current_plan(
|
||||
user_id int PRIMARY KEY,
|
||||
plan_id int,
|
||||
FOREIGN KEY (user_id, plan_id) REFERENCES plans(user_id, plan_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS actions(
|
||||
action_id serial PRIMARY KEY,
|
||||
action_description VARCHAR (500) NOT NULL,
|
||||
user_id int REFERENCES users(user_id),
|
||||
estimated_chunks SMALLINT,
|
||||
completed_chunks SMALLINT,
|
||||
completed_on TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
plan_id int REFERENCES plans(plan_id)
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION trigger_set_timestamp()
|
||||
RETURNS TRIGGER AS $set_updated$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$set_updated$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER set_updated
|
||||
BEFORE UPDATE ON actions
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE trigger_set_timestamp();
|
||||
|
||||
CREATE TRIGGER set_updated
|
||||
BEFORE UPDATE ON plans
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE trigger_set_timestamp();
|
||||
|
||||
CREATE TRIGGER set_updated
|
||||
BEFORE UPDATE ON users
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE trigger_set_timestamp();
|
||||
248
store/postgres.go
Normal file
248
store/postgres.go
Normal file
@@ -0,0 +1,248 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/util"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
type postgresStore struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
// GetPostgresStore provides a store to back the model based on a postgres connection.
|
||||
func GetPostgresStore(db *sqlx.DB) (models.Store, error) {
|
||||
|
||||
db.MapperFunc(util.ToSnake)
|
||||
return &postgresStore{db: db}, nil
|
||||
}
|
||||
|
||||
func (store *postgresStore) SelectActions(userID int) ([]*models.Action, error) {
|
||||
queryString := store.db.Rebind("SELECT action_id, action_description, user_id, estimated_chunks, completed_chunks, completed_on, created_at, updated_at, plan_id FROM actions WHERE user_id = ?")
|
||||
actions := make([]*models.Action, 0)
|
||||
err := store.db.Select(&actions, queryString, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return actions, nil
|
||||
}
|
||||
|
||||
func (store *postgresStore) SelectActionsByPlanID(plan *models.Plan, userID int) ([]*models.Action, error) {
|
||||
queryString := store.db.Rebind("SELECT action_id, action_description, user_id, estimated_chunks, completed_chunks, completed_on, created_at, updated_at, plan_id FROM actions WHERE plan_id = ? AND user_id = ?")
|
||||
actions := make([]*models.Action, 0)
|
||||
err := store.db.Select(&actions, queryString, plan.PlanID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return actions, nil
|
||||
}
|
||||
|
||||
func (store *postgresStore) SelectActionByID(id int, userID int) (*models.Action, error) {
|
||||
queryString := store.db.Rebind("SELECT action_id, action_description, user_id, estimated_chunks, completed_chunks, completed_on, created_at, updated_at, plan_id FROM actions WHERE action_id = ? AND user_id = ?")
|
||||
action := models.Action{}
|
||||
err := store.db.Get(&action, queryString, id, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &action, nil
|
||||
}
|
||||
|
||||
func (store *postgresStore) InsertAction(action *models.Action, userID int) (int, error) {
|
||||
queryString := store.db.Rebind(
|
||||
`INSERT INTO actions (action_description,
|
||||
user_id,
|
||||
estimated_chunks,
|
||||
completed_chunks,
|
||||
completed_on,
|
||||
plan_id) VALUES (?, ?, ?, ?, ?, ?) RETURNING action_id`,
|
||||
)
|
||||
tx := store.db.MustBegin()
|
||||
var id int
|
||||
err := tx.Get(
|
||||
&id,
|
||||
queryString,
|
||||
action.ActionDescription,
|
||||
userID,
|
||||
action.EstimatedChunks,
|
||||
action.CompletedChunks,
|
||||
action.CompletedOn,
|
||||
action.PlanID,
|
||||
)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return -1, err
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (store *postgresStore) UpdateAction(action *models.Action, userID int) error {
|
||||
query := `UPDATE actions SET
|
||||
action_description = :action_description,
|
||||
estimated_chunks = :estimated_chunks,
|
||||
completed_chunks = :completed_chunks,
|
||||
completed_on = :completed_on,
|
||||
plan_id = :plan_id
|
||||
WHERE action_id = :action_id
|
||||
AND user_id = :user_id`
|
||||
tx := store.db.MustBegin()
|
||||
actionToUse := &models.Action{
|
||||
ActionDescription: action.ActionDescription,
|
||||
EstimatedChunks: action.EstimatedChunks,
|
||||
CompletedChunks: action.CompletedChunks,
|
||||
CompletedOn: action.CompletedOn,
|
||||
PlanID: action.PlanID,
|
||||
ActionID: action.ActionID,
|
||||
UserID: int64(userID),
|
||||
}
|
||||
_, err := store.db.NamedExec(query, actionToUse)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (store *postgresStore) SelectPlans(userID int) ([]*models.Plan, error) {
|
||||
queryString := store.db.Rebind("SELECT plan_id, plan_description, user_id FROM plans WHERE user_id = ?")
|
||||
plans := make([]*models.Plan, 0)
|
||||
err := store.db.Select(&plans, queryString, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plans, nil
|
||||
}
|
||||
|
||||
func (store *postgresStore) SelectPlanByID(id int, userID int) (*models.Plan, error) {
|
||||
plan := models.Plan{}
|
||||
err := store.db.Get(&plan, store.db.Rebind("SELECT plan_id, plan_description, user_id FROM plans WHERE plan_id = ? AND user_id = ?"), id, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &plan, nil
|
||||
}
|
||||
|
||||
func (store *postgresStore) InsertPlan(plan *models.Plan, userID int) (int, error) {
|
||||
queryString := store.db.Rebind("INSERT INTO plans (plan_description, user_id) VALUES (?, ?) RETURNING plan_id")
|
||||
tx := store.db.MustBegin()
|
||||
var id int
|
||||
err := tx.Get(&id, queryString, plan.PlanDescription, userID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return -1, err
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (store *postgresStore) UpdatePlan(plan *models.Plan, userID int) error {
|
||||
query := `UPDATE plans SET
|
||||
plan_description = :plan_description
|
||||
WHERE plan_id = :plan_id
|
||||
AND user_id = :user_id`
|
||||
tx := store.db.MustBegin()
|
||||
planToUse := &models.Plan{
|
||||
PlanDescription: plan.PlanDescription,
|
||||
PlanID: plan.PlanID,
|
||||
UserID: int64(userID),
|
||||
}
|
||||
_, err := store.db.NamedExec(query, planToUse)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (store *postgresStore) ConnectionLive() error {
|
||||
return store.db.Ping()
|
||||
}
|
||||
|
||||
func (store *postgresStore) SelectUserByUsername(username string) (*models.User, error) {
|
||||
user := models.User{}
|
||||
err := store.db.Get(&user, store.db.Rebind("SELECT user_id, username, display_name, password FROM users WHERE username = ?"), username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (store *postgresStore) InsertUser(user *models.User) (int, error) {
|
||||
queryString := store.db.Rebind("INSERT INTO users (username, display_name, password) VALUES (?, ?, ?) RETURNING user_id")
|
||||
tx := store.db.MustBegin()
|
||||
var id int
|
||||
err := tx.Get(&id, queryString, user.Username, user.DisplayName, user.Password)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return -1, err
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (store *postgresStore) SelectCurrentPlan(userID int) (*models.CurrentPlan, error) {
|
||||
pp := models.CurrentPlan{}
|
||||
queryString := store.db.Rebind(`SELECT user_id, plan_id FROM user_current_plan WHERE user_id = ?`)
|
||||
err := store.db.Get(&pp, queryString, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pp, nil
|
||||
}
|
||||
|
||||
func (store *postgresStore) InsertCurrentPlan(currentPlan *models.CurrentPlan, userID int) error {
|
||||
queryString := store.db.Rebind("INSERT INTO user_current_plan (user_id, plan_id) VALUES (?, ?) RETURNING user_id")
|
||||
tx := store.db.MustBegin()
|
||||
var id int
|
||||
err := tx.Get(&id, queryString, userID, currentPlan.PlanID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *postgresStore) UpdateCurrentPlan(currentPlan *models.CurrentPlan, userID int) error {
|
||||
query := `UPDATE user_current_plan SET
|
||||
plan_id = :plan_id
|
||||
WHERE user_id = :user_id`
|
||||
tx := store.db.MustBegin()
|
||||
ppToUse := &models.CurrentPlan{
|
||||
PlanID: currentPlan.PlanID,
|
||||
UserID: int64(userID),
|
||||
}
|
||||
_, err := store.db.NamedExec(query, ppToUse)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
217
store/postgres_current_plan_test.go
Normal file
217
store/postgres_current_plan_test.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSelectCurrentPlan(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
planIDToUse := 1
|
||||
userIDToUse := 2
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
rows := sqlmock.NewRows([]string{"plan_id", "user_id"}).AddRow(planIDToUse, userIDToUse)
|
||||
mock.ExpectQuery(`^SELECT user_id, plan_id FROM user_current_plan WHERE user_id = \$1`).
|
||||
WithArgs(userIDToUse).
|
||||
WillReturnRows(rows)
|
||||
|
||||
currPlan, err := str.SelectCurrentPlan(userIDToUse)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(planIDToUse, currPlan.PlanID)
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectCurrentPlanErr(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
userIDToUse := 2
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
mock.ExpectQuery(`^SELECT user_id, plan_id FROM user_current_plan WHERE user_id = \$1`).
|
||||
WithArgs(userIDToUse).
|
||||
WillReturnError(fmt.Errorf("example error"))
|
||||
|
||||
currPlan, err := str.SelectCurrentPlan(userIDToUse)
|
||||
assert.NotNil(err)
|
||||
assert.Nil(currPlan)
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertCurrentPlan(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
planID := 1
|
||||
userID := 2
|
||||
badUserID := 7
|
||||
|
||||
cp := &models.CurrentPlan{PlanID: int64(planID), UserID: int64(badUserID)}
|
||||
|
||||
rows := sqlmock.NewRows([]string{"userID"}).AddRow(userID)
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(`^INSERT INTO user_current_plan \(user_id, plan_id\) VALUES \(\$1, \$2\) RETURNING user_id$`).
|
||||
WithArgs(userID, planID).
|
||||
WillReturnRows(rows)
|
||||
mock.ExpectCommit()
|
||||
|
||||
// function under test
|
||||
err := str.InsertCurrentPlan(cp, userID)
|
||||
// check results
|
||||
assert.Nil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestInsertCurrentPlanErr(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
userID := 2
|
||||
badUserID := 7
|
||||
planID := 1
|
||||
cp := &models.CurrentPlan{PlanID: int64(planID), UserID: int64(badUserID)}
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(`^INSERT INTO user_current_plan \(user_id, plan_id\) VALUES \(\$1, \$2\) RETURNING user_id$`).
|
||||
WithArgs(userID, planID).
|
||||
WillReturnError(fmt.Errorf("example error"))
|
||||
mock.ExpectRollback()
|
||||
|
||||
// function under test
|
||||
err := str.InsertCurrentPlan(cp, userID)
|
||||
// check results
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestInsertCurrentPlanCommitErr(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
planID := 1
|
||||
userID := 2
|
||||
cp := &models.CurrentPlan{PlanID: int64(planID), UserID: int64(userID)}
|
||||
|
||||
rows := sqlmock.NewRows([]string{"user_id"}).AddRow(userID)
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(`^INSERT INTO user_current_plan \(user_id, plan_id\) VALUES \(\$1, \$2\) RETURNING user_id$`).
|
||||
WithArgs(userID, planID).
|
||||
WillReturnRows(rows)
|
||||
mock.ExpectCommit().WillReturnError(fmt.Errorf("another error example"))
|
||||
|
||||
// function under test
|
||||
err := str.InsertCurrentPlan(cp, userID)
|
||||
// check results
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUpdateCurrentPlan(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
userIDToUse := 1
|
||||
planIDToUse := 2
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec(`
|
||||
UPDATE user_current_plan SET
|
||||
plan_id = \$1
|
||||
WHERE user_id = \$2`).
|
||||
WithArgs(planIDToUse, userIDToUse).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit()
|
||||
|
||||
// function under test
|
||||
err := str.UpdateCurrentPlan(&models.CurrentPlan{PlanID: int64(planIDToUse)}, userIDToUse)
|
||||
// check results
|
||||
assert.Nil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUpdateCurrentPlanErr(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
userIDToUse := 1
|
||||
planIDToUse := 2
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec(`
|
||||
UPDATE user_current_plan SET
|
||||
plan_id = \$1
|
||||
WHERE user_id = \$2`).
|
||||
WithArgs(planIDToUse, userIDToUse).
|
||||
WillReturnError(fmt.Errorf("example error"))
|
||||
mock.ExpectRollback()
|
||||
|
||||
// function under test
|
||||
err := str.UpdateCurrentPlan(&models.CurrentPlan{PlanID: int64(planIDToUse)}, userIDToUse)
|
||||
// check results
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUpdateCurrentPlanCommitErr(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
userIDToUse := 1
|
||||
planIDToUse := 2
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec(`
|
||||
UPDATE user_current_plan SET
|
||||
plan_id = \$1
|
||||
WHERE user_id = \$2`).
|
||||
WithArgs(planIDToUse, userIDToUse).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit().WillReturnError(fmt.Errorf("another error example"))
|
||||
|
||||
// function under test
|
||||
err := str.UpdateCurrentPlan(&models.CurrentPlan{PlanID: int64(planIDToUse)}, userIDToUse)
|
||||
// check results
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
264
store/postgres_plan_test.go
Normal file
264
store/postgres_plan_test.go
Normal file
@@ -0,0 +1,264 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSelectPlans(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
planDesc := "testing"
|
||||
idToUse := 1
|
||||
userIDToUse := 2
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
rows := sqlmock.NewRows([]string{"plan_id", "plan_description", "user_id"}).AddRow(idToUse, planDesc, userIDToUse)
|
||||
mock.ExpectQuery(`^SELECT plan_id, plan_description, user_id FROM plans WHERE user_id = \$1`).
|
||||
WithArgs(userIDToUse).
|
||||
WillReturnRows(rows)
|
||||
|
||||
plans, err := str.SelectPlans(userIDToUse)
|
||||
assert.Nil(err)
|
||||
assert.Equal(1, len(plans))
|
||||
plan := plans[0]
|
||||
assert.EqualValues(idToUse, plan.PlanID)
|
||||
assert.Equal(planDesc, plan.PlanDescription)
|
||||
assert.EqualValues(userIDToUse, plan.UserID)
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectPlanByID(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
planDesc := "tsaoeu"
|
||||
idToUse := 1
|
||||
userIDToUse := 2
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
rows := sqlmock.NewRows([]string{"plan_id", "plan_description", "user_id"}).AddRow(idToUse, planDesc, userIDToUse)
|
||||
mock.ExpectQuery(`^SELECT plan_id, plan_description, user_id FROM plans WHERE plan_id = \$1 AND user_id = \$2$`).
|
||||
WithArgs(idToUse, userIDToUse).
|
||||
WillReturnRows(rows)
|
||||
|
||||
plan, err := str.SelectPlanByID(idToUse, userIDToUse)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(idToUse, plan.PlanID)
|
||||
assert.Equal(planDesc, plan.PlanDescription)
|
||||
assert.EqualValues(userIDToUse, plan.UserID)
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertPlan(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
planDescription := "2021-01-01"
|
||||
userID := 2
|
||||
badUserID := 7
|
||||
|
||||
plan := &models.Plan{PlanDescription: planDescription, UserID: int64(badUserID)}
|
||||
|
||||
idToUse := 8
|
||||
|
||||
rows := sqlmock.NewRows([]string{"plan_id"}).AddRow(8)
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(`^INSERT INTO plans \(plan_description, user_id\) VALUES \(\$1, \$2\) RETURNING plan_id$`).
|
||||
WithArgs(planDescription, userID).
|
||||
WillReturnRows(rows)
|
||||
mock.ExpectCommit()
|
||||
|
||||
// function under test
|
||||
insertedId, err := str.InsertPlan(plan, userID)
|
||||
// check results
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(idToUse, insertedId)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestInsertPlanErr(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
planDescription := "2021-01-01"
|
||||
userID := 2
|
||||
badUserID := 7
|
||||
plan := &models.Plan{PlanDescription: planDescription, UserID: int64(badUserID)}
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(`^INSERT INTO plans \(plan_description, user_id\) VALUES \(\$1, \$2\) RETURNING plan_id$`).
|
||||
WithArgs(planDescription, userID).
|
||||
WillReturnError(fmt.Errorf("example error"))
|
||||
mock.ExpectRollback()
|
||||
|
||||
// function under test
|
||||
_, err := str.InsertPlan(plan, userID)
|
||||
// check results
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestInsertPlanCommitErr(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
planDescription := "2021-01-01"
|
||||
userID := 2
|
||||
plan := &models.Plan{PlanDescription: planDescription, UserID: int64(userID)}
|
||||
idToUse := 8
|
||||
|
||||
rows := sqlmock.NewRows([]string{"plan_id"}).AddRow(idToUse)
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(`^INSERT INTO plans \(plan_description, user_id\) VALUES \(\$1, \$2\) RETURNING plan_id$`).
|
||||
WithArgs(planDescription, userID).
|
||||
WillReturnRows(rows)
|
||||
mock.ExpectCommit().WillReturnError(fmt.Errorf("another error example"))
|
||||
|
||||
// function under test
|
||||
_, err := str.InsertPlan(plan, userID)
|
||||
// check results
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestErrPlanByID(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
idToUse := 1
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
mock.ExpectQuery(`^SELECT plan_id, plan_description, user_id FROM plans WHERE plan_id = \$1 AND user_id = \$2$`).
|
||||
WithArgs(idToUse, 8).
|
||||
WillReturnError(fmt.Errorf("example error"))
|
||||
|
||||
plan, err := str.SelectPlanByID(idToUse, 8)
|
||||
assert.NotNil(err)
|
||||
assert.Nil(plan)
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrPlans(t *testing.T) {
|
||||
// set up tests
|
||||
assert := assert.New(t)
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
mock.ExpectQuery(`^SELECT plan_id, plan_description, user_id FROM plans WHERE user_id = \$1$`).
|
||||
WithArgs(8).
|
||||
WillReturnError(fmt.Errorf("example error"))
|
||||
// function under test
|
||||
plans, err := str.SelectPlans(8)
|
||||
// test results
|
||||
assert.Nil(plans)
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePlan(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
planDescription := "2021-01-01"
|
||||
userID := 2
|
||||
idToUse := 8
|
||||
|
||||
plan := &models.Plan{PlanDescription: planDescription, UserID: int64(userID), PlanID: int64(idToUse)}
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec(`^UPDATE plans SET plan_description = \$1 WHERE plan_id = \$2 AND user_id = \$3$`).
|
||||
WithArgs(planDescription, idToUse, userID).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit()
|
||||
|
||||
// function under test
|
||||
err := str.UpdatePlan(plan, userID)
|
||||
// check results
|
||||
assert.Nil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
func TestUpdatePlanErr(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
planDescription := "2021-01-01"
|
||||
userID := 2
|
||||
idToUse := 8
|
||||
|
||||
plan := &models.Plan{PlanDescription: planDescription, UserID: int64(userID), PlanID: int64(idToUse)}
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec(`^UPDATE plans SET plan_description = \$1 WHERE plan_id = \$2 AND user_id = \$3$`).
|
||||
WithArgs(planDescription, idToUse, userID).
|
||||
WillReturnError(fmt.Errorf("example error"))
|
||||
mock.ExpectRollback()
|
||||
|
||||
// function under test
|
||||
err := str.UpdatePlan(plan, userID)
|
||||
// check results
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUpdatePlanCommitErr(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
planDescription := "2021-01-01"
|
||||
userID := 2
|
||||
idToUse := 8
|
||||
|
||||
plan := &models.Plan{PlanDescription: planDescription, UserID: int64(userID), PlanID: int64(idToUse)}
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec(`^UPDATE plans SET plan_description = \$1 WHERE plan_id = \$2 AND user_id = \$3$`).
|
||||
WithArgs(planDescription, idToUse, userID).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit().WillReturnError(fmt.Errorf("another error example"))
|
||||
|
||||
// function under test
|
||||
err := str.UpdatePlan(plan, userID)
|
||||
// check results
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
458
store/postgres_test.go
Normal file
458
store/postgres_test.go
Normal file
@@ -0,0 +1,458 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/store"
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getDbMock(t *testing.T) (models.Store, sqlmock.Sqlmock) {
|
||||
db, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
|
||||
if err != nil {
|
||||
t.Fatalf("got an error creating a stub db. How?: %s", err)
|
||||
}
|
||||
str, err := store.GetPostgresStore(sqlx.NewDb(db, "pgx"))
|
||||
if err != nil {
|
||||
t.Errorf("Error creating postgres store: %s", err)
|
||||
}
|
||||
|
||||
return str, mock
|
||||
}
|
||||
|
||||
func TestSelectActions(t *testing.T) {
|
||||
// set up test
|
||||
assert := assert.New(t)
|
||||
|
||||
createTime, _ := time.Parse("2006-01-02", "2020-12-31")
|
||||
updateTime, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
completeTime, _ := time.Parse("2006-01-02", "2021-01-05")
|
||||
idToUse := 1
|
||||
estChunks := 5
|
||||
compChunks := 7
|
||||
userIDToUse := 13
|
||||
desc := "Howdy, partner."
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
rows := sqlmock.NewRows([]string{
|
||||
"action_id",
|
||||
"action_description",
|
||||
"estimated_chunks",
|
||||
"completed_chunks",
|
||||
"completed_on",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"plan_id",
|
||||
"user_id"}).
|
||||
AddRow(idToUse, desc, estChunks, compChunks, completeTime, createTime, updateTime, idToUse, userIDToUse).
|
||||
AddRow(idToUse+1, desc, estChunks, compChunks, completeTime, createTime, updateTime, idToUse, userIDToUse)
|
||||
mock.ExpectQuery(`^SELECT action_id, action_description, user_id, estimated_chunks, completed_chunks, completed_on, created_at, updated_at, plan_id FROM actions WHERE user_id = \$1$`).
|
||||
WithArgs(userIDToUse).
|
||||
WillReturnRows(rows)
|
||||
|
||||
// function under test
|
||||
actions, err := str.SelectActions(userIDToUse)
|
||||
|
||||
// test results
|
||||
assert.Nil(err)
|
||||
assert.Equal(2, len(actions))
|
||||
action := actions[0]
|
||||
assert.EqualValues(idToUse, action.ActionID)
|
||||
assert.Equal(desc, action.ActionDescription)
|
||||
assert.Equal(estChunks, action.EstimatedChunks)
|
||||
assert.Equal(compChunks, action.CompletedChunks)
|
||||
assert.Equal(completeTime, *action.CompletedOn)
|
||||
assert.Equal(createTime, *action.CreatedAt)
|
||||
assert.Equal(updateTime, *action.UpdatedAt)
|
||||
assert.Equal(idToUse, action.PlanID)
|
||||
assert.EqualValues(userIDToUse, action.UserID)
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectActionsByPlanID(t *testing.T) {
|
||||
// set up test
|
||||
assert := assert.New(t)
|
||||
|
||||
createTime, _ := time.Parse("2006-01-02", "2020-12-31")
|
||||
updateTime, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
completeTime, _ := time.Parse("2006-01-02", "2021-01-05")
|
||||
idToUse := 1
|
||||
estChunks := 5
|
||||
compChunks := 7
|
||||
userIDToUse := 13
|
||||
desc := "Howdy, partner."
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
rows := sqlmock.NewRows([]string{
|
||||
"action_id",
|
||||
"action_description",
|
||||
"user_id",
|
||||
"estimated_chunks",
|
||||
"completed_chunks",
|
||||
"completed_on",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"plan_id"}).
|
||||
AddRow(idToUse, desc, userIDToUse, estChunks, compChunks, completeTime, createTime, updateTime, idToUse).
|
||||
AddRow(idToUse+1, desc, userIDToUse, estChunks, compChunks, completeTime, createTime, updateTime, idToUse)
|
||||
mock.ExpectQuery(`^SELECT action_id, action_description, user_id, estimated_chunks, completed_chunks, completed_on, created_at, updated_at, plan_id FROM actions WHERE plan_id = \$1 AND user_id = \$2$`).
|
||||
WithArgs(idToUse, userIDToUse).
|
||||
WillReturnRows(rows)
|
||||
|
||||
// function under test
|
||||
actions, err := str.SelectActionsByPlanID(&models.Plan{PlanID: int64(idToUse)}, userIDToUse)
|
||||
|
||||
// test results
|
||||
assert.Nil(err)
|
||||
assert.Equal(2, len(actions))
|
||||
action := actions[0]
|
||||
assert.EqualValues(idToUse, action.ActionID)
|
||||
assert.Equal(desc, action.ActionDescription)
|
||||
assert.Equal(estChunks, action.EstimatedChunks)
|
||||
assert.Equal(compChunks, action.CompletedChunks)
|
||||
assert.Equal(completeTime, *action.CompletedOn)
|
||||
assert.Equal(createTime, *action.CreatedAt)
|
||||
assert.Equal(updateTime, *action.UpdatedAt)
|
||||
assert.Equal(idToUse, action.PlanID)
|
||||
assert.EqualValues(userIDToUse, action.UserID)
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectActionsByPlanIDErr(t *testing.T) {
|
||||
// set up test
|
||||
assert := assert.New(t)
|
||||
|
||||
idToUse := 1
|
||||
userIDToUse := 13
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
mock.ExpectQuery(`^SELECT action_id, action_description, user_id, estimated_chunks, completed_chunks, completed_on, created_at, updated_at, plan_id FROM actions WHERE plan_id = \$1 AND user_id = \$2$`).
|
||||
WithArgs(idToUse, userIDToUse).
|
||||
WillReturnError(fmt.Errorf("example error"))
|
||||
|
||||
// function under test
|
||||
actions, err := str.SelectActionsByPlanID(&models.Plan{PlanID: int64(idToUse)}, userIDToUse)
|
||||
|
||||
// test results
|
||||
assert.NotNil(err)
|
||||
assert.Nil(actions)
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectActionById(t *testing.T) {
|
||||
// set up test
|
||||
assert := assert.New(t)
|
||||
|
||||
createTime, _ := time.Parse("2006-01-02", "2020-12-31")
|
||||
updateTime, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
completeTime, _ := time.Parse("2006-01-02", "2021-01-05")
|
||||
idToUse := 1
|
||||
estChunks := 5
|
||||
compChunks := 7
|
||||
userIDToUse := 13
|
||||
desc := "Howdy, partner."
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
rows := sqlmock.NewRows([]string{
|
||||
"action_id",
|
||||
"action_description",
|
||||
"user_id",
|
||||
"estimated_chunks",
|
||||
"completed_chunks",
|
||||
"completed_on",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"plan_id"}).
|
||||
AddRow(idToUse, desc, userIDToUse, estChunks, compChunks, completeTime, createTime, updateTime, idToUse)
|
||||
|
||||
mock.ExpectQuery(`^SELECT action_id, action_description, user_id, estimated_chunks, completed_chunks, completed_on, created_at, updated_at, plan_id FROM actions WHERE action_id = \$1 AND user_id = \$2`).
|
||||
WithArgs(1, userIDToUse).
|
||||
WillReturnRows(rows)
|
||||
|
||||
// function under test
|
||||
action, err := str.SelectActionByID(1, userIDToUse)
|
||||
|
||||
// test results
|
||||
assert.Nil(err)
|
||||
|
||||
assert.EqualValues(idToUse, action.ActionID)
|
||||
assert.Equal(desc, action.ActionDescription)
|
||||
assert.Equal(estChunks, action.EstimatedChunks)
|
||||
assert.Equal(compChunks, action.CompletedChunks)
|
||||
assert.Equal(completeTime, *action.CompletedOn)
|
||||
assert.Equal(createTime, *action.CreatedAt)
|
||||
assert.Equal(updateTime, *action.UpdatedAt)
|
||||
assert.Equal(idToUse, action.PlanID)
|
||||
assert.EqualValues(userIDToUse, action.UserID)
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrActions(t *testing.T) {
|
||||
// set up tests
|
||||
assert := assert.New(t)
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
mock.ExpectQuery(`^SELECT action_id, action_description, user_id, estimated_chunks, completed_chunks, completed_on, created_at, updated_at, plan_id FROM actions WHERE user_id = \$1$`).WithArgs(1).WillReturnError(fmt.Errorf("example error"))
|
||||
// function under test
|
||||
actions, err := str.SelectActions(1)
|
||||
// test results
|
||||
assert.Nil(actions)
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrActionByID(t *testing.T) {
|
||||
// set up tests
|
||||
assert := assert.New(t)
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
userIDToUse := 3
|
||||
|
||||
mock.ExpectQuery(`^SELECT action_id, action_description, user_id, estimated_chunks, completed_chunks, completed_on, created_at, updated_at, plan_id FROM actions WHERE action_id = \$1 AND user_id = \$2`).WithArgs(1, userIDToUse).WillReturnError(fmt.Errorf("example error"))
|
||||
// function under test
|
||||
action, err := str.SelectActionByID(1, userIDToUse)
|
||||
// test results
|
||||
assert.Nil(action)
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectionLive(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
mock.ExpectPing()
|
||||
|
||||
// perform func under tests
|
||||
err := str.ConnectionLive()
|
||||
// results
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestInsertAction(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
userIDToUse := 7
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
completedOn, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
action := &models.Action{
|
||||
CompletedOn: &completedOn,
|
||||
EstimatedChunks: 3,
|
||||
CompletedChunks: 6,
|
||||
PlanID: 5,
|
||||
ActionDescription: "testing",
|
||||
}
|
||||
|
||||
idToUse := 8
|
||||
|
||||
rows := sqlmock.NewRows([]string{"action_id"}).AddRow(8)
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(`^INSERT INTO actions \(action_description, user_id, estimated_chunks, completed_chunks, completed_on, plan_id\) VALUES \(\$1, \$2, \$3, \$4, \$5, \$6\) RETURNING action_id$`).
|
||||
WithArgs("testing", userIDToUse, 3, 6, completedOn, 5).
|
||||
WillReturnRows(rows)
|
||||
mock.ExpectCommit()
|
||||
|
||||
// function under test
|
||||
insertedId, err := str.InsertAction(action, userIDToUse)
|
||||
// check results
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(idToUse, insertedId)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestInsertActionErr(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
completedOn, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
action := &models.Action{
|
||||
CompletedOn: &completedOn,
|
||||
EstimatedChunks: 3,
|
||||
CompletedChunks: 6,
|
||||
PlanID: 5,
|
||||
ActionDescription: "testing",
|
||||
}
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(`^INSERT INTO actions \(action_description, user_id, estimated_chunks, completed_chunks, completed_on, plan_id\) VALUES \(\$1, \$2, \$3, \$4, \$5, \$6\) RETURNING action_id$`).
|
||||
WithArgs("testing", 7, 3, 6, completedOn, 5).
|
||||
WillReturnError(fmt.Errorf("example error"))
|
||||
mock.ExpectRollback()
|
||||
|
||||
// function under test
|
||||
_, err := str.InsertAction(action, 7)
|
||||
// check results
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestInsertActionCommitErr(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
completedOn, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
action := &models.Action{
|
||||
CompletedOn: &completedOn,
|
||||
EstimatedChunks: 3,
|
||||
CompletedChunks: 6,
|
||||
PlanID: 5,
|
||||
ActionDescription: "testing",
|
||||
}
|
||||
idToUse := 8
|
||||
userIDToUse := 11
|
||||
|
||||
rows := sqlmock.NewRows([]string{"plan_id"}).AddRow(idToUse)
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(`^INSERT INTO actions \(action_description, user_id, estimated_chunks, completed_chunks, completed_on, plan_id\) VALUES \(\$1, \$2, \$3, \$4, \$5, \$6\) RETURNING action_id$`).
|
||||
WithArgs("testing", userIDToUse, 3, 6, completedOn, 5).
|
||||
WillReturnRows(rows)
|
||||
mock.ExpectCommit().WillReturnError(fmt.Errorf("another error example"))
|
||||
|
||||
// function under test
|
||||
_, err := str.InsertAction(action, userIDToUse)
|
||||
// check results
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUpdateAction(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
completedOn, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
action := &models.Action{
|
||||
CompletedOn: &completedOn,
|
||||
EstimatedChunks: 3,
|
||||
CompletedChunks: 6,
|
||||
PlanID: 5,
|
||||
ActionDescription: "testing",
|
||||
ActionID: 2,
|
||||
}
|
||||
userIDToUse := 31
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec(`
|
||||
UPDATE actions SET
|
||||
action_description = \$1,
|
||||
estimated_chunks = \$2,
|
||||
completed_chunks = \$3,
|
||||
completed_on = \$4,
|
||||
plan_id = \$5
|
||||
WHERE action_id = \$6
|
||||
AND user_id = \$7`).
|
||||
WithArgs("testing", 3, 6, completedOn, 5, 2, userIDToUse).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit()
|
||||
|
||||
// function under test
|
||||
err := str.UpdateAction(action, userIDToUse)
|
||||
// check results
|
||||
assert.Nil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUpdateActionErr(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
completedOn, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
action := &models.Action{
|
||||
CompletedOn: &completedOn,
|
||||
EstimatedChunks: 3,
|
||||
CompletedChunks: 6,
|
||||
PlanID: 5,
|
||||
ActionDescription: "testing",
|
||||
ActionID: 2,
|
||||
}
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("UPDATE actions").
|
||||
WithArgs("testing", 3, 6, completedOn, 5, 2, 31).
|
||||
WillReturnError(fmt.Errorf("example error"))
|
||||
mock.ExpectRollback()
|
||||
|
||||
// function under test
|
||||
err := str.UpdateAction(action, 31)
|
||||
// check results
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUpdateActionCommitErr(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
completedOn, _ := time.Parse("2006-01-02", "2021-01-01")
|
||||
action := &models.Action{
|
||||
CompletedOn: &completedOn,
|
||||
EstimatedChunks: 3,
|
||||
CompletedChunks: 6,
|
||||
PlanID: 5,
|
||||
ActionDescription: "testing",
|
||||
ActionID: 2,
|
||||
}
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("UPDATE actions").
|
||||
WithArgs("testing", 3, 6, completedOn, 5, 2, 31).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit().WillReturnError(fmt.Errorf("another error example"))
|
||||
|
||||
// function under test
|
||||
err := str.UpdateAction(action, 31)
|
||||
// check results
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
150
store/postgres_user_test.go
Normal file
150
store/postgres_user_test.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSelectUserByUsername(t *testing.T) {
|
||||
// set up test
|
||||
assert := assert.New(t)
|
||||
|
||||
id := 1
|
||||
username := "test"
|
||||
displayName := "Tom Est"
|
||||
password := []byte("ABC€")
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
|
||||
rows := sqlmock.NewRows([]string{
|
||||
"user_id",
|
||||
"username",
|
||||
"display_name",
|
||||
"password",
|
||||
}).
|
||||
AddRow(id, username, displayName, password)
|
||||
|
||||
mock.ExpectQuery(`^SELECT user_id, username, display_name, password FROM users WHERE username = \$1`).
|
||||
WithArgs(username).
|
||||
WillReturnRows(rows)
|
||||
|
||||
// function under test
|
||||
user, err := str.SelectUserByUsername(username)
|
||||
|
||||
// test results
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(id, user.UserID)
|
||||
assert.Equal(username, user.Username)
|
||||
assert.Equal(displayName, user.DisplayName)
|
||||
assert.Equal(password, user.Password)
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrUserByID(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
username := "snth"
|
||||
mock.ExpectQuery(`^SELECT user_id, username, display_name, password FROM users WHERE username = \$1`).WithArgs(username).WillReturnError(fmt.Errorf("example error"))
|
||||
|
||||
user, err := str.SelectUserByUsername(username)
|
||||
assert.NotNil(err)
|
||||
assert.Nil(user)
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertUser(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
username := "test"
|
||||
displayName := "Tom Est"
|
||||
password := []byte("ABC€")
|
||||
usr := &models.User{Username: username, DisplayName: displayName, Password: password}
|
||||
|
||||
idToUse := 8
|
||||
|
||||
rows := sqlmock.NewRows([]string{"user_id"}).AddRow(8)
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(`^INSERT INTO users \(username, display_name, password\) VALUES \(\$1, \$2, \$3\) RETURNING user_id$`).
|
||||
WithArgs(username, displayName, password).
|
||||
WillReturnRows(rows)
|
||||
mock.ExpectCommit()
|
||||
|
||||
// function under test
|
||||
insertedId, err := str.InsertUser(usr)
|
||||
// check results
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(idToUse, insertedId)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestInsertUserErr(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
username := "test"
|
||||
displayName := "Tom Est"
|
||||
password := []byte("ABC€")
|
||||
usr := &models.User{Username: username, DisplayName: displayName, Password: password}
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(`^INSERT INTO users \(username, display_name, password\) VALUES \(\$1, \$2, \$3\) RETURNING user_id$`).
|
||||
WithArgs(username, displayName, password).
|
||||
WillReturnError(fmt.Errorf("example error"))
|
||||
mock.ExpectRollback()
|
||||
|
||||
// function under test
|
||||
_, err := str.InsertUser(usr)
|
||||
// check results
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestInsertUserCommitErr(t *testing.T) {
|
||||
// setup
|
||||
assert := assert.New(t)
|
||||
|
||||
str, mock := getDbMock(t)
|
||||
username := "test"
|
||||
displayName := "Tom Est"
|
||||
password := []byte("ABC€")
|
||||
usr := &models.User{Username: username, DisplayName: displayName, Password: password}
|
||||
|
||||
idToUse := 8
|
||||
|
||||
rows := sqlmock.NewRows([]string{"user_id"}).AddRow(idToUse)
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(`^INSERT INTO users \(username, display_name, password\) VALUES \(\$1, \$2, \$3\) RETURNING user_id$`).
|
||||
WithArgs(username, displayName, password).
|
||||
WillReturnRows(rows)
|
||||
mock.ExpectCommit().WillReturnError(fmt.Errorf("another error example"))
|
||||
|
||||
// function under test
|
||||
_, err := str.InsertUser(usr)
|
||||
// check results
|
||||
assert.NotNil(err)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
77
store/store.go
Normal file
77
store/store.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"log"
|
||||
|
||||
"gitea.deepak.science/deepak/gogmagog/config"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
|
||||
// Blank imports to provide drivers.
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
_ "github.com/jackc/pgx/v4/stdlib"
|
||||
)
|
||||
|
||||
// GetStore provides a store to back the model based on a postgres connection.
|
||||
func GetStore(dbConf *config.DBConfig) (models.Store, error) {
|
||||
|
||||
if dbConf.Type == "postgres" {
|
||||
db, err := createPostgresDB(dbConf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return GetPostgresStore(db)
|
||||
}
|
||||
return nil, fmt.Errorf("Unsupported database type: %v", dbConf.Type)
|
||||
|
||||
}
|
||||
|
||||
func createPostgresDB(dbConf *config.DBConfig) (*sqlx.DB, error) {
|
||||
connStr := fmt.Sprintf("user=%s password=%s host=%s port=%s database=%s sslmode=disable",
|
||||
dbConf.User,
|
||||
dbConf.Password,
|
||||
dbConf.Host,
|
||||
dbConf.Port,
|
||||
dbConf.Database)
|
||||
|
||||
tmp, err := sql.Open("pgx", connStr)
|
||||
if err != nil {
|
||||
log.Print("Could not connect to database: \n", err)
|
||||
return nil, err
|
||||
}
|
||||
db := sqlx.NewDb(tmp, "pgx")
|
||||
if err := db.Ping(); err != nil {
|
||||
log.Print("database ping failed\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
driver, err := postgres.WithInstance(db.DB, &postgres.Config{})
|
||||
if err != nil {
|
||||
log.Print("Could not create driver for db migration", err)
|
||||
return nil, err
|
||||
}
|
||||
m, err := migrate.NewWithDatabaseInstance("file://store/migrations", "postgres", driver)
|
||||
if err != nil {
|
||||
log.Print("Could not perform migration", err)
|
||||
return nil, err
|
||||
}
|
||||
if dbConf.DropOnStart {
|
||||
log.Print("Going down")
|
||||
m.Down()
|
||||
}
|
||||
if err := m.Up(); err != nil {
|
||||
if err == migrate.ErrNoChange {
|
||||
log.Print("No migration needed.")
|
||||
} else {
|
||||
log.Printf("An error occurred while syncing the database.. %v", err)
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
log.Print("Performed database migration")
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
53
tokens/deterministicToker.go
Normal file
53
tokens/deterministicToker.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package tokens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type deterministicToker struct{}
|
||||
|
||||
// GetDeterministicToker returns a zero security toker for testing purposes.
|
||||
// Do not use in production.
|
||||
func GetDeterministicToker() Toker {
|
||||
return &deterministicToker{}
|
||||
}
|
||||
|
||||
func (d *deterministicToker) EncodeUser(user *models.UserNoPassword) string {
|
||||
tok := &UserToken{ID: user.UserID, Username: user.Username}
|
||||
ret, _ := json.Marshal(tok)
|
||||
return string(ret)
|
||||
}
|
||||
|
||||
func (d *deterministicToker) DecodeTokenString(tokenString string) (*UserToken, error) {
|
||||
var tok UserToken
|
||||
err := json.Unmarshal([]byte(tokenString), &tok)
|
||||
return &tok, err
|
||||
}
|
||||
|
||||
func (d *deterministicToker) Authenticator(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tokenString := TokenFromHeader(r)
|
||||
if tokenString == "" {
|
||||
log.Print("No valid token found")
|
||||
unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
userToken, err := d.DecodeTokenString(tokenString)
|
||||
if err != nil {
|
||||
log.Printf("Error while verifying token: %s", err)
|
||||
unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Got user with ID: [%d]", userToken.ID)
|
||||
ctx := context.WithValue(r.Context(), userIDCtxKey, userToken.ID)
|
||||
ctx = context.WithValue(ctx, usernameCtxKey, userToken.Username)
|
||||
// Authenticated
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
80
tokens/deterministic_toker_middleware_test.go
Normal file
80
tokens/deterministic_toker_middleware_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package tokens_test
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var dtMiddlewareURL string = "/"
|
||||
|
||||
func dtRequestAuth(header string) *http.Request {
|
||||
req, _ := http.NewRequest("GET", dtMiddlewareURL, nil)
|
||||
req.Header.Add(authKey, header)
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func verifyingHandlerdt(t *testing.T, username string, userID int) http.Handler {
|
||||
assert := assert.New(t)
|
||||
toker := tokens.GetDeterministicToker()
|
||||
dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
receivedID, _ := tokens.GetUserID(ctx)
|
||||
receivedUsername, _ := tokens.GetUsername(ctx)
|
||||
assert.EqualValues(userID, receivedID)
|
||||
assert.Equal(username, receivedUsername)
|
||||
})
|
||||
return toker.Authenticator(dummyHandler)
|
||||
}
|
||||
|
||||
func TestMiddlewareNoTokendt(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, dtMiddlewareURL, nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
middlewareHandler := verifyingHandlerdt(t, "", 0)
|
||||
middlewareHandler.ServeHTTP(rr, req)
|
||||
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
}
|
||||
|
||||
func TestMiddlewareBadTokendt(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
req := mwRequestAuth("Bearer bad")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
middlewareHandler := verifyingHandlerdt(t, "", 0)
|
||||
middlewareHandler.ServeHTTP(rr, req)
|
||||
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
}
|
||||
|
||||
func TestMiddlewareGoodTokendt(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
idToUse := 3
|
||||
username := "username"
|
||||
displayName := "display name"
|
||||
user := &models.UserNoPassword{UserID: int64(idToUse), Username: username, DisplayName: displayName}
|
||||
|
||||
toker := tokens.GetDeterministicToker()
|
||||
validToken := toker.EncodeUser(user)
|
||||
log.Print(validToken)
|
||||
req := mwRequestAuth("Bearer " + validToken)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
middlewareHandler := verifyingHandlerdt(t, username, idToUse)
|
||||
middlewareHandler.ServeHTTP(rr, req)
|
||||
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
}
|
||||
88
tokens/middleware.go
Normal file
88
tokens/middleware.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package tokens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type contextKey struct {
|
||||
name string
|
||||
}
|
||||
|
||||
var userIDCtxKey = &contextKey{"UserID"}
|
||||
var usernameCtxKey = &contextKey{"Username"}
|
||||
|
||||
func unauthorized(w http.ResponseWriter, r *http.Request) {
|
||||
code := http.StatusUnauthorized
|
||||
http.Error(w, http.StatusText(code), code)
|
||||
}
|
||||
|
||||
// TokenFromHeader tries to retreive the token string from the
|
||||
// "Authorization" reqeust header: "Authorization: BEARER T".
|
||||
func TokenFromHeader(r *http.Request) string {
|
||||
// Get token from authorization header.
|
||||
bearer := r.Header.Get("Authorization")
|
||||
if len(bearer) > 7 && strings.ToUpper(bearer[0:6]) == "BEARER" {
|
||||
return bearer[7:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (tok *jwtToker) Authenticator(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tokenString := TokenFromHeader(r)
|
||||
if tokenString == "" {
|
||||
log.Print("No valid token found")
|
||||
unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
userToken, err := tok.DecodeTokenString(tokenString)
|
||||
if err != nil {
|
||||
log.Printf("Error while verifying token: %s", err)
|
||||
unauthorized(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Got user with ID: [%d]", userToken.ID)
|
||||
ctx := context.WithValue(r.Context(), userIDCtxKey, userToken.ID)
|
||||
ctx = context.WithValue(ctx, usernameCtxKey, userToken.Username)
|
||||
// Authenticated
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
// GetUserID is a convenience method that gets the user ID from the context.
|
||||
// I hate the fact that we're passing user ID on the context, but it is more
|
||||
// idiomatic Go than any type shenanigans.
|
||||
func GetUserID(ctx context.Context) (int, error) {
|
||||
userID, ok := ctx.Value(userIDCtxKey).(int64)
|
||||
if !ok {
|
||||
return -1, fmt.Errorf("Could not parse user ID [%s] from context", ctx.Value(userIDCtxKey))
|
||||
|
||||
}
|
||||
return int(userID), nil
|
||||
}
|
||||
|
||||
// SetUserID sets the username field on a context, necessary because the key is an unexported custom type.
|
||||
func SetUserID(ctx context.Context, id int) context.Context {
|
||||
return context.WithValue(ctx, userIDCtxKey, int64(id))
|
||||
}
|
||||
|
||||
// GetUsername does something similar to GetUserID.
|
||||
func GetUsername(ctx context.Context) (string, error) {
|
||||
username, ok := ctx.Value(usernameCtxKey).(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Could not parse username [%s] from context", ctx.Value(usernameCtxKey))
|
||||
}
|
||||
return username, nil
|
||||
}
|
||||
|
||||
// GetContextForUserValues is a test helper method that creates a context with user ID set.
|
||||
func GetContextForUserValues(userID int, username string) context.Context {
|
||||
ctx := context.WithValue(context.Background(), userIDCtxKey, int64(userID))
|
||||
return context.WithValue(ctx, usernameCtxKey, username)
|
||||
}
|
||||
49
tokens/middleware_context_test.go
Normal file
49
tokens/middleware_context_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package tokens_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGoodContext(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
idToUse := 3
|
||||
username := "username"
|
||||
|
||||
ctx := tokens.GetContextForUserValues(idToUse, username)
|
||||
|
||||
receivedID, err := tokens.GetUserID(ctx)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(idToUse, receivedID)
|
||||
|
||||
receivedUsername, err := tokens.GetUsername(ctx)
|
||||
assert.Nil(err)
|
||||
assert.Equal(username, receivedUsername)
|
||||
|
||||
}
|
||||
|
||||
func TestBadContext(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := tokens.GetUserID(ctx)
|
||||
assert.NotNil(err)
|
||||
|
||||
_, err = tokens.GetUsername(ctx)
|
||||
assert.NotNil(err)
|
||||
|
||||
}
|
||||
|
||||
func TestSetContext(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
idToUse := 3
|
||||
ctx := tokens.SetUserID(context.Background(), 3)
|
||||
receivedID, err := tokens.GetUserID(ctx)
|
||||
assert.Nil(err)
|
||||
assert.EqualValues(idToUse, receivedID)
|
||||
}
|
||||
78
tokens/middleware_http_test.go
Normal file
78
tokens/middleware_http_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package tokens_test
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var middlewareURL string = "/"
|
||||
|
||||
func mwRequestAuth(header string) *http.Request {
|
||||
req, _ := http.NewRequest("GET", middlewareURL, nil)
|
||||
req.Header.Add(authKey, header)
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func verifyingHandler(t *testing.T, username string, userID int) http.Handler {
|
||||
assert := assert.New(t)
|
||||
toker := tokens.New("secret")
|
||||
dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
receivedID, _ := tokens.GetUserID(ctx)
|
||||
receivedUsername, _ := tokens.GetUsername(ctx)
|
||||
assert.EqualValues(userID, receivedID)
|
||||
assert.Equal(username, receivedUsername)
|
||||
})
|
||||
return toker.Authenticator(dummyHandler)
|
||||
}
|
||||
|
||||
func TestMiddlewareNoToken(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, middlewareURL, nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
middlewareHandler := verifyingHandler(t, "", 0)
|
||||
middlewareHandler.ServeHTTP(rr, req)
|
||||
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
}
|
||||
|
||||
func TestMiddlewareBadToken(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
req := mwRequestAuth("Bearer bad")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
middlewareHandler := verifyingHandler(t, "", 0)
|
||||
middlewareHandler.ServeHTTP(rr, req)
|
||||
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusUnauthorized, status)
|
||||
}
|
||||
|
||||
func TestMiddlewareGoodToken(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
idToUse := 3
|
||||
username := "username"
|
||||
displayName := "display name"
|
||||
user := &models.UserNoPassword{UserID: int64(idToUse), Username: username, DisplayName: displayName}
|
||||
|
||||
toker := tokens.New("secret")
|
||||
validToken := toker.EncodeUser(user)
|
||||
req := mwRequestAuth("Bearer " + validToken)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
middlewareHandler := verifyingHandler(t, username, idToUse)
|
||||
middlewareHandler.ServeHTTP(rr, req)
|
||||
|
||||
status := rr.Code
|
||||
assert.Equal(http.StatusOK, status)
|
||||
}
|
||||
56
tokens/middleware_test.go
Normal file
56
tokens/middleware_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package tokens_test
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
url = ""
|
||||
authKey = "Authorization"
|
||||
)
|
||||
|
||||
func requestWithAuth(header string) *http.Request {
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Add(authKey, header)
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func TestHeaderParseBasic(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
header := "Bearer testing"
|
||||
req := requestWithAuth(header)
|
||||
|
||||
assert.Equal("testing", tokens.TokenFromHeader(req))
|
||||
}
|
||||
|
||||
func TestHeaderParseNoSpace(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
header := "Bearerxtesting"
|
||||
req := requestWithAuth(header)
|
||||
|
||||
assert.Equal("testing", tokens.TokenFromHeader(req))
|
||||
}
|
||||
|
||||
func TestHeaderParseUnicode(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
header := "Bearer 🌸"
|
||||
req := requestWithAuth(header)
|
||||
|
||||
assert.Equal("🌸", tokens.TokenFromHeader(req))
|
||||
}
|
||||
|
||||
func TestHeaderParseMalformed(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
header := "testing"
|
||||
req := requestWithAuth(header)
|
||||
|
||||
assert.Equal("", tokens.TokenFromHeader(req))
|
||||
}
|
||||
86
tokens/tokens.go
Normal file
86
tokens/tokens.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package tokens
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"github.com/go-chi/jwtauth"
|
||||
"github.com/lestrrat-go/jwx/jwt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Toker represents a tokenizer, capable of encoding and verifying tokens.
|
||||
type Toker interface {
|
||||
EncodeUser(user *models.UserNoPassword) string
|
||||
DecodeTokenString(tokenString string) (*UserToken, error)
|
||||
Authenticator(http.Handler) http.Handler
|
||||
}
|
||||
|
||||
type jwtToker struct {
|
||||
tokenAuth *jwtauth.JWTAuth
|
||||
}
|
||||
|
||||
// New returns a default Toker for a given secret key.
|
||||
func New(key string) Toker {
|
||||
return &jwtToker{tokenAuth: jwtauth.New("HS256", []byte(key), nil)}
|
||||
}
|
||||
|
||||
func (tok *jwtToker) EncodeUser(user *models.UserNoPassword) string {
|
||||
claims := map[string]interface{}{
|
||||
"user_id": user.UserID,
|
||||
"username": user.Username,
|
||||
"display_name": user.DisplayName,
|
||||
"iss": "gogmagog.deepak.science",
|
||||
"aud": "gogmagog.deepak.science",
|
||||
}
|
||||
jwtauth.SetIssuedNow(claims)
|
||||
jwtauth.SetExpiryIn(claims, 2*time.Hour)
|
||||
_, tokenString, _ := tok.tokenAuth.Encode(claims)
|
||||
return tokenString
|
||||
}
|
||||
|
||||
// UserToken represents a decoded jwt token.
|
||||
type UserToken struct {
|
||||
ID int64
|
||||
Username string
|
||||
}
|
||||
|
||||
func (tok *jwtToker) DecodeTokenString(tokenString string) (*UserToken, error) {
|
||||
token, err := tok.tokenAuth.Decode(tokenString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error decoding token")
|
||||
}
|
||||
|
||||
// Should never happen, remove soon.
|
||||
// if token == nil {
|
||||
// return nil, fmt.Errorf("Token was nil")
|
||||
// }
|
||||
|
||||
err = jwt.Validate(
|
||||
token,
|
||||
jwt.WithIssuer("gogmagog.deepak.science"),
|
||||
jwt.WithAudience("gogmagog.deepak.science"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userIDRaw, ok := token.Get("user_id")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error finding user_id claim")
|
||||
}
|
||||
userID, ok := userIDRaw.(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Could not parse [%s] as userID", userIDRaw)
|
||||
}
|
||||
usernameRaw, ok := token.Get("username")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error finding username claim")
|
||||
}
|
||||
username, ok := usernameRaw.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Could not parse [%s] as username", usernameRaw)
|
||||
}
|
||||
|
||||
return &UserToken{ID: int64(userID), Username: username}, nil
|
||||
}
|
||||
165
tokens/tokens_test.go
Normal file
165
tokens/tokens_test.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package tokens_test
|
||||
|
||||
import (
|
||||
"gitea.deepak.science/deepak/gogmagog/models"
|
||||
"gitea.deepak.science/deepak/gogmagog/tokens"
|
||||
"github.com/go-chi/jwtauth"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
toker := tokens.New("secret")
|
||||
idToUse := int64(3)
|
||||
usernameToUse := "test"
|
||||
usr := &models.UserNoPassword{
|
||||
UserID: idToUse,
|
||||
Username: usernameToUse,
|
||||
DisplayName: "Ted Est III",
|
||||
}
|
||||
token := toker.EncodeUser(usr)
|
||||
|
||||
userToken, err := toker.DecodeTokenString(token)
|
||||
assert.Nil(err)
|
||||
assert.Equal(usernameToUse, userToken.Username)
|
||||
assert.Equal(idToUse, userToken.ID)
|
||||
_, err = tokens.New("bad secret").DecodeTokenString(token)
|
||||
assert.NotNil(err)
|
||||
}
|
||||
|
||||
func getTokenString(claims map[string]interface{}) string {
|
||||
auth := jwtauth.New("HS256", []byte("secret"), nil)
|
||||
|
||||
jwtauth.SetIssuedNow(claims)
|
||||
jwtauth.SetExpiryIn(claims, 2*time.Hour)
|
||||
_, tokenString, _ := auth.Encode(claims)
|
||||
|
||||
return tokenString
|
||||
}
|
||||
|
||||
func TestDecodeBadIssuer(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
toker := tokens.New("secret")
|
||||
|
||||
idToUse := 3
|
||||
username := "test"
|
||||
gog := "gogmagog.deepak.science"
|
||||
|
||||
claims := map[string]interface{}{
|
||||
"user_id": int64(idToUse),
|
||||
"username": username,
|
||||
"display_name": "display_name",
|
||||
"iss": gog,
|
||||
"aud": "bad",
|
||||
}
|
||||
|
||||
token := getTokenString(claims)
|
||||
_, err := toker.DecodeTokenString(token)
|
||||
assert.NotNil(err)
|
||||
|
||||
}
|
||||
|
||||
func TestDecodeBadAudience(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
toker := tokens.New("secret")
|
||||
|
||||
idToUse := 3
|
||||
username := "test"
|
||||
gog := "gogmagog.deepak.science"
|
||||
|
||||
claims := map[string]interface{}{
|
||||
"user_id": int64(idToUse),
|
||||
"username": username,
|
||||
"display_name": "display_name",
|
||||
"iss": "bad",
|
||||
"aud": gog,
|
||||
}
|
||||
|
||||
token := getTokenString(claims)
|
||||
_, err := toker.DecodeTokenString(token)
|
||||
assert.NotNil(err)
|
||||
|
||||
}
|
||||
|
||||
func TestDecodeMissingUserID(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
toker := tokens.New("secret")
|
||||
|
||||
username := "test"
|
||||
gog := "gogmagog.deepak.science"
|
||||
|
||||
claims := map[string]interface{}{
|
||||
"username": username,
|
||||
"display_name": "display_name",
|
||||
"iss": gog,
|
||||
"aud": gog,
|
||||
}
|
||||
|
||||
token := getTokenString(claims)
|
||||
_, err := toker.DecodeTokenString(token)
|
||||
assert.NotNil(err)
|
||||
|
||||
}
|
||||
|
||||
func TestDecodeBadUserID(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
toker := tokens.New("secret")
|
||||
|
||||
username := "test"
|
||||
gog := "gogmagog.deepak.science"
|
||||
|
||||
claims := map[string]interface{}{
|
||||
"username": username,
|
||||
"user_id": "id",
|
||||
"display_name": "display_name",
|
||||
"iss": gog,
|
||||
"aud": gog,
|
||||
}
|
||||
|
||||
token := getTokenString(claims)
|
||||
_, err := toker.DecodeTokenString(token)
|
||||
assert.NotNil(err)
|
||||
|
||||
}
|
||||
|
||||
func TestDecodeMissingUsername(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
toker := tokens.New("secret")
|
||||
|
||||
idToUse := 3
|
||||
gog := "gogmagog.deepak.science"
|
||||
|
||||
claims := map[string]interface{}{
|
||||
"user_id": int64(idToUse),
|
||||
"display_name": "display_name",
|
||||
"iss": gog,
|
||||
"aud": gog,
|
||||
}
|
||||
|
||||
token := getTokenString(claims)
|
||||
_, err := toker.DecodeTokenString(token)
|
||||
assert.NotNil(err)
|
||||
|
||||
}
|
||||
|
||||
func TestDecodeBadUsername(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
toker := tokens.New("secret")
|
||||
|
||||
gog := "gogmagog.deepak.science"
|
||||
|
||||
claims := map[string]interface{}{
|
||||
"username": 5,
|
||||
"user_id": 3,
|
||||
"display_name": "display_name",
|
||||
"iss": gog,
|
||||
"aud": gog,
|
||||
}
|
||||
|
||||
token := getTokenString(claims)
|
||||
_, err := toker.DecodeTokenString(token)
|
||||
assert.NotNil(err)
|
||||
|
||||
}
|
||||
19
util/snake.go
Normal file
19
util/snake.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package util
|
||||
|
||||
import "unicode"
|
||||
|
||||
// ToSnake converts CamelCase to snake_case.
|
||||
func ToSnake(in string) string {
|
||||
runes := []rune(in)
|
||||
length := len(runes)
|
||||
|
||||
var out []rune
|
||||
for i := 0; i < length; i++ {
|
||||
if i > 0 && unicode.IsUpper(runes[i]) && ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {
|
||||
out = append(out, '_')
|
||||
}
|
||||
out = append(out, unicode.ToLower(runes[i]))
|
||||
}
|
||||
|
||||
return string(out)
|
||||
}
|
||||
69
util/snake_test.go
Normal file
69
util/snake_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.deepak.science/deepak/gogmagog/util"
|
||||
)
|
||||
|
||||
// from https://github.com/paultyng/snake
|
||||
|
||||
type SnakeTest struct {
|
||||
input string
|
||||
output string
|
||||
}
|
||||
|
||||
var tests = []SnakeTest{
|
||||
{"a", "a"},
|
||||
{"snake", "snake"},
|
||||
{"A", "a"},
|
||||
{"ID", "id"},
|
||||
{"MOTD", "motd"},
|
||||
{"Snake", "snake"},
|
||||
{"SnakeTest", "snake_test"},
|
||||
{"SnakeID", "snake_id"},
|
||||
{"SnakeIDGoogle", "snake_id_google"},
|
||||
{"LinuxMOTD", "linux_motd"},
|
||||
{"OMGWTFBBQ", "omgwtfbbq"},
|
||||
{"omg_wtf_bbq", "omg_wtf_bbq"},
|
||||
{"APIResponse", "api_response"},
|
||||
}
|
||||
|
||||
func TestToSnake(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
if util.ToSnake(test.input) != test.output {
|
||||
t.Errorf(`ToSnake("%s"), wanted "%s", got \%s"`, test.input, test.output, util.ToSnake(test.input))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var benchmarks = []string{
|
||||
"a",
|
||||
"snake",
|
||||
"A",
|
||||
"Snake",
|
||||
"SnakeTest",
|
||||
"SnakeID",
|
||||
"SnakeIDGoogle",
|
||||
"LinuxMOTD",
|
||||
"OMGWTFBBQ",
|
||||
"omg_wtf_bbq",
|
||||
"APIResponse",
|
||||
}
|
||||
|
||||
func BenchmarkToSnake(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, input := range benchmarks {
|
||||
util.ToSnake(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkToLower(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, input := range benchmarks {
|
||||
strings.ToLower(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user