Compare commits
52 Commits
Author | SHA1 | Date | |
---|---|---|---|
a0f896dc53
|
|||
fa7a79310d
|
|||
ce962ddd00
|
|||
164a37d41f
|
|||
3ebff29113
|
|||
7f04a9a7bb
|
|||
a2a21482e2
|
|||
ffb42f43eb | |||
3f9801b1bd | |||
825fd724ff | |||
5f58741159 | |||
47672c586d | |||
11897d0e1a | |||
02cea49fc4 | |||
a52b18ffe4 | |||
da4bc379d8 | |||
9c9f0c1f68 | |||
6a787e7e23 | |||
cdc8ecf49b | |||
bd981023da | |||
a982e07faf | |||
0bbb415852 | |||
6207229e9b | |||
2159aadfcb | |||
3ebf9b653e | |||
4d60a9b1b6 | |||
e042c4414f | |||
333fc1d12a | |||
7f2174a33e | |||
e26a466efc | |||
|
d9a46aa5b3 | ||
|
dbf82e213b | ||
4f01e2f5ce | |||
7281b9be65 | |||
19b5ef3d9f | |||
3fe51fc6c5 | |||
19a3a5004c | |||
0bf20cd2c9 | |||
35c217fe46 | |||
849c72b238 | |||
c565ec8f21 | |||
aa5e11329f | |||
b6556d6e90 | |||
400ce0db0c | |||
fc8f4e9d8b | |||
2beb9ecef9 | |||
0cca0f453f | |||
296e2771d6 | |||
c8005a4836 | |||
342a4d1d7c | |||
d7e9865c91 | |||
9f0beb7e3e |
69
.drone.yml
Normal file
69
.drone.yml
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build
|
||||
|
||||
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
|
||||
image: golangci/golangci-lint:latest
|
||||
environment:
|
||||
GOFLAGS: -mod=vendor
|
||||
CGO_ENABLED: 0
|
||||
commands:
|
||||
- golangci-lint run
|
||||
depends_on:
|
||||
- notify-start
|
||||
|
||||
- name: test
|
||||
image: golang:1.13.5-alpine
|
||||
environment:
|
||||
GOFLAGS: -mod=vendor
|
||||
CGO_ENABLED: 0
|
||||
commands:
|
||||
- go test ./...
|
||||
depends_on:
|
||||
- notify-start
|
||||
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
when:
|
||||
branch: master
|
||||
settings:
|
||||
username:
|
||||
from_secret: dockerhub_user
|
||||
password:
|
||||
from_secret: dockerhub_password
|
||||
repo: pztrn/fastpastebin
|
||||
auto_tag: true
|
||||
depends_on:
|
||||
- lint
|
||||
- test
|
||||
|
||||
- name: notify-end
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
- failure
|
||||
image: pztrn/discordrone
|
||||
settings:
|
||||
webhook_id:
|
||||
from_secret: discord_webhook_id
|
||||
webhook_token:
|
||||
from_secret: discord_webhook_secret
|
||||
message: "
|
||||
{{#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
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1 +1,6 @@
|
||||
examples/fastpastebin.yaml
|
||||
dist/
|
||||
data/
|
||||
.idea
|
||||
.vscode
|
||||
*DS_Store*
|
||||
|
31
.gitlab-ci.yml
Normal file
31
.gitlab-ci.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
image: docker:dind
|
||||
|
||||
variables:
|
||||
HOST: 0.0.0.0
|
||||
PORT: 2375
|
||||
DOCKER_HOST: tcp://docker:2375/
|
||||
DOCKER_DRIVER: overlay2
|
||||
CONTAINER_NAME: registry.gitlab.pztrn.name/fastpastebin/fastpastebin
|
||||
GIT_STRATEGY: clone
|
||||
DOCKER_TLS_CERTDIR: ""
|
||||
|
||||
services:
|
||||
- docker:dind
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
||||
before_script:
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
|
||||
build:
|
||||
stage: build
|
||||
tags:
|
||||
- docker
|
||||
script:
|
||||
- source docker/set_docker_tag.sh
|
||||
- docker build -t $CONTAINER_NAME:$DOCKER_TAG .
|
||||
- docker push $CONTAINER_NAME:$DOCKER_TAG
|
||||
only:
|
||||
- tags
|
||||
- master
|
21
.golangci.yml
Normal file
21
.golangci.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
run:
|
||||
deadline: 5m
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
# Because globals might exist, but according to our codestyle they
|
||||
# should be lowercased and considered as unexported.
|
||||
- gochecknoglobals
|
||||
# While it might be useful it'll create more problems that will solve.
|
||||
- gocritic
|
||||
# Complains about main() lengths, which isn't an issue.
|
||||
- funlen
|
||||
# Magic numbers everywhere and we can't get rid of them.
|
||||
- gomnd
|
||||
linters-settings:
|
||||
lll:
|
||||
line-length: 420
|
||||
gocognit:
|
||||
min-complexity: 50
|
||||
gocyclo:
|
||||
min-complexity: 40
|
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM golang:1.13.1-alpine AS build
|
||||
|
||||
WORKDIR /fastpastebin
|
||||
COPY . .
|
||||
|
||||
WORKDIR /fastpastebin/cmd/fastpastebin
|
||||
|
||||
RUN GOFLAGS="-mod=vendor" go build
|
||||
|
||||
FROM alpine:3.10
|
||||
LABEL maintainer "Stanislav N. <pztrn@pztrn.name>"
|
||||
|
||||
COPY --from=build /fastpastebin/cmd/fastpastebin/fastpastebin /app/fastpastebin
|
||||
COPY docker/fastpastebin.docker.yaml /app/fastpastebin.yaml
|
||||
|
||||
EXPOSE 25544
|
||||
ENTRYPOINT [ "/app/fastpastebin", "-config", "/app/fastpastebin.yaml" ]
|
187
Gopkg.lock
generated
187
Gopkg.lock
generated
@@ -1,187 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/alecthomas/chroma"
|
||||
packages = [
|
||||
".",
|
||||
"formatters",
|
||||
"formatters/html",
|
||||
"lexers",
|
||||
"lexers/a",
|
||||
"lexers/b",
|
||||
"lexers/c",
|
||||
"lexers/d",
|
||||
"lexers/e",
|
||||
"lexers/f",
|
||||
"lexers/g",
|
||||
"lexers/h",
|
||||
"lexers/i",
|
||||
"lexers/internal",
|
||||
"lexers/j",
|
||||
"lexers/k",
|
||||
"lexers/l",
|
||||
"lexers/m",
|
||||
"lexers/n",
|
||||
"lexers/o",
|
||||
"lexers/p",
|
||||
"lexers/q",
|
||||
"lexers/r",
|
||||
"lexers/s",
|
||||
"lexers/t",
|
||||
"lexers/v",
|
||||
"lexers/w",
|
||||
"lexers/x",
|
||||
"lexers/y",
|
||||
"styles"
|
||||
]
|
||||
revision = "3020e2ea8c6b1a9c2336022d847c4392c3997f02"
|
||||
version = "v0.4.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/danwakefield/fnmatch"
|
||||
packages = ["."]
|
||||
revision = "cbb64ac3d964b81592e64f957ad53df015803288"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/dchest/captcha"
|
||||
packages = ["."]
|
||||
revision = "6a29415a8364ec2971fdc62d9e415ed53fc20410"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
packages = ["."]
|
||||
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
|
||||
version = "v3.2.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/dlclark/regexp2"
|
||||
packages = [
|
||||
".",
|
||||
"syntax"
|
||||
]
|
||||
revision = "487489b64fb796de2e55f4e8a4ad1e145f80e957"
|
||||
version = "v1.1.6"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-sql-driver/mysql"
|
||||
packages = ["."]
|
||||
revision = "a0583e0143b1624142adab07e0e97fe106d99561"
|
||||
version = "v1.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/jmoiron/sqlx"
|
||||
packages = [
|
||||
".",
|
||||
"reflectx"
|
||||
]
|
||||
revision = "2aeb6a910c2b94f2d5eb53d9895d80e27264ec41"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/labstack/echo"
|
||||
packages = [
|
||||
".",
|
||||
"middleware"
|
||||
]
|
||||
revision = "6d227dfea4d2e52cb76856120b3c17f758139b4e"
|
||||
version = "3.3.5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/labstack/gommon"
|
||||
packages = [
|
||||
"bytes",
|
||||
"color",
|
||||
"log",
|
||||
"random"
|
||||
]
|
||||
revision = "588f4e8bddc6cb45c27b448e925c7fd6a5545434"
|
||||
version = "0.2.5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-colorable"
|
||||
packages = ["."]
|
||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
||||
version = "v0.0.9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pressly/goose"
|
||||
packages = ["."]
|
||||
revision = "056a4d47dcc4d67fa3947a4f13945a5c690e568b"
|
||||
version = "v2.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/pztrn/flagger"
|
||||
packages = ["."]
|
||||
revision = "1330c5f1b64f253b0505ee4a2417fb8be856b87d"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/rs/zerolog"
|
||||
packages = [
|
||||
".",
|
||||
"internal/cbor",
|
||||
"internal/json"
|
||||
]
|
||||
revision = "05eafee0eb17d0150591a8f30f0fa592cc9b7471"
|
||||
version = "v1.6.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/valyala/bytebufferpool"
|
||||
packages = ["."]
|
||||
revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/valyala/fasttemplate"
|
||||
packages = ["."]
|
||||
revision = "dcecefd839c4193db0d35b88ec65b4c12d360ab0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"acme",
|
||||
"acme/autocert",
|
||||
"pbkdf2",
|
||||
"scrypt"
|
||||
]
|
||||
revision = "b49d69b5da943f7ef3c9cf91c8777c1f78a0cc3c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"webdav",
|
||||
"webdav/internal/xml"
|
||||
]
|
||||
revision = "5f9ae10d9af5b1c89ae6904293b14b064d4ada23"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "cbbc999da32df943dac6cd71eb3ee39e1d7838b9"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||
version = "v2.2.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "aea0cd48405b88f2c799a3d994b952758f29e06ada92b4bbe6cc4ff105d95d59"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
34
Gopkg.toml
34
Gopkg.toml
@@ -1,34 +0,0 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/rs/zerolog"
|
||||
version = "1.6.0"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
66
README.md
66
README.md
@@ -1,80 +1,60 @@
|
||||
[Chat on Keybase.io](https://keybase.io/team/fastpastebin)
|
||||
|
||||
# Fast Pastebin
|
||||
|
||||
Easy-to-use-and-install pastebin software written in Go. No bells or
|
||||
whistles, no websockets and even NO JAVASCRIPT!(*)
|
||||
[](https://ci.dev.pztrn.name/fastpastebin/fastpastebin/) [](https://discord.gg/qHN6KsD) 
|
||||
|
||||
(*) Except fontawesome, because it's awesome :).
|
||||
Easy-to-use-and-install pastebin software written in Go. No bells or whistles, no websockets and even NO JAVASCRIPT!
|
||||
|
||||
# Current functionality.
|
||||
## Current functionality
|
||||
|
||||
* Create and view public and private pastes.
|
||||
* Syntax highlighting.
|
||||
* Pastes expiration.
|
||||
* Passwords for pastes.
|
||||
* Multiple storage backends. Currently: ``flatfiles``, ``mysql`` and ``postgresql``.
|
||||
|
||||
# Caveats.
|
||||
## Caveats
|
||||
|
||||
* No links at lines numbers. See https://github.com/alecthomas/chroma/issues/132
|
||||
* No links at lines numbers. See [this Chroma bug](https://github.com/alecthomas/chroma/issues/132)
|
||||
|
||||
# Installation and updating
|
||||
## Installation and updating
|
||||
|
||||
Just issue:
|
||||
|
||||
```
|
||||
go get -u -v github.com/pztrn/fastpastebin/cmd/fastpastebin
|
||||
```bash
|
||||
CGO_ENABLED=0 go get -u -v go.dev.pztrn.name/fastpastebin/cmd/fastpastebin
|
||||
```
|
||||
|
||||
This command can be used to update Fast Paste Bin.
|
||||
|
||||
**WARNING:** installation by compiling Fast Paste Bin from sources **require**
|
||||
at least 300 megabytes of free RAM! Eventually it'll run even on 64MB-powered
|
||||
VM, it's only a compilation issue.
|
||||
## 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.
|
||||
Configuration file position is irrelevant, there is no hardcoded paths where Fast Paste Bin looking for it's configuration. Use ``-config`` CLI parameter or ``FASTPASTEBIN_CONFIG`` environment variable to specify path.
|
||||
|
||||
Configuration file position is irrelevant, there is no hardcoded paths where
|
||||
Fast Paste Bin looking for it's configuration. Use ``-config`` CLI parameter
|
||||
or ``FASTPASTEBIN_CONFIG`` environment variable to specify path.
|
||||
## Developing
|
||||
|
||||
# Developing
|
||||
Developers should install [fileb0x](https://github.com/UnnoTed/fileb0x/) which is used as replacement to go-bindata for embedding assets into binary. After changing assets they should be recompiled into Go code. At repository root execute this command and you'll be fine:
|
||||
|
||||
Developers should install 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:
|
||||
|
||||
```
|
||||
```bash
|
||||
fileb0x fileb0x.yml
|
||||
```
|
||||
|
||||
Also if you're changed list of assets (by creating or deleting them) be sure
|
||||
to fix files list in ``fileb0x.yml`` file!
|
||||
Also if you're changed list of assets (by creating or deleting them) be sure to fix files list in ``fileb0x.yml`` file!
|
||||
|
||||
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 https://github.com/golang/go/wiki/CodeReviewComments
|
||||
with few exceptions:
|
||||
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:
|
||||
|
||||
* Imports should be organized in 3 groups: stdlib, local, other. See
|
||||
https://github.com/pztrn/fastpastebin/blob/master/pastes/api_http.go for
|
||||
example.
|
||||
* 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
|
||||
|
||||
This is a ToDo list which isn't sorted by any parameter at all. Just a list
|
||||
of tasks you can help with.
|
||||
This is a ToDo list which isn't sorted by any parameter at all. Just a list of tasks you can help with.
|
||||
|
||||
* User CP.
|
||||
* Files uploading.
|
||||
* Passwords for files.
|
||||
* Pastes forking and revisioning (like git or github gists).
|
||||
* Possibility to copy-paste-edit WISYWIG content.
|
||||
* Possibility to copy-paste-edit WYSIWYG content.
|
||||
* CLI client for pastes and files uploading.
|
@@ -1,35 +0,0 @@
|
||||
// Code generaTed by fileb0x at "2018-05-01 18:35:23.702577304 +0500 +05 m=+0.047872409" from config file "fileb0x.yml" DO NOT EDIT.
|
||||
// modified(2018-05-01 18:35:02 +0500 +05)
|
||||
// original path: assets/error.html
|
||||
|
||||
package static
|
||||
|
||||
import (
|
||||
|
||||
"os"
|
||||
)
|
||||
|
||||
// FileErrorHTML is "/error.html"
|
||||
var FileErrorHTML = []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\x20\x2d\x20\x45\x52\x52\x4f\x52\x21\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\x30\x2e\x6d\x69\x6e\x2e\x63\x73\x73\x22\x3e\x0a\x20\x20\x20\x20\x3c\x73\x63\x72\x69\x70\x74\x20\x64\x65\x66\x65\x72\x20\x73\x72\x63\x3d\x22\x2f\x73\x74\x61\x74\x69\x63\x2f\x6a\x73\x2f\x66\x6f\x6e\x74\x61\x77\x65\x73\x6f\x6d\x65\x2d\x35\x2e\x30\x2e\x37\x2e\x6a\x73\x22\x3e\x3c\x2f\x73\x63\x72\x69\x70\x74\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\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\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\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\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\x20\x20\x20\x20\x50\x61\x73\x74\x65\x73\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x61\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\x6e\x61\x76\x62\x61\x72\x2d\x62\x75\x72\x67\x65\x72\x20\x62\x75\x72\x67\x65\x72\x22\x20\x64\x61\x74\x61\x2d\x74\x61\x72\x67\x65\x74\x3d\x22\x6e\x61\x76\x62\x61\x72\x49\x74\x65\x6d\x73\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x73\x70\x61\x6e\x3e\x3c\x2f\x73\x70\x61\x6e\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x73\x70\x61\x6e\x3e\x3c\x2f\x73\x70\x61\x6e\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x73\x70\x61\x6e\x3e\x3c\x2f\x73\x70\x61\x6e\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\x0a\x20\x20\x20\x20\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\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\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\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\x6e\x61\x76\x62\x61\x72\x2d\x65\x6e\x64\x22\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\x3c\x2f\x6e\x61\x76\x3e\x0a\x20\x20\x20\x20\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\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\x73\x65\x63\x74\x69\x6f\x6e\x3e\x0a\x3c\x2f\x62\x6f\x64\x79\x3e\x0a\x0a\x3c\x2f\x68\x74\x6d\x6c\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)
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,35 +0,0 @@
|
||||
// Code generaTed by fileb0x at "2018-05-18 22:25:13.191256649 +0500 +05 m=+0.041507879" from config file "fileb0x.yml" DO NOT EDIT.
|
||||
// modified(2018-05-18 22:17:55.447214662 +0500 +05)
|
||||
// original path: assets/pastelist_list.html
|
||||
|
||||
package static
|
||||
|
||||
import (
|
||||
|
||||
"os"
|
||||
)
|
||||
|
||||
// FilePastelistListHTML is "/pastelist_list.html"
|
||||
var FilePastelistListHTML = []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\x20\x2d\x20\x50\x61\x73\x74\x65\x73\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\x30\x2e\x6d\x69\x6e\x2e\x63\x73\x73\x22\x3e\x0a\x20\x20\x20\x20\x3c\x73\x63\x72\x69\x70\x74\x20\x64\x65\x66\x65\x72\x20\x73\x72\x63\x3d\x22\x2f\x73\x74\x61\x74\x69\x63\x2f\x6a\x73\x2f\x66\x6f\x6e\x74\x61\x77\x65\x73\x6f\x6d\x65\x2d\x35\x2e\x30\x2e\x37\x2e\x6a\x73\x22\x3e\x3c\x2f\x73\x63\x72\x69\x70\x74\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\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\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\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\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\x20\x20\x20\x20\x50\x61\x73\x74\x65\x73\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x61\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\x6e\x61\x76\x62\x61\x72\x2d\x62\x75\x72\x67\x65\x72\x20\x62\x75\x72\x67\x65\x72\x22\x20\x64\x61\x74\x61\x2d\x74\x61\x72\x67\x65\x74\x3d\x22\x6e\x61\x76\x62\x61\x72\x49\x74\x65\x6d\x73\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x73\x70\x61\x6e\x3e\x3c\x2f\x73\x70\x61\x6e\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x73\x70\x61\x6e\x3e\x3c\x2f\x73\x70\x61\x6e\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x73\x70\x61\x6e\x3e\x3c\x2f\x73\x70\x61\x6e\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\x0a\x20\x20\x20\x20\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\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\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\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\x6e\x61\x76\x62\x61\x72\x2d\x65\x6e\x64\x22\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\x3c\x2f\x6e\x61\x76\x3e\x0a\x20\x20\x20\x20\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\x20\x20\x20\x20\x3c\x64\x69\x76\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\x7d\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\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x7b\x70\x61\x73\x74\x65\x73\x7d\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\x64\x69\x76\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\x7d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x73\x65\x63\x74\x69\x6f\x6e\x3e\x0a\x3c\x2f\x62\x6f\x64\x79\x3e\x0a\x0a\x3c\x2f\x68\x74\x6d\x6c\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)
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
assets/css/bulma-0.7.0.min.css
vendored
1
assets/css/bulma-0.7.0.min.css
vendored
File diff suppressed because one or more lines are too long
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-1.0.4.min.css
vendored
1
assets/css/bulma-tooltip-1.0.4.min.css
vendored
File diff suppressed because one or more lines are too long
1
assets/css/bulma-tooltip-3.0.0.min.css
vendored
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
File diff suppressed because one or more lines are too long
@@ -7,3 +7,8 @@
|
||||
.paste-data {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* This is a special case for multiline tooltips. See https://github.com/Wikiki/bulma-tooltip/issues/34 */
|
||||
.tooltip.is-tooltip-multiline::before {
|
||||
white-space:pre-line
|
||||
}
|
6
assets/database_not_available.html
Normal file
6
assets/database_not_available.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<section class="section">
|
||||
<div class="notification is-danger">
|
||||
<h3><strong>Database not available</strong></h3>
|
||||
<p>Something went wrong while trying to connect to database. Check logs for details.</p>
|
||||
</div>
|
||||
</section>
|
@@ -1,40 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Fast Paste Bin - ERROR!</title>
|
||||
<link rel="stylesheet" href="/static/css/bulma-0.7.0.min.css">
|
||||
<script defer src="/static/js/fontawesome-5.0.7.js"></script>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar is-dark">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">Fast Paste Bin</a>
|
||||
<a class="navbar-item" href="/pastes/">
|
||||
Pastes
|
||||
</a>
|
||||
<div class="navbar-burger burger" data-target="navbarItems">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="navbarItems" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="section">
|
||||
<section class="section">
|
||||
<div class="box has-background-warning">
|
||||
<p>{error}</p>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</div>
|
||||
</section>
|
11
assets/footer.html
Normal file
11
assets/footer.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<div class="container">
|
||||
<div class="content has-text-centered">
|
||||
<p>
|
||||
<strong>Fast paste bin</strong> version
|
||||
<strong>{version}</strong> by
|
||||
<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="https://gitlab.pztrn.name/fastpastebin/fastpastebin">source or binary releases here</a>!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
@@ -1,120 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Fast Paste Bin</title>
|
||||
<link rel="stylesheet" href="/static/css/bulma-0.7.0.min.css">
|
||||
<link rel="stylesheet" href="/static/css/bulma-tooltip-1.0.4.min.css">
|
||||
<script defer src="/static/js/fontawesome-5.0.7.js"></script>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar is-dark">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">Fast Paste Bin</a>
|
||||
<a class="navbar-item" href="/pastes/">
|
||||
Pastes
|
||||
</a>
|
||||
<div class="navbar-burger burger" data-target="navbarItems">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<section class="section">
|
||||
<form action="/paste/" method="POST" autocomplete="off">
|
||||
<div class="columns">
|
||||
<div class="column is-8 field">
|
||||
<div class="control">
|
||||
<textarea class="textarea" placeholder="Paste contents" name="paste-contents" id="paste-contents"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="navbarItems" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="section">
|
||||
<form action="/paste/" method="POST" autocomplete="off">
|
||||
<div class="columns">
|
||||
<div class="column is-8 field">
|
||||
<div class="column is-4">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<textarea class="textarea" placeholder="Paste contents" name="paste-contents" id="paste-contents"></textarea>
|
||||
<input class="input" type="text" placeholder="Paste title" name="paste-title" id="paste-title">
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="Paste title" name="paste-title" id="paste-title">
|
||||
<div class="field">
|
||||
<label for="paste-keep-for">Paste should be available for:</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select id="paste-keep-for" name="paste-keep-for">
|
||||
<option value="30M">30 minutes</option>
|
||||
<option value="1h">1 hour</option>
|
||||
<option value="3h">3 hours</option>
|
||||
<option value="6h">6 hours</option>
|
||||
<option value="12h">12 hours</option>
|
||||
<option value="1d">1 day</option>
|
||||
<option value="2d">2 days</option>
|
||||
<option value="3d">3 days</option>
|
||||
<option value="4d">4 days</option>
|
||||
<option value="5d">5 days</option>
|
||||
<option value="7d">7 days</option>
|
||||
<option value="14d">14 days</option>
|
||||
<option value="21d">21 days</option>
|
||||
<option value="1m">1 monts</option>
|
||||
<option value="3m">3 months</option>
|
||||
<option value="6m">6 months</option>
|
||||
<option value="forever">Forever</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="paste-keep-for">Paste should be available for:</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select id="paste-keep-for" name="paste-keep-for">
|
||||
<option value="30M">30 minutes</option>
|
||||
<option value="1h">1 hour</option>
|
||||
<option value="3h">3 hours</option>
|
||||
<option value="6h">6 hours</option>
|
||||
<option value="12h">12 hours</option>
|
||||
<option value="1d">1 day</option>
|
||||
<option value="2d">2 days</option>
|
||||
<option value="3d">3 days</option>
|
||||
<option value="4d">4 days</option>
|
||||
<option value="5d">5 days</option>
|
||||
<option value="7d">7 days</option>
|
||||
<option value="14d">14 days</option>
|
||||
<option value="21d">21 days</option>
|
||||
<option value="1m">1 monts</option>
|
||||
<option value="3m">3 months</option>
|
||||
<option value="6m">6 months</option>
|
||||
<option value="forever">Forever</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field tooltip is-tooltip-bottom is-tooltip-multiline" data-tooltip="Should this paste be accessible only with special URL and not shown in pastes list? WARNING: If you'll enter password into 'Password for paste' field this checkbox will be assumed as checked!">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="paste-private" id="paste-private"> Private paste with unique URL?
|
||||
</label>
|
||||
</div>
|
||||
<div>OR</div>
|
||||
<div class="field tooltip is-tooltip-bottom is-tooltip-multiline" data-tooltip="If you'll enter password here - 'Private paste with unique URL' checkbox will be assumed as checked.">
|
||||
<label for="paste-password">Password for paste:</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="Enter password." name="paste-password" id="paste-password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="paste-language">Language:</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select id="paste-language" name="paste-language">
|
||||
{lexers}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field tooltip is-tooltip-bottom is-tooltip-multiline" data-tooltip="Should this paste be accessible only with special URL and not shown in pastes list? WARNING: If you'll enter password into 'Password for paste' field this checkbox will be assumed as checked!">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="paste-private" id="paste-private"> Private paste with unique URL?
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="paste-captcha">Captcha:</label>
|
||||
<div class="control">
|
||||
<p>
|
||||
<img id=image src="/captcha/{captchaString}.png" alt="Captcha image">
|
||||
</p>
|
||||
<input class="input" type="text" placeholder="Captcha solution" name="paste-captcha-solution" id="paste-captcha-solution">
|
||||
<input class="input is-hidden" type="text" name="paste-captcha-id" id="paste-captcha-id" readonly value="{captchaString}">
|
||||
</div>
|
||||
<div>OR</div>
|
||||
<div class="field tooltip is-tooltip-bottom is-tooltip-multiline" data-tooltip="If you'll enter password here - 'Private
|
||||
paste with unique URL' checkbox will be assumed as checked.">
|
||||
<label for="paste-password">Password for paste:</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="Enter password." name="paste-password" id="paste-password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="paste-language">Language:</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select id="paste-language" name="paste-language">
|
||||
{lexers}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="paste-captcha">Captcha:</label>
|
||||
<div class="control">
|
||||
<p>
|
||||
<img id=image src="/captcha/{captchaString}.png" alt="Captcha image">
|
||||
</p>
|
||||
<input class="input" type="text" placeholder="Captcha solution" name="paste-captcha-solution" id="paste-captcha-solution">
|
||||
<input class="input is-hidden" type="text" name="paste-captcha-id" id="paste-captcha-id" readonly value="{captchaString}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="button is-success" type="submit" value="Paste!">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="button is-success" type="submit" value="Paste!">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
20
assets/main.html
Normal file
20
assets/main.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Fast Paste Bin</title>
|
||||
<link rel="stylesheet" href="/static/css/bulma-0.7.5.min.css">
|
||||
<link rel="stylesheet" href="/static/css/bulma-tooltip-3.0.0.min.css">
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{navigation} {documentBody}
|
||||
</body>
|
||||
<footer class="footer">
|
||||
{footer}
|
||||
</footer>
|
||||
|
||||
</html>
|
16
assets/navigation.html
Normal file
16
assets/navigation.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<nav class="navbar is-dark">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">Fast Paste Bin</a>
|
||||
<a class="navbar-item" href="/pastes/">
|
||||
Pastes
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="navbarItems" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
@@ -1,69 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Fast Paste Bin - Paste is protected with password</title>
|
||||
<link rel="stylesheet" href="/static/css/bulma-0.7.0.min.css">
|
||||
<script defer src="/static/js/fontawesome-5.0.7.js"></script>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar is-dark">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">Fast Paste Bin</a>
|
||||
<a class="navbar-item" href="/pastes/">
|
||||
Pastes
|
||||
</a>
|
||||
<div class="navbar-burger burger" data-target="navbarItems">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="navbarItems" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="section">
|
||||
<div class="content">
|
||||
<div class="columns">
|
||||
<div class="column is-4 is-offset-4">
|
||||
<form action="/paste/{pasteID}/{pasteTimestamp}/verify" method="POST" autocomplete="off">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">
|
||||
This paste is protected with password
|
||||
</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<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>
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="Enter password." name="paste-password" id="paste-password">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<section class="section">
|
||||
<div class="content">
|
||||
<div class="columns">
|
||||
<div class="column is-4 is-offset-4">
|
||||
<form action="/paste/{pasteID}/{pasteTimestamp}/verify" method="POST" autocomplete="off">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">
|
||||
This paste is protected with password
|
||||
</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<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>
|
||||
<div class="control">
|
||||
<input class="button is-success" type="submit" value="Check password!">
|
||||
<input class="input" type="text" placeholder="Enter password." name="paste-password" id="paste-password">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="button is-success" type="submit" value="Check password!">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</div>
|
||||
</section>
|
@@ -1,71 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Fast Paste Bin - Paste #{pasteID}</title>
|
||||
<link rel="stylesheet" href="/static/css/bulma-0.7.0.min.css">
|
||||
<script defer src="/static/js/fontawesome-5.0.7.js"></script>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar is-dark">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">Fast Paste Bin</a>
|
||||
<a class="navbar-item" href="/pastes/">
|
||||
Pastes
|
||||
</a>
|
||||
<div class="navbar-burger burger" data-target="navbarItems">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="navbarItems" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="section">
|
||||
<div class="content">
|
||||
<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Title</th>
|
||||
<th>Language</th>
|
||||
<th>Pasted on</th>
|
||||
<th>Will expire on</th>
|
||||
<th>Paste type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{pasteID}</td>
|
||||
<td>{pasteTitle}</td>
|
||||
<td>{pasteLanguage}</td>
|
||||
<td>{pasteDate}</td>
|
||||
<td>{pasteExpiration}</td>
|
||||
<td>{pasteType}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="6">
|
||||
<a class="button" href="/paste/{pasteID}/{pasteTs}raw">View raw</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<div class="paste-data">
|
||||
{pastedata}
|
||||
<section class="section">
|
||||
<div class="content">
|
||||
<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Title</th>
|
||||
<th>Language</th>
|
||||
<th>Pasted on</th>
|
||||
<th>Will expire on</th>
|
||||
<th>Paste type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{pasteID}</td>
|
||||
<td>{pasteTitle}</td>
|
||||
<td>{pasteLanguage}</td>
|
||||
<td>{pasteDate}</td>
|
||||
<td>{pasteExpiration}</td>
|
||||
<td>{pasteType}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="6">
|
||||
<a class="button" href="/paste/{pasteID}/{pasteTs}raw">View raw</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</section>
|
||||
<div class="paste-data">
|
||||
{pastedata}
|
||||
</div>
|
@@ -1,48 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Fast Paste Bin - Pastes</title>
|
||||
<link rel="stylesheet" href="/static/css/bulma-0.7.0.min.css">
|
||||
<script defer src="/static/js/fontawesome-5.0.7.js"></script>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar is-dark">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">Fast Paste Bin</a>
|
||||
<a class="navbar-item" href="/pastes/">
|
||||
Pastes
|
||||
</a>
|
||||
<div class="navbar-burger burger" data-target="navbarItems">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="navbarItems" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="section">
|
||||
<div>
|
||||
{pagination}
|
||||
</div>
|
||||
<div>
|
||||
{pastes}
|
||||
</div>
|
||||
<div>
|
||||
{pagination}
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<section class="section">
|
||||
<div>
|
||||
{pagination}
|
||||
</div>
|
||||
<div>
|
||||
{pastes}
|
||||
</div>
|
||||
<div>
|
||||
{pagination}
|
||||
</div>
|
||||
</section>
|
@@ -1,5 +1,5 @@
|
||||
// Code generated by fileb0x at "2018-05-18 22:25:13.180720675 +0500 +05 m=+0.030971779" from config file "fileb0x.yml" DO NOT EDIT.
|
||||
// modification hash(1679c5529785a2e624071b2679319ecb.608888160ddb195ed89fce5ab290ff92)
|
||||
// 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
|
||||
|
||||
@@ -7,6 +7,7 @@ package static
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -14,7 +15,6 @@ import (
|
||||
|
||||
|
||||
"golang.org/x/net/webdav"
|
||||
"context"
|
||||
|
||||
|
||||
)
|
||||
@@ -36,13 +36,17 @@ var (
|
||||
)
|
||||
|
||||
// HTTPFS implements http.FileSystem
|
||||
type HTTPFS struct {}
|
||||
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() {
|
||||
if CTX.Err() != nil {
|
||||
panic(CTX.Err())
|
||||
err := CTX.Err()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -50,10 +54,6 @@ func init() {
|
||||
|
||||
|
||||
|
||||
var err error
|
||||
|
||||
|
||||
|
||||
|
||||
err = FS.Mkdir(CTX, "static/", 0777)
|
||||
if err != nil && err != os.ErrExist {
|
||||
@@ -91,6 +91,7 @@ func init() {
|
||||
|
||||
// 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)
|
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)
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// Code generaTed by fileb0x at "2018-05-01 00:41:18.339400139 +0500 +05 m=+0.041230393" from config file "fileb0x.yml" DO NOT EDIT.
|
||||
// modified(2018-05-01 00:32:51 +0500 +05)
|
||||
// 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
|
@@ -1,5 +1,5 @@
|
||||
// Code generaTed by fileb0x at "2018-04-30 23:41:29.363654393 +0500 +05 m=+0.033742092" from config file "fileb0x.yml" DO NOT EDIT.
|
||||
// modified(2018-04-30 23:39:44.086392054 +0500 +05)
|
||||
// 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
|
@@ -1,5 +1,5 @@
|
||||
// Code generaTed by fileb0x at "2018-05-01 00:26:24.973087795 +0500 +05 m=+0.032533011" from config file "fileb0x.yml" DO NOT EDIT.
|
||||
// modified(2018-04-30 23:54:11 +0500 +05)
|
||||
// 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
|
@@ -1,5 +1,5 @@
|
||||
// Code generaTed by fileb0x at "2018-05-01 00:26:24.971847954 +0500 +05 m=+0.031293214" from config file "fileb0x.yml" DO NOT EDIT.
|
||||
// modified(2018-04-30 23:54:14 +0500 +05)
|
||||
// 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
|
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)
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// Code generaTed by fileb0x at "2018-05-01 18:35:23.706057355 +0500 +05 m=+0.051352478" from config file "fileb0x.yml" DO NOT EDIT.
|
||||
// modified(2018-05-01 18:35:21 +0500 +05)
|
||||
// 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
|
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
@@ -1,5 +1,5 @@
|
||||
// Code generaTed by fileb0x at "2018-05-01 17:43:41.903107504 +0500 +05 m=+0.039828393" from config file "fileb0x.yml" DO NOT EDIT.
|
||||
// modified(2018-05-01 17:43:40.11480207 +0500 +05)
|
||||
// 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
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
// 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")
|
||||
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() {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// Code generaTed by fileb0x at "2018-04-30 12:11:40.314331472 +0500 +05 m=+0.031685082" from config file "fileb0x.yml" DO NOT EDIT.
|
||||
// modified(2018-02-27 01:53:44 +0500 +05)
|
||||
// Code generaTed by fileb0x at "2021-01-09 06:15:34.33997584 +0500 +05 m=+0.044604593" from config file "fileb0x.yml" DO NOT EDIT.
|
||||
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
|
||||
// original path: assets/js/fontawesome-5.0.7.js
|
||||
|
||||
package static
|
26
builder.sh
Executable file
26
builder.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
VERSION=$1
|
||||
if [ "${VERSION}" == "" ]; then
|
||||
echo "Specify version as first parameter!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OS_LIST=("darwin/amd64" "dragonfly/amd64" "freebsd/386" "freebsd/amd64" "freebsd/arm" "linux/386" "linux/amd64" "linux/arm" "linux/arm64" "linux/ppc64" "linux/ppc64le" "linux/mips" "linux/mipsle" "linux/mips64" "linux/mips64le" "linux/s390x" "netbsd/386" "netbsd/amd64" "netbsd/arm" "openbsd/386" "openbsd/amd64" "openbsd/arm" "solaris/amd64" "windows/386" "windows/amd64")
|
||||
|
||||
if [ ! -d ./dist ]; then
|
||||
mkdir -p ./dist
|
||||
fi
|
||||
|
||||
for os in ${OS_LIST[@]}; do
|
||||
mkdir -p ./dist/${os}
|
||||
goos=$(echo ${os} | awk -F"/" '{ print $1 }')
|
||||
goarch=$(echo ${os} | awk -F"/" '{ print $2 }')
|
||||
echo "Building for ${goos} ${goarch}..."
|
||||
GOOS=${goos} GOARCH=${goarch} GOFLAGS="-mod=vendor" go build -o ./dist/${os}/fastpastebin ./cmd/fastpastebin/
|
||||
cp ./examples/fastpastebin.yaml.dist ./dist/${os}/fastpastebin.yaml
|
||||
cd ./dist/${os}/
|
||||
tar -czf fastpastebin-${VERSION}-${goos}-${goarch}.tar.gz fastpastebin fastpastebin.yaml
|
||||
mv fastpastebin-${VERSION}-${goos}-${goarch}.tar.gz ../../
|
||||
cd - &>/dev/null
|
||||
done
|
@@ -31,12 +31,13 @@ import (
|
||||
"syscall"
|
||||
|
||||
// local
|
||||
"github.com/pztrn/fastpastebin/api"
|
||||
"github.com/pztrn/fastpastebin/captcha"
|
||||
"github.com/pztrn/fastpastebin/context"
|
||||
"github.com/pztrn/fastpastebin/database"
|
||||
"github.com/pztrn/fastpastebin/database/migrations"
|
||||
"github.com/pztrn/fastpastebin/pastes"
|
||||
"go.dev.pztrn.name/fastpastebin/domains/dbnotavailable"
|
||||
"go.dev.pztrn.name/fastpastebin/domains/indexpage"
|
||||
"go.dev.pztrn.name/fastpastebin/domains/pastes"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/captcha"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/database"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -53,20 +54,23 @@ func main() {
|
||||
|
||||
// Continue loading.
|
||||
c.LoadConfiguration()
|
||||
c.InitializePost()
|
||||
database.New(c)
|
||||
c.Database.Initialize()
|
||||
migrations.New(c)
|
||||
migrations.Migrate()
|
||||
api.New(c)
|
||||
api.InitializeAPI()
|
||||
templater.Initialize(c)
|
||||
|
||||
captcha.New(c)
|
||||
|
||||
dbnotavailable.New(c)
|
||||
indexpage.New(c)
|
||||
pastes.New(c)
|
||||
|
||||
// CTRL+C handler.
|
||||
signalHandler := make(chan os.Signal, 1)
|
||||
shutdownDone := make(chan bool, 1)
|
||||
|
||||
signal.Notify(signalHandler, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
<-signalHandler
|
||||
c.Shutdown()
|
||||
|
@@ -1,72 +0,0 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"fmt"
|
||||
|
||||
// other
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// Database represents control structure for database connection.
|
||||
type Database struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
// GetDatabaseConnection returns current database connection.
|
||||
func (db *Database) GetDatabaseConnection() *sqlx.DB {
|
||||
return db.db
|
||||
}
|
||||
|
||||
// Initialize initializes connection to database.
|
||||
func (db *Database) Initialize() {
|
||||
c.Logger.Info().Msg("Initializing database connection...")
|
||||
|
||||
// There might be only user, without password. MySQL/MariaDB driver
|
||||
// in DSN wants "user" or "user:password", "user:" is invalid.
|
||||
var userpass = ""
|
||||
if c.Config.Database.Password == "" {
|
||||
userpass = c.Config.Database.Username
|
||||
} else {
|
||||
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, c.Config.Database.Address, c.Config.Database.Port, c.Config.Database.Database)
|
||||
c.Logger.Debug().Msgf("Database connection string: %s", dbConnString)
|
||||
|
||||
dbConn, err := sqlx.Connect("mysql", dbConnString)
|
||||
if err != nil {
|
||||
c.Logger.Panic().Msgf("Failed to connect to database: %s", err.Error())
|
||||
}
|
||||
|
||||
// Force UTC for current connection.
|
||||
_ = dbConn.MustExec("SET @@session.time_zone='+00:00';")
|
||||
|
||||
c.Logger.Info().Msg("Database connection established")
|
||||
db.db = dbConn
|
||||
}
|
37
docker-compose.yml
Normal file
37
docker-compose.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
version: "3"
|
||||
|
||||
volumes:
|
||||
mysql:
|
||||
|
||||
services:
|
||||
database:
|
||||
image: mariadb:10.3
|
||||
container_name: database
|
||||
restart: always
|
||||
volumes:
|
||||
- fastpastebin_mysql:/var/lib/mysql
|
||||
environment:
|
||||
- MYSQL_RANDOM_ROOT_PASSWORD=yes
|
||||
- MYSQL_DATABASE=fastpastebin
|
||||
- MYSQL_USER=fastpastebin
|
||||
- MYSQL_PASSWORD=fastpastebin
|
||||
fastpastebin:
|
||||
build: .
|
||||
image: fastpastebin
|
||||
container_name: fastpastebin
|
||||
restart: always
|
||||
depends_on:
|
||||
- database
|
||||
volumes:
|
||||
- ./docker/fastpastebin.docker.yaml:/app/fastpastebin.yaml:ro
|
||||
# ports:
|
||||
# - 25544:25544
|
||||
web:
|
||||
image: nginx:1.16-alpine
|
||||
container_name: nginx
|
||||
volumes:
|
||||
- ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
ports:
|
||||
- 8080:80
|
||||
depends_on:
|
||||
- fastpastebin
|
22
docker/fastpastebin.docker.yaml
Normal file
22
docker/fastpastebin.docker.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
database:
|
||||
type: "mysql"
|
||||
path: "./data"
|
||||
address: "database"
|
||||
port: "3306"
|
||||
username: "fastpastebin"
|
||||
password: "fastpastebin"
|
||||
database: "fastpastebin"
|
||||
|
||||
logging:
|
||||
log_to_file: false
|
||||
filename: ""
|
||||
# Log level. Acceptable parameters: DEBUG, INFO, WARN, ERROR, FATAL, PANIC.
|
||||
loglevel: "DEBUG"
|
||||
|
||||
http:
|
||||
address: "0.0.0.0"
|
||||
port: "25544"
|
||||
allow_insecure: true
|
||||
|
||||
pastes:
|
||||
pagination: 10
|
10
docker/nginx.conf
Normal file
10
docker/nginx.conf
Normal file
@@ -0,0 +1,10 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
location / {
|
||||
proxy_pass http://fastpastebin:25544;
|
||||
proxy_set_header Host $Host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
7
docker/set_docker_tag.sh
Normal file
7
docker/set_docker_tag.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [[ $CI_BUILD_REF_NAME == "master" ]]; then
|
||||
export DOCKER_TAG=latest;
|
||||
else
|
||||
export DOCKER_TAG="${CI_BUILD_REF_NAME}";
|
||||
fi
|
@@ -22,32 +22,26 @@
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
package api
|
||||
package dbnotavailable
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"net/http"
|
||||
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
||||
|
||||
// other
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
// Logs Echo requests.
|
||||
func echoReqLog(ec echo.Context, next echo.HandlerFunc) error {
|
||||
c.Logger.Info().
|
||||
Str("IP", ec.RealIP()).
|
||||
Str("Host", ec.Request().Host).
|
||||
Str("Method", ec.Request().Method).
|
||||
Str("Path", ec.Request().URL.Path).
|
||||
Str("UA", ec.Request().UserAgent()).
|
||||
Msg("HTTP request")
|
||||
// Database not available error page
|
||||
func dbNotAvailableGet(ec echo.Context) error {
|
||||
htmlData := templater.GetTemplate(ec, "database_not_available.html", nil)
|
||||
|
||||
next(ec)
|
||||
return nil
|
||||
return ec.HTML(http.StatusInternalServerError, htmlData)
|
||||
}
|
||||
|
||||
// Wrapper around previous function.
|
||||
func echoReqLogger() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return echoReqLog(c, next)
|
||||
}
|
||||
}
|
||||
func dbNotAvailableRawGet(ec echo.Context) error {
|
||||
return ec.String(http.StatusInternalServerError, "Database not available\nSomething went wrong while trying to connect to database. Check logs for details.")
|
||||
}
|
@@ -22,30 +22,22 @@
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
package http
|
||||
package dbnotavailable
|
||||
|
||||
import (
|
||||
// local
|
||||
"github.com/pztrn/fastpastebin/api/http/static"
|
||||
"github.com/pztrn/fastpastebin/context"
|
||||
|
||||
// other
|
||||
"github.com/labstack/echo"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||
)
|
||||
|
||||
var (
|
||||
c *context.Context
|
||||
)
|
||||
|
||||
// New initializes basic HTTP API, which shows only index page and serves
|
||||
// static files.
|
||||
// New initializes pastes package and adds necessary HTTP and API
|
||||
// endpoints.
|
||||
func New(cc *context.Context) {
|
||||
c = cc
|
||||
c.Logger.Info().Msg("Initializing HTTP API...")
|
||||
|
||||
// Static files.
|
||||
c.Echo.GET("/static/*", echo.WrapHandler(static.Handler))
|
||||
|
||||
// Index.
|
||||
c.Echo.GET("/", indexGet)
|
||||
c.Echo.GET("/database_not_available", dbNotAvailableGet)
|
||||
c.Echo.GET("/database_not_available/raw", dbNotAvailableRawGet)
|
||||
}
|
@@ -22,19 +22,21 @@
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
package json
|
||||
package indexpage
|
||||
|
||||
import (
|
||||
// local
|
||||
"github.com/pztrn/fastpastebin/context"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||
)
|
||||
|
||||
var (
|
||||
c *context.Context
|
||||
)
|
||||
|
||||
// New initializes basic JSON API.
|
||||
// New initializes pastes package and adds necessary HTTP and API
|
||||
// endpoints.
|
||||
func New(cc *context.Context) {
|
||||
c = cc
|
||||
c.Logger.Info().Msg("Initializing JSON API...")
|
||||
|
||||
c.Echo.GET("/", indexGet)
|
||||
}
|
@@ -22,16 +22,15 @@
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
package http
|
||||
package indexpage
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
// local
|
||||
"github.com/pztrn/fastpastebin/api/http/static"
|
||||
"github.com/pztrn/fastpastebin/captcha"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/captcha"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
||||
|
||||
// other
|
||||
"github.com/alecthomas/chroma/lexers"
|
||||
@@ -40,9 +39,10 @@ import (
|
||||
|
||||
// Index of this site.
|
||||
func indexGet(ec echo.Context) error {
|
||||
htmlRaw, err := static.ReadFile("index.html")
|
||||
if err != nil {
|
||||
return ec.String(http.StatusNotFound, "index.html wasn't found!")
|
||||
// We should check if database connection available.
|
||||
dbConn := c.Database.GetDatabaseConnection()
|
||||
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
||||
return ec.Redirect(http.StatusFound, "/database_not_available")
|
||||
}
|
||||
|
||||
// Generate list of available languages to highlight.
|
||||
@@ -53,11 +53,10 @@ func indexGet(ec echo.Context) error {
|
||||
availableLexersSelectOpts += "<option value='" + availableLexers[i] + "'>" + availableLexers[i] + "</option>"
|
||||
}
|
||||
|
||||
html := strings.Replace(string(htmlRaw), "{lexers}", availableLexersSelectOpts, 1)
|
||||
|
||||
// Captcha.
|
||||
captchaString := captcha.NewCaptcha()
|
||||
html = strings.Replace(html, "{captchaString}", captchaString, -1)
|
||||
|
||||
return ec.HTML(http.StatusOK, html)
|
||||
htmlData := templater.GetTemplate(ec, "index.html", map[string]string{"lexers": availableLexersSelectOpts, "captchaString": captchaString})
|
||||
|
||||
return ec.HTML(http.StatusOK, htmlData)
|
||||
}
|
@@ -25,35 +25,42 @@
|
||||
package pastes
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"regexp"
|
||||
|
||||
// local
|
||||
"github.com/pztrn/fastpastebin/context"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||
)
|
||||
|
||||
var (
|
||||
regexInts = regexp.MustCompile("[0-9]+")
|
||||
)
|
||||
|
||||
var (
|
||||
c *context.Context
|
||||
)
|
||||
|
||||
// New initializes pastes package and adds neccessary HTTP and API
|
||||
// New initializes pastes package and adds necessary HTTP and API
|
||||
// endpoints.
|
||||
func New(cc *context.Context) {
|
||||
c = cc
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// HTTP endpoints.
|
||||
////////////////////////////////////////////////////////////
|
||||
// New paste.
|
||||
c.Echo.POST("/paste/", pastePOST)
|
||||
|
||||
c.Echo.POST("/paste/", pastePOSTWebInterface)
|
||||
// Show public paste.
|
||||
c.Echo.GET("/paste/:id", pasteGET)
|
||||
c.Echo.GET("/paste/:id", pasteGETWebInterface)
|
||||
// Show RAW representation of public paste.
|
||||
c.Echo.GET("/paste/:id/raw", pasteRawGET)
|
||||
|
||||
c.Echo.GET("/paste/:id/raw", pasteRawGETWebInterface)
|
||||
// Show private paste.
|
||||
c.Echo.GET("/paste/:id/:timestamp", pasteGET)
|
||||
c.Echo.GET("/paste/:id/:timestamp", pasteGETWebInterface)
|
||||
// Show RAW representation of private paste.
|
||||
c.Echo.GET("/paste/:id/:timestamp/raw", pasteRawGET)
|
||||
c.Echo.GET("/paste/:id/:timestamp/raw", pasteRawGETWebInterface)
|
||||
// Verify access to passworded paste.
|
||||
c.Echo.GET("/paste/:id/:timestamp/verify", pastePasswordedVerifyGet)
|
||||
c.Echo.POST("/paste/:id/:timestamp/verify", pastePasswordedVerifyPost)
|
||||
|
||||
// Pastes list.
|
||||
c.Echo.GET("/pastes/", pastesGET)
|
||||
c.Echo.GET("/pastes/:page", pastesGET)
|
337
domains/pastes/paste_get.go
Normal file
337
domains/pastes/paste_get.go
Normal file
@@ -0,0 +1,337 @@
|
||||
package pastes
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"bytes"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||
"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 (
|
||||
pasteCookieInvalid = "PASTE_COOKIE_INVALID"
|
||||
pasteExpired = "PASTE_EXPIRED"
|
||||
pasteNotFound = "PASTE_NOT_FOUND"
|
||||
pasteTimestampInvalid = "PASTE_TIMESTAMP_INVALID"
|
||||
)
|
||||
|
||||
// Actual getting paste data and returns it's content without formatting.
|
||||
// This function will return paste's structure and optional error string
|
||||
// that defined in constants above.
|
||||
// Actually required only paste ID, all other parameters are optional
|
||||
// for some cases, e.g. public paste won't check for timestamp and cookie
|
||||
// value (they both will be ignored), but private will.
|
||||
func pasteGetData(pasteID int, timestamp int64, cookieValue string) (*structs.Paste, string) {
|
||||
// Get paste.
|
||||
paste, err1 := c.Database.GetPaste(pasteID)
|
||||
if err1 != nil {
|
||||
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste")
|
||||
return nil, pasteNotFound
|
||||
}
|
||||
|
||||
// Check if paste is expired.
|
||||
if paste.IsExpired() {
|
||||
c.Logger.Error().Int("paste ID", pasteID).Msg("Paste is expired")
|
||||
return nil, pasteExpired
|
||||
}
|
||||
|
||||
// Check if we have a private paste and it's parameters are correct.
|
||||
if paste.Private {
|
||||
pasteTs := paste.CreatedAt.Unix()
|
||||
if timestamp != pasteTs {
|
||||
c.Logger.Error().Int("paste ID", pasteID).Int64("paste timestamp", pasteTs).Int64("provided timestamp", timestamp).Msg("Incorrect timestamp provided for private paste")
|
||||
return nil, pasteTimestampInvalid
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a private paste requested and password for that paste
|
||||
// was defined - check additional things that required to view this
|
||||
// paste.
|
||||
if paste.Private && paste.Password != "" {
|
||||
// Generate cookie value to check.
|
||||
pasteCookieValue := paste.GenerateCryptedCookieValue()
|
||||
|
||||
if cookieValue != pasteCookieValue {
|
||||
return nil, pasteCookieInvalid
|
||||
}
|
||||
}
|
||||
|
||||
return paste, ""
|
||||
}
|
||||
|
||||
// GET for "/paste/PASTE_ID" and "/paste/PASTE_ID/TIMESTAMP" (private pastes).
|
||||
// Web interface version.
|
||||
func pasteGETWebInterface(ec echo.Context) error {
|
||||
pasteIDRaw := ec.Param("id")
|
||||
// We already get numbers from string, so we will not check strconv.Atoi()
|
||||
// error.
|
||||
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
|
||||
pasteIDStr := strconv.Itoa(pasteID)
|
||||
c.Logger.Debug().Int("paste ID", pasteID).Msg("Trying to get paste data")
|
||||
|
||||
// Check if we have timestamp passed.
|
||||
// If passed timestamp is invalid (isn't a real UNIX timestamp) we
|
||||
// will show 404 Not Found error and spam about that in logs.
|
||||
var timestamp int64
|
||||
|
||||
tsProvidedStr := ec.Param("timestamp")
|
||||
if tsProvidedStr != "" {
|
||||
tsProvided, err := strconv.ParseInt(tsProvidedStr, 10, 64)
|
||||
if err != nil {
|
||||
c.Logger.Error().Err(err).Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Msg("Invalid timestamp provided for getting private paste")
|
||||
|
||||
errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDStr+" not found")
|
||||
|
||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||
}
|
||||
|
||||
timestamp = tsProvided
|
||||
}
|
||||
|
||||
// Check if we have "PASTE-PASTEID" cookie defined. It is required
|
||||
// for private pastes.
|
||||
var cookieValue string
|
||||
|
||||
cookie, err1 := ec.Cookie("PASTE-" + pasteIDStr)
|
||||
if err1 == nil {
|
||||
cookieValue = cookie.Value
|
||||
}
|
||||
|
||||
paste, error := pasteGetData(pasteID, timestamp, cookieValue)
|
||||
|
||||
// For these cases we should return 404 Not Found page.
|
||||
if error == pasteExpired || error == pasteNotFound || error == pasteTimestampInvalid {
|
||||
errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDRaw+" not found")
|
||||
return ec.HTML(http.StatusNotFound, errtpl)
|
||||
}
|
||||
|
||||
// If passed cookie value was invalid - go to paste authorization
|
||||
// page.
|
||||
if error == pasteCookieInvalid {
|
||||
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")
|
||||
}
|
||||
|
||||
// Format paste data map.
|
||||
pasteData := make(map[string]string)
|
||||
pasteData["pasteTitle"] = paste.Title
|
||||
pasteData["pasteID"] = strconv.Itoa(paste.ID)
|
||||
pasteData["pasteDate"] = paste.CreatedAt.Format("2006-01-02 @ 15:04:05") + " UTC"
|
||||
pasteData["pasteLanguage"] = paste.Language
|
||||
|
||||
pasteExpirationString := "Never"
|
||||
if paste.KeepFor != 0 && paste.KeepForUnitType != 0 {
|
||||
pasteExpirationString = paste.GetExpirationTime().Format("2006-01-02 @ 15:04:05") + " UTC"
|
||||
}
|
||||
|
||||
pasteData["pasteExpiration"] = pasteExpirationString
|
||||
|
||||
if paste.Private {
|
||||
pasteData["pasteType"] = "<span class='has-text-danger'>Private</span>"
|
||||
pasteData["pasteTs"] = strconv.FormatInt(paste.CreatedAt.Unix(), 10) + "/"
|
||||
} else {
|
||||
pasteData["pasteType"] = "<span class='has-text-success'>Public</span>"
|
||||
pasteData["pasteTs"] = ""
|
||||
}
|
||||
|
||||
// Highlight.
|
||||
// Get lexer.
|
||||
lexer := lexers.Get(paste.Language)
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
// Tokenize paste data.
|
||||
lexered, err3 := lexer.Tokenise(nil, paste.Data)
|
||||
if err3 != nil {
|
||||
c.Logger.Error().Err(err3).Msg("Failed to tokenize paste data")
|
||||
}
|
||||
// Get style for HTML output.
|
||||
style := styles.Get("monokai")
|
||||
if style == nil {
|
||||
style = styles.Fallback
|
||||
}
|
||||
// Get HTML formatter.
|
||||
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.
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
err4 := formatter.Format(buf, style, lexered)
|
||||
if err4 != nil {
|
||||
c.Logger.Error().Err(err4).Msg("Failed to format paste data")
|
||||
}
|
||||
|
||||
pasteData["pastedata"] = buf.String()
|
||||
|
||||
// Get template and format it.
|
||||
pasteHTML := templater.GetTemplate(ec, "paste.html", pasteData)
|
||||
|
||||
return ec.HTML(http.StatusOK, pasteHTML)
|
||||
}
|
||||
|
||||
// GET for "/paste/PASTE_ID/TIMESTAMP/verify" - a password verify page.
|
||||
func pastePasswordedVerifyGet(ec echo.Context) error {
|
||||
pasteIDRaw := ec.Param("id")
|
||||
timestampRaw := ec.Param("timestamp")
|
||||
// We already get numbers from string, so we will not check strconv.Atoi()
|
||||
// error.
|
||||
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
|
||||
|
||||
// Get paste.
|
||||
paste, err1 := c.Database.GetPaste(pasteID)
|
||||
if err1 != nil {
|
||||
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste data")
|
||||
|
||||
errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDRaw+" not found")
|
||||
|
||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||
}
|
||||
|
||||
// Check for auth cookie. If present - redirect to paste.
|
||||
cookie, err := ec.Cookie("PASTE-" + strconv.Itoa(pasteID))
|
||||
if err == nil {
|
||||
// No cookie, redirect to auth page.
|
||||
c.Logger.Debug().Msg("Paste cookie found, checking it...")
|
||||
|
||||
// Generate cookie value to check.
|
||||
cookieValue := paste.GenerateCryptedCookieValue()
|
||||
|
||||
if cookieValue == cookie.Value {
|
||||
c.Logger.Info().Msg("Valid cookie, redirecting to paste page...")
|
||||
return ec.Redirect(http.StatusMovedPermanently, "/paste/"+pasteIDRaw+"/"+ec.Param("timestamp"))
|
||||
}
|
||||
|
||||
c.Logger.Debug().Msg("Invalid cookie, showing auth page")
|
||||
}
|
||||
|
||||
// HTML data.
|
||||
htmlData := make(map[string]string)
|
||||
htmlData["pasteID"] = strconv.Itoa(pasteID)
|
||||
htmlData["pasteTimestamp"] = timestampRaw
|
||||
|
||||
verifyHTML := templater.GetTemplate(ec, "passworded_paste_verify.html", htmlData)
|
||||
|
||||
return ec.HTML(http.StatusOK, verifyHTML)
|
||||
}
|
||||
|
||||
// POST for "/paste/PASTE_ID/TIMESTAMP/verify" - a password verify page.
|
||||
func pastePasswordedVerifyPost(ec echo.Context) error {
|
||||
// We should check if database connection available.
|
||||
dbConn := c.Database.GetDatabaseConnection()
|
||||
|
||||
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
||||
return ec.Redirect(http.StatusFound, "/database_not_available")
|
||||
}
|
||||
|
||||
pasteIDRaw := ec.Param("id")
|
||||
timestampRaw := ec.Param("timestamp")
|
||||
// We already get numbers from string, so we will not check strconv.Atoi()
|
||||
// error.
|
||||
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
|
||||
c.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste")
|
||||
|
||||
// Get paste.
|
||||
paste, err1 := c.Database.GetPaste(pasteID)
|
||||
if err1 != nil {
|
||||
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste")
|
||||
errtpl := templater.GetErrorTemplate(ec, "Paste #"+strconv.Itoa(pasteID)+" not found")
|
||||
|
||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||
}
|
||||
|
||||
params, err2 := ec.FormParams()
|
||||
if err2 != nil {
|
||||
c.Logger.Debug().Msg("No form parameters passed")
|
||||
|
||||
errtpl := templater.GetErrorTemplate(ec, "Paste #"+strconv.Itoa(pasteID)+" not found")
|
||||
|
||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||
}
|
||||
|
||||
if paste.VerifyPassword(params["paste-password"][0]) {
|
||||
// Set cookie that this paste's password is verified and paste
|
||||
// can be viewed.
|
||||
cookie := new(http.Cookie)
|
||||
cookie.Name = "PASTE-" + strconv.Itoa(pasteID)
|
||||
cookie.Value = paste.GenerateCryptedCookieValue()
|
||||
cookie.Expires = time.Now().Add(24 * time.Hour)
|
||||
ec.SetCookie(cookie)
|
||||
|
||||
return ec.Redirect(http.StatusFound, "/paste/"+strconv.Itoa(pasteID)+"/"+timestampRaw)
|
||||
}
|
||||
|
||||
errtpl := templater.GetErrorTemplate(ec, "Invalid password. Please, try again.")
|
||||
|
||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||
}
|
||||
|
||||
// GET for "/pastes/:id/raw", raw paste output.
|
||||
// Web interface version.
|
||||
func pasteRawGETWebInterface(ec echo.Context) error {
|
||||
// We should check if database connection available.
|
||||
dbConn := c.Database.GetDatabaseConnection()
|
||||
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
||||
return ec.Redirect(http.StatusFound, "/database_not_available/raw")
|
||||
}
|
||||
|
||||
pasteIDRaw := ec.Param("id")
|
||||
// We already get numbers from string, so we will not check strconv.Atoi()
|
||||
// error.
|
||||
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
|
||||
c.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste data")
|
||||
|
||||
// Get paste.
|
||||
paste, err1 := c.Database.GetPaste(pasteID)
|
||||
if err1 != nil {
|
||||
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.")
|
||||
}
|
||||
|
||||
if paste.IsExpired() {
|
||||
c.Logger.Error().Int("paste ID", pasteID).Msg("Paste is expired")
|
||||
return ec.HTML(http.StatusBadRequest, "Paste #"+pasteIDRaw+" does not exist.")
|
||||
}
|
||||
|
||||
// Check if we have a private paste and it's parameters are correct.
|
||||
if paste.Private {
|
||||
tsProvidedStr := ec.Param("timestamp")
|
||||
|
||||
tsProvided, err2 := strconv.ParseInt(tsProvidedStr, 10, 64)
|
||||
if err2 != nil {
|
||||
c.Logger.Error().Err(err2).Int("paste ID", pasteID).Str("provided timestamp", tsProvidedStr).Msg("Invalid timestamp provided for getting private paste")
|
||||
|
||||
return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
|
||||
}
|
||||
|
||||
pasteTs := paste.CreatedAt.Unix()
|
||||
if tsProvided != pasteTs {
|
||||
c.Logger.Error().Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Int64("paste timestamp", pasteTs).Msg("Incorrect timestamp provided for private paste")
|
||||
|
||||
return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
|
||||
}
|
||||
}
|
||||
|
||||
// nolint
|
||||
// ToDo: figure out how to handle passworded pastes here.
|
||||
// Return error for now.
|
||||
if paste.Password != "" {
|
||||
c.Logger.Error().Int("paste ID", pasteID).Msg("Cannot render paste as raw: passworded paste. Patches welcome!")
|
||||
return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
|
||||
}
|
||||
|
||||
return ec.String(http.StatusOK, paste.Data)
|
||||
}
|
153
domains/pastes/paste_post.go
Normal file
153
domains/pastes/paste_post.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package pastes
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/internal/captcha"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
||||
|
||||
// other
|
||||
"github.com/alecthomas/chroma/lexers"
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
// POST for "/paste/" which will create new paste and redirect to
|
||||
// "/pastes/CREATED_PASTE_ID". This handler will do all the job for
|
||||
// requests comes from browsers via web interface.
|
||||
func pastePOSTWebInterface(ec echo.Context) error {
|
||||
// We should check if database connection available.
|
||||
dbConn := c.Database.GetDatabaseConnection()
|
||||
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
||||
return ec.Redirect(http.StatusFound, "/database_not_available")
|
||||
}
|
||||
|
||||
params, err := ec.FormParams()
|
||||
if err != nil {
|
||||
c.Logger.Error().Msg("Passed paste form is empty")
|
||||
|
||||
errtpl := templater.GetErrorTemplate(ec, "Cannot create empty paste")
|
||||
|
||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||
}
|
||||
|
||||
c.Logger.Debug().Msgf("Received parameters: %+v", params)
|
||||
|
||||
// Do nothing if paste contents is empty.
|
||||
if len(params["paste-contents"][0]) == 0 {
|
||||
c.Logger.Debug().Msg("Empty paste submitted, ignoring")
|
||||
|
||||
errtpl := templater.GetErrorTemplate(ec, "Empty pastes aren't allowed.")
|
||||
|
||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||
}
|
||||
|
||||
if !strings.ContainsAny(params["paste-keep-for"][0], "Mmhd") && params["paste-keep-for"][0] != "forever" {
|
||||
c.Logger.Debug().Str("field value", params["paste-keep-for"][0]).Msg("'Keep paste for' field have invalid value")
|
||||
|
||||
errtpl := templater.GetErrorTemplate(ec, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).")
|
||||
|
||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||
}
|
||||
|
||||
// Verify captcha.
|
||||
if !captcha.Verify(params["paste-captcha-id"][0], params["paste-captcha-solution"][0]) {
|
||||
c.Logger.Debug().Str("captcha ID", params["paste-captcha-id"][0]).Str("captcha solution", params["paste-captcha-solution"][0]).Msg("Invalid captcha solution")
|
||||
|
||||
errtpl := templater.GetErrorTemplate(ec, "Invalid captcha solution.")
|
||||
|
||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||
}
|
||||
|
||||
paste := &structs.Paste{
|
||||
Title: params["paste-title"][0],
|
||||
Data: params["paste-contents"][0],
|
||||
Language: params["paste-language"][0],
|
||||
}
|
||||
|
||||
// Paste creation time in UTC.
|
||||
createdAt := time.Now().UTC()
|
||||
paste.CreatedAt = &createdAt
|
||||
|
||||
// Parse "keep for" field.
|
||||
// Defaulting to "forever".
|
||||
keepFor := 0
|
||||
keepForUnit := 0
|
||||
|
||||
if params["paste-keep-for"][0] != "forever" {
|
||||
keepForUnitRegex := regexp.MustCompile("[Mmhd]")
|
||||
|
||||
keepForRaw := regexInts.FindAllString(params["paste-keep-for"][0], 1)[0]
|
||||
|
||||
var err error
|
||||
|
||||
keepFor, err = strconv.Atoi(keepForRaw)
|
||||
if err != nil {
|
||||
if params["paste-keep-for"][0] == "forever" {
|
||||
c.Logger.Debug().Msg("Keeping paste forever!")
|
||||
|
||||
keepFor = 0
|
||||
} else {
|
||||
c.Logger.Debug().Err(err).Msg("Failed to parse 'Keep for' integer")
|
||||
|
||||
errtpl := templater.GetErrorTemplate(ec, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).")
|
||||
|
||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||
}
|
||||
}
|
||||
|
||||
keepForUnitRaw := keepForUnitRegex.FindAllString(params["paste-keep-for"][0], 1)[0]
|
||||
keepForUnit = structs.PasteKeepsCorrelation[keepForUnitRaw]
|
||||
}
|
||||
|
||||
paste.KeepFor = keepFor
|
||||
paste.KeepForUnitType = keepForUnit
|
||||
|
||||
// Try to autodetect if it was selected.
|
||||
if params["paste-language"][0] == "autodetect" {
|
||||
lexer := lexers.Analyse(params["paste-language"][0])
|
||||
if lexer != nil {
|
||||
paste.Language = lexer.Config().Name
|
||||
} else {
|
||||
paste.Language = "text"
|
||||
}
|
||||
}
|
||||
|
||||
// Private paste?
|
||||
paste.Private = false
|
||||
privateCheckbox, privateCheckboxFound := params["paste-private"]
|
||||
pastePassword, pastePasswordFound := params["paste-password"]
|
||||
|
||||
if privateCheckboxFound && privateCheckbox[0] == "on" || pastePasswordFound && pastePassword[0] != "" {
|
||||
paste.Private = true
|
||||
}
|
||||
|
||||
if pastePassword[0] != "" {
|
||||
_ = paste.CreatePassword(pastePassword[0])
|
||||
}
|
||||
|
||||
id, err2 := c.Database.SavePaste(paste)
|
||||
if err2 != nil {
|
||||
c.Logger.Error().Err(err2).Msg("Failed to save paste")
|
||||
|
||||
errtpl := templater.GetErrorTemplate(ec, "Failed to save paste. Please, try again later.")
|
||||
|
||||
return ec.HTML(http.StatusBadRequest, errtpl)
|
||||
}
|
||||
|
||||
newPasteIDAsString := strconv.FormatInt(id, 10)
|
||||
c.Logger.Debug().Msg("Paste saved, URL: /paste/" + newPasteIDAsString)
|
||||
|
||||
// Private pastes have it's timestamp in URL.
|
||||
if paste.Private {
|
||||
return ec.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString+"/"+strconv.FormatInt(paste.CreatedAt.Unix(), 10))
|
||||
}
|
||||
|
||||
return ec.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString)
|
||||
}
|
111
domains/pastes/pastes_get.go
Normal file
111
domains/pastes/pastes_get.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package pastes
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/internal/pagination"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/templater"
|
||||
|
||||
// other
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
// GET for "/pastes/", a list of publicly available pastes.
|
||||
// Web interface version.
|
||||
func pastesGET(ec echo.Context) error {
|
||||
// We should check if database connection available.
|
||||
dbConn := c.Database.GetDatabaseConnection()
|
||||
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
|
||||
return ec.Redirect(http.StatusFound, "/database_not_available")
|
||||
}
|
||||
|
||||
pageFromParamRaw := ec.Param("page")
|
||||
|
||||
var page = 1
|
||||
|
||||
if pageFromParamRaw != "" {
|
||||
pageRaw := regexInts.FindAllString(pageFromParamRaw, 1)[0]
|
||||
page, _ = strconv.Atoi(pageRaw)
|
||||
}
|
||||
|
||||
c.Logger.Debug().Int("page", page).Msg("Requested page")
|
||||
|
||||
// Get pastes IDs.
|
||||
pastes, err3 := c.Database.GetPagedPastes(page)
|
||||
c.Logger.Debug().Int("count", len(pastes)).Msg("Got pastes")
|
||||
|
||||
var pastesString = "No pastes to show."
|
||||
|
||||
// Show "No pastes to show" on any error for now.
|
||||
if err3 != nil {
|
||||
c.Logger.Error().Err(err3).Msg("Failed to get pastes list from database")
|
||||
|
||||
noPastesToShowTpl := templater.GetErrorTemplate(ec, "No pastes to show.")
|
||||
|
||||
return ec.HTML(http.StatusOK, noPastesToShowTpl)
|
||||
}
|
||||
|
||||
if len(pastes) > 0 {
|
||||
pastesString = ""
|
||||
|
||||
for i := range pastes {
|
||||
pasteDataMap := make(map[string]string)
|
||||
pasteDataMap["pasteID"] = strconv.Itoa(pastes[i].ID)
|
||||
pasteDataMap["pasteTitle"] = pastes[i].Title
|
||||
pasteDataMap["pasteDate"] = pastes[i].CreatedAt.Format("2006-01-02 @ 15:04:05") + " UTC"
|
||||
|
||||
// Get max 4 lines of each paste.
|
||||
pasteDataSplitted := strings.Split(pastes[i].Data, "\n")
|
||||
|
||||
var pasteData string
|
||||
|
||||
if len(pasteDataSplitted) < 4 {
|
||||
pasteData = pastes[i].Data
|
||||
} else {
|
||||
pasteData = strings.Join(pasteDataSplitted[0:4], "\n")
|
||||
}
|
||||
|
||||
pasteDataMap["pasteData"] = pasteData
|
||||
pasteTpl := templater.GetRawTemplate(ec, "pastelist_paste.html", pasteDataMap)
|
||||
|
||||
pastesString += pasteTpl
|
||||
}
|
||||
}
|
||||
|
||||
// Pagination.
|
||||
pages := c.Database.GetPastesPages()
|
||||
c.Logger.Debug().Int("total pages", pages).Int("current page", page).Msg("Paging data")
|
||||
paginationHTML := pagination.CreateHTML(page, pages, "/pastes/")
|
||||
|
||||
pasteListTpl := templater.GetTemplate(ec, "pastelist_list.html", map[string]string{"pastes": pastesString, "pagination": paginationHTML})
|
||||
|
||||
return ec.HTML(http.StatusOK, pasteListTpl)
|
||||
}
|
@@ -1,6 +1,13 @@
|
||||
# Database configuration.
|
||||
# Only MySQL database is supported for now.
|
||||
# Only MySQL database and flatfiles are supported for now.
|
||||
database:
|
||||
# Database type. The only supported ATM is "mysql" and "flatfiles".
|
||||
type: "flatfiles"
|
||||
# Path for data stored with "flatfiles" database adapter.
|
||||
# Will be completely ignored for MySQL/MariaDB.
|
||||
path: "./data"
|
||||
# Next parameters are strictly for MySQL/MariaDB connections and
|
||||
# will be ignored by "flatfiles" adapter.
|
||||
address: "localhost"
|
||||
port: "3306"
|
||||
username: "fastpastebin"
|
||||
@@ -18,9 +25,16 @@ logging:
|
||||
|
||||
# HTTP server configuration.
|
||||
http:
|
||||
address: "localhost"
|
||||
address: "127.0.0.1"
|
||||
port: "25544"
|
||||
# By default we're allowing only HTTPS requests. Setting this to true
|
||||
# will allow HTTP requests. Useful for developing or if you're
|
||||
# running Fast Pastebin behind reverse proxy that does SSL termination.
|
||||
allow_insecure: false
|
||||
allow_insecure: true
|
||||
# Maximum body size in megabytes. 1 should be enough for most use cases.
|
||||
max_body_size_megabytes: 1
|
||||
|
||||
# Pastes configuration.
|
||||
pastes:
|
||||
# Pastes per page.
|
||||
pagination: 10
|
10
fileb0x.yml
10
fileb0x.yml
@@ -5,7 +5,7 @@
|
||||
pkg: static
|
||||
|
||||
# destination
|
||||
dest: "./api/http/static/"
|
||||
dest: "./assets/static/"
|
||||
|
||||
# gofmt
|
||||
# type: bool
|
||||
@@ -79,8 +79,8 @@ custom:
|
||||
|
||||
# type: array of strings
|
||||
- files:
|
||||
- "assets/css/bulma-0.7.0.min.css"
|
||||
- "assets/css/bulma-tooltip-1.0.4.min.css"
|
||||
- "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"
|
||||
@@ -94,8 +94,12 @@ custom:
|
||||
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"
|
||||
|
21
go.mod
Normal file
21
go.mod
Normal file
@@ -0,0 +1,21 @@
|
||||
module go.dev.pztrn.name/fastpastebin
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/alecthomas/chroma v0.8.2
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/jmoiron/sqlx v1.2.0
|
||||
github.com/labstack/echo v3.3.10+incompatible
|
||||
github.com/labstack/gommon v0.3.0 // indirect
|
||||
github.com/lib/pq v1.9.0
|
||||
github.com/pressly/goose v2.6.0+incompatible
|
||||
github.com/rs/zerolog v1.20.0
|
||||
go.dev.pztrn.name/flagger v0.0.0-20200617193309-89bc9818b76c
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
107
go.sum
Normal file
107
go.sum
Normal file
@@ -0,0 +1,107 @@
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||
github.com/alecthomas/chroma v0.8.2 h1:x3zkuE2lUk/RIekyAJ3XRqSCP4zwWDfcw/YJCuCAACg=
|
||||
github.com/alecthomas/chroma v0.8.2/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
|
||||
github.com/dchest/captcha 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/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/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
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/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
|
||||
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
|
||||
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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pressly/goose v2.6.0+incompatible h1:3f8zIQ8rfgP9tyI0Hmcs2YNAqUCL1c+diLe3iU8Qd/k=
|
||||
github.com/pressly/goose v2.6.0+incompatible/go.mod h1:m+QHWCqxR3k8D9l7qfzuC/djtlfzxr34mozWDYEu1z8=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
|
||||
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
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/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
go.dev.pztrn.name/flagger v0.0.0-20200617193309-89bc9818b76c h1:+GgFefaTLsYDS0lXc8LNzTo6tcsA9qO3EkTAKduPAsI=
|
||||
go.dev.pztrn.name/flagger v0.0.0-20200617193309-89bc9818b76c/go.mod h1:ttPExQNCubgqqO5Y19LfIBKqmWtBocY7P9MXQEECuZo=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
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/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
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-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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
@@ -25,22 +25,25 @@
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo"
|
||||
// local
|
||||
"github.com/pztrn/fastpastebin/context"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||
|
||||
// other
|
||||
"github.com/dchest/captcha"
|
||||
"github.com/labstack/echo"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
var (
|
||||
c *context.Context
|
||||
c *context.Context
|
||||
log zerolog.Logger
|
||||
)
|
||||
|
||||
// New initializes captcha package and adds neccessary HTTP and API
|
||||
// New initializes captcha package and adds necessary HTTP and API
|
||||
// endpoints.
|
||||
func New(cc *context.Context) {
|
||||
c = cc
|
||||
log = c.Logger.With().Str("type", "internal").Str("package", "captcha").Logger()
|
||||
|
||||
// New paste.
|
||||
c.Echo.GET("/captcha/:id.png", echo.WrapHandler(captcha.Server(captcha.StdWidth, captcha.StdHeight)))
|
||||
@@ -49,7 +52,8 @@ func New(cc *context.Context) {
|
||||
// NewCaptcha creates new captcha string.
|
||||
func NewCaptcha() string {
|
||||
s := captcha.New()
|
||||
c.Logger.Debug().Msgf("Created new captcha string: %s", s)
|
||||
log.Debug().Str("captcha string", s).Msg("Created new captcha string")
|
||||
|
||||
return s
|
||||
}
|
||||
|
@@ -24,8 +24,10 @@
|
||||
|
||||
package config
|
||||
|
||||
// ConfigDatabase describes database configuration.
|
||||
type ConfigDatabase struct {
|
||||
// Database describes database configuration.
|
||||
type Database struct {
|
||||
Type string `yaml:"type"`
|
||||
Path string `yaml:"path"`
|
||||
Address string `yaml:"address"`
|
||||
Port string `yaml:"port"`
|
||||
Username string `yaml:"username"`
|
33
internal/config/http.go
Normal file
33
internal/config/http.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package config
|
||||
|
||||
// HTTP describes HTTP server configuration.
|
||||
type HTTP struct {
|
||||
Address string `yaml:"address"`
|
||||
Port string `yaml:"port"`
|
||||
AllowInsecure bool `yaml:"allow_insecure"`
|
||||
MaxBodySizeMegabytes string `yaml:"max_body_size_megabytes"`
|
||||
}
|
@@ -24,8 +24,8 @@
|
||||
|
||||
package config
|
||||
|
||||
// ConfigLogging describes logger configuration.
|
||||
type ConfigLogging struct {
|
||||
// Logging describes logger configuration.
|
||||
type Logging struct {
|
||||
LogToFile bool `yaml:"log_to_file"`
|
||||
FileName string `yaml:"filename"`
|
||||
LogLevel string `yaml:"loglevel"`
|
@@ -24,9 +24,7 @@
|
||||
|
||||
package config
|
||||
|
||||
// ConfigHTTP describes HTTP server configuration.
|
||||
type ConfigHTTP struct {
|
||||
Address string `yaml:"address"`
|
||||
Port string `yaml:"port"`
|
||||
AllowInsecure bool `yaml:"allow_insecure"`
|
||||
// Pastes describes pastes subsystem configuration.
|
||||
type Pastes struct {
|
||||
Pagination int `yaml:"pagination"`
|
||||
}
|
@@ -24,9 +24,10 @@
|
||||
|
||||
package config
|
||||
|
||||
// ConfigStruct describes whole configuration.
|
||||
type ConfigStruct struct {
|
||||
Database ConfigDatabase `yaml:"database"`
|
||||
Logging ConfigLogging `yaml:"logging"`
|
||||
HTTP ConfigHTTP `yaml:"http"`
|
||||
// Struct describes whole configuration.
|
||||
type Struct struct {
|
||||
Database Database `yaml:"database"`
|
||||
Logging Logging `yaml:"logging"`
|
||||
HTTP HTTP `yaml:"http"`
|
||||
Pastes Pastes `yaml:"pastes"`
|
||||
}
|
@@ -31,13 +31,13 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
// local
|
||||
"github.com/pztrn/fastpastebin/config"
|
||||
"github.com/pztrn/fastpastebin/database/interface"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/config"
|
||||
databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface"
|
||||
|
||||
// other
|
||||
"github.com/labstack/echo"
|
||||
"github.com/pztrn/flagger"
|
||||
"github.com/rs/zerolog"
|
||||
"go.dev.pztrn.name/flagger"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
@@ -46,7 +46,7 @@ import (
|
||||
// contains everything every part of application need, like configuration
|
||||
// access, logger, etc.
|
||||
type Context struct {
|
||||
Config *config.ConfigStruct
|
||||
Config *config.Struct
|
||||
Database databaseinterface.Interface
|
||||
Echo *echo.Echo
|
||||
Flagger *flagger.Flagger
|
||||
@@ -55,12 +55,12 @@ type Context struct {
|
||||
|
||||
// Initialize initializes context.
|
||||
func (c *Context) Initialize() {
|
||||
c.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger()
|
||||
c.initializeLogger()
|
||||
|
||||
c.Flagger = flagger.New(nil)
|
||||
c.Flagger = flagger.New("fastpastebin", nil)
|
||||
c.Flagger.Initialize()
|
||||
|
||||
c.Flagger.AddFlag(&flagger.Flag{
|
||||
_ = 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",
|
||||
@@ -68,13 +68,19 @@ func (c *Context) Initialize() {
|
||||
})
|
||||
}
|
||||
|
||||
// InitializePost initializes everything that needs a configuration.
|
||||
func (c *Context) InitializePost() {
|
||||
c.initializeLoggerPost()
|
||||
c.initializeHTTPServer()
|
||||
}
|
||||
|
||||
// LoadConfiguration loads configuration and executes right after Flagger
|
||||
// have parsed CLI flags, because it depends on "-config" defined in
|
||||
// Initialize().
|
||||
func (c *Context) LoadConfiguration() {
|
||||
c.Logger.Info().Msg("Loading configuration...")
|
||||
|
||||
var configPath = ""
|
||||
configPath := ""
|
||||
|
||||
// We're accepting configuration path from "-config" CLI parameter
|
||||
// and FASTPASTEBIN_CONFIG environment variable. Later have higher
|
||||
@@ -93,23 +99,23 @@ func (c *Context) LoadConfiguration() {
|
||||
// Normalize file path.
|
||||
normalizedConfigPath, err1 := filepath.Abs(configPath)
|
||||
if err1 != nil {
|
||||
c.Logger.Fatal().Msgf("Failed to normalize path to configuration file: %s", err1.Error())
|
||||
c.Logger.Fatal().Err(err1).Msg("Failed to normalize path to configuration file")
|
||||
}
|
||||
|
||||
c.Logger.Debug().Msgf("Configuration file path: %s", configPath)
|
||||
c.Logger.Debug().Str("path", configPath).Msg("Configuration file path")
|
||||
|
||||
c.Config = &config.ConfigStruct{}
|
||||
c.Config = &config.Struct{}
|
||||
|
||||
// Read configuration file.
|
||||
fileData, err2 := ioutil.ReadFile(normalizedConfigPath)
|
||||
if err2 != nil {
|
||||
c.Logger.Panic().Msgf("Failed to read configuration file: %s", err2.Error())
|
||||
c.Logger.Panic().Err(err2).Msg("Failed to read configuration file")
|
||||
}
|
||||
|
||||
// Parse it into structure.
|
||||
err3 := yaml.Unmarshal(fileData, c.Config)
|
||||
if err3 != nil {
|
||||
c.Logger.Panic().Msgf("Failed to parse configuration file: %s", err3.Error())
|
||||
c.Logger.Panic().Err(err3).Msg("Failed to parse configuration file")
|
||||
}
|
||||
|
||||
// Yay! See what it gets!
|
||||
@@ -129,4 +135,6 @@ func (c *Context) RegisterEcho(e *echo.Echo) {
|
||||
// Shutdown shutdowns entire application.
|
||||
func (c *Context) Shutdown() {
|
||||
c.Logger.Info().Msg("Shutting down Fast Pastebin...")
|
||||
|
||||
c.Database.Shutdown()
|
||||
}
|
@@ -24,6 +24,11 @@
|
||||
|
||||
package context
|
||||
|
||||
const (
|
||||
// Version .
|
||||
Version = "0.4.0"
|
||||
)
|
||||
|
||||
// New creates new context.
|
||||
func New() *Context {
|
||||
return &Context{}
|
47
internal/context/http_server.go
Normal file
47
internal/context/http_server.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/assets/static"
|
||||
|
||||
// other
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
func (c *Context) initializeHTTPServer() {
|
||||
c.Echo = echo.New()
|
||||
c.Echo.Use(c.echoReqLogger())
|
||||
c.Echo.Use(middleware.Recover())
|
||||
c.Echo.Use(middleware.BodyLimit(c.Config.HTTP.MaxBodySizeMegabytes + "M"))
|
||||
c.Echo.DisableHTTP2 = true
|
||||
c.Echo.HideBanner = true
|
||||
c.Echo.HidePort = true
|
||||
|
||||
// Static files.
|
||||
c.Echo.GET("/static/*", echo.WrapHandler(static.Handler))
|
||||
|
||||
listenAddress := c.Config.HTTP.Address + ":" + c.Config.HTTP.Port
|
||||
|
||||
go func() {
|
||||
c.Echo.Logger.Fatal(c.Echo.Start(listenAddress))
|
||||
}()
|
||||
c.Logger.Info().Str("address", listenAddress).Msg("Started HTTP server")
|
||||
}
|
||||
|
||||
// Wrapper around previous function.
|
||||
func (c *Context) echoReqLogger() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(ec echo.Context) error {
|
||||
c.Logger.Info().
|
||||
Str("IP", ec.RealIP()).
|
||||
Str("Host", ec.Request().Host).
|
||||
Str("Method", ec.Request().Method).
|
||||
Str("Path", ec.Request().URL.Path).
|
||||
Str("UA", ec.Request().UserAgent()).
|
||||
Msg("HTTP request")
|
||||
|
||||
return next(ec)
|
||||
}
|
||||
}
|
||||
}
|
80
internal/context/logger.go
Normal file
80
internal/context/logger.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
// other
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// Puts memory usage into log lines.
|
||||
func (c *Context) getMemoryUsage(e *zerolog.Event, level zerolog.Level, message string) {
|
||||
var m runtime.MemStats
|
||||
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
e.Str("memalloc", fmt.Sprintf("%dMB", m.Alloc/1024/1024))
|
||||
e.Str("memsys", fmt.Sprintf("%dMB", m.Sys/1024/1024))
|
||||
e.Str("numgc", fmt.Sprintf("%d", m.NumGC))
|
||||
}
|
||||
|
||||
// Initializes logger.
|
||||
func (c *Context) initializeLogger() {
|
||||
// Устанавливаем форматирование логгера.
|
||||
output := zerolog.ConsoleWriter{Out: os.Stdout, NoColor: false, TimeFormat: time.RFC3339}
|
||||
output.FormatLevel = func(i interface{}) string {
|
||||
var v string
|
||||
|
||||
if ii, ok := i.(string); ok {
|
||||
ii = strings.ToUpper(ii)
|
||||
switch ii {
|
||||
case "DEBUG":
|
||||
v = fmt.Sprintf("\x1b[30m%-5s\x1b[0m", ii)
|
||||
case "ERROR":
|
||||
v = fmt.Sprintf("\x1b[31m%-5s\x1b[0m", ii)
|
||||
case "FATAL":
|
||||
v = fmt.Sprintf("\x1b[35m%-5s\x1b[0m", ii)
|
||||
case "INFO":
|
||||
v = fmt.Sprintf("\x1b[32m%-5s\x1b[0m", ii)
|
||||
case "PANIC":
|
||||
v = fmt.Sprintf("\x1b[36m%-5s\x1b[0m", ii)
|
||||
case "WARN":
|
||||
v = fmt.Sprintf("\x1b[33m%-5s\x1b[0m", ii)
|
||||
default:
|
||||
v = ii
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("| %s |", v)
|
||||
}
|
||||
|
||||
c.Logger = zerolog.New(output).With().Timestamp().Logger()
|
||||
|
||||
c.Logger = c.Logger.Hook(zerolog.HookFunc(c.getMemoryUsage))
|
||||
}
|
||||
|
||||
// Initialize logger after configuration parse.
|
||||
func (c *Context) initializeLoggerPost() {
|
||||
// Set log level.
|
||||
c.Logger.Info().Msgf("Setting logger level: %s", c.Config.Logging.LogLevel)
|
||||
|
||||
switch c.Config.Logging.LogLevel {
|
||||
case "DEBUG":
|
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||
case "INFO":
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
case "WARN":
|
||||
zerolog.SetGlobalLevel(zerolog.WarnLevel)
|
||||
case "ERROR":
|
||||
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
||||
case "FATAL":
|
||||
zerolog.SetGlobalLevel(zerolog.FatalLevel)
|
||||
case "PANIC":
|
||||
zerolog.SetGlobalLevel(zerolog.PanicLevel)
|
||||
}
|
||||
}
|
136
internal/database/database.go
Normal file
136
internal/database/database.go
Normal file
@@ -0,0 +1,136 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles"
|
||||
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/postgresql"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||
|
||||
// other
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
// Database represents control structure for database connection.
|
||||
type Database struct {
|
||||
db dialectinterface.Interface
|
||||
}
|
||||
|
||||
// Database cleanup function. Executes once per hour, hardcoded for now and is
|
||||
// a subject of change in future.
|
||||
func (db *Database) cleanup() {
|
||||
for {
|
||||
c.Logger.Info().Msg("Starting pastes cleanup procedure...")
|
||||
|
||||
pages := db.db.GetPastesPages()
|
||||
|
||||
var pasteIDsToRemove []int
|
||||
|
||||
for i := 0; i < pages; i++ {
|
||||
pastes, err := db.db.GetPagedPastes(i)
|
||||
if err != nil {
|
||||
c.Logger.Error().Err(err).Int("page", i).Msg("Failed to perform database cleanup")
|
||||
}
|
||||
|
||||
for _, paste := range pastes {
|
||||
if paste.IsExpired() {
|
||||
pasteIDsToRemove = append(pasteIDsToRemove, paste.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, pasteID := range pasteIDsToRemove {
|
||||
err := db.DeletePaste(pasteID)
|
||||
if err != nil {
|
||||
c.Logger.Error().Err(err).Int("paste", pasteID).Msg("Failed to delete paste!")
|
||||
}
|
||||
}
|
||||
|
||||
c.Logger.Info().Msg("Pastes cleanup done.")
|
||||
|
||||
time.Sleep(time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) DeletePaste(pasteID int) error {
|
||||
return db.db.DeletePaste(pasteID)
|
||||
}
|
||||
|
||||
func (db *Database) GetDatabaseConnection() *sql.DB {
|
||||
if db.db != nil {
|
||||
return db.db.GetDatabaseConnection()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||
return db.db.GetPaste(pasteID)
|
||||
}
|
||||
|
||||
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||
return db.db.GetPagedPastes(page)
|
||||
}
|
||||
|
||||
func (db *Database) GetPastesPages() int {
|
||||
return db.db.GetPastesPages()
|
||||
}
|
||||
|
||||
// Initialize initializes connection to database.
|
||||
func (db *Database) Initialize() {
|
||||
c.Logger.Info().Msg("Initializing database connection...")
|
||||
|
||||
if c.Config.Database.Type == "mysql" {
|
||||
mysql.New(c)
|
||||
} else if c.Config.Database.Type == "flatfiles" {
|
||||
flatfiles.New(c)
|
||||
} else if c.Config.Database.Type == "postgresql" {
|
||||
postgresql.New(c)
|
||||
} else {
|
||||
c.Logger.Fatal().Str("type", c.Config.Database.Type).Msg("Unknown database type")
|
||||
}
|
||||
|
||||
go db.cleanup()
|
||||
}
|
||||
|
||||
func (db *Database) RegisterDialect(di dialectinterface.Interface) {
|
||||
db.db = di
|
||||
db.db.Initialize()
|
||||
}
|
||||
|
||||
func (db *Database) SavePaste(p *structs.Paste) (int64, error) {
|
||||
return db.db.SavePaste(p)
|
||||
}
|
||||
|
||||
func (db *Database) Shutdown() {
|
||||
db.db.Shutdown()
|
||||
}
|
43
internal/database/dialects/flatfiles/exported.go
Normal file
43
internal/database/dialects/flatfiles/exported.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package flatfiles
|
||||
|
||||
import (
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
||||
)
|
||||
|
||||
var (
|
||||
c *context.Context
|
||||
f *FlatFiles
|
||||
)
|
||||
|
||||
func New(cc *context.Context) {
|
||||
c = cc
|
||||
f = &FlatFiles{}
|
||||
|
||||
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
||||
}
|
289
internal/database/dialects/flatfiles/flatfiles.go
Normal file
289
internal/database/dialects/flatfiles/flatfiles.go
Normal file
@@ -0,0 +1,289 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package flatfiles
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||
)
|
||||
|
||||
type FlatFiles struct {
|
||||
path string
|
||||
pastesIndex []Index
|
||||
writeMutex sync.Mutex
|
||||
}
|
||||
|
||||
// DeletePaste deletes paste from disk and index.
|
||||
func (ff *FlatFiles) DeletePaste(pasteID int) error {
|
||||
// Delete from disk.
|
||||
err := os.Remove(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"))
|
||||
if err != nil {
|
||||
c.Logger.Error().Err(err).Msg("Failed to delete paste!")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete from index.
|
||||
ff.writeMutex.Lock()
|
||||
defer ff.writeMutex.Unlock()
|
||||
|
||||
pasteIndex := -1
|
||||
|
||||
for idx, paste := range ff.pastesIndex {
|
||||
if paste.ID == pasteID {
|
||||
pasteIndex = idx
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if pasteIndex != -1 {
|
||||
ff.pastesIndex = append(ff.pastesIndex[:pasteIndex], ff.pastesIndex[pasteIndex+1:]...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ff *FlatFiles) GetDatabaseConnection() *sql.DB {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ff *FlatFiles) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||
ff.writeMutex.Lock()
|
||||
pastePath := filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json")
|
||||
c.Logger.Debug().Str("path", pastePath).Msg("Trying to load paste data")
|
||||
|
||||
pasteInBytes, err := ioutil.ReadFile(pastePath)
|
||||
if err != nil {
|
||||
c.Logger.Debug().Err(err).Msg("Failed to read paste from storage")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.Logger.Debug().Int("paste bytes", len(pasteInBytes)).Msg("Loaded paste")
|
||||
ff.writeMutex.Unlock()
|
||||
|
||||
paste := &structs.Paste{}
|
||||
|
||||
err1 := json.Unmarshal(pasteInBytes, paste)
|
||||
if err1 != nil {
|
||||
c.Logger.Error().Err(err1).Msgf("Failed to parse paste")
|
||||
return nil, err1
|
||||
}
|
||||
|
||||
return paste, nil
|
||||
}
|
||||
|
||||
func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||
// Pagination.
|
||||
startPagination := 0
|
||||
if page > 1 {
|
||||
startPagination = (page - 1) * c.Config.Pastes.Pagination
|
||||
}
|
||||
|
||||
// Iteration one - get only public pastes.
|
||||
var publicPastes []Index
|
||||
|
||||
for _, paste := range ff.pastesIndex {
|
||||
if !paste.Private {
|
||||
publicPastes = append(publicPastes, paste)
|
||||
}
|
||||
}
|
||||
|
||||
// Iteration two - get paginated pastes.
|
||||
pastesData := make([]structs.Paste, 0)
|
||||
|
||||
for idx, paste := range publicPastes {
|
||||
if len(pastesData) == c.Config.Pastes.Pagination {
|
||||
break
|
||||
}
|
||||
|
||||
if idx < startPagination {
|
||||
c.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too low index")
|
||||
continue
|
||||
}
|
||||
|
||||
if (idx-1 >= startPagination && page > 1 && idx > startPagination+((page-1)*c.Config.Pastes.Pagination)) || (idx-1 >= startPagination && page == 1 && idx > startPagination+(page*c.Config.Pastes.Pagination)) {
|
||||
c.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too high index")
|
||||
break
|
||||
}
|
||||
|
||||
c.Logger.Debug().Int("ID", paste.ID).Int("index", idx).Msg("Getting paste data")
|
||||
|
||||
// Get paste data.
|
||||
pasteData := &structs.Paste{}
|
||||
|
||||
pasteRawData, err := ioutil.ReadFile(filepath.Join(ff.path, "pastes", strconv.Itoa(paste.ID)+".json"))
|
||||
if err != nil {
|
||||
c.Logger.Error().Err(err).Msg("Failed to read paste data")
|
||||
continue
|
||||
}
|
||||
|
||||
err1 := json.Unmarshal(pasteRawData, pasteData)
|
||||
if err1 != nil {
|
||||
c.Logger.Error().Err(err1).Msg("Failed to parse paste data")
|
||||
continue
|
||||
}
|
||||
|
||||
pastesData = append(pastesData, (*pasteData))
|
||||
}
|
||||
|
||||
return pastesData, nil
|
||||
}
|
||||
|
||||
func (ff *FlatFiles) GetPastesPages() int {
|
||||
// Get public pastes count.
|
||||
var publicPastes []Index
|
||||
|
||||
ff.writeMutex.Lock()
|
||||
for _, paste := range ff.pastesIndex {
|
||||
if !paste.Private {
|
||||
publicPastes = append(publicPastes, paste)
|
||||
}
|
||||
}
|
||||
ff.writeMutex.Unlock()
|
||||
|
||||
// Calculate pages.
|
||||
pages := len(publicPastes) / c.Config.Pastes.Pagination
|
||||
// Check if we have any remainder. Add 1 to pages count if so.
|
||||
if len(publicPastes)%c.Config.Pastes.Pagination > 0 {
|
||||
pages++
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
|
||||
func (ff *FlatFiles) Initialize() {
|
||||
c.Logger.Info().Msg("Initializing flatfiles storage...")
|
||||
|
||||
path := c.Config.Database.Path
|
||||
// Get proper paste file path.
|
||||
if strings.Contains(c.Config.Database.Path, "~") {
|
||||
curUser, err := user.Current()
|
||||
if err != nil {
|
||||
c.Logger.Error().Msg("Failed to get current user. Will replace '~' for '/' in storage path!")
|
||||
|
||||
path = strings.Replace(path, "~", "/", -1)
|
||||
}
|
||||
|
||||
path = strings.Replace(path, "~", curUser.HomeDir, -1)
|
||||
}
|
||||
|
||||
path, _ = filepath.Abs(path)
|
||||
ff.path = path
|
||||
|
||||
c.Logger.Debug().Msgf("Storage path is now: %s", ff.path)
|
||||
|
||||
// Create directory if necessary.
|
||||
if _, err := os.Stat(ff.path); err != nil {
|
||||
c.Logger.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...")
|
||||
_ = os.MkdirAll(ff.path, os.ModePerm)
|
||||
} else {
|
||||
c.Logger.Debug().Str("directory", ff.path).Msg("Directory already exists")
|
||||
}
|
||||
|
||||
// Create directory for pastes.
|
||||
if _, err := os.Stat(filepath.Join(ff.path, "pastes")); err != nil {
|
||||
c.Logger.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...")
|
||||
_ = os.MkdirAll(filepath.Join(ff.path, "pastes"), os.ModePerm)
|
||||
} else {
|
||||
c.Logger.Debug().Str("directory", ff.path).Msg("Directory already exists")
|
||||
}
|
||||
|
||||
// Load pastes index.
|
||||
ff.pastesIndex = []Index{}
|
||||
if _, err := os.Stat(filepath.Join(ff.path, "pastes", "index.json")); err != nil {
|
||||
c.Logger.Warn().Msg("Pastes index file does not exist, will create new one")
|
||||
} else {
|
||||
indexData, err := ioutil.ReadFile(filepath.Join(ff.path, "pastes", "index.json"))
|
||||
if err != nil {
|
||||
c.Logger.Fatal().Msg("Failed to read contents of index file!")
|
||||
}
|
||||
|
||||
err1 := json.Unmarshal(indexData, &ff.pastesIndex)
|
||||
if err1 != nil {
|
||||
c.Logger.Error().Err(err1).Msg("Failed to parse index file contents from JSON into internal structure. Will create new index file. All of your previous pastes will became unavailable.")
|
||||
}
|
||||
|
||||
c.Logger.Debug().Int("pastes count", len(ff.pastesIndex)).Msg("Parsed pastes index")
|
||||
}
|
||||
}
|
||||
|
||||
func (ff *FlatFiles) SavePaste(p *structs.Paste) (int64, error) {
|
||||
ff.writeMutex.Lock()
|
||||
// Write paste data on disk.
|
||||
filesOnDisk, _ := ioutil.ReadDir(filepath.Join(ff.path, "pastes"))
|
||||
pasteID := len(filesOnDisk) + 1
|
||||
p.ID = pasteID
|
||||
|
||||
c.Logger.Debug().Int("new paste ID", pasteID).Msg("Writing paste to disk")
|
||||
|
||||
data, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
ff.writeMutex.Unlock()
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"), data, 0644)
|
||||
if err != nil {
|
||||
ff.writeMutex.Unlock()
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Add it to cache.
|
||||
indexData := Index{}
|
||||
indexData.ID = pasteID
|
||||
indexData.Private = p.Private
|
||||
ff.pastesIndex = append(ff.pastesIndex, indexData)
|
||||
ff.writeMutex.Unlock()
|
||||
|
||||
return int64(pasteID), nil
|
||||
}
|
||||
|
||||
func (ff *FlatFiles) Shutdown() {
|
||||
c.Logger.Info().Msg("Saving indexes...")
|
||||
|
||||
indexData, err := json.Marshal(ff.pastesIndex)
|
||||
if err != nil {
|
||||
c.Logger.Error().Err(err).Msg("Failed to encode index data into JSON")
|
||||
return
|
||||
}
|
||||
|
||||
err1 := ioutil.WriteFile(filepath.Join(ff.path, "pastes", "index.json"), indexData, 0644)
|
||||
if err1 != nil {
|
||||
c.Logger.Error().Err(err1).Msg("Failed to write index data to file. Pretty sure that you've lost your pastes.")
|
||||
return
|
||||
}
|
||||
}
|
67
internal/database/dialects/flatfiles/handler.go
Normal file
67
internal/database/dialects/flatfiles/handler.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package flatfiles
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"database/sql"
|
||||
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||
)
|
||||
|
||||
type Handler struct{}
|
||||
|
||||
func (dbh Handler) DeletePaste(pasteID int) error {
|
||||
return f.DeletePaste(pasteID)
|
||||
}
|
||||
|
||||
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
||||
return f.GetDatabaseConnection()
|
||||
}
|
||||
|
||||
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||
return f.GetPaste(pasteID)
|
||||
}
|
||||
|
||||
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||
return f.GetPagedPastes(page)
|
||||
}
|
||||
|
||||
func (dbh Handler) GetPastesPages() int {
|
||||
return f.GetPastesPages()
|
||||
}
|
||||
|
||||
func (dbh Handler) Initialize() {
|
||||
f.Initialize()
|
||||
}
|
||||
|
||||
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
||||
return f.SavePaste(p)
|
||||
}
|
||||
|
||||
func (dbh Handler) Shutdown() {
|
||||
f.Shutdown()
|
||||
}
|
31
internal/database/dialects/flatfiles/index.go
Normal file
31
internal/database/dialects/flatfiles/index.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package flatfiles
|
||||
|
||||
// Index describes paste index structure for flatfiles data storage.
|
||||
type Index struct {
|
||||
ID int `yaml:"id"`
|
||||
Private bool `json:"private"`
|
||||
}
|
44
internal/database/dialects/interface/dialectinterface.go
Normal file
44
internal/database/dialects/interface/dialectinterface.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package dialectinterface
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"database/sql"
|
||||
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
DeletePaste(int) error
|
||||
GetDatabaseConnection() *sql.DB
|
||||
GetPaste(pasteID int) (*structs.Paste, error)
|
||||
GetPagedPastes(page int) ([]structs.Paste, error)
|
||||
GetPastesPages() int
|
||||
Initialize()
|
||||
SavePaste(p *structs.Paste) (int64, error)
|
||||
Shutdown()
|
||||
}
|
43
internal/database/dialects/mysql/exported.go
Normal file
43
internal/database/dialects/mysql/exported.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
||||
)
|
||||
|
||||
var (
|
||||
c *context.Context
|
||||
d *Database
|
||||
)
|
||||
|
||||
func New(cc *context.Context) {
|
||||
c = cc
|
||||
d = &Database{}
|
||||
|
||||
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
||||
}
|
67
internal/database/dialects/mysql/handler.go
Normal file
67
internal/database/dialects/mysql/handler.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"database/sql"
|
||||
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||
)
|
||||
|
||||
type Handler struct{}
|
||||
|
||||
func (dbh Handler) DeletePaste(pasteID int) error {
|
||||
return d.DeletePaste(pasteID)
|
||||
}
|
||||
|
||||
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
||||
return d.GetDatabaseConnection()
|
||||
}
|
||||
|
||||
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||
return d.GetPaste(pasteID)
|
||||
}
|
||||
|
||||
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||
return d.GetPagedPastes(page)
|
||||
}
|
||||
|
||||
func (dbh Handler) GetPastesPages() int {
|
||||
return d.GetPastesPages()
|
||||
}
|
||||
|
||||
func (dbh Handler) Initialize() {
|
||||
d.Initialize()
|
||||
}
|
||||
|
||||
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
||||
return d.SavePaste(p)
|
||||
}
|
||||
|
||||
func (dbh Handler) Shutdown() {
|
||||
d.Shutdown()
|
||||
}
|
@@ -30,7 +30,14 @@ import (
|
||||
)
|
||||
|
||||
func InitialUp(tx *sql.Tx) error {
|
||||
_, err := tx.Exec("CREATE TABLE `pastes` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Paste ID', `title` text NOT NULL COMMENT 'Paste title', `data` longtext NOT NULL COMMENT 'Paste data', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Paste creation timestamp', `keep_for` int(4) NOT NULL DEFAULT 1 COMMENT 'Keep for integer. 0 - forever.', `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';")
|
||||
_, err := tx.Exec(`CREATE TABLE pastes (
|
||||
id int(11) NOT NULL AUTO_INCREMENT COMMENT 'Paste ID',
|
||||
title text NOT NULL COMMENT 'Paste title',
|
||||
data longtext NOT NULL COMMENT 'Paste data',
|
||||
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Paste creation timestamp',
|
||||
keep_for int(4) NOT NULL DEFAULT 1 COMMENT 'Keep for integer. 0 - forever.',
|
||||
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';`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
@@ -26,10 +26,10 @@ package migrations
|
||||
|
||||
import (
|
||||
// local
|
||||
"github.com/pztrn/fastpastebin/context"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||
|
||||
// other
|
||||
//"github.com/jmoiron/sqlx"
|
||||
//"gitlab.com/jmoiron/sqlx"
|
||||
"github.com/pressly/goose"
|
||||
)
|
||||
|
||||
@@ -46,7 +46,7 @@ func New(cc *context.Context) {
|
||||
func Migrate() {
|
||||
c.Logger.Info().Msg("Migrating database...")
|
||||
|
||||
goose.SetDialect("mysql")
|
||||
_ = goose.SetDialect("mysql")
|
||||
goose.AddNamedMigration("1_initial.go", InitialUp, nil)
|
||||
goose.AddNamedMigration("2_paste_lang.go", PasteLangUp, PasteLangDown)
|
||||
goose.AddNamedMigration("3_private_pastes.go", PrivatePastesUp, PrivatePastesDown)
|
||||
@@ -54,8 +54,12 @@ func Migrate() {
|
||||
// Add new migrations BEFORE this message.
|
||||
|
||||
dbConn := c.Database.GetDatabaseConnection()
|
||||
err := goose.Up(dbConn.DB, ".")
|
||||
if err != nil {
|
||||
c.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
|
||||
if dbConn != nil {
|
||||
err := goose.Up(dbConn, ".")
|
||||
if err != nil {
|
||||
c.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
c.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
|
||||
}
|
||||
}
|
210
internal/database/dialects/mysql/mysqldatabase.go
Normal file
210
internal/database/dialects/mysql/mysqldatabase.go
Normal file
@@ -0,0 +1,210 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/mysql/migrations"
|
||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||
|
||||
// other
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// Database is a MySQL/MariaDB connection controlling structure.
|
||||
type Database struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
// Checks if queries can be run on known connection and reestablish it
|
||||
// if not.
|
||||
func (db *Database) check() {
|
||||
if db.db != nil {
|
||||
_, err := db.db.Exec("SELECT 1")
|
||||
if err != nil {
|
||||
db.Initialize()
|
||||
}
|
||||
} else {
|
||||
db.Initialize()
|
||||
}
|
||||
}
|
||||
|
||||
// DeletePaste deletes paste from database.
|
||||
func (db *Database) DeletePaste(pasteID int) error {
|
||||
db.check()
|
||||
|
||||
_, err := db.db.Exec(db.db.Rebind("DELETE FROM pastes WHERE id=?"), pasteID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) GetDatabaseConnection() *sql.DB {
|
||||
db.check()
|
||||
|
||||
if db.db != nil {
|
||||
return db.db.DB
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPaste returns a single paste by ID.
|
||||
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||
db.check()
|
||||
|
||||
p := &structs.Paste{}
|
||||
|
||||
err := db.db.Get(p, db.db.Rebind("SELECT * FROM `pastes` WHERE id=?"), pasteID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||
db.check()
|
||||
|
||||
var (
|
||||
pastesRaw []structs.Paste
|
||||
pastes []structs.Paste
|
||||
)
|
||||
|
||||
// Pagination.
|
||||
startPagination := 0
|
||||
if page > 1 {
|
||||
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 ?"), c.Config.Pastes.Pagination, startPagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range pastesRaw {
|
||||
if !pastesRaw[i].IsExpired() {
|
||||
pastes = append(pastes, pastesRaw[i])
|
||||
}
|
||||
}
|
||||
|
||||
return pastes, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetPastesPages() int {
|
||||
db.check()
|
||||
|
||||
var (
|
||||
pastesRaw []structs.Paste
|
||||
pastes []structs.Paste
|
||||
)
|
||||
|
||||
err := db.db.Get(&pastesRaw, "SELECT * FROM `pastes` WHERE private != true")
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Check if pastes isn't expired.
|
||||
for i := range pastesRaw {
|
||||
if !pastesRaw[i].IsExpired() {
|
||||
pastes = append(pastes, pastesRaw[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate pages.
|
||||
pages := len(pastes) / c.Config.Pastes.Pagination
|
||||
// Check if we have any remainder. Add 1 to pages count if so.
|
||||
if len(pastes)%c.Config.Pastes.Pagination > 0 {
|
||||
pages++
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
|
||||
// Initialize initializes MySQL/MariaDB connection.
|
||||
func (db *Database) Initialize() {
|
||||
c.Logger.Info().Msg("Initializing database connection...")
|
||||
|
||||
// There might be only user, without password. MySQL/MariaDB driver
|
||||
// in DSN wants "user" or "user:password", "user:" is invalid.
|
||||
var userpass string
|
||||
if c.Config.Database.Password == "" {
|
||||
userpass = c.Config.Database.Username
|
||||
} else {
|
||||
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, c.Config.Database.Address, c.Config.Database.Port, c.Config.Database.Database)
|
||||
c.Logger.Debug().Str("DSN", dbConnString).Msgf("Database connection string composed")
|
||||
|
||||
dbConn, err := sqlx.Connect("mysql", dbConnString)
|
||||
if err != nil {
|
||||
c.Logger.Error().Err(err).Msg("Failed to connect to database")
|
||||
return
|
||||
}
|
||||
|
||||
// Force UTC for current connection.
|
||||
_ = dbConn.MustExec("SET @@session.time_zone='+00:00';")
|
||||
|
||||
c.Logger.Info().Msg("Database connection established")
|
||||
|
||||
db.db = dbConn
|
||||
|
||||
// Perform migrations.
|
||||
migrations.New(c)
|
||||
migrations.Migrate()
|
||||
}
|
||||
|
||||
func (db *Database) SavePaste(p *structs.Paste) (int64, error) {
|
||||
db.check()
|
||||
|
||||
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 {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ID, err1 := result.LastInsertId()
|
||||
if err1 != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return ID, nil
|
||||
}
|
||||
|
||||
func (db *Database) Shutdown() {
|
||||
if db.db != nil {
|
||||
err := db.db.Close()
|
||||
if err != nil {
|
||||
c.Logger.Error().Err(err).Msg("Failed to close database connection")
|
||||
}
|
||||
}
|
||||
}
|
43
internal/database/dialects/postgresql/exported.go
Normal file
43
internal/database/dialects/postgresql/exported.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package postgresql
|
||||
|
||||
import (
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/internal/context"
|
||||
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
|
||||
)
|
||||
|
||||
var (
|
||||
c *context.Context
|
||||
d *Database
|
||||
)
|
||||
|
||||
func New(cc *context.Context) {
|
||||
c = cc
|
||||
d = &Database{}
|
||||
|
||||
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
|
||||
}
|
67
internal/database/dialects/postgresql/handler.go
Normal file
67
internal/database/dialects/postgresql/handler.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package postgresql
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"database/sql"
|
||||
|
||||
// local
|
||||
"go.dev.pztrn.name/fastpastebin/internal/structs"
|
||||
)
|
||||
|
||||
type Handler struct{}
|
||||
|
||||
func (dbh Handler) DeletePaste(pasteID int) error {
|
||||
return d.DeletePaste(pasteID)
|
||||
}
|
||||
|
||||
func (dbh Handler) GetDatabaseConnection() *sql.DB {
|
||||
return d.GetDatabaseConnection()
|
||||
}
|
||||
|
||||
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
|
||||
return d.GetPaste(pasteID)
|
||||
}
|
||||
|
||||
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
|
||||
return d.GetPagedPastes(page)
|
||||
}
|
||||
|
||||
func (dbh Handler) GetPastesPages() int {
|
||||
return d.GetPastesPages()
|
||||
}
|
||||
|
||||
func (dbh Handler) Initialize() {
|
||||
d.Initialize()
|
||||
}
|
||||
|
||||
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
|
||||
return d.SavePaste(p)
|
||||
}
|
||||
|
||||
func (dbh Handler) Shutdown() {
|
||||
d.Shutdown()
|
||||
}
|
@@ -22,49 +22,35 @@
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
package api
|
||||
package migrations
|
||||
|
||||
import (
|
||||
// local
|
||||
"github.com/pztrn/fastpastebin/api/http"
|
||||
"github.com/pztrn/fastpastebin/api/json"
|
||||
"github.com/pztrn/fastpastebin/context"
|
||||
|
||||
// other
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/middleware"
|
||||
// stdlib
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
var (
|
||||
c *context.Context
|
||||
e *echo.Echo
|
||||
)
|
||||
func InitialUp(tx *sql.Tx) error {
|
||||
_, err := tx.Exec(`
|
||||
CREATE TABLE pastes
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
data TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
keep_for INTEGER NOT NULL DEFAULT 1,
|
||||
keep_for_unit_type SMALLINT NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
// New initializes variables for api package.
|
||||
func New(cc *context.Context) {
|
||||
c = cc
|
||||
}
|
||||
|
||||
// InitializeAPI initializes HTTP API and starts web server.
|
||||
func InitializeAPI() {
|
||||
c.Logger.Info().Msg("Initializing HTTP server...")
|
||||
|
||||
e = echo.New()
|
||||
e.Use(echoReqLogger())
|
||||
e.Use(middleware.Recover())
|
||||
e.DisableHTTP2 = true
|
||||
e.HideBanner = true
|
||||
e.HidePort = true
|
||||
|
||||
c.RegisterEcho(e)
|
||||
|
||||
http.New(c)
|
||||
json.New(c)
|
||||
|
||||
listenAddress := c.Config.HTTP.Address + ":" + c.Config.HTTP.Port
|
||||
|
||||
go func() {
|
||||
e.Logger.Fatal(e.Start(listenAddress))
|
||||
}()
|
||||
c.Logger.Info().Msgf("Started HTTP server at %s", listenAddress)
|
||||
COMMENT ON COLUMN pastes.id IS 'Paste ID';
|
||||
COMMENT ON COLUMN pastes.title IS 'Paste title';
|
||||
COMMENT ON COLUMN pastes.data IS 'Paste data';
|
||||
COMMENT ON COLUMN pastes.created_at IS 'Paste creation timestamp';
|
||||
COMMENT ON COLUMN pastes.keep_for IS 'Keep for integer. 0 - forever.';
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -22,23 +22,27 @@
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
package database
|
||||
package migrations
|
||||
|
||||
import (
|
||||
// other
|
||||
"github.com/jmoiron/sqlx"
|
||||
// stdlib
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// Handler is an interfaceable structure that proxifies calls from anyone
|
||||
// to Database structure.
|
||||
type Handler struct{}
|
||||
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';")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// GetDatabaseConnection returns current database connection.
|
||||
func (dbh Handler) GetDatabaseConnection() *sqlx.DB {
|
||||
return d.GetDatabaseConnection()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initialize initializes connection to database.
|
||||
func (dbh Handler) Initialize() {
|
||||
d.Initialize()
|
||||
func PasteLangDown(tx *sql.Tx) error {
|
||||
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN language")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
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.';")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PrivatePastesDown(tx *sql.Tx) error {
|
||||
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN private")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
// Fast Paste Bin - uberfast and easy-to-use pastebin.
|
||||
//
|
||||
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
|
||||
// developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
func PasswordedPastesUp(tx *sql.Tx) error {
|
||||
_, err := tx.Exec("ALTER TABLE pastes ADD COLUMN password VARCHAR(64) NOT NULL DEFAULT ''; COMMENT ON COLUMN pastes.password IS 'Password for paste (scrypted and sha256ed).';")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err1 := tx.Exec("ALTER TABLE pastes ADD COLUMN password_salt VARCHAR(64) NOT NULL DEFAULT ''; COMMENT ON COLUMN pastes.password_salt IS 'Password salt (sha256ed).';")
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PasswordedPastesDown(tx *sql.Tx) error {
|
||||
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN password")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err1 := tx.Exec("ALTER TABLE pastes DROP COLUMN password_salt")
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user