Repository: StarpTech/go-web
Branch: master
Commit: 80bf6b777a6b
Files: 56
Total size: 57.6 KB
Directory structure:
gitextract_pi_oupt8/
├── .editorconfig
├── .github/
│ └── workflows/
│ ├── go.yml
│ └── goreleaser.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── app.json
├── config/
│ └── config.go
├── docker-compose.yml
├── go.mod
├── go.sum
├── internal/
│ ├── cache/
│ │ └── cache.go
│ ├── context/
│ │ └── app_context.go
│ ├── controller/
│ │ ├── healthcheck.go
│ │ ├── healthcheck_test.go
│ │ ├── init_test.go
│ │ ├── metric_test.go
│ │ ├── user-list.go
│ │ ├── user.go
│ │ └── user_test.go
│ ├── core/
│ │ ├── cache_store.go
│ │ ├── error_handler.go
│ │ ├── errors/
│ │ │ └── boom.go
│ │ ├── middleware/
│ │ │ └── app_context.go
│ │ ├── router.go
│ │ ├── server.go
│ │ ├── template.go
│ │ ├── user_store.go
│ │ └── validator.go
│ ├── i18n/
│ │ └── i18n.go
│ ├── models/
│ │ ├── models.go
│ │ └── user.go
│ └── store/
│ ├── cache.go
│ └── user.go
├── locales/
│ └── en/
│ └── default.po
├── main.go
├── scripts/
│ └── create.db.sql
└── web/
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── README.md
├── package.json
├── src/
│ ├── app/
│ │ ├── app.js
│ │ ├── components/
│ │ │ ├── header.js
│ │ │ └── like-button.js
│ │ ├── index.js
│ │ └── styles.scss
│ └── global/
│ ├── app.js
│ ├── global.html
│ ├── index.js
│ └── styles.scss
└── templates/
├── layouts/
│ └── base.html
└── pages/
├── user-list.html
└── user.html
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
; http://editorconfig.org/
root = true
[*]
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[{Makefile,go.mod,go.sum,*.go}]
indent_style = tab
indent_size = 8
================================================
FILE: .github/workflows/go.yml
================================================
name: CI
on:
pull_request:
paths-ignore:
- "**.md"
- "docs/**"
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.14
uses: actions/setup-go@v1
with:
go-version: 1.14
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Install tools
run: make setup
- name: Test
run: make ci
================================================
FILE: .github/workflows/goreleaser.yml
================================================
name: Release with goreleaser
on:
push:
branches:
- "!*"
tags:
- "v*.*.*"
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Unshallow
run: git fetch --prune --unshallow
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.13.x
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v1
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
vendor
.netrc
.vscode
.vs
.tern-project
.DS_Store
.idea
.cgo_ldflags
tmp
.eslintcache
# dependencies
web/node_modules
web/.pnp
web/.pnp.js
# testing
web/coverage
# misc
.DS_Store
.env*
/.sass-cache
/connect.lock
/coverage/*
/test-results/*
/libpeerconnection.log
npm-debug.log
testem.log
/typings
/vendor
# dist
dist
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# make artifacts
/bin
# vendored files
/vendor
# test outputs
/test-results.xml
junit-results
cypress/screenshots
cypress/videos
coverage.txt
# gcloud utils
cloud_sql_proxy
.now
# tools
air
air.log
# test data
cockroach-data
bin
================================================
FILE: CONTRIBUTING.md
================================================
## Setup your machine
`go-web` is written in [Go](https://golang.org/).
Prerequisites:
- `make`
- [Go 1.14+](https://golang.org/doc/install)
- [Docker](https://www.docker.com/)
- `gpg` (probably already installed on your system)
Clone `goweb` anywhere:
```sh
$ git clone git@github.com:StarpTech/go-web.git
```
Install the build and lint dependencies:
```sh
$ make setup
```
A good way of making sure everything is all right is running the test suite:
```sh
$ make test
```
## Test your change
You can create a branch for your changes and try to build from the source as you go:
```sh
$ make
```
When you are satisfied with the changes, we suggest you run:
```sh
$ make ci
```
Which runs all the linters and tests.
## Create a commit
Commit messages should be well formatted, and to make that "standardized", we
are using Conventional Commits.
You can follow the documentation on
[their website](https://www.conventionalcommits.org).
## Submit a pull request
Push your branch to your `go-web` fork and open a pull request against the
master branch.
## Deployment
Tag a new release and push it to origin. This will trigger the Github CI to deploy the commit.
```
git tag v1.0.0
git push origin v1.0.0
```
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 Dustin Deus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
MODULE = $(shell env GO111MODULE=on $(GO) list -m)
DATE ?= $(shell date +%FT%T%z)
VERSION ?= $(shell git describe --tags --always --dirty --match=v* 2> /dev/null || \
cat $(CURDIR)/.version 2> /dev/null || echo v0)
PKGS = $(or $(PKG),$(shell env GO111MODULE=on $(GO) list ./...))
TESTPKGS = $(shell env GO111MODULE=on $(GO) list -f \
'{{ if or .TestGoFiles .XTestGoFiles }}{{ .ImportPath }}{{ end }}' \
$(PKGS))
BIN = $(CURDIR)/bin
GO = go
TIMEOUT = 15
V = 0
Q = $(if $(filter 1,$V),,@)
M = $(shell printf "\033[34;1m▶\033[0m")
export GO111MODULE=on
export GOPROXY=https://proxy.golang.org,direct
.PHONY: all
all: fmt lint | $(BIN) ; $(info $(M) building executable…) @ ## Build program binary
$Q $(GO) build \
-tags release \
-ldflags '-X $(MODULE)/cmd.Version=$(VERSION) -X $(MODULE)/cmd.BuildDate=$(DATE)' \
-o $(BIN)/$(basename $(MODULE)) main.go
# Tools
# Install all the build and lint dependencies
setup:
# Install by default to .bin
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- v1.24.0
go mod tidy
.PHONY: setup
$(BIN):
@mkdir -p $@
$(BIN)/%: | $(BIN) ; $(info $(M) building $(PACKAGE)…)
$Q tmp=$$(mktemp -d); \
env GO111MODULE=off GOPATH=$$tmp GOBIN=$(BIN) $(GO) get $(PACKAGE) \
|| ret=$$?; \
rm -rf $$tmp ; exit $$ret
# Tests
# Run all the tests
test:
LC_ALL=C go test $(TEST_OPTIONS) -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m
.PHONY: test
.PHONY: cover
cover: ; $(info $(M) running coverage…) @ # Run all the tests and opens the coverage report
go tool cover -html=coverage.txt
.PHONY: cover
.PHONY: fmt
fmt: ; $(info $(M) running gofmt…) @ ## Run gofmt on all source files
$Q $(GO) fmt $(PKGS)
.PHONY: lint
lint: ; $(info $(M) running lint…) @ ## Run gofmt on all source files
./bin/golangci-lint run ./...
.PHONY: ci
ci: all test; $(info $(M) running all the tests and code checks…) @ ## Run all the tests and code checks
# UI
.PHONY: ui
ui: ; @ ## Run frontend development server
cd ui && yarn run dev
.PHONY: build-ui
build-ui: ; @ ## Build frontend production build
cd ui && yarn run build
# API
.PHONY: start
start-api: ; @ ## Start api
go run main.go
# Misc
.PHONY: clean
clean: ; $(info $(M) cleaning…) @ ## Cleanup everything
@rm -rf $(BIN)
@rm -rf test/tests.* test/coverage.*
.PHONY: help
help:
@grep -E '^[ a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
.PHONY: version
version:
@echo $(VERSION)
================================================
FILE: README.md
================================================

[](http://opensource.org/licenses/MIT)
[](https://github.com/StarpTech/go-web/actions)
[](https://goreportcard.com/report/github.com/StarpTech/go-web)
# Go-Web
Modern Web Application with Golang "Keep it simple, stupid"
# Stack
## Backend
- HTTP Middleware [Echo](https://echo.labstack.com/)
- ORM library [gorm](https://github.com/jinzhu/gorm)
- Configuration [env](https://github.com/caarlos0/env)
- Load ENV variables from .env file [godotenv](https://github.com/joho/godotenv)
- Payload validation [validator](https://github.com/go-playground/validator)
- Cache [Redis](https://github.com/go-redis/redis)
- Localization [gotext](https://github.com/leonelquinteros/gotext)
- Database [CockroachDB](https://github.com/cockroachdb/cockroach)
- Releasing [goreleaser](https://github.com/goreleaser/goreleaser)
## Frontend
- Server side templating [Go Templates](https://golang.org/pkg/text/template/)
- Module Bundler [Parcel bundler](https://github.com/parcel-bundler/parcel)
- Javascript UI library [React](https://github.com/facebook/react)
# Getting Started
## Project structure
Follows https://github.com/golang-standards/project-layout
## Building From Source
This project requires Go +1.13 and Go module support.
To build the project run:
```
make
```
## Bootstrap infrastructure and run application
This project requires docker and docker compose to run the required services.
1. To run the services:
```
docker-compose up
```
2. To create database
```
docker run --network="host" -it cockroachdb/cockroach:v19.2.1 sql --insecure -e "$(cat ./scripts/create.db.sql)"
```
3. Build [web application](ui/README.md)
4. Start server
```
go run main.go
```
5. Navigate to users list [page](http://127.0.0.1/users)
## CI and Static Analysis
### CI
All pull requests will run through CI, which is currently hosted by Github-CI.
Community contributors should be able to see the outcome of this process by looking at the checks on their PR.
Please fix any issues to ensure a prompt review from members of the team.
### Static Analysis
This project uses the following static analysis tools.
Failure during the running of any of these tools results in a failed build.
Generally, code must be adjusted to satisfy these tools, though there are exceptions.
- [go vet](https://golang.org/cmd/vet/) checks for Go code that should be considered incorrect.
- [go fmt](https://golang.org/cmd/gofmt/) checks that Go code is correctly formatted.
- [golangci-lint](https://github.com/golangci/golangci-lintt) checks for things like: unused code, code that can be simplified, code that is incorrect and code that will have performance issues.
- [go mod tidy](https://tip.golang.org/cmd/go/#hdr-Add_missing_and_remove_unused_modules) ensures that the source code and go.mod agree.
# Releasing
When a new tag is pushed, the version is released with [goreleaser](https://github.com/goreleaser/goreleaser).
```
$ git tag -a v0.1.0 -m "First release"
$ git push origin v0.1.0 # => want to release v0.1.0
```
# Tooling
- IDE plugin [vscode-go](https://github.com/Microsoft/vscode-go)
- Administration of cockroachdb [DBeaver](https://dbeaver.io/)
- REST client [Postman](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en)
- Go testing in the browser [go-convey](https://github.com/smartystreets/goconvey)
- Benchmarking [bombardier](http://github.com/codesenberg/bombardier)
# Documentation
```
$ godoc github.com/starptech/go-web/pkg/controller
$ godoc -http=:6060
```
Visit localhost:6060 and search for `go-web`
# Benchmarking
```
$ bombardier -c 10 -n 10000 http://localhost:8080/users
```
# Cockroachdb Cluster overview
http://localhost:8111/
## Deploy on Heroku
[](https://heroku.com/deploy?template=https://github.com/StarpTech/go-web)
# Further reading
- http://www.alexedwards.net/blog/organising-database-access
- https://12factor.net/
- https://dev.otto.de/2015/09/30/on-monoliths-and-microservices/
================================================
FILE: app.json
================================================
{
"name": "go-web",
"description": "Modern Web Application with Golang",
"website": "https://github.com/StarpTech/go-web",
"repository": "https://github.com/StarpTech/go-web",
"logo": "https://github.com/StarpTech/go-web/raw/master/big-gopher.png",
"success_url": "/",
"keywords": [
"starter-kit",
"golang",
"frontend",
"api"
],
"addons": [
"heroku-postgresql",
"heroku-redis"
],
"env": {}
}
================================================
FILE: config/config.go
================================================
package config
import (
"log"
"github.com/caarlos0/env"
"github.com/joho/godotenv"
)
type Configuration struct {
Address string `env:"ADDRESS" envDefault:":8080"`
Dialect string `env:"DIALECT,required" envDefault:"postgres"`
AssetsBuildDir string `env:"ASSETS_BUILD_DIR"`
TemplateDir string `env:"TPL_DIR"`
LayoutDir string `env:"LAYOUT_DIR"`
RedisAddr string `env:"REDIS_ADDR" envDefault:":6379"`
RedisPwd string `env:"REDIS_PWD"`
ConnectionString string `env:"CONNECTION_STRING,required"`
IsProduction bool `env:"PRODUCTION"`
GrayLogAddr string `env:"GRAYLOG_ADDR"`
RequestLogger bool `env:"REQUEST_LOGGER"`
LocaleDir string `env:"LOCALE_DIR" envDefault:"locales"`
Lang string `env:"LANG" envDefault:"en_US"`
LangDomain string `env:"LANG_DOMAIN" envDefault:"default"`
JwtSecret string `env:"JWT_SECRET,required"`
}
func NewConfig(files ...string) (*Configuration, error) {
err := godotenv.Load(files...)
if err != nil {
log.Printf("No .env file could be found %q\n", files)
}
cfg := Configuration{}
err = env.Parse(&cfg)
if err != nil {
return nil, err
}
return &cfg, nil
}
================================================
FILE: docker-compose.yml
================================================
version: "3"
services:
roach1:
container_name: roach1
image: cockroachdb/cockroach:v19.2.1
command: start --insecure
ports:
- "26257:26257"
- "8111:8080"
volumes:
- ./cockroach-data/roach1:/cockroach/cockroach-data
networks:
roachnet:
aliases:
- roach1
roach2:
container_name: roach2
image: cockroachdb/cockroach:v19.2.1
command: start --insecure --join=roach1
volumes:
- ./cockroach-data/roach2:/cockroach/cockroach-data
depends_on:
- roach1
networks:
roachnet:
aliases:
- roach2
roach3:
container_name: roach3
image: cockroachdb/cockroach:v19.2.1
command: start --insecure --join=roach1
volumes:
- ./cockroach-data/roach3:/cockroach/cockroach-data
depends_on:
- roach1
networks:
roachnet:
aliases:
- roach3
redis:
ports:
- "6379:6379"
image: "redis:alpine"
networks:
roachnet:
driver: bridge
================================================
FILE: go.mod
================================================
module github.com/starptech/go-web
go 1.13
require (
github.com/caarlos0/env v3.5.0+incompatible
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 // indirect
github.com/go-playground/locales v0.12.1 // indirect
github.com/go-playground/universal-translator v0.16.0 // indirect
github.com/go-redis/redis v6.15.5+incompatible
github.com/go-sql-driver/mysql v1.5.0 // indirect
github.com/golang/protobuf v1.3.1 // indirect
github.com/jinzhu/gorm v1.9.12
github.com/jinzhu/now v1.1.1 // indirect
github.com/joho/godotenv v1.3.0
github.com/labstack/echo/v4 v4.1.14
github.com/labstack/gommon v0.3.0
github.com/leodido/go-urn v1.1.0 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mattn/go-sqlite3 v2.0.2+incompatible // indirect
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect
github.com/onsi/ginkgo v1.11.0 // indirect
github.com/onsi/gomega v1.8.1 // indirect
github.com/prometheus/client_golang v0.9.1
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect
github.com/prometheus/common v0.2.0 // indirect
github.com/prometheus/procfs v0.0.0-20190322151404-55ae3d9d5573 // indirect
github.com/stretchr/testify v1.4.0
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad // indirect
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa // indirect
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/go-playground/validator.v9 v9.31.0
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
)
================================================
FILE: go.sum
================================================
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs=
github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 h1:OGNva6WhsKst5OZf7eZOklDztV3hwtTHovdrLHV+MsA=
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-redis/redis v6.15.5+incompatible h1:pLky8I0rgiblWfa8C1EV7fPEUv0aH6vKRaYHc/YRHVk=
github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1/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/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q=
github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/labstack/echo/v4 v4.1.14 h1:h8XP66UfB3tUm+L3QPw7tmwAu3pJaA/nyfHPCcz46ic=
github.com/labstack/echo/v4 v4.1.14/go.mod h1:Q5KZ1vD3V5FEzjM79hjwVrC3ABr7F5IdM23bXQMRDGg=
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.2+incompatible h1:qzw9c2GNT8UFrgWNDhCTqRqYUSmu/Dav/9Z58LGpk7U=
github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a h1:0Q3H0YXzMHiciXtRcM+j0jiCe8WKPQHoRgQiRTnfcLY=
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a/go.mod h1:CdTTBOYzS5E4mWS1N8NWP6AHI19MP0A2B18n3hLzRMk=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34=
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/pkg/errors v0.8.0/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=
github.com/prometheus/client_golang v0.9.1 h1:K47Rk0v/fkEfwfQet2KWhscE0cJzjgCCDBG2KHZoVno=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190322151404-55ae3d9d5573 h1:gAuD3LIrjkoOOPLlhGlZWZXztrQII9a9kT6HS5jFtSY=
github.com/prometheus/procfs v0.0.0-20190322151404-55ae3d9d5573/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad h1:Jh8cai0fqIK+f6nG0UgPW5wFk8wmiMhM3AyciDBdtQg=
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M=
gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc=
gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
================================================
FILE: internal/cache/cache.go
================================================
package cache
import (
"log"
"github.com/go-redis/redis"
"github.com/starptech/go-web/config"
)
func NewCache(config *config.Configuration) *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: config.RedisAddr,
Password: config.RedisPwd,
DB: 0, // use default DB
})
pong, err := client.Ping().Result()
if err != nil || pong == "" {
log.Fatalf("redis cache: got no PONG back %q", err)
}
return client
}
================================================
FILE: internal/context/app_context.go
================================================
package context
import (
"github.com/labstack/echo/v4"
"github.com/starptech/go-web/config"
"github.com/starptech/go-web/internal/i18n"
"github.com/starptech/go-web/internal/store"
)
// AppContext is the new context in the request / response cycle
// We can use the db store, cache and central configuration
type AppContext struct {
echo.Context
UserStore store.User
Cache store.Cache
Config *config.Configuration
Loc i18n.I18ner
}
================================================
FILE: internal/controller/healthcheck.go
================================================
package controller
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/starptech/go-web/internal/context"
)
type Healthcheck struct{}
type healthcheckReport struct {
Health string `json:"health"`
Details map[string]bool `json:"details"`
}
// GetHealthcheck returns the current functional state of the application
func (ctrl Healthcheck) GetHealthcheck(c echo.Context) error {
cc := c.(*context.AppContext)
m := healthcheckReport{Health: "OK"}
dbCheck := cc.UserStore.Ping()
cacheCheck := cc.Cache.Ping()
if dbCheck != nil {
m.Health = "NOT"
m.Details["db"] = false
}
if cacheCheck != nil {
m.Health = "NOT"
m.Details["cache"] = false
}
return c.JSON(http.StatusOK, m)
}
================================================
FILE: internal/controller/healthcheck_test.go
================================================
package controller
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)
func TestHealthcheck(t *testing.T) {
req := httptest.NewRequest(echo.GET, "/.well-known/health-check", nil)
rec := httptest.NewRecorder()
e.server.Echo.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
}
================================================
FILE: internal/controller/init_test.go
================================================
package controller
import (
"os"
"testing"
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/log"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/starptech/go-web/config"
"github.com/starptech/go-web/internal/core"
"github.com/starptech/go-web/internal/models"
)
var e struct {
config *config.Configuration
logger *log.Logger
server *core.Server
testUser *models.User
}
func TestMain(m *testing.M) {
e.config = &config.Configuration{
ConnectionString: "host=localhost user=gorm dbname=gorm sslmode=disable password=mypassword",
TemplateDir: "../templates/*.html",
LayoutDir: "../templates/layouts/*.html",
Dialect: "postgres",
RedisAddr: ":6379",
}
e.server = core.NewServer(e.config)
setup()
code := m.Run()
tearDown()
os.Exit(code)
}
func setup() {
userCtrl := &User{}
healthCtrl := &Healthcheck{}
g := e.server.Echo.Group("/api")
g.GET("/users/:id", userCtrl.GetUserJSON)
u := e.server.Echo.Group("/users")
u.GET("/:id", userCtrl.GetUser)
e.server.Echo.GET("/.well-known/health-check", healthCtrl.GetHealthcheck)
e.server.Echo.GET("/.well-known/metrics", echo.WrapHandler(promhttp.Handler()))
// test data
user := models.User{Name: "Peter"}
mr := e.server.GetModelRegistry()
err := mr.Register(user)
if err != nil {
e.server.Echo.Logger.Fatal(err)
}
mr.AutoMigrateAll()
mr.Save(&user)
e.testUser = &user
}
func tearDown() {
e.server.GetModelRegistry().AutoDropAll()
}
================================================
FILE: internal/controller/metric_test.go
================================================
package controller
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)
func TestMetric(t *testing.T) {
req := httptest.NewRequest(echo.GET, "/.well-known/metrics", nil)
rec := httptest.NewRecorder()
e.server.Echo.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
}
================================================
FILE: internal/controller/user-list.go
================================================
package controller
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/starptech/go-web/internal/context"
"github.com/starptech/go-web/internal/core/errors"
"github.com/starptech/go-web/internal/models"
)
type (
UserList struct{}
UserListViewModel struct {
Users []UserViewModel
}
)
func (ctrl UserList) GetUsers(c echo.Context) error {
cc := c.(*context.AppContext)
users := []models.User{}
err := cc.UserStore.Find(&users)
if err != nil {
b := errors.NewBoom(errors.UserNotFound, errors.ErrorText(errors.UserNotFound), err)
c.Logger().Error(err)
return c.JSON(http.StatusNotFound, b)
}
viewModel := UserListViewModel{
Users: make([]UserViewModel, len(users)),
}
for index, user := range users {
viewModel.Users[index] = UserViewModel{
Name: user.Name,
ID: user.ID,
}
}
return c.Render(http.StatusOK, "user-list.html", viewModel)
}
================================================
FILE: internal/controller/user.go
================================================
package controller
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/starptech/go-web/internal/context"
"github.com/starptech/go-web/internal/core/errors"
"github.com/starptech/go-web/internal/models"
)
type (
User struct{}
UserViewModel struct {
Name string
ID string
}
)
func (ctrl User) GetUser(c echo.Context) error {
cc := c.(*context.AppContext)
userID := c.Param("id")
user := models.User{ID: userID}
err := cc.UserStore.First(&user)
if err != nil {
b := errors.NewBoom(errors.UserNotFound, errors.ErrorText(errors.UserNotFound), err)
c.Logger().Error(err)
return c.JSON(http.StatusNotFound, b)
}
vm := UserViewModel{
Name: user.Name,
ID: user.ID,
}
return c.Render(http.StatusOK, "user.html", vm)
}
func (ctrl User) GetUserJSON(c echo.Context) error {
cc := c.(*context.AppContext)
userID := c.Param("id")
user := models.User{ID: userID}
err := cc.UserStore.First(&user)
if err != nil {
b := errors.NewBoom(errors.UserNotFound, errors.ErrorText(errors.UserNotFound), err)
c.Logger().Error(err)
return c.JSON(http.StatusNotFound, b)
}
vm := UserViewModel{
Name: user.Name,
ID: user.ID,
}
return c.JSON(http.StatusOK, vm)
}
================================================
FILE: internal/controller/user_test.go
================================================
package controller
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/labstack/echo/v4"
"github.com/starptech/go-web/internal/context"
"github.com/starptech/go-web/internal/core/middleware"
"github.com/starptech/go-web/internal/models"
"github.com/stretchr/testify/assert"
)
type UserFakeStore struct{}
func (s *UserFakeStore) First(m *models.User) error {
return nil
}
func (s *UserFakeStore) Find(m *[]models.User) error {
return nil
}
func (s *UserFakeStore) Create(m *models.User) error {
return nil
}
func (s *UserFakeStore) Ping() error {
return nil
}
func TestUserPage(t *testing.T) {
req := httptest.NewRequest(echo.GET, "/users/"+e.testUser.ID, nil)
rec := httptest.NewRecorder()
e.server.Echo.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
}
func TestUnitGetUserJson(t *testing.T) {
s := echo.New()
g := s.Group("/api")
req := httptest.NewRequest(echo.GET, "/api/users/"+e.testUser.ID, nil)
rec := httptest.NewRecorder()
userCtrl := &User{}
cc := &context.AppContext{
Config: e.config,
UserStore: &UserFakeStore{},
}
s.Use(middleware.AppContext(cc))
g.GET("/users/:id", userCtrl.GetUserJSON)
s.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
}
================================================
FILE: internal/core/cache_store.go
================================================
package core
import (
"time"
"github.com/go-redis/redis"
)
// CacheStore simple redis implementation
type CacheStore struct {
Cache *redis.Client
}
func (s *CacheStore) Ping() error {
return s.Cache.Ping().Err()
}
func (s *CacheStore) Get(key string) (string, error) {
return s.Cache.Get(key).Result()
}
func (s *CacheStore) Set(key string, value interface{}, exp time.Duration) (string, error) {
return s.Cache.Set(key, value, exp).Result()
}
================================================
FILE: internal/core/error_handler.go
================================================
package core
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/starptech/go-web/internal/core/errors"
)
func HTTPErrorHandler(err error, c echo.Context) {
c.Logger().Error(err)
code := http.StatusInternalServerError
switch v := err.(type) {
case *echo.HTTPError:
err := c.JSON(v.Code, v)
if err != nil {
c.Logger().Error("error handler: json encoding", err)
}
default:
e := errors.NewBoom(errors.InternalError, "Bad implementation", nil)
err := c.JSON(code, e)
if err != nil {
c.Logger().Error("error handler: json encoding", err)
}
}
}
================================================
FILE: internal/core/errors/boom.go
================================================
package errors
const (
InternalError = "internalError"
UserNotFound = "userNotFound"
InvalidBindingModel = "invalidBindingModel"
EntityCreationError = "entityCreationError"
)
var errorMessage = map[string]string{
"internalError": "an internal error occured",
"userNotFound": "user could not be found",
"invalidBindingModel": "model could not be bound",
"EntityCreationError": "could not create entity",
}
// Booms can contain multiple boom errors
type Booms struct {
Errors []Boom `json:"errors"`
}
func (b *Booms) Add(e Boom) {
b.Errors = append(b.Errors, e)
}
func NewBooms() Booms {
return Booms{}
}
// boom represent the basic structure of an json error
type Boom struct {
Code string `json:"code"`
Message string `json:"message"`
Details interface{} `json:"details"`
}
func NewBoom(code, msg string, details interface{}) Boom {
return Boom{Code: code, Message: msg, Details: details}
}
func ErrorText(code string) string {
return errorMessage[code]
}
================================================
FILE: internal/core/middleware/app_context.go
================================================
package middleware
import (
"github.com/labstack/echo/v4"
"github.com/starptech/go-web/internal/context"
)
func AppContext(cc *context.AppContext) echo.MiddlewareFunc {
return func(h echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
cc.Context = c
return h(cc)
}
}
}
================================================
FILE: internal/core/router.go
================================================
package core
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/starptech/go-web/internal/context"
mid "github.com/starptech/go-web/internal/core/middleware"
"github.com/starptech/go-web/internal/i18n"
v "gopkg.in/go-playground/validator.v9"
)
func NewRouter(server *Server) *echo.Echo {
config := server.config
e := echo.New()
e.Validator = &Validator{validator: v.New()}
cc := context.AppContext{
Cache: &CacheStore{Cache: server.cache},
Config: config,
UserStore: &UserStore{DB: server.db},
Loc: i18n.New(),
}
e.Use(mid.AppContext(&cc))
if config.RequestLogger {
e.Use(middleware.Logger()) // request logger
}
e.Use(middleware.Recover()) // panic errors are thrown
e.Use(middleware.BodyLimit("5M")) // limit body payload to 5MB
e.Use(middleware.Secure()) // provide protection against injection attacks
e.Use(middleware.RequestID()) // generate unique requestId
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
}))
// add custom error formating
e.HTTPErrorHandler = HTTPErrorHandler
// Add html templates with go template syntax
renderer := newTemplateRenderer(config.LayoutDir, config.TemplateDir)
e.Renderer = renderer
return e
}
================================================
FILE: internal/core/server.go
================================================
package core
import (
"context"
"log"
"os"
"os/signal"
"time"
"github.com/go-redis/redis"
"github.com/jinzhu/gorm"
"github.com/labstack/echo/v4"
"github.com/starptech/go-web/config"
"github.com/starptech/go-web/internal/cache"
"github.com/starptech/go-web/internal/i18n"
"github.com/starptech/go-web/internal/models"
)
type Server struct {
Echo *echo.Echo // HTTP middleware
config *config.Configuration // Configuration
db *gorm.DB // Database connection
cache *redis.Client // Redis cache connection
modelRegistry *models.Model // Model registry for migration
}
// NewServer will create a new instance of the application
func NewServer(config *config.Configuration) *Server {
server := &Server{}
server.config = config
i18n.Configure(config.LocaleDir, config.Lang, config.LangDomain)
server.modelRegistry = models.NewModel()
err := server.modelRegistry.OpenWithConfig(config)
if err != nil {
log.Fatalf("gorm: could not connect to db %q", err)
}
server.cache = cache.NewCache(config)
server.db = server.modelRegistry.DB
server.Echo = NewRouter(server)
return server
}
// GetDB returns gorm (ORM)
func (s *Server) GetDB() *gorm.DB {
return s.db
}
// GetCache returns the current redis client
func (s *Server) GetCache() *redis.Client {
return s.cache
}
// GetConfig return the current app configuration
func (s *Server) GetConfig() *config.Configuration {
return s.config
}
// GetModelRegistry returns the model registry
func (s *Server) GetModelRegistry() *models.Model {
return s.modelRegistry
}
// Start the http server
func (s *Server) Start(addr string) error {
return s.Echo.Start(addr)
}
// ServeStaticFiles serve static files for development purpose
func (s *Server) ServeStaticFiles() {
s.Echo.Static("/assets", s.config.AssetsBuildDir)
}
// GracefulShutdown Wait for interrupt signal
// to gracefully shutdown the server with a timeout of 5 seconds.
func (s *Server) GracefulShutdown() {
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// close cache
if s.cache != nil {
cErr := s.cache.Close()
if cErr != nil {
s.Echo.Logger.Fatal(cErr)
}
}
// close database connection
if s.db != nil {
dErr := s.db.Close()
if dErr != nil {
s.Echo.Logger.Fatal(dErr)
}
}
// shutdown http server
if err := s.Echo.Shutdown(ctx); err != nil {
s.Echo.Logger.Fatal(err)
}
}
================================================
FILE: internal/core/template.go
================================================
package core
import (
"fmt"
"html/template"
"io"
"log"
"path/filepath"
"github.com/labstack/echo/v4"
"github.com/starptech/go-web/internal/i18n"
)
var mainTmpl = `{{define "main" }} {{ template "base" . }} {{ end }}`
type templateRenderer struct {
templates map[string]*template.Template
}
// NewTemplateRenderer creates a new setup to render layout based go templates
func newTemplateRenderer(layoutsDir, templatesDir string) *templateRenderer {
r := &templateRenderer{}
r.templates = make(map[string]*template.Template)
r.Load(layoutsDir, templatesDir)
return r
}
func (t *templateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
tmpl, ok := t.templates[name]
if !ok {
c.Logger().Fatalf("the template %s does not exist", name)
return fmt.Errorf("the template %s does not exist", name)
}
return tmpl.ExecuteTemplate(w, "base", data)
}
func (t *templateRenderer) Load(layoutsDir, templatesDir string) {
layouts, err := filepath.Glob(layoutsDir)
if err != nil {
log.Fatal(err)
}
includes, err := filepath.Glob(templatesDir)
if err != nil {
log.Fatal(err)
}
funcMap := template.FuncMap{
"Loc": i18n.Get,
}
mainTemplate := template.New("main")
mainTemplate.Funcs(funcMap)
mainTemplate, err = mainTemplate.Parse(mainTmpl)
if err != nil {
log.Fatal(err)
}
for _, file := range includes {
fileName := filepath.Base(file)
files := append(layouts, file)
t.templates[fileName], err = mainTemplate.Clone()
if err != nil {
log.Fatal(err)
}
t.templates[fileName] = template.Must(t.templates[fileName].ParseFiles(files...))
}
}
================================================
FILE: internal/core/user_store.go
================================================
package core
import (
"github.com/jinzhu/gorm"
"github.com/starptech/go-web/internal/models"
)
// UserStore implements the UserStore interface
type UserStore struct {
DB *gorm.DB
}
func (s *UserStore) First(m *models.User) error {
return s.DB.First(m).Error
}
func (s *UserStore) Create(m *models.User) error {
return s.DB.Create(m).Error
}
func (s *UserStore) Find(m *[]models.User) error {
return s.DB.Find(m).Error
}
func (s *UserStore) Ping() error {
return s.DB.DB().Ping()
}
================================================
FILE: internal/core/validator.go
================================================
package core
import (
validator "gopkg.in/go-playground/validator.v9"
)
type Validator struct {
validator *validator.Validate
}
func (v *Validator) Validate(i interface{}) error {
return v.validator.Struct(i)
}
================================================
FILE: internal/i18n/i18n.go
================================================
package i18n
import gotext "gopkg.in/leonelquinteros/gotext.v1"
type I18ner interface {
Get(string, ...interface{}) string
}
type I18n struct{}
func New() *I18n {
return &I18n{}
}
func Configure(lib, lang, dom string) {
gotext.Configure(lib, lang, dom)
}
func (i *I18n) Get(str string, vars ...interface{}) string {
return gotext.Get(str, vars...)
}
func Get(str string, vars ...interface{}) string {
return gotext.Get(str, vars...)
}
================================================
FILE: internal/models/models.go
================================================
package models
import (
"errors"
"reflect"
"strings"
"time"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
"github.com/starptech/go-web/config"
)
// Model facilitate database interactions
type Model struct {
models map[string]reflect.Value
isOpen bool
*gorm.DB
}
// NewModel returns a new Model without opening database connection
func NewModel() *Model {
return &Model{
models: make(map[string]reflect.Value),
}
}
// IsOpen returns true if the Model has already established connection
// to the database
func (m *Model) IsOpen() bool {
return m.isOpen
}
// OpenWithConfig opens database connection with the settings found in cfg
func (m *Model) OpenWithConfig(cfg *config.Configuration) error {
db, err := gorm.Open(cfg.Dialect, cfg.ConnectionString)
if err != nil {
return err
}
// https://github.com/go-sql-driver/mysql/issues/461
db.DB().SetConnMaxLifetime(time.Minute * 5)
db.DB().SetMaxIdleConns(0)
db.DB().SetMaxOpenConns(20)
m.DB = db
m.isOpen = true
return nil
}
// Register adds the values to the models registry
func (m *Model) Register(values ...interface{}) error {
// do not work on them.models first, this is like an insurance policy
// whenever we encounter any error in the values nothing goes into the registry
models := make(map[string]reflect.Value)
if len(values) > 0 {
for _, val := range values {
rVal := reflect.ValueOf(val)
if rVal.Kind() == reflect.Ptr {
rVal = rVal.Elem()
}
switch rVal.Kind() {
case reflect.Struct:
models[getTypeName(rVal.Type())] = reflect.New(rVal.Type())
default:
return errors.New("models must be structs")
}
}
}
for k, v := range models {
m.models[k] = v
}
return nil
}
// AutoMigrateAll runs migrations for all the registered models
func (m *Model) AutoMigrateAll() {
for _, v := range m.models {
m.AutoMigrate(v.Interface())
}
}
// AutoDropAll drops all tables of all registered models
func (m *Model) AutoDropAll() {
for _, v := range m.models {
m.DropTableIfExists(v.Interface())
}
}
func getTypeName(typ reflect.Type) string {
if typ.Name() != "" {
return typ.Name()
}
split := strings.Split(typ.String(), ".")
return split[len(split)-1]
}
================================================
FILE: internal/models/user.go
================================================
package models
import "time"
type User struct {
ID string `gorm:"type:uuid;primary_key;default:gen_random_uuid()"`
Name string `sql:"type:varchar(30)"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
================================================
FILE: internal/store/cache.go
================================================
package store
import "time"
type Cache interface {
Ping() error
Get(string) (string, error)
Set(string, interface{}, time.Duration) (string, error)
}
================================================
FILE: internal/store/user.go
================================================
package store
import "github.com/starptech/go-web/internal/models"
type User interface {
First(m *models.User) error
Find(m *[]models.User) error
Create(m *models.User) error
Ping() error
}
================================================
FILE: locales/en/default.po
================================================
# msgid ""
# msgstr ""
# Initial comment
# Headers below
"Language: en\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
# Some comment
msgid "My text"
msgstr "Translated text"
# More comments
msgid "Another string"
msgstr ""
================================================
FILE: main.go
================================================
package main
import (
"log"
"github.com/labstack/echo/v4"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/starptech/go-web/config"
"github.com/starptech/go-web/internal/controller"
"github.com/starptech/go-web/internal/core"
"github.com/starptech/go-web/internal/models"
)
func main() {
config, err := config.NewConfig()
if err != nil {
log.Fatalf("%+v\n", err)
}
// create server
server := core.NewServer(config)
// serve files for dev
server.ServeStaticFiles()
userCtrl := &controller.User{}
userListCtrl := &controller.UserList{}
healthCtrl := &controller.Healthcheck{}
// api endpoints
g := server.Echo.Group("/api")
g.GET("/users/:id", userCtrl.GetUserJSON)
// pages
u := server.Echo.Group("/users")
u.GET("", userListCtrl.GetUsers)
u.GET("/:id", userCtrl.GetUser)
// metric / health endpoint according to RFC 5785
server.Echo.GET("/.well-known/health-check", healthCtrl.GetHealthcheck)
server.Echo.GET("/.well-known/metrics", echo.WrapHandler(promhttp.Handler()))
// migration for dev
user := models.User{Name: "Peter"}
mr := server.GetModelRegistry()
err = mr.Register(user)
if err != nil {
server.Echo.Logger.Fatal(err)
}
mr.AutoMigrateAll()
mr.Create(&user)
// Start server
go func() {
if err := server.Start(config.Address); err != nil {
server.Echo.Logger.Info("shutting down the server")
}
}()
server.GracefulShutdown()
}
================================================
FILE: scripts/create.db.sql
================================================
CREATE USER IF NOT EXISTS goweb;
CREATE DATABASE goweb;
GRANT ALL ON DATABASE goweb TO goweb;
================================================
FILE: web/.babelrc
================================================
{
"presets": [
[
"@babel/preset-env",
{
"modules": false
}
],
"@babel/preset-react"
]
}
================================================
FILE: web/.eslintrc.json
================================================
{
"rules": {},
"env": {
"es6": true,
"browser": true
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"extends": [
"eslint:recommended",
"plugin:prettier/recommended"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"plugins": [
"react"
]
}
================================================
FILE: web/.gitignore
================================================
.cache/
coverage/
dist/*
!dist/index.html
node_modules/
*.log
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
================================================
FILE: web/README.md
================================================
# Go Web
Web application for the api
## Building and running on localhost
First install dependencies:
```sh
yarn install
```
To run in hot module reloading mode:
```sh
yarn start
```
To create a production build:
```sh
yarn run build-prod
```
## Development
Run the server and start the bundler in watch mode
```sh
yarn start
go run main.go
```
================================================
FILE: web/package.json
================================================
{
"name": "empty-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"keywords": [],
"author": "",
"license": "ISC",
"scripts": {
"clean": "rm -rf dist",
"start": "parcel watch src/app/index.js --public-url /assets/dist",
"build-prod": "yarn clean && parcel build src/app/index.js --public-url /assets/dist"
},
"dependencies": {
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
"devDependencies": {
"@babel/core": "^7.8.3",
"@babel/preset-env": "^7.8.3",
"@babel/preset-react": "^7.8.3",
"bootstrap": "^4.4.1",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.9.0",
"eslint-plugin-prettier": "^3.1.2",
"parcel-bundler": "^1.12.4",
"prettier": "^1.19.1",
"sass": "^1.25.1-test.1"
}
}
================================================
FILE: web/src/app/app.js
================================================
import React from "react";
export default function App({ message }) {
return (
<div>
<h1>{message}</h1>
</div>
);
}
================================================
FILE: web/src/app/components/header.js
================================================
import React from "react";
export default function Header({ message }) {
return (
<header className="bg-white text-white">
<div className="container text-center text-dark">
<img src="http://127.0.0.1:8080/assets/images/go-web.png" />
<h1>{message}</h1>
<p className="lead">Welcome to modern web development with Go</p>
</div>
</header>
);
}
================================================
FILE: web/src/app/components/like-button.js
================================================
import React, { useState } from "react";
export default function LikeButton({ id }) {
const [likes, setLikes] = useState(() => parseInt(id));
return (
<button className="btn btn-secondary" onClick={() => setLikes(draft => setLikes(++draft))}>
Like ({likes})
</button>
);
}
================================================
FILE: web/src/app/index.js
================================================
import React from "react";
import ReactDOM from "react-dom";
import App from "./app";
import LikeButton from "./components/like-button";
import Header from "./components/header";
import "./styles.scss";
var mountNode = document.getElementById("app");
ReactDOM.render(<App />, mountNode);
document.querySelectorAll(".like-button-component").forEach(domContainer => {
ReactDOM.render(<LikeButton {...domContainer.dataset} />, domContainer);
});
document.querySelectorAll(".header-component").forEach(domContainer => {
ReactDOM.render(<Header {...domContainer.dataset} />, domContainer);
});
================================================
FILE: web/src/app/styles.scss
================================================
@import "~bootstrap/scss/bootstrap";
header {
padding: 156px 0 100px;
}
section {
padding: 50px 0;
}
.bd-highlight {
background-color: rgb(237, 253, 255);
border: 1px solid rgb(106, 214, 227);
}
================================================
FILE: web/src/global/app.js
================================================
import React from "react";
export default function App({ message }) {
return (
<div className="container">
<ul className="nav">
<li className="nav-item">
<a className="nav-link active" href="/">
Home
</a>
</li>
<li className="nav-item">
<a className="nav-link" href="/users">
Users
</a>
</li>
</ul>
<h1>{message}</h1>
<img src="http://127.0.0.1:8080/assets/images/go-web.png" />
</div>
);
}
================================================
FILE: web/src/global/global.html
================================================
<script src="index.js"></script>
================================================
FILE: web/src/global/index.js
================================================
import React from "react";
import ReactDOM from "react-dom";
import App from "./app";
import "./styles.scss";
var mountNode = document.getElementById("app");
ReactDOM.render(
<App message="Welcome to modern web development with Go" />,
mountNode
);
================================================
FILE: web/src/global/styles.scss
================================================
@import "~bootstrap/scss/bootstrap";
================================================
FILE: web/templates/layouts/base.html
================================================
{{ define "base" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link href="/assets/dist/index.css" rel="stylesheet">
<title>{{ block "title" . }} {{end}}</title>
</head>
<body id="page-top">
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top" id="mainNav">
<div class="container">
<a class="navbar-brand js-scroll-trigger" href="/users">Go Web</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link js-scroll-trigger" href="#about">About</a>
</li>
<li class="nav-item">
<a class="nav-link js-scroll-trigger" href="#services">Services</a>
</li>
<li class="nav-item">
<a class="nav-link js-scroll-trigger" href="#contact">Contact</a>
</li>
</ul>
</div>
</div>
</nav>
{{ template "header" . }}
<section id="about">
<div class="container">
<div class="row">
<div id="app"></div>
</div>
{{ template "content" . }}
</div>
</section>
<!-- Footer -->
<footer class="py-5 bg-dark">
<div class="container">
<p class="m-0 text-center text-white">Copyright © Your Website 2019</p>
</div>
<!-- /.container -->
</footer>
<script src="/assets/dist/index.js"></script>
</body>
</html>
{{ end }}
================================================
FILE: web/templates/pages/user-list.html
================================================
{{define "title"}}User list{{end}}
{{define "header"}}
<div class="header-component" data-message="User list"></div>
{{end}}
{{define "content"}}
<div class="d-flex flex-column bd-highlight mb-3">
{{range .Users}}
<div class="p-2 bd-highlight">{{.Name}}: <a href="/users/{{.ID}}">Details</a></div>
{{end}}
</div>
{{end}}
================================================
FILE: web/templates/pages/user.html
================================================
{{define "title"}}User {{.Name}}{{end}}
{{define "header"}}
<div class="header-component" data-message="{{.Name}}"></div>
{{end}}
{{define "content"}}
<div class="d-flex flex-column bd-highlight mb-3">
<div class="p-2 bd-highlight">ID: {{.ID}}</div>
<div class="p-2 bd-highlight">Name: {{.Name}}</div>
<div class="p-2 bd-highlight">Translated content: {{ Loc "My text" }}</div>
<div class="p-2 bd-highlight">
Mount server rendered html as react component:
<div class="like-button-component d-inline-block" data-id="101"></div>
</div>
</div>
{{end}}
gitextract_pi_oupt8/
├── .editorconfig
├── .github/
│ └── workflows/
│ ├── go.yml
│ └── goreleaser.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── app.json
├── config/
│ └── config.go
├── docker-compose.yml
├── go.mod
├── go.sum
├── internal/
│ ├── cache/
│ │ └── cache.go
│ ├── context/
│ │ └── app_context.go
│ ├── controller/
│ │ ├── healthcheck.go
│ │ ├── healthcheck_test.go
│ │ ├── init_test.go
│ │ ├── metric_test.go
│ │ ├── user-list.go
│ │ ├── user.go
│ │ └── user_test.go
│ ├── core/
│ │ ├── cache_store.go
│ │ ├── error_handler.go
│ │ ├── errors/
│ │ │ └── boom.go
│ │ ├── middleware/
│ │ │ └── app_context.go
│ │ ├── router.go
│ │ ├── server.go
│ │ ├── template.go
│ │ ├── user_store.go
│ │ └── validator.go
│ ├── i18n/
│ │ └── i18n.go
│ ├── models/
│ │ ├── models.go
│ │ └── user.go
│ └── store/
│ ├── cache.go
│ └── user.go
├── locales/
│ └── en/
│ └── default.po
├── main.go
├── scripts/
│ └── create.db.sql
└── web/
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── README.md
├── package.json
├── src/
│ ├── app/
│ │ ├── app.js
│ │ ├── components/
│ │ │ ├── header.js
│ │ │ └── like-button.js
│ │ ├── index.js
│ │ └── styles.scss
│ └── global/
│ ├── app.js
│ ├── global.html
│ ├── index.js
│ └── styles.scss
└── templates/
├── layouts/
│ └── base.html
└── pages/
├── user-list.html
└── user.html
SYMBOL INDEX (85 symbols across 29 files)
FILE: config/config.go
type Configuration (line 10) | type Configuration struct
function NewConfig (line 28) | func NewConfig(files ...string) (*Configuration, error) {
FILE: internal/cache/cache.go
function NewCache (line 10) | func NewCache(config *config.Configuration) *redis.Client {
FILE: internal/context/app_context.go
type AppContext (line 12) | type AppContext struct
FILE: internal/controller/healthcheck.go
type Healthcheck (line 10) | type Healthcheck struct
method GetHealthcheck (line 18) | func (ctrl Healthcheck) GetHealthcheck(c echo.Context) error {
type healthcheckReport (line 12) | type healthcheckReport struct
FILE: internal/controller/healthcheck_test.go
function TestHealthcheck (line 12) | func TestHealthcheck(t *testing.T) {
FILE: internal/controller/init_test.go
function TestMain (line 22) | func TestMain(m *testing.M) {
function setup (line 40) | func setup() {
function tearDown (line 68) | func tearDown() {
FILE: internal/controller/metric_test.go
function TestMetric (line 12) | func TestMetric(t *testing.T) {
FILE: internal/controller/user-list.go
type UserList (line 13) | type UserList struct
method GetUsers (line 19) | func (ctrl UserList) GetUsers(c echo.Context) error {
type UserListViewModel (line 14) | type UserListViewModel struct
FILE: internal/controller/user.go
type User (line 13) | type User struct
method GetUser (line 20) | func (ctrl User) GetUser(c echo.Context) error {
method GetUserJSON (line 43) | func (ctrl User) GetUserJSON(c echo.Context) error {
type UserViewModel (line 14) | type UserViewModel struct
FILE: internal/controller/user_test.go
type UserFakeStore (line 15) | type UserFakeStore struct
method First (line 17) | func (s *UserFakeStore) First(m *models.User) error {
method Find (line 20) | func (s *UserFakeStore) Find(m *[]models.User) error {
method Create (line 23) | func (s *UserFakeStore) Create(m *models.User) error {
method Ping (line 26) | func (s *UserFakeStore) Ping() error {
function TestUserPage (line 30) | func TestUserPage(t *testing.T) {
function TestUnitGetUserJson (line 38) | func TestUnitGetUserJson(t *testing.T) {
FILE: internal/core/cache_store.go
type CacheStore (line 10) | type CacheStore struct
method Ping (line 14) | func (s *CacheStore) Ping() error {
method Get (line 18) | func (s *CacheStore) Get(key string) (string, error) {
method Set (line 22) | func (s *CacheStore) Set(key string, value interface{}, exp time.Durat...
FILE: internal/core/error_handler.go
function HTTPErrorHandler (line 10) | func HTTPErrorHandler(err error, c echo.Context) {
FILE: internal/core/errors/boom.go
constant InternalError (line 4) | InternalError = "internalError"
constant UserNotFound (line 5) | UserNotFound = "userNotFound"
constant InvalidBindingModel (line 6) | InvalidBindingModel = "invalidBindingModel"
constant EntityCreationError (line 7) | EntityCreationError = "entityCreationError"
type Booms (line 18) | type Booms struct
method Add (line 22) | func (b *Booms) Add(e Boom) {
function NewBooms (line 26) | func NewBooms() Booms {
type Boom (line 31) | type Boom struct
function NewBoom (line 37) | func NewBoom(code, msg string, details interface{}) Boom {
function ErrorText (line 41) | func ErrorText(code string) string {
FILE: internal/core/middleware/app_context.go
function AppContext (line 8) | func AppContext(cc *context.AppContext) echo.MiddlewareFunc {
FILE: internal/core/router.go
function NewRouter (line 13) | func NewRouter(server *Server) *echo.Echo {
FILE: internal/core/server.go
type Server (line 19) | type Server struct
method GetDB (line 47) | func (s *Server) GetDB() *gorm.DB {
method GetCache (line 52) | func (s *Server) GetCache() *redis.Client {
method GetConfig (line 57) | func (s *Server) GetConfig() *config.Configuration {
method GetModelRegistry (line 62) | func (s *Server) GetModelRegistry() *models.Model {
method Start (line 67) | func (s *Server) Start(addr string) error {
method ServeStaticFiles (line 72) | func (s *Server) ServeStaticFiles() {
method GracefulShutdown (line 78) | func (s *Server) GracefulShutdown() {
function NewServer (line 28) | func NewServer(config *config.Configuration) *Server {
FILE: internal/core/template.go
type templateRenderer (line 16) | type templateRenderer struct
method Render (line 28) | func (t *templateRenderer) Render(w io.Writer, name string, data inter...
method Load (line 38) | func (t *templateRenderer) Load(layoutsDir, templatesDir string) {
function newTemplateRenderer (line 21) | func newTemplateRenderer(layoutsDir, templatesDir string) *templateRende...
FILE: internal/core/user_store.go
type UserStore (line 9) | type UserStore struct
method First (line 13) | func (s *UserStore) First(m *models.User) error {
method Create (line 17) | func (s *UserStore) Create(m *models.User) error {
method Find (line 21) | func (s *UserStore) Find(m *[]models.User) error {
method Ping (line 25) | func (s *UserStore) Ping() error {
FILE: internal/core/validator.go
type Validator (line 7) | type Validator struct
method Validate (line 11) | func (v *Validator) Validate(i interface{}) error {
FILE: internal/i18n/i18n.go
type I18ner (line 5) | type I18ner interface
type I18n (line 9) | type I18n struct
method Get (line 19) | func (i *I18n) Get(str string, vars ...interface{}) string {
function New (line 11) | func New() *I18n {
function Configure (line 15) | func Configure(lib, lang, dom string) {
function Get (line 23) | func Get(str string, vars ...interface{}) string {
FILE: internal/models/models.go
type Model (line 15) | type Model struct
method IsOpen (line 30) | func (m *Model) IsOpen() bool {
method OpenWithConfig (line 35) | func (m *Model) OpenWithConfig(cfg *config.Configuration) error {
method Register (line 52) | func (m *Model) Register(values ...interface{}) error {
method AutoMigrateAll (line 78) | func (m *Model) AutoMigrateAll() {
method AutoDropAll (line 85) | func (m *Model) AutoDropAll() {
function NewModel (line 22) | func NewModel() *Model {
function getTypeName (line 91) | func getTypeName(typ reflect.Type) string {
FILE: internal/models/user.go
type User (line 5) | type User struct
FILE: internal/store/cache.go
type Cache (line 5) | type Cache interface
FILE: internal/store/user.go
type User (line 5) | type User interface
FILE: main.go
function main (line 14) | func main() {
FILE: web/src/app/app.js
function App (line 3) | function App({ message }) {
FILE: web/src/app/components/header.js
function Header (line 3) | function Header({ message }) {
FILE: web/src/app/components/like-button.js
function LikeButton (line 3) | function LikeButton({ id }) {
FILE: web/src/global/app.js
function App (line 3) | function App({ message }) {
Condensed preview — 56 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (66K chars).
[
{
"path": ".editorconfig",
"chars": 226,
"preview": "; http://editorconfig.org/\n\nroot = true\n\n[*]\ninsert_final_newline = true\ncharset = utf-8\ntrim_trailing_whitespace = true"
},
{
"path": ".github/workflows/go.yml",
"chars": 458,
"preview": "name: CI\n\non:\n pull_request:\n paths-ignore:\n - \"**.md\"\n - \"docs/**\"\n\njobs:\n build:\n name: Build\n ru"
},
{
"path": ".github/workflows/goreleaser.yml",
"chars": 594,
"preview": "name: Release with goreleaser\non:\n push:\n branches:\n - \"!*\"\n tags:\n - \"v*.*.*\"\n\njobs:\n goreleaser:\n "
},
{
"path": ".gitignore",
"chars": 700,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\nvendor\n.netrc\n.vscode\n.vs\n.tern-p"
},
{
"path": "CONTRIBUTING.md",
"chars": 1226,
"preview": "## Setup your machine\n\n`go-web` is written in [Go](https://golang.org/).\n\nPrerequisites:\n\n- `make`\n- [Go 1.14+](https://"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2017 Dustin Deus\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "Makefile",
"chars": 2607,
"preview": "MODULE = $(shell env GO111MODULE=on $(GO) list -m)\nDATE ?= $(shell date +%FT%T%z)\nVERSION ?= $(shell git describe -"
},
{
"path": "README.md",
"chars": 4282,
"preview": "\n\n[](http://opensource.org"
},
{
"path": "app.json",
"chars": 437,
"preview": "{\n \"name\": \"go-web\",\n \"description\": \"Modern Web Application with Golang\",\n \"website\": \"https://github.com/StarpTech/"
},
{
"path": "config/config.go",
"chars": 1208,
"preview": "package config\n\nimport (\n\t\"log\"\n\n\t\"github.com/caarlos0/env\"\n\t\"github.com/joho/godotenv\"\n)\n\ntype Configuration struct {\n\t"
},
{
"path": "docker-compose.yml",
"chars": 1012,
"preview": "version: \"3\"\nservices:\n roach1:\n container_name: roach1\n image: cockroachdb/cockroach:v19.2.1\n command: start "
},
{
"path": "go.mod",
"chars": 1571,
"preview": "module github.com/starptech/go-web\n\ngo 1.13\n\nrequire (\n\tgithub.com/caarlos0/env v3.5.0+incompatible\n\tgithub.com/denisenk"
},
{
"path": "go.sum",
"chars": 15644,
"preview": "github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc="
},
{
"path": "internal/cache/cache.go",
"chars": 446,
"preview": "package cache\n\nimport (\n\t\"log\"\n\n\t\"github.com/go-redis/redis\"\n\t\"github.com/starptech/go-web/config\"\n)\n\nfunc NewCache(conf"
},
{
"path": "internal/context/app_context.go",
"chars": 456,
"preview": "package context\n\nimport (\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/starptech/go-web/config\"\n\t\"github.com/starptech/go"
},
{
"path": "internal/controller/healthcheck.go",
"chars": 721,
"preview": "package controller\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/starptech/go-web/internal/context\""
},
{
"path": "internal/controller/healthcheck_test.go",
"chars": 366,
"preview": "package controller\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/st"
},
{
"path": "internal/controller/init_test.go",
"chars": 1500,
"preview": "package controller\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/labstack/gommon/log\"\n\t\"github"
},
{
"path": "internal/controller/metric_test.go",
"chars": 356,
"preview": "package controller\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/st"
},
{
"path": "internal/controller/user-list.go",
"chars": 903,
"preview": "package controller\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/starptech/go-web/internal/context\""
},
{
"path": "internal/controller/user.go",
"chars": 1223,
"preview": "package controller\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/starptech/go-web/internal/context\""
},
{
"path": "internal/controller/user_test.go",
"chars": 1239,
"preview": "package controller\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/st"
},
{
"path": "internal/core/cache_store.go",
"chars": 456,
"preview": "package core\n\nimport (\n\t\"time\"\n\n\t\"github.com/go-redis/redis\"\n)\n\n// CacheStore simple redis implementation\ntype CacheStor"
},
{
"path": "internal/core/error_handler.go",
"chars": 582,
"preview": "package core\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/starptech/go-web/internal/core/errors\"\n)"
},
{
"path": "internal/core/errors/boom.go",
"chars": 1019,
"preview": "package errors\n\nconst (\n\tInternalError = \"internalError\"\n\tUserNotFound = \"userNotFound\"\n\tInvalidBindingMode"
},
{
"path": "internal/core/middleware/app_context.go",
"chars": 306,
"preview": "package middleware\n\nimport (\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/starptech/go-web/internal/context\"\n)\n\nfunc AppC"
},
{
"path": "internal/core/router.go",
"chars": 1383,
"preview": "package core\n\nimport (\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/labstack/echo/v4/middleware\"\n\t\"github.com/starptech/g"
},
{
"path": "internal/core/server.go",
"chars": 2540,
"preview": "package core\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n\n\t\"github.com/go-redis/redis\"\n\t\"github.com/jinzhu/go"
},
{
"path": "internal/core/template.go",
"chars": 1625,
"preview": "package core\n\nimport (\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"log\"\n\t\"path/filepath\"\n\n\t\"github.com/labstack/echo/v4\"\n\t\"github.co"
},
{
"path": "internal/core/user_store.go",
"chars": 494,
"preview": "package core\n\nimport (\n\t\"github.com/jinzhu/gorm\"\n\t\"github.com/starptech/go-web/internal/models\"\n)\n\n// UserStore implemen"
},
{
"path": "internal/core/validator.go",
"chars": 217,
"preview": "package core\n\nimport (\n\tvalidator \"gopkg.in/go-playground/validator.v9\"\n)\n\ntype Validator struct {\n\tvalidator *validator"
},
{
"path": "internal/i18n/i18n.go",
"chars": 447,
"preview": "package i18n\n\nimport gotext \"gopkg.in/leonelquinteros/gotext.v1\"\n\ntype I18ner interface {\n\tGet(string, ...interface{}) s"
},
{
"path": "internal/models/models.go",
"chars": 2220,
"preview": "package models\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/jinzhu/gorm\"\n\t_ \"github.com/jinzhu/gorm/d"
},
{
"path": "internal/models/user.go",
"chars": 234,
"preview": "package models\n\nimport \"time\"\n\ntype User struct {\n\tID string `gorm:\"type:uuid;primary_key;default:gen_random_uuid"
},
{
"path": "internal/store/cache.go",
"chars": 155,
"preview": "package store\n\nimport \"time\"\n\ntype Cache interface {\n\tPing() error\n\tGet(string) (string, error)\n\tSet(string, interface{}"
},
{
"path": "internal/store/user.go",
"chars": 196,
"preview": "package store\n\nimport \"github.com/starptech/go-web/internal/models\"\n\ntype User interface {\n\tFirst(m *models.User) error\n"
},
{
"path": "locales/en/default.po",
"chars": 305,
"preview": "# msgid \"\"\n# msgstr \"\"\n# Initial comment\n# Headers below\n\"Language: en\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"C"
},
{
"path": "main.go",
"chars": 1417,
"preview": "package main\n\nimport (\n\t\"log\"\n\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\""
},
{
"path": "scripts/create.db.sql",
"chars": 93,
"preview": "CREATE USER IF NOT EXISTS goweb;\nCREATE DATABASE goweb;\nGRANT ALL ON DATABASE goweb TO goweb;"
},
{
"path": "web/.babelrc",
"chars": 130,
"preview": "{\n \"presets\": [\n [\n \"@babel/preset-env\",\n {\n \"modules\": false\n }\n ],\n \"@babel/preset-rea"
},
{
"path": "web/.eslintrc.json",
"chars": 390,
"preview": "{\n \"rules\": {},\n \"env\": {\n \"es6\": true,\n \"browser\": true\n },\n \"parserOptions\": {\n \"ecmaVersion\": 2018,\n "
},
{
"path": "web/.gitignore",
"chars": 157,
"preview": "\n.cache/\ncoverage/\ndist/*\n!dist/index.html\nnode_modules/\n*.log\n\n# OS generated files\n.DS_Store\n.DS_Store?\n._*\n.Spotlight"
},
{
"path": "web/README.md",
"chars": 356,
"preview": "# Go Web\n\nWeb application for the api\n\n## Building and running on localhost\n\nFirst install dependencies:\n\n```sh\nyarn ins"
},
{
"path": "web/package.json",
"chars": 786,
"preview": "{\n \"name\": \"empty-project\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"keywords\": [],\n \"autho"
},
{
"path": "web/src/app/app.js",
"chars": 135,
"preview": "import React from \"react\";\n\nexport default function App({ message }) {\n return (\n <div>\n <h1>{message}</h1>\n "
},
{
"path": "web/src/app/components/header.js",
"chars": 390,
"preview": "import React from \"react\";\n\nexport default function Header({ message }) {\n return (\n <header className=\"bg-white tex"
},
{
"path": "web/src/app/components/like-button.js",
"chars": 294,
"preview": "import React, { useState } from \"react\";\n\nexport default function LikeButton({ id }) {\n const [likes, setLikes] = useSt"
},
{
"path": "web/src/app/index.js",
"chars": 595,
"preview": "import React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport App from \"./app\";\nimport LikeButton from \"./componen"
},
{
"path": "web/src/app/styles.scss",
"chars": 206,
"preview": "@import \"~bootstrap/scss/bootstrap\";\n\nheader {\n padding: 156px 0 100px;\n}\n\nsection {\n padding: 50px 0;\n}\n\n.bd-highligh"
},
{
"path": "web/src/global/app.js",
"chars": 524,
"preview": "import React from \"react\";\n\nexport default function App({ message }) {\n return (\n <div className=\"container\">\n "
},
{
"path": "web/src/global/global.html",
"chars": 33,
"preview": "<script src=\"index.js\"></script>\n"
},
{
"path": "web/src/global/index.js",
"chars": 254,
"preview": "import React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport App from \"./app\";\nimport \"./styles.scss\";\n\nvar mount"
},
{
"path": "web/src/global/styles.scss",
"chars": 37,
"preview": "@import \"~bootstrap/scss/bootstrap\";\n"
},
{
"path": "web/templates/layouts/base.html",
"chars": 1870,
"preview": "{{ define \"base\" }}\n\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content="
},
{
"path": "web/templates/pages/user-list.html",
"chars": 331,
"preview": "{{define \"title\"}}User list{{end}}\n\n{{define \"header\"}}\n<div class=\"header-component\" data-message=\"User list\"></div>\n{{"
},
{
"path": "web/templates/pages/user.html",
"chars": 571,
"preview": "{{define \"title\"}}User {{.Name}}{{end}}\n\n{{define \"header\"}}\n<div class=\"header-component\" data-message=\"{{.Name}}\"></di"
}
]
About this extraction
This page contains the full source code of the StarpTech/go-web GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 56 files (57.6 KB), approximately 22.0k tokens, and a symbol index with 85 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.