Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
b87921c811 | |||
74ea9b6863 | |||
30f8b4975c | |||
dcd361de1f | |||
2ec0e28243 | |||
df5671586e | |||
59dafc373f | |||
25489dc103 | |||
1dc6dfe00e | |||
02e933efed | |||
d2b3304a5a | |||
9164c53c54 | |||
18734ebd18 | |||
6b04a1dcd5 | |||
1cb93c7d97 | |||
f8f0302564 | |||
7b6a425908 | |||
591c24bab7 | |||
d85c9cb53c | |||
2b44a60ee7 | |||
218e0bf667 | |||
006eb6e72a | |||
5eae5595e9 | |||
0f47434f60 | |||
383233202e | |||
968d945205 | |||
6ea6e2e144 | |||
3265c5a4b2 | |||
1210ecb510 | |||
dec022e460 | |||
79791ef228 |
79
.drone.yml
79
.drone.yml
|
@ -1,69 +1,58 @@
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: build
|
name: lint and test
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: notify-start
|
|
||||||
image: pztrn/discordrone
|
|
||||||
settings:
|
|
||||||
webhook_id:
|
|
||||||
from_secret: discord_webhook_id
|
|
||||||
webhook_token:
|
|
||||||
from_secret: discord_webhook_secret
|
|
||||||
message: 'Starting building **{{repo.name}}#{{build.number}}@{{commit.sha}}** @ {{datetime build.started "02-Jan-2006 15:04:05 MST" "Asia/Yekaterinburg"}} (See {{build.link}} for logs).'
|
|
||||||
|
|
||||||
- name: lint
|
- name: lint
|
||||||
image: golangci/golangci-lint:latest
|
image: code.pztrn.name/containers/mirror/golangci/golangci-lint:v1.48.0
|
||||||
|
pull: if-not-exists
|
||||||
environment:
|
environment:
|
||||||
GOFLAGS: -mod=vendor
|
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
commands:
|
commands:
|
||||||
- golangci-lint run
|
- golangci-lint run
|
||||||
depends_on:
|
|
||||||
- notify-start
|
|
||||||
|
|
||||||
- name: test
|
- name: test
|
||||||
image: golang:1.13.5-alpine
|
image: code.pztrn.name/containers/mirror/golang:1.19.0-alpine
|
||||||
|
pull: if-not-exists
|
||||||
environment:
|
environment:
|
||||||
GOFLAGS: -mod=vendor
|
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
commands:
|
commands:
|
||||||
- go test ./...
|
- go test ./...
|
||||||
depends_on:
|
|
||||||
- notify-start
|
|
||||||
|
|
||||||
- name: docker
|
---
|
||||||
image: plugins/docker
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: build docker images
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- "lint and test"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build master image
|
||||||
|
image: code.pztrn.name/containers/mirror/plugins/docker:20.13.0
|
||||||
|
pull: if-not-exists
|
||||||
|
privileged: true
|
||||||
when:
|
when:
|
||||||
branch: master
|
branch: ["master"]
|
||||||
settings:
|
settings:
|
||||||
username:
|
registry: code.pztrn.name
|
||||||
from_secret: dockerhub_user
|
username: drone
|
||||||
password:
|
password:
|
||||||
from_secret: dockerhub_password
|
from_secret: drone_secret
|
||||||
repo: pztrn/fastpastebin
|
repo: code.pztrn.name/apps/fastpastebin
|
||||||
auto_tag: true
|
auto_tag: true
|
||||||
depends_on:
|
|
||||||
- lint
|
|
||||||
- test
|
|
||||||
|
|
||||||
- name: notify-end
|
- name: build tagged image
|
||||||
|
image: code.pztrn.name/containers/mirror/plugins/docker:20.13.0
|
||||||
|
pull: if-not-exists
|
||||||
|
privileged: true
|
||||||
when:
|
when:
|
||||||
status:
|
event: ["tag"]
|
||||||
- success
|
|
||||||
- failure
|
|
||||||
image: pztrn/discordrone
|
|
||||||
settings:
|
settings:
|
||||||
webhook_id:
|
registry: code.pztrn.name
|
||||||
from_secret: discord_webhook_id
|
username: drone
|
||||||
webhook_token:
|
password:
|
||||||
from_secret: discord_webhook_secret
|
from_secret: drone_secret
|
||||||
message: "
|
repo: code.pztrn.name/apps/fastpastebin
|
||||||
{{#success build.status}}
|
auto_tag: true
|
||||||
**{{repo.name}}#{{build.number}}@{{commit.sha}}** built in {{since build.startedint}} and pushed to hub.docker.com.
|
|
||||||
{{ else }}
|
|
||||||
**{{repo.name}}#{{build.number}}@{{commit.sha}}** failed. See {{build.link}}.
|
|
||||||
{{/success}}"
|
|
||||||
depends_on:
|
|
||||||
- docker
|
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
||||||
examples/fastpastebin.yaml
|
examples/fastpastebin.yaml
|
||||||
dist/
|
dist/
|
||||||
data/
|
data/
|
||||||
|
vendor/
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
*DS_Store*
|
*DS_Store*
|
||||||
|
|
|
@ -1,28 +1,35 @@
|
||||||
image: docker:dind
|
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
HOST: 0.0.0.0
|
|
||||||
PORT: 2375
|
|
||||||
DOCKER_HOST: tcp://docker:2375/
|
DOCKER_HOST: tcp://docker:2375/
|
||||||
DOCKER_DRIVER: overlay2
|
DOCKER_DRIVER: overlay2
|
||||||
CONTAINER_NAME: registry.gitlab.pztrn.name/fastpastebin/fastpastebin
|
DOCKER_TCP_PORT: 2375
|
||||||
GIT_STRATEGY: clone
|
|
||||||
DOCKER_TLS_CERTDIR: ""
|
DOCKER_TLS_CERTDIR: ""
|
||||||
|
CONTAINER_NAME: registry.gitlab.pztrn.name/fastpastebin/fastpastebin
|
||||||
|
DIND_IMAGE: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/docker:dind
|
||||||
|
GOLANGCILINT_IMAGE: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/golangci/golangci-lint:v1.40.1-alpine
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- docker:dind
|
- name: ${DIND_IMAGE}
|
||||||
|
alias: docker
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
|
- test
|
||||||
- build
|
- build
|
||||||
|
|
||||||
before_script:
|
lint:
|
||||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
stage: test
|
||||||
|
image: ${GOLANGCILINT_IMAGE}
|
||||||
build:
|
|
||||||
stage: build
|
|
||||||
tags:
|
tags:
|
||||||
- docker
|
- docker
|
||||||
script:
|
script:
|
||||||
|
- golangci-lint run ./...
|
||||||
|
|
||||||
|
build:
|
||||||
|
stage: build
|
||||||
|
image: $DIND_IMAGE
|
||||||
|
tags:
|
||||||
|
- docker
|
||||||
|
script:
|
||||||
|
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||||
- source docker/set_docker_tag.sh
|
- source docker/set_docker_tag.sh
|
||||||
- docker build -t $CONTAINER_NAME:$DOCKER_TAG .
|
- docker build -t $CONTAINER_NAME:$DOCKER_TAG .
|
||||||
- docker push $CONTAINER_NAME:$DOCKER_TAG
|
- docker push $CONTAINER_NAME:$DOCKER_TAG
|
||||||
|
|
|
@ -12,6 +12,11 @@ linters:
|
||||||
- funlen
|
- funlen
|
||||||
# Magic numbers everywhere and we can't get rid of them.
|
# Magic numbers everywhere and we can't get rid of them.
|
||||||
- gomnd
|
- gomnd
|
||||||
|
# This linter MIGHT BE good, but who decided that I want keepFor in
|
||||||
|
# JSON instead of keep_for for KeepFor field?
|
||||||
|
- tagliatelle
|
||||||
|
# Deprecated.
|
||||||
|
- exhaustivestruct
|
||||||
linters-settings:
|
linters-settings:
|
||||||
lll:
|
lll:
|
||||||
line-length: 420
|
line-length: 420
|
||||||
|
@ -19,3 +24,12 @@ linters-settings:
|
||||||
min-complexity: 50
|
min-complexity: 50
|
||||||
gocyclo:
|
gocyclo:
|
||||||
min-complexity: 40
|
min-complexity: 40
|
||||||
|
cyclop:
|
||||||
|
max-complexity: 40
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
# There will be some ToDos.
|
||||||
|
- linters:
|
||||||
|
- godox
|
||||||
|
text: "TODO"
|
||||||
|
|
5
.markdownlint.json
Normal file
5
.markdownlint.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"line-length": false,
|
||||||
|
"first-line-h1": false,
|
||||||
|
"no-duplicate-header": false
|
||||||
|
}
|
82
CHANGELOG.md
82
CHANGELOG.md
|
@ -1,12 +1,84 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
``[A]`` - added
|
All notable changes to this project will be documented in this file.
|
||||||
``[F]`` - fixed
|
|
||||||
``[R]`` - removed
|
|
||||||
|
|
||||||
---
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## 0.1.0
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.4.1] - 2022-08-14
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Update docker images - alpine to 3.16.1, golang to 1.19, golangci-lint to 1.48.0.
|
||||||
|
* Update chroma to v2.2.0.
|
||||||
|
* Update bulma to 0.9.4.
|
||||||
|
* Update bulma-tooltip to 1.2 (was 3.0.0, but repo was switched to CreativeBulma).
|
||||||
|
* Update github.com/dchest/captcha to v1.0.0.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Put valid repository's link in footer.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
* Removed `flagger` dependency.
|
||||||
|
|
||||||
|
## [0.4.0] - 2021-01-09
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* PostgreSQL support.
|
||||||
|
* Docker containerization for every commit and tag.
|
||||||
|
* Pastes cleanup procedure.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Updated bulma to v 0.7.5.
|
||||||
|
* Moved from `io/ioutil` to `os` package for reading/writing files and directories.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Dirty hack to get database connection reestablish (for sure).
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
Release changelogs lost :(.
|
||||||
|
|
||||||
|
## [0.2.0] - 2018-05-27
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Possibility to create different database backends. Currently `mysql` and `flatfiles` are available.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* De-hardcoded pagination configuration, it is now configurable via configuration file.
|
||||||
|
|
||||||
|
## [0.1.1] - 2018-05-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Footer copyrights.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Refactored templates: now they're included in each other if neccessary.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed nasty bugs with private pastes that causing fastpastebin to crash.
|
||||||
|
* Logger level from configuration now properly set.
|
||||||
|
|
||||||
|
## [0.1.0] - 2018-05-19
|
||||||
|
|
||||||
First normal release. Fast Paste Bin is able to handle public, private
|
First normal release. Fast Paste Bin is able to handle public, private
|
||||||
and passworded pastes.
|
and passworded pastes.
|
||||||
|
|
||||||
|
[Unreleased]: https://code.pztrn.name/apps/fastpastebin/compare/v0.4.1...HEAD
|
||||||
|
[0.4.1]: https://code.pztrn.name/apps/fastpastebin/compare/0.4.0...v0.4.1
|
||||||
|
[0.4.0]: https://code.pztrn.name/apps/fastpastebin/compare/v0.2.0...0.4.0
|
||||||
|
[0.2.0]: https://code.pztrn.name/apps/fastpastebin/compare/v0.1.1...v0.2.0
|
||||||
|
[0.1.1]: https://code.pztrn.name/apps/fastpastebin/compare/v0.1.0...v0.1.1
|
||||||
|
[0.1.0]: https://code.pztrn.name/apps/fastpastebin/src/tag/v0.1.0
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
FROM golang:1.13.1-alpine AS build
|
FROM code.pztrn.name/containers/mirror/golang:1.19.0-alpine AS build
|
||||||
|
|
||||||
WORKDIR /fastpastebin
|
WORKDIR /fastpastebin
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
WORKDIR /fastpastebin/cmd/fastpastebin
|
WORKDIR /fastpastebin/cmd/fastpastebin
|
||||||
|
|
||||||
RUN GOFLAGS="-mod=vendor" go build
|
RUN CGO_ENABLED=0 go build -tags netgo
|
||||||
|
|
||||||
FROM alpine:3.10
|
FROM code.pztrn.name/containers/mirror/alpine:3.16.1
|
||||||
LABEL maintainer "Stanislav N. <pztrn@pztrn.name>"
|
LABEL maintainer "Stanislav N. <pztrn@pztrn.name>"
|
||||||
|
|
||||||
COPY --from=build /fastpastebin/cmd/fastpastebin/fastpastebin /app/fastpastebin
|
COPY --from=build /fastpastebin/cmd/fastpastebin/fastpastebin /app/fastpastebin
|
||||||
|
|
52
README.md
52
README.md
|
@ -1,9 +1,13 @@
|
||||||
# Fast Pastebin
|
# Fast Pastebin
|
||||||
|
|
||||||
[![Drone (self-hosted)](https://img.shields.io/drone/build/fastpastebin/fastpastebin?server=https%3A%2F%2Fci.dev.pztrn.name)](https://ci.dev.pztrn.name/fastpastebin/fastpastebin/) [![Discord](https://img.shields.io/discord/632359730089689128)](https://discord.gg/qHN6KsD) ![Keybase XLM](https://img.shields.io/keybase/xlm/pztrn)
|
[![Build Status](https://ci.code.pztrn.name/api/badges/apps/fastpastebin/status.svg)](https://ci.code.pztrn.name/apps/fastpastebin)
|
||||||
|
|
||||||
Easy-to-use-and-install pastebin software written in Go. No bells or whistles, no websockets and even NO JAVASCRIPT!
|
Easy-to-use-and-install pastebin software written in Go. No bells or whistles, no websockets and even NO JAVASCRIPT!
|
||||||
|
|
||||||
|
**Please, use [my gitea](https://code.pztrn.name/apps/fastpastebin) for bug reporting. All other places are mirrors!**
|
||||||
|
|
||||||
|
Also, [join Matrix room](https://matrix.to/#/%23fastpastebin:pztrn.online?via=matrix.org) for near-realtime chat.
|
||||||
|
|
||||||
## Current functionality
|
## Current functionality
|
||||||
|
|
||||||
* Create and view public and private pastes.
|
* Create and view public and private pastes.
|
||||||
|
@ -14,18 +18,41 @@ Easy-to-use-and-install pastebin software written in Go. No bells or whistles, n
|
||||||
|
|
||||||
## Caveats
|
## Caveats
|
||||||
|
|
||||||
* No links at lines numbers. See [this Chroma bug](https://github.com/alecthomas/chroma/issues/132)
|
* Not known at this moment.
|
||||||
|
|
||||||
## Installation and updating
|
## Installation and updating
|
||||||
|
|
||||||
Just issue:
|
Just issue:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
CGO_ENABLED=0 go get -u -v go.dev.pztrn.name/fastpastebin/cmd/fastpastebin
|
CGO_ENABLED=0 go install go.dev.pztrn.name/fastpastebin/cmd/fastpastebin@VERSION
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Replace `VERSION` with a [tag of your choice](https://code.pztrn.name/apps/fastpastebin/releases).
|
||||||
|
|
||||||
This command can be used to update Fast Paste Bin.
|
This command can be used to update Fast Paste Bin.
|
||||||
|
|
||||||
|
Also Fast Paste Bin is dockerized, see [here](https://code.pztrn.name/apps/-/packages/container/fastpastebin) for instructions.
|
||||||
|
|
||||||
|
Compose file with resources limits, as used by me:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
version: "2.4"
|
||||||
|
|
||||||
|
services:
|
||||||
|
fastpastebin:
|
||||||
|
restart: always
|
||||||
|
image: code.pztrn.name/apps/fastpastebin:0.4.1
|
||||||
|
volumes:
|
||||||
|
- "./fastpastebin.yaml:/app/fastpastebin.yaml"
|
||||||
|
ports:
|
||||||
|
- "25544:25544"
|
||||||
|
cpus: 2
|
||||||
|
mem_limit: 1G
|
||||||
|
memswap_limit: 0
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Take a look at [example configuration file](examples/fastpastebin.yaml.dist) which contains all supported options and their descriptions.
|
Take a look at [example configuration file](examples/fastpastebin.yaml.dist) which contains all supported options and their descriptions.
|
||||||
|
@ -34,18 +61,21 @@ Configuration file position is irrelevant, there is no hardcoded paths where Fas
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
Developers should install [fileb0x](https://github.com/UnnoTed/fileb0x/) which is used as replacement to go-bindata for embedding assets into binary. After changing assets they should be recompiled into Go code. At repository root execute this command and you'll be fine:
|
### Branching, versions, etc
|
||||||
|
|
||||||
```bash
|
There is a `develop` branch which represents current development state. **All new commits (by me) and merge requests (by others) should go to that branch**.
|
||||||
fileb0x fileb0x.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
Also if you're changed list of assets (by creating or deleting them) be sure to fix files list in ``fileb0x.yml`` file!
|
Branch `master` represents "latest version" state and always stable.
|
||||||
|
|
||||||
The rest is default - use linters, formatters, etc. VSCode with Go plugin is recommended for developing as it will perform most of linting-formatting
|
### Code
|
||||||
actions automagically. Try to follow [Go's code review comments](https://github.com/golang/go/wiki/CodeReviewComments) with few exceptions:
|
|
||||||
|
Use linters, formatters, etc. VSCode with Go plugin is recommended for developing as it will perform most of linting-formatting
|
||||||
|
actions automagically.
|
||||||
|
|
||||||
|
Also, Sublime Text with LSP-gopls will also work just fine.
|
||||||
|
|
||||||
|
Try to follow [Go's code review comments](https://github.com/golang/go/wiki/CodeReviewComments) with few exceptions:
|
||||||
|
|
||||||
* Imports should be organized in 3 groups: stdlib, local, other. See [this file](https://sources.dev.pztrn.name/fastpastebin/fastpastebin/src/branch/master/domains/pastes/paste_get.go) for example.
|
|
||||||
* We're not forcing any limits on line length for code, only for comments, they should be 72-76 chars long.
|
* We're not forcing any limits on line length for code, only for comments, they should be 72-76 chars long.
|
||||||
|
|
||||||
## ToDo
|
## ToDo
|
||||||
|
|
8
assets/assets.go
Normal file
8
assets/assets.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
//nolint:gofmt,gofumpt,goimports
|
||||||
|
package assets
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
// Data is an embedded assets data.
|
||||||
|
//go:embed *
|
||||||
|
var Data embed.FS
|
1
assets/css/bulma-0.7.5.min.css
vendored
1
assets/css/bulma-0.7.5.min.css
vendored
File diff suppressed because one or more lines are too long
1
assets/css/bulma-tooltip-3.0.0.min.css
vendored
1
assets/css/bulma-tooltip-3.0.0.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -5,7 +5,7 @@
|
||||||
<strong>{version}</strong> by
|
<strong>{version}</strong> by
|
||||||
<a href="https://pztrn.name">Stanislav N. aka pztrn</a>. The source code is licensed
|
<a href="https://pztrn.name">Stanislav N. aka pztrn</a>. The source code is licensed
|
||||||
<a href="http://opensource.org/licenses/mit-license.php">MIT</a>. Get
|
<a href="http://opensource.org/licenses/mit-license.php">MIT</a>. Get
|
||||||
<a href="https://gitlab.pztrn.name/fastpastebin/fastpastebin">source or binary releases here</a>!
|
<a href="https://code.pztrn.name/apps/fastpastebin">source or binary releases here</a>!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -38,13 +38,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field tooltip is-tooltip-bottom is-tooltip-multiline" data-tooltip="Should this paste be accessible only with special URL and not shown in pastes list? WARNING: If you'll enter password into 'Password for paste' field this checkbox will be assumed as checked!">
|
<div class="field tooltip is-tooltip-bottom is-tooltip-multiline has-tooltip-arrow" data-tooltip="Should this paste be accessible only with special URL and not shown in pastes list? WARNING: If you'll enter password into 'Password for paste' field this checkbox will be assumed as checked!">
|
||||||
<label class="checkbox">
|
<label class="checkbox">
|
||||||
<input type="checkbox" name="paste-private" id="paste-private"> Private paste with unique URL?
|
<input type="checkbox" name="paste-private" id="paste-private"> Private paste with unique URL?
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div>OR</div>
|
<div>OR</div>
|
||||||
<div class="field tooltip is-tooltip-bottom is-tooltip-multiline" data-tooltip="If you'll enter password here - 'Private paste with unique URL' checkbox will be assumed as checked.">
|
<div class="field tooltip is-tooltip-bottom is-tooltip-multiline has-tooltip-arrow" data-tooltip="If you'll enter password here - 'Private paste with unique URL' checkbox will be assumed as checked.">
|
||||||
<label for="paste-password">Password for paste:</label>
|
<label for="paste-password">Password for paste:</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input class="input" type="text" placeholder="Enter password." name="paste-password" id="paste-password">
|
<input class="input" type="text" placeholder="Enter password." name="paste-password" id="paste-password">
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,8 +5,8 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Fast Paste Bin</title>
|
<title>Fast Paste Bin</title>
|
||||||
<link rel="stylesheet" href="/static/css/bulma-0.7.5.min.css">
|
<link rel="stylesheet" href="/static/css/bulma-0.9.4.min.css">
|
||||||
<link rel="stylesheet" href="/static/css/bulma-tooltip-3.0.0.min.css">
|
<link rel="stylesheet" href="/static/css/bulma-tooltip-1.2.min.css">
|
||||||
<link rel="stylesheet" href="/static/css/style.css">
|
<link rel="stylesheet" href="/static/css/style.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -1,183 +0,0 @@
|
||||||
// Code generated by fileb0x at "2021-01-09 06:15:34.328599857 +0500 +05 m=+0.033228523" from config file "fileb0x.yml" DO NOT EDIT.
|
|
||||||
// modification hash(a501d12d9fe3316e4b2134554bdc730c.238872c2653234e79053054d1ba776af)
|
|
||||||
|
|
||||||
package static
|
|
||||||
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
|
|
||||||
"golang.org/x/net/webdav"
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// CTX is a context for webdav vfs
|
|
||||||
CTX = context.Background()
|
|
||||||
|
|
||||||
|
|
||||||
// FS is a virtual memory file system
|
|
||||||
FS = webdav.NewMemFS()
|
|
||||||
|
|
||||||
|
|
||||||
// Handler is used to server files through a http handler
|
|
||||||
Handler *webdav.Handler
|
|
||||||
|
|
||||||
// HTTP is the http file system
|
|
||||||
HTTP http.FileSystem = new(HTTPFS)
|
|
||||||
)
|
|
||||||
|
|
||||||
// HTTPFS implements http.FileSystem
|
|
||||||
type HTTPFS struct {
|
|
||||||
// Prefix allows to limit the path of all requests. F.e. a prefix "css" would allow only calls to /css/*
|
|
||||||
Prefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
err := CTX.Err()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err = FS.Mkdir(CTX, "static/", 0777)
|
|
||||||
if err != nil && err != os.ErrExist {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err = FS.Mkdir(CTX, "static/css/", 0777)
|
|
||||||
if err != nil && err != os.ErrExist {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err = FS.Mkdir(CTX, "static/js/", 0777)
|
|
||||||
if err != nil && err != os.ErrExist {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Handler = &webdav.Handler{
|
|
||||||
FileSystem: FS,
|
|
||||||
LockSystem: webdav.NewMemLS(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Open a file
|
|
||||||
func (hfs *HTTPFS) Open(path string) (http.File, error) {
|
|
||||||
path = hfs.Prefix + path
|
|
||||||
|
|
||||||
|
|
||||||
f, err := FS.OpenFile(CTX, path, os.O_RDONLY, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFile is adapTed from ioutil
|
|
||||||
func ReadFile(path string) ([]byte, error) {
|
|
||||||
f, err := FS.OpenFile(CTX, path, os.O_RDONLY, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, bytes.MinRead))
|
|
||||||
|
|
||||||
// If the buffer overflows, we will get bytes.ErrTooLarge.
|
|
||||||
// Return that as an error. Any other panic remains.
|
|
||||||
defer func() {
|
|
||||||
e := recover()
|
|
||||||
if e == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
|
|
||||||
err = panicErr
|
|
||||||
} else {
|
|
||||||
panic(e)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
_, err = buf.ReadFrom(f)
|
|
||||||
return buf.Bytes(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteFile is adapTed from ioutil
|
|
||||||
func WriteFile(filename string, data []byte, perm os.FileMode) error {
|
|
||||||
f, err := FS.OpenFile(CTX, filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
n, err := f.Write(data)
|
|
||||||
if err == nil && n < len(data) {
|
|
||||||
err = io.ErrShortWrite
|
|
||||||
}
|
|
||||||
if err1 := f.Close(); err == nil {
|
|
||||||
err = err1
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WalkDirs looks for files in the given dir and returns a list of files in it
|
|
||||||
// usage for all files in the b0x: WalkDirs("", false)
|
|
||||||
func WalkDirs(name string, includeDirsInList bool, files ...string) ([]string, error) {
|
|
||||||
f, err := FS.OpenFile(CTX, name, os.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fileInfos, err := f.Readdir(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, info := range fileInfos {
|
|
||||||
filename := path.Join(name, info.Name())
|
|
||||||
|
|
||||||
if includeDirsInList || !info.IsDir() {
|
|
||||||
files = append(files, filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.IsDir() {
|
|
||||||
files, err = WalkDirs(filename, includeDirsInList, files...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Code generaTed by fileb0x at "2021-01-09 06:15:34.380518788 +0500 +05 m=+0.085147493" from config file "fileb0x.yml" DO NOT EDIT.
|
|
||||||
// modified(2021-01-08 11:51:03.805743275 +0500 +05)
|
|
||||||
// original path: assets/database_not_available.html
|
|
||||||
|
|
||||||
package static
|
|
||||||
|
|
||||||
import (
|
|
||||||
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileDatabaseNotAvailableHTML is "/database_not_available.html"
|
|
||||||
var FileDatabaseNotAvailableHTML = []byte("\x3c\x73\x65\x63\x74\x69\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x73\x65\x63\x74\x69\x6f\x6e\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x6f\x74\x69\x66\x69\x63\x61\x74\x69\x6f\x6e\x20\x69\x73\x2d\x64\x61\x6e\x67\x65\x72\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x68\x33\x3e\x3c\x73\x74\x72\x6f\x6e\x67\x3e\x44\x61\x74\x61\x62\x61\x73\x65\x20\x6e\x6f\x74\x20\x61\x76\x61\x69\x6c\x61\x62\x6c\x65\x3c\x2f\x73\x74\x72\x6f\x6e\x67\x3e\x3c\x2f\x68\x33\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x70\x3e\x53\x6f\x6d\x65\x74\x68\x69\x6e\x67\x20\x77\x65\x6e\x74\x20\x77\x72\x6f\x6e\x67\x20\x77\x68\x69\x6c\x65\x20\x74\x72\x79\x69\x6e\x67\x20\x74\x6f\x20\x63\x6f\x6e\x6e\x65\x63\x74\x20\x74\x6f\x20\x64\x61\x74\x61\x62\x61\x73\x65\x2e\x20\x43\x68\x65\x63\x6b\x20\x6c\x6f\x67\x73\x20\x66\x6f\x72\x20\x64\x65\x74\x61\x69\x6c\x73\x2e\x3c\x2f\x70\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x73\x65\x63\x74\x69\x6f\x6e\x3e")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
|
|
||||||
f, err := FS.OpenFile(CTX, "/database_not_available.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_, err = f.Write(FileDatabaseNotAvailableHTML)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Code generaTed by fileb0x at "2021-01-09 06:15:34.366001707 +0500 +05 m=+0.070630336" from config file "fileb0x.yml" DO NOT EDIT.
|
|
||||||
// modified(2021-01-08 11:51:03.805743275 +0500 +05)
|
|
||||||
// original path: assets/error.html
|
|
||||||
|
|
||||||
package static
|
|
||||||
|
|
||||||
import (
|
|
||||||
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileErrorHTML is "/error.html"
|
|
||||||
var FileErrorHTML = []byte("\x3c\x73\x65\x63\x74\x69\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x73\x65\x63\x74\x69\x6f\x6e\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x62\x6f\x78\x20\x68\x61\x73\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x2d\x77\x61\x72\x6e\x69\x6e\x67\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x70\x3e\x7b\x65\x72\x72\x6f\x72\x7d\x3c\x2f\x70\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x73\x65\x63\x74\x69\x6f\x6e\x3e")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
|
|
||||||
f, err := FS.OpenFile(CTX, "/error.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_, err = f.Write(FileErrorHTML)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Code generaTed by fileb0x at "2021-01-09 06:15:34.379885693 +0500 +05 m=+0.084514363" from config file "fileb0x.yml" DO NOT EDIT.
|
|
||||||
// modified(2021-01-09 06:13:46.102341215 +0500 +05)
|
|
||||||
// original path: assets/footer.html
|
|
||||||
|
|
||||||
package static
|
|
||||||
|
|
||||||
import (
|
|
||||||
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileFooterHTML is "/footer.html"
|
|
||||||
var FileFooterHTML = []byte("\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6f\x6e\x74\x61\x69\x6e\x65\x72\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6f\x6e\x74\x65\x6e\x74\x20\x68\x61\x73\x2d\x74\x65\x78\x74\x2d\x63\x65\x6e\x74\x65\x72\x65\x64\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x70\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x73\x74\x72\x6f\x6e\x67\x3e\x46\x61\x73\x74\x20\x70\x61\x73\x74\x65\x20\x62\x69\x6e\x3c\x2f\x73\x74\x72\x6f\x6e\x67\x3e\x20\x76\x65\x72\x73\x69\x6f\x6e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x73\x74\x72\x6f\x6e\x67\x3e\x7b\x76\x65\x72\x73\x69\x6f\x6e\x7d\x3c\x2f\x73\x74\x72\x6f\x6e\x67\x3e\x20\x62\x79\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x68\x72\x65\x66\x3d\x22\x68\x74\x74\x70\x73\x3a\x2f\x2f\x70\x7a\x74\x72\x6e\x2e\x6e\x61\x6d\x65\x22\x3e\x53\x74\x61\x6e\x69\x73\x6c\x61\x76\x20\x4e\x2e\x20\x61\x6b\x61\x20\x70\x7a\x74\x72\x6e\x3c\x2f\x61\x3e\x2e\x20\x54\x68\x65\x20\x73\x6f\x75\x72\x63\x65\x20\x63\x6f\x64\x65\x20\x69\x73\x20\x6c\x69\x63\x65\x6e\x73\x65\x64\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x68\x72\x65\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6f\x70\x65\x6e\x73\x6f\x75\x72\x63\x65\x2e\x6f\x72\x67\x2f\x6c\x69\x63\x65\x6e\x73\x65\x73\x2f\x6d\x69\x74\x2d\x6c\x69\x63\x65\x6e\x73\x65\x2e\x70\x68\x70\x22\x3e\x4d\x49\x54\x3c\x2f\x61\x3e\x2e\x20\x47\x65\x74\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x68\x72\x65\x66\x3d\x22\x68\x74\x74\x70\x73\x3a\x2f\x2f\x67\x69\x74\x6c\x61\x62\x2e\x70\x7a\x74\x72\x6e\x2e\x6e\x61\x6d\x65\x2f\x66\x61\x73\x74\x70\x61\x73\x74\x65\x62\x69\x6e\x2f\x66\x61\x73\x74\x70\x61\x73\x74\x65\x62\x69\x6e\x22\x3e\x73\x6f\x75\x72\x63\x65\x20\x6f\x72\x20\x62\x69\x6e\x61\x72\x79\x20\x72\x65\x6c\x65\x61\x73\x65\x73\x20\x68\x65\x72\x65\x3c\x2f\x61\x3e\x21\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x70\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x64\x69\x76\x3e\x0a")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
|
|
||||||
f, err := FS.OpenFile(CTX, "/footer.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_, err = f.Write(FileFooterHTML)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,35 +0,0 @@
|
||||||
// Code generaTed by fileb0x at "2021-01-09 06:15:34.333846038 +0500 +05 m=+0.038474816" from config file "fileb0x.yml" DO NOT EDIT.
|
|
||||||
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
|
|
||||||
// original path: assets/main.html
|
|
||||||
|
|
||||||
package static
|
|
||||||
|
|
||||||
import (
|
|
||||||
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileMainHTML is "/main.html"
|
|
||||||
var FileMainHTML = []byte("\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\x20\x68\x74\x6d\x6c\x3e\x0a\x3c\x68\x74\x6d\x6c\x3e\x0a\x0a\x3c\x68\x65\x61\x64\x3e\x0a\x20\x20\x20\x20\x3c\x6d\x65\x74\x61\x20\x63\x68\x61\x72\x73\x65\x74\x3d\x22\x75\x74\x66\x2d\x38\x22\x3e\x0a\x20\x20\x20\x20\x3c\x6d\x65\x74\x61\x20\x6e\x61\x6d\x65\x3d\x22\x76\x69\x65\x77\x70\x6f\x72\x74\x22\x20\x63\x6f\x6e\x74\x65\x6e\x74\x3d\x22\x77\x69\x64\x74\x68\x3d\x64\x65\x76\x69\x63\x65\x2d\x77\x69\x64\x74\x68\x2c\x20\x69\x6e\x69\x74\x69\x61\x6c\x2d\x73\x63\x61\x6c\x65\x3d\x31\x22\x3e\x0a\x20\x20\x20\x20\x3c\x74\x69\x74\x6c\x65\x3e\x46\x61\x73\x74\x20\x50\x61\x73\x74\x65\x20\x42\x69\x6e\x3c\x2f\x74\x69\x74\x6c\x65\x3e\x0a\x20\x20\x20\x20\x3c\x6c\x69\x6e\x6b\x20\x72\x65\x6c\x3d\x22\x73\x74\x79\x6c\x65\x73\x68\x65\x65\x74\x22\x20\x68\x72\x65\x66\x3d\x22\x2f\x73\x74\x61\x74\x69\x63\x2f\x63\x73\x73\x2f\x62\x75\x6c\x6d\x61\x2d\x30\x2e\x37\x2e\x35\x2e\x6d\x69\x6e\x2e\x63\x73\x73\x22\x3e\x0a\x20\x20\x20\x20\x3c\x6c\x69\x6e\x6b\x20\x72\x65\x6c\x3d\x22\x73\x74\x79\x6c\x65\x73\x68\x65\x65\x74\x22\x20\x68\x72\x65\x66\x3d\x22\x2f\x73\x74\x61\x74\x69\x63\x2f\x63\x73\x73\x2f\x62\x75\x6c\x6d\x61\x2d\x74\x6f\x6f\x6c\x74\x69\x70\x2d\x33\x2e\x30\x2e\x30\x2e\x6d\x69\x6e\x2e\x63\x73\x73\x22\x3e\x0a\x20\x20\x20\x20\x3c\x6c\x69\x6e\x6b\x20\x72\x65\x6c\x3d\x22\x73\x74\x79\x6c\x65\x73\x68\x65\x65\x74\x22\x20\x68\x72\x65\x66\x3d\x22\x2f\x73\x74\x61\x74\x69\x63\x2f\x63\x73\x73\x2f\x73\x74\x79\x6c\x65\x2e\x63\x73\x73\x22\x3e\x0a\x3c\x2f\x68\x65\x61\x64\x3e\x0a\x0a\x3c\x62\x6f\x64\x79\x3e\x0a\x20\x20\x20\x20\x7b\x6e\x61\x76\x69\x67\x61\x74\x69\x6f\x6e\x7d\x20\x7b\x64\x6f\x63\x75\x6d\x65\x6e\x74\x42\x6f\x64\x79\x7d\x0a\x3c\x2f\x62\x6f\x64\x79\x3e\x0a\x3c\x66\x6f\x6f\x74\x65\x72\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x6f\x6f\x74\x65\x72\x22\x3e\x0a\x20\x20\x20\x20\x7b\x66\x6f\x6f\x74\x65\x72\x7d\x0a\x3c\x2f\x66\x6f\x6f\x74\x65\x72\x3e\x0a\x0a\x3c\x2f\x68\x74\x6d\x6c\x3e")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
|
|
||||||
f, err := FS.OpenFile(CTX, "/main.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_, err = f.Write(FileMainHTML)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Code generaTed by fileb0x at "2021-01-09 06:15:34.381029312 +0500 +05 m=+0.085658023" from config file "fileb0x.yml" DO NOT EDIT.
|
|
||||||
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
|
|
||||||
// original path: assets/navigation.html
|
|
||||||
|
|
||||||
package static
|
|
||||||
|
|
||||||
import (
|
|
||||||
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileNavigationHTML is "/navigation.html"
|
|
||||||
var FileNavigationHTML = []byte("\x3c\x6e\x61\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x61\x76\x62\x61\x72\x20\x69\x73\x2d\x64\x61\x72\x6b\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x61\x76\x62\x61\x72\x2d\x62\x72\x61\x6e\x64\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x61\x76\x62\x61\x72\x2d\x69\x74\x65\x6d\x22\x20\x68\x72\x65\x66\x3d\x22\x2f\x22\x3e\x46\x61\x73\x74\x20\x50\x61\x73\x74\x65\x20\x42\x69\x6e\x3c\x2f\x61\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x61\x76\x62\x61\x72\x2d\x69\x74\x65\x6d\x22\x20\x68\x72\x65\x66\x3d\x22\x2f\x70\x61\x73\x74\x65\x73\x2f\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x50\x61\x73\x74\x65\x73\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x61\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x69\x64\x3d\x22\x6e\x61\x76\x62\x61\x72\x49\x74\x65\x6d\x73\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x61\x76\x62\x61\x72\x2d\x6d\x65\x6e\x75\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x61\x76\x62\x61\x72\x2d\x73\x74\x61\x72\x74\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x61\x76\x62\x61\x72\x2d\x65\x6e\x64\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x6e\x61\x76\x3e")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
|
|
||||||
f, err := FS.OpenFile(CTX, "/navigation.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_, err = f.Write(FileNavigationHTML)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Code generaTed by fileb0x at "2021-01-09 06:15:34.373359597 +0500 +05 m=+0.077988268" from config file "fileb0x.yml" DO NOT EDIT.
|
|
||||||
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
|
|
||||||
// original path: assets/pagination.html
|
|
||||||
|
|
||||||
package static
|
|
||||||
|
|
||||||
import (
|
|
||||||
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilePaginationHTML is "/pagination.html"
|
|
||||||
var FilePaginationHTML = []byte("\x3c\x73\x65\x63\x74\x69\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x73\x65\x63\x74\x69\x6f\x6e\x22\x3e\x0a\x20\x20\x20\x20\x3c\x6e\x61\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x20\x69\x73\x2d\x63\x65\x6e\x74\x65\x72\x65\x64\x22\x20\x72\x6f\x6c\x65\x3d\x22\x6e\x61\x76\x69\x67\x61\x74\x69\x6f\x6e\x22\x20\x61\x72\x69\x61\x2d\x6c\x61\x62\x65\x6c\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x2d\x70\x72\x65\x76\x69\x6f\x75\x73\x22\x20\x68\x72\x65\x66\x3d\x22\x7b\x70\x72\x65\x76\x69\x6f\x75\x73\x50\x61\x67\x65\x4c\x69\x6e\x6b\x7d\x22\x3e\x50\x72\x65\x76\x69\x6f\x75\x73\x20\x70\x61\x67\x65\x3c\x2f\x61\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x75\x6c\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x2d\x6c\x69\x73\x74\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x7b\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x4c\x69\x6e\x6b\x73\x7d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x75\x6c\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x2d\x6e\x65\x78\x74\x22\x20\x68\x72\x65\x66\x3d\x22\x7b\x6e\x65\x78\x74\x50\x61\x67\x65\x4c\x69\x6e\x6b\x7d\x22\x3e\x4e\x65\x78\x74\x20\x70\x61\x67\x65\x3c\x2f\x61\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x6e\x61\x76\x3e\x0a\x3c\x2f\x73\x65\x63\x74\x69\x6f\x6e\x3e")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
|
|
||||||
f, err := FS.OpenFile(CTX, "/pagination.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_, err = f.Write(FilePaginationHTML)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Code generaTed by fileb0x at "2021-01-09 06:15:34.372861545 +0500 +05 m=+0.077490249" from config file "fileb0x.yml" DO NOT EDIT.
|
|
||||||
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
|
|
||||||
// original path: assets/pagination_ellipsis.html
|
|
||||||
|
|
||||||
package static
|
|
||||||
|
|
||||||
import (
|
|
||||||
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilePaginationEllipsisHTML is "/pagination_ellipsis.html"
|
|
||||||
var FilePaginationEllipsisHTML = []byte("\x3c\x6c\x69\x3e\x0a\x20\x20\x20\x20\x3c\x73\x70\x61\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x2d\x65\x6c\x6c\x69\x70\x73\x69\x73\x22\x3e\x26\x68\x65\x6c\x6c\x69\x70\x3b\x3c\x2f\x73\x70\x61\x6e\x3e\x0a\x3c\x2f\x6c\x69\x3e")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
|
|
||||||
f, err := FS.OpenFile(CTX, "/pagination_ellipsis.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_, err = f.Write(FilePaginationEllipsisHTML)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Code generaTed by fileb0x at "2021-01-09 06:15:34.334873281 +0500 +05 m=+0.039502074" from config file "fileb0x.yml" DO NOT EDIT.
|
|
||||||
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
|
|
||||||
// original path: assets/pagination_link.html
|
|
||||||
|
|
||||||
package static
|
|
||||||
|
|
||||||
import (
|
|
||||||
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilePaginationLinkHTML is "/pagination_link.html"
|
|
||||||
var FilePaginationLinkHTML = []byte("\x3c\x6c\x69\x3e\x0a\x20\x20\x20\x20\x3c\x61\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x2d\x6c\x69\x6e\x6b\x22\x20\x61\x72\x69\x61\x2d\x6c\x61\x62\x65\x6c\x3d\x22\x47\x6f\x20\x74\x6f\x20\x70\x61\x67\x65\x20\x7b\x70\x61\x67\x65\x4e\x75\x6d\x7d\x22\x20\x68\x72\x65\x66\x3d\x22\x7b\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x4c\x69\x6e\x6b\x7d\x22\x3e\x7b\x70\x61\x67\x65\x4e\x75\x6d\x7d\x3c\x2f\x61\x3e\x0a\x3c\x2f\x6c\x69\x3e")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
|
|
||||||
f, err := FS.OpenFile(CTX, "/pagination_link.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_, err = f.Write(FilePaginationLinkHTML)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Code generaTed by fileb0x at "2021-01-09 06:15:34.366696762 +0500 +05 m=+0.071325415" from config file "fileb0x.yml" DO NOT EDIT.
|
|
||||||
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
|
|
||||||
// original path: assets/pagination_link_current.html
|
|
||||||
|
|
||||||
package static
|
|
||||||
|
|
||||||
import (
|
|
||||||
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilePaginationLinkCurrentHTML is "/pagination_link_current.html"
|
|
||||||
var FilePaginationLinkCurrentHTML = []byte("\x3c\x6c\x69\x3e\x0a\x20\x20\x20\x20\x3c\x61\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x2d\x6c\x69\x6e\x6b\x20\x69\x73\x2d\x63\x75\x72\x72\x65\x6e\x74\x22\x20\x61\x72\x69\x61\x2d\x6c\x61\x62\x65\x6c\x3d\x22\x50\x61\x67\x65\x20\x7b\x70\x61\x67\x65\x4e\x75\x6d\x7d\x22\x20\x61\x72\x69\x61\x2d\x63\x75\x72\x72\x65\x6e\x74\x3d\x22\x70\x61\x67\x65\x22\x3e\x7b\x70\x61\x67\x65\x4e\x75\x6d\x7d\x3c\x2f\x61\x3e\x0a\x3c\x2f\x6c\x69\x3e")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
|
|
||||||
f, err := FS.OpenFile(CTX, "/pagination_link_current.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_, err = f.Write(FilePaginationLinkCurrentHTML)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,35 +0,0 @@
|
||||||
// Code generaTed by fileb0x at "2021-01-09 06:15:34.36723926 +0500 +05 m=+0.071867934" from config file "fileb0x.yml" DO NOT EDIT.
|
|
||||||
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
|
|
||||||
// original path: assets/paste.html
|
|
||||||
|
|
||||||
package static
|
|
||||||
|
|
||||||
import (
|
|
||||||
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilePasteHTML is "/paste.html"
|
|
||||||
var FilePasteHTML = []byte("\x3c\x73\x65\x63\x74\x69\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x73\x65\x63\x74\x69\x6f\x6e\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6f\x6e\x74\x65\x6e\x74\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x61\x62\x6c\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x74\x61\x62\x6c\x65\x20\x69\x73\x2d\x62\x6f\x72\x64\x65\x72\x65\x64\x20\x69\x73\x2d\x73\x74\x72\x69\x70\x65\x64\x20\x69\x73\x2d\x6e\x61\x72\x72\x6f\x77\x20\x69\x73\x2d\x68\x6f\x76\x65\x72\x61\x62\x6c\x65\x20\x69\x73\x2d\x66\x75\x6c\x6c\x77\x69\x64\x74\x68\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x68\x65\x61\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x72\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x68\x3e\x23\x3c\x2f\x74\x68\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x68\x3e\x54\x69\x74\x6c\x65\x3c\x2f\x74\x68\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x68\x3e\x4c\x61\x6e\x67\x75\x61\x67\x65\x3c\x2f\x74\x68\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x68\x3e\x50\x61\x73\x74\x65\x64\x20\x6f\x6e\x3c\x2f\x74\x68\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x68\x3e\x57\x69\x6c\x6c\x20\x65\x78\x70\x69\x72\x65\x20\x6f\x6e\x3c\x2f\x74\x68\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x68\x3e\x50\x61\x73\x74\x65\x20\x74\x79\x70\x65\x3c\x2f\x74\x68\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x74\x72\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x74\x68\x65\x61\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x62\x6f\x64\x79\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x72\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x64\x3e\x7b\x70\x61\x73\x74\x65\x49\x44\x7d\x3c\x2f\x74\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x64\x3e\x7b\x70\x61\x73\x74\x65\x54\x69\x74\x6c\x65\x7d\x3c\x2f\x74\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x64\x3e\x7b\x70\x61\x73\x74\x65\x4c\x61\x6e\x67\x75\x61\x67\x65\x7d\x3c\x2f\x74\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x64\x3e\x7b\x70\x61\x73\x74\x65\x44\x61\x74\x65\x7d\x3c\x2f\x74\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x64\x3e\x7b\x70\x61\x73\x74\x65\x45\x78\x70\x69\x72\x61\x74\x69\x6f\x6e\x7d\x3c\x2f\x74\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x64\x3e\x7b\x70\x61\x73\x74\x65\x54\x79\x70\x65\x7d\x3c\x2f\x74\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x74\x72\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x72\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x64\x20\x63\x6f\x6c\x73\x70\x61\x6e\x3d\x22\x36\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x63\x6c\x61\x73\x73\x3d\x22\x62\x75\x74\x74\x6f\x6e\x22\x20\x68\x72\x65\x66\x3d\x22\x2f\x70\x61\x73\x74\x65\x2f\x7b\x70\x61\x73\x74\x65\x49\x44\x7d\x2f\x7b\x70\x61\x73\x74\x65\x54\x73\x7d\x72\x61\x77\x22\x3e\x56\x69\x65\x77\x20\x72\x61\x77\x3c\x2f\x61\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x74\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x74\x72\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x74\x62\x6f\x64\x79\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x74\x61\x62\x6c\x65\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x73\x65\x63\x74\x69\x6f\x6e\x3e\x0a\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x73\x74\x65\x2d\x64\x61\x74\x61\x22\x3e\x0a\x20\x20\x20\x20\x7b\x70\x61\x73\x74\x65\x64\x61\x74\x61\x7d\x0a\x3c\x2f\x64\x69\x76\x3e")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
|
|
||||||
f, err := FS.OpenFile(CTX, "/paste.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_, err = f.Write(FilePasteHTML)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Code generaTed by fileb0x at "2021-01-09 06:15:34.335668233 +0500 +05 m=+0.040296968" from config file "fileb0x.yml" DO NOT EDIT.
|
|
||||||
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
|
|
||||||
// original path: assets/pastelist_list.html
|
|
||||||
|
|
||||||
package static
|
|
||||||
|
|
||||||
import (
|
|
||||||
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilePastelistListHTML is "/pastelist_list.html"
|
|
||||||
var FilePastelistListHTML = []byte("\x3c\x73\x65\x63\x74\x69\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x73\x65\x63\x74\x69\x6f\x6e\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x7b\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x7b\x70\x61\x73\x74\x65\x73\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x7b\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x73\x65\x63\x74\x69\x6f\x6e\x3e")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
|
|
||||||
f, err := FS.OpenFile(CTX, "/pastelist_list.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_, err = f.Write(FilePastelistListHTML)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Code generaTed by fileb0x at "2021-01-09 06:15:34.336449591 +0500 +05 m=+0.041078378" from config file "fileb0x.yml" DO NOT EDIT.
|
|
||||||
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
|
|
||||||
// original path: assets/pastelist_paste.html
|
|
||||||
|
|
||||||
package static
|
|
||||||
|
|
||||||
import (
|
|
||||||
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilePastelistPasteHTML is "/pastelist_paste.html"
|
|
||||||
var FilePastelistPasteHTML = []byte("\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6f\x6e\x74\x65\x6e\x74\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x61\x72\x64\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x68\x65\x61\x64\x65\x72\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x61\x72\x64\x2d\x68\x65\x61\x64\x65\x72\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x70\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x61\x72\x64\x2d\x68\x65\x61\x64\x65\x72\x2d\x74\x69\x74\x6c\x65\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x50\x61\x73\x74\x65\x20\x23\x7b\x70\x61\x73\x74\x65\x49\x44\x7d\x2c\x20\x70\x6f\x73\x74\x65\x64\x20\x6f\x6e\x20\x7b\x70\x61\x73\x74\x65\x44\x61\x74\x65\x7d\x20\x61\x6e\x64\x20\x74\x69\x74\x6c\x65\x64\x20\x61\x73\x20\x22\x7b\x70\x61\x73\x74\x65\x54\x69\x74\x6c\x65\x7d\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x70\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x68\x65\x61\x64\x65\x72\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x61\x72\x64\x2d\x63\x6f\x6e\x74\x65\x6e\x74\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6f\x6e\x74\x65\x6e\x74\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x70\x72\x65\x3e\x7b\x70\x61\x73\x74\x65\x44\x61\x74\x61\x7d\x3c\x2f\x70\x72\x65\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x66\x6f\x6f\x74\x65\x72\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x61\x72\x64\x2d\x66\x6f\x6f\x74\x65\x72\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x68\x72\x65\x66\x3d\x22\x2f\x70\x61\x73\x74\x65\x2f\x7b\x70\x61\x73\x74\x65\x49\x44\x7d\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x61\x72\x64\x2d\x66\x6f\x6f\x74\x65\x72\x2d\x69\x74\x65\x6d\x20\x62\x75\x74\x74\x6f\x6e\x20\x69\x73\x2d\x73\x75\x63\x63\x65\x73\x73\x20\x69\x73\x2d\x72\x61\x64\x69\x75\x73\x6c\x65\x73\x73\x22\x3e\x56\x69\x65\x77\x3c\x2f\x61\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x66\x6f\x6f\x74\x65\x72\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x64\x69\x76\x3e")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
|
|
||||||
f, err := FS.OpenFile(CTX, "/pastelist_paste.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_, err = f.Write(FilePastelistPasteHTML)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,35 +0,0 @@
|
||||||
// Code generaTed by fileb0x at "2021-01-09 06:15:34.37218288 +0500 +05 m=+0.076811562" from config file "fileb0x.yml" DO NOT EDIT.
|
|
||||||
// modified(2021-01-08 11:51:03.805743275 +0500 +05)
|
|
||||||
// original path: assets/css/style.css
|
|
||||||
|
|
||||||
package static
|
|
||||||
|
|
||||||
import (
|
|
||||||
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileStaticCSSStyleCSS is "static/css/style.css"
|
|
||||||
var FileStaticCSSStyleCSS = []byte("\x23\x70\x61\x73\x74\x65\x2d\x63\x6f\x6e\x74\x65\x6e\x74\x73\x20\x7b\x0a\x20\x20\x20\x20\x66\x6f\x6e\x74\x2d\x66\x61\x6d\x69\x6c\x79\x3a\x20\x6d\x6f\x6e\x6f\x73\x70\x61\x63\x65\x3b\x0a\x20\x20\x20\x20\x66\x6f\x6e\x74\x2d\x73\x69\x7a\x65\x3a\x20\x30\x2e\x39\x72\x65\x6d\x3b\x0a\x20\x20\x20\x20\x68\x65\x69\x67\x68\x74\x3a\x20\x39\x30\x76\x68\x3b\x0a\x7d\x0a\x0a\x2e\x70\x61\x73\x74\x65\x2d\x64\x61\x74\x61\x20\x7b\x0a\x20\x20\x20\x20\x66\x6f\x6e\x74\x2d\x73\x69\x7a\x65\x3a\x20\x30\x2e\x39\x72\x65\x6d\x3b\x0a\x7d\x0a\x0a\x2f\x2a\x20\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x73\x70\x65\x63\x69\x61\x6c\x20\x63\x61\x73\x65\x20\x66\x6f\x72\x20\x6d\x75\x6c\x74\x69\x6c\x69\x6e\x65\x20\x74\x6f\x6f\x6c\x74\x69\x70\x73\x2e\x20\x53\x65\x65\x20\x68\x74\x74\x70\x73\x3a\x2f\x2f\x67\x69\x74\x68\x75\x62\x2e\x63\x6f\x6d\x2f\x57\x69\x6b\x69\x6b\x69\x2f\x62\x75\x6c\x6d\x61\x2d\x74\x6f\x6f\x6c\x74\x69\x70\x2f\x69\x73\x73\x75\x65\x73\x2f\x33\x34\x20\x2a\x2f\x0a\x2e\x74\x6f\x6f\x6c\x74\x69\x70\x2e\x69\x73\x2d\x74\x6f\x6f\x6c\x74\x69\x70\x2d\x6d\x75\x6c\x74\x69\x6c\x69\x6e\x65\x3a\x3a\x62\x65\x66\x6f\x72\x65\x20\x7b\x0a\x20\x20\x20\x20\x77\x68\x69\x74\x65\x2d\x73\x70\x61\x63\x65\x3a\x70\x72\x65\x2d\x6c\x69\x6e\x65\x0a\x7d")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
|
|
||||||
f, err := FS.OpenFile(CTX, "static/css/style.css", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_, err = f.Write(FileStaticCSSStyleCSS)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
1
assets/static/css/bulma-0.9.4.min.css
vendored
Normal file
1
assets/static/css/bulma-0.9.4.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
2
assets/static/css/bulma-tooltip-1.2.min.css
vendored
Executable file
2
assets/static/css/bulma-tooltip-1.2.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
1
assets/static/css/bulma.css.map
Normal file
1
assets/static/css/bulma.css.map
Normal file
File diff suppressed because one or more lines are too long
|
@ -25,12 +25,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/domains/dbnotavailable"
|
"go.dev.pztrn.name/fastpastebin/domains/dbnotavailable"
|
||||||
"go.dev.pztrn.name/fastpastebin/domains/indexpage"
|
"go.dev.pztrn.name/fastpastebin/domains/indexpage"
|
||||||
"go.dev.pztrn.name/fastpastebin/domains/pastes"
|
"go.dev.pztrn.name/fastpastebin/domains/pastes"
|
||||||
|
@ -41,29 +40,29 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
c := context.New()
|
appCtx := context.New()
|
||||||
c.Initialize()
|
appCtx.Initialize()
|
||||||
|
|
||||||
c.Logger.Info().Msg("Starting Fast Pastebin...")
|
appCtx.Logger.Info().Msg("Starting Fast Pastebin...")
|
||||||
|
|
||||||
// Here goes initial initialization for packages that want CLI flags
|
// Here goes initial initialization for packages that want CLI flags
|
||||||
// to be added.
|
// to be added.
|
||||||
|
|
||||||
// Parse flags.
|
// Parse flags.
|
||||||
c.Flagger.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// Continue loading.
|
// Continue loading.
|
||||||
c.LoadConfiguration()
|
appCtx.LoadConfiguration()
|
||||||
c.InitializePost()
|
appCtx.InitializePost()
|
||||||
database.New(c)
|
database.New(appCtx)
|
||||||
c.Database.Initialize()
|
appCtx.Database.Initialize()
|
||||||
templater.Initialize(c)
|
templater.Initialize(appCtx)
|
||||||
|
|
||||||
captcha.New(c)
|
captcha.New(appCtx)
|
||||||
|
|
||||||
dbnotavailable.New(c)
|
dbnotavailable.New(appCtx)
|
||||||
indexpage.New(c)
|
indexpage.New(appCtx)
|
||||||
pastes.New(c)
|
pastes.New(appCtx)
|
||||||
|
|
||||||
// CTRL+C handler.
|
// CTRL+C handler.
|
||||||
signalHandler := make(chan os.Signal, 1)
|
signalHandler := make(chan os.Signal, 1)
|
||||||
|
@ -73,7 +72,7 @@ func main() {
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-signalHandler
|
<-signalHandler
|
||||||
c.Shutdown()
|
appCtx.Shutdown()
|
||||||
shutdownDone <- true
|
shutdownDone <- true
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ volumes:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
image: mariadb:10.3
|
image: mariadb:10.5
|
||||||
container_name: database
|
container_name: database
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
@ -25,23 +25,21 @@
|
||||||
package dbnotavailable
|
package dbnotavailable
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
|
||||||
|
|
||||||
// other
|
|
||||||
"github.com/labstack/echo"
|
"github.com/labstack/echo"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Database not available error page
|
// Database not available error page.
|
||||||
func dbNotAvailableGet(ec echo.Context) error {
|
func dbNotAvailableGet(ec echo.Context) error {
|
||||||
htmlData := templater.GetTemplate(ec, "database_not_available.html", nil)
|
htmlData := templater.GetTemplate(ec, "database_not_available.html", nil)
|
||||||
|
|
||||||
|
//nolint:wrapcheck
|
||||||
return ec.HTML(http.StatusInternalServerError, htmlData)
|
return ec.HTML(http.StatusInternalServerError, htmlData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dbNotAvailableRawGet(ec echo.Context) error {
|
func dbNotAvailableRawGet(ec echo.Context) error {
|
||||||
|
//nolint:wrapcheck
|
||||||
return ec.String(http.StatusInternalServerError, "Database not available\nSomething went wrong while trying to connect to database. Check logs for details.")
|
return ec.String(http.StatusInternalServerError, "Database not available\nSomething went wrong while trying to connect to database. Check logs for details.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,19 +25,16 @@
|
||||||
package dbnotavailable
|
package dbnotavailable
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var ctx *context.Context
|
||||||
c *context.Context
|
|
||||||
)
|
|
||||||
|
|
||||||
// New initializes pastes package and adds necessary HTTP and API
|
// New initializes pastes package and adds necessary HTTP and API
|
||||||
// endpoints.
|
// endpoints.
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
c = cc
|
ctx = cc
|
||||||
|
|
||||||
c.Echo.GET("/database_not_available", dbNotAvailableGet)
|
ctx.Echo.GET("/database_not_available", dbNotAvailableGet)
|
||||||
c.Echo.GET("/database_not_available/raw", dbNotAvailableRawGet)
|
ctx.Echo.GET("/database_not_available/raw", dbNotAvailableRawGet)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,18 +25,15 @@
|
||||||
package indexpage
|
package indexpage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var ctx *context.Context
|
||||||
c *context.Context
|
|
||||||
)
|
|
||||||
|
|
||||||
// New initializes pastes package and adds necessary HTTP and API
|
// New initializes pastes package and adds necessary HTTP and API
|
||||||
// endpoints.
|
// endpoints.
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
c = cc
|
ctx = cc
|
||||||
|
|
||||||
c.Echo.GET("/", indexGet)
|
ctx.Echo.GET("/", indexGet)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,30 +25,28 @@
|
||||||
package indexpage
|
package indexpage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
// local
|
"github.com/alecthomas/chroma/v2/lexers"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/captcha"
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
|
||||||
|
|
||||||
// other
|
|
||||||
"github.com/alecthomas/chroma/lexers"
|
|
||||||
"github.com/labstack/echo"
|
"github.com/labstack/echo"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/internal/captcha"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Index of this site.
|
// Index of this site.
|
||||||
func indexGet(ec echo.Context) error {
|
func indexGet(ectx echo.Context) error {
|
||||||
// We should check if database connection available.
|
// We should check if database connection available.
|
||||||
dbConn := c.Database.GetDatabaseConnection()
|
dbConn := ctx.Database.GetDatabaseConnection()
|
||||||
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
|
||||||
return ec.Redirect(http.StatusFound, "/database_not_available")
|
//nolint:wrapcheck
|
||||||
|
return ectx.Redirect(http.StatusFound, "/database_not_available")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate list of available languages to highlight.
|
// Generate list of available languages to highlight.
|
||||||
availableLexers := lexers.Names(false)
|
availableLexers := lexers.Names(false)
|
||||||
|
|
||||||
var availableLexersSelectOpts = "<option value='text'>Text</option><option value='autodetect'>Auto detect</option><option disabled>-----</option>"
|
availableLexersSelectOpts := "<option value='text'>Text</option><option value='autodetect'>Auto detect</option><option disabled>-----</option>"
|
||||||
for i := range availableLexers {
|
for i := range availableLexers {
|
||||||
availableLexersSelectOpts += "<option value='" + availableLexers[i] + "'>" + availableLexers[i] + "</option>"
|
availableLexersSelectOpts += "<option value='" + availableLexers[i] + "'>" + availableLexers[i] + "</option>"
|
||||||
}
|
}
|
||||||
|
@ -56,7 +54,8 @@ func indexGet(ec echo.Context) error {
|
||||||
// Captcha.
|
// Captcha.
|
||||||
captchaString := captcha.NewCaptcha()
|
captchaString := captcha.NewCaptcha()
|
||||||
|
|
||||||
htmlData := templater.GetTemplate(ec, "index.html", map[string]string{"lexers": availableLexersSelectOpts, "captchaString": captchaString})
|
htmlData := templater.GetTemplate(ectx, "index.html", map[string]string{"lexers": availableLexersSelectOpts, "captchaString": captchaString})
|
||||||
|
|
||||||
return ec.HTML(http.StatusOK, htmlData)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusOK, htmlData)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,43 +25,37 @@
|
||||||
package pastes
|
package pastes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var regexInts = regexp.MustCompile("[0-9]+")
|
||||||
regexInts = regexp.MustCompile("[0-9]+")
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var ctx *context.Context
|
||||||
c *context.Context
|
|
||||||
)
|
|
||||||
|
|
||||||
// New initializes pastes package and adds necessary HTTP and API
|
// New initializes pastes package and adds necessary HTTP and API
|
||||||
// endpoints.
|
// endpoints.
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
c = cc
|
ctx = cc
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// HTTP endpoints.
|
// HTTP endpoints.
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// New paste.
|
// New paste.
|
||||||
c.Echo.POST("/paste/", pastePOSTWebInterface)
|
ctx.Echo.POST("/paste/", pastePOSTWebInterface)
|
||||||
// Show public paste.
|
// Show public paste.
|
||||||
c.Echo.GET("/paste/:id", pasteGETWebInterface)
|
ctx.Echo.GET("/paste/:id", pasteGETWebInterface)
|
||||||
// Show RAW representation of public paste.
|
// Show RAW representation of public paste.
|
||||||
c.Echo.GET("/paste/:id/raw", pasteRawGETWebInterface)
|
ctx.Echo.GET("/paste/:id/raw", pasteRawGETWebInterface)
|
||||||
// Show private paste.
|
// Show private paste.
|
||||||
c.Echo.GET("/paste/:id/:timestamp", pasteGETWebInterface)
|
ctx.Echo.GET("/paste/:id/:timestamp", pasteGETWebInterface)
|
||||||
// Show RAW representation of private paste.
|
// Show RAW representation of private paste.
|
||||||
c.Echo.GET("/paste/:id/:timestamp/raw", pasteRawGETWebInterface)
|
ctx.Echo.GET("/paste/:id/:timestamp/raw", pasteRawGETWebInterface)
|
||||||
// Verify access to passworded paste.
|
// Verify access to passworded paste.
|
||||||
c.Echo.GET("/paste/:id/:timestamp/verify", pastePasswordedVerifyGet)
|
ctx.Echo.GET("/paste/:id/:timestamp/verify", pastePasswordedVerifyGet)
|
||||||
c.Echo.POST("/paste/:id/:timestamp/verify", pastePasswordedVerifyPost)
|
ctx.Echo.POST("/paste/:id/:timestamp/verify", pastePasswordedVerifyPost)
|
||||||
// Pastes list.
|
// Pastes list.
|
||||||
c.Echo.GET("/pastes/", pastesGET)
|
ctx.Echo.GET("/pastes/", pastesGET)
|
||||||
c.Echo.GET("/pastes/:page", pastesGET)
|
ctx.Echo.GET("/pastes/:page", pastesGET)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,18 @@
|
||||||
package pastes
|
package pastes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
// local
|
htmlfmt "github.com/alecthomas/chroma/v2/formatters/html"
|
||||||
|
"github.com/alecthomas/chroma/v2/lexers"
|
||||||
|
"github.com/alecthomas/chroma/v2/styles"
|
||||||
|
"github.com/labstack/echo"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
||||||
|
|
||||||
// other
|
|
||||||
"github.com/alecthomas/chroma"
|
|
||||||
"github.com/alecthomas/chroma/formatters"
|
|
||||||
htmlfmt "github.com/alecthomas/chroma/formatters/html"
|
|
||||||
"github.com/alecthomas/chroma/lexers"
|
|
||||||
"github.com/alecthomas/chroma/styles"
|
|
||||||
"github.com/labstack/echo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -35,23 +30,26 @@ const (
|
||||||
// value (they both will be ignored), but private will.
|
// value (they both will be ignored), but private will.
|
||||||
func pasteGetData(pasteID int, timestamp int64, cookieValue string) (*structs.Paste, string) {
|
func pasteGetData(pasteID int, timestamp int64, cookieValue string) (*structs.Paste, string) {
|
||||||
// Get paste.
|
// Get paste.
|
||||||
paste, err1 := c.Database.GetPaste(pasteID)
|
paste, err1 := ctx.Database.GetPaste(pasteID)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste")
|
ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste")
|
||||||
|
|
||||||
return nil, pasteNotFound
|
return nil, pasteNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if paste is expired.
|
// Check if paste is expired.
|
||||||
if paste.IsExpired() {
|
if paste.IsExpired() {
|
||||||
c.Logger.Error().Int("paste ID", pasteID).Msg("Paste is expired")
|
ctx.Logger.Error().Int("paste ID", pasteID).Msg("Paste is expired")
|
||||||
|
|
||||||
return nil, pasteExpired
|
return nil, pasteExpired
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have a private paste and it's parameters are correct.
|
// Check if we have a private paste and it's parameters are correct.
|
||||||
if paste.Private {
|
if paste.Private {
|
||||||
pasteTs := paste.CreatedAt.Unix()
|
pasteTS := paste.CreatedAt.Unix()
|
||||||
if timestamp != pasteTs {
|
if timestamp != pasteTS {
|
||||||
c.Logger.Error().Int("paste ID", pasteID).Int64("paste timestamp", pasteTs).Int64("provided timestamp", timestamp).Msg("Incorrect timestamp provided for private paste")
|
ctx.Logger.Error().Int("paste ID", pasteID).Int64("paste timestamp", pasteTS).Int64("provided timestamp", timestamp).Msg("Incorrect timestamp provided for private paste")
|
||||||
|
|
||||||
return nil, pasteTimestampInvalid
|
return nil, pasteTimestampInvalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,28 +71,29 @@ func pasteGetData(pasteID int, timestamp int64, cookieValue string) (*structs.Pa
|
||||||
|
|
||||||
// GET for "/paste/PASTE_ID" and "/paste/PASTE_ID/TIMESTAMP" (private pastes).
|
// GET for "/paste/PASTE_ID" and "/paste/PASTE_ID/TIMESTAMP" (private pastes).
|
||||||
// Web interface version.
|
// Web interface version.
|
||||||
func pasteGETWebInterface(ec echo.Context) error {
|
func pasteGETWebInterface(ectx echo.Context) error {
|
||||||
pasteIDRaw := ec.Param("id")
|
pasteIDRaw := ectx.Param("id")
|
||||||
// We already get numbers from string, so we will not check strconv.Atoi()
|
// We already get numbers from string, so we will not check strconv.Atoi()
|
||||||
// error.
|
// error.
|
||||||
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
|
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
|
||||||
pasteIDStr := strconv.Itoa(pasteID)
|
pasteIDStr := strconv.Itoa(pasteID)
|
||||||
c.Logger.Debug().Int("paste ID", pasteID).Msg("Trying to get paste data")
|
ctx.Logger.Debug().Int("paste ID", pasteID).Msg("Trying to get paste data")
|
||||||
|
|
||||||
// Check if we have timestamp passed.
|
// Check if we have timestamp passed.
|
||||||
// If passed timestamp is invalid (isn't a real UNIX timestamp) we
|
// If passed timestamp is invalid (isn't a real UNIX timestamp) we
|
||||||
// will show 404 Not Found error and spam about that in logs.
|
// will show 404 Not Found error and spam about that in logs.
|
||||||
var timestamp int64
|
var timestamp int64
|
||||||
|
|
||||||
tsProvidedStr := ec.Param("timestamp")
|
tsProvidedStr := ectx.Param("timestamp")
|
||||||
if tsProvidedStr != "" {
|
if tsProvidedStr != "" {
|
||||||
tsProvided, err := strconv.ParseInt(tsProvidedStr, 10, 64)
|
tsProvided, err := strconv.ParseInt(tsProvidedStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error().Err(err).Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Msg("Invalid timestamp provided for getting private paste")
|
ctx.Logger.Error().Err(err).Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Msg("Invalid timestamp provided for getting private paste")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDStr+" not found")
|
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+pasteIDStr+" not found")
|
||||||
|
|
||||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusBadRequest, errtpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp = tsProvided
|
timestamp = tsProvided
|
||||||
|
@ -104,24 +103,28 @@ func pasteGETWebInterface(ec echo.Context) error {
|
||||||
// for private pastes.
|
// for private pastes.
|
||||||
var cookieValue string
|
var cookieValue string
|
||||||
|
|
||||||
cookie, err1 := ec.Cookie("PASTE-" + pasteIDStr)
|
cookie, err1 := ectx.Cookie("PASTE-" + pasteIDStr)
|
||||||
if err1 == nil {
|
if err1 == nil {
|
||||||
cookieValue = cookie.Value
|
cookieValue = cookie.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
paste, error := pasteGetData(pasteID, timestamp, cookieValue)
|
paste, err := pasteGetData(pasteID, timestamp, cookieValue)
|
||||||
|
|
||||||
// For these cases we should return 404 Not Found page.
|
// For these cases we should return 404 Not Found page.
|
||||||
if error == pasteExpired || error == pasteNotFound || error == pasteTimestampInvalid {
|
if err == pasteExpired || err == pasteNotFound || err == pasteTimestampInvalid {
|
||||||
errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDRaw+" not found")
|
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+pasteIDRaw+" not found")
|
||||||
return ec.HTML(http.StatusNotFound, errtpl)
|
|
||||||
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusNotFound, errtpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If passed cookie value was invalid - go to paste authorization
|
// If passed cookie value was invalid - go to paste authorization
|
||||||
// page.
|
// page.
|
||||||
if error == pasteCookieInvalid {
|
if err == pasteCookieInvalid {
|
||||||
c.Logger.Info().Int("paste ID", pasteID).Msg("Invalid cookie, redirecting to auth page")
|
ctx.Logger.Info().Int("paste ID", pasteID).Msg("Invalid cookie, redirecting to auth page")
|
||||||
return ec.Redirect(http.StatusMovedPermanently, "/paste/"+pasteIDStr+"/"+ec.Param("timestamp")+"/verify")
|
|
||||||
|
//nolint:wrapcheck
|
||||||
|
return ectx.Redirect(http.StatusMovedPermanently, "/paste/"+pasteIDStr+"/"+ectx.Param("timestamp")+"/verify")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format paste data map.
|
// Format paste data map.
|
||||||
|
@ -155,7 +158,7 @@ func pasteGETWebInterface(ec echo.Context) error {
|
||||||
// Tokenize paste data.
|
// Tokenize paste data.
|
||||||
lexered, err3 := lexer.Tokenise(nil, paste.Data)
|
lexered, err3 := lexer.Tokenise(nil, paste.Data)
|
||||||
if err3 != nil {
|
if err3 != nil {
|
||||||
c.Logger.Error().Err(err3).Msg("Failed to tokenize paste data")
|
ctx.Logger.Error().Err(err3).Msg("Failed to tokenize paste data")
|
||||||
}
|
}
|
||||||
// Get style for HTML output.
|
// Get style for HTML output.
|
||||||
style := styles.Get("monokai")
|
style := styles.Get("monokai")
|
||||||
|
@ -163,59 +166,61 @@ func pasteGETWebInterface(ec echo.Context) error {
|
||||||
style = styles.Fallback
|
style = styles.Fallback
|
||||||
}
|
}
|
||||||
// Get HTML formatter.
|
// Get HTML formatter.
|
||||||
formatter := chroma.Formatter(htmlfmt.New(htmlfmt.WithLineNumbers(true), htmlfmt.LineNumbersInTable(true), htmlfmt.LinkableLineNumbers(true, "L")))
|
formatter := htmlfmt.New(htmlfmt.WithLineNumbers(true), htmlfmt.LineNumbersInTable(true), htmlfmt.LinkableLineNumbers(true, "L"))
|
||||||
if formatter == nil {
|
|
||||||
formatter = formatters.Fallback
|
|
||||||
}
|
|
||||||
// Create buffer and format into it.
|
// Create buffer and format into it.
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
err4 := formatter.Format(buf, style, lexered)
|
err4 := formatter.Format(buf, style, lexered)
|
||||||
if err4 != nil {
|
if err4 != nil {
|
||||||
c.Logger.Error().Err(err4).Msg("Failed to format paste data")
|
ctx.Logger.Error().Err(err4).Msg("Failed to format paste data")
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteData["pastedata"] = buf.String()
|
pasteData["pastedata"] = buf.String()
|
||||||
|
|
||||||
// Get template and format it.
|
// Get template and format it.
|
||||||
pasteHTML := templater.GetTemplate(ec, "paste.html", pasteData)
|
pasteHTML := templater.GetTemplate(ectx, "paste.html", pasteData)
|
||||||
|
|
||||||
return ec.HTML(http.StatusOK, pasteHTML)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusOK, pasteHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET for "/paste/PASTE_ID/TIMESTAMP/verify" - a password verify page.
|
// GET for "/paste/PASTE_ID/TIMESTAMP/verify" - a password verify page.
|
||||||
func pastePasswordedVerifyGet(ec echo.Context) error {
|
func pastePasswordedVerifyGet(ectx echo.Context) error {
|
||||||
pasteIDRaw := ec.Param("id")
|
pasteIDRaw := ectx.Param("id")
|
||||||
timestampRaw := ec.Param("timestamp")
|
timestampRaw := ectx.Param("timestamp")
|
||||||
// We already get numbers from string, so we will not check strconv.Atoi()
|
// We already get numbers from string, so we will not check strconv.Atoi()
|
||||||
// error.
|
// error.
|
||||||
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
|
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
|
||||||
|
|
||||||
// Get paste.
|
// Get paste.
|
||||||
paste, err1 := c.Database.GetPaste(pasteID)
|
paste, err1 := ctx.Database.GetPaste(pasteID)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste data")
|
ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste data")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDRaw+" not found")
|
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+pasteIDRaw+" not found")
|
||||||
|
|
||||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusBadRequest, errtpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for auth cookie. If present - redirect to paste.
|
// Check for auth cookie. If present - redirect to paste.
|
||||||
cookie, err := ec.Cookie("PASTE-" + strconv.Itoa(pasteID))
|
cookie, err := ectx.Cookie("PASTE-" + strconv.Itoa(pasteID))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// No cookie, redirect to auth page.
|
// No cookie, redirect to auth page.
|
||||||
c.Logger.Debug().Msg("Paste cookie found, checking it...")
|
ctx.Logger.Debug().Msg("Paste cookie found, checking it...")
|
||||||
|
|
||||||
// Generate cookie value to check.
|
// Generate cookie value to check.
|
||||||
cookieValue := paste.GenerateCryptedCookieValue()
|
cookieValue := paste.GenerateCryptedCookieValue()
|
||||||
|
|
||||||
if cookieValue == cookie.Value {
|
if cookieValue == cookie.Value {
|
||||||
c.Logger.Info().Msg("Valid cookie, redirecting to paste page...")
|
ctx.Logger.Info().Msg("Valid cookie, redirecting to paste page...")
|
||||||
return ec.Redirect(http.StatusMovedPermanently, "/paste/"+pasteIDRaw+"/"+ec.Param("timestamp"))
|
|
||||||
|
//nolint:wrapcheck
|
||||||
|
return ectx.Redirect(http.StatusMovedPermanently, "/paste/"+pasteIDRaw+"/"+ectx.Param("timestamp"))
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Logger.Debug().Msg("Invalid cookie, showing auth page")
|
ctx.Logger.Debug().Msg("Invalid cookie, showing auth page")
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTML data.
|
// HTML data.
|
||||||
|
@ -223,43 +228,47 @@ func pastePasswordedVerifyGet(ec echo.Context) error {
|
||||||
htmlData["pasteID"] = strconv.Itoa(pasteID)
|
htmlData["pasteID"] = strconv.Itoa(pasteID)
|
||||||
htmlData["pasteTimestamp"] = timestampRaw
|
htmlData["pasteTimestamp"] = timestampRaw
|
||||||
|
|
||||||
verifyHTML := templater.GetTemplate(ec, "passworded_paste_verify.html", htmlData)
|
verifyHTML := templater.GetTemplate(ectx, "passworded_paste_verify.html", htmlData)
|
||||||
|
|
||||||
return ec.HTML(http.StatusOK, verifyHTML)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusOK, verifyHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST for "/paste/PASTE_ID/TIMESTAMP/verify" - a password verify page.
|
// POST for "/paste/PASTE_ID/TIMESTAMP/verify" - a password verify page.
|
||||||
func pastePasswordedVerifyPost(ec echo.Context) error {
|
func pastePasswordedVerifyPost(ectx echo.Context) error {
|
||||||
// We should check if database connection available.
|
// We should check if database connection available.
|
||||||
dbConn := c.Database.GetDatabaseConnection()
|
dbConn := ctx.Database.GetDatabaseConnection()
|
||||||
|
|
||||||
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
|
||||||
return ec.Redirect(http.StatusFound, "/database_not_available")
|
//nolint:wrapcheck
|
||||||
|
return ectx.Redirect(http.StatusFound, "/database_not_available")
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteIDRaw := ec.Param("id")
|
pasteIDRaw := ectx.Param("id")
|
||||||
timestampRaw := ec.Param("timestamp")
|
timestampRaw := ectx.Param("timestamp")
|
||||||
// We already get numbers from string, so we will not check strconv.Atoi()
|
// We already get numbers from string, so we will not check strconv.Atoi()
|
||||||
// error.
|
// error.
|
||||||
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
|
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
|
||||||
c.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste")
|
ctx.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste")
|
||||||
|
|
||||||
// Get paste.
|
// Get paste.
|
||||||
paste, err1 := c.Database.GetPaste(pasteID)
|
paste, err1 := ctx.Database.GetPaste(pasteID)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste")
|
ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste")
|
||||||
errtpl := templater.GetErrorTemplate(ec, "Paste #"+strconv.Itoa(pasteID)+" not found")
|
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+strconv.Itoa(pasteID)+" not found")
|
||||||
|
|
||||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusBadRequest, errtpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
params, err2 := ec.FormParams()
|
params, err2 := ectx.FormParams()
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
c.Logger.Debug().Msg("No form parameters passed")
|
ctx.Logger.Debug().Msg("No form parameters passed")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ec, "Paste #"+strconv.Itoa(pasteID)+" not found")
|
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+strconv.Itoa(pasteID)+" not found")
|
||||||
|
|
||||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusBadRequest, errtpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
if paste.VerifyPassword(params["paste-password"][0]) {
|
if paste.VerifyPassword(params["paste-password"][0]) {
|
||||||
|
@ -269,69 +278,79 @@ func pastePasswordedVerifyPost(ec echo.Context) error {
|
||||||
cookie.Name = "PASTE-" + strconv.Itoa(pasteID)
|
cookie.Name = "PASTE-" + strconv.Itoa(pasteID)
|
||||||
cookie.Value = paste.GenerateCryptedCookieValue()
|
cookie.Value = paste.GenerateCryptedCookieValue()
|
||||||
cookie.Expires = time.Now().Add(24 * time.Hour)
|
cookie.Expires = time.Now().Add(24 * time.Hour)
|
||||||
ec.SetCookie(cookie)
|
ectx.SetCookie(cookie)
|
||||||
|
|
||||||
return ec.Redirect(http.StatusFound, "/paste/"+strconv.Itoa(pasteID)+"/"+timestampRaw)
|
//nolint:wrapcheck
|
||||||
|
return ectx.Redirect(http.StatusFound, "/paste/"+strconv.Itoa(pasteID)+"/"+timestampRaw)
|
||||||
}
|
}
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ec, "Invalid password. Please, try again.")
|
errtpl := templater.GetErrorTemplate(ectx, "Invalid password. Please, try again.")
|
||||||
|
|
||||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusBadRequest, errtpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET for "/pastes/:id/raw", raw paste output.
|
// GET for "/pastes/:id/raw", raw paste output.
|
||||||
// Web interface version.
|
// Web interface version.
|
||||||
func pasteRawGETWebInterface(ec echo.Context) error {
|
func pasteRawGETWebInterface(ectx echo.Context) error {
|
||||||
// We should check if database connection available.
|
// We should check if database connection available.
|
||||||
dbConn := c.Database.GetDatabaseConnection()
|
dbConn := ctx.Database.GetDatabaseConnection()
|
||||||
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
|
||||||
return ec.Redirect(http.StatusFound, "/database_not_available/raw")
|
//nolint:wrapcheck
|
||||||
|
return ectx.Redirect(http.StatusFound, "/database_not_available/raw")
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteIDRaw := ec.Param("id")
|
pasteIDRaw := ectx.Param("id")
|
||||||
// We already get numbers from string, so we will not check strconv.Atoi()
|
// We already get numbers from string, so we will not check strconv.Atoi()
|
||||||
// error.
|
// error.
|
||||||
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
|
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
|
||||||
c.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste data")
|
ctx.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste data")
|
||||||
|
|
||||||
// Get paste.
|
// Get paste.
|
||||||
paste, err1 := c.Database.GetPaste(pasteID)
|
paste, err1 := ctx.Database.GetPaste(pasteID)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste from database")
|
ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste from database")
|
||||||
return ec.HTML(http.StatusBadRequest, "Paste #"+pasteIDRaw+" does not exist.")
|
|
||||||
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusBadRequest, "Paste #"+pasteIDRaw+" does not exist.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if paste.IsExpired() {
|
if paste.IsExpired() {
|
||||||
c.Logger.Error().Int("paste ID", pasteID).Msg("Paste is expired")
|
ctx.Logger.Error().Int("paste ID", pasteID).Msg("Paste is expired")
|
||||||
return ec.HTML(http.StatusBadRequest, "Paste #"+pasteIDRaw+" does not exist.")
|
|
||||||
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusBadRequest, "Paste #"+pasteIDRaw+" does not exist.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have a private paste and it's parameters are correct.
|
// Check if we have a private paste and it's parameters are correct.
|
||||||
if paste.Private {
|
if paste.Private {
|
||||||
tsProvidedStr := ec.Param("timestamp")
|
tsProvidedStr := ectx.Param("timestamp")
|
||||||
|
|
||||||
tsProvided, err2 := strconv.ParseInt(tsProvidedStr, 10, 64)
|
tsProvided, err2 := strconv.ParseInt(tsProvidedStr, 10, 64)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
c.Logger.Error().Err(err2).Int("paste ID", pasteID).Str("provided timestamp", tsProvidedStr).Msg("Invalid timestamp provided for getting private paste")
|
ctx.Logger.Error().Err(err2).Int("paste ID", pasteID).Str("provided timestamp", tsProvidedStr).Msg("Invalid timestamp provided for getting private paste")
|
||||||
|
|
||||||
return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
|
//nolint:wrapcheck
|
||||||
|
return ectx.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteTs := paste.CreatedAt.Unix()
|
pasteTS := paste.CreatedAt.Unix()
|
||||||
if tsProvided != pasteTs {
|
if tsProvided != pasteTS {
|
||||||
c.Logger.Error().Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Int64("paste timestamp", pasteTs).Msg("Incorrect timestamp provided for private paste")
|
ctx.Logger.Error().Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Int64("paste timestamp", pasteTS).Msg("Incorrect timestamp provided for private paste")
|
||||||
|
|
||||||
return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
|
//nolint:wrapcheck
|
||||||
|
return ectx.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint
|
//nolint
|
||||||
// ToDo: figure out how to handle passworded pastes here.
|
// ToDo: figure out how to handle passworded pastes here.
|
||||||
// Return error for now.
|
// Return error for now.
|
||||||
if paste.Password != "" {
|
if paste.Password != "" {
|
||||||
c.Logger.Error().Int("paste ID", pasteID).Msg("Cannot render paste as raw: passworded paste. Patches welcome!")
|
ctx.Logger.Error().Int("paste ID", pasteID).Msg("Cannot render paste as raw: passworded paste. Patches welcome!")
|
||||||
return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
|
return ectx.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return ec.String(http.StatusOK, paste.Data)
|
//nolint:wrapcheck
|
||||||
|
return ectx.String(http.StatusOK, paste.Data)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,70 +1,75 @@
|
||||||
package pastes
|
package pastes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
// local
|
"github.com/alecthomas/chroma/v2/lexers"
|
||||||
|
"github.com/labstack/echo"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/captcha"
|
"go.dev.pztrn.name/fastpastebin/internal/captcha"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
||||||
|
|
||||||
// other
|
|
||||||
"github.com/alecthomas/chroma/lexers"
|
|
||||||
"github.com/labstack/echo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const KeepPastesForever = "forever"
|
||||||
|
|
||||||
// POST for "/paste/" which will create new paste and redirect to
|
// POST for "/paste/" which will create new paste and redirect to
|
||||||
// "/pastes/CREATED_PASTE_ID". This handler will do all the job for
|
// "/pastes/CREATED_PASTE_ID". This handler will do all the job for
|
||||||
// requests comes from browsers via web interface.
|
// requests comes from browsers via web interface.
|
||||||
func pastePOSTWebInterface(ec echo.Context) error {
|
func pastePOSTWebInterface(ectx echo.Context) error {
|
||||||
// We should check if database connection available.
|
// We should check if database connection available.
|
||||||
dbConn := c.Database.GetDatabaseConnection()
|
dbConn := ctx.Database.GetDatabaseConnection()
|
||||||
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
|
||||||
return ec.Redirect(http.StatusFound, "/database_not_available")
|
//nolint:wrapcheck
|
||||||
|
return ectx.Redirect(http.StatusFound, "/database_not_available")
|
||||||
}
|
}
|
||||||
|
|
||||||
params, err := ec.FormParams()
|
params, err := ectx.FormParams()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error().Msg("Passed paste form is empty")
|
ctx.Logger.Error().Msg("Passed paste form is empty")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ec, "Cannot create empty paste")
|
errtpl := templater.GetErrorTemplate(ectx, "Cannot create empty paste")
|
||||||
|
|
||||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusBadRequest, errtpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Logger.Debug().Msgf("Received parameters: %+v", params)
|
ctx.Logger.Debug().Msgf("Received parameters: %+v", params)
|
||||||
|
|
||||||
// Do nothing if paste contents is empty.
|
// Do nothing if paste contents is empty.
|
||||||
if len(params["paste-contents"][0]) == 0 {
|
if len(params["paste-contents"][0]) == 0 {
|
||||||
c.Logger.Debug().Msg("Empty paste submitted, ignoring")
|
ctx.Logger.Debug().Msg("Empty paste submitted, ignoring")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ec, "Empty pastes aren't allowed.")
|
errtpl := templater.GetErrorTemplate(ectx, "Empty pastes aren't allowed.")
|
||||||
|
|
||||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusBadRequest, errtpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.ContainsAny(params["paste-keep-for"][0], "Mmhd") && params["paste-keep-for"][0] != "forever" {
|
if !strings.ContainsAny(params["paste-keep-for"][0], "Mmhd") && params["paste-keep-for"][0] != KeepPastesForever {
|
||||||
c.Logger.Debug().Str("field value", params["paste-keep-for"][0]).Msg("'Keep paste for' field have invalid value")
|
ctx.Logger.Debug().Str("field value", params["paste-keep-for"][0]).Msg("'Keep paste for' field have invalid value")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ec, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).")
|
errtpl := templater.GetErrorTemplate(ectx, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).")
|
||||||
|
|
||||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusBadRequest, errtpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify captcha.
|
// Verify captcha.
|
||||||
if !captcha.Verify(params["paste-captcha-id"][0], params["paste-captcha-solution"][0]) {
|
if !captcha.Verify(params["paste-captcha-id"][0], params["paste-captcha-solution"][0]) {
|
||||||
c.Logger.Debug().Str("captcha ID", params["paste-captcha-id"][0]).Str("captcha solution", params["paste-captcha-solution"][0]).Msg("Invalid captcha solution")
|
ctx.Logger.Debug().Str("captcha ID", params["paste-captcha-id"][0]).Str("captcha solution", params["paste-captcha-solution"][0]).Msg("Invalid captcha solution")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ec, "Invalid captcha solution.")
|
errtpl := templater.GetErrorTemplate(ectx, "Invalid captcha solution.")
|
||||||
|
|
||||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusBadRequest, errtpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:exhaustruct
|
||||||
paste := &structs.Paste{
|
paste := &structs.Paste{
|
||||||
Title: params["paste-title"][0],
|
Title: params["paste-title"][0],
|
||||||
Data: params["paste-contents"][0],
|
Data: params["paste-contents"][0],
|
||||||
|
@ -80,7 +85,7 @@ func pastePOSTWebInterface(ec echo.Context) error {
|
||||||
keepFor := 0
|
keepFor := 0
|
||||||
keepForUnit := 0
|
keepForUnit := 0
|
||||||
|
|
||||||
if params["paste-keep-for"][0] != "forever" {
|
if params["paste-keep-for"][0] != KeepPastesForever {
|
||||||
keepForUnitRegex := regexp.MustCompile("[Mmhd]")
|
keepForUnitRegex := regexp.MustCompile("[Mmhd]")
|
||||||
|
|
||||||
keepForRaw := regexInts.FindAllString(params["paste-keep-for"][0], 1)[0]
|
keepForRaw := regexInts.FindAllString(params["paste-keep-for"][0], 1)[0]
|
||||||
|
@ -89,16 +94,17 @@ func pastePOSTWebInterface(ec echo.Context) error {
|
||||||
|
|
||||||
keepFor, err = strconv.Atoi(keepForRaw)
|
keepFor, err = strconv.Atoi(keepForRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if params["paste-keep-for"][0] == "forever" {
|
if params["paste-keep-for"][0] == KeepPastesForever {
|
||||||
c.Logger.Debug().Msg("Keeping paste forever!")
|
ctx.Logger.Debug().Msg("Keeping paste forever!")
|
||||||
|
|
||||||
keepFor = 0
|
keepFor = 0
|
||||||
} else {
|
} else {
|
||||||
c.Logger.Debug().Err(err).Msg("Failed to parse 'Keep for' integer")
|
ctx.Logger.Debug().Err(err).Msg("Failed to parse 'Keep for' integer")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ec, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).")
|
errtpl := templater.GetErrorTemplate(ectx, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).")
|
||||||
|
|
||||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusBadRequest, errtpl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,22 +138,25 @@ func pastePOSTWebInterface(ec echo.Context) error {
|
||||||
_ = paste.CreatePassword(pastePassword[0])
|
_ = paste.CreatePassword(pastePassword[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err2 := c.Database.SavePaste(paste)
|
pasteID, err2 := ctx.Database.SavePaste(paste)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
c.Logger.Error().Err(err2).Msg("Failed to save paste")
|
ctx.Logger.Error().Err(err2).Msg("Failed to save paste")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ec, "Failed to save paste. Please, try again later.")
|
errtpl := templater.GetErrorTemplate(ectx, "Failed to save paste. Please, try again later.")
|
||||||
|
|
||||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusBadRequest, errtpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
newPasteIDAsString := strconv.FormatInt(id, 10)
|
newPasteIDAsString := strconv.FormatInt(pasteID, 10)
|
||||||
c.Logger.Debug().Msg("Paste saved, URL: /paste/" + newPasteIDAsString)
|
ctx.Logger.Debug().Msg("Paste saved, URL: /paste/" + newPasteIDAsString)
|
||||||
|
|
||||||
// Private pastes have it's timestamp in URL.
|
// Private pastes have it's timestamp in URL.
|
||||||
if paste.Private {
|
if paste.Private {
|
||||||
return ec.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString+"/"+strconv.FormatInt(paste.CreatedAt.Unix(), 10))
|
//nolint:wrapcheck
|
||||||
|
return ectx.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString+"/"+strconv.FormatInt(paste.CreatedAt.Unix(), 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
return ec.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString)
|
//nolint:wrapcheck
|
||||||
|
return ectx.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,87 +25,87 @@
|
||||||
package pastes
|
package pastes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
// local
|
"github.com/labstack/echo"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/pagination"
|
"go.dev.pztrn.name/fastpastebin/internal/pagination"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
||||||
|
|
||||||
// other
|
|
||||||
"github.com/labstack/echo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GET for "/pastes/", a list of publicly available pastes.
|
// GET for "/pastes/", a list of publicly available pastes.
|
||||||
// Web interface version.
|
// Web interface version.
|
||||||
func pastesGET(ec echo.Context) error {
|
func pastesGET(ectx echo.Context) error {
|
||||||
// We should check if database connection available.
|
// We should check if database connection available.
|
||||||
dbConn := c.Database.GetDatabaseConnection()
|
dbConn := ctx.Database.GetDatabaseConnection()
|
||||||
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
|
||||||
return ec.Redirect(http.StatusFound, "/database_not_available")
|
//nolint:wrapcheck
|
||||||
|
return ectx.Redirect(http.StatusFound, "/database_not_available")
|
||||||
}
|
}
|
||||||
|
|
||||||
pageFromParamRaw := ec.Param("page")
|
pageFromParamRaw := ectx.Param("page")
|
||||||
|
|
||||||
var page = 1
|
page := 1
|
||||||
|
|
||||||
if pageFromParamRaw != "" {
|
if pageFromParamRaw != "" {
|
||||||
pageRaw := regexInts.FindAllString(pageFromParamRaw, 1)[0]
|
pageRaw := regexInts.FindAllString(pageFromParamRaw, 1)[0]
|
||||||
page, _ = strconv.Atoi(pageRaw)
|
page, _ = strconv.Atoi(pageRaw)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Logger.Debug().Int("page", page).Msg("Requested page")
|
ctx.Logger.Debug().Int("page", page).Msg("Requested page")
|
||||||
|
|
||||||
// Get pastes IDs.
|
// Get pastes IDs.
|
||||||
pastes, err3 := c.Database.GetPagedPastes(page)
|
pastes, err3 := ctx.Database.GetPagedPastes(page)
|
||||||
c.Logger.Debug().Int("count", len(pastes)).Msg("Got pastes")
|
ctx.Logger.Debug().Int("count", len(pastes)).Msg("Got pastes")
|
||||||
|
|
||||||
var pastesString = "No pastes to show."
|
pastesString := "No pastes to show."
|
||||||
|
|
||||||
// Show "No pastes to show" on any error for now.
|
// Show "No pastes to show" on any error for now.
|
||||||
if err3 != nil {
|
if err3 != nil {
|
||||||
c.Logger.Error().Err(err3).Msg("Failed to get pastes list from database")
|
ctx.Logger.Error().Err(err3).Msg("Failed to get pastes list from database")
|
||||||
|
|
||||||
noPastesToShowTpl := templater.GetErrorTemplate(ec, "No pastes to show.")
|
noPastesToShowTpl := templater.GetErrorTemplate(ectx, "No pastes to show.")
|
||||||
|
|
||||||
return ec.HTML(http.StatusOK, noPastesToShowTpl)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusOK, noPastesToShowTpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(pastes) > 0 {
|
if len(pastes) > 0 {
|
||||||
pastesString = ""
|
pastesString = ""
|
||||||
|
|
||||||
for i := range pastes {
|
for _, paste := range pastes {
|
||||||
pasteDataMap := make(map[string]string)
|
pasteDataMap := make(map[string]string)
|
||||||
pasteDataMap["pasteID"] = strconv.Itoa(pastes[i].ID)
|
pasteDataMap["pasteID"] = strconv.Itoa(paste.ID)
|
||||||
pasteDataMap["pasteTitle"] = pastes[i].Title
|
pasteDataMap["pasteTitle"] = paste.Title
|
||||||
pasteDataMap["pasteDate"] = pastes[i].CreatedAt.Format("2006-01-02 @ 15:04:05") + " UTC"
|
pasteDataMap["pasteDate"] = paste.CreatedAt.Format("2006-01-02 @ 15:04:05") + " UTC"
|
||||||
|
|
||||||
// Get max 4 lines of each paste.
|
// Get max 4 lines of each paste.
|
||||||
pasteDataSplitted := strings.Split(pastes[i].Data, "\n")
|
pasteDataSplitted := strings.Split(paste.Data, "\n")
|
||||||
|
|
||||||
var pasteData string
|
var pasteData string
|
||||||
|
|
||||||
if len(pasteDataSplitted) < 4 {
|
if len(pasteDataSplitted) < 4 {
|
||||||
pasteData = pastes[i].Data
|
pasteData = paste.Data
|
||||||
} else {
|
} else {
|
||||||
pasteData = strings.Join(pasteDataSplitted[0:4], "\n")
|
pasteData = strings.Join(pasteDataSplitted[0:4], "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteDataMap["pasteData"] = pasteData
|
pasteDataMap["pasteData"] = pasteData
|
||||||
pasteTpl := templater.GetRawTemplate(ec, "pastelist_paste.html", pasteDataMap)
|
pasteTpl := templater.GetRawTemplate(ectx, "pastelist_paste.html", pasteDataMap)
|
||||||
|
|
||||||
pastesString += pasteTpl
|
pastesString += pasteTpl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pagination.
|
// Pagination.
|
||||||
pages := c.Database.GetPastesPages()
|
pages := ctx.Database.GetPastesPages()
|
||||||
c.Logger.Debug().Int("total pages", pages).Int("current page", page).Msg("Paging data")
|
ctx.Logger.Debug().Int("total pages", pages).Int("current page", page).Msg("Paging data")
|
||||||
paginationHTML := pagination.CreateHTML(page, pages, "/pastes/")
|
paginationHTML := pagination.CreateHTML(page, pages, "/pastes/")
|
||||||
|
|
||||||
pasteListTpl := templater.GetTemplate(ec, "pastelist_list.html", map[string]string{"pastes": pastesString, "pagination": paginationHTML})
|
pasteListTpl := templater.GetTemplate(ectx, "pastelist_list.html", map[string]string{"pastes": pastesString, "pagination": paginationHTML})
|
||||||
|
|
||||||
return ec.HTML(http.StatusOK, pasteListTpl)
|
//nolint:wrapcheck
|
||||||
|
return ectx.HTML(http.StatusOK, pasteListTpl)
|
||||||
}
|
}
|
||||||
|
|
112
fileb0x.yml
112
fileb0x.yml
|
@ -1,112 +0,0 @@
|
||||||
# all folders and files are relative to the path
|
|
||||||
# where fileb0x was run at!
|
|
||||||
|
|
||||||
# default: main
|
|
||||||
pkg: static
|
|
||||||
|
|
||||||
# destination
|
|
||||||
dest: "./assets/static/"
|
|
||||||
|
|
||||||
# gofmt
|
|
||||||
# type: bool
|
|
||||||
# default: false
|
|
||||||
fmt: false
|
|
||||||
|
|
||||||
# compress files
|
|
||||||
# at the moment, only supports gzip
|
|
||||||
#
|
|
||||||
# type: object
|
|
||||||
compression:
|
|
||||||
# activates the compression
|
|
||||||
#
|
|
||||||
# type: bool
|
|
||||||
# default: false
|
|
||||||
compress: false
|
|
||||||
|
|
||||||
# valid values are:
|
|
||||||
# -> "NoCompression"
|
|
||||||
# -> "BestSpeed"
|
|
||||||
# -> "BestCompression"
|
|
||||||
# -> "DefaultCompression" or ""
|
|
||||||
#
|
|
||||||
# type: string
|
|
||||||
# default: "DefaultCompression" # when: Compress == true && Method == ""
|
|
||||||
method: ""
|
|
||||||
|
|
||||||
# true = do it yourself (the file is written as gzip compressed file into the memory file system)
|
|
||||||
# false = decompress files at run time (while writing file into memory file system)
|
|
||||||
#
|
|
||||||
# type: bool
|
|
||||||
# default: false
|
|
||||||
keep: false
|
|
||||||
|
|
||||||
# ---------------
|
|
||||||
# -- DANGEROUS --
|
|
||||||
# ---------------
|
|
||||||
#
|
|
||||||
# cleans the destination folder (only b0xfiles)
|
|
||||||
# you should use this when using the spread function
|
|
||||||
# type: bool
|
|
||||||
# default: false
|
|
||||||
clean: true
|
|
||||||
|
|
||||||
# default: ab0x.go
|
|
||||||
output: "ab0x.go"
|
|
||||||
|
|
||||||
# [unexporTed] builds non-exporTed functions, variables and types...
|
|
||||||
# type: bool
|
|
||||||
# default: false
|
|
||||||
unexporTed: false
|
|
||||||
|
|
||||||
# [spread] means it will make a file to hold all fileb0x data
|
|
||||||
# and each file into a separaTed .go file
|
|
||||||
#
|
|
||||||
# example:
|
|
||||||
# theres 2 files in the folder assets, they're: hello.json and world.txt
|
|
||||||
# when spread is activaTed, fileb0x will make a file:
|
|
||||||
# b0x.go or [output]'s data, assets_hello.json.go and assets_world.txt.go
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# type: bool
|
|
||||||
# default: false
|
|
||||||
spread: true
|
|
||||||
|
|
||||||
# [lcf] log changed files when spread is active
|
|
||||||
lcf: true
|
|
||||||
|
|
||||||
# type: array of objects
|
|
||||||
custom:
|
|
||||||
|
|
||||||
# type: array of strings
|
|
||||||
- files:
|
|
||||||
- "assets/css/bulma-0.7.5.min.css"
|
|
||||||
- "assets/css/bulma-tooltip-3.0.0.min.css"
|
|
||||||
- "assets/css/bulma.css.map"
|
|
||||||
- "assets/css/style.css"
|
|
||||||
- "assets/js/fontawesome-5.0.7.js"
|
|
||||||
|
|
||||||
# base is the path that will be removed from all files' path
|
|
||||||
# type: string
|
|
||||||
base: "assets"
|
|
||||||
|
|
||||||
# prefix is the path that will be added to all files' path
|
|
||||||
# type: string
|
|
||||||
prefix: "static/"
|
|
||||||
# end: files
|
|
||||||
- files:
|
|
||||||
- "assets/database_not_available.html"
|
|
||||||
- "assets/error.html"
|
|
||||||
- "assets/footer.html"
|
|
||||||
- "assets/index.html"
|
|
||||||
- "assets/main.html"
|
|
||||||
- "assets/navigation.html"
|
|
||||||
- "assets/pagination_ellipsis.html"
|
|
||||||
- "assets/pagination_link_current.html"
|
|
||||||
- "assets/pagination_link.html"
|
|
||||||
- "assets/pagination.html"
|
|
||||||
- "assets/passworded_paste_verify.html"
|
|
||||||
- "assets/paste.html"
|
|
||||||
- "assets/pastelist_list.html"
|
|
||||||
- "assets/pastelist_paste.html"
|
|
||||||
base: "assets"
|
|
||||||
prefix: ""
|
|
28
go.mod
28
go.mod
|
@ -1,21 +1,23 @@
|
||||||
module go.dev.pztrn.name/fastpastebin
|
module go.dev.pztrn.name/fastpastebin
|
||||||
|
|
||||||
go 1.13
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/chroma v0.8.2
|
github.com/alecthomas/chroma/v2 v2.2.0
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v1.0.0
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.5.0
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
github.com/jmoiron/sqlx v1.2.0
|
github.com/jmoiron/sqlx v1.3.5
|
||||||
|
github.com/kr/pretty v0.3.0 // indirect
|
||||||
github.com/labstack/echo v3.3.10+incompatible
|
github.com/labstack/echo v3.3.10+incompatible
|
||||||
github.com/labstack/gommon v0.3.0 // indirect
|
github.com/labstack/gommon v0.3.1 // indirect
|
||||||
github.com/lib/pq v1.9.0
|
github.com/lib/pq v1.10.6
|
||||||
github.com/pressly/goose v2.6.0+incompatible
|
github.com/pressly/goose v2.7.0+incompatible
|
||||||
github.com/rs/zerolog v1.20.0
|
github.com/rs/zerolog v1.27.0
|
||||||
go.dev.pztrn.name/flagger v0.0.0-20200617193309-89bc9818b76c
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
|
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
154
go.sum
154
go.sum
|
@ -1,107 +1,91 @@
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY=
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||||
github.com/alecthomas/chroma v0.8.2 h1:x3zkuE2lUk/RIekyAJ3XRqSCP4zwWDfcw/YJCuCAACg=
|
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae h1:zzGwJfFlFGD94CyyYwCJeSuD32Gj9GTaSi5y9hoVzdY=
|
||||||
github.com/alecthomas/chroma v0.8.2/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
|
||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
|
||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
|
github.com/dchest/captcha v1.0.0 h1:vw+bm/qMFvTgcjQlYVTuQBJkarm5R0YSsDKhm1HZI2o=
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
|
github.com/dchest/captcha v1.0.0/go.mod h1:7zoElIawLp7GUMLcj54K9kbw+jEyvz2K0FDdRRYhvWo=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
|
||||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
|
||||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
|
||||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
||||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||||
|
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
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/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
|
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
|
||||||
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||||
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
|
||||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||||
github.com/lib/pq v1.0.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.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
|
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
|
||||||
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
|
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
|
||||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
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.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
|
|
||||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
|
||||||
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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pressly/goose v2.6.0+incompatible h1:3f8zIQ8rfgP9tyI0Hmcs2YNAqUCL1c+diLe3iU8Qd/k=
|
github.com/pressly/goose v2.7.0+incompatible h1:PWejVEv07LCerQEzMMeAtjuyCKbyprZ/LBa6K5P0OCQ=
|
||||||
github.com/pressly/goose v2.6.0+incompatible/go.mod h1:m+QHWCqxR3k8D9l7qfzuC/djtlfzxr34mozWDYEu1z8=
|
github.com/pressly/goose v2.7.0+incompatible/go.mod h1:m+QHWCqxR3k8D9l7qfzuC/djtlfzxr34mozWDYEu1z8=
|
||||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||||
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
|
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
|
||||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
go.dev.pztrn.name/flagger v0.0.0-20200617193309-89bc9818b76c h1:+GgFefaTLsYDS0lXc8LNzTo6tcsA9qO3EkTAKduPAsI=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||||
go.dev.pztrn.name/flagger v0.0.0-20200617193309-89bc9818b76c/go.mod h1:ttPExQNCubgqqO5Y19LfIBKqmWtBocY7P9MXQEECuZo=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
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-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
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-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
|
||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-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-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0=
|
||||||
|
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
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/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/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
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.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
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=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
@ -25,28 +25,25 @@
|
||||||
package captcha
|
package captcha
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
|
||||||
|
|
||||||
// other
|
|
||||||
"github.com/dchest/captcha"
|
"github.com/dchest/captcha"
|
||||||
"github.com/labstack/echo"
|
"github.com/labstack/echo"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
c *context.Context
|
ctx *context.Context
|
||||||
log zerolog.Logger
|
log zerolog.Logger
|
||||||
)
|
)
|
||||||
|
|
||||||
// New initializes captcha package and adds necessary HTTP and API
|
// New initializes captcha package and adds necessary HTTP and API
|
||||||
// endpoints.
|
// endpoints.
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
c = cc
|
ctx = cc
|
||||||
log = c.Logger.With().Str("type", "internal").Str("package", "captcha").Logger()
|
log = ctx.Logger.With().Str("type", "internal").Str("package", "captcha").Logger()
|
||||||
|
|
||||||
// New paste.
|
// New paste.
|
||||||
c.Echo.GET("/captcha/:id.png", echo.WrapHandler(captcha.Server(captcha.StdWidth, captcha.StdHeight)))
|
ctx.Echo.GET("/captcha/:id.png", echo.WrapHandler(captcha.Server(captcha.StdWidth, captcha.StdHeight)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCaptcha creates new captcha string.
|
// NewCaptcha creates new captcha string.
|
||||||
|
|
|
@ -28,6 +28,6 @@ package config
|
||||||
type HTTP struct {
|
type HTTP struct {
|
||||||
Address string `yaml:"address"`
|
Address string `yaml:"address"`
|
||||||
Port string `yaml:"port"`
|
Port string `yaml:"port"`
|
||||||
AllowInsecure bool `yaml:"allow_insecure"`
|
|
||||||
MaxBodySizeMegabytes string `yaml:"max_body_size_megabytes"`
|
MaxBodySizeMegabytes string `yaml:"max_body_size_megabytes"`
|
||||||
|
AllowInsecure bool `yaml:"allow_insecure"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ package config
|
||||||
|
|
||||||
// Logging describes logger configuration.
|
// Logging describes logger configuration.
|
||||||
type Logging struct {
|
type Logging struct {
|
||||||
LogToFile bool `yaml:"log_to_file"`
|
|
||||||
FileName string `yaml:"filename"`
|
FileName string `yaml:"filename"`
|
||||||
LogLevel string `yaml:"loglevel"`
|
LogLevel string `yaml:"loglevel"`
|
||||||
|
LogToFile bool `yaml:"log_to_file"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,19 +25,14 @@
|
||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
"flag"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/config"
|
|
||||||
databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface"
|
|
||||||
|
|
||||||
// other
|
|
||||||
"github.com/labstack/echo"
|
"github.com/labstack/echo"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.dev.pztrn.name/flagger"
|
"go.dev.pztrn.name/fastpastebin/internal/config"
|
||||||
|
databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,23 +44,15 @@ type Context struct {
|
||||||
Config *config.Struct
|
Config *config.Struct
|
||||||
Database databaseinterface.Interface
|
Database databaseinterface.Interface
|
||||||
Echo *echo.Echo
|
Echo *echo.Echo
|
||||||
Flagger *flagger.Flagger
|
|
||||||
Logger zerolog.Logger
|
Logger zerolog.Logger
|
||||||
|
configPathFromCLI string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize initializes context.
|
// Initialize initializes context.
|
||||||
func (c *Context) Initialize() {
|
func (c *Context) Initialize() {
|
||||||
c.initializeLogger()
|
c.initializeLogger()
|
||||||
|
|
||||||
c.Flagger = flagger.New("fastpastebin", nil)
|
flag.StringVar(&c.configPathFromCLI, "config", "NO_CONFIG", "Configuration file path. Can be overridded with FASTPASTEBIN_CONFIG environment variable.")
|
||||||
c.Flagger.Initialize()
|
|
||||||
|
|
||||||
_ = c.Flagger.AddFlag(&flagger.Flag{
|
|
||||||
Name: "config",
|
|
||||||
Description: "Configuration file path. Can be overridded with FASTPASTEBIN_CONFIG environment variable (this is what used in tests).",
|
|
||||||
Type: "string",
|
|
||||||
DefaultValue: "NO_CONFIG",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializePost initializes everything that needs a configuration.
|
// InitializePost initializes everything that needs a configuration.
|
||||||
|
@ -80,20 +67,18 @@ func (c *Context) InitializePost() {
|
||||||
func (c *Context) LoadConfiguration() {
|
func (c *Context) LoadConfiguration() {
|
||||||
c.Logger.Info().Msg("Loading configuration...")
|
c.Logger.Info().Msg("Loading configuration...")
|
||||||
|
|
||||||
configPath := ""
|
configPath := c.configPathFromCLI
|
||||||
|
|
||||||
// We're accepting configuration path from "-config" CLI parameter
|
// We're accepting configuration path from "-config" CLI parameter
|
||||||
// and FASTPASTEBIN_CONFIG environment variable. Later have higher
|
// and FASTPASTEBIN_CONFIG environment variable. Later have higher
|
||||||
// weight and can override "-config" value.
|
// weight and can override "-config" value.
|
||||||
configPathFromCLI, err := c.Flagger.GetStringValue("config")
|
|
||||||
configPathFromEnv, configPathFromEnvFound := os.LookupEnv("FASTPASTEBIN_CONFIG")
|
configPathFromEnv, configPathFromEnvFound := os.LookupEnv("FASTPASTEBIN_CONFIG")
|
||||||
|
if configPathFromEnvFound {
|
||||||
if err != nil && configPathFromEnvFound || err == nil && configPathFromEnvFound {
|
|
||||||
configPath = configPathFromEnv
|
configPath = configPathFromEnv
|
||||||
} else if err != nil && !configPathFromEnvFound || err == nil && configPathFromCLI == "NO_CONFIG" {
|
}
|
||||||
|
|
||||||
|
if configPath == "NO_CONFIG" {
|
||||||
c.Logger.Panic().Msg("Configuration file path wasn't passed via '-config' or 'FASTPASTEBIN_CONFIG' environment variable. Cannot continue.")
|
c.Logger.Panic().Msg("Configuration file path wasn't passed via '-config' or 'FASTPASTEBIN_CONFIG' environment variable. Cannot continue.")
|
||||||
} else if err == nil && !configPathFromEnvFound {
|
|
||||||
configPath = configPathFromCLI
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize file path.
|
// Normalize file path.
|
||||||
|
@ -104,10 +89,11 @@ func (c *Context) LoadConfiguration() {
|
||||||
|
|
||||||
c.Logger.Debug().Str("path", configPath).Msg("Configuration file path")
|
c.Logger.Debug().Str("path", configPath).Msg("Configuration file path")
|
||||||
|
|
||||||
|
//nolint:exhaustruct
|
||||||
c.Config = &config.Struct{}
|
c.Config = &config.Struct{}
|
||||||
|
|
||||||
// Read configuration file.
|
// Read configuration file.
|
||||||
fileData, err2 := ioutil.ReadFile(normalizedConfigPath)
|
fileData, err2 := os.ReadFile(normalizedConfigPath)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
c.Logger.Panic().Err(err2).Msg("Failed to read configuration file")
|
c.Logger.Panic().Err(err2).Msg("Failed to read configuration file")
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,10 +26,11 @@ package context
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Version .
|
// Version .
|
||||||
Version = "0.4.0"
|
Version = "0.4.1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New creates new context.
|
// New creates new context.
|
||||||
func New() *Context {
|
func New() *Context {
|
||||||
|
//nolint:exhaustruct
|
||||||
return &Context{}
|
return &Context{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// local
|
"net/http"
|
||||||
"go.dev.pztrn.name/fastpastebin/assets/static"
|
|
||||||
|
|
||||||
// other
|
|
||||||
"github.com/labstack/echo"
|
"github.com/labstack/echo"
|
||||||
"github.com/labstack/echo/middleware"
|
"github.com/labstack/echo/middleware"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/assets"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Context) initializeHTTPServer() {
|
func (c *Context) initializeHTTPServer() {
|
||||||
|
@ -19,7 +18,7 @@ func (c *Context) initializeHTTPServer() {
|
||||||
c.Echo.HidePort = true
|
c.Echo.HidePort = true
|
||||||
|
|
||||||
// Static files.
|
// Static files.
|
||||||
c.Echo.GET("/static/*", echo.WrapHandler(static.Handler))
|
c.Echo.GET("/static/*", echo.WrapHandler(http.FileServer(http.FS(assets.Data))))
|
||||||
|
|
||||||
listenAddress := c.Config.HTTP.Address + ":" + c.Config.HTTP.Port
|
listenAddress := c.Config.HTTP.Address + ":" + c.Config.HTTP.Port
|
||||||
|
|
||||||
|
@ -32,16 +31,16 @@ func (c *Context) initializeHTTPServer() {
|
||||||
// Wrapper around previous function.
|
// Wrapper around previous function.
|
||||||
func (c *Context) echoReqLogger() echo.MiddlewareFunc {
|
func (c *Context) echoReqLogger() echo.MiddlewareFunc {
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(ec echo.Context) error {
|
return func(ectx echo.Context) error {
|
||||||
c.Logger.Info().
|
c.Logger.Info().
|
||||||
Str("IP", ec.RealIP()).
|
Str("IP", ectx.RealIP()).
|
||||||
Str("Host", ec.Request().Host).
|
Str("Host", ectx.Request().Host).
|
||||||
Str("Method", ec.Request().Method).
|
Str("Method", ectx.Request().Method).
|
||||||
Str("Path", ec.Request().URL.Path).
|
Str("Path", ectx.Request().URL.Path).
|
||||||
Str("UA", ec.Request().UserAgent()).
|
Str("UA", ectx.Request().UserAgent()).
|
||||||
Msg("HTTP request")
|
Msg("HTTP request")
|
||||||
|
|
||||||
return next(ec)
|
return next(ectx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +1,55 @@
|
||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
// other
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Puts memory usage into log lines.
|
// Puts memory usage into log lines.
|
||||||
func (c *Context) getMemoryUsage(e *zerolog.Event, level zerolog.Level, message string) {
|
func (c *Context) getMemoryUsage(event *zerolog.Event, level zerolog.Level, message string) {
|
||||||
var m runtime.MemStats
|
var memstats runtime.MemStats
|
||||||
|
|
||||||
runtime.ReadMemStats(&m)
|
runtime.ReadMemStats(&memstats)
|
||||||
|
|
||||||
e.Str("memalloc", fmt.Sprintf("%dMB", m.Alloc/1024/1024))
|
event.Str("memalloc", fmt.Sprintf("%dMB", memstats.Alloc/1024/1024))
|
||||||
e.Str("memsys", fmt.Sprintf("%dMB", m.Sys/1024/1024))
|
event.Str("memsys", fmt.Sprintf("%dMB", memstats.Sys/1024/1024))
|
||||||
e.Str("numgc", fmt.Sprintf("%d", m.NumGC))
|
event.Str("numgc", fmt.Sprintf("%d", memstats.NumGC))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initializes logger.
|
// Initializes logger.
|
||||||
func (c *Context) initializeLogger() {
|
func (c *Context) initializeLogger() {
|
||||||
// Устанавливаем форматирование логгера.
|
// Устанавливаем форматирование логгера.
|
||||||
|
//nolint:exhaustruct
|
||||||
output := zerolog.ConsoleWriter{Out: os.Stdout, NoColor: false, TimeFormat: time.RFC3339}
|
output := zerolog.ConsoleWriter{Out: os.Stdout, NoColor: false, TimeFormat: time.RFC3339}
|
||||||
output.FormatLevel = func(i interface{}) string {
|
output.FormatLevel = func(lvlRaw interface{}) string {
|
||||||
var v string
|
var lvl string
|
||||||
|
|
||||||
if ii, ok := i.(string); ok {
|
if lvlAsString, ok := lvlRaw.(string); ok {
|
||||||
ii = strings.ToUpper(ii)
|
lvlAsString = strings.ToUpper(lvlAsString)
|
||||||
switch ii {
|
switch lvlAsString {
|
||||||
case "DEBUG":
|
case "DEBUG":
|
||||||
v = fmt.Sprintf("\x1b[30m%-5s\x1b[0m", ii)
|
lvl = fmt.Sprintf("\x1b[30m%-5s\x1b[0m", lvlAsString)
|
||||||
case "ERROR":
|
case "ERROR":
|
||||||
v = fmt.Sprintf("\x1b[31m%-5s\x1b[0m", ii)
|
lvl = fmt.Sprintf("\x1b[31m%-5s\x1b[0m", lvlAsString)
|
||||||
case "FATAL":
|
case "FATAL":
|
||||||
v = fmt.Sprintf("\x1b[35m%-5s\x1b[0m", ii)
|
lvl = fmt.Sprintf("\x1b[35m%-5s\x1b[0m", lvlAsString)
|
||||||
case "INFO":
|
case "INFO":
|
||||||
v = fmt.Sprintf("\x1b[32m%-5s\x1b[0m", ii)
|
lvl = fmt.Sprintf("\x1b[32m%-5s\x1b[0m", lvlAsString)
|
||||||
case "PANIC":
|
case "PANIC":
|
||||||
v = fmt.Sprintf("\x1b[36m%-5s\x1b[0m", ii)
|
lvl = fmt.Sprintf("\x1b[36m%-5s\x1b[0m", lvlAsString)
|
||||||
case "WARN":
|
case "WARN":
|
||||||
v = fmt.Sprintf("\x1b[33m%-5s\x1b[0m", ii)
|
lvl = fmt.Sprintf("\x1b[33m%-5s\x1b[0m", lvlAsString)
|
||||||
default:
|
default:
|
||||||
v = ii
|
lvl = lvlAsString
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("| %s |", v)
|
return fmt.Sprintf("| %s |", lvl)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Logger = zerolog.New(output).With().Timestamp().Logger()
|
c.Logger = zerolog.New(output).With().Timestamp().Logger()
|
||||||
|
|
|
@ -25,19 +25,16 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
// local
|
// MySQL driver.
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles"
|
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles"
|
||||||
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/mysql"
|
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/mysql"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/postgresql"
|
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/postgresql"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||||
|
|
||||||
// other
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Database represents control structure for database connection.
|
// Database represents control structure for database connection.
|
||||||
|
@ -49,7 +46,7 @@ type Database struct {
|
||||||
// a subject of change in future.
|
// a subject of change in future.
|
||||||
func (db *Database) cleanup() {
|
func (db *Database) cleanup() {
|
||||||
for {
|
for {
|
||||||
c.Logger.Info().Msg("Starting pastes cleanup procedure...")
|
ctx.Logger.Info().Msg("Starting pastes cleanup procedure...")
|
||||||
|
|
||||||
pages := db.db.GetPastesPages()
|
pages := db.db.GetPastesPages()
|
||||||
|
|
||||||
|
@ -58,7 +55,7 @@ func (db *Database) cleanup() {
|
||||||
for i := 0; i < pages; i++ {
|
for i := 0; i < pages; i++ {
|
||||||
pastes, err := db.db.GetPagedPastes(i)
|
pastes, err := db.db.GetPagedPastes(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error().Err(err).Int("page", i).Msg("Failed to perform database cleanup")
|
ctx.Logger.Error().Err(err).Int("page", i).Msg("Failed to perform database cleanup")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, paste := range pastes {
|
for _, paste := range pastes {
|
||||||
|
@ -71,17 +68,18 @@ func (db *Database) cleanup() {
|
||||||
for _, pasteID := range pasteIDsToRemove {
|
for _, pasteID := range pasteIDsToRemove {
|
||||||
err := db.DeletePaste(pasteID)
|
err := db.DeletePaste(pasteID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error().Err(err).Int("paste", pasteID).Msg("Failed to delete paste!")
|
ctx.Logger.Error().Err(err).Int("paste", pasteID).Msg("Failed to delete paste!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Logger.Info().Msg("Pastes cleanup done.")
|
ctx.Logger.Info().Msg("Pastes cleanup done.")
|
||||||
|
|
||||||
time.Sleep(time.Hour)
|
time.Sleep(time.Hour)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) DeletePaste(pasteID int) error {
|
func (db *Database) DeletePaste(pasteID int) error {
|
||||||
|
//nolint:wrapcheck
|
||||||
return db.db.DeletePaste(pasteID)
|
return db.db.DeletePaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,10 +92,12 @@ func (db *Database) GetDatabaseConnection() *sql.DB {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
|
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||||
|
//nolint:wrapcheck
|
||||||
return db.db.GetPaste(pasteID)
|
return db.db.GetPaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
|
//nolint:wrapcheck
|
||||||
return db.db.GetPagedPastes(page)
|
return db.db.GetPagedPastes(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,16 +107,16 @@ func (db *Database) GetPastesPages() int {
|
||||||
|
|
||||||
// Initialize initializes connection to database.
|
// Initialize initializes connection to database.
|
||||||
func (db *Database) Initialize() {
|
func (db *Database) Initialize() {
|
||||||
c.Logger.Info().Msg("Initializing database connection...")
|
ctx.Logger.Info().Msg("Initializing database connection...")
|
||||||
|
|
||||||
if c.Config.Database.Type == "mysql" {
|
if ctx.Config.Database.Type == "mysql" {
|
||||||
mysql.New(c)
|
mysql.New(ctx)
|
||||||
} else if c.Config.Database.Type == "flatfiles" {
|
} else if ctx.Config.Database.Type == flatfiles.FlatFileDialect {
|
||||||
flatfiles.New(c)
|
flatfiles.New(ctx)
|
||||||
} else if c.Config.Database.Type == "postgresql" {
|
} else if ctx.Config.Database.Type == "postgresql" {
|
||||||
postgresql.New(c)
|
postgresql.New(ctx)
|
||||||
} else {
|
} else {
|
||||||
c.Logger.Fatal().Str("type", c.Config.Database.Type).Msg("Unknown database type")
|
ctx.Logger.Fatal().Str("type", ctx.Config.Database.Type).Msg("Unknown database type")
|
||||||
}
|
}
|
||||||
|
|
||||||
go db.cleanup()
|
go db.cleanup()
|
||||||
|
@ -128,6 +128,7 @@ func (db *Database) RegisterDialect(di dialectinterface.Interface) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) SavePaste(p *structs.Paste) (int64, error) {
|
func (db *Database) SavePaste(p *structs.Paste) (int64, error) {
|
||||||
|
//nolint:wrapcheck
|
||||||
return db.db.SavePaste(p)
|
return db.db.SavePaste(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,19 +25,21 @@
|
||||||
package flatfiles
|
package flatfiles
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const FlatFileDialect = "flatfiles"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
c *context.Context
|
ctx *context.Context
|
||||||
f *FlatFiles
|
flf *FlatFiles
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
c = cc
|
ctx = cc
|
||||||
f = &FlatFiles{}
|
//nolint:exhaustruct
|
||||||
|
flf = &FlatFiles{}
|
||||||
|
|
||||||
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
ctx.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,8 @@
|
||||||
package flatfiles
|
package flatfiles
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -36,14 +34,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FlatFiles struct {
|
type FlatFiles struct {
|
||||||
|
writeMutex sync.Mutex
|
||||||
path string
|
path string
|
||||||
pastesIndex []Index
|
pastesIndex []Index
|
||||||
writeMutex sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePaste deletes paste from disk and index.
|
// DeletePaste deletes paste from disk and index.
|
||||||
|
@ -51,8 +48,9 @@ func (ff *FlatFiles) DeletePaste(pasteID int) error {
|
||||||
// Delete from disk.
|
// Delete from disk.
|
||||||
err := os.Remove(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"))
|
err := os.Remove(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error().Err(err).Msg("Failed to delete paste!")
|
ctx.Logger.Error().Err(err).Msg("Failed to delete paste!")
|
||||||
|
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,22 +82,27 @@ func (ff *FlatFiles) GetDatabaseConnection() *sql.DB {
|
||||||
func (ff *FlatFiles) GetPaste(pasteID int) (*structs.Paste, error) {
|
func (ff *FlatFiles) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||||
ff.writeMutex.Lock()
|
ff.writeMutex.Lock()
|
||||||
pastePath := filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json")
|
pastePath := filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json")
|
||||||
c.Logger.Debug().Str("path", pastePath).Msg("Trying to load paste data")
|
ctx.Logger.Debug().Str("path", pastePath).Msg("Trying to load paste data")
|
||||||
|
|
||||||
pasteInBytes, err := ioutil.ReadFile(pastePath)
|
pasteInBytes, err := os.ReadFile(pastePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Debug().Err(err).Msg("Failed to read paste from storage")
|
ctx.Logger.Debug().Err(err).Msg("Failed to read paste from storage")
|
||||||
|
|
||||||
|
//nolint:wrapcheck
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Logger.Debug().Int("paste bytes", len(pasteInBytes)).Msg("Loaded paste")
|
ctx.Logger.Debug().Int("paste bytes", len(pasteInBytes)).Msg("Loaded paste")
|
||||||
ff.writeMutex.Unlock()
|
ff.writeMutex.Unlock()
|
||||||
|
|
||||||
|
//nolint:exhaustruct
|
||||||
paste := &structs.Paste{}
|
paste := &structs.Paste{}
|
||||||
|
|
||||||
err1 := json.Unmarshal(pasteInBytes, paste)
|
err1 := json.Unmarshal(pasteInBytes, paste)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
c.Logger.Error().Err(err1).Msgf("Failed to parse paste")
|
ctx.Logger.Error().Err(err1).Msgf("Failed to parse paste")
|
||||||
|
|
||||||
|
//nolint:wrapcheck
|
||||||
return nil, err1
|
return nil, err1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +113,7 @@ func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
// Pagination.
|
// Pagination.
|
||||||
startPagination := 0
|
startPagination := 0
|
||||||
if page > 1 {
|
if page > 1 {
|
||||||
startPagination = (page - 1) * c.Config.Pastes.Pagination
|
startPagination = (page - 1) * ctx.Config.Pastes.Pagination
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iteration one - get only public pastes.
|
// Iteration one - get only public pastes.
|
||||||
|
@ -126,34 +129,39 @@ func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
pastesData := make([]structs.Paste, 0)
|
pastesData := make([]structs.Paste, 0)
|
||||||
|
|
||||||
for idx, paste := range publicPastes {
|
for idx, paste := range publicPastes {
|
||||||
if len(pastesData) == c.Config.Pastes.Pagination {
|
if len(pastesData) == ctx.Config.Pastes.Pagination {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if idx < startPagination {
|
if idx < startPagination {
|
||||||
c.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too low index")
|
ctx.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too low index")
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (idx-1 >= startPagination && page > 1 && idx > startPagination+((page-1)*c.Config.Pastes.Pagination)) || (idx-1 >= startPagination && page == 1 && idx > startPagination+(page*c.Config.Pastes.Pagination)) {
|
if (idx-1 >= startPagination && page > 1 && idx > startPagination+((page-1)*ctx.Config.Pastes.Pagination)) || (idx-1 >= startPagination && page == 1 && idx > startPagination+(page*ctx.Config.Pastes.Pagination)) {
|
||||||
c.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too high index")
|
ctx.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too high index")
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Logger.Debug().Int("ID", paste.ID).Int("index", idx).Msg("Getting paste data")
|
ctx.Logger.Debug().Int("ID", paste.ID).Int("index", idx).Msg("Getting paste data")
|
||||||
|
|
||||||
// Get paste data.
|
// Get paste data.
|
||||||
|
//nolint:exhaustruct
|
||||||
pasteData := &structs.Paste{}
|
pasteData := &structs.Paste{}
|
||||||
|
|
||||||
pasteRawData, err := ioutil.ReadFile(filepath.Join(ff.path, "pastes", strconv.Itoa(paste.ID)+".json"))
|
pasteRawData, err := os.ReadFile(filepath.Join(ff.path, "pastes", strconv.Itoa(paste.ID)+".json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error().Err(err).Msg("Failed to read paste data")
|
ctx.Logger.Error().Err(err).Msg("Failed to read paste data")
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err1 := json.Unmarshal(pasteRawData, pasteData)
|
err1 := json.Unmarshal(pasteRawData, pasteData)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
c.Logger.Error().Err(err1).Msg("Failed to parse paste data")
|
ctx.Logger.Error().Err(err1).Msg("Failed to parse paste data")
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,9 +184,9 @@ func (ff *FlatFiles) GetPastesPages() int {
|
||||||
ff.writeMutex.Unlock()
|
ff.writeMutex.Unlock()
|
||||||
|
|
||||||
// Calculate pages.
|
// Calculate pages.
|
||||||
pages := len(publicPastes) / c.Config.Pastes.Pagination
|
pages := len(publicPastes) / ctx.Config.Pastes.Pagination
|
||||||
// Check if we have any remainder. Add 1 to pages count if so.
|
// Check if we have any remainder. Add 1 to pages count if so.
|
||||||
if len(publicPastes)%c.Config.Pastes.Pagination > 0 {
|
if len(publicPastes)%ctx.Config.Pastes.Pagination > 0 {
|
||||||
pages++
|
pages++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,14 +194,14 @@ func (ff *FlatFiles) GetPastesPages() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ff *FlatFiles) Initialize() {
|
func (ff *FlatFiles) Initialize() {
|
||||||
c.Logger.Info().Msg("Initializing flatfiles storage...")
|
ctx.Logger.Info().Msg("Initializing flatfiles storage...")
|
||||||
|
|
||||||
path := c.Config.Database.Path
|
path := ctx.Config.Database.Path
|
||||||
// Get proper paste file path.
|
// Get proper paste file path.
|
||||||
if strings.Contains(c.Config.Database.Path, "~") {
|
if strings.Contains(ctx.Config.Database.Path, "~") {
|
||||||
curUser, err := user.Current()
|
curUser, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error().Msg("Failed to get current user. Will replace '~' for '/' in storage path!")
|
ctx.Logger.Error().Msg("Failed to get current user. Will replace '~' for '/' in storage path!")
|
||||||
|
|
||||||
path = strings.Replace(path, "~", "/", -1)
|
path = strings.Replace(path, "~", "/", -1)
|
||||||
}
|
}
|
||||||
|
@ -204,68 +212,73 @@ func (ff *FlatFiles) Initialize() {
|
||||||
path, _ = filepath.Abs(path)
|
path, _ = filepath.Abs(path)
|
||||||
ff.path = path
|
ff.path = path
|
||||||
|
|
||||||
c.Logger.Debug().Msgf("Storage path is now: %s", ff.path)
|
ctx.Logger.Debug().Msgf("Storage path is now: %s", ff.path)
|
||||||
|
|
||||||
// Create directory if necessary.
|
// Create directory if necessary.
|
||||||
if _, err := os.Stat(ff.path); err != nil {
|
if _, err := os.Stat(ff.path); err != nil {
|
||||||
c.Logger.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...")
|
ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...")
|
||||||
_ = os.MkdirAll(ff.path, os.ModePerm)
|
_ = os.MkdirAll(ff.path, os.ModePerm)
|
||||||
} else {
|
} else {
|
||||||
c.Logger.Debug().Str("directory", ff.path).Msg("Directory already exists")
|
ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create directory for pastes.
|
// Create directory for pastes.
|
||||||
if _, err := os.Stat(filepath.Join(ff.path, "pastes")); err != nil {
|
if _, err := os.Stat(filepath.Join(ff.path, "pastes")); err != nil {
|
||||||
c.Logger.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...")
|
ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...")
|
||||||
_ = os.MkdirAll(filepath.Join(ff.path, "pastes"), os.ModePerm)
|
_ = os.MkdirAll(filepath.Join(ff.path, "pastes"), os.ModePerm)
|
||||||
} else {
|
} else {
|
||||||
c.Logger.Debug().Str("directory", ff.path).Msg("Directory already exists")
|
ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load pastes index.
|
// Load pastes index.
|
||||||
ff.pastesIndex = []Index{}
|
ff.pastesIndex = []Index{}
|
||||||
if _, err := os.Stat(filepath.Join(ff.path, "pastes", "index.json")); err != nil {
|
if _, err := os.Stat(filepath.Join(ff.path, "pastes", "index.json")); err != nil {
|
||||||
c.Logger.Warn().Msg("Pastes index file does not exist, will create new one")
|
ctx.Logger.Warn().Msg("Pastes index file does not exist, will create new one")
|
||||||
} else {
|
} else {
|
||||||
indexData, err := ioutil.ReadFile(filepath.Join(ff.path, "pastes", "index.json"))
|
indexData, err := os.ReadFile(filepath.Join(ff.path, "pastes", "index.json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Fatal().Msg("Failed to read contents of index file!")
|
ctx.Logger.Fatal().Msg("Failed to read contents of index file!")
|
||||||
}
|
}
|
||||||
|
|
||||||
err1 := json.Unmarshal(indexData, &ff.pastesIndex)
|
err1 := json.Unmarshal(indexData, &ff.pastesIndex)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
c.Logger.Error().Err(err1).Msg("Failed to parse index file contents from JSON into internal structure. Will create new index file. All of your previous pastes will became unavailable.")
|
ctx.Logger.Error().Err(err1).Msg("Failed to parse index file contents from JSON into internal structure. Will create new index file. All of your previous pastes will became unavailable.")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Logger.Debug().Int("pastes count", len(ff.pastesIndex)).Msg("Parsed pastes index")
|
ctx.Logger.Debug().Int("pastes count", len(ff.pastesIndex)).Msg("Parsed pastes index")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ff *FlatFiles) SavePaste(p *structs.Paste) (int64, error) {
|
func (ff *FlatFiles) SavePaste(paste *structs.Paste) (int64, error) {
|
||||||
ff.writeMutex.Lock()
|
ff.writeMutex.Lock()
|
||||||
// Write paste data on disk.
|
// Write paste data on disk.
|
||||||
filesOnDisk, _ := ioutil.ReadDir(filepath.Join(ff.path, "pastes"))
|
filesOnDisk, _ := os.ReadDir(filepath.Join(ff.path, "pastes"))
|
||||||
pasteID := len(filesOnDisk) + 1
|
pasteID := len(filesOnDisk) + 1
|
||||||
p.ID = pasteID
|
paste.ID = pasteID
|
||||||
|
|
||||||
c.Logger.Debug().Int("new paste ID", pasteID).Msg("Writing paste to disk")
|
ctx.Logger.Debug().Int("new paste ID", pasteID).Msg("Writing paste to disk")
|
||||||
|
|
||||||
data, err := json.Marshal(p)
|
data, err := json.Marshal(paste)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ff.writeMutex.Unlock()
|
ff.writeMutex.Unlock()
|
||||||
|
|
||||||
|
//nolint:wrapcheck
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ioutil.WriteFile(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"), data, 0644)
|
err = os.WriteFile(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"), data, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ff.writeMutex.Unlock()
|
ff.writeMutex.Unlock()
|
||||||
|
|
||||||
|
//nolint:wrapcheck
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add it to cache.
|
// Add it to cache.
|
||||||
|
//nolint:exhaustruct
|
||||||
indexData := Index{}
|
indexData := Index{}
|
||||||
indexData.ID = pasteID
|
indexData.ID = pasteID
|
||||||
indexData.Private = p.Private
|
indexData.Private = paste.Private
|
||||||
ff.pastesIndex = append(ff.pastesIndex, indexData)
|
ff.pastesIndex = append(ff.pastesIndex, indexData)
|
||||||
ff.writeMutex.Unlock()
|
ff.writeMutex.Unlock()
|
||||||
|
|
||||||
|
@ -273,17 +286,19 @@ func (ff *FlatFiles) SavePaste(p *structs.Paste) (int64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ff *FlatFiles) Shutdown() {
|
func (ff *FlatFiles) Shutdown() {
|
||||||
c.Logger.Info().Msg("Saving indexes...")
|
ctx.Logger.Info().Msg("Saving indexes...")
|
||||||
|
|
||||||
indexData, err := json.Marshal(ff.pastesIndex)
|
indexData, err := json.Marshal(ff.pastesIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error().Err(err).Msg("Failed to encode index data into JSON")
|
ctx.Logger.Error().Err(err).Msg("Failed to encode index data into JSON")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err1 := ioutil.WriteFile(filepath.Join(ff.path, "pastes", "index.json"), indexData, 0644)
|
err1 := os.WriteFile(filepath.Join(ff.path, "pastes", "index.json"), indexData, 0o600)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
c.Logger.Error().Err(err1).Msg("Failed to write index data to file. Pretty sure that you've lost your pastes.")
|
ctx.Logger.Error().Err(err1).Msg("Failed to write index data to file. Pretty sure that you've lost your pastes.")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,43 +25,41 @@
|
||||||
package flatfiles
|
package flatfiles
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct{}
|
type Handler struct{}
|
||||||
|
|
||||||
func (dbh Handler) DeletePaste(pasteID int) error {
|
func (dbh Handler) DeletePaste(pasteID int) error {
|
||||||
return f.DeletePaste(pasteID)
|
return flf.DeletePaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
||||||
return f.GetDatabaseConnection()
|
return flf.GetDatabaseConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||||
return f.GetPaste(pasteID)
|
return flf.GetPaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
return f.GetPagedPastes(page)
|
return flf.GetPagedPastes(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPastesPages() int {
|
func (dbh Handler) GetPastesPages() int {
|
||||||
return f.GetPastesPages()
|
return flf.GetPastesPages()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) Initialize() {
|
func (dbh Handler) Initialize() {
|
||||||
f.Initialize()
|
flf.Initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
||||||
return f.SavePaste(p)
|
return flf.SavePaste(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) Shutdown() {
|
func (dbh Handler) Shutdown() {
|
||||||
f.Shutdown()
|
flf.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,8 @@
|
||||||
package dialectinterface
|
package dialectinterface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -25,19 +25,19 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
c *context.Context
|
ctx *context.Context
|
||||||
d *Database
|
dbAdapter *Database
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
c = cc
|
ctx = cc
|
||||||
d = &Database{}
|
//nolint:exhaustruct
|
||||||
|
dbAdapter = &Database{}
|
||||||
|
|
||||||
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
ctx.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,43 +25,41 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct{}
|
type Handler struct{}
|
||||||
|
|
||||||
func (dbh Handler) DeletePaste(pasteID int) error {
|
func (dbh Handler) DeletePaste(pasteID int) error {
|
||||||
return d.DeletePaste(pasteID)
|
return dbAdapter.DeletePaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
||||||
return d.GetDatabaseConnection()
|
return dbAdapter.GetDatabaseConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||||
return d.GetPaste(pasteID)
|
return dbAdapter.GetPaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
return d.GetPagedPastes(page)
|
return dbAdapter.GetPagedPastes(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPastesPages() int {
|
func (dbh Handler) GetPastesPages() int {
|
||||||
return d.GetPastesPages()
|
return dbAdapter.GetPastesPages()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) Initialize() {
|
func (dbh Handler) Initialize() {
|
||||||
d.Initialize()
|
dbAdapter.Initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
||||||
return d.SavePaste(p)
|
return dbAdapter.SavePaste(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) Shutdown() {
|
func (dbh Handler) Shutdown() {
|
||||||
d.Shutdown()
|
dbAdapter.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -39,6 +38,7 @@ func InitialUp(tx *sql.Tx) error {
|
||||||
keep_for_unit_type int(1) NOT NULL DEFAULT 1 COMMENT 'Keep for unit type. 1 - minutes, 2 - hours, 3 - days, 4 - months.',
|
keep_for_unit_type int(1) NOT NULL DEFAULT 1 COMMENT 'Keep for unit type. 1 - minutes, 2 - hours, 3 - days, 4 - months.',
|
||||||
PRIMARY KEY (id), UNIQUE KEY id (id)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='Pastes';`)
|
PRIMARY KEY (id), UNIQUE KEY id (id)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='Pastes';`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,13 +25,13 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PasteLangUp(tx *sql.Tx) error {
|
func PasteLangUp(tx *sql.Tx) error {
|
||||||
_, err := tx.Exec("ALTER TABLE `pastes` ADD `language` VARCHAR(191) NOT NULL DEFAULT 'text' COMMENT 'Paste language'")
|
_, err := tx.Exec("ALTER TABLE `pastes` ADD `language` VARCHAR(191) NOT NULL DEFAULT 'text' COMMENT 'Paste language'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ func PasteLangUp(tx *sql.Tx) error {
|
||||||
func PasteLangDown(tx *sql.Tx) error {
|
func PasteLangDown(tx *sql.Tx) error {
|
||||||
_, err := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `language`")
|
_, err := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `language`")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,13 +25,13 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PrivatePastesUp(tx *sql.Tx) error {
|
func PrivatePastesUp(tx *sql.Tx) error {
|
||||||
_, err := tx.Exec("ALTER TABLE `pastes` ADD `private` BOOL NOT NULL DEFAULT false COMMENT 'Private paste? If true - additional URL parameter (UNIX TIMESTAMP) of paste will be required to access.'")
|
_, err := tx.Exec("ALTER TABLE `pastes` ADD `private` BOOL NOT NULL DEFAULT false COMMENT 'Private paste? If true - additional URL parameter (UNIX TIMESTAMP) of paste will be required to access.'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ func PrivatePastesUp(tx *sql.Tx) error {
|
||||||
func PrivatePastesDown(tx *sql.Tx) error {
|
func PrivatePastesDown(tx *sql.Tx) error {
|
||||||
_, err := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `private`")
|
_, err := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `private`")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,32 +25,35 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PasswordedPastesUp(tx *sql.Tx) error {
|
func PasswordedPastesUp(txn *sql.Tx) error {
|
||||||
_, err := tx.Exec("ALTER TABLE `pastes` ADD `password` varchar(64) NOT NULL DEFAULT '' COMMENT 'Password for paste (scrypted and sha256ed).'")
|
_, err := txn.Exec("ALTER TABLE `pastes` ADD `password` varchar(64) NOT NULL DEFAULT '' COMMENT 'Password for paste (scrypted and sha256ed).'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err1 := tx.Exec("ALTER TABLE `pastes` ADD `password_salt` varchar(64) NOT NULL DEFAULT '' COMMENT 'Password salt (sha256ed).'")
|
_, err1 := txn.Exec("ALTER TABLE `pastes` ADD `password_salt` varchar(64) NOT NULL DEFAULT '' COMMENT 'Password salt (sha256ed).'")
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func PasswordedPastesDown(tx *sql.Tx) error {
|
func PasswordedPastesDown(txn *sql.Tx) error {
|
||||||
_, err := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `password`")
|
_, err := txn.Exec("ALTER TABLE `pastes` DROP COLUMN `password`")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err1 := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `password_salt`")
|
_, err1 := txn.Exec("ALTER TABLE `pastes` DROP COLUMN `password_salt`")
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,26 +25,20 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
|
||||||
|
|
||||||
// other
|
|
||||||
//"gitlab.com/jmoiron/sqlx"
|
|
||||||
"github.com/pressly/goose"
|
"github.com/pressly/goose"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var ctx *context.Context
|
||||||
c *context.Context
|
|
||||||
)
|
|
||||||
|
|
||||||
// New initializes migrations.
|
// New initializes migrations.
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
c = cc
|
ctx = cc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate launching migrations.
|
// Migrate launching migrations.
|
||||||
func Migrate() {
|
func Migrate() {
|
||||||
c.Logger.Info().Msg("Migrating database...")
|
ctx.Logger.Info().Msg("Migrating database...")
|
||||||
|
|
||||||
_ = goose.SetDialect("mysql")
|
_ = goose.SetDialect("mysql")
|
||||||
goose.AddNamedMigration("1_initial.go", InitialUp, nil)
|
goose.AddNamedMigration("1_initial.go", InitialUp, nil)
|
||||||
|
@ -53,13 +47,13 @@ func Migrate() {
|
||||||
goose.AddNamedMigration("4_passworded_pastes.go", PasswordedPastesUp, PasswordedPastesDown)
|
goose.AddNamedMigration("4_passworded_pastes.go", PasswordedPastesUp, PasswordedPastesDown)
|
||||||
// Add new migrations BEFORE this message.
|
// Add new migrations BEFORE this message.
|
||||||
|
|
||||||
dbConn := c.Database.GetDatabaseConnection()
|
dbConn := ctx.Database.GetDatabaseConnection()
|
||||||
if dbConn != nil {
|
if dbConn != nil {
|
||||||
err := goose.Up(dbConn, ".")
|
err := goose.Up(dbConn, ".")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
|
ctx.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
|
ctx.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,17 +25,14 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
// local
|
// MySQL driver.
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/mysql/migrations"
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
|
||||||
|
|
||||||
// other
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/mysql/migrations"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Database is a MySQL/MariaDB connection controlling structure.
|
// Database is a MySQL/MariaDB connection controlling structure.
|
||||||
|
@ -62,6 +59,7 @@ func (db *Database) DeletePaste(pasteID int) error {
|
||||||
|
|
||||||
_, err := db.db.Exec(db.db.Rebind("DELETE FROM pastes WHERE id=?"), pasteID)
|
_, err := db.db.Exec(db.db.Rebind("DELETE FROM pastes WHERE id=?"), pasteID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,14 +80,16 @@ func (db *Database) GetDatabaseConnection() *sql.DB {
|
||||||
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
|
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||||
db.check()
|
db.check()
|
||||||
|
|
||||||
p := &structs.Paste{}
|
//nolint:exhaustruct
|
||||||
|
paste := &structs.Paste{}
|
||||||
|
|
||||||
err := db.db.Get(p, db.db.Rebind("SELECT * FROM `pastes` WHERE id=?"), pasteID)
|
err := db.db.Get(paste, db.db.Rebind("SELECT * FROM `pastes` WHERE id=?"), pasteID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return p, nil
|
return paste, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
|
@ -103,11 +103,12 @@ func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
// Pagination.
|
// Pagination.
|
||||||
startPagination := 0
|
startPagination := 0
|
||||||
if page > 1 {
|
if page > 1 {
|
||||||
startPagination = (page - 1) * c.Config.Pastes.Pagination
|
startPagination = (page - 1) * ctx.Config.Pastes.Pagination
|
||||||
}
|
}
|
||||||
|
|
||||||
err := db.db.Select(&pastesRaw, db.db.Rebind("SELECT * FROM `pastes` WHERE private != true ORDER BY id DESC LIMIT ? OFFSET ?"), c.Config.Pastes.Pagination, startPagination)
|
err := db.db.Select(&pastesRaw, db.db.Rebind("SELECT * FROM `pastes` WHERE private != true ORDER BY id DESC LIMIT ? OFFSET ?"), ctx.Config.Pastes.Pagination, startPagination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,9 +142,9 @@ func (db *Database) GetPastesPages() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate pages.
|
// Calculate pages.
|
||||||
pages := len(pastes) / c.Config.Pastes.Pagination
|
pages := len(pastes) / ctx.Config.Pastes.Pagination
|
||||||
// Check if we have any remainder. Add 1 to pages count if so.
|
// Check if we have any remainder. Add 1 to pages count if so.
|
||||||
if len(pastes)%c.Config.Pastes.Pagination > 0 {
|
if len(pastes)%ctx.Config.Pastes.Pagination > 0 {
|
||||||
pages++
|
pages++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,35 +153,36 @@ func (db *Database) GetPastesPages() int {
|
||||||
|
|
||||||
// Initialize initializes MySQL/MariaDB connection.
|
// Initialize initializes MySQL/MariaDB connection.
|
||||||
func (db *Database) Initialize() {
|
func (db *Database) Initialize() {
|
||||||
c.Logger.Info().Msg("Initializing database connection...")
|
ctx.Logger.Info().Msg("Initializing database connection...")
|
||||||
|
|
||||||
// There might be only user, without password. MySQL/MariaDB driver
|
// There might be only user, without password. MySQL/MariaDB driver
|
||||||
// in DSN wants "user" or "user:password", "user:" is invalid.
|
// in DSN wants "user" or "user:password", "user:" is invalid.
|
||||||
var userpass string
|
var userpass string
|
||||||
if c.Config.Database.Password == "" {
|
if ctx.Config.Database.Password == "" {
|
||||||
userpass = c.Config.Database.Username
|
userpass = ctx.Config.Database.Username
|
||||||
} else {
|
} else {
|
||||||
userpass = c.Config.Database.Username + ":" + c.Config.Database.Password
|
userpass = ctx.Config.Database.Username + ":" + ctx.Config.Database.Password
|
||||||
}
|
}
|
||||||
|
|
||||||
dbConnString := fmt.Sprintf("%s@tcp(%s:%s)/%s?parseTime=true&collation=utf8mb4_unicode_ci&charset=utf8mb4", userpass, c.Config.Database.Address, c.Config.Database.Port, c.Config.Database.Database)
|
dbConnString := fmt.Sprintf("%s@tcp(%s:%s)/%s?parseTime=true&collation=utf8mb4_unicode_ci&charset=utf8mb4", userpass, ctx.Config.Database.Address, ctx.Config.Database.Port, ctx.Config.Database.Database)
|
||||||
c.Logger.Debug().Str("DSN", dbConnString).Msgf("Database connection string composed")
|
ctx.Logger.Debug().Str("DSN", dbConnString).Msgf("Database connection string composed")
|
||||||
|
|
||||||
dbConn, err := sqlx.Connect("mysql", dbConnString)
|
dbConn, err := sqlx.Connect("mysql", dbConnString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error().Err(err).Msg("Failed to connect to database")
|
ctx.Logger.Error().Err(err).Msg("Failed to connect to database")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force UTC for current connection.
|
// Force UTC for current connection.
|
||||||
_ = dbConn.MustExec("SET @@session.time_zone='+00:00';")
|
_ = dbConn.MustExec("SET @@session.time_zone='+00:00';")
|
||||||
|
|
||||||
c.Logger.Info().Msg("Database connection established")
|
ctx.Logger.Info().Msg("Database connection established")
|
||||||
|
|
||||||
db.db = dbConn
|
db.db = dbConn
|
||||||
|
|
||||||
// Perform migrations.
|
// Perform migrations.
|
||||||
migrations.New(c)
|
migrations.New(ctx)
|
||||||
migrations.Migrate()
|
migrations.Migrate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,22 +191,24 @@ func (db *Database) SavePaste(p *structs.Paste) (int64, error) {
|
||||||
|
|
||||||
result, err := db.db.NamedExec("INSERT INTO `pastes` (title, data, created_at, keep_for, keep_for_unit_type, language, private, password, password_salt) VALUES (:title, :data, :created_at, :keep_for, :keep_for_unit_type, :language, :private, :password, :password_salt)", p)
|
result, err := db.db.NamedExec("INSERT INTO `pastes` (title, data, created_at, keep_for, keep_for_unit_type, language, private, password, password_salt) VALUES (:title, :data, :created_at, :keep_for, :keep_for_unit_type, :language, :private, :password, :password_salt)", p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ID, err1 := result.LastInsertId()
|
lastInsertID, err1 := result.LastInsertId()
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ID, nil
|
return lastInsertID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) Shutdown() {
|
func (db *Database) Shutdown() {
|
||||||
if db.db != nil {
|
if db.db != nil {
|
||||||
err := db.db.Close()
|
err := db.db.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error().Err(err).Msg("Failed to close database connection")
|
ctx.Logger.Error().Err(err).Msg("Failed to close database connection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,19 +25,19 @@
|
||||||
package postgresql
|
package postgresql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
c *context.Context
|
ctx *context.Context
|
||||||
d *Database
|
dbAdapter *Database
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
c = cc
|
ctx = cc
|
||||||
d = &Database{}
|
//nolint:exhaustruct
|
||||||
|
dbAdapter = &Database{}
|
||||||
|
|
||||||
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
ctx.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,43 +25,41 @@
|
||||||
package postgresql
|
package postgresql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct{}
|
type Handler struct{}
|
||||||
|
|
||||||
func (dbh Handler) DeletePaste(pasteID int) error {
|
func (dbh Handler) DeletePaste(pasteID int) error {
|
||||||
return d.DeletePaste(pasteID)
|
return dbAdapter.DeletePaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
||||||
return d.GetDatabaseConnection()
|
return dbAdapter.GetDatabaseConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||||
return d.GetPaste(pasteID)
|
return dbAdapter.GetPaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
return d.GetPagedPastes(page)
|
return dbAdapter.GetPagedPastes(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPastesPages() int {
|
func (dbh Handler) GetPastesPages() int {
|
||||||
return d.GetPastesPages()
|
return dbAdapter.GetPastesPages()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) Initialize() {
|
func (dbh Handler) Initialize() {
|
||||||
d.Initialize()
|
dbAdapter.Initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
||||||
return d.SavePaste(p)
|
return dbAdapter.SavePaste(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) Shutdown() {
|
func (dbh Handler) Shutdown() {
|
||||||
d.Shutdown()
|
dbAdapter.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,6 +48,7 @@ func InitialUp(tx *sql.Tx) error {
|
||||||
COMMENT ON COLUMN pastes.keep_for_unit_type IS 'Keep for unit type. 0 - forever, 1 - minutes, 2 - hours, 3 - days, 4 - months.';
|
COMMENT ON COLUMN pastes.keep_for_unit_type IS 'Keep for unit type. 0 - forever, 1 - minutes, 2 - hours, 3 - days, 4 - months.';
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,13 +25,13 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PasteLangUp(tx *sql.Tx) error {
|
func PasteLangUp(tx *sql.Tx) error {
|
||||||
_, err := tx.Exec("ALTER TABLE pastes ADD COLUMN language VARCHAR(191) NOT NULL DEFAULT 'text'; COMMENT ON COLUMN pastes.language IS 'Paste language';")
|
_, err := tx.Exec("ALTER TABLE pastes ADD COLUMN language VARCHAR(191) NOT NULL DEFAULT 'text'; COMMENT ON COLUMN pastes.language IS 'Paste language';")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ func PasteLangUp(tx *sql.Tx) error {
|
||||||
func PasteLangDown(tx *sql.Tx) error {
|
func PasteLangDown(tx *sql.Tx) error {
|
||||||
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN language")
|
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN language")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,13 +25,13 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PrivatePastesUp(tx *sql.Tx) error {
|
func PrivatePastesUp(tx *sql.Tx) error {
|
||||||
_, err := tx.Exec("ALTER TABLE pastes ADD COLUMN private BOOLEAN NOT NULL DEFAULT false; COMMENT ON COLUMN pastes.private IS 'Private paste? If true - additional URL parameter (UNIX TIMESTAMP) of paste will be required to access.';")
|
_, err := tx.Exec("ALTER TABLE pastes ADD COLUMN private BOOLEAN NOT NULL DEFAULT false; COMMENT ON COLUMN pastes.private IS 'Private paste? If true - additional URL parameter (UNIX TIMESTAMP) of paste will be required to access.';")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ func PrivatePastesUp(tx *sql.Tx) error {
|
||||||
func PrivatePastesDown(tx *sql.Tx) error {
|
func PrivatePastesDown(tx *sql.Tx) error {
|
||||||
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN private")
|
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN private")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,32 +25,35 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PasswordedPastesUp(tx *sql.Tx) error {
|
func PasswordedPastesUp(txn *sql.Tx) error {
|
||||||
_, err := tx.Exec("ALTER TABLE pastes ADD COLUMN password VARCHAR(64) NOT NULL DEFAULT ''; COMMENT ON COLUMN pastes.password IS 'Password for paste (scrypted and sha256ed).';")
|
_, err := txn.Exec("ALTER TABLE pastes ADD COLUMN password VARCHAR(64) NOT NULL DEFAULT ''; COMMENT ON COLUMN pastes.password IS 'Password for paste (scrypted and sha256ed).';")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err1 := tx.Exec("ALTER TABLE pastes ADD COLUMN password_salt VARCHAR(64) NOT NULL DEFAULT ''; COMMENT ON COLUMN pastes.password_salt IS 'Password salt (sha256ed).';")
|
_, err1 := txn.Exec("ALTER TABLE pastes ADD COLUMN password_salt VARCHAR(64) NOT NULL DEFAULT ''; COMMENT ON COLUMN pastes.password_salt IS 'Password salt (sha256ed).';")
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func PasswordedPastesDown(tx *sql.Tx) error {
|
func PasswordedPastesDown(txn *sql.Tx) error {
|
||||||
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN password")
|
_, err := txn.Exec("ALTER TABLE pastes DROP COLUMN password")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err1 := tx.Exec("ALTER TABLE pastes DROP COLUMN password_salt")
|
_, err1 := txn.Exec("ALTER TABLE pastes DROP COLUMN password_salt")
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,26 +25,20 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
|
||||||
|
|
||||||
// other
|
|
||||||
//"gitlab.com/jmoiron/sqlx"
|
|
||||||
"github.com/pressly/goose"
|
"github.com/pressly/goose"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var ctx *context.Context
|
||||||
c *context.Context
|
|
||||||
)
|
|
||||||
|
|
||||||
// New initializes migrations.
|
// New initializes migrations.
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
c = cc
|
ctx = cc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate launching migrations.
|
// Migrate launching migrations.
|
||||||
func Migrate() {
|
func Migrate() {
|
||||||
c.Logger.Info().Msg("Migrating database...")
|
ctx.Logger.Info().Msg("Migrating database...")
|
||||||
|
|
||||||
_ = goose.SetDialect("postgres")
|
_ = goose.SetDialect("postgres")
|
||||||
goose.AddNamedMigration("1_initial.go", InitialUp, nil)
|
goose.AddNamedMigration("1_initial.go", InitialUp, nil)
|
||||||
|
@ -53,14 +47,14 @@ func Migrate() {
|
||||||
goose.AddNamedMigration("4_passworded_pastes.go", PasswordedPastesUp, PasswordedPastesDown)
|
goose.AddNamedMigration("4_passworded_pastes.go", PasswordedPastesUp, PasswordedPastesDown)
|
||||||
// Add new migrations BEFORE this message.
|
// Add new migrations BEFORE this message.
|
||||||
|
|
||||||
dbConn := c.Database.GetDatabaseConnection()
|
dbConn := ctx.Database.GetDatabaseConnection()
|
||||||
if dbConn != nil {
|
if dbConn != nil {
|
||||||
err := goose.Up(dbConn, ".")
|
err := goose.Up(dbConn, ".")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Info().Msgf("%+v", err)
|
ctx.Logger.Info().Msgf("%+v", err)
|
||||||
c.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
|
ctx.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
|
ctx.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,20 +24,19 @@
|
||||||
|
|
||||||
package postgresql
|
package postgresql
|
||||||
|
|
||||||
|
//nolint:gci
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
// local
|
// PostgreSQL driver.
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/postgresql/migrations"
|
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/postgresql/migrations"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||||
|
|
||||||
// other
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
// postgresql adapter
|
|
||||||
_ "github.com/lib/pq"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Database is a PostgreSQL connection controlling structure.
|
// Database is a PostgreSQL connection controlling structure.
|
||||||
|
@ -64,6 +63,7 @@ func (db *Database) DeletePaste(pasteID int) error {
|
||||||
|
|
||||||
_, err := db.db.Exec(db.db.Rebind("DELETE FROM pastes WHERE id=?"), pasteID)
|
_, err := db.db.Exec(db.db.Rebind("DELETE FROM pastes WHERE id=?"), pasteID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,10 +84,12 @@ func (db *Database) GetDatabaseConnection() *sql.DB {
|
||||||
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
|
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||||
db.check()
|
db.check()
|
||||||
|
|
||||||
p := &structs.Paste{}
|
//nolint:exhaustruct
|
||||||
|
paste := &structs.Paste{}
|
||||||
|
|
||||||
err := db.db.Get(p, db.db.Rebind("SELECT * FROM pastes WHERE id=$1"), pasteID)
|
err := db.db.Get(paste, db.db.Rebind("SELECT * FROM pastes WHERE id=$1"), pasteID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,10 +97,10 @@ func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||||
// timestamps in server's local timezone. We should convert them.
|
// timestamps in server's local timezone. We should convert them.
|
||||||
loc, _ := time.LoadLocation("UTC")
|
loc, _ := time.LoadLocation("UTC")
|
||||||
|
|
||||||
utcCreatedAt := p.CreatedAt.In(loc)
|
utcCreatedAt := paste.CreatedAt.In(loc)
|
||||||
p.CreatedAt = &utcCreatedAt
|
paste.CreatedAt = &utcCreatedAt
|
||||||
|
|
||||||
return p, nil
|
return paste, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
|
@ -112,11 +114,12 @@ func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
// Pagination.
|
// Pagination.
|
||||||
startPagination := 0
|
startPagination := 0
|
||||||
if page > 1 {
|
if page > 1 {
|
||||||
startPagination = (page - 1) * c.Config.Pastes.Pagination
|
startPagination = (page - 1) * ctx.Config.Pastes.Pagination
|
||||||
}
|
}
|
||||||
|
|
||||||
err := db.db.Select(&pastesRaw, db.db.Rebind("SELECT * FROM pastes WHERE private != true ORDER BY id DESC LIMIT $1 OFFSET $2"), c.Config.Pastes.Pagination, startPagination)
|
err := db.db.Select(&pastesRaw, db.db.Rebind("SELECT * FROM pastes WHERE private != true ORDER BY id DESC LIMIT $1 OFFSET $2"), ctx.Config.Pastes.Pagination, startPagination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,9 +159,9 @@ func (db *Database) GetPastesPages() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate pages.
|
// Calculate pages.
|
||||||
pages := len(pastes) / c.Config.Pastes.Pagination
|
pages := len(pastes) / ctx.Config.Pastes.Pagination
|
||||||
// Check if we have any remainder. Add 1 to pages count if so.
|
// Check if we have any remainder. Add 1 to pages count if so.
|
||||||
if len(pastes)%c.Config.Pastes.Pagination > 0 {
|
if len(pastes)%ctx.Config.Pastes.Pagination > 0 {
|
||||||
pages++
|
pages++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,56 +170,59 @@ func (db *Database) GetPastesPages() int {
|
||||||
|
|
||||||
// Initialize initializes MySQL/MariaDB connection.
|
// Initialize initializes MySQL/MariaDB connection.
|
||||||
func (db *Database) Initialize() {
|
func (db *Database) Initialize() {
|
||||||
c.Logger.Info().Msg("Initializing database connection...")
|
ctx.Logger.Info().Msg("Initializing database connection...")
|
||||||
|
|
||||||
var userpass string
|
var userpass string
|
||||||
if c.Config.Database.Password == "" {
|
if ctx.Config.Database.Password == "" {
|
||||||
userpass = c.Config.Database.Username
|
userpass = ctx.Config.Database.Username
|
||||||
} else {
|
} else {
|
||||||
userpass = c.Config.Database.Username + ":" + c.Config.Database.Password
|
userpass = ctx.Config.Database.Username + ":" + ctx.Config.Database.Password
|
||||||
}
|
}
|
||||||
|
|
||||||
dbConnString := fmt.Sprintf("postgres://%s@%s:%s/%s?connect_timeout=10&fallback_application_name=fastpastebin&sslmode=disable", userpass, c.Config.Database.Address, c.Config.Database.Port, c.Config.Database.Database)
|
dbConnString := fmt.Sprintf("postgres://%s@%s/%s?connect_timeout=10&fallback_application_name=fastpastebin&sslmode=disable", userpass, net.JoinHostPort(ctx.Config.Database.Address, ctx.Config.Database.Port), ctx.Config.Database.Database)
|
||||||
c.Logger.Debug().Str("DSN", dbConnString).Msg("Database connection string composed")
|
ctx.Logger.Debug().Str("DSN", dbConnString).Msg("Database connection string composed")
|
||||||
|
|
||||||
dbConn, err := sqlx.Connect("postgres", dbConnString)
|
dbConn, err := sqlx.Connect("postgres", dbConnString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error().Err(err).Msg("Failed to connect to database")
|
ctx.Logger.Error().Err(err).Msg("Failed to connect to database")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Logger.Info().Msg("Database connection established")
|
ctx.Logger.Info().Msg("Database connection established")
|
||||||
|
|
||||||
db.db = dbConn
|
db.db = dbConn
|
||||||
|
|
||||||
// Perform migrations.
|
// Perform migrations.
|
||||||
migrations.New(c)
|
migrations.New(ctx)
|
||||||
migrations.Migrate()
|
migrations.Migrate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) SavePaste(p *structs.Paste) (int64, error) {
|
func (db *Database) SavePaste(paste *structs.Paste) (int64, error) {
|
||||||
db.check()
|
db.check()
|
||||||
|
|
||||||
stmt, err := db.db.PrepareNamed("INSERT INTO pastes (title, data, created_at, keep_for, keep_for_unit_type, language, private, password, password_salt) VALUES (:title, :data, :created_at, :keep_for, :keep_for_unit_type, :language, :private, :password, :password_salt) RETURNING id")
|
stmt, err := db.db.PrepareNamed("INSERT INTO pastes (title, data, created_at, keep_for, keep_for_unit_type, language, private, password, password_salt) VALUES (:title, :data, :created_at, :keep_for, :keep_for_unit_type, :language, :private, :password, :password_salt) RETURNING id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var id int64
|
var newPasteID int64
|
||||||
|
|
||||||
err = stmt.Get(&id, p)
|
err = stmt.Get(&newPasteID, paste)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return id, nil
|
return newPasteID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) Shutdown() {
|
func (db *Database) Shutdown() {
|
||||||
if db.db != nil {
|
if db.db != nil {
|
||||||
err := db.db.Close()
|
err := db.db.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error().Err(err).Msg("Failed to close database connection")
|
ctx.Logger.Error().Err(err).Msg("Failed to close database connection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,20 +25,20 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface"
|
databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
c *context.Context
|
ctx *context.Context
|
||||||
d *Database
|
dbAdapter *Database
|
||||||
)
|
)
|
||||||
|
|
||||||
// New initializes database structure.
|
// New initializes database structure.
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
c = cc
|
ctx = cc
|
||||||
d = &Database{}
|
//nolint:exhaustruct
|
||||||
|
dbAdapter = &Database{}
|
||||||
|
|
||||||
c.RegisterDatabaseInterface(databaseinterface.Interface(Handler{}))
|
ctx.RegisterDatabaseInterface(databaseinterface.Interface(Handler{}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,11 +25,8 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
// local
|
|
||||||
|
|
||||||
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||||
)
|
)
|
||||||
|
@ -39,38 +36,38 @@ import (
|
||||||
type Handler struct{}
|
type Handler struct{}
|
||||||
|
|
||||||
func (dbh Handler) DeletePaste(pasteID int) error {
|
func (dbh Handler) DeletePaste(pasteID int) error {
|
||||||
return d.DeletePaste(pasteID)
|
return dbAdapter.DeletePaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
||||||
return d.GetDatabaseConnection()
|
return dbAdapter.GetDatabaseConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||||
return d.GetPaste(pasteID)
|
return dbAdapter.GetPaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
return d.GetPagedPastes(page)
|
return dbAdapter.GetPagedPastes(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPastesPages() int {
|
func (dbh Handler) GetPastesPages() int {
|
||||||
return d.GetPastesPages()
|
return dbAdapter.GetPastesPages()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize initializes connection to database.
|
// Initialize initializes connection to database.
|
||||||
func (dbh Handler) Initialize() {
|
func (dbh Handler) Initialize() {
|
||||||
d.Initialize()
|
dbAdapter.Initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) RegisterDialect(di dialectinterface.Interface) {
|
func (dbh Handler) RegisterDialect(di dialectinterface.Interface) {
|
||||||
d.RegisterDialect(di)
|
dbAdapter.RegisterDialect(di)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
||||||
return d.SavePaste(p)
|
return dbAdapter.SavePaste(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) Shutdown() {
|
func (dbh Handler) Shutdown() {
|
||||||
d.Shutdown()
|
dbAdapter.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,8 @@
|
||||||
package databaseinterface
|
package databaseinterface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
// local
|
|
||||||
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,33 +1,31 @@
|
||||||
package pagination
|
package pagination
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
// local
|
"go.dev.pztrn.name/fastpastebin/assets"
|
||||||
"go.dev.pztrn.name/fastpastebin/assets/static"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateHTML creates pagination HTML based on passed parameters.
|
// CreateHTML creates pagination HTML based on passed parameters.
|
||||||
func CreateHTML(currentPage int, pages int, linksBase string) string {
|
func CreateHTML(currentPage int, pages int, linksBase string) string {
|
||||||
// Load templates.
|
// Load templates.
|
||||||
paginationHTMLRaw, err := static.ReadFile("pagination.html")
|
paginationHTMLRaw, err := assets.Data.ReadFile("pagination.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "Missing pagination.html"
|
return "Missing pagination.html"
|
||||||
}
|
}
|
||||||
|
|
||||||
paginationLinkRaw, err1 := static.ReadFile("pagination_link.html")
|
paginationLinkRaw, err1 := assets.Data.ReadFile("pagination_link.html")
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
return "Missing pagination_link.html"
|
return "Missing pagination_link.html"
|
||||||
}
|
}
|
||||||
|
|
||||||
paginationLinkCurrentRaw, err2 := static.ReadFile("pagination_link_current.html")
|
paginationLinkCurrentRaw, err2 := assets.Data.ReadFile("pagination_link_current.html")
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return "Missing pagination_link_current.html"
|
return "Missing pagination_link_current.html"
|
||||||
}
|
}
|
||||||
|
|
||||||
paginationEllipsisRaw, err3 := static.ReadFile("pagination_ellipsis.html")
|
paginationEllipsisRaw, err3 := assets.Data.ReadFile("pagination_ellipsis.html")
|
||||||
if err3 != nil {
|
if err3 != nil {
|
||||||
return "Missing pagination_ellipsis.html"
|
return "Missing pagination_ellipsis.html"
|
||||||
}
|
}
|
||||||
|
@ -44,13 +42,16 @@ func CreateHTML(currentPage int, pages int, linksBase string) string {
|
||||||
var (
|
var (
|
||||||
ellipsisStartAdded = false
|
ellipsisStartAdded = false
|
||||||
ellipsisEndAdded = false
|
ellipsisEndAdded = false
|
||||||
|
//nolint:varnamelen
|
||||||
i = 2
|
i = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
for i <= pages {
|
for i <= pages {
|
||||||
|
// ToDo: fix it!
|
||||||
|
//nolint:nestif
|
||||||
if pages > 5 {
|
if pages > 5 {
|
||||||
if currentPage-3 < i && currentPage+3 > i || i == pages {
|
if currentPage-3 < i && currentPage+3 > i || i == pages {
|
||||||
var paginationItemRaw = string(paginationLinkRaw)
|
paginationItemRaw := string(paginationLinkRaw)
|
||||||
if i == currentPage {
|
if i == currentPage {
|
||||||
paginationItemRaw = string(paginationLinkCurrentRaw)
|
paginationItemRaw = string(paginationLinkCurrentRaw)
|
||||||
}
|
}
|
||||||
|
@ -68,7 +69,7 @@ func CreateHTML(currentPage int, pages int, linksBase string) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var paginationItemRaw = string(paginationLinkRaw)
|
paginationItemRaw := string(paginationLinkRaw)
|
||||||
if i == currentPage {
|
if i == currentPage {
|
||||||
paginationItemRaw = string(paginationLinkCurrentRaw)
|
paginationItemRaw = string(paginationLinkCurrentRaw)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,26 +25,31 @@
|
||||||
package structs
|
package structs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
// other
|
|
||||||
"golang.org/x/crypto/scrypt"
|
"golang.org/x/crypto/scrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// PasteKeepForever indicates that paste should be kept forever.
|
||||||
PasteKeepForever = 0
|
PasteKeepForever = 0
|
||||||
|
// PasteKeepForMinutes indicates that saved timeout is in minutes.
|
||||||
PasteKeepForMinutes = 1
|
PasteKeepForMinutes = 1
|
||||||
|
// PasteKeepForHours indicates that saved timeout is in hours.
|
||||||
PasteKeepForHours = 2
|
PasteKeepForHours = 2
|
||||||
|
// PasteKeepForDays indicates that saved timeout is in days.
|
||||||
PasteKeepForDays = 3
|
PasteKeepForDays = 3
|
||||||
|
// PasteKeepForMonths indicates that saved timeout is in months.
|
||||||
PasteKeepForMonths = 4
|
PasteKeepForMonths = 4
|
||||||
|
|
||||||
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PasteKeepsCorrelation is a correlation map between database representation
|
||||||
|
// and passed data representation.
|
||||||
var PasteKeepsCorrelation = map[string]int{
|
var PasteKeepsCorrelation = map[string]int{
|
||||||
"M": PasteKeepForMinutes,
|
"M": PasteKeepForMinutes,
|
||||||
"h": PasteKeepForHours,
|
"h": PasteKeepForHours,
|
||||||
|
@ -70,6 +75,8 @@ type Paste struct {
|
||||||
// CreatePassword creates password for current paste.
|
// CreatePassword creates password for current paste.
|
||||||
func (p *Paste) CreatePassword(password string) error {
|
func (p *Paste) CreatePassword(password string) error {
|
||||||
// Create salt - random string.
|
// Create salt - random string.
|
||||||
|
// Yes, it is insecure. Should be refactored!
|
||||||
|
//nolint:gosec
|
||||||
seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
|
seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
saltBytes := make([]byte, 64)
|
saltBytes := make([]byte, 64)
|
||||||
|
|
||||||
|
@ -83,6 +90,7 @@ func (p *Paste) CreatePassword(password string) error {
|
||||||
// Create crypted password and hash it.
|
// Create crypted password and hash it.
|
||||||
passwordCrypted, err := scrypt.Key([]byte(password), []byte(p.PasswordSalt), 131072, 8, 1, 64)
|
passwordCrypted, err := scrypt.Key([]byte(password), []byte(p.PasswordSalt), 131072, 8, 1, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
//nolint:wrapcheck
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +103,7 @@ func (p *Paste) CreatePassword(password string) error {
|
||||||
// GenerateCryptedCookieValue generates crypted cookie value for paste.
|
// GenerateCryptedCookieValue generates crypted cookie value for paste.
|
||||||
func (p *Paste) GenerateCryptedCookieValue() string {
|
func (p *Paste) GenerateCryptedCookieValue() string {
|
||||||
cookieValueCrypted, _ := scrypt.Key([]byte(p.Password), []byte(p.PasswordSalt), 131072, 8, 1, 64)
|
cookieValueCrypted, _ := scrypt.Key([]byte(p.Password), []byte(p.PasswordSalt), 131072, 8, 1, 64)
|
||||||
|
|
||||||
return fmt.Sprintf("%x", sha256.Sum256(cookieValueCrypted))
|
return fmt.Sprintf("%x", sha256.Sum256(cookieValueCrypted))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,21 +25,17 @@
|
||||||
package templater
|
package templater
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// stdlib
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
// local
|
|
||||||
"go.dev.pztrn.name/fastpastebin/assets/static"
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
|
||||||
|
|
||||||
// other
|
|
||||||
"github.com/labstack/echo"
|
"github.com/labstack/echo"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/assets"
|
||||||
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
c *context.Context
|
ctx *context.Context
|
||||||
log zerolog.Logger
|
log zerolog.Logger
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,11 +50,12 @@ func GetErrorTemplate(ec echo.Context, errorText string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRawTemplate returns only raw template data.
|
// GetRawTemplate returns only raw template data.
|
||||||
func GetRawTemplate(ec echo.Context, templateName string, data map[string]string) string {
|
func GetRawTemplate(ectx echo.Context, templateName string, data map[string]string) string {
|
||||||
// Getting main template.
|
// Getting main template.
|
||||||
tplRaw, err := static.ReadFile(templateName)
|
tplRaw, err := assets.Data.ReadFile(templateName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = ec.String(http.StatusBadRequest, templateName+" not found.")
|
_ = ectx.String(http.StatusBadRequest, templateName+" not found.")
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,27 +69,30 @@ func GetRawTemplate(ec echo.Context, templateName string, data map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTemplate returns formatted template that can be outputted to client.
|
// GetTemplate returns formatted template that can be outputted to client.
|
||||||
func GetTemplate(ec echo.Context, name string, data map[string]string) string {
|
func GetTemplate(ectx echo.Context, name string, data map[string]string) string {
|
||||||
log.Debug().Str("name", name).Msg("Requested template")
|
log.Debug().Str("name", name).Msg("Requested template")
|
||||||
|
|
||||||
// Getting main template.
|
// Getting main template.
|
||||||
mainhtml, err := static.ReadFile("main.html")
|
mainhtml, err := assets.Data.ReadFile("main.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = ec.String(http.StatusBadRequest, "main.html not found.")
|
_ = ectx.String(http.StatusBadRequest, "main.html not found.")
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getting navigation.
|
// Getting navigation.
|
||||||
navhtml, err1 := static.ReadFile("navigation.html")
|
navhtml, err1 := assets.Data.ReadFile("navigation.html")
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
_ = ec.String(http.StatusBadRequest, "navigation.html not found.")
|
_ = ectx.String(http.StatusBadRequest, "navigation.html not found.")
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getting footer.
|
// Getting footer.
|
||||||
footerhtml, err2 := static.ReadFile("footer.html")
|
footerhtml, err2 := assets.Data.ReadFile("footer.html")
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
_ = ec.String(http.StatusBadRequest, "footer.html not found.")
|
_ = ectx.String(http.StatusBadRequest, "footer.html not found.")
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,9 +103,10 @@ func GetTemplate(ec echo.Context, name string, data map[string]string) string {
|
||||||
tpl = strings.Replace(tpl, "{version}", context.Version, 1)
|
tpl = strings.Replace(tpl, "{version}", context.Version, 1)
|
||||||
|
|
||||||
// Get requested template.
|
// Get requested template.
|
||||||
reqhtml, err3 := static.ReadFile(name)
|
reqhtml, err3 := assets.Data.ReadFile(name)
|
||||||
if err3 != nil {
|
if err3 != nil {
|
||||||
_ = ec.String(http.StatusBadRequest, name+" not found.")
|
_ = ectx.String(http.StatusBadRequest, name+" not found.")
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +123,6 @@ func GetTemplate(ec echo.Context, name string, data map[string]string) string {
|
||||||
|
|
||||||
// Initialize initializes package.
|
// Initialize initializes package.
|
||||||
func Initialize(cc *context.Context) {
|
func Initialize(cc *context.Context) {
|
||||||
c = cc
|
ctx = cc
|
||||||
log = c.Logger.With().Str("type", "internal").Str("package", "templater").Logger()
|
log = ctx.Logger.With().Str("type", "internal").Str("package", "templater").Logger()
|
||||||
}
|
}
|
||||||
|
|
19
vendor/github.com/alecthomas/chroma/.gitignore
generated
vendored
19
vendor/github.com/alecthomas/chroma/.gitignore
generated
vendored
|
@ -1,19 +0,0 @@
|
||||||
# Binaries for programs and plugins
|
|
||||||
*.exe
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
/cmd/chroma/chroma
|
|
||||||
|
|
||||||
# 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/
|
|
||||||
|
|
||||||
_models/
|
|
||||||
|
|
||||||
_examples/
|
|
55
vendor/github.com/alecthomas/chroma/.golangci.yml
generated
vendored
55
vendor/github.com/alecthomas/chroma/.golangci.yml
generated
vendored
|
@ -1,55 +0,0 @@
|
||||||
run:
|
|
||||||
tests: true
|
|
||||||
skip-dirs:
|
|
||||||
- _examples
|
|
||||||
|
|
||||||
output:
|
|
||||||
print-issued-lines: false
|
|
||||||
|
|
||||||
linters:
|
|
||||||
enable-all: true
|
|
||||||
disable:
|
|
||||||
- maligned
|
|
||||||
- megacheck
|
|
||||||
- lll
|
|
||||||
- gocyclo
|
|
||||||
- dupl
|
|
||||||
- gochecknoglobals
|
|
||||||
- funlen
|
|
||||||
- godox
|
|
||||||
- wsl
|
|
||||||
- gomnd
|
|
||||||
- gocognit
|
|
||||||
|
|
||||||
linters-settings:
|
|
||||||
govet:
|
|
||||||
check-shadowing: true
|
|
||||||
gocyclo:
|
|
||||||
min-complexity: 10
|
|
||||||
dupl:
|
|
||||||
threshold: 100
|
|
||||||
goconst:
|
|
||||||
min-len: 8
|
|
||||||
min-occurrences: 3
|
|
||||||
|
|
||||||
issues:
|
|
||||||
max-per-linter: 0
|
|
||||||
max-same: 0
|
|
||||||
exclude-use-default: false
|
|
||||||
exclude:
|
|
||||||
# Captured by errcheck.
|
|
||||||
- '^(G104|G204):'
|
|
||||||
# Very commonly not checked.
|
|
||||||
- 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
|
|
||||||
- 'exported method (.*\.MarshalJSON|.*\.UnmarshalJSON|.*\.EntityURN|.*\.GoString|.*\.Pos) should have comment or be unexported'
|
|
||||||
- 'composite literal uses unkeyed fields'
|
|
||||||
- 'declaration of "err" shadows declaration'
|
|
||||||
- 'should not use dot imports'
|
|
||||||
- 'Potential file inclusion via variable'
|
|
||||||
- 'should have comment or be unexported'
|
|
||||||
- 'comment on exported var .* should be of the form'
|
|
||||||
- 'at least one file in a package should have a package comment'
|
|
||||||
- 'string literal contains the Unicode'
|
|
||||||
- 'methods on the same type should have the same receiver name'
|
|
||||||
- '_TokenType_name should be _TokenTypeName'
|
|
||||||
- '`_TokenType_map` should be `_TokenTypeMap`'
|
|
33
vendor/github.com/alecthomas/chroma/.goreleaser.yml
generated
vendored
33
vendor/github.com/alecthomas/chroma/.goreleaser.yml
generated
vendored
|
@ -1,33 +0,0 @@
|
||||||
project_name: chroma
|
|
||||||
release:
|
|
||||||
github:
|
|
||||||
owner: alecthomas
|
|
||||||
name: chroma
|
|
||||||
brews:
|
|
||||||
-
|
|
||||||
install: bin.install "chroma"
|
|
||||||
builds:
|
|
||||||
- goos:
|
|
||||||
- linux
|
|
||||||
- darwin
|
|
||||||
- windows
|
|
||||||
goarch:
|
|
||||||
- amd64
|
|
||||||
- "386"
|
|
||||||
goarm:
|
|
||||||
- "6"
|
|
||||||
main: ./cmd/chroma/main.go
|
|
||||||
ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}
|
|
||||||
binary: chroma
|
|
||||||
archives:
|
|
||||||
-
|
|
||||||
format: tar.gz
|
|
||||||
name_template: '{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{
|
|
||||||
.Arm }}{{ end }}'
|
|
||||||
files:
|
|
||||||
- COPYING
|
|
||||||
- README*
|
|
||||||
snapshot:
|
|
||||||
name_template: SNAPSHOT-{{ .Commit }}
|
|
||||||
checksum:
|
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt'
|
|
12
vendor/github.com/alecthomas/chroma/.travis.yml
generated
vendored
12
vendor/github.com/alecthomas/chroma/.travis.yml
generated
vendored
|
@ -1,12 +0,0 @@
|
||||||
sudo: false
|
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- "1.13.x"
|
|
||||||
script:
|
|
||||||
- go test -v ./...
|
|
||||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s v1.22.2
|
|
||||||
- ./bin/golangci-lint run
|
|
||||||
- git clean -fdx .
|
|
||||||
after_success:
|
|
||||||
curl -sL https://git.io/goreleaser | bash && goreleaser
|
|
||||||
|
|
19
vendor/github.com/alecthomas/chroma/COPYING
generated
vendored
19
vendor/github.com/alecthomas/chroma/COPYING
generated
vendored
|
@ -1,19 +0,0 @@
|
||||||
Copyright (C) 2017 Alec Thomas
|
|
||||||
|
|
||||||
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.
|
|
19
vendor/github.com/alecthomas/chroma/Makefile
generated
vendored
19
vendor/github.com/alecthomas/chroma/Makefile
generated
vendored
|
@ -1,19 +0,0 @@
|
||||||
.PHONY: chromad upload all
|
|
||||||
|
|
||||||
all: README.md tokentype_string.go
|
|
||||||
|
|
||||||
README.md: lexers/*/*.go
|
|
||||||
./table.py
|
|
||||||
|
|
||||||
tokentype_string.go: types.go
|
|
||||||
go generate
|
|
||||||
|
|
||||||
chromad:
|
|
||||||
(cd ./cmd/chromad && go get github.com/GeertJohan/go.rice/rice@master && go install github.com/GeertJohan/go.rice/rice)
|
|
||||||
rm -f chromad
|
|
||||||
(export CGOENABLED=0 GOOS=linux ; cd ./cmd/chromad && go build -o ../../chromad .)
|
|
||||||
rice append -i ./cmd/chromad --exec=./chromad
|
|
||||||
|
|
||||||
upload: chromad
|
|
||||||
scp chromad root@swapoff.org: && \
|
|
||||||
ssh root@swapoff.org 'install -m755 ./chromad /srv/http/swapoff.org/bin && service chromad restart'
|
|
267
vendor/github.com/alecthomas/chroma/README.md
generated
vendored
267
vendor/github.com/alecthomas/chroma/README.md
generated
vendored
|
@ -1,267 +0,0 @@
|
||||||
# Chroma — A general purpose syntax highlighter in pure Go [![Golang Documentation](https://godoc.org/github.com/alecthomas/chroma?status.svg)](https://godoc.org/github.com/alecthomas/chroma) [![Build Status](https://travis-ci.org/alecthomas/chroma.svg)](https://travis-ci.org/alecthomas/chroma) [![Gitter chat](https://badges.gitter.im/alecthomas.svg)](https://gitter.im/alecthomas/Lobby)
|
|
||||||
|
|
||||||
> **NOTE:** As Chroma has just been released, its API is still in flux. That said, the high-level interface should not change significantly.
|
|
||||||
|
|
||||||
Chroma takes source code and other structured text and converts it into syntax
|
|
||||||
highlighted HTML, ANSI-coloured text, etc.
|
|
||||||
|
|
||||||
Chroma is based heavily on [Pygments](http://pygments.org/), and includes
|
|
||||||
translators for Pygments lexers and styles.
|
|
||||||
|
|
||||||
<a id="markdown-table-of-contents" name="table-of-contents"></a>
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
<!-- TOC -->
|
|
||||||
|
|
||||||
1. [Table of Contents](#table-of-contents)
|
|
||||||
2. [Supported languages](#supported-languages)
|
|
||||||
3. [Try it](#try-it)
|
|
||||||
4. [Using the library](#using-the-library)
|
|
||||||
1. [Quick start](#quick-start)
|
|
||||||
2. [Identifying the language](#identifying-the-language)
|
|
||||||
3. [Formatting the output](#formatting-the-output)
|
|
||||||
4. [The HTML formatter](#the-html-formatter)
|
|
||||||
5. [More detail](#more-detail)
|
|
||||||
1. [Lexers](#lexers)
|
|
||||||
2. [Formatters](#formatters)
|
|
||||||
3. [Styles](#styles)
|
|
||||||
6. [Command-line interface](#command-line-interface)
|
|
||||||
7. [What's missing compared to Pygments?](#whats-missing-compared-to-pygments)
|
|
||||||
|
|
||||||
<!-- /TOC -->
|
|
||||||
|
|
||||||
<a id="markdown-supported-languages" name="supported-languages"></a>
|
|
||||||
## Supported languages
|
|
||||||
|
|
||||||
Prefix | Language
|
|
||||||
:----: | --------
|
|
||||||
A | ABAP, ABNF, ActionScript, ActionScript 3, Ada, Angular2, ANTLR, ApacheConf, APL, AppleScript, Arduino, Awk
|
|
||||||
B | Ballerina, Base Makefile, Bash, Batchfile, BlitzBasic, BNF, Brainfuck
|
|
||||||
C | C, C#, C++, Cap'n Proto, Cassandra CQL, Ceylon, CFEngine3, cfstatement, ChaiScript, Cheetah, Clojure, CMake, COBOL, CoffeeScript, Common Lisp, Coq, Crystal, CSS, Cython
|
|
||||||
D | D, Dart, Diff, Django/Jinja, Docker, DTD
|
|
||||||
E | EBNF, Elixir, Elm, EmacsLisp, Erlang
|
|
||||||
F | Factor, Fish, Forth, Fortran, FSharp
|
|
||||||
G | GAS, GDScript, Genshi, Genshi HTML, Genshi Text, GLSL, Gnuplot, Go, Go HTML Template, Go Text Template, GraphQL, Groovy
|
|
||||||
H | Handlebars, Haskell, Haxe, HCL, Hexdump, HTML, HTTP, Hy
|
|
||||||
I | Idris, INI, Io
|
|
||||||
J | J, Java, JavaScript, JSON, Julia, Jungle
|
|
||||||
K | Kotlin
|
|
||||||
L | Lighttpd configuration file, LLVM, Lua
|
|
||||||
M | Mako, markdown, Mason, Mathematica, Matlab, MiniZinc, MLIR, Modula-2, MonkeyC, MorrowindScript, Myghty, MySQL
|
|
||||||
N | NASM, Newspeak, Nginx configuration file, Nim, Nix
|
|
||||||
O | Objective-C, OCaml, Octave, OpenSCAD, Org Mode
|
|
||||||
P | PacmanConf, Perl, PHP, Pig, PkgConfig, PL/pgSQL, plaintext, PostgreSQL SQL dialect, PostScript, POVRay, PowerShell, Prolog, Protocol Buffer, Puppet, Python, Python 3
|
|
||||||
Q | QBasic
|
|
||||||
R | R, Racket, Ragel, react, reg, reStructuredText, Rexx, Ruby, Rust
|
|
||||||
S | Sass, Scala, Scheme, Scilab, SCSS, Smalltalk, Smarty, SML, Snobol, Solidity, SPARQL, SQL, SquidConf, Swift, SYSTEMD, systemverilog
|
|
||||||
T | TableGen, TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turing, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData
|
|
||||||
V | VB.net, verilog, VHDL, VimL, vue
|
|
||||||
W | WDTE
|
|
||||||
X | XML, Xorg
|
|
||||||
Y | YAML
|
|
||||||
|
|
||||||
|
|
||||||
_I will attempt to keep this section up to date, but an authoritative list can be
|
|
||||||
displayed with `chroma --list`._
|
|
||||||
|
|
||||||
<a id="markdown-try-it" name="try-it"></a>
|
|
||||||
## Try it
|
|
||||||
|
|
||||||
Try out various languages and styles on the [Chroma Playground](https://swapoff.org/chroma/playground/).
|
|
||||||
|
|
||||||
<a id="markdown-using-the-library" name="using-the-library"></a>
|
|
||||||
## Using the library
|
|
||||||
|
|
||||||
Chroma, like Pygments, has the concepts of
|
|
||||||
[lexers](https://github.com/alecthomas/chroma/tree/master/lexers),
|
|
||||||
[formatters](https://github.com/alecthomas/chroma/tree/master/formatters) and
|
|
||||||
[styles](https://github.com/alecthomas/chroma/tree/master/styles).
|
|
||||||
|
|
||||||
Lexers convert source text into a stream of tokens, styles specify how token
|
|
||||||
types are mapped to colours, and formatters convert tokens and styles into
|
|
||||||
formatted output.
|
|
||||||
|
|
||||||
A package exists for each of these, containing a global `Registry` variable
|
|
||||||
with all of the registered implementations. There are also helper functions
|
|
||||||
for using the registry in each package, such as looking up lexers by name or
|
|
||||||
matching filenames, etc.
|
|
||||||
|
|
||||||
In all cases, if a lexer, formatter or style can not be determined, `nil` will
|
|
||||||
be returned. In this situation you may want to default to the `Fallback`
|
|
||||||
value in each respective package, which provides sane defaults.
|
|
||||||
|
|
||||||
<a id="markdown-quick-start" name="quick-start"></a>
|
|
||||||
### Quick start
|
|
||||||
|
|
||||||
A convenience function exists that can be used to simply format some source
|
|
||||||
text, without any effort:
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := quick.Highlight(os.Stdout, someSourceCode, "go", "html", "monokai")
|
|
||||||
```
|
|
||||||
|
|
||||||
<a id="markdown-identifying-the-language" name="identifying-the-language"></a>
|
|
||||||
### Identifying the language
|
|
||||||
|
|
||||||
To highlight code, you'll first have to identify what language the code is
|
|
||||||
written in. There are three primary ways to do that:
|
|
||||||
|
|
||||||
1. Detect the language from its filename.
|
|
||||||
|
|
||||||
```go
|
|
||||||
lexer := lexers.Match("foo.go")
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Explicitly specify the language by its Chroma syntax ID (a full list is available from `lexers.Names()`).
|
|
||||||
|
|
||||||
```go
|
|
||||||
lexer := lexers.Get("go")
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Detect the language from its content.
|
|
||||||
|
|
||||||
```go
|
|
||||||
lexer := lexers.Analyse("package main\n\nfunc main()\n{\n}\n")
|
|
||||||
```
|
|
||||||
|
|
||||||
In all cases, `nil` will be returned if the language can not be identified.
|
|
||||||
|
|
||||||
```go
|
|
||||||
if lexer == nil {
|
|
||||||
lexer = lexers.Fallback
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
At this point, it should be noted that some lexers can be extremely chatty. To
|
|
||||||
mitigate this, you can use the coalescing lexer to coalesce runs of identical
|
|
||||||
token types into a single token:
|
|
||||||
|
|
||||||
```go
|
|
||||||
lexer = chroma.Coalesce(lexer)
|
|
||||||
```
|
|
||||||
|
|
||||||
<a id="markdown-formatting-the-output" name="formatting-the-output"></a>
|
|
||||||
### Formatting the output
|
|
||||||
|
|
||||||
Once a language is identified you will need to pick a formatter and a style (theme).
|
|
||||||
|
|
||||||
```go
|
|
||||||
style := styles.Get("swapoff")
|
|
||||||
if style == nil {
|
|
||||||
style = styles.Fallback
|
|
||||||
}
|
|
||||||
formatter := formatters.Get("html")
|
|
||||||
if formatter == nil {
|
|
||||||
formatter = formatters.Fallback
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then obtain an iterator over the tokens:
|
|
||||||
|
|
||||||
```go
|
|
||||||
contents, err := ioutil.ReadAll(r)
|
|
||||||
iterator, err := lexer.Tokenise(nil, string(contents))
|
|
||||||
```
|
|
||||||
|
|
||||||
And finally, format the tokens from the iterator:
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := formatter.Format(w, style, iterator)
|
|
||||||
```
|
|
||||||
|
|
||||||
<a id="markdown-the-html-formatter" name="the-html-formatter"></a>
|
|
||||||
### The HTML formatter
|
|
||||||
|
|
||||||
By default the `html` registered formatter generates standalone HTML with
|
|
||||||
embedded CSS. More flexibility is available through the `formatters/html` package.
|
|
||||||
|
|
||||||
Firstly, the output generated by the formatter can be customised with the
|
|
||||||
following constructor options:
|
|
||||||
|
|
||||||
- `Standalone()` - generate standalone HTML with embedded CSS.
|
|
||||||
- `WithClasses()` - use classes rather than inlined style attributes.
|
|
||||||
- `ClassPrefix(prefix)` - prefix each generated CSS class.
|
|
||||||
- `TabWidth(width)` - Set the rendered tab width, in characters.
|
|
||||||
- `WithLineNumbers()` - Render line numbers (style with `LineNumbers`).
|
|
||||||
- `LinkableLineNumbers()` - Make the line numbers linkable.
|
|
||||||
- `HighlightLines(ranges)` - Highlight lines in these ranges (style with `LineHighlight`).
|
|
||||||
- `LineNumbersInTable()` - Use a table for formatting line numbers and code, rather than spans.
|
|
||||||
|
|
||||||
If `WithClasses()` is used, the corresponding CSS can be obtained from the formatter with:
|
|
||||||
|
|
||||||
```go
|
|
||||||
formatter := html.New(html.WithClasses())
|
|
||||||
err := formatter.WriteCSS(w, style)
|
|
||||||
```
|
|
||||||
|
|
||||||
<a id="markdown-more-detail" name="more-detail"></a>
|
|
||||||
## More detail
|
|
||||||
|
|
||||||
<a id="markdown-lexers" name="lexers"></a>
|
|
||||||
### Lexers
|
|
||||||
|
|
||||||
See the [Pygments documentation](http://pygments.org/docs/lexerdevelopment/)
|
|
||||||
for details on implementing lexers. Most concepts apply directly to Chroma,
|
|
||||||
but see existing lexer implementations for real examples.
|
|
||||||
|
|
||||||
In many cases lexers can be automatically converted directly from Pygments by
|
|
||||||
using the included Python 3 script `pygments2chroma.py`. I use something like
|
|
||||||
the following:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
python3 ~/Projects/chroma/_tools/pygments2chroma.py \
|
|
||||||
pygments.lexers.jvm.KotlinLexer \
|
|
||||||
> ~/Projects/chroma/lexers/kotlin.go \
|
|
||||||
&& gofmt -s -w ~/Projects/chroma/lexers/*.go
|
|
||||||
```
|
|
||||||
|
|
||||||
See notes in [pygments-lexers.go](https://github.com/alecthomas/chroma/blob/master/pygments-lexers.txt)
|
|
||||||
for a list of lexers, and notes on some of the issues importing them.
|
|
||||||
|
|
||||||
<a id="markdown-formatters" name="formatters"></a>
|
|
||||||
### Formatters
|
|
||||||
|
|
||||||
Chroma supports HTML output, as well as terminal output in 8 colour, 256 colour, and true-colour.
|
|
||||||
|
|
||||||
A `noop` formatter is included that outputs the token text only, and a `tokens`
|
|
||||||
formatter outputs raw tokens. The latter is useful for debugging lexers.
|
|
||||||
|
|
||||||
<a id="markdown-styles" name="styles"></a>
|
|
||||||
### Styles
|
|
||||||
|
|
||||||
Chroma styles use the [same syntax](http://pygments.org/docs/styles/) as Pygments.
|
|
||||||
|
|
||||||
All Pygments styles have been converted to Chroma using the `_tools/style.py` script.
|
|
||||||
|
|
||||||
When you work with one of [Chroma's styles](https://github.com/alecthomas/chroma/tree/master/styles), know that the `chroma.Background` token type provides the default style for tokens. It does so by defining a foreground color and background color.
|
|
||||||
|
|
||||||
For example, this gives each token name not defined in the style a default color of `#f8f8f8` and uses `#000000` for the highlighted code block's background:
|
|
||||||
|
|
||||||
~~~go
|
|
||||||
chroma.Background: "#f8f8f2 bg:#000000",
|
|
||||||
~~~
|
|
||||||
|
|
||||||
Also, token types in a style file are hierarchical. For instance, when `CommentSpecial` is not defined, Chroma uses the token style from `Comment`. So when several comment tokens use the same color, you'll only need to define `Comment` and override the one that has a different color.
|
|
||||||
|
|
||||||
For a quick overview of the available styles and how they look, check out the [Chroma Style Gallery](https://xyproto.github.io/splash/docs/).
|
|
||||||
|
|
||||||
<a id="markdown-command-line-interface" name="command-line-interface"></a>
|
|
||||||
## Command-line interface
|
|
||||||
|
|
||||||
A command-line interface to Chroma is included. It can be installed with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
go get -u github.com/alecthomas/chroma/cmd/chroma
|
|
||||||
```
|
|
||||||
|
|
||||||
<a id="markdown-whats-missing-compared-to-pygments" name="whats-missing-compared-to-pygments"></a>
|
|
||||||
## What's missing compared to Pygments?
|
|
||||||
|
|
||||||
- Quite a few lexers, for various reasons (pull-requests welcome):
|
|
||||||
- Pygments lexers for complex languages often include custom code to
|
|
||||||
handle certain aspects, such as Perl6's ability to nest code inside
|
|
||||||
regular expressions. These require time and effort to convert.
|
|
||||||
- I mostly only converted languages I had heard of, to reduce the porting cost.
|
|
||||||
- Some more esoteric features of Pygments are omitted for simplicity.
|
|
||||||
- Though the Chroma API supports content detection, very few languages support them.
|
|
||||||
I have plans to implement a statistical analyser at some point, but not enough time.
|
|
35
vendor/github.com/alecthomas/chroma/coalesce.go
generated
vendored
35
vendor/github.com/alecthomas/chroma/coalesce.go
generated
vendored
|
@ -1,35 +0,0 @@
|
||||||
package chroma
|
|
||||||
|
|
||||||
// Coalesce is a Lexer interceptor that collapses runs of common types into a single token.
|
|
||||||
func Coalesce(lexer Lexer) Lexer { return &coalescer{lexer} }
|
|
||||||
|
|
||||||
type coalescer struct{ Lexer }
|
|
||||||
|
|
||||||
func (d *coalescer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) {
|
|
||||||
var prev Token
|
|
||||||
it, err := d.Lexer.Tokenise(options, text)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return func() Token {
|
|
||||||
for token := it(); token != (EOF); token = it() {
|
|
||||||
if len(token.Value) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if prev == EOF {
|
|
||||||
prev = token
|
|
||||||
} else {
|
|
||||||
if prev.Type == token.Type && len(prev.Value) < 8192 {
|
|
||||||
prev.Value += token.Value
|
|
||||||
} else {
|
|
||||||
out := prev
|
|
||||||
prev = token
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out := prev
|
|
||||||
prev = EOF
|
|
||||||
return out
|
|
||||||
}, nil
|
|
||||||
}
|
|
164
vendor/github.com/alecthomas/chroma/colour.go
generated
vendored
164
vendor/github.com/alecthomas/chroma/colour.go
generated
vendored
|
@ -1,164 +0,0 @@
|
||||||
package chroma
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ANSI2RGB maps ANSI colour names, as supported by Chroma, to hex RGB values.
|
|
||||||
var ANSI2RGB = map[string]string{
|
|
||||||
"#ansiblack": "000000",
|
|
||||||
"#ansidarkred": "7f0000",
|
|
||||||
"#ansidarkgreen": "007f00",
|
|
||||||
"#ansibrown": "7f7fe0",
|
|
||||||
"#ansidarkblue": "00007f",
|
|
||||||
"#ansipurple": "7f007f",
|
|
||||||
"#ansiteal": "007f7f",
|
|
||||||
"#ansilightgray": "e5e5e5",
|
|
||||||
// Normal
|
|
||||||
"#ansidarkgray": "555555",
|
|
||||||
"#ansired": "ff0000",
|
|
||||||
"#ansigreen": "00ff00",
|
|
||||||
"#ansiyellow": "ffff00",
|
|
||||||
"#ansiblue": "0000ff",
|
|
||||||
"#ansifuchsia": "ff00ff",
|
|
||||||
"#ansiturquoise": "00ffff",
|
|
||||||
"#ansiwhite": "ffffff",
|
|
||||||
|
|
||||||
// Aliases without the "ansi" prefix, because...why?
|
|
||||||
"#black": "000000",
|
|
||||||
"#darkred": "7f0000",
|
|
||||||
"#darkgreen": "007f00",
|
|
||||||
"#brown": "7f7fe0",
|
|
||||||
"#darkblue": "00007f",
|
|
||||||
"#purple": "7f007f",
|
|
||||||
"#teal": "007f7f",
|
|
||||||
"#lightgray": "e5e5e5",
|
|
||||||
// Normal
|
|
||||||
"#darkgray": "555555",
|
|
||||||
"#red": "ff0000",
|
|
||||||
"#green": "00ff00",
|
|
||||||
"#yellow": "ffff00",
|
|
||||||
"#blue": "0000ff",
|
|
||||||
"#fuchsia": "ff00ff",
|
|
||||||
"#turquoise": "00ffff",
|
|
||||||
"#white": "ffffff",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Colour represents an RGB colour.
|
|
||||||
type Colour int32
|
|
||||||
|
|
||||||
// NewColour creates a Colour directly from RGB values.
|
|
||||||
func NewColour(r, g, b uint8) Colour {
|
|
||||||
return ParseColour(fmt.Sprintf("%02x%02x%02x", r, g, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Distance between this colour and another.
|
|
||||||
//
|
|
||||||
// This uses the approach described here (https://www.compuphase.com/cmetric.htm).
|
|
||||||
// This is not as accurate as LAB, et. al. but is *vastly* simpler and sufficient for our needs.
|
|
||||||
func (c Colour) Distance(e2 Colour) float64 {
|
|
||||||
ar, ag, ab := int64(c.Red()), int64(c.Green()), int64(c.Blue())
|
|
||||||
br, bg, bb := int64(e2.Red()), int64(e2.Green()), int64(e2.Blue())
|
|
||||||
rmean := (ar + br) / 2
|
|
||||||
r := ar - br
|
|
||||||
g := ag - bg
|
|
||||||
b := ab - bb
|
|
||||||
return math.Sqrt(float64((((512 + rmean) * r * r) >> 8) + 4*g*g + (((767 - rmean) * b * b) >> 8)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Brighten returns a copy of this colour with its brightness adjusted.
|
|
||||||
//
|
|
||||||
// If factor is negative, the colour is darkened.
|
|
||||||
//
|
|
||||||
// Uses approach described here (http://www.pvladov.com/2012/09/make-color-lighter-or-darker.html).
|
|
||||||
func (c Colour) Brighten(factor float64) Colour {
|
|
||||||
r := float64(c.Red())
|
|
||||||
g := float64(c.Green())
|
|
||||||
b := float64(c.Blue())
|
|
||||||
|
|
||||||
if factor < 0 {
|
|
||||||
factor++
|
|
||||||
r *= factor
|
|
||||||
g *= factor
|
|
||||||
b *= factor
|
|
||||||
} else {
|
|
||||||
r = (255-r)*factor + r
|
|
||||||
g = (255-g)*factor + g
|
|
||||||
b = (255-b)*factor + b
|
|
||||||
}
|
|
||||||
return NewColour(uint8(r), uint8(g), uint8(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// BrightenOrDarken brightens a colour if it is < 0.5 brighteness or darkens if > 0.5 brightness.
|
|
||||||
func (c Colour) BrightenOrDarken(factor float64) Colour {
|
|
||||||
if c.Brightness() < 0.5 {
|
|
||||||
return c.Brighten(factor)
|
|
||||||
}
|
|
||||||
return c.Brighten(-factor)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Brightness of the colour (roughly) in the range 0.0 to 1.0
|
|
||||||
func (c Colour) Brightness() float64 {
|
|
||||||
return (float64(c.Red()) + float64(c.Green()) + float64(c.Blue())) / 255.0 / 3.0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseColour in the forms #rgb, #rrggbb, #ansi<colour>, or #<colour>.
|
|
||||||
// Will return an "unset" colour if invalid.
|
|
||||||
func ParseColour(colour string) Colour {
|
|
||||||
colour = normaliseColour(colour)
|
|
||||||
n, err := strconv.ParseUint(colour, 16, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return Colour(n + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustParseColour is like ParseColour except it panics if the colour is invalid.
|
|
||||||
//
|
|
||||||
// Will panic if colour is in an invalid format.
|
|
||||||
func MustParseColour(colour string) Colour {
|
|
||||||
parsed := ParseColour(colour)
|
|
||||||
if !parsed.IsSet() {
|
|
||||||
panic(fmt.Errorf("invalid colour %q", colour))
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns true if the colour is set.
|
|
||||||
func (c Colour) IsSet() bool { return c != 0 }
|
|
||||||
|
|
||||||
func (c Colour) String() string { return fmt.Sprintf("#%06x", int(c-1)) }
|
|
||||||
func (c Colour) GoString() string { return fmt.Sprintf("Colour(0x%06x)", int(c-1)) }
|
|
||||||
|
|
||||||
// Red component of colour.
|
|
||||||
func (c Colour) Red() uint8 { return uint8(((c - 1) >> 16) & 0xff) }
|
|
||||||
|
|
||||||
// Green component of colour.
|
|
||||||
func (c Colour) Green() uint8 { return uint8(((c - 1) >> 8) & 0xff) }
|
|
||||||
|
|
||||||
// Blue component of colour.
|
|
||||||
func (c Colour) Blue() uint8 { return uint8((c - 1) & 0xff) }
|
|
||||||
|
|
||||||
// Colours is an orderable set of colours.
|
|
||||||
type Colours []Colour
|
|
||||||
|
|
||||||
func (c Colours) Len() int { return len(c) }
|
|
||||||
func (c Colours) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
|
||||||
func (c Colours) Less(i, j int) bool { return c[i] < c[j] }
|
|
||||||
|
|
||||||
// Convert colours to #rrggbb.
|
|
||||||
func normaliseColour(colour string) string {
|
|
||||||
if ansi, ok := ANSI2RGB[colour]; ok {
|
|
||||||
return ansi
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(colour, "#") {
|
|
||||||
colour = colour[1:]
|
|
||||||
if len(colour) == 3 {
|
|
||||||
return colour[0:1] + colour[0:1] + colour[1:2] + colour[1:2] + colour[2:3] + colour[2:3]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return colour
|
|
||||||
}
|
|
137
vendor/github.com/alecthomas/chroma/delegate.go
generated
vendored
137
vendor/github.com/alecthomas/chroma/delegate.go
generated
vendored
|
@ -1,137 +0,0 @@
|
||||||
package chroma
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
)
|
|
||||||
|
|
||||||
type delegatingLexer struct {
|
|
||||||
root Lexer
|
|
||||||
language Lexer
|
|
||||||
}
|
|
||||||
|
|
||||||
// DelegatingLexer combines two lexers to handle the common case of a language embedded inside another, such as PHP
|
|
||||||
// inside HTML or PHP inside plain text.
|
|
||||||
//
|
|
||||||
// It takes two lexer as arguments: a root lexer and a language lexer. First everything is scanned using the language
|
|
||||||
// lexer, which must return "Other" for unrecognised tokens. Then all "Other" tokens are lexed using the root lexer.
|
|
||||||
// Finally, these two sets of tokens are merged.
|
|
||||||
//
|
|
||||||
// The lexers from the template lexer package use this base lexer.
|
|
||||||
func DelegatingLexer(root Lexer, language Lexer) Lexer {
|
|
||||||
return &delegatingLexer{
|
|
||||||
root: root,
|
|
||||||
language: language,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *delegatingLexer) Config() *Config {
|
|
||||||
return d.language.Config()
|
|
||||||
}
|
|
||||||
|
|
||||||
// An insertion is the character range where language tokens should be inserted.
|
|
||||||
type insertion struct {
|
|
||||||
start, end int
|
|
||||||
tokens []Token
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *delegatingLexer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) { // nolint: gocognit
|
|
||||||
tokens, err := Tokenise(Coalesce(d.language), options, text)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Compute insertions and gather "Other" tokens.
|
|
||||||
others := &bytes.Buffer{}
|
|
||||||
insertions := []*insertion{}
|
|
||||||
var insert *insertion
|
|
||||||
offset := 0
|
|
||||||
var last Token
|
|
||||||
for _, t := range tokens {
|
|
||||||
if t.Type == Other {
|
|
||||||
if last != EOF && insert != nil && last.Type != Other {
|
|
||||||
insert.end = offset
|
|
||||||
}
|
|
||||||
others.WriteString(t.Value)
|
|
||||||
} else {
|
|
||||||
if last == EOF || last.Type == Other {
|
|
||||||
insert = &insertion{start: offset}
|
|
||||||
insertions = append(insertions, insert)
|
|
||||||
}
|
|
||||||
insert.tokens = append(insert.tokens, t)
|
|
||||||
}
|
|
||||||
last = t
|
|
||||||
offset += len(t.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(insertions) == 0 {
|
|
||||||
return d.root.Tokenise(options, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lex the other tokens.
|
|
||||||
rootTokens, err := Tokenise(Coalesce(d.root), options, others.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interleave the two sets of tokens.
|
|
||||||
var out []Token
|
|
||||||
offset = 0 // Offset into text.
|
|
||||||
tokenIndex := 0
|
|
||||||
nextToken := func() Token {
|
|
||||||
if tokenIndex >= len(rootTokens) {
|
|
||||||
return EOF
|
|
||||||
}
|
|
||||||
t := rootTokens[tokenIndex]
|
|
||||||
tokenIndex++
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
insertionIndex := 0
|
|
||||||
nextInsertion := func() *insertion {
|
|
||||||
if insertionIndex >= len(insertions) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
i := insertions[insertionIndex]
|
|
||||||
insertionIndex++
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
t := nextToken()
|
|
||||||
i := nextInsertion()
|
|
||||||
for t != EOF || i != nil {
|
|
||||||
// fmt.Printf("%d->%d:%q %d->%d:%q\n", offset, offset+len(t.Value), t.Value, i.start, i.end, Stringify(i.tokens...))
|
|
||||||
if t == EOF || (i != nil && i.start < offset+len(t.Value)) {
|
|
||||||
var l Token
|
|
||||||
l, t = splitToken(t, i.start-offset)
|
|
||||||
if l != EOF {
|
|
||||||
out = append(out, l)
|
|
||||||
offset += len(l.Value)
|
|
||||||
}
|
|
||||||
out = append(out, i.tokens...)
|
|
||||||
offset += i.end - i.start
|
|
||||||
if t == EOF {
|
|
||||||
t = nextToken()
|
|
||||||
}
|
|
||||||
i = nextInsertion()
|
|
||||||
} else {
|
|
||||||
out = append(out, t)
|
|
||||||
offset += len(t.Value)
|
|
||||||
t = nextToken()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Literator(out...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitToken(t Token, offset int) (l Token, r Token) {
|
|
||||||
if t == EOF {
|
|
||||||
return EOF, EOF
|
|
||||||
}
|
|
||||||
if offset == 0 {
|
|
||||||
return EOF, t
|
|
||||||
}
|
|
||||||
if offset == len(t.Value) {
|
|
||||||
return t, EOF
|
|
||||||
}
|
|
||||||
l = t.Clone()
|
|
||||||
r = t.Clone()
|
|
||||||
l.Value = l.Value[:offset]
|
|
||||||
r.Value = r.Value[offset:]
|
|
||||||
return
|
|
||||||
}
|
|
7
vendor/github.com/alecthomas/chroma/doc.go
generated
vendored
7
vendor/github.com/alecthomas/chroma/doc.go
generated
vendored
|
@ -1,7 +0,0 @@
|
||||||
// Package chroma takes source code and other structured text and converts it into syntax highlighted HTML, ANSI-
|
|
||||||
// coloured text, etc.
|
|
||||||
//
|
|
||||||
// Chroma is based heavily on Pygments, and includes translators for Pygments lexers and styles.
|
|
||||||
//
|
|
||||||
// For more information, go here: https://github.com/alecthomas/chroma
|
|
||||||
package chroma
|
|
43
vendor/github.com/alecthomas/chroma/formatter.go
generated
vendored
43
vendor/github.com/alecthomas/chroma/formatter.go
generated
vendored
|
@ -1,43 +0,0 @@
|
||||||
package chroma
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Formatter for Chroma lexers.
|
|
||||||
type Formatter interface {
|
|
||||||
// Format returns a formatting function for tokens.
|
|
||||||
//
|
|
||||||
// If the iterator panics, the Formatter should recover.
|
|
||||||
Format(w io.Writer, style *Style, iterator Iterator) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// A FormatterFunc is a Formatter implemented as a function.
|
|
||||||
//
|
|
||||||
// Guards against iterator panics.
|
|
||||||
type FormatterFunc func(w io.Writer, style *Style, iterator Iterator) error
|
|
||||||
|
|
||||||
func (f FormatterFunc) Format(w io.Writer, s *Style, it Iterator) (err error) { // nolint
|
|
||||||
defer func() {
|
|
||||||
if perr := recover(); perr != nil {
|
|
||||||
err = perr.(error)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return f(w, s, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
type recoveringFormatter struct {
|
|
||||||
Formatter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r recoveringFormatter) Format(w io.Writer, s *Style, it Iterator) (err error) {
|
|
||||||
defer func() {
|
|
||||||
if perr := recover(); perr != nil {
|
|
||||||
err = perr.(error)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return r.Formatter.Format(w, s, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecoveringFormatter wraps a formatter with panic recovery.
|
|
||||||
func RecoveringFormatter(formatter Formatter) Formatter { return recoveringFormatter{formatter} }
|
|
57
vendor/github.com/alecthomas/chroma/formatters/api.go
generated
vendored
57
vendor/github.com/alecthomas/chroma/formatters/api.go
generated
vendored
|
@ -1,57 +0,0 @@
|
||||||
package formatters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/alecthomas/chroma"
|
|
||||||
"github.com/alecthomas/chroma/formatters/html"
|
|
||||||
"github.com/alecthomas/chroma/formatters/svg"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// NoOp formatter.
|
|
||||||
NoOp = Register("noop", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style, iterator chroma.Iterator) error {
|
|
||||||
for t := iterator(); t != chroma.EOF; t = iterator() {
|
|
||||||
if _, err := io.WriteString(w, t.Value); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}))
|
|
||||||
// Default HTML formatter outputs self-contained HTML.
|
|
||||||
htmlFull = Register("html", html.New(html.Standalone(true), html.WithClasses(true))) // nolint
|
|
||||||
SVG = Register("svg", svg.New(svg.EmbedFont("Liberation Mono", svg.FontLiberationMono, svg.WOFF)))
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fallback formatter.
|
|
||||||
var Fallback = NoOp
|
|
||||||
|
|
||||||
// Registry of Formatters.
|
|
||||||
var Registry = map[string]chroma.Formatter{}
|
|
||||||
|
|
||||||
// Names of registered formatters.
|
|
||||||
func Names() []string {
|
|
||||||
out := []string{}
|
|
||||||
for name := range Registry {
|
|
||||||
out = append(out, name)
|
|
||||||
}
|
|
||||||
sort.Strings(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get formatter by name.
|
|
||||||
//
|
|
||||||
// If the given formatter is not found, the Fallback formatter will be returned.
|
|
||||||
func Get(name string) chroma.Formatter {
|
|
||||||
if f, ok := Registry[name]; ok {
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
return Fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register a named formatter.
|
|
||||||
func Register(name string, formatter chroma.Formatter) chroma.Formatter {
|
|
||||||
Registry[name] = formatter
|
|
||||||
return formatter
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user