Compare commits
No commits in common. "HEAD" and "0.4.0" have entirely different histories.
79
.drone.yml
79
.drone.yml
@ -1,58 +1,69 @@
|
|||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: lint and test
|
name: build
|
||||||
|
|
||||||
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: code.pztrn.name/containers/mirror/golangci/golangci-lint:v1.48.0
|
image: golangci/golangci-lint:latest
|
||||||
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: code.pztrn.name/containers/mirror/golang:1.19.0-alpine
|
image: golang:1.13.5-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
|
||||||
kind: pipeline
|
image: plugins/docker
|
||||||
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:
|
||||||
registry: code.pztrn.name
|
username:
|
||||||
username: drone
|
from_secret: dockerhub_user
|
||||||
password:
|
password:
|
||||||
from_secret: drone_secret
|
from_secret: dockerhub_password
|
||||||
repo: code.pztrn.name/apps/fastpastebin
|
repo: pztrn/fastpastebin
|
||||||
auto_tag: true
|
auto_tag: true
|
||||||
|
depends_on:
|
||||||
|
- lint
|
||||||
|
- test
|
||||||
|
|
||||||
- name: build tagged image
|
- name: notify-end
|
||||||
image: code.pztrn.name/containers/mirror/plugins/docker:20.13.0
|
|
||||||
pull: if-not-exists
|
|
||||||
privileged: true
|
|
||||||
when:
|
when:
|
||||||
event: ["tag"]
|
status:
|
||||||
|
- success
|
||||||
|
- failure
|
||||||
|
image: pztrn/discordrone
|
||||||
settings:
|
settings:
|
||||||
registry: code.pztrn.name
|
webhook_id:
|
||||||
username: drone
|
from_secret: discord_webhook_id
|
||||||
password:
|
webhook_token:
|
||||||
from_secret: drone_secret
|
from_secret: discord_webhook_secret
|
||||||
repo: code.pztrn.name/apps/fastpastebin
|
message: "
|
||||||
auto_tag: true
|
{{#success build.status}}
|
||||||
|
**{{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,7 +1,6 @@
|
|||||||
examples/fastpastebin.yaml
|
examples/fastpastebin.yaml
|
||||||
dist/
|
dist/
|
||||||
data/
|
data/
|
||||||
vendor/
|
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
*DS_Store*
|
*DS_Store*
|
||||||
|
@ -1,35 +1,28 @@
|
|||||||
|
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
|
||||||
DOCKER_TCP_PORT: 2375
|
|
||||||
DOCKER_TLS_CERTDIR: ""
|
|
||||||
CONTAINER_NAME: registry.gitlab.pztrn.name/fastpastebin/fastpastebin
|
CONTAINER_NAME: registry.gitlab.pztrn.name/fastpastebin/fastpastebin
|
||||||
DIND_IMAGE: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/docker:dind
|
GIT_STRATEGY: clone
|
||||||
GOLANGCILINT_IMAGE: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/golangci/golangci-lint:v1.40.1-alpine
|
DOCKER_TLS_CERTDIR: ""
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- name: ${DIND_IMAGE}
|
- docker:dind
|
||||||
alias: docker
|
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- test
|
|
||||||
- build
|
- build
|
||||||
|
|
||||||
lint:
|
before_script:
|
||||||
stage: test
|
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||||
image: ${GOLANGCILINT_IMAGE}
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
script:
|
|
||||||
- golangci-lint run ./...
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
stage: build
|
stage: build
|
||||||
image: $DIND_IMAGE
|
|
||||||
tags:
|
tags:
|
||||||
- docker
|
- docker
|
||||||
script:
|
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,11 +12,6 @@ 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
|
||||||
@ -24,12 +19,3 @@ 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"
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"line-length": false,
|
|
||||||
"first-line-h1": false,
|
|
||||||
"no-duplicate-header": false
|
|
||||||
}
|
|
82
CHANGELOG.md
82
CHANGELOG.md
@ -1,84 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
``[A]`` - added
|
||||||
|
``[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).
|
|
||||||
|
|
||||||
## [Unreleased]
|
## 0.1.0
|
||||||
|
|
||||||
## [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 code.pztrn.name/containers/mirror/golang:1.19.0-alpine AS build
|
FROM golang:1.13.1-alpine AS build
|
||||||
|
|
||||||
WORKDIR /fastpastebin
|
WORKDIR /fastpastebin
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
WORKDIR /fastpastebin/cmd/fastpastebin
|
WORKDIR /fastpastebin/cmd/fastpastebin
|
||||||
|
|
||||||
RUN CGO_ENABLED=0 go build -tags netgo
|
RUN GOFLAGS="-mod=vendor" go build
|
||||||
|
|
||||||
FROM code.pztrn.name/containers/mirror/alpine:3.16.1
|
FROM alpine:3.10
|
||||||
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,13 +1,9 @@
|
|||||||
# Fast Pastebin
|
# Fast Pastebin
|
||||||
|
|
||||||
[![Build Status](https://ci.code.pztrn.name/api/badges/apps/fastpastebin/status.svg)](https://ci.code.pztrn.name/apps/fastpastebin)
|
[![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)
|
||||||
|
|
||||||
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.
|
||||||
@ -18,41 +14,18 @@ Also, [join Matrix room](https://matrix.to/#/%23fastpastebin:pztrn.online?via=ma
|
|||||||
|
|
||||||
## Caveats
|
## Caveats
|
||||||
|
|
||||||
* Not known at this moment.
|
* No links at lines numbers. See [this Chroma bug](https://github.com/alecthomas/chroma/issues/132)
|
||||||
|
|
||||||
## Installation and updating
|
## Installation and updating
|
||||||
|
|
||||||
Just issue:
|
Just issue:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
CGO_ENABLED=0 go install go.dev.pztrn.name/fastpastebin/cmd/fastpastebin@VERSION
|
CGO_ENABLED=0 go get -u -v go.dev.pztrn.name/fastpastebin/cmd/fastpastebin
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
||||||
@ -61,21 +34,18 @@ Configuration file position is irrelevant, there is no hardcoded paths where Fas
|
|||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
### Branching, versions, etc
|
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:
|
||||||
|
|
||||||
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**.
|
```bash
|
||||||
|
fileb0x fileb0x.yml
|
||||||
|
```
|
||||||
|
|
||||||
Branch `master` represents "latest version" state and always stable.
|
Also if you're changed list of assets (by creating or deleting them) be sure to fix files list in ``fileb0x.yml`` file!
|
||||||
|
|
||||||
### Code
|
The rest is default - use linters, formatters, etc. VSCode with Go plugin is recommended for developing as it will perform most of linting-formatting
|
||||||
|
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
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
//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
Normal file
1
assets/css/bulma-0.7.5.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
assets/css/bulma-tooltip-3.0.0.min.css
vendored
Normal file
1
assets/css/bulma-tooltip-3.0.0.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
assets/css/bulma.css.map
Normal file
1
assets/css/bulma.css.map
Normal file
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://code.pztrn.name/apps/fastpastebin">source or binary releases here</a>!
|
<a href="https://gitlab.pztrn.name/fastpastebin/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 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!">
|
<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!">
|
||||||
<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 has-tooltip-arrow" 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" 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">
|
||||||
|
5
assets/js/fontawesome-5.0.7.js
Normal file
5
assets/js/fontawesome-5.0.7.js
Normal file
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.9.4.min.css">
|
<link rel="stylesheet" href="/static/css/bulma-0.7.5.min.css">
|
||||||
<link rel="stylesheet" href="/static/css/bulma-tooltip-1.2.min.css">
|
<link rel="stylesheet" href="/static/css/bulma-tooltip-3.0.0.min.css">
|
||||||
<link rel="stylesheet" href="/static/css/style.css">
|
<link rel="stylesheet" href="/static/css/style.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
183
assets/static/ab0x.go
Normal file
183
assets/static/ab0x.go
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
35
assets/static/b0xfile__database_not_available.html.go
Normal file
35
assets/static/b0xfile__database_not_available.html.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
35
assets/static/b0xfile__error.html.go
Normal file
35
assets/static/b0xfile__error.html.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
35
assets/static/b0xfile__footer.html.go
Normal file
35
assets/static/b0xfile__footer.html.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
35
assets/static/b0xfile__index.html.go
Normal file
35
assets/static/b0xfile__index.html.go
Normal file
File diff suppressed because one or more lines are too long
35
assets/static/b0xfile__main.html.go
Normal file
35
assets/static/b0xfile__main.html.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
35
assets/static/b0xfile__navigation.html.go
Normal file
35
assets/static/b0xfile__navigation.html.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
35
assets/static/b0xfile__pagination.html.go
Normal file
35
assets/static/b0xfile__pagination.html.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
35
assets/static/b0xfile__pagination_ellipsis.html.go
Normal file
35
assets/static/b0xfile__pagination_ellipsis.html.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
35
assets/static/b0xfile__pagination_link.html.go
Normal file
35
assets/static/b0xfile__pagination_link.html.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
35
assets/static/b0xfile__pagination_link_current.html.go
Normal file
35
assets/static/b0xfile__pagination_link_current.html.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
35
assets/static/b0xfile__passworded_paste_verify.html.go
Normal file
35
assets/static/b0xfile__passworded_paste_verify.html.go
Normal file
File diff suppressed because one or more lines are too long
35
assets/static/b0xfile__paste.html.go
Normal file
35
assets/static/b0xfile__paste.html.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
35
assets/static/b0xfile__pastelist_list.html.go
Normal file
35
assets/static/b0xfile__pastelist_list.html.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
35
assets/static/b0xfile__pastelist_paste.html.go
Normal file
35
assets/static/b0xfile__pastelist_paste.html.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
35
assets/static/b0xfile_static_css_bulma-0.7.5.min.css.go
Normal file
35
assets/static/b0xfile_static_css_bulma-0.7.5.min.css.go
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
35
assets/static/b0xfile_static_css_bulma.css.map.go
Normal file
35
assets/static/b0xfile_static_css_bulma.css.map.go
Normal file
File diff suppressed because one or more lines are too long
35
assets/static/b0xfile_static_css_style.css.go
Normal file
35
assets/static/b0xfile_static_css_style.css.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
35
assets/static/b0xfile_static_js_fontawesome-5.0.7.js.go
Normal file
35
assets/static/b0xfile_static_js_fontawesome-5.0.7.js.go
Normal file
File diff suppressed because one or more lines are too long
1
assets/static/css/bulma-0.9.4.min.css
vendored
1
assets/static/css/bulma-0.9.4.min.css
vendored
File diff suppressed because one or more lines are too long
2
assets/static/css/bulma-tooltip-1.2.min.css
vendored
2
assets/static/css/bulma-tooltip-1.2.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
@ -25,11 +25,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
// stdlib
|
||||||
"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"
|
||||||
@ -40,29 +41,29 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
appCtx := context.New()
|
c := context.New()
|
||||||
appCtx.Initialize()
|
c.Initialize()
|
||||||
|
|
||||||
appCtx.Logger.Info().Msg("Starting Fast Pastebin...")
|
c.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.
|
||||||
flag.Parse()
|
c.Flagger.Parse()
|
||||||
|
|
||||||
// Continue loading.
|
// Continue loading.
|
||||||
appCtx.LoadConfiguration()
|
c.LoadConfiguration()
|
||||||
appCtx.InitializePost()
|
c.InitializePost()
|
||||||
database.New(appCtx)
|
database.New(c)
|
||||||
appCtx.Database.Initialize()
|
c.Database.Initialize()
|
||||||
templater.Initialize(appCtx)
|
templater.Initialize(c)
|
||||||
|
|
||||||
captcha.New(appCtx)
|
captcha.New(c)
|
||||||
|
|
||||||
dbnotavailable.New(appCtx)
|
dbnotavailable.New(c)
|
||||||
indexpage.New(appCtx)
|
indexpage.New(c)
|
||||||
pastes.New(appCtx)
|
pastes.New(c)
|
||||||
|
|
||||||
// CTRL+C handler.
|
// CTRL+C handler.
|
||||||
signalHandler := make(chan os.Signal, 1)
|
signalHandler := make(chan os.Signal, 1)
|
||||||
@ -72,7 +73,7 @@ func main() {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-signalHandler
|
<-signalHandler
|
||||||
appCtx.Shutdown()
|
c.Shutdown()
|
||||||
shutdownDone <- true
|
shutdownDone <- true
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ volumes:
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
image: mariadb:10.5
|
image: mariadb:10.3
|
||||||
container_name: database
|
container_name: database
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
|
@ -25,21 +25,23 @@
|
|||||||
package dbnotavailable
|
package dbnotavailable
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// stdlib
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/labstack/echo"
|
// local
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
||||||
|
|
||||||
|
// other
|
||||||
|
"github.com/labstack/echo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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,16 +25,19 @@
|
|||||||
package dbnotavailable
|
package dbnotavailable
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// local
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ctx *context.Context
|
var (
|
||||||
|
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) {
|
||||||
ctx = cc
|
c = cc
|
||||||
|
|
||||||
ctx.Echo.GET("/database_not_available", dbNotAvailableGet)
|
c.Echo.GET("/database_not_available", dbNotAvailableGet)
|
||||||
ctx.Echo.GET("/database_not_available/raw", dbNotAvailableRawGet)
|
c.Echo.GET("/database_not_available/raw", dbNotAvailableRawGet)
|
||||||
}
|
}
|
||||||
|
@ -25,15 +25,18 @@
|
|||||||
package indexpage
|
package indexpage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// local
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ctx *context.Context
|
var (
|
||||||
|
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) {
|
||||||
ctx = cc
|
c = cc
|
||||||
|
|
||||||
ctx.Echo.GET("/", indexGet)
|
c.Echo.GET("/", indexGet)
|
||||||
}
|
}
|
||||||
|
@ -25,28 +25,30 @@
|
|||||||
package indexpage
|
package indexpage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// stdlib
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/alecthomas/chroma/v2/lexers"
|
// local
|
||||||
"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/templater"
|
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
||||||
|
|
||||||
|
// other
|
||||||
|
"github.com/alecthomas/chroma/lexers"
|
||||||
|
"github.com/labstack/echo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Index of this site.
|
// Index of this site.
|
||||||
func indexGet(ectx echo.Context) error {
|
func indexGet(ec echo.Context) error {
|
||||||
// We should check if database connection available.
|
// We should check if database connection available.
|
||||||
dbConn := ctx.Database.GetDatabaseConnection()
|
dbConn := c.Database.GetDatabaseConnection()
|
||||||
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
|
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
||||||
//nolint:wrapcheck
|
return ec.Redirect(http.StatusFound, "/database_not_available")
|
||||||
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)
|
||||||
|
|
||||||
availableLexersSelectOpts := "<option value='text'>Text</option><option value='autodetect'>Auto detect</option><option disabled>-----</option>"
|
var 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>"
|
||||||
}
|
}
|
||||||
@ -54,8 +56,7 @@ func indexGet(ectx echo.Context) error {
|
|||||||
// Captcha.
|
// Captcha.
|
||||||
captchaString := captcha.NewCaptcha()
|
captchaString := captcha.NewCaptcha()
|
||||||
|
|
||||||
htmlData := templater.GetTemplate(ectx, "index.html", map[string]string{"lexers": availableLexersSelectOpts, "captchaString": captchaString})
|
htmlData := templater.GetTemplate(ec, "index.html", map[string]string{"lexers": availableLexersSelectOpts, "captchaString": captchaString})
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusOK, htmlData)
|
||||||
return ectx.HTML(http.StatusOK, htmlData)
|
|
||||||
}
|
}
|
||||||
|
@ -25,37 +25,43 @@
|
|||||||
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 regexInts = regexp.MustCompile("[0-9]+")
|
var (
|
||||||
|
regexInts = regexp.MustCompile("[0-9]+")
|
||||||
|
)
|
||||||
|
|
||||||
var ctx *context.Context
|
var (
|
||||||
|
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) {
|
||||||
ctx = cc
|
c = cc
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// HTTP endpoints.
|
// HTTP endpoints.
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// New paste.
|
// New paste.
|
||||||
ctx.Echo.POST("/paste/", pastePOSTWebInterface)
|
c.Echo.POST("/paste/", pastePOSTWebInterface)
|
||||||
// Show public paste.
|
// Show public paste.
|
||||||
ctx.Echo.GET("/paste/:id", pasteGETWebInterface)
|
c.Echo.GET("/paste/:id", pasteGETWebInterface)
|
||||||
// Show RAW representation of public paste.
|
// Show RAW representation of public paste.
|
||||||
ctx.Echo.GET("/paste/:id/raw", pasteRawGETWebInterface)
|
c.Echo.GET("/paste/:id/raw", pasteRawGETWebInterface)
|
||||||
// Show private paste.
|
// Show private paste.
|
||||||
ctx.Echo.GET("/paste/:id/:timestamp", pasteGETWebInterface)
|
c.Echo.GET("/paste/:id/:timestamp", pasteGETWebInterface)
|
||||||
// Show RAW representation of private paste.
|
// Show RAW representation of private paste.
|
||||||
ctx.Echo.GET("/paste/:id/:timestamp/raw", pasteRawGETWebInterface)
|
c.Echo.GET("/paste/:id/:timestamp/raw", pasteRawGETWebInterface)
|
||||||
// Verify access to passworded paste.
|
// Verify access to passworded paste.
|
||||||
ctx.Echo.GET("/paste/:id/:timestamp/verify", pastePasswordedVerifyGet)
|
c.Echo.GET("/paste/:id/:timestamp/verify", pastePasswordedVerifyGet)
|
||||||
ctx.Echo.POST("/paste/:id/:timestamp/verify", pastePasswordedVerifyPost)
|
c.Echo.POST("/paste/:id/:timestamp/verify", pastePasswordedVerifyPost)
|
||||||
// Pastes list.
|
// Pastes list.
|
||||||
ctx.Echo.GET("/pastes/", pastesGET)
|
c.Echo.GET("/pastes/", pastesGET)
|
||||||
ctx.Echo.GET("/pastes/:page", pastesGET)
|
c.Echo.GET("/pastes/:page", pastesGET)
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,23 @@
|
|||||||
package pastes
|
package pastes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// stdlib
|
||||||
"bytes"
|
"bytes"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
htmlfmt "github.com/alecthomas/chroma/v2/formatters/html"
|
// local
|
||||||
"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 (
|
||||||
@ -30,26 +35,23 @@ 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 := ctx.Database.GetPaste(pasteID)
|
paste, err1 := c.Database.GetPaste(pasteID)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste")
|
c.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() {
|
||||||
ctx.Logger.Error().Int("paste ID", pasteID).Msg("Paste is expired")
|
c.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 {
|
||||||
ctx.Logger.Error().Int("paste ID", pasteID).Int64("paste timestamp", pasteTS).Int64("provided timestamp", timestamp).Msg("Incorrect timestamp provided for private paste")
|
c.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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,29 +73,28 @@ 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(ectx echo.Context) error {
|
func pasteGETWebInterface(ec echo.Context) error {
|
||||||
pasteIDRaw := ectx.Param("id")
|
pasteIDRaw := ec.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)
|
||||||
ctx.Logger.Debug().Int("paste ID", pasteID).Msg("Trying to get paste data")
|
c.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 := ectx.Param("timestamp")
|
tsProvidedStr := ec.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 {
|
||||||
ctx.Logger.Error().Err(err).Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Msg("Invalid timestamp provided for getting private paste")
|
c.Logger.Error().Err(err).Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Msg("Invalid timestamp provided for getting private paste")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+pasteIDStr+" not found")
|
errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDStr+" not found")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||||
return ectx.HTML(http.StatusBadRequest, errtpl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp = tsProvided
|
timestamp = tsProvided
|
||||||
@ -103,28 +104,24 @@ func pasteGETWebInterface(ectx echo.Context) error {
|
|||||||
// for private pastes.
|
// for private pastes.
|
||||||
var cookieValue string
|
var cookieValue string
|
||||||
|
|
||||||
cookie, err1 := ectx.Cookie("PASTE-" + pasteIDStr)
|
cookie, err1 := ec.Cookie("PASTE-" + pasteIDStr)
|
||||||
if err1 == nil {
|
if err1 == nil {
|
||||||
cookieValue = cookie.Value
|
cookieValue = cookie.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
paste, err := pasteGetData(pasteID, timestamp, cookieValue)
|
paste, error := 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 err == pasteExpired || err == pasteNotFound || err == pasteTimestampInvalid {
|
if error == pasteExpired || error == pasteNotFound || error == pasteTimestampInvalid {
|
||||||
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+pasteIDRaw+" not found")
|
errtpl := templater.GetErrorTemplate(ec, "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 err == pasteCookieInvalid {
|
if error == pasteCookieInvalid {
|
||||||
ctx.Logger.Info().Int("paste ID", pasteID).Msg("Invalid cookie, redirecting to auth page")
|
c.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.
|
||||||
@ -158,7 +155,7 @@ func pasteGETWebInterface(ectx 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 {
|
||||||
ctx.Logger.Error().Err(err3).Msg("Failed to tokenize paste data")
|
c.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")
|
||||||
@ -166,61 +163,59 @@ func pasteGETWebInterface(ectx echo.Context) error {
|
|||||||
style = styles.Fallback
|
style = styles.Fallback
|
||||||
}
|
}
|
||||||
// Get HTML formatter.
|
// Get HTML formatter.
|
||||||
formatter := htmlfmt.New(htmlfmt.WithLineNumbers(true), htmlfmt.LineNumbersInTable(true), htmlfmt.LinkableLineNumbers(true, "L"))
|
formatter := chroma.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 {
|
||||||
ctx.Logger.Error().Err(err4).Msg("Failed to format paste data")
|
c.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(ectx, "paste.html", pasteData)
|
pasteHTML := templater.GetTemplate(ec, "paste.html", pasteData)
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusOK, pasteHTML)
|
||||||
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(ectx echo.Context) error {
|
func pastePasswordedVerifyGet(ec echo.Context) error {
|
||||||
pasteIDRaw := ectx.Param("id")
|
pasteIDRaw := ec.Param("id")
|
||||||
timestampRaw := ectx.Param("timestamp")
|
timestampRaw := ec.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 := ctx.Database.GetPaste(pasteID)
|
paste, err1 := c.Database.GetPaste(pasteID)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste data")
|
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste data")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+pasteIDRaw+" not found")
|
errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDRaw+" not found")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||||
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 := ectx.Cookie("PASTE-" + strconv.Itoa(pasteID))
|
cookie, err := ec.Cookie("PASTE-" + strconv.Itoa(pasteID))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// No cookie, redirect to auth page.
|
// No cookie, redirect to auth page.
|
||||||
ctx.Logger.Debug().Msg("Paste cookie found, checking it...")
|
c.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 {
|
||||||
ctx.Logger.Info().Msg("Valid cookie, redirecting to paste page...")
|
c.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"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Debug().Msg("Invalid cookie, showing auth page")
|
c.Logger.Debug().Msg("Invalid cookie, showing auth page")
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTML data.
|
// HTML data.
|
||||||
@ -228,47 +223,43 @@ func pastePasswordedVerifyGet(ectx echo.Context) error {
|
|||||||
htmlData["pasteID"] = strconv.Itoa(pasteID)
|
htmlData["pasteID"] = strconv.Itoa(pasteID)
|
||||||
htmlData["pasteTimestamp"] = timestampRaw
|
htmlData["pasteTimestamp"] = timestampRaw
|
||||||
|
|
||||||
verifyHTML := templater.GetTemplate(ectx, "passworded_paste_verify.html", htmlData)
|
verifyHTML := templater.GetTemplate(ec, "passworded_paste_verify.html", htmlData)
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusOK, verifyHTML)
|
||||||
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(ectx echo.Context) error {
|
func pastePasswordedVerifyPost(ec echo.Context) error {
|
||||||
// We should check if database connection available.
|
// We should check if database connection available.
|
||||||
dbConn := ctx.Database.GetDatabaseConnection()
|
dbConn := c.Database.GetDatabaseConnection()
|
||||||
|
|
||||||
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
|
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
||||||
//nolint:wrapcheck
|
return ec.Redirect(http.StatusFound, "/database_not_available")
|
||||||
return ectx.Redirect(http.StatusFound, "/database_not_available")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteIDRaw := ectx.Param("id")
|
pasteIDRaw := ec.Param("id")
|
||||||
timestampRaw := ectx.Param("timestamp")
|
timestampRaw := ec.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])
|
||||||
ctx.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste")
|
c.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste")
|
||||||
|
|
||||||
// Get paste.
|
// Get paste.
|
||||||
paste, err1 := ctx.Database.GetPaste(pasteID)
|
paste, err1 := c.Database.GetPaste(pasteID)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste")
|
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste")
|
||||||
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+strconv.Itoa(pasteID)+" not found")
|
errtpl := templater.GetErrorTemplate(ec, "Paste #"+strconv.Itoa(pasteID)+" not found")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||||
return ectx.HTML(http.StatusBadRequest, errtpl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
params, err2 := ectx.FormParams()
|
params, err2 := ec.FormParams()
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
ctx.Logger.Debug().Msg("No form parameters passed")
|
c.Logger.Debug().Msg("No form parameters passed")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+strconv.Itoa(pasteID)+" not found")
|
errtpl := templater.GetErrorTemplate(ec, "Paste #"+strconv.Itoa(pasteID)+" not found")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||||
return ectx.HTML(http.StatusBadRequest, errtpl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if paste.VerifyPassword(params["paste-password"][0]) {
|
if paste.VerifyPassword(params["paste-password"][0]) {
|
||||||
@ -278,79 +269,69 @@ func pastePasswordedVerifyPost(ectx 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)
|
||||||
ectx.SetCookie(cookie)
|
ec.SetCookie(cookie)
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.Redirect(http.StatusFound, "/paste/"+strconv.Itoa(pasteID)+"/"+timestampRaw)
|
||||||
return ectx.Redirect(http.StatusFound, "/paste/"+strconv.Itoa(pasteID)+"/"+timestampRaw)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ectx, "Invalid password. Please, try again.")
|
errtpl := templater.GetErrorTemplate(ec, "Invalid password. Please, try again.")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||||
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(ectx echo.Context) error {
|
func pasteRawGETWebInterface(ec echo.Context) error {
|
||||||
// We should check if database connection available.
|
// We should check if database connection available.
|
||||||
dbConn := ctx.Database.GetDatabaseConnection()
|
dbConn := c.Database.GetDatabaseConnection()
|
||||||
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
|
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
||||||
//nolint:wrapcheck
|
return ec.Redirect(http.StatusFound, "/database_not_available/raw")
|
||||||
return ectx.Redirect(http.StatusFound, "/database_not_available/raw")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteIDRaw := ectx.Param("id")
|
pasteIDRaw := ec.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])
|
||||||
ctx.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste data")
|
c.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste data")
|
||||||
|
|
||||||
// Get paste.
|
// Get paste.
|
||||||
paste, err1 := ctx.Database.GetPaste(pasteID)
|
paste, err1 := c.Database.GetPaste(pasteID)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste from database")
|
c.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() {
|
||||||
ctx.Logger.Error().Int("paste ID", pasteID).Msg("Paste is expired")
|
c.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 := ectx.Param("timestamp")
|
tsProvidedStr := ec.Param("timestamp")
|
||||||
|
|
||||||
tsProvided, err2 := strconv.ParseInt(tsProvidedStr, 10, 64)
|
tsProvided, err2 := strconv.ParseInt(tsProvidedStr, 10, 64)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
ctx.Logger.Error().Err(err2).Int("paste ID", pasteID).Str("provided timestamp", tsProvidedStr).Msg("Invalid timestamp provided for getting private paste")
|
c.Logger.Error().Err(err2).Int("paste ID", pasteID).Str("provided timestamp", tsProvidedStr).Msg("Invalid timestamp provided for getting private paste")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
|
||||||
return ectx.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteTS := paste.CreatedAt.Unix()
|
pasteTs := paste.CreatedAt.Unix()
|
||||||
if tsProvided != pasteTS {
|
if tsProvided != pasteTs {
|
||||||
ctx.Logger.Error().Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Int64("paste timestamp", pasteTS).Msg("Incorrect timestamp provided for private paste")
|
c.Logger.Error().Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Int64("paste timestamp", pasteTs).Msg("Incorrect timestamp provided for private paste")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
|
||||||
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 != "" {
|
||||||
ctx.Logger.Error().Int("paste ID", pasteID).Msg("Cannot render paste as raw: passworded paste. Patches welcome!")
|
c.Logger.Error().Int("paste ID", pasteID).Msg("Cannot render paste as raw: passworded paste. Patches welcome!")
|
||||||
return ectx.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
|
return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.String(http.StatusOK, paste.Data)
|
||||||
return ectx.String(http.StatusOK, paste.Data)
|
|
||||||
}
|
}
|
||||||
|
@ -1,75 +1,70 @@
|
|||||||
package pastes
|
package pastes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// stdlib
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alecthomas/chroma/v2/lexers"
|
// local
|
||||||
"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"
|
||||||
)
|
|
||||||
|
|
||||||
const KeepPastesForever = "forever"
|
// other
|
||||||
|
"github.com/alecthomas/chroma/lexers"
|
||||||
|
"github.com/labstack/echo"
|
||||||
|
)
|
||||||
|
|
||||||
// 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(ectx echo.Context) error {
|
func pastePOSTWebInterface(ec echo.Context) error {
|
||||||
// We should check if database connection available.
|
// We should check if database connection available.
|
||||||
dbConn := ctx.Database.GetDatabaseConnection()
|
dbConn := c.Database.GetDatabaseConnection()
|
||||||
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
|
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
||||||
//nolint:wrapcheck
|
return ec.Redirect(http.StatusFound, "/database_not_available")
|
||||||
return ectx.Redirect(http.StatusFound, "/database_not_available")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
params, err := ectx.FormParams()
|
params, err := ec.FormParams()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger.Error().Msg("Passed paste form is empty")
|
c.Logger.Error().Msg("Passed paste form is empty")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ectx, "Cannot create empty paste")
|
errtpl := templater.GetErrorTemplate(ec, "Cannot create empty paste")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||||
return ectx.HTML(http.StatusBadRequest, errtpl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Debug().Msgf("Received parameters: %+v", params)
|
c.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 {
|
||||||
ctx.Logger.Debug().Msg("Empty paste submitted, ignoring")
|
c.Logger.Debug().Msg("Empty paste submitted, ignoring")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ectx, "Empty pastes aren't allowed.")
|
errtpl := templater.GetErrorTemplate(ec, "Empty pastes aren't allowed.")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||||
return ectx.HTML(http.StatusBadRequest, errtpl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.ContainsAny(params["paste-keep-for"][0], "Mmhd") && params["paste-keep-for"][0] != KeepPastesForever {
|
if !strings.ContainsAny(params["paste-keep-for"][0], "Mmhd") && params["paste-keep-for"][0] != "forever" {
|
||||||
ctx.Logger.Debug().Str("field value", params["paste-keep-for"][0]).Msg("'Keep paste for' field have invalid value")
|
c.Logger.Debug().Str("field value", params["paste-keep-for"][0]).Msg("'Keep paste for' field have invalid value")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ectx, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).")
|
errtpl := templater.GetErrorTemplate(ec, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||||
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]) {
|
||||||
ctx.Logger.Debug().Str("captcha ID", params["paste-captcha-id"][0]).Str("captcha solution", params["paste-captcha-solution"][0]).Msg("Invalid captcha solution")
|
c.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(ectx, "Invalid captcha solution.")
|
errtpl := templater.GetErrorTemplate(ec, "Invalid captcha solution.")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||||
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],
|
||||||
@ -85,7 +80,7 @@ func pastePOSTWebInterface(ectx echo.Context) error {
|
|||||||
keepFor := 0
|
keepFor := 0
|
||||||
keepForUnit := 0
|
keepForUnit := 0
|
||||||
|
|
||||||
if params["paste-keep-for"][0] != KeepPastesForever {
|
if params["paste-keep-for"][0] != "forever" {
|
||||||
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]
|
||||||
@ -94,17 +89,16 @@ func pastePOSTWebInterface(ectx 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] == KeepPastesForever {
|
if params["paste-keep-for"][0] == "forever" {
|
||||||
ctx.Logger.Debug().Msg("Keeping paste forever!")
|
c.Logger.Debug().Msg("Keeping paste forever!")
|
||||||
|
|
||||||
keepFor = 0
|
keepFor = 0
|
||||||
} else {
|
} else {
|
||||||
ctx.Logger.Debug().Err(err).Msg("Failed to parse 'Keep for' integer")
|
c.Logger.Debug().Err(err).Msg("Failed to parse 'Keep for' integer")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ectx, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).")
|
errtpl := templater.GetErrorTemplate(ec, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||||
return ectx.HTML(http.StatusBadRequest, errtpl)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,25 +132,22 @@ func pastePOSTWebInterface(ectx echo.Context) error {
|
|||||||
_ = paste.CreatePassword(pastePassword[0])
|
_ = paste.CreatePassword(pastePassword[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteID, err2 := ctx.Database.SavePaste(paste)
|
id, err2 := c.Database.SavePaste(paste)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
ctx.Logger.Error().Err(err2).Msg("Failed to save paste")
|
c.Logger.Error().Err(err2).Msg("Failed to save paste")
|
||||||
|
|
||||||
errtpl := templater.GetErrorTemplate(ectx, "Failed to save paste. Please, try again later.")
|
errtpl := templater.GetErrorTemplate(ec, "Failed to save paste. Please, try again later.")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||||
return ectx.HTML(http.StatusBadRequest, errtpl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newPasteIDAsString := strconv.FormatInt(pasteID, 10)
|
newPasteIDAsString := strconv.FormatInt(id, 10)
|
||||||
ctx.Logger.Debug().Msg("Paste saved, URL: /paste/" + newPasteIDAsString)
|
c.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 {
|
||||||
//nolint:wrapcheck
|
return ec.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString+"/"+strconv.FormatInt(paste.CreatedAt.Unix(), 10))
|
||||||
return ectx.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString+"/"+strconv.FormatInt(paste.CreatedAt.Unix(), 10))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString)
|
||||||
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"
|
||||||
|
|
||||||
"github.com/labstack/echo"
|
// local
|
||||||
"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(ectx echo.Context) error {
|
func pastesGET(ec echo.Context) error {
|
||||||
// We should check if database connection available.
|
// We should check if database connection available.
|
||||||
dbConn := ctx.Database.GetDatabaseConnection()
|
dbConn := c.Database.GetDatabaseConnection()
|
||||||
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
|
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
||||||
//nolint:wrapcheck
|
return ec.Redirect(http.StatusFound, "/database_not_available")
|
||||||
return ectx.Redirect(http.StatusFound, "/database_not_available")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pageFromParamRaw := ectx.Param("page")
|
pageFromParamRaw := ec.Param("page")
|
||||||
|
|
||||||
page := 1
|
var 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Debug().Int("page", page).Msg("Requested page")
|
c.Logger.Debug().Int("page", page).Msg("Requested page")
|
||||||
|
|
||||||
// Get pastes IDs.
|
// Get pastes IDs.
|
||||||
pastes, err3 := ctx.Database.GetPagedPastes(page)
|
pastes, err3 := c.Database.GetPagedPastes(page)
|
||||||
ctx.Logger.Debug().Int("count", len(pastes)).Msg("Got pastes")
|
c.Logger.Debug().Int("count", len(pastes)).Msg("Got pastes")
|
||||||
|
|
||||||
pastesString := "No pastes to show."
|
var 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 {
|
||||||
ctx.Logger.Error().Err(err3).Msg("Failed to get pastes list from database")
|
c.Logger.Error().Err(err3).Msg("Failed to get pastes list from database")
|
||||||
|
|
||||||
noPastesToShowTpl := templater.GetErrorTemplate(ectx, "No pastes to show.")
|
noPastesToShowTpl := templater.GetErrorTemplate(ec, "No pastes to show.")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusOK, noPastesToShowTpl)
|
||||||
return ectx.HTML(http.StatusOK, noPastesToShowTpl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(pastes) > 0 {
|
if len(pastes) > 0 {
|
||||||
pastesString = ""
|
pastesString = ""
|
||||||
|
|
||||||
for _, paste := range pastes {
|
for i := range pastes {
|
||||||
pasteDataMap := make(map[string]string)
|
pasteDataMap := make(map[string]string)
|
||||||
pasteDataMap["pasteID"] = strconv.Itoa(paste.ID)
|
pasteDataMap["pasteID"] = strconv.Itoa(pastes[i].ID)
|
||||||
pasteDataMap["pasteTitle"] = paste.Title
|
pasteDataMap["pasteTitle"] = pastes[i].Title
|
||||||
pasteDataMap["pasteDate"] = paste.CreatedAt.Format("2006-01-02 @ 15:04:05") + " UTC"
|
pasteDataMap["pasteDate"] = pastes[i].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(paste.Data, "\n")
|
pasteDataSplitted := strings.Split(pastes[i].Data, "\n")
|
||||||
|
|
||||||
var pasteData string
|
var pasteData string
|
||||||
|
|
||||||
if len(pasteDataSplitted) < 4 {
|
if len(pasteDataSplitted) < 4 {
|
||||||
pasteData = paste.Data
|
pasteData = pastes[i].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(ectx, "pastelist_paste.html", pasteDataMap)
|
pasteTpl := templater.GetRawTemplate(ec, "pastelist_paste.html", pasteDataMap)
|
||||||
|
|
||||||
pastesString += pasteTpl
|
pastesString += pasteTpl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pagination.
|
// Pagination.
|
||||||
pages := ctx.Database.GetPastesPages()
|
pages := c.Database.GetPastesPages()
|
||||||
ctx.Logger.Debug().Int("total pages", pages).Int("current page", page).Msg("Paging data")
|
c.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(ectx, "pastelist_list.html", map[string]string{"pastes": pastesString, "pagination": paginationHTML})
|
pasteListTpl := templater.GetTemplate(ec, "pastelist_list.html", map[string]string{"pastes": pastesString, "pagination": paginationHTML})
|
||||||
|
|
||||||
//nolint:wrapcheck
|
return ec.HTML(http.StatusOK, pasteListTpl)
|
||||||
return ectx.HTML(http.StatusOK, pasteListTpl)
|
|
||||||
}
|
}
|
||||||
|
112
fileb0x.yml
Normal file
112
fileb0x.yml
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
# 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,23 +1,21 @@
|
|||||||
module go.dev.pztrn.name/fastpastebin
|
module go.dev.pztrn.name/fastpastebin
|
||||||
|
|
||||||
go 1.16
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/chroma/v2 v2.2.0
|
github.com/alecthomas/chroma v0.8.2
|
||||||
github.com/dchest/captcha v1.0.0
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
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.7.0 // indirect
|
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.6.0
|
github.com/go-sql-driver/mysql v1.5.0
|
||||||
github.com/jmoiron/sqlx v1.3.5
|
github.com/jmoiron/sqlx v1.2.0
|
||||||
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.1 // indirect
|
github.com/labstack/gommon v0.3.0 // indirect
|
||||||
github.com/lib/pq v1.10.6
|
github.com/lib/pq v1.9.0
|
||||||
github.com/pressly/goose v2.7.0+incompatible
|
github.com/pressly/goose v2.6.0+incompatible
|
||||||
github.com/rs/zerolog v1.27.0
|
github.com/rs/zerolog v1.20.0
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
go.dev.pztrn.name/flagger v0.0.0-20200617193309-89bc9818b76c
|
||||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||||
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
|
||||||
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,91 +1,107 @@
|
|||||||
github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY=
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae h1:zzGwJfFlFGD94CyyYwCJeSuD32Gj9GTaSi5y9hoVzdY=
|
github.com/alecthomas/chroma v0.8.2 h1:x3zkuE2lUk/RIekyAJ3XRqSCP4zwWDfcw/YJCuCAACg=
|
||||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
github.com/alecthomas/chroma v0.8.2/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||||
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 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||||
|
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 v1.0.0 h1:vw+bm/qMFvTgcjQlYVTuQBJkarm5R0YSsDKhm1HZI2o=
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
|
||||||
github.com/dchest/captcha v1.0.0/go.mod h1:7zoElIawLp7GUMLcj54K9kbw+jEyvz2K0FDdRRYhvWo=
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
|
||||||
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/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||||
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.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
|
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
||||||
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
|
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
|
||||||
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
|
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
|
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.7.0+incompatible h1:PWejVEv07LCerQEzMMeAtjuyCKbyprZ/LBa6K5P0OCQ=
|
github.com/pressly/goose v2.6.0+incompatible h1:3f8zIQ8rfgP9tyI0Hmcs2YNAqUCL1c+diLe3iU8Qd/k=
|
||||||
github.com/pressly/goose v2.7.0+incompatible/go.mod h1:m+QHWCqxR3k8D9l7qfzuC/djtlfzxr34mozWDYEu1z8=
|
github.com/pressly/goose v2.6.0+incompatible/go.mod h1:m+QHWCqxR3k8D9l7qfzuC/djtlfzxr34mozWDYEu1z8=
|
||||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
|
||||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
|
||||||
github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
|
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||||
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0 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.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
|
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
go.dev.pztrn.name/flagger v0.0.0-20200617193309-89bc9818b76c h1:+GgFefaTLsYDS0lXc8LNzTo6tcsA9qO3EkTAKduPAsI=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
go.dev.pztrn.name/flagger v0.0.0-20200617193309-89bc9818b76c/go.mod h1:ttPExQNCubgqqO5Y19LfIBKqmWtBocY7P9MXQEECuZo=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
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/sys v0.0.0-20210423082822-04245dca01da/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-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/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
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/check.v1 v1.0.0-20180628173108-788fd7840127/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-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,25 +25,28 @@
|
|||||||
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 (
|
||||||
ctx *context.Context
|
c *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) {
|
||||||
ctx = cc
|
c = cc
|
||||||
log = ctx.Logger.With().Str("type", "internal").Str("package", "captcha").Logger()
|
log = c.Logger.With().Str("type", "internal").Str("package", "captcha").Logger()
|
||||||
|
|
||||||
// New paste.
|
// New paste.
|
||||||
ctx.Echo.GET("/captcha/:id.png", echo.WrapHandler(captcha.Server(captcha.StdWidth, captcha.StdHeight)))
|
c.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"`
|
||||||
MaxBodySizeMegabytes string `yaml:"max_body_size_megabytes"`
|
|
||||||
AllowInsecure bool `yaml:"allow_insecure"`
|
AllowInsecure bool `yaml:"allow_insecure"`
|
||||||
|
MaxBodySizeMegabytes string `yaml:"max_body_size_megabytes"`
|
||||||
}
|
}
|
||||||
|
@ -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,14 +25,19 @@
|
|||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
// stdlib
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/labstack/echo"
|
// local
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/config"
|
"go.dev.pztrn.name/fastpastebin/internal/config"
|
||||||
databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface"
|
databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface"
|
||||||
|
|
||||||
|
// other
|
||||||
|
"github.com/labstack/echo"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"go.dev.pztrn.name/flagger"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,18 +46,26 @@ import (
|
|||||||
// contains everything every part of application need, like configuration
|
// contains everything every part of application need, like configuration
|
||||||
// access, logger, etc.
|
// access, logger, etc.
|
||||||
type Context struct {
|
type Context struct {
|
||||||
Config *config.Struct
|
Config *config.Struct
|
||||||
Database databaseinterface.Interface
|
Database databaseinterface.Interface
|
||||||
Echo *echo.Echo
|
Echo *echo.Echo
|
||||||
Logger zerolog.Logger
|
Flagger *flagger.Flagger
|
||||||
configPathFromCLI string
|
Logger zerolog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize initializes context.
|
// Initialize initializes context.
|
||||||
func (c *Context) Initialize() {
|
func (c *Context) Initialize() {
|
||||||
c.initializeLogger()
|
c.initializeLogger()
|
||||||
|
|
||||||
flag.StringVar(&c.configPathFromCLI, "config", "NO_CONFIG", "Configuration file path. Can be overridded with FASTPASTEBIN_CONFIG environment variable.")
|
c.Flagger = flagger.New("fastpastebin", nil)
|
||||||
|
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.
|
||||||
@ -67,18 +80,20 @@ 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 := c.configPathFromCLI
|
configPath := ""
|
||||||
|
|
||||||
// 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 {
|
|
||||||
configPath = configPathFromEnv
|
|
||||||
}
|
|
||||||
|
|
||||||
if configPath == "NO_CONFIG" {
|
if err != nil && configPathFromEnvFound || err == nil && configPathFromEnvFound {
|
||||||
|
configPath = configPathFromEnv
|
||||||
|
} else if err != nil && !configPathFromEnvFound || err == nil && configPathFromCLI == "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.
|
||||||
@ -89,11 +104,10 @@ 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 := os.ReadFile(normalizedConfigPath)
|
fileData, err2 := ioutil.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,11 +26,10 @@ package context
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Version .
|
// Version .
|
||||||
Version = "0.4.1"
|
Version = "0.4.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New creates new context.
|
// New creates new context.
|
||||||
func New() *Context {
|
func New() *Context {
|
||||||
//nolint:exhaustruct
|
|
||||||
return &Context{}
|
return &Context{}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
// local
|
||||||
|
"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() {
|
||||||
@ -18,7 +19,7 @@ func (c *Context) initializeHTTPServer() {
|
|||||||
c.Echo.HidePort = true
|
c.Echo.HidePort = true
|
||||||
|
|
||||||
// Static files.
|
// Static files.
|
||||||
c.Echo.GET("/static/*", echo.WrapHandler(http.FileServer(http.FS(assets.Data))))
|
c.Echo.GET("/static/*", echo.WrapHandler(static.Handler))
|
||||||
|
|
||||||
listenAddress := c.Config.HTTP.Address + ":" + c.Config.HTTP.Port
|
listenAddress := c.Config.HTTP.Address + ":" + c.Config.HTTP.Port
|
||||||
|
|
||||||
@ -31,16 +32,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(ectx echo.Context) error {
|
return func(ec echo.Context) error {
|
||||||
c.Logger.Info().
|
c.Logger.Info().
|
||||||
Str("IP", ectx.RealIP()).
|
Str("IP", ec.RealIP()).
|
||||||
Str("Host", ectx.Request().Host).
|
Str("Host", ec.Request().Host).
|
||||||
Str("Method", ectx.Request().Method).
|
Str("Method", ec.Request().Method).
|
||||||
Str("Path", ectx.Request().URL.Path).
|
Str("Path", ec.Request().URL.Path).
|
||||||
Str("UA", ectx.Request().UserAgent()).
|
Str("UA", ec.Request().UserAgent()).
|
||||||
Msg("HTTP request")
|
Msg("HTTP request")
|
||||||
|
|
||||||
return next(ectx)
|
return next(ec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,55 +1,56 @@
|
|||||||
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(event *zerolog.Event, level zerolog.Level, message string) {
|
func (c *Context) getMemoryUsage(e *zerolog.Event, level zerolog.Level, message string) {
|
||||||
var memstats runtime.MemStats
|
var m runtime.MemStats
|
||||||
|
|
||||||
runtime.ReadMemStats(&memstats)
|
runtime.ReadMemStats(&m)
|
||||||
|
|
||||||
event.Str("memalloc", fmt.Sprintf("%dMB", memstats.Alloc/1024/1024))
|
e.Str("memalloc", fmt.Sprintf("%dMB", m.Alloc/1024/1024))
|
||||||
event.Str("memsys", fmt.Sprintf("%dMB", memstats.Sys/1024/1024))
|
e.Str("memsys", fmt.Sprintf("%dMB", m.Sys/1024/1024))
|
||||||
event.Str("numgc", fmt.Sprintf("%d", memstats.NumGC))
|
e.Str("numgc", fmt.Sprintf("%d", m.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(lvlRaw interface{}) string {
|
output.FormatLevel = func(i interface{}) string {
|
||||||
var lvl string
|
var v string
|
||||||
|
|
||||||
if lvlAsString, ok := lvlRaw.(string); ok {
|
if ii, ok := i.(string); ok {
|
||||||
lvlAsString = strings.ToUpper(lvlAsString)
|
ii = strings.ToUpper(ii)
|
||||||
switch lvlAsString {
|
switch ii {
|
||||||
case "DEBUG":
|
case "DEBUG":
|
||||||
lvl = fmt.Sprintf("\x1b[30m%-5s\x1b[0m", lvlAsString)
|
v = fmt.Sprintf("\x1b[30m%-5s\x1b[0m", ii)
|
||||||
case "ERROR":
|
case "ERROR":
|
||||||
lvl = fmt.Sprintf("\x1b[31m%-5s\x1b[0m", lvlAsString)
|
v = fmt.Sprintf("\x1b[31m%-5s\x1b[0m", ii)
|
||||||
case "FATAL":
|
case "FATAL":
|
||||||
lvl = fmt.Sprintf("\x1b[35m%-5s\x1b[0m", lvlAsString)
|
v = fmt.Sprintf("\x1b[35m%-5s\x1b[0m", ii)
|
||||||
case "INFO":
|
case "INFO":
|
||||||
lvl = fmt.Sprintf("\x1b[32m%-5s\x1b[0m", lvlAsString)
|
v = fmt.Sprintf("\x1b[32m%-5s\x1b[0m", ii)
|
||||||
case "PANIC":
|
case "PANIC":
|
||||||
lvl = fmt.Sprintf("\x1b[36m%-5s\x1b[0m", lvlAsString)
|
v = fmt.Sprintf("\x1b[36m%-5s\x1b[0m", ii)
|
||||||
case "WARN":
|
case "WARN":
|
||||||
lvl = fmt.Sprintf("\x1b[33m%-5s\x1b[0m", lvlAsString)
|
v = fmt.Sprintf("\x1b[33m%-5s\x1b[0m", ii)
|
||||||
default:
|
default:
|
||||||
lvl = lvlAsString
|
v = ii
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("| %s |", lvl)
|
return fmt.Sprintf("| %s |", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Logger = zerolog.New(output).With().Timestamp().Logger()
|
c.Logger = zerolog.New(output).With().Timestamp().Logger()
|
||||||
|
@ -25,16 +25,19 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// stdlib
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
// MySQL driver.
|
// local
|
||||||
_ "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.
|
||||||
@ -46,7 +49,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 {
|
||||||
ctx.Logger.Info().Msg("Starting pastes cleanup procedure...")
|
c.Logger.Info().Msg("Starting pastes cleanup procedure...")
|
||||||
|
|
||||||
pages := db.db.GetPastesPages()
|
pages := db.db.GetPastesPages()
|
||||||
|
|
||||||
@ -55,7 +58,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 {
|
||||||
ctx.Logger.Error().Err(err).Int("page", i).Msg("Failed to perform database cleanup")
|
c.Logger.Error().Err(err).Int("page", i).Msg("Failed to perform database cleanup")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, paste := range pastes {
|
for _, paste := range pastes {
|
||||||
@ -68,18 +71,17 @@ 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 {
|
||||||
ctx.Logger.Error().Err(err).Int("paste", pasteID).Msg("Failed to delete paste!")
|
c.Logger.Error().Err(err).Int("paste", pasteID).Msg("Failed to delete paste!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Info().Msg("Pastes cleanup done.")
|
c.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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,12 +94,10 @@ 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() {
|
||||||
ctx.Logger.Info().Msg("Initializing database connection...")
|
c.Logger.Info().Msg("Initializing database connection...")
|
||||||
|
|
||||||
if ctx.Config.Database.Type == "mysql" {
|
if c.Config.Database.Type == "mysql" {
|
||||||
mysql.New(ctx)
|
mysql.New(c)
|
||||||
} else if ctx.Config.Database.Type == flatfiles.FlatFileDialect {
|
} else if c.Config.Database.Type == "flatfiles" {
|
||||||
flatfiles.New(ctx)
|
flatfiles.New(c)
|
||||||
} else if ctx.Config.Database.Type == "postgresql" {
|
} else if c.Config.Database.Type == "postgresql" {
|
||||||
postgresql.New(ctx)
|
postgresql.New(c)
|
||||||
} else {
|
} else {
|
||||||
ctx.Logger.Fatal().Str("type", ctx.Config.Database.Type).Msg("Unknown database type")
|
c.Logger.Fatal().Str("type", c.Config.Database.Type).Msg("Unknown database type")
|
||||||
}
|
}
|
||||||
|
|
||||||
go db.cleanup()
|
go db.cleanup()
|
||||||
@ -128,7 +128,6 @@ 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,21 +25,19 @@
|
|||||||
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 (
|
||||||
ctx *context.Context
|
c *context.Context
|
||||||
flf *FlatFiles
|
f *FlatFiles
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
ctx = cc
|
c = cc
|
||||||
//nolint:exhaustruct
|
f = &FlatFiles{}
|
||||||
flf = &FlatFiles{}
|
|
||||||
|
|
||||||
ctx.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,10 @@
|
|||||||
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"
|
||||||
@ -34,13 +36,14 @@ 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.
|
||||||
@ -48,9 +51,8 @@ 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 {
|
||||||
ctx.Logger.Error().Err(err).Msg("Failed to delete paste!")
|
c.Logger.Error().Err(err).Msg("Failed to delete paste!")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,27 +84,22 @@ 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")
|
||||||
ctx.Logger.Debug().Str("path", pastePath).Msg("Trying to load paste data")
|
c.Logger.Debug().Str("path", pastePath).Msg("Trying to load paste data")
|
||||||
|
|
||||||
pasteInBytes, err := os.ReadFile(pastePath)
|
pasteInBytes, err := ioutil.ReadFile(pastePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger.Debug().Err(err).Msg("Failed to read paste from storage")
|
c.Logger.Debug().Err(err).Msg("Failed to read paste from storage")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Debug().Int("paste bytes", len(pasteInBytes)).Msg("Loaded paste")
|
c.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 {
|
||||||
ctx.Logger.Error().Err(err1).Msgf("Failed to parse paste")
|
c.Logger.Error().Err(err1).Msgf("Failed to parse paste")
|
||||||
|
|
||||||
//nolint:wrapcheck
|
|
||||||
return nil, err1
|
return nil, err1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +110,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) * ctx.Config.Pastes.Pagination
|
startPagination = (page - 1) * c.Config.Pastes.Pagination
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iteration one - get only public pastes.
|
// Iteration one - get only public pastes.
|
||||||
@ -129,39 +126,34 @@ 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) == ctx.Config.Pastes.Pagination {
|
if len(pastesData) == c.Config.Pastes.Pagination {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if idx < startPagination {
|
if idx < startPagination {
|
||||||
ctx.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too low index")
|
c.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)*ctx.Config.Pastes.Pagination)) || (idx-1 >= startPagination && page == 1 && idx > startPagination+(page*ctx.Config.Pastes.Pagination)) {
|
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)) {
|
||||||
ctx.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too high index")
|
c.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too high index")
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Debug().Int("ID", paste.ID).Int("index", idx).Msg("Getting paste data")
|
c.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 := os.ReadFile(filepath.Join(ff.path, "pastes", strconv.Itoa(paste.ID)+".json"))
|
pasteRawData, err := ioutil.ReadFile(filepath.Join(ff.path, "pastes", strconv.Itoa(paste.ID)+".json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger.Error().Err(err).Msg("Failed to read paste data")
|
c.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 {
|
||||||
ctx.Logger.Error().Err(err1).Msg("Failed to parse paste data")
|
c.Logger.Error().Err(err1).Msg("Failed to parse paste data")
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,9 +176,9 @@ func (ff *FlatFiles) GetPastesPages() int {
|
|||||||
ff.writeMutex.Unlock()
|
ff.writeMutex.Unlock()
|
||||||
|
|
||||||
// Calculate pages.
|
// Calculate pages.
|
||||||
pages := len(publicPastes) / ctx.Config.Pastes.Pagination
|
pages := len(publicPastes) / c.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)%ctx.Config.Pastes.Pagination > 0 {
|
if len(publicPastes)%c.Config.Pastes.Pagination > 0 {
|
||||||
pages++
|
pages++
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,14 +186,14 @@ func (ff *FlatFiles) GetPastesPages() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ff *FlatFiles) Initialize() {
|
func (ff *FlatFiles) Initialize() {
|
||||||
ctx.Logger.Info().Msg("Initializing flatfiles storage...")
|
c.Logger.Info().Msg("Initializing flatfiles storage...")
|
||||||
|
|
||||||
path := ctx.Config.Database.Path
|
path := c.Config.Database.Path
|
||||||
// Get proper paste file path.
|
// Get proper paste file path.
|
||||||
if strings.Contains(ctx.Config.Database.Path, "~") {
|
if strings.Contains(c.Config.Database.Path, "~") {
|
||||||
curUser, err := user.Current()
|
curUser, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger.Error().Msg("Failed to get current user. Will replace '~' for '/' in storage path!")
|
c.Logger.Error().Msg("Failed to get current user. Will replace '~' for '/' in storage path!")
|
||||||
|
|
||||||
path = strings.Replace(path, "~", "/", -1)
|
path = strings.Replace(path, "~", "/", -1)
|
||||||
}
|
}
|
||||||
@ -212,73 +204,68 @@ func (ff *FlatFiles) Initialize() {
|
|||||||
path, _ = filepath.Abs(path)
|
path, _ = filepath.Abs(path)
|
||||||
ff.path = path
|
ff.path = path
|
||||||
|
|
||||||
ctx.Logger.Debug().Msgf("Storage path is now: %s", ff.path)
|
c.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 {
|
||||||
ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...")
|
c.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 {
|
||||||
ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory already exists")
|
c.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 {
|
||||||
ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...")
|
c.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 {
|
||||||
ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory already exists")
|
c.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 {
|
||||||
ctx.Logger.Warn().Msg("Pastes index file does not exist, will create new one")
|
c.Logger.Warn().Msg("Pastes index file does not exist, will create new one")
|
||||||
} else {
|
} else {
|
||||||
indexData, err := os.ReadFile(filepath.Join(ff.path, "pastes", "index.json"))
|
indexData, err := ioutil.ReadFile(filepath.Join(ff.path, "pastes", "index.json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger.Fatal().Msg("Failed to read contents of index file!")
|
c.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 {
|
||||||
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.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.Debug().Int("pastes count", len(ff.pastesIndex)).Msg("Parsed pastes index")
|
c.Logger.Debug().Int("pastes count", len(ff.pastesIndex)).Msg("Parsed pastes index")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ff *FlatFiles) SavePaste(paste *structs.Paste) (int64, error) {
|
func (ff *FlatFiles) SavePaste(p *structs.Paste) (int64, error) {
|
||||||
ff.writeMutex.Lock()
|
ff.writeMutex.Lock()
|
||||||
// Write paste data on disk.
|
// Write paste data on disk.
|
||||||
filesOnDisk, _ := os.ReadDir(filepath.Join(ff.path, "pastes"))
|
filesOnDisk, _ := ioutil.ReadDir(filepath.Join(ff.path, "pastes"))
|
||||||
pasteID := len(filesOnDisk) + 1
|
pasteID := len(filesOnDisk) + 1
|
||||||
paste.ID = pasteID
|
p.ID = pasteID
|
||||||
|
|
||||||
ctx.Logger.Debug().Int("new paste ID", pasteID).Msg("Writing paste to disk")
|
c.Logger.Debug().Int("new paste ID", pasteID).Msg("Writing paste to disk")
|
||||||
|
|
||||||
data, err := json.Marshal(paste)
|
data, err := json.Marshal(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ff.writeMutex.Unlock()
|
ff.writeMutex.Unlock()
|
||||||
|
|
||||||
//nolint:wrapcheck
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"), data, 0o600)
|
err = ioutil.WriteFile(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"), data, 0644)
|
||||||
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 = paste.Private
|
indexData.Private = p.Private
|
||||||
ff.pastesIndex = append(ff.pastesIndex, indexData)
|
ff.pastesIndex = append(ff.pastesIndex, indexData)
|
||||||
ff.writeMutex.Unlock()
|
ff.writeMutex.Unlock()
|
||||||
|
|
||||||
@ -286,19 +273,17 @@ func (ff *FlatFiles) SavePaste(paste *structs.Paste) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ff *FlatFiles) Shutdown() {
|
func (ff *FlatFiles) Shutdown() {
|
||||||
ctx.Logger.Info().Msg("Saving indexes...")
|
c.Logger.Info().Msg("Saving indexes...")
|
||||||
|
|
||||||
indexData, err := json.Marshal(ff.pastesIndex)
|
indexData, err := json.Marshal(ff.pastesIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger.Error().Err(err).Msg("Failed to encode index data into JSON")
|
c.Logger.Error().Err(err).Msg("Failed to encode index data into JSON")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err1 := os.WriteFile(filepath.Join(ff.path, "pastes", "index.json"), indexData, 0o600)
|
err1 := ioutil.WriteFile(filepath.Join(ff.path, "pastes", "index.json"), indexData, 0644)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
ctx.Logger.Error().Err(err1).Msg("Failed to write index data to file. Pretty sure that you've lost your pastes.")
|
c.Logger.Error().Err(err1).Msg("Failed to write index data to file. Pretty sure that you've lost your pastes.")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,41 +25,43 @@
|
|||||||
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 flf.DeletePaste(pasteID)
|
return f.DeletePaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
||||||
return flf.GetDatabaseConnection()
|
return f.GetDatabaseConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||||
return flf.GetPaste(pasteID)
|
return f.GetPaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
return flf.GetPagedPastes(page)
|
return f.GetPagedPastes(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPastesPages() int {
|
func (dbh Handler) GetPastesPages() int {
|
||||||
return flf.GetPastesPages()
|
return f.GetPastesPages()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) Initialize() {
|
func (dbh Handler) Initialize() {
|
||||||
flf.Initialize()
|
f.Initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
||||||
return flf.SavePaste(p)
|
return f.SavePaste(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) Shutdown() {
|
func (dbh Handler) Shutdown() {
|
||||||
flf.Shutdown()
|
f.Shutdown()
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,10 @@
|
|||||||
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 (
|
||||||
ctx *context.Context
|
c *context.Context
|
||||||
dbAdapter *Database
|
d *Database
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
ctx = cc
|
c = cc
|
||||||
//nolint:exhaustruct
|
d = &Database{}
|
||||||
dbAdapter = &Database{}
|
|
||||||
|
|
||||||
ctx.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
||||||
}
|
}
|
||||||
|
@ -25,41 +25,43 @@
|
|||||||
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 dbAdapter.DeletePaste(pasteID)
|
return d.DeletePaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
||||||
return dbAdapter.GetDatabaseConnection()
|
return d.GetDatabaseConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||||
return dbAdapter.GetPaste(pasteID)
|
return d.GetPaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
return dbAdapter.GetPagedPastes(page)
|
return d.GetPagedPastes(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPastesPages() int {
|
func (dbh Handler) GetPastesPages() int {
|
||||||
return dbAdapter.GetPastesPages()
|
return d.GetPastesPages()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) Initialize() {
|
func (dbh Handler) Initialize() {
|
||||||
dbAdapter.Initialize()
|
d.Initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
||||||
return dbAdapter.SavePaste(p)
|
return d.SavePaste(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) Shutdown() {
|
func (dbh Handler) Shutdown() {
|
||||||
dbAdapter.Shutdown()
|
d.Shutdown()
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// stdlib
|
||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,7 +39,6 @@ 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,7 +41,6 @@ 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,7 +41,6 @@ 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,35 +25,32 @@
|
|||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// stdlib
|
||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PasswordedPastesUp(txn *sql.Tx) error {
|
func PasswordedPastesUp(tx *sql.Tx) error {
|
||||||
_, err := txn.Exec("ALTER TABLE `pastes` ADD `password` varchar(64) NOT NULL DEFAULT '' COMMENT 'Password for paste (scrypted and sha256ed).'")
|
_, err := tx.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 := txn.Exec("ALTER TABLE `pastes` ADD `password_salt` varchar(64) NOT NULL DEFAULT '' COMMENT 'Password salt (sha256ed).'")
|
_, err1 := tx.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(txn *sql.Tx) error {
|
func PasswordedPastesDown(tx *sql.Tx) error {
|
||||||
_, err := txn.Exec("ALTER TABLE `pastes` DROP COLUMN `password`")
|
_, err := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `password`")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//nolint:wrapcheck
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err1 := txn.Exec("ALTER TABLE `pastes` DROP COLUMN `password_salt`")
|
_, err1 := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `password_salt`")
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
//nolint:wrapcheck
|
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,20 +25,26 @@
|
|||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pressly/goose"
|
// local
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
|
|
||||||
|
// other
|
||||||
|
//"gitlab.com/jmoiron/sqlx"
|
||||||
|
"github.com/pressly/goose"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ctx *context.Context
|
var (
|
||||||
|
c *context.Context
|
||||||
|
)
|
||||||
|
|
||||||
// New initializes migrations.
|
// New initializes migrations.
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
ctx = cc
|
c = cc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate launching migrations.
|
// Migrate launching migrations.
|
||||||
func Migrate() {
|
func Migrate() {
|
||||||
ctx.Logger.Info().Msg("Migrating database...")
|
c.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)
|
||||||
@ -47,13 +53,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 := ctx.Database.GetDatabaseConnection()
|
dbConn := c.Database.GetDatabaseConnection()
|
||||||
if dbConn != nil {
|
if dbConn != nil {
|
||||||
err := goose.Up(dbConn, ".")
|
err := goose.Up(dbConn, ".")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
|
c.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
|
c.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,14 +25,17 @@
|
|||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// stdlib
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
// MySQL driver.
|
// local
|
||||||
_ "github.com/go-sql-driver/mysql"
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/mysql/migrations"
|
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/mysql/migrations"
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||||
|
|
||||||
|
// other
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Database is a MySQL/MariaDB connection controlling structure.
|
// Database is a MySQL/MariaDB connection controlling structure.
|
||||||
@ -59,7 +62,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,16 +82,14 @@ 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()
|
||||||
|
|
||||||
//nolint:exhaustruct
|
p := &structs.Paste{}
|
||||||
paste := &structs.Paste{}
|
|
||||||
|
|
||||||
err := db.db.Get(paste, db.db.Rebind("SELECT * FROM `pastes` WHERE id=?"), pasteID)
|
err := db.db.Get(p, db.db.Rebind("SELECT * FROM `pastes` WHERE id=?"), pasteID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//nolint:wrapcheck
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return paste, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
@ -103,12 +103,11 @@ func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
|||||||
// Pagination.
|
// Pagination.
|
||||||
startPagination := 0
|
startPagination := 0
|
||||||
if page > 1 {
|
if page > 1 {
|
||||||
startPagination = (page - 1) * ctx.Config.Pastes.Pagination
|
startPagination = (page - 1) * c.Config.Pastes.Pagination
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//nolint:wrapcheck
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,9 +141,9 @@ func (db *Database) GetPastesPages() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate pages.
|
// Calculate pages.
|
||||||
pages := len(pastes) / ctx.Config.Pastes.Pagination
|
pages := len(pastes) / c.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)%ctx.Config.Pastes.Pagination > 0 {
|
if len(pastes)%c.Config.Pastes.Pagination > 0 {
|
||||||
pages++
|
pages++
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,36 +152,35 @@ func (db *Database) GetPastesPages() int {
|
|||||||
|
|
||||||
// Initialize initializes MySQL/MariaDB connection.
|
// Initialize initializes MySQL/MariaDB connection.
|
||||||
func (db *Database) Initialize() {
|
func (db *Database) Initialize() {
|
||||||
ctx.Logger.Info().Msg("Initializing database connection...")
|
c.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 ctx.Config.Database.Password == "" {
|
if c.Config.Database.Password == "" {
|
||||||
userpass = ctx.Config.Database.Username
|
userpass = c.Config.Database.Username
|
||||||
} else {
|
} else {
|
||||||
userpass = ctx.Config.Database.Username + ":" + ctx.Config.Database.Password
|
userpass = c.Config.Database.Username + ":" + c.Config.Database.Password
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
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)
|
||||||
ctx.Logger.Debug().Str("DSN", dbConnString).Msgf("Database connection string composed")
|
c.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 {
|
||||||
ctx.Logger.Error().Err(err).Msg("Failed to connect to database")
|
c.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';")
|
||||||
|
|
||||||
ctx.Logger.Info().Msg("Database connection established")
|
c.Logger.Info().Msg("Database connection established")
|
||||||
|
|
||||||
db.db = dbConn
|
db.db = dbConn
|
||||||
|
|
||||||
// Perform migrations.
|
// Perform migrations.
|
||||||
migrations.New(ctx)
|
migrations.New(c)
|
||||||
migrations.Migrate()
|
migrations.Migrate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,24 +189,22 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
lastInsertID, err1 := result.LastInsertId()
|
ID, err1 := result.LastInsertId()
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
//nolint:wrapcheck
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return lastInsertID, nil
|
return ID, 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 {
|
||||||
ctx.Logger.Error().Err(err).Msg("Failed to close database connection")
|
c.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 (
|
||||||
ctx *context.Context
|
c *context.Context
|
||||||
dbAdapter *Database
|
d *Database
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
ctx = cc
|
c = cc
|
||||||
//nolint:exhaustruct
|
d = &Database{}
|
||||||
dbAdapter = &Database{}
|
|
||||||
|
|
||||||
ctx.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
||||||
}
|
}
|
||||||
|
@ -25,41 +25,43 @@
|
|||||||
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 dbAdapter.DeletePaste(pasteID)
|
return d.DeletePaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
||||||
return dbAdapter.GetDatabaseConnection()
|
return d.GetDatabaseConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||||
return dbAdapter.GetPaste(pasteID)
|
return d.GetPaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
return dbAdapter.GetPagedPastes(page)
|
return d.GetPagedPastes(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPastesPages() int {
|
func (dbh Handler) GetPastesPages() int {
|
||||||
return dbAdapter.GetPastesPages()
|
return d.GetPastesPages()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) Initialize() {
|
func (dbh Handler) Initialize() {
|
||||||
dbAdapter.Initialize()
|
d.Initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
||||||
return dbAdapter.SavePaste(p)
|
return d.SavePaste(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) Shutdown() {
|
func (dbh Handler) Shutdown() {
|
||||||
dbAdapter.Shutdown()
|
d.Shutdown()
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// stdlib
|
||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -48,7 +49,6 @@ 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,7 +41,6 @@ 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,7 +41,6 @@ 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,35 +25,32 @@
|
|||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// stdlib
|
||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PasswordedPastesUp(txn *sql.Tx) error {
|
func PasswordedPastesUp(tx *sql.Tx) error {
|
||||||
_, 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).';")
|
_, 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).';")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//nolint:wrapcheck
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, 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).';")
|
_, 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).';")
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
//nolint:wrapcheck
|
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func PasswordedPastesDown(txn *sql.Tx) error {
|
func PasswordedPastesDown(tx *sql.Tx) error {
|
||||||
_, err := txn.Exec("ALTER TABLE pastes DROP COLUMN password")
|
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN password")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//nolint:wrapcheck
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err1 := txn.Exec("ALTER TABLE pastes DROP COLUMN password_salt")
|
_, err1 := tx.Exec("ALTER TABLE pastes DROP COLUMN password_salt")
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
//nolint:wrapcheck
|
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,20 +25,26 @@
|
|||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pressly/goose"
|
// local
|
||||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||||
|
|
||||||
|
// other
|
||||||
|
//"gitlab.com/jmoiron/sqlx"
|
||||||
|
"github.com/pressly/goose"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ctx *context.Context
|
var (
|
||||||
|
c *context.Context
|
||||||
|
)
|
||||||
|
|
||||||
// New initializes migrations.
|
// New initializes migrations.
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
ctx = cc
|
c = cc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate launching migrations.
|
// Migrate launching migrations.
|
||||||
func Migrate() {
|
func Migrate() {
|
||||||
ctx.Logger.Info().Msg("Migrating database...")
|
c.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)
|
||||||
@ -47,14 +53,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 := ctx.Database.GetDatabaseConnection()
|
dbConn := c.Database.GetDatabaseConnection()
|
||||||
if dbConn != nil {
|
if dbConn != nil {
|
||||||
err := goose.Up(dbConn, ".")
|
err := goose.Up(dbConn, ".")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger.Info().Msgf("%+v", err)
|
c.Logger.Info().Msgf("%+v", err)
|
||||||
ctx.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
|
c.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
|
c.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,19 +24,20 @@
|
|||||||
|
|
||||||
package postgresql
|
package postgresql
|
||||||
|
|
||||||
//nolint:gci
|
|
||||||
import (
|
import (
|
||||||
|
// stdlib
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
// PostgreSQL driver.
|
// local
|
||||||
_ "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.
|
||||||
@ -63,7 +64,6 @@ 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,12 +84,10 @@ 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()
|
||||||
|
|
||||||
//nolint:exhaustruct
|
p := &structs.Paste{}
|
||||||
paste := &structs.Paste{}
|
|
||||||
|
|
||||||
err := db.db.Get(paste, db.db.Rebind("SELECT * FROM pastes WHERE id=$1"), pasteID)
|
err := db.db.Get(p, db.db.Rebind("SELECT * FROM pastes WHERE id=$1"), pasteID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//nolint:wrapcheck
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,10 +95,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 := paste.CreatedAt.In(loc)
|
utcCreatedAt := p.CreatedAt.In(loc)
|
||||||
paste.CreatedAt = &utcCreatedAt
|
p.CreatedAt = &utcCreatedAt
|
||||||
|
|
||||||
return paste, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
@ -114,12 +112,11 @@ func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
|||||||
// Pagination.
|
// Pagination.
|
||||||
startPagination := 0
|
startPagination := 0
|
||||||
if page > 1 {
|
if page > 1 {
|
||||||
startPagination = (page - 1) * ctx.Config.Pastes.Pagination
|
startPagination = (page - 1) * c.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"), ctx.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"), c.Config.Pastes.Pagination, startPagination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//nolint:wrapcheck
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,9 +156,9 @@ func (db *Database) GetPastesPages() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate pages.
|
// Calculate pages.
|
||||||
pages := len(pastes) / ctx.Config.Pastes.Pagination
|
pages := len(pastes) / c.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)%ctx.Config.Pastes.Pagination > 0 {
|
if len(pastes)%c.Config.Pastes.Pagination > 0 {
|
||||||
pages++
|
pages++
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,59 +167,56 @@ func (db *Database) GetPastesPages() int {
|
|||||||
|
|
||||||
// Initialize initializes MySQL/MariaDB connection.
|
// Initialize initializes MySQL/MariaDB connection.
|
||||||
func (db *Database) Initialize() {
|
func (db *Database) Initialize() {
|
||||||
ctx.Logger.Info().Msg("Initializing database connection...")
|
c.Logger.Info().Msg("Initializing database connection...")
|
||||||
|
|
||||||
var userpass string
|
var userpass string
|
||||||
if ctx.Config.Database.Password == "" {
|
if c.Config.Database.Password == "" {
|
||||||
userpass = ctx.Config.Database.Username
|
userpass = c.Config.Database.Username
|
||||||
} else {
|
} else {
|
||||||
userpass = ctx.Config.Database.Username + ":" + ctx.Config.Database.Password
|
userpass = c.Config.Database.Username + ":" + c.Config.Database.Password
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
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)
|
||||||
ctx.Logger.Debug().Str("DSN", dbConnString).Msg("Database connection string composed")
|
c.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 {
|
||||||
ctx.Logger.Error().Err(err).Msg("Failed to connect to database")
|
c.Logger.Error().Err(err).Msg("Failed to connect to database")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Info().Msg("Database connection established")
|
c.Logger.Info().Msg("Database connection established")
|
||||||
|
|
||||||
db.db = dbConn
|
db.db = dbConn
|
||||||
|
|
||||||
// Perform migrations.
|
// Perform migrations.
|
||||||
migrations.New(ctx)
|
migrations.New(c)
|
||||||
migrations.Migrate()
|
migrations.Migrate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) SavePaste(paste *structs.Paste) (int64, error) {
|
func (db *Database) SavePaste(p *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 newPasteID int64
|
var id int64
|
||||||
|
|
||||||
err = stmt.Get(&newPasteID, paste)
|
err = stmt.Get(&id, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//nolint:wrapcheck
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return newPasteID, nil
|
return id, 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 {
|
||||||
ctx.Logger.Error().Err(err).Msg("Failed to close database connection")
|
c.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 (
|
||||||
ctx *context.Context
|
c *context.Context
|
||||||
dbAdapter *Database
|
d *Database
|
||||||
)
|
)
|
||||||
|
|
||||||
// New initializes database structure.
|
// New initializes database structure.
|
||||||
func New(cc *context.Context) {
|
func New(cc *context.Context) {
|
||||||
ctx = cc
|
c = cc
|
||||||
//nolint:exhaustruct
|
d = &Database{}
|
||||||
dbAdapter = &Database{}
|
|
||||||
|
|
||||||
ctx.RegisterDatabaseInterface(databaseinterface.Interface(Handler{}))
|
c.RegisterDatabaseInterface(databaseinterface.Interface(Handler{}))
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,11 @@
|
|||||||
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"
|
||||||
)
|
)
|
||||||
@ -36,38 +39,38 @@ import (
|
|||||||
type Handler struct{}
|
type Handler struct{}
|
||||||
|
|
||||||
func (dbh Handler) DeletePaste(pasteID int) error {
|
func (dbh Handler) DeletePaste(pasteID int) error {
|
||||||
return dbAdapter.DeletePaste(pasteID)
|
return d.DeletePaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
||||||
return dbAdapter.GetDatabaseConnection()
|
return d.GetDatabaseConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||||
return dbAdapter.GetPaste(pasteID)
|
return d.GetPaste(pasteID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||||
return dbAdapter.GetPagedPastes(page)
|
return d.GetPagedPastes(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) GetPastesPages() int {
|
func (dbh Handler) GetPastesPages() int {
|
||||||
return dbAdapter.GetPastesPages()
|
return d.GetPastesPages()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize initializes connection to database.
|
// Initialize initializes connection to database.
|
||||||
func (dbh Handler) Initialize() {
|
func (dbh Handler) Initialize() {
|
||||||
dbAdapter.Initialize()
|
d.Initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) RegisterDialect(di dialectinterface.Interface) {
|
func (dbh Handler) RegisterDialect(di dialectinterface.Interface) {
|
||||||
dbAdapter.RegisterDialect(di)
|
d.RegisterDialect(di)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
||||||
return dbAdapter.SavePaste(p)
|
return d.SavePaste(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbh Handler) Shutdown() {
|
func (dbh Handler) Shutdown() {
|
||||||
dbAdapter.Shutdown()
|
d.Shutdown()
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,10 @@
|
|||||||
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,31 +1,33 @@
|
|||||||
package pagination
|
package pagination
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
// stdlib
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.dev.pztrn.name/fastpastebin/assets"
|
// local
|
||||||
|
"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 := assets.Data.ReadFile("pagination.html")
|
paginationHTMLRaw, err := static.ReadFile("pagination.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "Missing pagination.html"
|
return "Missing pagination.html"
|
||||||
}
|
}
|
||||||
|
|
||||||
paginationLinkRaw, err1 := assets.Data.ReadFile("pagination_link.html")
|
paginationLinkRaw, err1 := static.ReadFile("pagination_link.html")
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
return "Missing pagination_link.html"
|
return "Missing pagination_link.html"
|
||||||
}
|
}
|
||||||
|
|
||||||
paginationLinkCurrentRaw, err2 := assets.Data.ReadFile("pagination_link_current.html")
|
paginationLinkCurrentRaw, err2 := static.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 := assets.Data.ReadFile("pagination_ellipsis.html")
|
paginationEllipsisRaw, err3 := static.ReadFile("pagination_ellipsis.html")
|
||||||
if err3 != nil {
|
if err3 != nil {
|
||||||
return "Missing pagination_ellipsis.html"
|
return "Missing pagination_ellipsis.html"
|
||||||
}
|
}
|
||||||
@ -42,16 +44,13 @@ 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 {
|
||||||
paginationItemRaw := string(paginationLinkRaw)
|
var paginationItemRaw = string(paginationLinkRaw)
|
||||||
if i == currentPage {
|
if i == currentPage {
|
||||||
paginationItemRaw = string(paginationLinkCurrentRaw)
|
paginationItemRaw = string(paginationLinkCurrentRaw)
|
||||||
}
|
}
|
||||||
@ -69,7 +68,7 @@ func CreateHTML(currentPage int, pages int, linksBase string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
paginationItemRaw := string(paginationLinkRaw)
|
var paginationItemRaw = string(paginationLinkRaw)
|
||||||
if i == currentPage {
|
if i == currentPage {
|
||||||
paginationItemRaw = string(paginationLinkCurrentRaw)
|
paginationItemRaw = string(paginationLinkCurrentRaw)
|
||||||
}
|
}
|
||||||
|
@ -25,31 +25,26 @@
|
|||||||
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 = 3
|
||||||
// PasteKeepForDays indicates that saved timeout is in days.
|
PasteKeepForMonths = 4
|
||||||
PasteKeepForDays = 3
|
|
||||||
// PasteKeepForMonths indicates that saved timeout is in months.
|
|
||||||
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,
|
||||||
@ -75,8 +70,6 @@ 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)
|
||||||
|
|
||||||
@ -90,7 +83,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +95,6 @@ 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,17 +25,21 @@
|
|||||||
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 (
|
||||||
ctx *context.Context
|
c *context.Context
|
||||||
log zerolog.Logger
|
log zerolog.Logger
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -50,12 +54,11 @@ func GetErrorTemplate(ec echo.Context, errorText string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRawTemplate returns only raw template data.
|
// GetRawTemplate returns only raw template data.
|
||||||
func GetRawTemplate(ectx echo.Context, templateName string, data map[string]string) string {
|
func GetRawTemplate(ec echo.Context, templateName string, data map[string]string) string {
|
||||||
// Getting main template.
|
// Getting main template.
|
||||||
tplRaw, err := assets.Data.ReadFile(templateName)
|
tplRaw, err := static.ReadFile(templateName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = ectx.String(http.StatusBadRequest, templateName+" not found.")
|
_ = ec.String(http.StatusBadRequest, templateName+" not found.")
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,30 +72,27 @@ func GetRawTemplate(ectx echo.Context, templateName string, data map[string]stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetTemplate returns formatted template that can be outputted to client.
|
// GetTemplate returns formatted template that can be outputted to client.
|
||||||
func GetTemplate(ectx echo.Context, name string, data map[string]string) string {
|
func GetTemplate(ec 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 := assets.Data.ReadFile("main.html")
|
mainhtml, err := static.ReadFile("main.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = ectx.String(http.StatusBadRequest, "main.html not found.")
|
_ = ec.String(http.StatusBadRequest, "main.html not found.")
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getting navigation.
|
// Getting navigation.
|
||||||
navhtml, err1 := assets.Data.ReadFile("navigation.html")
|
navhtml, err1 := static.ReadFile("navigation.html")
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
_ = ectx.String(http.StatusBadRequest, "navigation.html not found.")
|
_ = ec.String(http.StatusBadRequest, "navigation.html not found.")
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getting footer.
|
// Getting footer.
|
||||||
footerhtml, err2 := assets.Data.ReadFile("footer.html")
|
footerhtml, err2 := static.ReadFile("footer.html")
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
_ = ectx.String(http.StatusBadRequest, "footer.html not found.")
|
_ = ec.String(http.StatusBadRequest, "footer.html not found.")
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,10 +103,9 @@ func GetTemplate(ectx 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 := assets.Data.ReadFile(name)
|
reqhtml, err3 := static.ReadFile(name)
|
||||||
if err3 != nil {
|
if err3 != nil {
|
||||||
_ = ectx.String(http.StatusBadRequest, name+" not found.")
|
_ = ec.String(http.StatusBadRequest, name+" not found.")
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +122,6 @@ func GetTemplate(ectx 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) {
|
||||||
ctx = cc
|
c = cc
|
||||||
log = ctx.Logger.With().Str("type", "internal").Str("package", "templater").Logger()
|
log = c.Logger.With().Str("type", "internal").Str("package", "templater").Logger()
|
||||||
}
|
}
|
||||||
|
19
vendor/github.com/alecthomas/chroma/.gitignore
generated
vendored
Normal file
19
vendor/github.com/alecthomas/chroma/.gitignore
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# 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
Normal file
55
vendor/github.com/alecthomas/chroma/.golangci.yml
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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
Normal file
33
vendor/github.com/alecthomas/chroma/.goreleaser.yml
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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
Normal file
12
vendor/github.com/alecthomas/chroma/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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
Normal file
19
vendor/github.com/alecthomas/chroma/COPYING
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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
Normal file
19
vendor/github.com/alecthomas/chroma/Makefile
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
.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
Normal file
267
vendor/github.com/alecthomas/chroma/README.md
generated
vendored
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
# 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
Normal file
35
vendor/github.com/alecthomas/chroma/coalesce.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
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
Normal file
164
vendor/github.com/alecthomas/chroma/colour.go
generated
vendored
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
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
Normal file
137
vendor/github.com/alecthomas/chroma/delegate.go
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
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
Normal file
7
vendor/github.com/alecthomas/chroma/doc.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// 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
Normal file
43
vendor/github.com/alecthomas/chroma/formatter.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
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
Normal file
57
vendor/github.com/alecthomas/chroma/formatters/api.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
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