You've already forked golang-base-project
Initial commit
This commit is contained in:
49
.github/workflows/deploy.yml
vendored
Normal file
49
.github/workflows/deploy.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# This is a GitHub action which uses SSH and docker to deploy to a server running a local container registry. This is how I deploy my own personal sites.
|
||||||
|
name: Build and Deploy to server
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Install SSH key
|
||||||
|
uses: shimataro/ssh-key-action@v2
|
||||||
|
with:
|
||||||
|
key: ${{ secrets.PRIVATE_SSH_KEY }}
|
||||||
|
known_hosts: ${{ secrets.KNOWN_HOSTS }}
|
||||||
|
if_key_exists: replace
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |-
|
||||||
|
docker build \
|
||||||
|
--tag "localhost:5000/$IMAGE:$GITHUB_SHA" \
|
||||||
|
--build-arg GITHUB_SHA="$GITHUB_SHA" \
|
||||||
|
--build-arg GITHUB_REF="$GITHUB_REF" \
|
||||||
|
.
|
||||||
|
|
||||||
|
- name: Upload
|
||||||
|
run: |-
|
||||||
|
docker save localhost:5000/$IMAGE:$GITHUB_SHA | gzip | ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} docker load
|
||||||
|
|
||||||
|
- name: Publish
|
||||||
|
run: |-
|
||||||
|
ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} docker push localhost:5000/$IMAGE:$GITHUB_SHA
|
||||||
|
- run: |-
|
||||||
|
ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} docker image tag localhost:5000/$IMAGE:$GITHUB_SHA localhost:5000/$IMAGE:latest
|
||||||
|
- run: |-
|
||||||
|
ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} docker push localhost:5000/$IMAGE:latest
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
run: |-
|
||||||
|
ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "cd /var/www/${{ github.event.repository.name }}; docker-compose pull"
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.idea
|
||||||
|
node_modules
|
||||||
|
docker-compose.local.yml
|
20
Dockerfile
Normal file
20
Dockerfile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
FROM golang:1.16.5-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN apk add --update nodejs npm
|
||||||
|
RUN apk --no-cache add ca-certificates
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o "main" -ldflags="-w -s" ./cmd/base/main.go
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
|
COPY --from=builder /app/main /usr/bin/
|
||||||
|
COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs/
|
||||||
|
|
||||||
|
CMD ["main"]
|
||||||
|
|
||||||
|
EXPOSE 80
|
7
LICENSE
Normal file
7
LICENSE
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
ISC License
|
||||||
|
|
||||||
|
Copyright 2021 Markus Tenghamn (https://github.com/uberswe) m@rkus.io
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
81
README.md
Normal file
81
README.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# Golang Base Project
|
||||||
|
|
||||||
|
A minimal Golang project with user authentication ready out of the box. All frontend assets should be less than 50 kB on every page load. Functionality includes:
|
||||||
|
|
||||||
|
- Login
|
||||||
|
- Logout
|
||||||
|
- Register
|
||||||
|
- User Activation
|
||||||
|
- Resend Activation Email
|
||||||
|
- Forgot Password
|
||||||
|
- Admin Dashboard
|
||||||
|
- Search
|
||||||
|
- Throttling
|
||||||
|
|
||||||
|
This easiest way for me to achieve this was with a database. I decided to use [GORM](https://gorm.io/docs/) which should fully support MySQL, PostgreSQL, SQLite, SQL Server and Clickhouse or any other databases compatible with these dialects.
|
||||||
|
|
||||||
|
The frontend is based off of examples from [https://getbootstrap.com/docs/5.0/examples/](https://getbootstrap.com/docs/5.0/examples/).
|
||||||
|
|
||||||
|
## Project structure
|
||||||
|
|
||||||
|
This is the latest way I like to organize my projects. It's something that is always evolving and I know some will like this structure while others may not and that is ok.
|
||||||
|
|
||||||
|
I have mixed in the frontend assets with the go project. The `/src` folder has js and css assets for the frontend which are compiled and put into `/dist` where all the html templates also are.
|
||||||
|
|
||||||
|
I create packages when I feel there is a need for them to be shared at different levels of the project such as in another package or the routes, my rules here are very flexible and I have yet to come up with a good rule. Packages are in folders like `/middleware`, `email`, `util` and `config`.
|
||||||
|
|
||||||
|
You can run this project with a single go file by typing `go run cmd/base/main.go`.
|
||||||
|
|
||||||
|
There is a `/models` package which contains all the database models.
|
||||||
|
|
||||||
|
The `/routes` package contains all the route functions and logic. Typically, I try to break of the logic into other packages when functions become too big but I have no strict rule here.
|
||||||
|
|
||||||
|
All in all I have tried to keep the project simple and easy to understand. I want this project to serve as a template for myself and perhaps others when you want to create a new website.
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
A dockerfile and docker compose file is provided to make running this project easy. Simply run `docker-compose up`.
|
||||||
|
|
||||||
|
You will need to change the env variables for sending email, when testing locally I recommend [Mailtrap.io](https://mailtrap.io/).
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
I have tried to keep the dependencies low, there is always a balance here in my opinion and I have included the golang vendor folder and compiled assets so that there is no need to download anything to use this project other than the project itself.
|
||||||
|
|
||||||
|
### Go Dependencies
|
||||||
|
|
||||||
|
The following dependencies are used with Go.
|
||||||
|
|
||||||
|
- [Gin](https://github.com/gin-gonic/gin) - A web framework which makes routes, middleware and static assets easier to use and handle.
|
||||||
|
- [GORM](https://gorm.io/index.html) - An ORM library to make writing queries easier.
|
||||||
|
|
||||||
|
### NPM Dependencies
|
||||||
|
|
||||||
|
I personally dislike NPM because I there seems to be so many dependencies in javascript projects often with vulnerabilites which are hard to fix. However, npm is also one of the easiest ways to build an optimized frontend today which can be modified and built upon by others. I have tried to keep dependencies low and most are only used for the compiling of the static assets.
|
||||||
|
|
||||||
|
- [Bootstrap 5](https://getbootstrap.com/docs/5.0/getting-started/introduction/) - Bootstrap 5 is a frontend framework that makes it easy to create a good looking website which is responsive.
|
||||||
|
- [Webpack](https://webpack.js.org/) - An easy way to bundle and compile assets.
|
||||||
|
- [sass-loader](https://www.npmjs.com/package/sass-loader) - To compile scss files from bootstrap together with custom styles added by me.
|
||||||
|
- [PurgeCss](https://purgecss.com/) - PurgeCss removes unused CSS so that we only load what is needed. Since we have Bootstrap our compiled css would be around 150kB without PurgeCSS compared to around 10kB as it is now.
|
||||||
|
|
||||||
|
There are some more dependencies but I have focused on mentioning those that have the greatest impact on the project as a whole.
|
||||||
|
|
||||||
|
## GitHub Actions
|
||||||
|
|
||||||
|
There is a workflow to deploy to my personal server whenever there is a merge to master. This is the way I like to deploy for personal projects. The steps are:
|
||||||
|
|
||||||
|
- Make a new image with docker
|
||||||
|
- Push this image to a private container registry on my server, you can see the registry here https://registry.beubo.com/repositories/20
|
||||||
|
- Then I use docker-compose to pull the latest image from the private registry
|
||||||
|
|
||||||
|
I use [supervisor](http://supervisord.org/) with [docker-compose](https://docs.docker.com/compose/production/) to run my containers. [Caddy](https://caddyserver.com/) handles the SSL configuration and routing. I use [Ansible](https://docs.ansible.com/ansible/latest/user_guide/playbooks.html) to manage my configurations.
|
||||||
|
|
||||||
|
## Contributions
|
||||||
|
|
||||||
|
Contributions are welcome and greatly appreciated. Please note that I am not looking to add any more features to this project but I am happy to take care of bugfixes, updates and other suggestions. If you have a question or suggestion please feel free to [open an issue](https://github.com/uberswe/golang-base-project/issues/new). To contribute code, please fork this repository, make your changes on a separate branch and then [open a pull request](https://github.com/uberswe/golang-base-project/compare).
|
||||||
|
|
||||||
|
For security related issues please see my profile, [@uberswe](https://github.com/uberswe), for ways of contacting me privately.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Please see the `LICENSE` file in the project repository.
|
16
cmd/base/main.go
Normal file
16
cmd/base/main.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import baseproject "github.com/uberswe/golang-base-project"
|
||||||
|
|
||||||
|
// @title Golang Base Project
|
||||||
|
// @version 1.0
|
||||||
|
// @description A minimal golang project with user authentication ready out of the box. All frontend assets should be less than 50 kB on every page load.
|
||||||
|
|
||||||
|
// @contact.name Markus Tenghamn
|
||||||
|
// @contact.email m@rkus.io
|
||||||
|
|
||||||
|
// @license.name ISC License
|
||||||
|
// @license.url https://github.com/uberswe/golang-base-project/blob/main/LICENSE
|
||||||
|
func main() {
|
||||||
|
baseproject.Run()
|
||||||
|
}
|
20
config/main.go
Normal file
20
config/main.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Package config defines the env configuration variables
|
||||||
|
package config
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Port string
|
||||||
|
CookieSecret string
|
||||||
|
Database string
|
||||||
|
DatabaseHost string
|
||||||
|
DatabasePort string
|
||||||
|
DatabaseName string
|
||||||
|
DatabaseUsername string
|
||||||
|
DatabasePassword string
|
||||||
|
BaseURL string
|
||||||
|
SMTPUsername string
|
||||||
|
SMTPPassword string
|
||||||
|
SMTPHost string
|
||||||
|
SMTPPort string
|
||||||
|
SMTPSender string
|
||||||
|
RequestsPerMinute int
|
||||||
|
}
|
69
database.go
Normal file
69
database.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package baseproject
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/uberswe/golang-base-project/config"
|
||||||
|
"github.com/uberswe/golang-base-project/models"
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func connectToDatabase(c config.Config) (db *gorm.DB, err error) {
|
||||||
|
if c.Database == "sqlite" {
|
||||||
|
// In-memory sqlite if no database name is specified
|
||||||
|
dsn := "file::memory:?cache=shared"
|
||||||
|
if c.DatabaseName != "" {
|
||||||
|
dsn = fmt.Sprintf("%s.db", c.DatabaseName)
|
||||||
|
}
|
||||||
|
db, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
||||||
|
} else if c.Database == "mysql" {
|
||||||
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", c.DatabaseUsername, c.DatabasePassword, c.DatabaseHost, c.DatabasePort, c.DatabaseName)
|
||||||
|
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||||
|
Logger: logger.Default.LogMode(logger.Info),
|
||||||
|
})
|
||||||
|
} else if c.Database == "postgres" {
|
||||||
|
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=UTC", c.DatabaseHost, c.DatabaseUsername, c.DatabasePassword, c.DatabaseName, c.DatabasePort)
|
||||||
|
db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
||||||
|
} else {
|
||||||
|
return db, fmt.Errorf("no database specified: %s", c.Database)
|
||||||
|
}
|
||||||
|
return db, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateDatabase(db *gorm.DB) error {
|
||||||
|
err := db.AutoMigrate(&models.Token{}, &models.Session{}, &models.User{}, &models.Website{})
|
||||||
|
seed(db)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func seed(db *gorm.DB) {
|
||||||
|
// We seed some websites for our search results
|
||||||
|
websites := []models.Website{
|
||||||
|
{
|
||||||
|
Title: "A Tour of Go",
|
||||||
|
Description: "A Tour of Go has several interactive examples of how Go which you can learn from. There is a menu available if you would like to skip to different sections.",
|
||||||
|
URL: "https://go.dev/tour/welcome/1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Title: "Go by Example",
|
||||||
|
Description: "As described on the website: Go by Example is a hands-on introduction to Go using annotated example programs. I have used this site many times as a reference when I need to look something up.",
|
||||||
|
URL: "https://gobyexample.com/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Title: "Go.dev",
|
||||||
|
Description: "Learn how to install Go on your machine and read the documentation on the Go website.",
|
||||||
|
URL: "https://go.dev/learn/",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, w := range websites {
|
||||||
|
res := db.Where(&w).First(&w)
|
||||||
|
// If no record exists we insert
|
||||||
|
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
|
||||||
|
db.Save(&w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
dist/assets/css/main.css
vendored
Normal file
6
dist/assets/css/main.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
dist/assets/images/android-chrome-192x192.png
vendored
Normal file
BIN
dist/assets/images/android-chrome-192x192.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
BIN
dist/assets/images/android-chrome-512x512.png
vendored
Normal file
BIN
dist/assets/images/android-chrome-512x512.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
dist/assets/images/apple-touch-icon.png
vendored
Normal file
BIN
dist/assets/images/apple-touch-icon.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
BIN
dist/assets/images/favicon-16x16.png
vendored
Normal file
BIN
dist/assets/images/favicon-16x16.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 400 B |
BIN
dist/assets/images/favicon-32x32.png
vendored
Normal file
BIN
dist/assets/images/favicon-32x32.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 684 B |
BIN
dist/assets/images/favicon.ico
vendored
Normal file
BIN
dist/assets/images/favicon.ico
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
1
dist/assets/images/site.webmanifest
vendored
Normal file
1
dist/assets/images/site.webmanifest
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
1
dist/assets/js/main.js
vendored
Normal file
1
dist/assets/js/main.js
vendored
Normal file
File diff suppressed because one or more lines are too long
13
dist/templates/404.html
vendored
Normal file
13
dist/templates/404.html
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{{ template "head.html" . }}
|
||||||
|
|
||||||
|
<main class="flex-shrink-0">
|
||||||
|
<div class="container">
|
||||||
|
<h1>404 Not Found</h1>
|
||||||
|
<p>The page you're looking for could not be found. <a href="/">Click here</a> to return to the main page.</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="/assets/js/main.js"></script>
|
||||||
|
{{ template "tracking.html" }}
|
||||||
|
</body>
|
||||||
|
</html>
|
9
dist/templates/activate.html
vendored
Normal file
9
dist/templates/activate.html
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{{ template "header.html" . }}
|
||||||
|
|
||||||
|
<main class="flex-shrink-0">
|
||||||
|
<div class="container">
|
||||||
|
{{ template "messages.html" . }}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{{ template "footer.html" . }}
|
10
dist/templates/admin.html
vendored
Normal file
10
dist/templates/admin.html
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{{ template "header.html" . }}
|
||||||
|
|
||||||
|
<main class="flex-shrink-0">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="mt-5">Admin Dashboard</h1>
|
||||||
|
<p>You now have an authenticated session, feel free to log out using the link in the navbar above.</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{{ template "footer.html" . }}
|
9
dist/templates/footer.html
vendored
Normal file
9
dist/templates/footer.html
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<footer class="footer mt-auto py-3 bg-light">
|
||||||
|
<div class="container">
|
||||||
|
<span class="text-muted">Fork this project on <a href="https://github.com/uberswe/golang-base-project">GitHub</a> | Created by <a href="https://github.com/uberswe">Markus Tenghamn</a></span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<script src="/assets/js/main.js"></script>
|
||||||
|
{{ template "tracking.html" }}
|
||||||
|
</body>
|
||||||
|
</html>
|
18
dist/templates/forgotpassword.html
vendored
Normal file
18
dist/templates/forgotpassword.html
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{{ template "header.html" . }}
|
||||||
|
<main class="form-signin">
|
||||||
|
<form method="post" action="/user/password/forgot">
|
||||||
|
<h1 class="h3 mb-3 fw-normal">Forgot password?</h1>
|
||||||
|
|
||||||
|
{{ template "messages.html" . }}
|
||||||
|
|
||||||
|
<p>Use the form below to reset your password. If we have an account with your email you will receive instructions on how to reset your passsword.</p>
|
||||||
|
|
||||||
|
<div class="form-floating">
|
||||||
|
<input name="email" type="email" class="form-control" id="floatingInput" placeholder="name@example.com">
|
||||||
|
<label for="floatingInput">Email address</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="w-100 btn btn-lg btn-primary" type="submit">Request reset email</button>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
{{ template "footer.html" . }}
|
24
dist/templates/head.html
vendored
Normal file
24
dist/templates/head.html
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" class="h-100">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="author" content="Markus Tenghamn">
|
||||||
|
<title>{{ .Title }} - Golang Base Project</title>
|
||||||
|
|
||||||
|
<link rel="canonical" href="https://golangbase.com">
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" href="/assets/images/apple-touch-icon.png"
|
||||||
|
sizes="180x180">
|
||||||
|
<link rel="icon" href="/assets/images/favicon-32x32.png" sizes="32x32"
|
||||||
|
type="image/png">
|
||||||
|
<link rel="icon" href="/assets/images/favicon-16x16.png" sizes="16x16"
|
||||||
|
type="image/png">
|
||||||
|
<link rel="manifest" href="/assets/images/site.webmanifest">
|
||||||
|
<link rel="icon" href="/assets/images/favicon.ico">
|
||||||
|
<meta name="theme-color" content="#2E61B8">
|
||||||
|
<link href="/assets/css/main.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="d-flex flex-column h-100">
|
44
dist/templates/header.html
vendored
Normal file
44
dist/templates/header.html
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{{ template "head.html" . }}
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="/">Golang Base Project</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse"
|
||||||
|
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||||
|
{{ if .IsAuthenticated }}
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-md-0">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/admin">Admin</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/logout">Logout</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{{ else }}
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-md-0">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/login">Login</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/register">Register</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{{ end }}
|
||||||
|
<form method="post" action="/search" class="d-flex">
|
||||||
|
<input name="search" class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
|
||||||
|
<button class="btn btn-outline-success" type="submit">Search</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
11
dist/templates/index.html
vendored
Normal file
11
dist/templates/index.html
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{{ template "header.html" . }}
|
||||||
|
|
||||||
|
<main class="flex-shrink-0">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="mt-5">Golang Base Project</h1>
|
||||||
|
<p class="lead">A simple website with user login and registration. The frontend uses <a href="https://getbootstrap.com/docs/5.0/getting-started/introduction/">Bootstrap 5</a> and the backend is written in <a href="https://go.dev/">Go</a>.</p>
|
||||||
|
<p>Read more about this project on <a href="https://github.com/uberswe/golang-base-project">GitHub</a>.</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{{ template "footer.html" . }}
|
24
dist/templates/login.html
vendored
Normal file
24
dist/templates/login.html
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{{ template "header.html" . }}
|
||||||
|
<main class="form-signin">
|
||||||
|
<form method="post" action="/login">
|
||||||
|
<h1 class="h3 mb-3 fw-normal">Login</h1>
|
||||||
|
|
||||||
|
{{ template "messages.html" . }}
|
||||||
|
|
||||||
|
<div class="form-floating">
|
||||||
|
<input name="email" type="email" class="form-control" id="floatingInput" placeholder="name@example.com">
|
||||||
|
<label for="floatingInput">Email address</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-floating">
|
||||||
|
<input name="password" type="password" class="form-control" id="floatingPassword" placeholder="Password">
|
||||||
|
<label for="floatingPassword">Password</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<p>By pressing the button below to login you agree to the use of cookies on this website.</p>
|
||||||
|
</div>
|
||||||
|
<button class="w-100 btn btn-lg btn-primary" type="submit">Login</button>
|
||||||
|
<p class="mt-5 mb-3 text-muted"><a href="/user/password/forgot">Forgot password?</a></p>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
{{ template "footer.html" . }}
|
15
dist/templates/messages.html
vendored
Normal file
15
dist/templates/messages.html
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{{ range $message := .Messages }}
|
||||||
|
{{ if eq $message.Type "error" }}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
{{ $message.Content }}
|
||||||
|
</div>
|
||||||
|
{{ else if eq $message.Type "success" }}
|
||||||
|
<div class="alert alert-success" role="alert">
|
||||||
|
{{ $message.Content }}
|
||||||
|
</div>
|
||||||
|
{{ else }}
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
{{ $message.Content }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
22
dist/templates/register.html
vendored
Normal file
22
dist/templates/register.html
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{{ template "header.html" . }}
|
||||||
|
<main class="form-signin">
|
||||||
|
<form method="post" action="/register">
|
||||||
|
<h1 class="h3 mb-3 fw-normal">Register</h1>
|
||||||
|
|
||||||
|
{{ template "messages.html" . }}
|
||||||
|
|
||||||
|
<div class="form-floating">
|
||||||
|
<input name="email" type="email" class="form-control" id="floatingInput" placeholder="name@example.com">
|
||||||
|
<label for="floatingInput">Email address</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-floating">
|
||||||
|
<input name="password" type="password" class="form-control" id="floatingPassword" placeholder="Password">
|
||||||
|
<label for="floatingPassword">Password</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="w-100 btn btn-lg btn-primary" type="submit">Register</button>
|
||||||
|
|
||||||
|
<p class="mt-5 mb-3 text-muted"><a href="/activate/resend">Request a new activation email</a></p>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
{{ template "footer.html" . }}
|
18
dist/templates/resendactivation.html
vendored
Normal file
18
dist/templates/resendactivation.html
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{{ template "header.html" . }}
|
||||||
|
<main class="form-signin">
|
||||||
|
<form method="post" action="/activate/resend">
|
||||||
|
<h1 class="h3 mb-3 fw-normal">Resend Activation Email</h1>
|
||||||
|
|
||||||
|
{{ template "messages.html" . }}
|
||||||
|
|
||||||
|
<p>If you have already registered but never activated your account you can use the form below to request a new activation email.</p>
|
||||||
|
|
||||||
|
<div class="form-floating">
|
||||||
|
<input type="email" class="form-control" id="floatingInput" placeholder="name@example.com">
|
||||||
|
<label for="floatingInput">Email address</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="w-100 btn btn-lg btn-primary" type="submit">Request activation email</button>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
{{ template "footer.html" . }}
|
18
dist/templates/resetpassword.html
vendored
Normal file
18
dist/templates/resetpassword.html
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{{ template "header.html" . }}
|
||||||
|
<main class="form-signin">
|
||||||
|
<form method="post" action="/user/password/reset/{{ .Token }}">
|
||||||
|
<h1 class="h3 mb-3 fw-normal">Reset password</h1>
|
||||||
|
|
||||||
|
{{ template "messages.html" . }}
|
||||||
|
|
||||||
|
<p>Please enter a new password.</p>
|
||||||
|
|
||||||
|
<div class="form-floating">
|
||||||
|
<input name="password" type="password" class="form-control" id="floatingPassword" placeholder="Password">
|
||||||
|
<label for="floatingPassword">Password</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="w-100 btn btn-lg btn-primary" type="submit">Reset password</button>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
{{ template "footer.html" . }}
|
17
dist/templates/search.html
vendored
Normal file
17
dist/templates/search.html
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{{ template "header.html" . }}
|
||||||
|
|
||||||
|
<main class="flex-shrink-0">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="mt-5">Search Results</h1>
|
||||||
|
{{ template "messages.html" . }}
|
||||||
|
{{ range $result := .Results }}
|
||||||
|
<div class="search-result">
|
||||||
|
<h3>{{ $result.Title }}</h3>
|
||||||
|
<p>{{ $result.Description }}</p>
|
||||||
|
<p><a href="{{ $result.URL }}">{{ $result.URL }}</a></p>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{{ template "footer.html" . }}
|
19
dist/templates/tracking.html
vendored
Normal file
19
dist/templates/tracking.html
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
<!-- Start Open Web Analytics Tracker -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
//<![CDATA[
|
||||||
|
var owa_baseUrl = 'https://a.beubo.com/';
|
||||||
|
var owa_cmds = owa_cmds || [];
|
||||||
|
owa_cmds.push(['setSiteId', '7b4cb510b6ba1cc436f253b7536b0a95']);
|
||||||
|
owa_cmds.push(['trackPageView']);
|
||||||
|
owa_cmds.push(['trackClicks']);
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var _owa = document.createElement('script'); _owa.type = 'text/javascript'; _owa.async = true;
|
||||||
|
owa_baseUrl = ('https:' == document.location.protocol ? window.owa_baseSecUrl || owa_baseUrl.replace(/http:/, 'https:') : owa_baseUrl );
|
||||||
|
_owa.src = owa_baseUrl + 'modules/base/js/owa.tracker-combined-min.js';
|
||||||
|
var _owa_s = document.getElementsByTagName('script')[0]; _owa_s.parentNode.insertBefore(_owa, _owa_s);
|
||||||
|
}());
|
||||||
|
//]]>
|
||||||
|
</script>
|
||||||
|
<!-- End Open Web Analytics Code -->
|
40
docker-compose.yml
Normal file
40
docker-compose.yml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: mysql:5.7
|
||||||
|
volumes:
|
||||||
|
- db_data:/var/lib/mysql
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: base_project
|
||||||
|
MYSQL_DATABASE: base_project
|
||||||
|
MYSQL_USER: base_project
|
||||||
|
MYSQL_PASSWORD: base_project
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
golang-base-project:
|
||||||
|
build: .
|
||||||
|
container_name: golang-base-project
|
||||||
|
environment:
|
||||||
|
- PORT=:80
|
||||||
|
- BASE_URL=http://localhost:8080
|
||||||
|
- COOKIE_SECRET=
|
||||||
|
- DATABASE=mysql
|
||||||
|
- DATABASE_NAME=base_project
|
||||||
|
- DATABASE_HOST=db
|
||||||
|
- DATABASE_PORT=3306
|
||||||
|
- DATABASE_USERNAME=base_project
|
||||||
|
- DATABASE_PASSWORD=base_project
|
||||||
|
- SMTP_USERNAME=
|
||||||
|
- SMTP_PASSWORD=
|
||||||
|
- SMTP_HOST=
|
||||||
|
- SMTP_PORT=
|
||||||
|
- SMTP_SENDER=
|
||||||
|
- STRICT_TRANSPORT_SECURITY=false
|
||||||
|
- REQUESTS_PER_MINUTE=5
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db_data: {}
|
56
email/main.go
Normal file
56
email/main.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Package email handles the sending of emails
|
||||||
|
package email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/uberswe/golang-base-project/config"
|
||||||
|
"github.com/uberswe/golang-base-project/util"
|
||||||
|
"log"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/smtp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
Config config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(c config.Config) Service {
|
||||||
|
return Service{
|
||||||
|
Config: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) Send(to string, subject string, message string) {
|
||||||
|
// Authentication.
|
||||||
|
auth := smtp.PlainAuth("", s.Config.SMTPUsername, s.Config.SMTPPassword, s.Config.SMTPHost)
|
||||||
|
|
||||||
|
// RFC #822 Standard
|
||||||
|
writer := multipart.NewWriter(bytes.NewBufferString(""))
|
||||||
|
var b bytes.Buffer
|
||||||
|
_, _ = fmt.Fprintf(&b, "From: %s\r\nTo: %s\r\nSubject: %s\r\n", s.Config.SMTPSender, to, subject)
|
||||||
|
_, _ = fmt.Fprintf(&b, "MIME-Version: 1.0\r\n")
|
||||||
|
_, _ = fmt.Fprintf(&b, "Content-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"%s\"\r\n", writer.Boundary())
|
||||||
|
_, _ = fmt.Fprintf(&b, "\r\n\r\n--%s\r\nContent-Type: %s; charset=UTF-8;\nContent-Transfer-Encoding: 8bit\r\n\r\n", writer.Boundary(), "text/plain")
|
||||||
|
b.Write([]byte(message))
|
||||||
|
htmlMessage := util.StringLinkToHTMLLink(message)
|
||||||
|
htmlMessage = util.NL2BR(htmlMessage)
|
||||||
|
_, _ = fmt.Fprintf(&b, "\r\n\r\n--%s\r\nContent-Type: %s; charset=UTF-8;\nContent-Transfer-Encoding: 8bit\r\n\r\n", writer.Boundary(), "text/html")
|
||||||
|
b.Write([]byte(htmlMessage))
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(&b, "\r\n\r\n--%s--\r\n", writer.Boundary())
|
||||||
|
|
||||||
|
sender := s.Config.SMTPSender
|
||||||
|
if strings.Contains(sender, "<") {
|
||||||
|
sender = util.GetStringBetweenStrings(sender, "<", ">")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sending email.
|
||||||
|
err := smtp.SendMail(fmt.Sprintf("%s:%s", s.Config.SMTPHost, s.Config.SMTPPort), auth, sender, []string{to}, b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println(fmt.Sprintf("Email sent to %s", to))
|
||||||
|
}
|
74
env.go
Normal file
74
env.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package baseproject
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/uberswe/golang-base-project/config"
|
||||||
|
"github.com/uberswe/golang-base-project/util"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadEnvVariables() (c config.Config) {
|
||||||
|
c.Port = ":8080"
|
||||||
|
if os.Getenv("PORT") != "" {
|
||||||
|
c.Port = os.Getenv("PORT")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.BaseURL = "https://golangbase.com/"
|
||||||
|
if os.Getenv("BASE_URL") != "" {
|
||||||
|
c.BaseURL = os.Getenv("BASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A random secret will be generated when the application starts if no secret is provided. It is highly recommended to provide a secret.
|
||||||
|
c.CookieSecret = util.GenerateULID()
|
||||||
|
if os.Getenv("COOKIE_SECRET") != "" {
|
||||||
|
c.CookieSecret = os.Getenv("COOKIE_SECRET")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Database = "sqlite"
|
||||||
|
if os.Getenv("DATABASE") != "" {
|
||||||
|
c.Database = os.Getenv("DATABASE")
|
||||||
|
}
|
||||||
|
if os.Getenv("DATABASE_NAME") != "" {
|
||||||
|
c.DatabaseName = os.Getenv("DATABASE_NAME")
|
||||||
|
}
|
||||||
|
if os.Getenv("DATABASE_HOST") != "" {
|
||||||
|
c.DatabaseHost = os.Getenv("DATABASE_HOST")
|
||||||
|
}
|
||||||
|
if os.Getenv("DATABASE_PORT") != "" {
|
||||||
|
c.DatabasePort = os.Getenv("DATABASE_PORT")
|
||||||
|
}
|
||||||
|
if os.Getenv("DATABASE_USERNAME") != "" {
|
||||||
|
c.DatabaseUsername = os.Getenv("DATABASE_USERNAME")
|
||||||
|
}
|
||||||
|
if os.Getenv("DATABASE_PASSWORD") != "" {
|
||||||
|
c.DatabasePassword = os.Getenv("DATABASE_PASSWORD")
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("SMTP_USERNAME") != "" {
|
||||||
|
c.SMTPUsername = os.Getenv("SMTP_USERNAME")
|
||||||
|
}
|
||||||
|
if os.Getenv("SMTP_PASSWORD") != "" {
|
||||||
|
c.SMTPPassword = os.Getenv("SMTP_PASSWORD")
|
||||||
|
}
|
||||||
|
if os.Getenv("SMTP_HOST") != "" {
|
||||||
|
c.SMTPHost = os.Getenv("SMTP_HOST")
|
||||||
|
}
|
||||||
|
if os.Getenv("SMTP_PORT") != "" {
|
||||||
|
c.SMTPPort = os.Getenv("SMTP_PORT")
|
||||||
|
}
|
||||||
|
if os.Getenv("SMTP_SENDER") != "" {
|
||||||
|
c.SMTPSender = os.Getenv("SMTP_SENDER")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.RequestsPerMinute = 5
|
||||||
|
if os.Getenv("REQUESTS_PER_MINUTE") != "" {
|
||||||
|
i, err := strconv.Atoi(os.Getenv("REQUESTS_PER_MINUTE"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.RequestsPerMinute = i
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
17
go.mod
Normal file
17
go.mod
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
module github.com/uberswe/golang-base-project
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-contrib/sessions v0.0.4
|
||||||
|
github.com/gin-gonic/gin v1.7.7
|
||||||
|
github.com/oklog/ulid/v2 v2.0.2
|
||||||
|
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2
|
||||||
|
github.com/swaggo/gin-swagger v1.3.3
|
||||||
|
github.com/ulule/limiter/v3 v3.9.0
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
||||||
|
gorm.io/driver/mysql v1.2.1
|
||||||
|
gorm.io/driver/postgres v1.2.3
|
||||||
|
gorm.io/driver/sqlite v1.2.6
|
||||||
|
gorm.io/gorm v1.22.4
|
||||||
|
)
|
385
go.sum
Normal file
385
go.sum
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
|
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||||
|
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
|
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||||
|
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
||||||
|
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
|
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
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/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/gin-contrib/gzip v0.0.3 h1:etUaeesHhEORpZMp18zoOhepboiWnFtXrBZxszWUn4k=
|
||||||
|
github.com/gin-contrib/gzip v0.0.3/go.mod h1:YxxswVZIqOvcHEQpsSn+QF5guQtO1dCfy0shBPy4jFc=
|
||||||
|
github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
|
||||||
|
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
|
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||||
|
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||||
|
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||||
|
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
|
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
|
||||||
|
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
|
||||||
|
github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ=
|
||||||
|
github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg=
|
||||||
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
|
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
|
||||||
|
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||||
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
|
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
|
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||||
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
|
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||||
|
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
|
||||||
|
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
||||||
|
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
|
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||||
|
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||||
|
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||||
|
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||||
|
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||||
|
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||||
|
github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8=
|
||||||
|
github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||||
|
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/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||||
|
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
|
||||||
|
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
|
||||||
|
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
|
||||||
|
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||||
|
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||||
|
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||||
|
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||||
|
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||||
|
github.com/jackc/pgtype v1.9.0 h1:/SH1RxEtltvJgsDqp3TbiTFApD3mey3iygpuEGeuBXk=
|
||||||
|
github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||||
|
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=
|
||||||
|
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||||
|
github.com/jackc/pgx/v4 v4.14.0 h1:TgdrmgnM7VY72EuSQzBbBd4JA1RLqJolrw9nQVZABVc=
|
||||||
|
github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8=
|
||||||
|
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
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.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/jinzhu/now v1.1.3 h1:PlHq1bSCSZL9K0wUhbm2pGLoTWs2GwVhsP6emvGV/ZI=
|
||||||
|
github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||||
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||||
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
|
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=
|
||||||
|
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
|
||||||
|
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||||
|
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||||
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
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 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
|
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
|
github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
|
||||||
|
github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
|
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
|
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||||
|
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=
|
||||||
|
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
|
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||||
|
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||||
|
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||||
|
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
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/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/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
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/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
|
||||||
|
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM=
|
||||||
|
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||||
|
github.com/swaggo/gin-swagger v1.3.3 h1:XHyYmeNVFG5PbyWHG4jXtxOm2P4kiZapDCWsyDDiQ/I=
|
||||||
|
github.com/swaggo/gin-swagger v1.3.3/go.mod h1:ymsZuGpbbu+S7ZoQ49QPpZoDBj6uqhb8WizgQPVgWl0=
|
||||||
|
github.com/swaggo/swag v1.7.4 h1:up+ixy8yOqJKiFcuhMgkuYuF4xnevuhnFAXXF8OSfNg=
|
||||||
|
github.com/swaggo/swag v1.7.4/go.mod h1:zD8h6h4SPv7t3l+4BKdRquqW1ASWjKZgT6Qv9z3kNqI=
|
||||||
|
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||||
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
|
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||||
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
|
github.com/ulule/limiter/v3 v3.9.0 h1:ebASTkd6QNNUGhuDrWqImMpsg9GtItgNgxF3nKao58Q=
|
||||||
|
github.com/ulule/limiter/v3 v3.9.0/go.mod h1:icWc7rrF3T07dj59AhU4+HqKh0uWeh1wKct31wmcoF0=
|
||||||
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
|
||||||
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
|
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||||
|
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||||
|
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||||
|
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI=
|
||||||
|
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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-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-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||||
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
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=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/mysql v1.2.1 h1:h+3f1l9Ng2C072Y2tIiLgPpWN78r1KXL7bHJ0nTjlhU=
|
||||||
|
gorm.io/driver/mysql v1.2.1/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo=
|
||||||
|
gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To=
|
||||||
|
gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs=
|
||||||
|
gorm.io/driver/sqlite v1.2.6 h1:SStaH/b+280M7C8vXeZLz/zo9cLQmIGwwj3cSj7p6l4=
|
||||||
|
gorm.io/driver/sqlite v1.2.6/go.mod h1:gyoX0vHiiwi0g49tv+x2E7l8ksauLK0U/gShcdUsjWY=
|
||||||
|
gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||||
|
gorm.io/gorm v1.22.4 h1:8aPcyEJhY0MAt8aY6Dc524Pn+pO29K+ydu+e/cXSpQM=
|
||||||
|
gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
98
main.go
Normal file
98
main.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package baseproject
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
swaggerFiles "github.com/swaggo/files"
|
||||||
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
|
"github.com/uberswe/golang-base-project/middleware"
|
||||||
|
"github.com/uberswe/golang-base-project/routes"
|
||||||
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed dist/*
|
||||||
|
var staticFS embed.FS
|
||||||
|
|
||||||
|
func Run() {
|
||||||
|
var t *template.Template
|
||||||
|
conf := loadEnvVariables()
|
||||||
|
|
||||||
|
db, err := connectToDatabase(conf)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
log.Println(db)
|
||||||
|
|
||||||
|
err = migrateDatabase(db)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err = loadTemplates()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
store := cookie.NewStore([]byte(conf.CookieSecret))
|
||||||
|
r.Use(sessions.Sessions("golang_base_project_session", store))
|
||||||
|
|
||||||
|
r.SetHTMLTemplate(t)
|
||||||
|
|
||||||
|
subFS, err := fs.Sub(staticFS, "dist/assets")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.StaticFS("/assets", http.FS(subFS))
|
||||||
|
|
||||||
|
r.Use(middleware.Session(db))
|
||||||
|
r.Use(middleware.General())
|
||||||
|
|
||||||
|
controller := routes.New(db, conf)
|
||||||
|
|
||||||
|
r.GET("/", controller.Index)
|
||||||
|
r.GET("/search", controller.Search)
|
||||||
|
r.POST("/search", controller.Search)
|
||||||
|
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||||
|
r.NoRoute(controller.NoRoute)
|
||||||
|
|
||||||
|
noAuth := r.Group("/")
|
||||||
|
noAuth.Use(middleware.NoAuth())
|
||||||
|
|
||||||
|
noAuth.GET("/login", controller.Login)
|
||||||
|
noAuth.GET("/register", controller.Register)
|
||||||
|
noAuth.GET("/activate/resend", controller.ResendActivation)
|
||||||
|
noAuth.GET("/activate/:token", controller.Activate)
|
||||||
|
noAuth.GET("/user/password/forgot", controller.ForgotPassword)
|
||||||
|
noAuth.GET("/user/password/reset/:token", controller.ResetPassword)
|
||||||
|
|
||||||
|
noAuthPost := noAuth.Group("/")
|
||||||
|
noAuthPost.Use(middleware.Throttle(conf.RequestsPerMinute))
|
||||||
|
|
||||||
|
noAuthPost.POST("/login", controller.LoginPost)
|
||||||
|
noAuthPost.POST("/register", controller.RegisterPost)
|
||||||
|
noAuthPost.POST("/activate/resend", controller.ResendActivationPost)
|
||||||
|
noAuthPost.POST("/user/password/forgot", controller.ForgotPasswordPost)
|
||||||
|
noAuthPost.POST("/user/password/reset/:token", controller.ResetPasswordPost)
|
||||||
|
|
||||||
|
admin := r.Group("/")
|
||||||
|
admin.Use(middleware.Auth())
|
||||||
|
admin.Use(middleware.Sensitive())
|
||||||
|
|
||||||
|
admin.GET("/admin", controller.Admin)
|
||||||
|
// We need to handle post from the login redirect
|
||||||
|
admin.POST("/admin", controller.Admin)
|
||||||
|
admin.GET("/logout", controller.Logout)
|
||||||
|
|
||||||
|
err = r.Run(conf.Port)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
20
middleware/auth.go
Normal file
20
middleware/auth.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Package middleware defines all the middlewares for the application
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const UserIDKey = "UserID"
|
||||||
|
|
||||||
|
func Auth() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
_, exists := c.Get(UserIDKey)
|
||||||
|
if !exists {
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, "/login")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
middleware/general.go
Normal file
19
middleware/general.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// General handles the default headers that should be present in every response
|
||||||
|
func General() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Header("X-Content-Type-Options", "nosniff")
|
||||||
|
c.Header("X-XSS-Protection", "1; mode=block")
|
||||||
|
c.Header("X-Frame-Options", "DENY")
|
||||||
|
if os.Getenv("STRICT_TRANSPORT_SECURITY") == "true" {
|
||||||
|
c.Header("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload;")
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
21
middleware/noauth.go
Normal file
21
middleware/noauth.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var SessionIdentifierKey = "SESSION_IDENTIFIER"
|
||||||
|
|
||||||
|
// NoAuth is for routes that can only be accessed when the user is unauthenticated
|
||||||
|
func NoAuth() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
_, exists := c.Get(UserIDKey)
|
||||||
|
if !exists {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, "/admin")
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
}
|
14
middleware/sensitive.go
Normal file
14
middleware/sensitive.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sensitive middleware handles headers that should be set for routes that may contain sensitive data such as personal information
|
||||||
|
func Sensitive() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Header("Cache-control", "no-store")
|
||||||
|
c.Header("Pragma", "no-cache")
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
29
middleware/session.go
Normal file
29
middleware/session.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/uberswe/golang-base-project/models"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Session(db *gorm.DB) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
sessionIdentifierInterface := session.Get(SessionIdentifierKey)
|
||||||
|
|
||||||
|
if sessionIdentifier, ok := sessionIdentifierInterface.(string); ok {
|
||||||
|
ses := models.Session{
|
||||||
|
Identifier: sessionIdentifier,
|
||||||
|
}
|
||||||
|
res := db.Where(&ses).First(&ses)
|
||||||
|
if res.Error == nil {
|
||||||
|
c.Set(UserIDKey, ses.UserID)
|
||||||
|
} else {
|
||||||
|
log.Println(res.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
25
middleware/throttle.go
Normal file
25
middleware/throttle.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/ulule/limiter/v3"
|
||||||
|
middlewareGin "github.com/ulule/limiter/v3/drivers/middleware/gin"
|
||||||
|
"github.com/ulule/limiter/v3/drivers/store/memory"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Throttle(limit int) gin.HandlerFunc {
|
||||||
|
// Define a limit rate to 3 requests per minute.
|
||||||
|
rate, err := limiter.NewRateFromFormatted("5-M")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
store := memory.NewStore()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new middleware with the limiter instance.
|
||||||
|
return middlewareGin.NewMiddleware(limiter.New(store, rate))
|
||||||
|
}
|
9
models/session.go
Normal file
9
models/session.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "gorm.io/gorm"
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
gorm.Model
|
||||||
|
Identifier string
|
||||||
|
UserID uint
|
||||||
|
}
|
16
models/token.go
Normal file
16
models/token.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "gorm.io/gorm"
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
gorm.Model
|
||||||
|
Value string
|
||||||
|
Type string
|
||||||
|
ModelID int
|
||||||
|
ModelType string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenUserActivation string = "user_activation"
|
||||||
|
TokenPasswordReset string = "password_reset"
|
||||||
|
)
|
16
models/user.go
Normal file
16
models/user.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Package models defines all the database models for the application
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
gorm.Model
|
||||||
|
Email string
|
||||||
|
Password string
|
||||||
|
ActivatedAt *time.Time
|
||||||
|
Tokens []Token `gorm:"polymorphic:Model;"`
|
||||||
|
Sessions []Session
|
||||||
|
}
|
10
models/website.go
Normal file
10
models/website.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "gorm.io/gorm"
|
||||||
|
|
||||||
|
type Website struct {
|
||||||
|
gorm.Model
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
URL string
|
||||||
|
}
|
7693
package-lock.json
generated
Normal file
7693
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
package.json
Normal file
32
package.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "golang-base-project",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A base project with a golang backend ready to handle user authentication.",
|
||||||
|
"main": "index.js",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "Markus Tenghamn (https://github.com/uberswe) m@rkus.io",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"autoprefixer": "^10.4.0",
|
||||||
|
"css-loader": "^6.5.1",
|
||||||
|
"glob": "^7.2.0",
|
||||||
|
"mini-css-extract-plugin": "^2.4.5",
|
||||||
|
"node-sass": "^6.0.1",
|
||||||
|
"postcss": "^8.4.4",
|
||||||
|
"postcss-loader": "^6.2.1",
|
||||||
|
"postcss-normalize": "^10.0.1",
|
||||||
|
"purgecss-webpack-plugin": "^4.1.3",
|
||||||
|
"sass": "^1.44.0",
|
||||||
|
"sass-loader": "^12.3.0",
|
||||||
|
"webpack": "^5.64.4",
|
||||||
|
"webpack-cli": "^4.9.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bootstrap": "^5.1.3"
|
||||||
|
}
|
||||||
|
}
|
71
routes/activate.go
Normal file
71
routes/activate.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/uberswe/golang-base-project/models"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (controller Controller) Activate(c *gin.Context) {
|
||||||
|
activationError := "Please provide a valid activation token"
|
||||||
|
activationSuccess := "Account activated. You may now proceed to login to your account."
|
||||||
|
pd := PageData{
|
||||||
|
Title: "Activate",
|
||||||
|
IsAuthenticated: isAuthenticated(c),
|
||||||
|
}
|
||||||
|
token := c.Param("token")
|
||||||
|
activationToken := models.Token{
|
||||||
|
Value: token,
|
||||||
|
Type: models.TokenUserActivation,
|
||||||
|
}
|
||||||
|
|
||||||
|
res := controller.db.Where(&activationToken).First(&activationToken)
|
||||||
|
if res.Error != nil {
|
||||||
|
log.Println(res.Error)
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: activationError,
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusBadRequest, "activate.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := models.User{}
|
||||||
|
user.ID = uint(activationToken.ModelID)
|
||||||
|
|
||||||
|
res = controller.db.Where(&user).First(&user)
|
||||||
|
if res.Error != nil {
|
||||||
|
log.Println(res.Error)
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: activationError,
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusBadRequest, "activate.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
user.ActivatedAt = &now
|
||||||
|
|
||||||
|
res = controller.db.Save(&user)
|
||||||
|
if res.Error != nil {
|
||||||
|
log.Println(res.Error)
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: activationError,
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusBadRequest, "activate.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't need to check for an error here, even if it's not deleted it will not really affect application logic
|
||||||
|
controller.db.Delete(&activationToken)
|
||||||
|
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "success",
|
||||||
|
Content: activationSuccess,
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusOK, "activate.html", pd)
|
||||||
|
}
|
14
routes/admin.go
Normal file
14
routes/admin.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (controller Controller) Admin(c *gin.Context) {
|
||||||
|
pd := PageData{
|
||||||
|
Title: "Admin",
|
||||||
|
IsAuthenticated: isAuthenticated(c),
|
||||||
|
}
|
||||||
|
c.HTML(http.StatusOK, "admin.html", pd)
|
||||||
|
}
|
83
routes/forgotpassword.go
Normal file
83
routes/forgotpassword.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
email2 "github.com/uberswe/golang-base-project/email"
|
||||||
|
"github.com/uberswe/golang-base-project/models"
|
||||||
|
"github.com/uberswe/golang-base-project/util"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (controller Controller) ForgotPassword(c *gin.Context) {
|
||||||
|
pd := PageData{
|
||||||
|
Title: "Forgot Password",
|
||||||
|
IsAuthenticated: isAuthenticated(c),
|
||||||
|
}
|
||||||
|
c.HTML(http.StatusOK, "forgotpassword.html", pd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (controller Controller) ForgotPasswordPost(c *gin.Context) {
|
||||||
|
pd := PageData{
|
||||||
|
Title: "Forgot Password",
|
||||||
|
IsAuthenticated: isAuthenticated(c),
|
||||||
|
}
|
||||||
|
email := c.PostForm("email")
|
||||||
|
user := models.User{Email: email}
|
||||||
|
res := controller.db.Where(&user).First(&user)
|
||||||
|
if res.Error == nil && user.ActivatedAt != nil {
|
||||||
|
go controller.forgotPasswordEmailHandler(user.ID, email)
|
||||||
|
}
|
||||||
|
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "success",
|
||||||
|
Content: "An email with instructions describing how to reset your password has been sent.",
|
||||||
|
})
|
||||||
|
|
||||||
|
// We always return a positive response here to prevent user enumeration
|
||||||
|
c.HTML(http.StatusOK, "forgotpassword.html", pd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (controller Controller) forgotPasswordEmailHandler(userID uint, email string) {
|
||||||
|
forgotPasswordToken := models.Token{
|
||||||
|
Value: util.GenerateULID(),
|
||||||
|
Type: models.TokenPasswordReset,
|
||||||
|
}
|
||||||
|
|
||||||
|
res := controller.db.Where(&forgotPasswordToken).First(&forgotPasswordToken)
|
||||||
|
if (res.Error != nil && res.Error != gorm.ErrRecordNotFound) || res.RowsAffected > 0 {
|
||||||
|
// If the forgot password token already exists we try to generate it again
|
||||||
|
controller.forgotPasswordEmailHandler(userID, email)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forgotPasswordToken.ModelID = int(userID)
|
||||||
|
forgotPasswordToken.ModelType = "User"
|
||||||
|
|
||||||
|
res = controller.db.Save(&forgotPasswordToken)
|
||||||
|
if res.Error != nil || res.RowsAffected == 0 {
|
||||||
|
log.Println(res.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.sendForgotPasswordEmail(forgotPasswordToken.Value, email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (controller Controller) sendForgotPasswordEmail(token string, email string) {
|
||||||
|
u, err := url.Parse(controller.config.BaseURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Path = path.Join(u.Path, "/user/password/reset/", token)
|
||||||
|
|
||||||
|
resetPasswordURL := u.String()
|
||||||
|
|
||||||
|
emailService := email2.New(controller.config)
|
||||||
|
|
||||||
|
emailService.Send(email, "Password Reset", fmt.Sprintf("Use the following link to reset your password. If this was not requested by you, please ignore this email.\n%s", resetPasswordURL))
|
||||||
|
}
|
14
routes/index.go
Normal file
14
routes/index.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (controller Controller) Index(c *gin.Context) {
|
||||||
|
pd := PageData{
|
||||||
|
Title: "Home",
|
||||||
|
IsAuthenticated: isAuthenticated(c),
|
||||||
|
}
|
||||||
|
c.HTML(http.StatusOK, "index.html", pd)
|
||||||
|
}
|
102
routes/login.go
Normal file
102
routes/login.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/uberswe/golang-base-project/middleware"
|
||||||
|
"github.com/uberswe/golang-base-project/models"
|
||||||
|
"github.com/uberswe/golang-base-project/util"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (controller Controller) Login(c *gin.Context) {
|
||||||
|
pd := PageData{
|
||||||
|
Title: "Login",
|
||||||
|
}
|
||||||
|
c.HTML(http.StatusOK, "login.html", pd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (controller Controller) LoginPost(c *gin.Context) {
|
||||||
|
loginError := "Could not login, please make sure that you have typed in the correct email and password. If you have forgotten your password, please click the forgot password link below."
|
||||||
|
pd := PageData{
|
||||||
|
Title: "Login",
|
||||||
|
IsAuthenticated: isAuthenticated(c),
|
||||||
|
}
|
||||||
|
email := c.PostForm("email")
|
||||||
|
user := models.User{Email: email}
|
||||||
|
|
||||||
|
res := controller.db.Where(&user).First(&user)
|
||||||
|
if res.Error != nil {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: loginError,
|
||||||
|
})
|
||||||
|
log.Println(res.Error)
|
||||||
|
c.HTML(http.StatusInternalServerError, "login.html", pd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.RowsAffected == 0 {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: loginError,
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusBadRequest, "login.html", pd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.ActivatedAt == nil {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: "Account is not activated yet.",
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusBadRequest, "login.html", pd)
|
||||||
|
}
|
||||||
|
|
||||||
|
password := c.PostForm("password")
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
|
||||||
|
if err != nil {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: loginError,
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusBadRequest, "login.html", pd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a ulid for the current session
|
||||||
|
sessionIdentifier := util.GenerateULID()
|
||||||
|
|
||||||
|
ses := models.Session{
|
||||||
|
Identifier: sessionIdentifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session is valid for 1 hour
|
||||||
|
ses.DeletedAt.Time = time.Now().Add(time.Hour * 1)
|
||||||
|
ses.UserID = user.ID
|
||||||
|
|
||||||
|
res = controller.db.Save(&ses)
|
||||||
|
if res.Error != nil {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: loginError,
|
||||||
|
})
|
||||||
|
log.Println(res.Error)
|
||||||
|
c.HTML(http.StatusInternalServerError, "login.html", pd)
|
||||||
|
}
|
||||||
|
|
||||||
|
session := sessions.Default(c)
|
||||||
|
session.Set(middleware.SessionIdentifierKey, sessionIdentifier)
|
||||||
|
|
||||||
|
err = session.Save()
|
||||||
|
if err != nil {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: loginError,
|
||||||
|
})
|
||||||
|
log.Println(err)
|
||||||
|
c.HTML(http.StatusInternalServerError, "login.html", pd)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, "/admin")
|
||||||
|
}
|
18
routes/logout.go
Normal file
18
routes/logout.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/uberswe/golang-base-project/middleware"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (controller Controller) Logout(c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
session.Delete(middleware.SessionIdentifierKey)
|
||||||
|
err := session.Save()
|
||||||
|
log.Println(err)
|
||||||
|
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, "/")
|
||||||
|
}
|
37
routes/main.go
Normal file
37
routes/main.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Package routes defines all the handling functions for all the routes
|
||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/uberswe/golang-base-project/config"
|
||||||
|
"github.com/uberswe/golang-base-project/middleware"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
db *gorm.DB
|
||||||
|
config config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db *gorm.DB, c config.Config) Controller {
|
||||||
|
return Controller{
|
||||||
|
db: db,
|
||||||
|
config: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageData struct {
|
||||||
|
Title string
|
||||||
|
Messages []Message
|
||||||
|
IsAuthenticated bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Type string // success, warning, error, etc.
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAuthenticated(c *gin.Context) bool {
|
||||||
|
_, exists := c.Get(middleware.UserIDKey)
|
||||||
|
return exists
|
||||||
|
}
|
14
routes/noroute.go
Normal file
14
routes/noroute.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (controller Controller) NoRoute(c *gin.Context) {
|
||||||
|
pd := PageData{
|
||||||
|
Title: "404 Not Found",
|
||||||
|
IsAuthenticated: isAuthenticated(c),
|
||||||
|
}
|
||||||
|
c.HTML(http.StatusOK, "404.html", pd)
|
||||||
|
}
|
141
routes/register.go
Normal file
141
routes/register.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
email2 "github.com/uberswe/golang-base-project/email"
|
||||||
|
"github.com/uberswe/golang-base-project/models"
|
||||||
|
"github.com/uberswe/golang-base-project/util"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (controller Controller) Register(c *gin.Context) {
|
||||||
|
pd := PageData{
|
||||||
|
Title: "Register",
|
||||||
|
IsAuthenticated: isAuthenticated(c),
|
||||||
|
}
|
||||||
|
c.HTML(http.StatusOK, "register.html", pd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (controller Controller) RegisterPost(c *gin.Context) {
|
||||||
|
passwordError := "Your password must be 8 characters in length or longer"
|
||||||
|
registerError := "Could not register, please make sure the details you have provided are correct and that you do not already have an existing account."
|
||||||
|
registerSuccess := "Thank you for registering. An activation email has been sent with steps describing how to activate your account."
|
||||||
|
pd := PageData{
|
||||||
|
Title: "Register",
|
||||||
|
IsAuthenticated: isAuthenticated(c),
|
||||||
|
}
|
||||||
|
password := c.PostForm("password")
|
||||||
|
if len(password) < 8 {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: passwordError,
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusBadRequest, "register.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The password is hashed as early as possible to make timing attacks that reveal registered users harder
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: registerError,
|
||||||
|
})
|
||||||
|
log.Println(err)
|
||||||
|
c.HTML(http.StatusInternalServerError, "register.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
email := c.PostForm("email")
|
||||||
|
user := models.User{Email: email}
|
||||||
|
|
||||||
|
res := controller.db.Where(&user).First(&user)
|
||||||
|
if (res.Error != nil && res.Error != gorm.ErrRecordNotFound) || res.RowsAffected > 0 {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: registerError,
|
||||||
|
})
|
||||||
|
log.Println(res.Error)
|
||||||
|
c.HTML(http.StatusInternalServerError, "register.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: registerError,
|
||||||
|
})
|
||||||
|
log.Println(err)
|
||||||
|
c.HTML(http.StatusInternalServerError, "register.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Password = string(hashedPassword)
|
||||||
|
|
||||||
|
res = controller.db.Save(&user)
|
||||||
|
if res.Error != nil || res.RowsAffected == 0 {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: registerError,
|
||||||
|
})
|
||||||
|
log.Println(res.Error)
|
||||||
|
c.HTML(http.StatusInternalServerError, "register.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate activation token and send activation email
|
||||||
|
go controller.activationEmailHandler(user.ID, email)
|
||||||
|
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "success",
|
||||||
|
Content: registerSuccess,
|
||||||
|
})
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "register.html", pd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (controller Controller) activationEmailHandler(userID uint, email string) {
|
||||||
|
activationToken := models.Token{
|
||||||
|
Value: util.GenerateULID(),
|
||||||
|
Type: models.TokenUserActivation,
|
||||||
|
}
|
||||||
|
|
||||||
|
res := controller.db.Where(&activationToken).First(&activationToken)
|
||||||
|
if (res.Error != nil && res.Error != gorm.ErrRecordNotFound) || res.RowsAffected > 0 {
|
||||||
|
// If the activation token already exists we try to generate it again
|
||||||
|
controller.activationEmailHandler(userID, email)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
activationToken.ModelID = int(userID)
|
||||||
|
activationToken.ModelType = "User"
|
||||||
|
|
||||||
|
res = controller.db.Save(&activationToken)
|
||||||
|
if res.Error != nil || res.RowsAffected == 0 {
|
||||||
|
log.Println(res.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.sendActivationEmail(activationToken.Value, email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (controller Controller) sendActivationEmail(token string, email string) {
|
||||||
|
u, err := url.Parse(controller.config.BaseURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Path = path.Join(u.Path, "/activate/", token)
|
||||||
|
|
||||||
|
activationURL := u.String()
|
||||||
|
|
||||||
|
emailService := email2.New(controller.config)
|
||||||
|
|
||||||
|
emailService.Send(email, "User Activation", fmt.Sprintf("Use the following link to activate your account. If this was not requested by you, please ignore this email.\n%s", activationURL))
|
||||||
|
}
|
50
routes/resendactivation.go
Normal file
50
routes/resendactivation.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/uberswe/golang-base-project/models"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (controller Controller) ResendActivation(c *gin.Context) {
|
||||||
|
pd := PageData{
|
||||||
|
Title: "Resend Activation Email",
|
||||||
|
IsAuthenticated: isAuthenticated(c),
|
||||||
|
}
|
||||||
|
c.HTML(http.StatusOK, "resendactivation.html", pd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (controller Controller) ResendActivationPost(c *gin.Context) {
|
||||||
|
pd := PageData{
|
||||||
|
Title: "Resend Activation Email",
|
||||||
|
IsAuthenticated: isAuthenticated(c),
|
||||||
|
}
|
||||||
|
email := c.PostForm("email")
|
||||||
|
user := models.User{Email: email}
|
||||||
|
res := controller.db.Where(&user).First(&user)
|
||||||
|
if res.Error == nil && user.ActivatedAt == nil {
|
||||||
|
activationToken := models.Token{
|
||||||
|
Type: models.TokenUserActivation,
|
||||||
|
ModelID: int(user.ID),
|
||||||
|
}
|
||||||
|
|
||||||
|
res = controller.db.Where(&activationToken).First(&activationToken)
|
||||||
|
if res.Error == nil {
|
||||||
|
// If the activation token exists we simply send an email
|
||||||
|
go controller.sendActivationEmail(activationToken.Value, user.Email)
|
||||||
|
} else {
|
||||||
|
// If there is no token then we need to generate a new token
|
||||||
|
go controller.activationEmailHandler(user.ID, user.Email)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Println(res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We always return a positive response here to prevent user enumeration and other attacks
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "success",
|
||||||
|
Content: "A new activation email has been sent if the account exists and is not already activated. Please remember to check your spam inbox in case the email is not showing in your inbox.",
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusOK, "resendactivation.html", pd)
|
||||||
|
}
|
118
routes/resetpassword.go
Normal file
118
routes/resetpassword.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/uberswe/golang-base-project/models"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResetPasswordPageData struct {
|
||||||
|
PageData
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (controller Controller) ResetPassword(c *gin.Context) {
|
||||||
|
token := c.Param("token")
|
||||||
|
pd := ResetPasswordPageData{
|
||||||
|
PageData: PageData{
|
||||||
|
Title: "Reset Password",
|
||||||
|
IsAuthenticated: isAuthenticated(c),
|
||||||
|
},
|
||||||
|
Token: token,
|
||||||
|
}
|
||||||
|
c.HTML(http.StatusOK, "resetpassword.html", pd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (controller Controller) ResetPasswordPost(c *gin.Context) {
|
||||||
|
passwordError := "Your password must be 8 characters in length or longer"
|
||||||
|
resetError := "Could not reset password, please try again"
|
||||||
|
|
||||||
|
token := c.Param("token")
|
||||||
|
pd := ResetPasswordPageData{
|
||||||
|
PageData: PageData{
|
||||||
|
Title: "Reset Password",
|
||||||
|
IsAuthenticated: isAuthenticated(c),
|
||||||
|
},
|
||||||
|
Token: token,
|
||||||
|
}
|
||||||
|
password := c.PostForm("password")
|
||||||
|
|
||||||
|
if len(password) < 8 {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: passwordError,
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusBadRequest, "resetpassword.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forgotPasswordToken := models.Token{
|
||||||
|
Value: token,
|
||||||
|
Type: models.TokenPasswordReset,
|
||||||
|
}
|
||||||
|
|
||||||
|
res := controller.db.Where(&forgotPasswordToken).First(&forgotPasswordToken)
|
||||||
|
if res.Error != nil {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: resetError,
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusBadRequest, "resetpassword.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := models.User{}
|
||||||
|
user.ID = uint(forgotPasswordToken.ModelID)
|
||||||
|
res = controller.db.Where(&user).First(&user)
|
||||||
|
if res.Error != nil {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: resetError,
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusBadRequest, "resetpassword.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: resetError,
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusBadRequest, "resetpassword.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Password = string(hashedPassword)
|
||||||
|
|
||||||
|
res = controller.db.Save(&user)
|
||||||
|
if res.Error != nil {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: resetError,
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusBadRequest, "resetpassword.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res = controller.db.Delete(&forgotPasswordToken)
|
||||||
|
if res.Error != nil {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: resetError,
|
||||||
|
})
|
||||||
|
c.HTML(http.StatusBadRequest, "resetpassword.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "success",
|
||||||
|
Content: "Your password has successfully been reset.",
|
||||||
|
})
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "resetpassword.html", pd)
|
||||||
|
}
|
46
routes/search.go
Normal file
46
routes/search.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/uberswe/golang-base-project/models"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SearchData struct {
|
||||||
|
PageData
|
||||||
|
Results []models.Website
|
||||||
|
}
|
||||||
|
|
||||||
|
func (controller Controller) Search(c *gin.Context) {
|
||||||
|
pd := SearchData{
|
||||||
|
PageData: PageData{
|
||||||
|
Title: "Search",
|
||||||
|
IsAuthenticated: isAuthenticated(c),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
search := c.PostForm("search")
|
||||||
|
|
||||||
|
var results []models.Website
|
||||||
|
|
||||||
|
log.Println(search)
|
||||||
|
search = fmt.Sprintf("%s%s%s", "%", search, "%")
|
||||||
|
|
||||||
|
log.Println(search)
|
||||||
|
res := controller.db.Where("title LIKE ? OR description LIKE ?", search, search).Find(&results)
|
||||||
|
|
||||||
|
if res.Error != nil || len(results) == 0 {
|
||||||
|
pd.Messages = append(pd.Messages, Message{
|
||||||
|
Type: "error",
|
||||||
|
Content: "No results found",
|
||||||
|
})
|
||||||
|
log.Println(res.Error)
|
||||||
|
c.HTML(http.StatusOK, "search.html", pd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pd.Results = results
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "search.html", pd)
|
||||||
|
}
|
44
src/custom.scss
Normal file
44
src/custom.scss
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
main > .container {
|
||||||
|
padding: 60px 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 330px;
|
||||||
|
padding: 15px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin .checkbox {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin .form-floating:focus-within {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin input[type="email"] {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin input[type="password"] {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-placeholder-img {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
text-anchor: middle;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.bd-placeholder-img-lg {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
}
|
||||||
|
}
|
4
src/index.js
Normal file
4
src/index.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// Collapse is needed for responsive navbar
|
||||||
|
import 'bootstrap/js/src/base-component'
|
||||||
|
import 'bootstrap/js/src/collapse'
|
||||||
|
import 'bootstrap/js/src/alert'
|
2
src/style.scss
Normal file
2
src/style.scss
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
@import "custom";
|
||||||
|
@import "~bootstrap/scss/bootstrap";
|
10
staticcheck.conf
Normal file
10
staticcheck.conf
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
checks = ["all"]
|
||||||
|
initialisms = ["ACL", "API", "ASCII", "CPU", "CSS", "DNS",
|
||||||
|
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
|
||||||
|
"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
|
||||||
|
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
|
||||||
|
"UDP", "UI", "GID", "UID", "UUID", "URI",
|
||||||
|
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
|
||||||
|
"XSS", "SIP", "RTP", "AMQP", "DB", "TS"]
|
||||||
|
dot_import_whitelist = []
|
||||||
|
http_status_code_whitelist = []
|
36
templates.go
Normal file
36
templates.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Package baseproject is the main package of the golang-base-project which defines all routes and database connections and settings, the glue to the entire application
|
||||||
|
package baseproject
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadTemplates() (*template.Template, error) {
|
||||||
|
var err4 error
|
||||||
|
t := template.New("")
|
||||||
|
err := fs.WalkDir(staticFS, ".", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
f, err2 := staticFS.Open(path)
|
||||||
|
if err2 != nil {
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
h, err3 := ioutil.ReadAll(f)
|
||||||
|
if err3 != nil {
|
||||||
|
return err3
|
||||||
|
}
|
||||||
|
parts := strings.Split(path, "/")
|
||||||
|
if len(parts) > 0 && strings.HasSuffix(parts[len(parts)-1], ".html") {
|
||||||
|
t, err4 = t.New(parts[len(parts)-1]).Parse(string(h))
|
||||||
|
if err4 != nil {
|
||||||
|
return err4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return t, err
|
||||||
|
}
|
36
util/text.go
Normal file
36
util/text.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Package util provides utility functions for various things such as strings
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NL2BR(s string) string {
|
||||||
|
return strings.Replace(s, "\n", "<br>", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringLinkToHTMLLink(s string) string {
|
||||||
|
s = strings.Replace(s, "\n", " \n ", -1)
|
||||||
|
for _, p := range strings.Split(s, " ") {
|
||||||
|
u, err := url.ParseRequestURI(p)
|
||||||
|
if err == nil && u.Scheme != "" {
|
||||||
|
s = strings.Replace(s, p, fmt.Sprintf("<a href=\"%s\">%s</a>", p, p), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Replace(s, " \n ", "\n", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStringBetweenStrings(str string, start string, end string) (result string) {
|
||||||
|
s := strings.Index(str, start)
|
||||||
|
if s == -1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s += len(start)
|
||||||
|
e := strings.Index(str[s:], end)
|
||||||
|
if e == -1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return str[s : s+e]
|
||||||
|
}
|
13
util/ulid.go
Normal file
13
util/ulid.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/oklog/ulid/v2"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateULID() string {
|
||||||
|
entropy := ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)
|
||||||
|
res := ulid.MustNew(ulid.Timestamp(time.Now()), entropy)
|
||||||
|
return res.String()
|
||||||
|
}
|
16
vendor/github.com/KyleBanks/depth/.gitignore
generated
vendored
Normal file
16
vendor/github.com/KyleBanks/depth/.gitignore
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||||
|
.glide/
|
||||||
|
|
||||||
|
bin/
|
9
vendor/github.com/KyleBanks/depth/.travis.yml
generated
vendored
Normal file
9
vendor/github.com/KyleBanks/depth/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
go:
|
||||||
|
- 1.9.x
|
||||||
|
before_install:
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
script:
|
||||||
|
- $HOME/gopath/bin/goveralls -service=travis-ci
|
||||||
|
#script: go test $(go list ./... | grep -v vendor/)
|
21
vendor/github.com/KyleBanks/depth/LICENSE
generated
vendored
Normal file
21
vendor/github.com/KyleBanks/depth/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Kyle Banks
|
||||||
|
|
||||||
|
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.
|
32
vendor/github.com/KyleBanks/depth/Makefile
generated
vendored
Normal file
32
vendor/github.com/KyleBanks/depth/Makefile
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
VERSION = 1.2.1
|
||||||
|
|
||||||
|
RELEASE_PKG = ./cmd/depth
|
||||||
|
INSTALL_PKG = $(RELEASE_PKG)
|
||||||
|
|
||||||
|
|
||||||
|
# Remote includes require 'mmake'
|
||||||
|
# github.com/tj/mmake
|
||||||
|
include github.com/KyleBanks/make/go/install
|
||||||
|
include github.com/KyleBanks/make/go/sanity
|
||||||
|
include github.com/KyleBanks/make/go/release
|
||||||
|
include github.com/KyleBanks/make/go/bench
|
||||||
|
include github.com/KyleBanks/make/git/precommit
|
||||||
|
|
||||||
|
# Runs a number of depth commands as examples of what's possible.
|
||||||
|
example: | install
|
||||||
|
depth github.com/KyleBanks/depth/cmd/depth strings ./
|
||||||
|
|
||||||
|
depth -internal strings
|
||||||
|
|
||||||
|
depth -json github.com/KyleBanks/depth/cmd/depth
|
||||||
|
|
||||||
|
depth -test github.com/KyleBanks/depth/cmd/depth
|
||||||
|
|
||||||
|
depth -test -internal strings
|
||||||
|
|
||||||
|
depth -test -internal -max 3 strings
|
||||||
|
|
||||||
|
depth .
|
||||||
|
|
||||||
|
depth ./cmd/depth
|
||||||
|
.PHONY: example
|
232
vendor/github.com/KyleBanks/depth/README.md
generated
vendored
Normal file
232
vendor/github.com/KyleBanks/depth/README.md
generated
vendored
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
# depth
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/KyleBanks/depth)
|
||||||
|
[](https://travis-ci.org/KyleBanks/depth)
|
||||||
|
[](https://goreportcard.com/report/github.com/KyleBanks/depth)
|
||||||
|
[](https://coveralls.io/github/KyleBanks/depth?branch=master)
|
||||||
|
|
||||||
|
`depth` is tool to retrieve and visualize Go source code dependency trees.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Download the appropriate binary for your platform from the [Releases](https://github.com/KyleBanks/depth/releases) page, or:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get github.com/KyleBanks/depth/cmd/depth
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
`depth` can be used as a standalone command-line application, or as a package within your own project.
|
||||||
|
|
||||||
|
### Command-Line
|
||||||
|
|
||||||
|
Simply execute `depth` with one or more package names to visualize. You can use the fully qualified import path of the package, like so:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth github.com/KyleBanks/depth/cmd/depth
|
||||||
|
github.com/KyleBanks/depth/cmd/depth
|
||||||
|
├ encoding/json
|
||||||
|
├ flag
|
||||||
|
├ fmt
|
||||||
|
├ io
|
||||||
|
├ log
|
||||||
|
├ os
|
||||||
|
├ strings
|
||||||
|
└ github.com/KyleBanks/depth
|
||||||
|
├ fmt
|
||||||
|
├ go/build
|
||||||
|
├ path
|
||||||
|
├ sort
|
||||||
|
└ strings
|
||||||
|
12 dependencies (11 internal, 1 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
Or you can use a relative path, for example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth .
|
||||||
|
$ depth ./cmd/depth
|
||||||
|
$ depth ../
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use `depth` on the Go standard library:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth strings
|
||||||
|
strings
|
||||||
|
├ errors
|
||||||
|
├ io
|
||||||
|
├ unicode
|
||||||
|
└ unicode/utf8
|
||||||
|
5 dependencies (5 internal, 0 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
Visualizing multiple packages at a time is supported by simply naming the packages you'd like to visualize:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth strings github.com/KyleBanks/depth
|
||||||
|
strings
|
||||||
|
├ errors
|
||||||
|
├ io
|
||||||
|
├ unicode
|
||||||
|
└ unicode/utf8
|
||||||
|
5 dependencies (5 internal, 0 external, 0 testing).
|
||||||
|
github.com/KyleBanks/depth
|
||||||
|
├ fmt
|
||||||
|
├ go/build
|
||||||
|
├ path
|
||||||
|
├ sort
|
||||||
|
└ strings
|
||||||
|
7 dependencies (7 internal, 0 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `-internal`
|
||||||
|
|
||||||
|
By default, `depth` only resolves the top level of dependencies for standard library packages, however you can use the `-internal` flag to visualize all internal dependencies:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth -internal strings
|
||||||
|
strings
|
||||||
|
├ errors
|
||||||
|
├ io
|
||||||
|
├ errors
|
||||||
|
└ sync
|
||||||
|
├ internal/race
|
||||||
|
└ unsafe
|
||||||
|
├ runtime
|
||||||
|
├ runtime/internal/atomic
|
||||||
|
└ unsafe
|
||||||
|
├ runtime/internal/sys
|
||||||
|
└ unsafe
|
||||||
|
├ sync/atomic
|
||||||
|
└ unsafe
|
||||||
|
└ unsafe
|
||||||
|
├ unicode
|
||||||
|
└ unicode/utf8
|
||||||
|
12 dependencies (12 internal, 0 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `-max`
|
||||||
|
|
||||||
|
The `-max` flag limits the dependency tree to the maximum depth provided. For example, if you supply `-max 1` on the `depth` package, your output would look like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ depth -max 1 github.com/KyleBanks/depth/cmd/depth
|
||||||
|
github.com/KyleBanks/depth/cmd/depth
|
||||||
|
├ encoding/json
|
||||||
|
├ flag
|
||||||
|
├ fmt
|
||||||
|
├ io
|
||||||
|
├ log
|
||||||
|
├ os
|
||||||
|
├ strings
|
||||||
|
└ github.com/KyleBanks/depth
|
||||||
|
7 dependencies (6 internal, 1 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
The `-max` flag is particularly useful in conjunction with the `-internal` flag which can lead to very deep dependency trees.
|
||||||
|
|
||||||
|
#### `-test`
|
||||||
|
|
||||||
|
By default, `depth` ignores dependencies that are only required for testing. However, you can view test dependencies using the `-test` flag:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth -test strings
|
||||||
|
strings
|
||||||
|
├ bytes
|
||||||
|
├ errors
|
||||||
|
├ fmt
|
||||||
|
├ io
|
||||||
|
├ io/ioutil
|
||||||
|
├ math/rand
|
||||||
|
├ reflect
|
||||||
|
├ sync
|
||||||
|
├ testing
|
||||||
|
├ unicode
|
||||||
|
├ unicode/utf8
|
||||||
|
└ unsafe
|
||||||
|
13 dependencies (13 internal, 0 external, 8 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `-explain target-package`
|
||||||
|
|
||||||
|
The `-explain` flag instructs `depth` to print import chains in which the
|
||||||
|
`target-package` is found:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth -explain strings github.com/KyleBanks/depth/cmd/depth
|
||||||
|
github.com/KyleBanks/depth/cmd/depth -> strings
|
||||||
|
github.com/KyleBanks/depth/cmd/depth -> github.com/KyleBanks/depth -> strings
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `-json`
|
||||||
|
|
||||||
|
The `-json` flag instructs `depth` to output dependencies in JSON format:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth -json github.com/KyleBanks/depth/cmd/depth
|
||||||
|
{
|
||||||
|
"name": "github.com/KyleBanks/depth/cmd/depth",
|
||||||
|
"deps": [
|
||||||
|
{
|
||||||
|
"name": "encoding/json",
|
||||||
|
"internal": true,
|
||||||
|
"deps": null
|
||||||
|
},
|
||||||
|
...
|
||||||
|
{
|
||||||
|
"name": "github.com/KyleBanks/depth",
|
||||||
|
"internal": false,
|
||||||
|
"deps": [
|
||||||
|
{
|
||||||
|
"name": "go/build",
|
||||||
|
"internal": true,
|
||||||
|
"deps": null
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integrating With Your Project
|
||||||
|
|
||||||
|
The `depth` package can easily be used to retrieve the dependency tree for a particular package in your own project. For example, here's how you would retrieve the dependency tree for the `strings` package:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/KyleBanks/depth"
|
||||||
|
|
||||||
|
var t depth.Tree
|
||||||
|
err := t.Resolve("strings")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output: "'strings' has 4 dependencies."
|
||||||
|
log.Printf("'%v' has %v dependencies.", t.Root.Name, len(t.Root.Deps))
|
||||||
|
```
|
||||||
|
|
||||||
|
For additional customization, simply set the appropriate flags on the `Tree` before resolving:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/KyleBanks/depth"
|
||||||
|
|
||||||
|
t := depth.Tree {
|
||||||
|
ResolveInternal: true,
|
||||||
|
ResolveTest: true,
|
||||||
|
MaxDepth: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
err := t.Resolve("strings")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
`depth` was developed by [Kyle Banks](https://twitter.com/kylewbanks).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
`depth` is available under the [MIT](./LICENSE) license.
|
129
vendor/github.com/KyleBanks/depth/depth.go
generated
vendored
Normal file
129
vendor/github.com/KyleBanks/depth/depth.go
generated
vendored
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// Package depth provides the ability to traverse and retrieve Go source code dependencies in the form of
|
||||||
|
// internal and external packages.
|
||||||
|
//
|
||||||
|
// For example, the dependencies of the stdlib `strings` package can be resolved like so:
|
||||||
|
//
|
||||||
|
// import "github.com/KyleBanks/depth"
|
||||||
|
//
|
||||||
|
// var t depth.Tree
|
||||||
|
// err := t.Resolve("strings")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Output: "strings has 4 dependencies."
|
||||||
|
// log.Printf("%v has %v dependencies.", t.Root.Name, len(t.Root.Deps))
|
||||||
|
//
|
||||||
|
// For additional customization, simply set the appropriate flags on the `Tree` before resolving:
|
||||||
|
//
|
||||||
|
// import "github.com/KyleBanks/depth"
|
||||||
|
//
|
||||||
|
// t := depth.Tree {
|
||||||
|
// ResolveInternal: true,
|
||||||
|
// ResolveTest: true,
|
||||||
|
// MaxDepth: 10,
|
||||||
|
// }
|
||||||
|
// err := t.Resolve("strings")
|
||||||
|
package depth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"go/build"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrRootPkgNotResolved is returned when the root Pkg of the Tree cannot be resolved,
|
||||||
|
// typically because it does not exist.
|
||||||
|
var ErrRootPkgNotResolved = errors.New("unable to resolve root package")
|
||||||
|
|
||||||
|
// Importer defines a type that can import a package and return its details.
|
||||||
|
type Importer interface {
|
||||||
|
Import(name, srcDir string, im build.ImportMode) (*build.Package, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tree represents the top level of a Pkg and the configuration used to
|
||||||
|
// initialize and represent its contents.
|
||||||
|
type Tree struct {
|
||||||
|
Root *Pkg
|
||||||
|
|
||||||
|
ResolveInternal bool
|
||||||
|
ResolveTest bool
|
||||||
|
MaxDepth int
|
||||||
|
|
||||||
|
Importer Importer
|
||||||
|
|
||||||
|
importCache map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve recursively finds all dependencies for the root Pkg name provided,
|
||||||
|
// and the packages it depends on.
|
||||||
|
func (t *Tree) Resolve(name string) error {
|
||||||
|
pwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Root = &Pkg{
|
||||||
|
Name: name,
|
||||||
|
Tree: t,
|
||||||
|
SrcDir: pwd,
|
||||||
|
Test: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the import cache each time to ensure a reused Tree doesn't
|
||||||
|
// reuse the same cache.
|
||||||
|
t.importCache = nil
|
||||||
|
|
||||||
|
// Allow custom importers, but use build.Default if none is provided.
|
||||||
|
if t.Importer == nil {
|
||||||
|
t.Importer = &build.Default
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Root.Resolve(t.Importer)
|
||||||
|
if !t.Root.Resolved {
|
||||||
|
return ErrRootPkgNotResolved
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldResolveInternal determines if internal packages should be further resolved beyond the
|
||||||
|
// current parent.
|
||||||
|
//
|
||||||
|
// For example, if the parent Pkg is `github.com/foo/bar` and true is returned, all the
|
||||||
|
// internal dependencies it relies on will be resolved. If for example `strings` is one of those
|
||||||
|
// dependencies, and it is passed as the parent here, false may be returned and its internal
|
||||||
|
// dependencies will not be resolved.
|
||||||
|
func (t *Tree) shouldResolveInternal(parent *Pkg) bool {
|
||||||
|
if t.ResolveInternal {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent == t.Root
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAtMaxDepth returns true when the depth of the Pkg provided is at or beyond the maximum
|
||||||
|
// depth allowed by the tree.
|
||||||
|
//
|
||||||
|
// If the Tree has a MaxDepth of zero, true is never returned.
|
||||||
|
func (t *Tree) isAtMaxDepth(p *Pkg) bool {
|
||||||
|
if t.MaxDepth == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.depth() >= t.MaxDepth
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasSeenImport returns true if the import name provided has already been seen within the tree.
|
||||||
|
// This function only returns false for a name once.
|
||||||
|
func (t *Tree) hasSeenImport(name string) bool {
|
||||||
|
if t.importCache == nil {
|
||||||
|
t.importCache = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := t.importCache[name]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
t.importCache[name] = struct{}{}
|
||||||
|
return false
|
||||||
|
}
|
184
vendor/github.com/KyleBanks/depth/pkg.go
generated
vendored
Normal file
184
vendor/github.com/KyleBanks/depth/pkg.go
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
package depth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"go/build"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pkg represents a Go source package, and its dependencies.
|
||||||
|
type Pkg struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
SrcDir string `json:"-"`
|
||||||
|
|
||||||
|
Internal bool `json:"internal"`
|
||||||
|
Resolved bool `json:"resolved"`
|
||||||
|
Test bool `json:"-"`
|
||||||
|
|
||||||
|
Tree *Tree `json:"-"`
|
||||||
|
Parent *Pkg `json:"-"`
|
||||||
|
Deps []Pkg `json:"deps"`
|
||||||
|
|
||||||
|
Raw *build.Package `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve recursively finds all dependencies for the Pkg and the packages it depends on.
|
||||||
|
func (p *Pkg) Resolve(i Importer) {
|
||||||
|
// Resolved is always true, regardless of if we skip the import,
|
||||||
|
// it is only false if there is an error while importing.
|
||||||
|
p.Resolved = true
|
||||||
|
|
||||||
|
name := p.cleanName()
|
||||||
|
if name == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop resolving imports if we've reached max depth or found a duplicate.
|
||||||
|
var importMode build.ImportMode
|
||||||
|
if p.Tree.hasSeenImport(name) || p.Tree.isAtMaxDepth(p) {
|
||||||
|
importMode = build.FindOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg, err := i.Import(name, p.SrcDir, importMode)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Check the error type?
|
||||||
|
p.Resolved = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Raw = pkg
|
||||||
|
|
||||||
|
// Update the name with the fully qualified import path.
|
||||||
|
p.Name = pkg.ImportPath
|
||||||
|
|
||||||
|
// If this is an internal dependency, we may need to skip it.
|
||||||
|
if pkg.Goroot {
|
||||||
|
p.Internal = true
|
||||||
|
if !p.Tree.shouldResolveInternal(p) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//first we set the regular dependencies, then we add the test dependencies
|
||||||
|
//sharing the same set. This allows us to mark all test-only deps linearly
|
||||||
|
unique := make(map[string]struct{})
|
||||||
|
p.setDeps(i, pkg.Imports, pkg.Dir, unique, false)
|
||||||
|
if p.Tree.ResolveTest {
|
||||||
|
p.setDeps(i, append(pkg.TestImports, pkg.XTestImports...), pkg.Dir, unique, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setDeps takes a slice of import paths and the source directory they are relative to,
|
||||||
|
// and creates the Deps of the Pkg. Each dependency is also further resolved prior to being added
|
||||||
|
// to the Pkg.
|
||||||
|
func (p *Pkg) setDeps(i Importer, imports []string, srcDir string, unique map[string]struct{}, isTest bool) {
|
||||||
|
for _, imp := range imports {
|
||||||
|
// Mostly for testing files where cyclic imports are allowed.
|
||||||
|
if imp == p.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip duplicates.
|
||||||
|
if _, ok := unique[imp]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unique[imp] = struct{}{}
|
||||||
|
|
||||||
|
p.addDep(i, imp, srcDir, isTest)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(byInternalAndName(p.Deps))
|
||||||
|
}
|
||||||
|
|
||||||
|
// addDep creates a Pkg and it's dependencies from an imported package name.
|
||||||
|
func (p *Pkg) addDep(i Importer, name string, srcDir string, isTest bool) {
|
||||||
|
dep := Pkg{
|
||||||
|
Name: name,
|
||||||
|
SrcDir: srcDir,
|
||||||
|
Tree: p.Tree,
|
||||||
|
Parent: p,
|
||||||
|
Test: isTest,
|
||||||
|
}
|
||||||
|
dep.Resolve(i)
|
||||||
|
|
||||||
|
p.Deps = append(p.Deps, dep)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isParent goes recursively up the chain of Pkgs to determine if the name provided is ever a
|
||||||
|
// parent of the current Pkg.
|
||||||
|
func (p *Pkg) isParent(name string) bool {
|
||||||
|
if p.Parent == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Parent.Name == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Parent.isParent(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// depth returns the depth of the Pkg within the Tree.
|
||||||
|
func (p *Pkg) depth() int {
|
||||||
|
if p.Parent == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Parent.depth() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanName returns a cleaned version of the Pkg name used for resolving dependencies.
|
||||||
|
//
|
||||||
|
// If an empty string is returned, dependencies should not be resolved.
|
||||||
|
func (p *Pkg) cleanName() string {
|
||||||
|
name := p.Name
|
||||||
|
|
||||||
|
// C 'package' cannot be resolved.
|
||||||
|
if name == "C" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal golang_org/* packages must be prefixed with vendor/
|
||||||
|
//
|
||||||
|
// Thanks to @davecheney for this:
|
||||||
|
// https://github.com/davecheney/graphpkg/blob/master/main.go#L46
|
||||||
|
if strings.HasPrefix(name, "golang_org") {
|
||||||
|
name = path.Join("vendor", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the Pkg containing the Pkg name and status.
|
||||||
|
func (p *Pkg) String() string {
|
||||||
|
b := bytes.NewBufferString(p.Name)
|
||||||
|
|
||||||
|
if !p.Resolved {
|
||||||
|
b.Write([]byte(" (unresolved)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// byInternalAndName ensures a slice of Pkgs are sorted such that the internal stdlib
|
||||||
|
// packages are always above external packages (ie. github.com/whatever).
|
||||||
|
type byInternalAndName []Pkg
|
||||||
|
|
||||||
|
func (b byInternalAndName) Len() int {
|
||||||
|
return len(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b byInternalAndName) Swap(i, j int) {
|
||||||
|
b[i], b[j] = b[j], b[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b byInternalAndName) Less(i, j int) bool {
|
||||||
|
if b[i].Internal && !b[j].Internal {
|
||||||
|
return true
|
||||||
|
} else if !b[i].Internal && b[j].Internal {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return b[i].Name < b[j].Name
|
||||||
|
}
|
5
vendor/github.com/PuerkitoBio/purell/.gitignore
generated
vendored
Normal file
5
vendor/github.com/PuerkitoBio/purell/.gitignore
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
*.sublime-*
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
tags
|
12
vendor/github.com/PuerkitoBio/purell/.travis.yml
generated
vendored
Normal file
12
vendor/github.com/PuerkitoBio/purell/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.4.x
|
||||||
|
- 1.5.x
|
||||||
|
- 1.6.x
|
||||||
|
- 1.7.x
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- "1.10.x"
|
||||||
|
- "1.11.x"
|
||||||
|
- tip
|
12
vendor/github.com/PuerkitoBio/purell/LICENSE
generated
vendored
Normal file
12
vendor/github.com/PuerkitoBio/purell/LICENSE
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Copyright (c) 2012, Martin Angers
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
188
vendor/github.com/PuerkitoBio/purell/README.md
generated
vendored
Normal file
188
vendor/github.com/PuerkitoBio/purell/README.md
generated
vendored
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
# Purell
|
||||||
|
|
||||||
|
Purell is a tiny Go library to normalize URLs. It returns a pure URL. Pure-ell. Sanitizer and all. Yeah, I know...
|
||||||
|
|
||||||
|
Based on the [wikipedia paper][wiki] and the [RFC 3986 document][rfc].
|
||||||
|
|
||||||
|
[](http://travis-ci.org/PuerkitoBio/purell)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
`go get github.com/PuerkitoBio/purell`
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
* **v1.1.1** : Fix failing test due to Go1.12 changes (thanks to @ianlancetaylor).
|
||||||
|
* **2016-11-14 (v1.1.0)** : IDN: Conform to RFC 5895: Fold character width (thanks to @beeker1121).
|
||||||
|
* **2016-07-27 (v1.0.0)** : Normalize IDN to ASCII (thanks to @zenovich).
|
||||||
|
* **2015-02-08** : Add fix for relative paths issue ([PR #5][pr5]) and add fix for unnecessary encoding of reserved characters ([see issue #7][iss7]).
|
||||||
|
* **v0.2.0** : Add benchmarks, Attempt IDN support.
|
||||||
|
* **v0.1.0** : Initial release.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
From `example_test.go` (note that in your code, you would import "github.com/PuerkitoBio/purell", and would prefix references to its methods and constants with "purell."):
|
||||||
|
|
||||||
|
```go
|
||||||
|
package purell
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleNormalizeURLString() {
|
||||||
|
if normalized, err := NormalizeURLString("hTTp://someWEBsite.com:80/Amazing%3f/url/",
|
||||||
|
FlagLowercaseScheme|FlagLowercaseHost|FlagUppercaseEscapes); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
fmt.Print(normalized)
|
||||||
|
}
|
||||||
|
// Output: http://somewebsite.com:80/Amazing%3F/url/
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleMustNormalizeURLString() {
|
||||||
|
normalized := MustNormalizeURLString("hTTpS://someWEBsite.com:443/Amazing%fa/url/",
|
||||||
|
FlagsUnsafeGreedy)
|
||||||
|
fmt.Print(normalized)
|
||||||
|
|
||||||
|
// Output: http://somewebsite.com/Amazing%FA/url
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNormalizeURL() {
|
||||||
|
if u, err := url.Parse("Http://SomeUrl.com:8080/a/b/.././c///g?c=3&a=1&b=9&c=0#target"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
normalized := NormalizeURL(u, FlagsUsuallySafeGreedy|FlagRemoveDuplicateSlashes|FlagRemoveFragment)
|
||||||
|
fmt.Print(normalized)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output: http://someurl.com:8080/a/c/g?c=3&a=1&b=9&c=0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
As seen in the examples above, purell offers three methods, `NormalizeURLString(string, NormalizationFlags) (string, error)`, `MustNormalizeURLString(string, NormalizationFlags) (string)` and `NormalizeURL(*url.URL, NormalizationFlags) (string)`. They all normalize the provided URL based on the specified flags. Here are the available flags:
|
||||||
|
|
||||||
|
```go
|
||||||
|
const (
|
||||||
|
// Safe normalizations
|
||||||
|
FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1
|
||||||
|
FlagLowercaseHost // http://HOST -> http://host
|
||||||
|
FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF
|
||||||
|
FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA
|
||||||
|
FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$
|
||||||
|
FlagRemoveDefaultPort // http://host:80 -> http://host
|
||||||
|
FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path
|
||||||
|
|
||||||
|
// Usually safe normalizations
|
||||||
|
FlagRemoveTrailingSlash // http://host/path/ -> http://host/path
|
||||||
|
FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags)
|
||||||
|
FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c
|
||||||
|
|
||||||
|
// Unsafe normalizations
|
||||||
|
FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/
|
||||||
|
FlagRemoveFragment // http://host/path#fragment -> http://host/path
|
||||||
|
FlagForceHTTP // https://host -> http://host
|
||||||
|
FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b
|
||||||
|
FlagRemoveWWW // http://www.host/ -> http://host/
|
||||||
|
FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags)
|
||||||
|
FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3
|
||||||
|
|
||||||
|
// Normalizations not in the wikipedia article, required to cover tests cases
|
||||||
|
// submitted by jehiah
|
||||||
|
FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147
|
||||||
|
FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147
|
||||||
|
FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147
|
||||||
|
FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path
|
||||||
|
FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path
|
||||||
|
|
||||||
|
// Convenience set of safe normalizations
|
||||||
|
FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator
|
||||||
|
|
||||||
|
// For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags,
|
||||||
|
// while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix".
|
||||||
|
|
||||||
|
// Convenience set of usually safe normalizations (includes FlagsSafe)
|
||||||
|
FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments
|
||||||
|
FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments
|
||||||
|
|
||||||
|
// Convenience set of unsafe normalizations (includes FlagsUsuallySafe)
|
||||||
|
FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery
|
||||||
|
FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery
|
||||||
|
|
||||||
|
// Convenience set of all available flags
|
||||||
|
FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
|
||||||
|
FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
For convenience, the set of flags `FlagsSafe`, `FlagsUsuallySafe[Greedy|NonGreedy]`, `FlagsUnsafe[Greedy|NonGreedy]` and `FlagsAll[Greedy|NonGreedy]` are provided for the similarly grouped normalizations on [wikipedia's URL normalization page][wiki]. You can add (using the bitwise OR `|` operator) or remove (using the bitwise AND NOT `&^` operator) individual flags from the sets if required, to build your own custom set.
|
||||||
|
|
||||||
|
The [full godoc reference is available on gopkgdoc][godoc].
|
||||||
|
|
||||||
|
Some things to note:
|
||||||
|
|
||||||
|
* `FlagDecodeUnnecessaryEscapes`, `FlagEncodeNecessaryEscapes`, `FlagUppercaseEscapes` and `FlagRemoveEmptyQuerySeparator` are always implicitly set, because internally, the URL string is parsed as an URL object, which automatically decodes unnecessary escapes, uppercases and encodes necessary ones, and removes empty query separators (an unnecessary `?` at the end of the url). So this operation cannot **not** be done. For this reason, `FlagRemoveEmptyQuerySeparator` (as well as the other three) has been included in the `FlagsSafe` convenience set, instead of `FlagsUnsafe`, where Wikipedia puts it.
|
||||||
|
|
||||||
|
* The `FlagDecodeUnnecessaryEscapes` decodes the following escapes (*from -> to*):
|
||||||
|
- %24 -> $
|
||||||
|
- %26 -> &
|
||||||
|
- %2B-%3B -> +,-./0123456789:;
|
||||||
|
- %3D -> =
|
||||||
|
- %40-%5A -> @ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||||
|
- %5F -> _
|
||||||
|
- %61-%7A -> abcdefghijklmnopqrstuvwxyz
|
||||||
|
- %7E -> ~
|
||||||
|
|
||||||
|
|
||||||
|
* When the `NormalizeURL` function is used (passing an URL object), this source URL object is modified (that is, after the call, the URL object will be modified to reflect the normalization).
|
||||||
|
|
||||||
|
* The *replace IP with domain name* normalization (`http://208.77.188.166/ → http://www.example.com/`) is obviously not possible for a library without making some network requests. This is not implemented in purell.
|
||||||
|
|
||||||
|
* The *remove unused query string parameters* and *remove default query parameters* are also not implemented, since this is a very case-specific normalization, and it is quite trivial to do with an URL object.
|
||||||
|
|
||||||
|
### Safe vs Usually Safe vs Unsafe
|
||||||
|
|
||||||
|
Purell allows you to control the level of risk you take while normalizing an URL. You can aggressively normalize, play it totally safe, or anything in between.
|
||||||
|
|
||||||
|
Consider the following URL:
|
||||||
|
|
||||||
|
`HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid`
|
||||||
|
|
||||||
|
Normalizing with the `FlagsSafe` gives:
|
||||||
|
|
||||||
|
`https://www.root.com/toto/tE%1F///a/./b/../c/?z=3&w=2&a=4&w=1#invalid`
|
||||||
|
|
||||||
|
With the `FlagsUsuallySafeGreedy`:
|
||||||
|
|
||||||
|
`https://www.root.com/toto/tE%1F///a/c?z=3&w=2&a=4&w=1#invalid`
|
||||||
|
|
||||||
|
And with `FlagsUnsafeGreedy`:
|
||||||
|
|
||||||
|
`http://root.com/toto/tE%1F/a/c?a=4&w=1&w=2&z=3`
|
||||||
|
|
||||||
|
## TODOs
|
||||||
|
|
||||||
|
* Add a class/default instance to allow specifying custom directory index names? At the moment, removing directory index removes `(^|/)((?:default|index)\.\w{1,4})$`.
|
||||||
|
|
||||||
|
## Thanks / Contributions
|
||||||
|
|
||||||
|
@rogpeppe
|
||||||
|
@jehiah
|
||||||
|
@opennota
|
||||||
|
@pchristopher1275
|
||||||
|
@zenovich
|
||||||
|
@beeker1121
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The [BSD 3-Clause license][bsd].
|
||||||
|
|
||||||
|
[bsd]: http://opensource.org/licenses/BSD-3-Clause
|
||||||
|
[wiki]: http://en.wikipedia.org/wiki/URL_normalization
|
||||||
|
[rfc]: http://tools.ietf.org/html/rfc3986#section-6
|
||||||
|
[godoc]: http://go.pkgdoc.org/github.com/PuerkitoBio/purell
|
||||||
|
[pr5]: https://github.com/PuerkitoBio/purell/pull/5
|
||||||
|
[iss7]: https://github.com/PuerkitoBio/purell/issues/7
|
379
vendor/github.com/PuerkitoBio/purell/purell.go
generated
vendored
Normal file
379
vendor/github.com/PuerkitoBio/purell/purell.go
generated
vendored
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
/*
|
||||||
|
Package purell offers URL normalization as described on the wikipedia page:
|
||||||
|
http://en.wikipedia.org/wiki/URL_normalization
|
||||||
|
*/
|
||||||
|
package purell
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/urlesc"
|
||||||
|
"golang.org/x/net/idna"
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
|
"golang.org/x/text/width"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A set of normalization flags determines how a URL will
|
||||||
|
// be normalized.
|
||||||
|
type NormalizationFlags uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Safe normalizations
|
||||||
|
FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1
|
||||||
|
FlagLowercaseHost // http://HOST -> http://host
|
||||||
|
FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF
|
||||||
|
FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA
|
||||||
|
FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$
|
||||||
|
FlagRemoveDefaultPort // http://host:80 -> http://host
|
||||||
|
FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path
|
||||||
|
|
||||||
|
// Usually safe normalizations
|
||||||
|
FlagRemoveTrailingSlash // http://host/path/ -> http://host/path
|
||||||
|
FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags)
|
||||||
|
FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c
|
||||||
|
|
||||||
|
// Unsafe normalizations
|
||||||
|
FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/
|
||||||
|
FlagRemoveFragment // http://host/path#fragment -> http://host/path
|
||||||
|
FlagForceHTTP // https://host -> http://host
|
||||||
|
FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b
|
||||||
|
FlagRemoveWWW // http://www.host/ -> http://host/
|
||||||
|
FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags)
|
||||||
|
FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3
|
||||||
|
|
||||||
|
// Normalizations not in the wikipedia article, required to cover tests cases
|
||||||
|
// submitted by jehiah
|
||||||
|
FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147
|
||||||
|
FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147
|
||||||
|
FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147
|
||||||
|
FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path
|
||||||
|
FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path
|
||||||
|
|
||||||
|
// Convenience set of safe normalizations
|
||||||
|
FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator
|
||||||
|
|
||||||
|
// For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags,
|
||||||
|
// while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix".
|
||||||
|
|
||||||
|
// Convenience set of usually safe normalizations (includes FlagsSafe)
|
||||||
|
FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments
|
||||||
|
FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments
|
||||||
|
|
||||||
|
// Convenience set of unsafe normalizations (includes FlagsUsuallySafe)
|
||||||
|
FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery
|
||||||
|
FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery
|
||||||
|
|
||||||
|
// Convenience set of all available flags
|
||||||
|
FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
|
||||||
|
FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultHttpPort = ":80"
|
||||||
|
defaultHttpsPort = ":443"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Regular expressions used by the normalizations
|
||||||
|
var rxPort = regexp.MustCompile(`(:\d+)/?$`)
|
||||||
|
var rxDirIndex = regexp.MustCompile(`(^|/)((?:default|index)\.\w{1,4})$`)
|
||||||
|
var rxDupSlashes = regexp.MustCompile(`/{2,}`)
|
||||||
|
var rxDWORDHost = regexp.MustCompile(`^(\d+)((?:\.+)?(?:\:\d*)?)$`)
|
||||||
|
var rxOctalHost = regexp.MustCompile(`^(0\d*)\.(0\d*)\.(0\d*)\.(0\d*)((?:\.+)?(?:\:\d*)?)$`)
|
||||||
|
var rxHexHost = regexp.MustCompile(`^0x([0-9A-Fa-f]+)((?:\.+)?(?:\:\d*)?)$`)
|
||||||
|
var rxHostDots = regexp.MustCompile(`^(.+?)(:\d+)?$`)
|
||||||
|
var rxEmptyPort = regexp.MustCompile(`:+$`)
|
||||||
|
|
||||||
|
// Map of flags to implementation function.
|
||||||
|
// FlagDecodeUnnecessaryEscapes has no action, since it is done automatically
|
||||||
|
// by parsing the string as an URL. Same for FlagUppercaseEscapes and FlagRemoveEmptyQuerySeparator.
|
||||||
|
|
||||||
|
// Since maps have undefined traversing order, make a slice of ordered keys
|
||||||
|
var flagsOrder = []NormalizationFlags{
|
||||||
|
FlagLowercaseScheme,
|
||||||
|
FlagLowercaseHost,
|
||||||
|
FlagRemoveDefaultPort,
|
||||||
|
FlagRemoveDirectoryIndex,
|
||||||
|
FlagRemoveDotSegments,
|
||||||
|
FlagRemoveFragment,
|
||||||
|
FlagForceHTTP, // Must be after remove default port (because https=443/http=80)
|
||||||
|
FlagRemoveDuplicateSlashes,
|
||||||
|
FlagRemoveWWW,
|
||||||
|
FlagAddWWW,
|
||||||
|
FlagSortQuery,
|
||||||
|
FlagDecodeDWORDHost,
|
||||||
|
FlagDecodeOctalHost,
|
||||||
|
FlagDecodeHexHost,
|
||||||
|
FlagRemoveUnnecessaryHostDots,
|
||||||
|
FlagRemoveEmptyPortSeparator,
|
||||||
|
FlagRemoveTrailingSlash, // These two (add/remove trailing slash) must be last
|
||||||
|
FlagAddTrailingSlash,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... and then the map, where order is unimportant
|
||||||
|
var flags = map[NormalizationFlags]func(*url.URL){
|
||||||
|
FlagLowercaseScheme: lowercaseScheme,
|
||||||
|
FlagLowercaseHost: lowercaseHost,
|
||||||
|
FlagRemoveDefaultPort: removeDefaultPort,
|
||||||
|
FlagRemoveDirectoryIndex: removeDirectoryIndex,
|
||||||
|
FlagRemoveDotSegments: removeDotSegments,
|
||||||
|
FlagRemoveFragment: removeFragment,
|
||||||
|
FlagForceHTTP: forceHTTP,
|
||||||
|
FlagRemoveDuplicateSlashes: removeDuplicateSlashes,
|
||||||
|
FlagRemoveWWW: removeWWW,
|
||||||
|
FlagAddWWW: addWWW,
|
||||||
|
FlagSortQuery: sortQuery,
|
||||||
|
FlagDecodeDWORDHost: decodeDWORDHost,
|
||||||
|
FlagDecodeOctalHost: decodeOctalHost,
|
||||||
|
FlagDecodeHexHost: decodeHexHost,
|
||||||
|
FlagRemoveUnnecessaryHostDots: removeUnncessaryHostDots,
|
||||||
|
FlagRemoveEmptyPortSeparator: removeEmptyPortSeparator,
|
||||||
|
FlagRemoveTrailingSlash: removeTrailingSlash,
|
||||||
|
FlagAddTrailingSlash: addTrailingSlash,
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustNormalizeURLString returns the normalized string, and panics if an error occurs.
|
||||||
|
// It takes an URL string as input, as well as the normalization flags.
|
||||||
|
func MustNormalizeURLString(u string, f NormalizationFlags) string {
|
||||||
|
result, e := NormalizeURLString(u, f)
|
||||||
|
if e != nil {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizeURLString returns the normalized string, or an error if it can't be parsed into an URL object.
|
||||||
|
// It takes an URL string as input, as well as the normalization flags.
|
||||||
|
func NormalizeURLString(u string, f NormalizationFlags) (string, error) {
|
||||||
|
parsed, err := url.Parse(u)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f&FlagLowercaseHost == FlagLowercaseHost {
|
||||||
|
parsed.Host = strings.ToLower(parsed.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The idna package doesn't fully conform to RFC 5895
|
||||||
|
// (https://tools.ietf.org/html/rfc5895), so we do it here.
|
||||||
|
// Taken from Go 1.8 cycle source, courtesy of bradfitz.
|
||||||
|
// TODO: Remove when (if?) idna package conforms to RFC 5895.
|
||||||
|
parsed.Host = width.Fold.String(parsed.Host)
|
||||||
|
parsed.Host = norm.NFC.String(parsed.Host)
|
||||||
|
if parsed.Host, err = idna.ToASCII(parsed.Host); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NormalizeURL(parsed, f), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizeURL returns the normalized string.
|
||||||
|
// It takes a parsed URL object as input, as well as the normalization flags.
|
||||||
|
func NormalizeURL(u *url.URL, f NormalizationFlags) string {
|
||||||
|
for _, k := range flagsOrder {
|
||||||
|
if f&k == k {
|
||||||
|
flags[k](u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return urlesc.Escape(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lowercaseScheme(u *url.URL) {
|
||||||
|
if len(u.Scheme) > 0 {
|
||||||
|
u.Scheme = strings.ToLower(u.Scheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lowercaseHost(u *url.URL) {
|
||||||
|
if len(u.Host) > 0 {
|
||||||
|
u.Host = strings.ToLower(u.Host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeDefaultPort(u *url.URL) {
|
||||||
|
if len(u.Host) > 0 {
|
||||||
|
scheme := strings.ToLower(u.Scheme)
|
||||||
|
u.Host = rxPort.ReplaceAllStringFunc(u.Host, func(val string) string {
|
||||||
|
if (scheme == "http" && val == defaultHttpPort) || (scheme == "https" && val == defaultHttpsPort) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeTrailingSlash(u *url.URL) {
|
||||||
|
if l := len(u.Path); l > 0 {
|
||||||
|
if strings.HasSuffix(u.Path, "/") {
|
||||||
|
u.Path = u.Path[:l-1]
|
||||||
|
}
|
||||||
|
} else if l = len(u.Host); l > 0 {
|
||||||
|
if strings.HasSuffix(u.Host, "/") {
|
||||||
|
u.Host = u.Host[:l-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTrailingSlash(u *url.URL) {
|
||||||
|
if l := len(u.Path); l > 0 {
|
||||||
|
if !strings.HasSuffix(u.Path, "/") {
|
||||||
|
u.Path += "/"
|
||||||
|
}
|
||||||
|
} else if l = len(u.Host); l > 0 {
|
||||||
|
if !strings.HasSuffix(u.Host, "/") {
|
||||||
|
u.Host += "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeDotSegments(u *url.URL) {
|
||||||
|
if len(u.Path) > 0 {
|
||||||
|
var dotFree []string
|
||||||
|
var lastIsDot bool
|
||||||
|
|
||||||
|
sections := strings.Split(u.Path, "/")
|
||||||
|
for _, s := range sections {
|
||||||
|
if s == ".." {
|
||||||
|
if len(dotFree) > 0 {
|
||||||
|
dotFree = dotFree[:len(dotFree)-1]
|
||||||
|
}
|
||||||
|
} else if s != "." {
|
||||||
|
dotFree = append(dotFree, s)
|
||||||
|
}
|
||||||
|
lastIsDot = (s == "." || s == "..")
|
||||||
|
}
|
||||||
|
// Special case if host does not end with / and new path does not begin with /
|
||||||
|
u.Path = strings.Join(dotFree, "/")
|
||||||
|
if u.Host != "" && !strings.HasSuffix(u.Host, "/") && !strings.HasPrefix(u.Path, "/") {
|
||||||
|
u.Path = "/" + u.Path
|
||||||
|
}
|
||||||
|
// Special case if the last segment was a dot, make sure the path ends with a slash
|
||||||
|
if lastIsDot && !strings.HasSuffix(u.Path, "/") {
|
||||||
|
u.Path += "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeDirectoryIndex(u *url.URL) {
|
||||||
|
if len(u.Path) > 0 {
|
||||||
|
u.Path = rxDirIndex.ReplaceAllString(u.Path, "$1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFragment(u *url.URL) {
|
||||||
|
u.Fragment = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func forceHTTP(u *url.URL) {
|
||||||
|
if strings.ToLower(u.Scheme) == "https" {
|
||||||
|
u.Scheme = "http"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeDuplicateSlashes(u *url.URL) {
|
||||||
|
if len(u.Path) > 0 {
|
||||||
|
u.Path = rxDupSlashes.ReplaceAllString(u.Path, "/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeWWW(u *url.URL) {
|
||||||
|
if len(u.Host) > 0 && strings.HasPrefix(strings.ToLower(u.Host), "www.") {
|
||||||
|
u.Host = u.Host[4:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addWWW(u *url.URL) {
|
||||||
|
if len(u.Host) > 0 && !strings.HasPrefix(strings.ToLower(u.Host), "www.") {
|
||||||
|
u.Host = "www." + u.Host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortQuery(u *url.URL) {
|
||||||
|
q := u.Query()
|
||||||
|
|
||||||
|
if len(q) > 0 {
|
||||||
|
arKeys := make([]string, len(q))
|
||||||
|
i := 0
|
||||||
|
for k := range q {
|
||||||
|
arKeys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Strings(arKeys)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
for _, k := range arKeys {
|
||||||
|
sort.Strings(q[k])
|
||||||
|
for _, v := range q[k] {
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
buf.WriteRune('&')
|
||||||
|
}
|
||||||
|
buf.WriteString(fmt.Sprintf("%s=%s", k, urlesc.QueryEscape(v)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild the raw query string
|
||||||
|
u.RawQuery = buf.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeDWORDHost(u *url.URL) {
|
||||||
|
if len(u.Host) > 0 {
|
||||||
|
if matches := rxDWORDHost.FindStringSubmatch(u.Host); len(matches) > 2 {
|
||||||
|
var parts [4]int64
|
||||||
|
|
||||||
|
dword, _ := strconv.ParseInt(matches[1], 10, 0)
|
||||||
|
for i, shift := range []uint{24, 16, 8, 0} {
|
||||||
|
parts[i] = dword >> shift & 0xFF
|
||||||
|
}
|
||||||
|
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeOctalHost(u *url.URL) {
|
||||||
|
if len(u.Host) > 0 {
|
||||||
|
if matches := rxOctalHost.FindStringSubmatch(u.Host); len(matches) > 5 {
|
||||||
|
var parts [4]int64
|
||||||
|
|
||||||
|
for i := 1; i <= 4; i++ {
|
||||||
|
parts[i-1], _ = strconv.ParseInt(matches[i], 8, 0)
|
||||||
|
}
|
||||||
|
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[5])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeHexHost(u *url.URL) {
|
||||||
|
if len(u.Host) > 0 {
|
||||||
|
if matches := rxHexHost.FindStringSubmatch(u.Host); len(matches) > 2 {
|
||||||
|
// Conversion is safe because of regex validation
|
||||||
|
parsed, _ := strconv.ParseInt(matches[1], 16, 0)
|
||||||
|
// Set host as DWORD (base 10) encoded host
|
||||||
|
u.Host = fmt.Sprintf("%d%s", parsed, matches[2])
|
||||||
|
// The rest is the same as decoding a DWORD host
|
||||||
|
decodeDWORDHost(u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeUnncessaryHostDots(u *url.URL) {
|
||||||
|
if len(u.Host) > 0 {
|
||||||
|
if matches := rxHostDots.FindStringSubmatch(u.Host); len(matches) > 1 {
|
||||||
|
// Trim the leading and trailing dots
|
||||||
|
u.Host = strings.Trim(matches[1], ".")
|
||||||
|
if len(matches) > 2 {
|
||||||
|
u.Host += matches[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeEmptyPortSeparator(u *url.URL) {
|
||||||
|
if len(u.Host) > 0 {
|
||||||
|
u.Host = rxEmptyPort.ReplaceAllString(u.Host, "")
|
||||||
|
}
|
||||||
|
}
|
15
vendor/github.com/PuerkitoBio/urlesc/.travis.yml
generated
vendored
Normal file
15
vendor/github.com/PuerkitoBio/urlesc/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.4.x
|
||||||
|
- 1.5.x
|
||||||
|
- 1.6.x
|
||||||
|
- 1.7.x
|
||||||
|
- 1.8.x
|
||||||
|
- tip
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go build .
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v
|
27
vendor/github.com/PuerkitoBio/urlesc/LICENSE
generated
vendored
Normal file
27
vendor/github.com/PuerkitoBio/urlesc/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
16
vendor/github.com/PuerkitoBio/urlesc/README.md
generated
vendored
Normal file
16
vendor/github.com/PuerkitoBio/urlesc/README.md
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
urlesc [](https://travis-ci.org/PuerkitoBio/urlesc) [](http://godoc.org/github.com/PuerkitoBio/urlesc)
|
||||||
|
======
|
||||||
|
|
||||||
|
Package urlesc implements query escaping as per RFC 3986.
|
||||||
|
|
||||||
|
It contains some parts of the net/url package, modified so as to allow
|
||||||
|
some reserved characters incorrectly escaped by net/url (see [issue 5684](https://github.com/golang/go/issues/5684)).
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
go get github.com/PuerkitoBio/urlesc
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Go license (BSD-3-Clause)
|
||||||
|
|
180
vendor/github.com/PuerkitoBio/urlesc/urlesc.go
generated
vendored
Normal file
180
vendor/github.com/PuerkitoBio/urlesc/urlesc.go
generated
vendored
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package urlesc implements query escaping as per RFC 3986.
|
||||||
|
// It contains some parts of the net/url package, modified so as to allow
|
||||||
|
// some reserved characters incorrectly escaped by net/url.
|
||||||
|
// See https://github.com/golang/go/issues/5684
|
||||||
|
package urlesc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type encoding int
|
||||||
|
|
||||||
|
const (
|
||||||
|
encodePath encoding = 1 + iota
|
||||||
|
encodeUserPassword
|
||||||
|
encodeQueryComponent
|
||||||
|
encodeFragment
|
||||||
|
)
|
||||||
|
|
||||||
|
// Return true if the specified character should be escaped when
|
||||||
|
// appearing in a URL string, according to RFC 3986.
|
||||||
|
func shouldEscape(c byte, mode encoding) bool {
|
||||||
|
// §2.3 Unreserved characters (alphanum)
|
||||||
|
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c {
|
||||||
|
case '-', '.', '_', '~': // §2.3 Unreserved characters (mark)
|
||||||
|
return false
|
||||||
|
|
||||||
|
// §2.2 Reserved characters (reserved)
|
||||||
|
case ':', '/', '?', '#', '[', ']', '@', // gen-delims
|
||||||
|
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': // sub-delims
|
||||||
|
// Different sections of the URL allow a few of
|
||||||
|
// the reserved characters to appear unescaped.
|
||||||
|
switch mode {
|
||||||
|
case encodePath: // §3.3
|
||||||
|
// The RFC allows sub-delims and : @.
|
||||||
|
// '/', '[' and ']' can be used to assign meaning to individual path
|
||||||
|
// segments. This package only manipulates the path as a whole,
|
||||||
|
// so we allow those as well. That leaves only ? and # to escape.
|
||||||
|
return c == '?' || c == '#'
|
||||||
|
|
||||||
|
case encodeUserPassword: // §3.2.1
|
||||||
|
// The RFC allows : and sub-delims in
|
||||||
|
// userinfo. The parsing of userinfo treats ':' as special so we must escape
|
||||||
|
// all the gen-delims.
|
||||||
|
return c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']' || c == '@'
|
||||||
|
|
||||||
|
case encodeQueryComponent: // §3.4
|
||||||
|
// The RFC allows / and ?.
|
||||||
|
return c != '/' && c != '?'
|
||||||
|
|
||||||
|
case encodeFragment: // §4.1
|
||||||
|
// The RFC text is silent but the grammar allows
|
||||||
|
// everything, so escape nothing but #
|
||||||
|
return c == '#'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything else must be escaped.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryEscape escapes the string so it can be safely placed
|
||||||
|
// inside a URL query.
|
||||||
|
func QueryEscape(s string) string {
|
||||||
|
return escape(s, encodeQueryComponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func escape(s string, mode encoding) string {
|
||||||
|
spaceCount, hexCount := 0, 0
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if shouldEscape(c, mode) {
|
||||||
|
if c == ' ' && mode == encodeQueryComponent {
|
||||||
|
spaceCount++
|
||||||
|
} else {
|
||||||
|
hexCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if spaceCount == 0 && hexCount == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
t := make([]byte, len(s)+2*hexCount)
|
||||||
|
j := 0
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
switch c := s[i]; {
|
||||||
|
case c == ' ' && mode == encodeQueryComponent:
|
||||||
|
t[j] = '+'
|
||||||
|
j++
|
||||||
|
case shouldEscape(c, mode):
|
||||||
|
t[j] = '%'
|
||||||
|
t[j+1] = "0123456789ABCDEF"[c>>4]
|
||||||
|
t[j+2] = "0123456789ABCDEF"[c&15]
|
||||||
|
j += 3
|
||||||
|
default:
|
||||||
|
t[j] = s[i]
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
var uiReplacer = strings.NewReplacer(
|
||||||
|
"%21", "!",
|
||||||
|
"%27", "'",
|
||||||
|
"%28", "(",
|
||||||
|
"%29", ")",
|
||||||
|
"%2A", "*",
|
||||||
|
)
|
||||||
|
|
||||||
|
// unescapeUserinfo unescapes some characters that need not to be escaped as per RFC3986.
|
||||||
|
func unescapeUserinfo(s string) string {
|
||||||
|
return uiReplacer.Replace(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape reassembles the URL into a valid URL string.
|
||||||
|
// The general form of the result is one of:
|
||||||
|
//
|
||||||
|
// scheme:opaque
|
||||||
|
// scheme://userinfo@host/path?query#fragment
|
||||||
|
//
|
||||||
|
// If u.Opaque is non-empty, String uses the first form;
|
||||||
|
// otherwise it uses the second form.
|
||||||
|
//
|
||||||
|
// In the second form, the following rules apply:
|
||||||
|
// - if u.Scheme is empty, scheme: is omitted.
|
||||||
|
// - if u.User is nil, userinfo@ is omitted.
|
||||||
|
// - if u.Host is empty, host/ is omitted.
|
||||||
|
// - if u.Scheme and u.Host are empty and u.User is nil,
|
||||||
|
// the entire scheme://userinfo@host/ is omitted.
|
||||||
|
// - if u.Host is non-empty and u.Path begins with a /,
|
||||||
|
// the form host/path does not add its own /.
|
||||||
|
// - if u.RawQuery is empty, ?query is omitted.
|
||||||
|
// - if u.Fragment is empty, #fragment is omitted.
|
||||||
|
func Escape(u *url.URL) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if u.Scheme != "" {
|
||||||
|
buf.WriteString(u.Scheme)
|
||||||
|
buf.WriteByte(':')
|
||||||
|
}
|
||||||
|
if u.Opaque != "" {
|
||||||
|
buf.WriteString(u.Opaque)
|
||||||
|
} else {
|
||||||
|
if u.Scheme != "" || u.Host != "" || u.User != nil {
|
||||||
|
buf.WriteString("//")
|
||||||
|
if ui := u.User; ui != nil {
|
||||||
|
buf.WriteString(unescapeUserinfo(ui.String()))
|
||||||
|
buf.WriteByte('@')
|
||||||
|
}
|
||||||
|
if h := u.Host; h != "" {
|
||||||
|
buf.WriteString(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if u.Path != "" && u.Path[0] != '/' && u.Host != "" {
|
||||||
|
buf.WriteByte('/')
|
||||||
|
}
|
||||||
|
buf.WriteString(escape(u.Path, encodePath))
|
||||||
|
}
|
||||||
|
if u.RawQuery != "" {
|
||||||
|
buf.WriteByte('?')
|
||||||
|
buf.WriteString(u.RawQuery)
|
||||||
|
}
|
||||||
|
if u.Fragment != "" {
|
||||||
|
buf.WriteByte('#')
|
||||||
|
buf.WriteString(escape(u.Fragment, encodeFragment))
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
3
vendor/github.com/gin-contrib/sessions/.gitignore
generated
vendored
Normal file
3
vendor/github.com/gin-contrib/sessions/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
coverage.out
|
||||||
|
vendor/*
|
||||||
|
!/vendor/vendor.json
|
21
vendor/github.com/gin-contrib/sessions/LICENSE
generated
vendored
Normal file
21
vendor/github.com/gin-contrib/sessions/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 Gin-Gonic
|
||||||
|
|
||||||
|
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.
|
367
vendor/github.com/gin-contrib/sessions/README.md
generated
vendored
Normal file
367
vendor/github.com/gin-contrib/sessions/README.md
generated
vendored
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
# sessions
|
||||||
|
|
||||||
|
[](https://github.com/gin-contrib/sessions/actions/workflows/lint.yml)
|
||||||
|
[](https://github.com/gin-contrib/sessions/actions/workflows/testing.yml)
|
||||||
|
[](https://codecov.io/gh/gin-contrib/sessions)
|
||||||
|
[](https://goreportcard.com/report/github.com/gin-contrib/sessions)
|
||||||
|
[](https://godoc.org/github.com/gin-contrib/sessions)
|
||||||
|
[](https://gitter.im/gin-gonic/gin)
|
||||||
|
|
||||||
|
Gin middleware for session management with multi-backend support:
|
||||||
|
|
||||||
|
- [cookie-based](#cookie-based)
|
||||||
|
- [Redis](#redis)
|
||||||
|
- [memcached](#memcached)
|
||||||
|
- [MongoDB](#mongodb)
|
||||||
|
- [memstore](#memstore)
|
||||||
|
- [PostgreSQL](#postgresql)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Start using it
|
||||||
|
|
||||||
|
Download and install it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/gin-contrib/sessions
|
||||||
|
```
|
||||||
|
|
||||||
|
Import it in your code:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/gin-contrib/sessions"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Examples
|
||||||
|
|
||||||
|
### single session
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
store := cookie.NewStore([]byte("secret"))
|
||||||
|
r.Use(sessions.Sessions("mysession", store))
|
||||||
|
|
||||||
|
r.GET("/hello", func(c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
|
||||||
|
if session.Get("hello") != "world" {
|
||||||
|
session.Set("hello", "world")
|
||||||
|
session.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{"hello": session.Get("hello")})
|
||||||
|
})
|
||||||
|
r.Run(":8000")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### multiple sessions
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
store := cookie.NewStore([]byte("secret"))
|
||||||
|
sessionNames := []string{"a", "b"}
|
||||||
|
r.Use(sessions.SessionsMany(sessionNames, store))
|
||||||
|
|
||||||
|
r.GET("/hello", func(c *gin.Context) {
|
||||||
|
sessionA := sessions.DefaultMany(c, "a")
|
||||||
|
sessionB := sessions.DefaultMany(c, "b")
|
||||||
|
|
||||||
|
if sessionA.Get("hello") != "world!" {
|
||||||
|
sessionA.Set("hello", "world!")
|
||||||
|
sessionA.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
if sessionB.Get("hello") != "world?" {
|
||||||
|
sessionB.Set("hello", "world?")
|
||||||
|
sessionB.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"a": sessionA.Get("hello"),
|
||||||
|
"b": sessionB.Get("hello"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
r.Run(":8000")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backend Examples
|
||||||
|
|
||||||
|
### cookie-based
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
store := cookie.NewStore([]byte("secret"))
|
||||||
|
r.Use(sessions.Sessions("mysession", store))
|
||||||
|
|
||||||
|
r.GET("/incr", func(c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
var count int
|
||||||
|
v := session.Get("count")
|
||||||
|
if v == nil {
|
||||||
|
count = 0
|
||||||
|
} else {
|
||||||
|
count = v.(int)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
session.Set("count", count)
|
||||||
|
session.Save()
|
||||||
|
c.JSON(200, gin.H{"count": count})
|
||||||
|
})
|
||||||
|
r.Run(":8000")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Redis
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/redis"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))
|
||||||
|
r.Use(sessions.Sessions("mysession", store))
|
||||||
|
|
||||||
|
r.GET("/incr", func(c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
var count int
|
||||||
|
v := session.Get("count")
|
||||||
|
if v == nil {
|
||||||
|
count = 0
|
||||||
|
} else {
|
||||||
|
count = v.(int)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
session.Set("count", count)
|
||||||
|
session.Save()
|
||||||
|
c.JSON(200, gin.H{"count": count})
|
||||||
|
})
|
||||||
|
r.Run(":8000")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memcached
|
||||||
|
|
||||||
|
#### ASCII Protocol
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bradfitz/gomemcache/memcache"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/memcached"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
store := memcached.NewStore(memcache.New("localhost:11211"), "", []byte("secret"))
|
||||||
|
r.Use(sessions.Sessions("mysession", store))
|
||||||
|
|
||||||
|
r.GET("/incr", func(c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
var count int
|
||||||
|
v := session.Get("count")
|
||||||
|
if v == nil {
|
||||||
|
count = 0
|
||||||
|
} else {
|
||||||
|
count = v.(int)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
session.Set("count", count)
|
||||||
|
session.Save()
|
||||||
|
c.JSON(200, gin.H{"count": count})
|
||||||
|
})
|
||||||
|
r.Run(":8000")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Binary protocol (with optional SASL authentication)
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/memcached"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/memcachier/mc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
client := mc.NewMC("localhost:11211", "username", "password")
|
||||||
|
store := memcached.NewMemcacheStore(client, "", []byte("secret"))
|
||||||
|
r.Use(sessions.Sessions("mysession", store))
|
||||||
|
|
||||||
|
r.GET("/incr", func(c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
var count int
|
||||||
|
v := session.Get("count")
|
||||||
|
if v == nil {
|
||||||
|
count = 0
|
||||||
|
} else {
|
||||||
|
count = v.(int)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
session.Set("count", count)
|
||||||
|
session.Save()
|
||||||
|
c.JSON(200, gin.H{"count": count})
|
||||||
|
})
|
||||||
|
r.Run(":8000")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### MongoDB
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/mongo"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/globalsign/mgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
session, err := mgo.Dial("localhost:27017/test")
|
||||||
|
if err != nil {
|
||||||
|
// handle err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := session.DB("").C("sessions")
|
||||||
|
store := mongo.NewStore(c, 3600, true, []byte("secret"))
|
||||||
|
r.Use(sessions.Sessions("mysession", store))
|
||||||
|
|
||||||
|
r.GET("/incr", func(c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
var count int
|
||||||
|
v := session.Get("count")
|
||||||
|
if v == nil {
|
||||||
|
count = 0
|
||||||
|
} else {
|
||||||
|
count = v.(int)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
session.Set("count", count)
|
||||||
|
session.Save()
|
||||||
|
c.JSON(200, gin.H{"count": count})
|
||||||
|
})
|
||||||
|
r.Run(":8000")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### memstore
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/memstore"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
store := memstore.NewStore([]byte("secret"))
|
||||||
|
r.Use(sessions.Sessions("mysession", store))
|
||||||
|
|
||||||
|
r.GET("/incr", func(c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
var count int
|
||||||
|
v := session.Get("count")
|
||||||
|
if v == nil {
|
||||||
|
count = 0
|
||||||
|
} else {
|
||||||
|
count = v.(int)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
session.Set("count", count)
|
||||||
|
session.Save()
|
||||||
|
c.JSON(200, gin.H{"count": count})
|
||||||
|
})
|
||||||
|
r.Run(":8000")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PostgreSQL
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/postgres"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
db, err := sql.Open("postgres", "postgresql://username:password@localhost:5432/database")
|
||||||
|
if err != nil {
|
||||||
|
// handle err
|
||||||
|
}
|
||||||
|
|
||||||
|
store, err := postgres.NewStore(db, []byte("secret"))
|
||||||
|
if err != nil {
|
||||||
|
// handle err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Use(sessions.Sessions("mysession", store))
|
||||||
|
|
||||||
|
r.GET("/incr", func(c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
var count int
|
||||||
|
v := session.Get("count")
|
||||||
|
if v == nil {
|
||||||
|
count = 0
|
||||||
|
} else {
|
||||||
|
count = v.(int)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
session.Set("count", count)
|
||||||
|
session.Save()
|
||||||
|
c.JSON(200, gin.H{"count": count})
|
||||||
|
})
|
||||||
|
r.Run(":8000")
|
||||||
|
}
|
||||||
|
```
|
31
vendor/github.com/gin-contrib/sessions/cookie/cookie.go
generated
vendored
Normal file
31
vendor/github.com/gin-contrib/sessions/cookie/cookie.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package cookie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
gsessions "github.com/gorilla/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Store interface {
|
||||||
|
sessions.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys are defined in pairs to allow key rotation, but the common case is to set a single
|
||||||
|
// authentication key and optionally an encryption key.
|
||||||
|
//
|
||||||
|
// The first key in a pair is used for authentication and the second for encryption. The
|
||||||
|
// encryption key can be set to nil or omitted in the last pair, but the authentication key
|
||||||
|
// is required in all pairs.
|
||||||
|
//
|
||||||
|
// It is recommended to use an authentication key with 32 or 64 bytes. The encryption key,
|
||||||
|
// if set, must be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256 modes.
|
||||||
|
func NewStore(keyPairs ...[]byte) Store {
|
||||||
|
return &store{gsessions.NewCookieStore(keyPairs...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
type store struct {
|
||||||
|
*gsessions.CookieStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *store) Options(options sessions.Options) {
|
||||||
|
c.CookieStore.Options = options.ToGorillaOptions()
|
||||||
|
}
|
21
vendor/github.com/gin-contrib/sessions/go.mod
generated
vendored
Normal file
21
vendor/github.com/gin-contrib/sessions/go.mod
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
module github.com/gin-contrib/sessions
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0
|
||||||
|
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
|
||||||
|
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1
|
||||||
|
github.com/gin-gonic/gin v1.7.4
|
||||||
|
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8
|
||||||
|
github.com/gomodule/redigo v2.0.0+incompatible
|
||||||
|
github.com/gorilla/context v1.1.1
|
||||||
|
github.com/gorilla/sessions v1.2.0
|
||||||
|
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b
|
||||||
|
github.com/lib/pq v1.10.3 // indirect
|
||||||
|
github.com/memcachier/mc v2.0.1+incompatible
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b
|
||||||
|
github.com/stretchr/testify v1.7.0 // indirect
|
||||||
|
)
|
86
vendor/github.com/gin-contrib/sessions/go.sum
generated
vendored
Normal file
86
vendor/github.com/gin-contrib/sessions/go.sum
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0 h1:grN4CYLduV1d9SYBSYrAMPVf57cxEa7KhenvwOXTktw=
|
||||||
|
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
||||||
|
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04=
|
||||||
|
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
|
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1 h1:4QHxgr7hM4gVD8uOwrk8T1fjkKRLwaLjmTkU0ibhZKU=
|
||||||
|
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
||||||
|
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/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
|
||||||
|
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||||
|
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is=
|
||||||
|
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||||
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
|
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||||
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
|
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||||
|
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
|
||||||
|
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
|
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||||
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b h1:TLCm7HR+P9HM2NXaAJaIiHerOUMedtFJeAfaYwZ8YhY=
|
||||||
|
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
||||||
|
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||||
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
|
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
|
||||||
|
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/memcachier/mc v2.0.1+incompatible h1:s8EDz0xrJLP8goitwZOoq1vA/sm0fPS4X3KAF0nyhWQ=
|
||||||
|
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
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=
|
||||||
|
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc=
|
||||||
|
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||||
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
|
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||||
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
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=
|
||||||
|
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/yaml.v2 v2.2.2/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=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
30
vendor/github.com/gin-contrib/sessions/session_options_go1.10.go
generated
vendored
Normal file
30
vendor/github.com/gin-contrib/sessions/session_options_go1.10.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// +build !go1.11
|
||||||
|
|
||||||
|
package sessions
|
||||||
|
|
||||||
|
import (
|
||||||
|
gsessions "github.com/gorilla/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options stores configuration for a session or session store.
|
||||||
|
// Fields are a subset of http.Cookie fields.
|
||||||
|
type Options struct {
|
||||||
|
Path string
|
||||||
|
Domain string
|
||||||
|
// MaxAge=0 means no 'Max-Age' attribute specified.
|
||||||
|
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'.
|
||||||
|
// MaxAge>0 means Max-Age attribute present and given in seconds.
|
||||||
|
MaxAge int
|
||||||
|
Secure bool
|
||||||
|
HttpOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options Options) ToGorillaOptions() *gsessions.Options {
|
||||||
|
return &gsessions.Options{
|
||||||
|
Path: options.Path,
|
||||||
|
Domain: options.Domain,
|
||||||
|
MaxAge: options.MaxAge,
|
||||||
|
Secure: options.Secure,
|
||||||
|
HttpOnly: options.HttpOnly,
|
||||||
|
}
|
||||||
|
}
|
36
vendor/github.com/gin-contrib/sessions/session_options_go1.11.go
generated
vendored
Normal file
36
vendor/github.com/gin-contrib/sessions/session_options_go1.11.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// +build go1.11
|
||||||
|
|
||||||
|
package sessions
|
||||||
|
|
||||||
|
import (
|
||||||
|
gsessions "github.com/gorilla/sessions"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options stores configuration for a session or session store.
|
||||||
|
// Fields are a subset of http.Cookie fields.
|
||||||
|
type Options struct {
|
||||||
|
Path string
|
||||||
|
Domain string
|
||||||
|
// MaxAge=0 means no 'Max-Age' attribute specified.
|
||||||
|
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'.
|
||||||
|
// MaxAge>0 means Max-Age attribute present and given in seconds.
|
||||||
|
MaxAge int
|
||||||
|
Secure bool
|
||||||
|
HttpOnly bool
|
||||||
|
// rfc-draft to preventing CSRF: https://tools.ietf.org/html/draft-west-first-party-cookies-07
|
||||||
|
// refer: https://godoc.org/net/http
|
||||||
|
// https://www.sjoerdlangkemper.nl/2016/04/14/preventing-csrf-with-samesite-cookie-attribute/
|
||||||
|
SameSite http.SameSite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options Options) ToGorillaOptions() *gsessions.Options {
|
||||||
|
return &gsessions.Options{
|
||||||
|
Path: options.Path,
|
||||||
|
Domain: options.Domain,
|
||||||
|
MaxAge: options.MaxAge,
|
||||||
|
Secure: options.Secure,
|
||||||
|
HttpOnly: options.HttpOnly,
|
||||||
|
SameSite: options.SameSite,
|
||||||
|
}
|
||||||
|
}
|
151
vendor/github.com/gin-contrib/sessions/sessions.go
generated
vendored
Normal file
151
vendor/github.com/gin-contrib/sessions/sessions.go
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package sessions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/context"
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultKey = "github.com/gin-contrib/sessions"
|
||||||
|
errorFormat = "[sessions] ERROR! %s\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Store interface {
|
||||||
|
sessions.Store
|
||||||
|
Options(Options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wraps thinly gorilla-session methods.
|
||||||
|
// Session stores the values and optional configuration for a session.
|
||||||
|
type Session interface {
|
||||||
|
// ID of the session, generated by stores. It should not be used for user data.
|
||||||
|
ID() string
|
||||||
|
// Get returns the session value associated to the given key.
|
||||||
|
Get(key interface{}) interface{}
|
||||||
|
// Set sets the session value associated to the given key.
|
||||||
|
Set(key interface{}, val interface{})
|
||||||
|
// Delete removes the session value associated to the given key.
|
||||||
|
Delete(key interface{})
|
||||||
|
// Clear deletes all values in the session.
|
||||||
|
Clear()
|
||||||
|
// AddFlash adds a flash message to the session.
|
||||||
|
// A single variadic argument is accepted, and it is optional: it defines the flash key.
|
||||||
|
// If not defined "_flash" is used by default.
|
||||||
|
AddFlash(value interface{}, vars ...string)
|
||||||
|
// Flashes returns a slice of flash messages from the session.
|
||||||
|
// A single variadic argument is accepted, and it is optional: it defines the flash key.
|
||||||
|
// If not defined "_flash" is used by default.
|
||||||
|
Flashes(vars ...string) []interface{}
|
||||||
|
// Options sets configuration for a session.
|
||||||
|
Options(Options)
|
||||||
|
// Save saves all sessions used during the current request.
|
||||||
|
Save() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sessions(name string, store Store) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
s := &session{name, c.Request, store, nil, false, c.Writer}
|
||||||
|
c.Set(DefaultKey, s)
|
||||||
|
defer context.Clear(c.Request)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SessionsMany(names []string, store Store) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
sessions := make(map[string]Session, len(names))
|
||||||
|
for _, name := range names {
|
||||||
|
sessions[name] = &session{name, c.Request, store, nil, false, c.Writer}
|
||||||
|
}
|
||||||
|
c.Set(DefaultKey, sessions)
|
||||||
|
defer context.Clear(c.Request)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type session struct {
|
||||||
|
name string
|
||||||
|
request *http.Request
|
||||||
|
store Store
|
||||||
|
session *sessions.Session
|
||||||
|
written bool
|
||||||
|
writer http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) ID() string {
|
||||||
|
return s.Session().ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) Get(key interface{}) interface{} {
|
||||||
|
return s.Session().Values[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) Set(key interface{}, val interface{}) {
|
||||||
|
s.Session().Values[key] = val
|
||||||
|
s.written = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) Delete(key interface{}) {
|
||||||
|
delete(s.Session().Values, key)
|
||||||
|
s.written = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) Clear() {
|
||||||
|
for key := range s.Session().Values {
|
||||||
|
s.Delete(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) AddFlash(value interface{}, vars ...string) {
|
||||||
|
s.Session().AddFlash(value, vars...)
|
||||||
|
s.written = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) Flashes(vars ...string) []interface{} {
|
||||||
|
s.written = true
|
||||||
|
return s.Session().Flashes(vars...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) Options(options Options) {
|
||||||
|
s.Session().Options = options.ToGorillaOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) Save() error {
|
||||||
|
if s.Written() {
|
||||||
|
e := s.Session().Save(s.request, s.writer)
|
||||||
|
if e == nil {
|
||||||
|
s.written = false
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) Session() *sessions.Session {
|
||||||
|
if s.session == nil {
|
||||||
|
var err error
|
||||||
|
s.session, err = s.store.Get(s.request, s.name)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf(errorFormat, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s.session
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) Written() bool {
|
||||||
|
return s.written
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortcut to get session
|
||||||
|
func Default(c *gin.Context) Session {
|
||||||
|
return c.MustGet(DefaultKey).(Session)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortcut to get session with given name
|
||||||
|
func DefaultMany(c *gin.Context, name string) Session {
|
||||||
|
return c.MustGet(DefaultKey).(map[string]Session)[name]
|
||||||
|
}
|
26
vendor/github.com/gin-contrib/sse/.travis.yml
generated
vendored
Normal file
26
vendor/github.com/gin-contrib/sse/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
go:
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
- 1.11.x
|
||||||
|
- 1.12.x
|
||||||
|
- master
|
||||||
|
|
||||||
|
git:
|
||||||
|
depth: 10
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
include:
|
||||||
|
- go: 1.11.x
|
||||||
|
env: GO111MODULE=on
|
||||||
|
- go: 1.12.x
|
||||||
|
env: GO111MODULE=on
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v -covermode=count -coverprofile=coverage.out
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
21
vendor/github.com/gin-contrib/sse/LICENSE
generated
vendored
Normal file
21
vendor/github.com/gin-contrib/sse/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Manuel Martínez-Almeida
|
||||||
|
|
||||||
|
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.
|
58
vendor/github.com/gin-contrib/sse/README.md
generated
vendored
Normal file
58
vendor/github.com/gin-contrib/sse/README.md
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Server-Sent Events
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/gin-contrib/sse)
|
||||||
|
[](https://travis-ci.org/gin-contrib/sse)
|
||||||
|
[](https://codecov.io/gh/gin-contrib/sse)
|
||||||
|
[](https://goreportcard.com/report/github.com/gin-contrib/sse)
|
||||||
|
|
||||||
|
Server-sent events (SSE) is a technology where a browser receives automatic updates from a server via HTTP connection. The Server-Sent Events EventSource API is [standardized as part of HTML5[1] by the W3C](http://www.w3.org/TR/2009/WD-eventsource-20091029/).
|
||||||
|
|
||||||
|
- [Read this great SSE introduction by the HTML5Rocks guys](http://www.html5rocks.com/en/tutorials/eventsource/basics/)
|
||||||
|
- [Browser support](http://caniuse.com/#feat=eventsource)
|
||||||
|
|
||||||
|
## Sample code
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/gin-contrib/sse"
|
||||||
|
|
||||||
|
func httpHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
// data can be a primitive like a string, an integer or a float
|
||||||
|
sse.Encode(w, sse.Event{
|
||||||
|
Event: "message",
|
||||||
|
Data: "some data\nmore data",
|
||||||
|
})
|
||||||
|
|
||||||
|
// also a complex type, like a map, a struct or a slice
|
||||||
|
sse.Encode(w, sse.Event{
|
||||||
|
Id: "124",
|
||||||
|
Event: "message",
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"user": "manu",
|
||||||
|
"date": time.Now().Unix(),
|
||||||
|
"content": "hi!",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```
|
||||||
|
event: message
|
||||||
|
data: some data\\nmore data
|
||||||
|
|
||||||
|
id: 124
|
||||||
|
event: message
|
||||||
|
data: {"content":"hi!","date":1431540810,"user":"manu"}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Content-Type
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Println(sse.ContentType)
|
||||||
|
```
|
||||||
|
```
|
||||||
|
text/event-stream
|
||||||
|
```
|
||||||
|
|
||||||
|
## Decoding support
|
||||||
|
|
||||||
|
There is a client-side implementation of SSE coming soon.
|
5
vendor/github.com/gin-contrib/sse/go.mod
generated
vendored
Normal file
5
vendor/github.com/gin-contrib/sse/go.mod
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module github.com/gin-contrib/sse
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.3.0
|
7
vendor/github.com/gin-contrib/sse/go.sum
generated
vendored
Normal file
7
vendor/github.com/gin-contrib/sse/go.sum
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
116
vendor/github.com/gin-contrib/sse/sse-decoder.go
generated
vendored
Normal file
116
vendor/github.com/gin-contrib/sse/sse-decoder.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package sse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type decoder struct {
|
||||||
|
events []Event
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decode(r io.Reader) ([]Event, error) {
|
||||||
|
var dec decoder
|
||||||
|
return dec.decode(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) dispatchEvent(event Event, data string) {
|
||||||
|
dataLength := len(data)
|
||||||
|
if dataLength > 0 {
|
||||||
|
//If the data buffer's last character is a U+000A LINE FEED (LF) character, then remove the last character from the data buffer.
|
||||||
|
data = data[:dataLength-1]
|
||||||
|
dataLength--
|
||||||
|
}
|
||||||
|
if dataLength == 0 && event.Event == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if event.Event == "" {
|
||||||
|
event.Event = "message"
|
||||||
|
}
|
||||||
|
event.Data = data
|
||||||
|
d.events = append(d.events, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decode(r io.Reader) ([]Event, error) {
|
||||||
|
buf, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentEvent Event
|
||||||
|
var dataBuffer *bytes.Buffer = new(bytes.Buffer)
|
||||||
|
// TODO (and unit tests)
|
||||||
|
// Lines must be separated by either a U+000D CARRIAGE RETURN U+000A LINE FEED (CRLF) character pair,
|
||||||
|
// a single U+000A LINE FEED (LF) character,
|
||||||
|
// or a single U+000D CARRIAGE RETURN (CR) character.
|
||||||
|
lines := bytes.Split(buf, []byte{'\n'})
|
||||||
|
for _, line := range lines {
|
||||||
|
if len(line) == 0 {
|
||||||
|
// If the line is empty (a blank line). Dispatch the event.
|
||||||
|
d.dispatchEvent(currentEvent, dataBuffer.String())
|
||||||
|
|
||||||
|
// reset current event and data buffer
|
||||||
|
currentEvent = Event{}
|
||||||
|
dataBuffer.Reset()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if line[0] == byte(':') {
|
||||||
|
// If the line starts with a U+003A COLON character (:), ignore the line.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var field, value []byte
|
||||||
|
colonIndex := bytes.IndexRune(line, ':')
|
||||||
|
if colonIndex != -1 {
|
||||||
|
// If the line contains a U+003A COLON character character (:)
|
||||||
|
// Collect the characters on the line before the first U+003A COLON character (:),
|
||||||
|
// and let field be that string.
|
||||||
|
field = line[:colonIndex]
|
||||||
|
// Collect the characters on the line after the first U+003A COLON character (:),
|
||||||
|
// and let value be that string.
|
||||||
|
value = line[colonIndex+1:]
|
||||||
|
// If value starts with a single U+0020 SPACE character, remove it from value.
|
||||||
|
if len(value) > 0 && value[0] == ' ' {
|
||||||
|
value = value[1:]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, the string is not empty but does not contain a U+003A COLON character character (:)
|
||||||
|
// Use the whole line as the field name, and the empty string as the field value.
|
||||||
|
field = line
|
||||||
|
value = []byte{}
|
||||||
|
}
|
||||||
|
// The steps to process the field given a field name and a field value depend on the field name,
|
||||||
|
// as given in the following list. Field names must be compared literally,
|
||||||
|
// with no case folding performed.
|
||||||
|
switch string(field) {
|
||||||
|
case "event":
|
||||||
|
// Set the event name buffer to field value.
|
||||||
|
currentEvent.Event = string(value)
|
||||||
|
case "id":
|
||||||
|
// Set the event stream's last event ID to the field value.
|
||||||
|
currentEvent.Id = string(value)
|
||||||
|
case "retry":
|
||||||
|
// If the field value consists of only characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9),
|
||||||
|
// then interpret the field value as an integer in base ten, and set the event stream's reconnection time to that integer.
|
||||||
|
// Otherwise, ignore the field.
|
||||||
|
currentEvent.Id = string(value)
|
||||||
|
case "data":
|
||||||
|
// Append the field value to the data buffer,
|
||||||
|
dataBuffer.Write(value)
|
||||||
|
// then append a single U+000A LINE FEED (LF) character to the data buffer.
|
||||||
|
dataBuffer.WriteString("\n")
|
||||||
|
default:
|
||||||
|
//Otherwise. The field is ignored.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Once the end of the file is reached, the user agent must dispatch the event one final time.
|
||||||
|
d.dispatchEvent(currentEvent, dataBuffer.String())
|
||||||
|
|
||||||
|
return d.events, nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user