Started big code refactoring

pull/1/head
Leonid Maslakov 1 year ago
parent 75a4b715d9
commit b913f7b5ed

@ -21,7 +21,7 @@ package main
import (
"git.lcomrade.su/root/lenpaste/internal/api"
"git.lcomrade.su/root/lenpaste/internal/config"
"git.lcomrade.su/root/lenpaste/internal/pages"
"git.lcomrade.su/root/lenpaste/internal/web"
"git.lcomrade.su/root/lenpaste/internal/storage"
"errors"
"io"

@ -3,3 +3,5 @@ module git.lcomrade.su/root/lenpaste
go 1.11
replace git.lcomrade.su/root/lenpaste/internal => ./internal
require github.com/mattn/go-sqlite3 v1.14.12

@ -1,2 +1,4 @@
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.13/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=

@ -0,0 +1,37 @@
// Copyright (C) 2021-2022 Leonid Maslakov.
// This file is part of Lenpaste.
// Lenpaste is free software: you can redistribute it
// and/or modify it under the terms of the
// GNU Affero Public License as published by the
// Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
// Lenpaste is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero Public License for more details.
// You should have received a copy of the GNU Affero Public License along with Lenpaste.
// If not, see <https://www.gnu.org/licenses/>.
package logger
import (
"net/http"
"time"
)
type Config struct {
}
func (config Config) HttpRequest(req *http.Request) {
now := time.Now()
println(now.Format("2006/01/02 15:04:05"), "[REQUEST]", req.RemoteAddr, req.Method, req.URL.Path)
}
func (config Config) HttpError(req *http.Request, err error) {
now := time.Now()
println(now.Format("2006/01/02 15:04:05"), "[ERROR]", req.RemoteAddr, req.Method, req.URL.Path, "Error:", err.Error())
}

@ -1,385 +0,0 @@
// Copyright (C) 2021-2022 Leonid Maslakov.
// This file is part of Lenpaste.
// Lenpaste is free software: you can redistribute it
// and/or modify it under the terms of the
// GNU Affero Public License as published by the
// Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
// Lenpaste is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero Public License for more details.
// You should have received a copy of the GNU Affero Public License along with Lenpaste.
// If not, see <https://www.gnu.org/licenses/>.
package pages
import (
"git.lcomrade.su/root/lenpaste/internal/config"
"git.lcomrade.su/root/lenpaste/internal/storage"
"bytes"
"html/template"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"time"
)
const (
webDir = "./web"
)
type PastePageType struct {
Title string
Text string
OneUse bool
ExpiresMin int64
ExpiresHour int64
ExpiresDay int64
//Syntax string
//Password string
}
type errorPageType struct {
Code int
Error string
}
//Style
func Style(rw http.ResponseWriter, req *http.Request) {
//Return response
rw.Header().Set("Content-Type", "text/css")
io.WriteString(rw, pages.StyleCSS)
}
//New paste
func NewPaste(rw http.ResponseWriter, req *http.Request) {
//Return response
rw.Header().Set("Content-Type", "text/html")
io.WriteString(rw, pages.New)
}
//New paste done
type NewPasteType struct {
Name string
Host string
}
func NewPasteDone(rw http.ResponseWriter, req *http.Request) {
//Get form data
req.ParseForm()
text := req.Form.Get("text")
expiration := req.Form.Get("expiration")
title := req.Form.Get("title")
oneUse := false
if req.Form.Get("oneUse") == "true" {
oneUse = true
}
//Create paste
paste, err := storage.NewPaste(text, expiration, oneUse, title)
if err != nil {
errorHandler(rw, err, 400)
return
}
//Total paste info
pasteTotal := NewPasteType{
Name: paste.Name,
Host: req.Host,
}
//Set Header
rw.Header().Set("Content-Type", "text/html")
//Filling the html page template
tmpl := pages.NewDoneTmpl
err = tmpl.Execute(rw, pasteTotal)
if err != nil {
errorHandler(rw, err, 400)
return
}
}
//About API page
func API(rw http.ResponseWriter, req *http.Request) {
//Return response
rw.Header().Set("Content-Type", "text/html")
io.WriteString(rw, pages.API)
}
//Server rules page
func Rules(rw http.ResponseWriter, req *http.Request) {
//Return response
rw.Header().Set("Content-Type", "text/html")
io.WriteString(rw, pages.Rules)
}
//Server version page
func Version(rw http.ResponseWriter, req *http.Request) {
//Return response
rw.Header().Set("Content-Type", "text/html")
io.WriteString(rw, pages.Version)
}
//Get paste
func GetPaste(rw http.ResponseWriter, req *http.Request) {
//Set Header
rw.Header().Set("Content-Type", "text/html")
//Main page
if req.URL.Path == "/" {
io.WriteString(rw, pages.Main)
return
}
//Get paste name
name := filepath.Base(req.URL.Path)
//Get paste
pasteInfo, err := storage.GetPaste(name)
if err != nil {
errorHandler(rw, err, 404)
return
}
//Convert paste info
deltaTime := pasteInfo.Info.DeleteTime - time.Now().Unix()
deltaTime = deltaTime / 60
pastePage := PastePageType{
Title: pasteInfo.Info.Title,
Text: pasteInfo.Text,
OneUse: pasteInfo.Info.OneUse,
ExpiresMin: deltaTime % 60,
ExpiresHour: deltaTime / 60 % 24,
ExpiresDay: deltaTime / 60 / 24,
}
//Filling the html page template
tmpl := pages.GetTmpl
err = tmpl.Execute(rw, pastePage)
if err != nil {
errorHandler(rw, err, 400)
return
}
}
//Error page
func errorHandler(rw http.ResponseWriter, err error, code int) {
//Set Header
rw.WriteHeader(code)
rw.Header().Set("Content-Type", "text/html")
//Get error info
errorInfo := errorPageType{
Code: code,
Error: err.Error(),
}
//Filling the html page template
tmpl := pages.ErrorTmpl
err = tmpl.Execute(rw, errorInfo)
if err != nil {
http.Error(rw, err.Error(), 400)
return
}
}
//Load pages (init)
func loadFile(path string) (string, error) {
var out string
//Open file
file, err := os.Open(path)
if err != nil {
return out, err
}
defer file.Close()
//Read file
fileByte, err := ioutil.ReadAll(file)
out = string(fileByte)
if err != nil {
return out, err
}
return out, nil
}
func loadMain() (string, error) {
var mainHTML string
//Load HTML template
tmpl, err := template.ParseFiles(filepath.Join(webDir, "main.tmpl"))
if err != nil {
return mainHTML, err
}
//Read about file
about, err := config.ReadAbout()
if err != nil {
return mainHTML, err
}
//Execute template
buf := new(bytes.Buffer)
err = tmpl.Execute(buf, about)
if err != nil {
return mainHTML, err
}
mainHTML = buf.String()
return mainHTML, nil
}
func loadRules() (string, error) {
var rulesHTML string
//Load HTML template
tmpl, err := template.ParseFiles(filepath.Join(webDir, "rules.tmpl"))
if err != nil {
return rulesHTML, err
}
//Read rules file
rules, err := config.ReadRules()
if err != nil {
return rulesHTML, err
}
if rules.Exist == false {
rules.Text = "This server has no rules."
}
//Execute template
buf := new(bytes.Buffer)
err = tmpl.Execute(buf, rules)
if err != nil {
return rulesHTML, err
}
rulesHTML = buf.String()
return rulesHTML, nil
}
func loadVersion() (string, error) {
var versionHTML string
//Load HTML template
tmpl, err := template.ParseFiles(filepath.Join(webDir, "version.tmpl"))
if err != nil {
return versionHTML, err
}
//Read version file
version, err := config.ReadVersion()
if err != nil {
return versionHTML, err
}
//Execute template
buf := new(bytes.Buffer)
err = tmpl.Execute(buf, version)
if err != nil {
return versionHTML, err
}
versionHTML = buf.String()
return versionHTML, nil
}
type pagesType struct {
StyleCSS string
Main string
API string
Rules string
Version string
New string
NewDoneTmpl *template.Template
GetTmpl *template.Template
ErrorTmpl *template.Template
}
var pages pagesType
func Load() error {
var err error
//Style
pages.StyleCSS, err = loadFile(filepath.Join(webDir, "style.css"))
if err != nil {
return err
}
//Main page
pages.Main, err = loadMain()
if err != nil {
return err
}
//About API page
pages.API, err = loadFile(filepath.Join(webDir, "api.html"))
if err != nil {
return err
}
//Rules page
pages.Rules, err = loadRules()
if err != nil {
return err
}
//Version page
pages.Version, err = loadVersion()
if err != nil {
return err
}
//New page
pages.New, err = loadFile(filepath.Join(webDir, "new.html"))
if err != nil {
return err
}
//New done tmpl
pages.NewDoneTmpl, err = template.ParseFiles(filepath.Join(webDir, "new_done.tmpl"))
if err != nil {
return err
}
//Get tmpl
pages.GetTmpl, err = template.ParseFiles(filepath.Join(webDir, "get.tmpl"))
if err != nil {
return err
}
//Error tmpl
pages.ErrorTmpl, err = template.ParseFiles(filepath.Join(webDir, "error.tmpl"))
if err != nil {
return err
}
return nil
}

@ -0,0 +1,44 @@
// Copyright (C) 2021-2022 Leonid Maslakov.
// This file is part of Lenpaste.
// Lenpaste is free software: you can redistribute it
// and/or modify it under the terms of the
// GNU Affero Public License as published by the
// Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
// Lenpaste is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero Public License for more details.
// You should have received a copy of the GNU Affero Public License along with Lenpaste.
// If not, see <https://www.gnu.org/licenses/>.
package storage
import(
"crypto/rand"
"math/big"
)
func genTokenCrypto(tokenLen int) (string, error) {
// Generate token
var chars = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
charsLen := int64(len(chars))
charsLenBig := big.NewInt(charsLen)
token := ""
for i := 0; i < tokenLen; i++ {
randInt, err := rand.Int(rand.Reader, charsLenBig)
if err != nil {
return "", err
}
token = token + string(chars[randInt.Int64()])
}
return token, nil
}

@ -0,0 +1,95 @@
// Copyright (C) 2021-2022 Leonid Maslakov.
// This file is part of Lenpaste.
// Lenpaste is free software: you can redistribute it
// and/or modify it under the terms of the
// GNU Affero Public License as published by the
// Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
// Lenpaste is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero Public License for more details.
// You should have received a copy of the GNU Affero Public License along with Lenpaste.
// If not, see <https://www.gnu.org/licenses/>.
package storage
import(
"errors"
)
var(
ErrUnknowExpir = errors.New("unknown expiration")
)
func ExpirationToTime(expiration string) (int64, error) {
switch expiration {
// Minutes
case "5m":
return 60 * 5, nil
case "10m":
return 60 * 10, nil
case "20m":
return 60 * 20, nil
case "30m":
return 60 * 30, nil
case "40m":
return 60 * 40, nil
case "50m":
return 60 * 50, nil
// Hours
case "1h":
return 60 * 60, nil
case "2h":
return 60 * 60 * 2, nil
case "4h":
return 60 * 60 * 4, nil
case "12h":
return 60 * 60 * 12, nil
// Days
case "1d":
return 60 * 60 * 24, nil
case "2d":
return 60 * 60 * 24 * 2, nil
case "3d":
return 60 * 60 * 24 * 3, nil
case "4d":
return 60 * 60 * 24 * 4, nil
case "5d":
return 60 * 60 * 24 * 5, nil
case "6d":
return 60 * 60 * 24 * 6, nil
// Weeks
case "1w":
return 60 * 60 * 24 * 7, nil
case "2w":
return 60 * 60 * 24 * 7 * 2, nil
case "3w":
return 60 * 60 * 24 * 7 * 3, nil
}
return 0, ErrUnknowExpir
}

@ -19,376 +19,47 @@
package storage
import (
"encoding/json"
"database/sql"
"errors"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"time"
_ "github.com/mattn/go-sqlite3"
)
const (
pasteDir = "./data/paste"
pasteFileMod = 0700
pasteTextPrefix = ".txt"
pasteInfoPrefix = ".json"
pasteNameLength = 8
var (
ErrNotFoundID = errors.New("db: could not find ID")
)
//Type
type PasteInfoType struct {
CreateTime int64
DeleteTime int64
OneUse bool
Title string
//Syntax string
//Password string
type DB struct {
DriverName string
DataSourceName string
}
type NewPasteType struct {
Name string
func (dbInfo DB) openDB() (*sql.DB, error) {
db, err := sql.Open(dbInfo.DriverName, dbInfo.DataSourceName)
return db, err
}
type GetPasteType struct {
Name string
Text string
Info PasteInfoType
}
//Service
func randString() string {
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
randString := make([]rune, pasteNameLength)
for i := range randString {
rand.Seed(time.Now().UnixNano())
randString[i] = letters[rand.Intn(len(letters))]
}
return string(randString)
//rand.Seed(time.Now().UnixNano())
//return strconv.Itoa(int(rand.Int63()))
}
func expirParse(expiration string) (int64, error) {
switch expiration {
//Minutes
case "5m":
return 60 * 5, nil
case "10m":
return 60 * 10, nil
case "20m":
return 60 * 20, nil
case "30m":
return 60 * 30, nil
case "40m":
return 60 * 40, nil
case "50m":
return 60 * 50, nil
//Hours
case "1h":
return 60 * 60, nil
case "2h":
return 60 * 60 * 2, nil
case "4h":
return 60 * 60 * 4, nil
case "12h":
return 60 * 60 * 12, nil
//Days
case "1d":
return 60 * 60 * 24, nil
case "2d":
return 60 * 60 * 24 * 2, nil
case "3d":
return 60 * 60 * 24 * 3, nil
case "4d":
return 60 * 60 * 24 * 4, nil
case "5d":
return 60 * 60 * 24 * 5, nil
case "6d":
return 60 * 60 * 24 * 6, nil
//Weeks
case "1w":
return 60 * 60 * 24 * 7, nil
case "2w":
return 60 * 60 * 24 * 7 * 2, nil
case "3w":
return 60 * 60 * 24 * 7 * 3, nil
}
return 0, errors.New("unknown expiration: " + expiration)
}
func genPasteInfo(expir string, oneUse bool, title string) ([]byte, error) {
var infoByte []byte
//Time
nowTime := time.Now().Unix()
expirTime, err := expirParse(expir)
if err != nil {
return infoByte, err
}
delTime := nowTime + expirTime
//Struct
pasteInfo := PasteInfoType{
CreateTime: nowTime,
DeleteTime: delTime,
OneUse: oneUse,
Title: title,
}
//Marshal (json)
infoByte, err = json.MarshalIndent(pasteInfo, "", " ")
if err != nil {
return infoByte, err
}
return infoByte, nil
}
func getPasteInfo(name string) (PasteInfoType, error) {
pasteInfo := PasteInfoType{}
//File name
fileName := filepath.Join(pasteDir, name+pasteInfoPrefix)
//Open file
file, err := os.Open(fileName)
if err != nil {
return pasteInfo, err
}
defer file.Close()
//Decode JSON
jsonParser := json.NewDecoder(file)
err = jsonParser.Decode(&pasteInfo)
if err != nil {
return pasteInfo, err
}
return pasteInfo, nil
}
func isPasteExist(name string) bool {
//File name
fileName := filepath.Join(pasteDir, name)
infoFileName := fileName + pasteInfoPrefix
textFileName := fileName + pasteTextPrefix
//Check (INFO)
_, err := os.Stat(infoFileName)
if os.IsNotExist(err) == true {
return false
}
//Check (TEXT)
_, err = os.Stat(textFileName)
if os.IsNotExist(err) == true {
return false
}
//Return
return true
}
//Create Paste
func NewPaste(pasteText string, expir string, oneUse bool, title string) (NewPasteType, error) {
var paste NewPasteType
//Paste name
pasteName := randString()
if isPasteExist(pasteName) == true {
return paste, errors.New("paste with that name exists")
}
//File name
fileName := filepath.Join(pasteDir, pasteName)
infoFileName := fileName + pasteInfoPrefix
textFileName := fileName + pasteTextPrefix
//Paste info
pasteInfo, err := genPasteInfo(expir, oneUse, title)
if err != nil {
return paste, err
}
//Make paste dir
err = os.MkdirAll(pasteDir, pasteFileMod)
if err != nil {
return paste, err
}
//Write (info)
err = ioutil.WriteFile(infoFileName, pasteInfo, pasteFileMod)
if err != nil {
return paste, err
}
//Write (text)
err = ioutil.WriteFile(textFileName, []byte(pasteText), pasteFileMod)
if err != nil {
return paste, err
}
//Return
paste = NewPasteType{
Name: pasteName,
}
return paste, nil
}
//Get Paste
func GetPaste(name string) (GetPasteType, error) {
var pasteInfo PasteInfoType
var pasteText string
var paste GetPasteType
//File name
fileName := filepath.Join(pasteDir, name)
textFileName := fileName + pasteTextPrefix
//Get paste info
pasteInfo, err := getPasteInfo(name)
if err != nil {
//If paste does not exist
if os.IsNotExist(err) == true {
return paste, errors.New("paste not exist: " + name)
} else {
return paste, err
}
}
//Expiry time check
if pasteInfo.DeleteTime <= time.Now().Unix() {
//Delete expired paste
err = DelPaste(name)
if err != nil {
return paste, err
}
//Return error
return paste, errors.New("paste not exist: " + name)
}
//Get paste text
pasteTextByte, err := ioutil.ReadFile(textFileName)
if err != nil {
return paste, err
}
pasteText = string(pasteTextByte)
//One use check
if pasteInfo.OneUse == true {
err := DelPaste(name)
if err != nil {
return paste, err
}
}
//Return
paste = GetPasteType{
Name: name,
Text: pasteText,
Info: pasteInfo,
}
return paste, nil
}
//Delete Paste
func DelPaste(name string) error {
//File name
fileName := filepath.Join(pasteDir, name)
infoFileName := fileName + pasteInfoPrefix
textFileName := fileName + pasteTextPrefix
//Remove info
err := os.Remove(infoFileName)
func (dbInfo DB) InitDB() error {
// Open DB
db, err := dbInfo.openDB()
if err != nil {
return err
}
//Remove text
err = os.Remove(textFileName)
defer db.Close()
// Create tables
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS "pastes" (
"id" TEXT PRIMARY KEY,
"title" TEXT NOT NULL,
"body" TEXT NOT NULL,
"create_time" INTEGER NOT NULL,
"delete_time" INTEGER NOT NULL,
"one_use" BOOL NOT NULL
);
`)
if err != nil {
return err
}
return nil
}
//Deletes expired pastes
func DelExpiredPaste() []error {
var errs []error
//Get file prefix len
infoPrefixLen := len(pasteInfoPrefix)
//Get files list
filesList, err := ioutil.ReadDir(pasteDir)
if err != nil {
return append(errs, err)
}
//Read files list
for _, file := range filesList {
//Skip dirs
if file.IsDir() == true {
continue
}
//Get file info
name := file.Name()
nameLen := len(name)
//Check file name (info prefix)
if name[nameLen-infoPrefixLen:] == pasteInfoPrefix {
//Get paste name
pasteName := name[0 : nameLen-infoPrefixLen]
//Get paste info
paste, err := getPasteInfo(pasteName)
if err != nil {
errs = append(errs, err)
}
//Expiry time check
if paste.DeleteTime <= time.Now().Unix() {
//Delete expired paste
err := DelPaste(pasteName)
if err != nil {
errs = append(errs, err)
}
}
}
}
return errs
}
func init() {
//Make paste dir (errors are ignored)
os.MkdirAll(pasteDir, pasteFileMod)
}

@ -0,0 +1,174 @@
// Copyright (C) 2021-2022 Leonid Maslakov.
// This file is part of Lenpaste.
// Lenpaste is free software: you can redistribute it
// and/or modify it under the terms of the
// GNU Affero Public License as published by the
// Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
// Lenpaste is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero Public License for more details.
// You should have received a copy of the GNU Affero Public License along with Lenpaste.
// If not, see <https://www.gnu.org/licenses/>.
package storage
import (
"time"
)
type Paste struct {
ID string `json:"id"` // Ignored when creating
Title string `json:"title"`
Body string `json:"body"`
CreateTime int64 `json:"createTime"` // Ignored when creating
DeleteTime int64 `json:"deleteTime"`
OneUse bool `json:"oneUse"`
//Syntax string `json:"syntax"`
//Password string `json:"password"`
}
func (dbInfo DB) PasteAdd(paste Paste) (Paste, error) {
// Open DB
db, err := dbInfo.openDB()
if err != nil {
return paste, err
}
defer db.Close()
// Generate ID
paste.ID, err = genTokenCrypto(8)
if err != nil {
return paste, err
}
// Add
_, err = db.Exec(
`INSERT INTO "pastes" ("id", "title", "body", "create_time", "delete_time", "one_use") VALUES (?, ?, ?, ?, ?, ?)`,
paste.ID, paste.Title, paste.Body, time.Now().Unix(), paste.DeleteTime, paste.OneUse,
)
if err != nil {
return paste, err
}
return paste, nil
}
func (dbInfo DB) PasteDelete(id string) error {
// Open DB
db, err := dbInfo.openDB()
if err != nil {
return err
}
defer db.Close()
// Delete
result, err := db.Exec(
`DELETE FROM "pastes" WHERE id = ?`,
id,
)
if err != nil {
return err
}
// Check result
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrNotFoundID
}
return nil
}
func (dbInfo DB) PasteGet(id string) (Paste, error) {
var paste Paste
// Open DB
db, err := dbInfo.openDB()
if err != nil {
return paste, err
}
defer db.Close()
// Make query
row := db.QueryRow(
`SELECT "id", "title", "body", "create_time", "delete_time", "one_use" FROM "pastes" WHERE "id" = ?`,
id,
)
// Read query
err = row.Scan(&paste.ID, &paste.Title, &paste.Body, &paste.CreateTime, &paste.DeleteTime, &paste.OneUse)
if err != nil {
return paste, err
}
return paste, nil
}
func (dbInfo DB) PasteGetList() ([]Paste, error) {
var pastes []Paste
// Open DB
db, err := dbInfo.openDB()
if err != nil {
return pastes, err
}
defer db.Close()
// Make query
rows, err := db.Query(
`SELECT "id", "title", "body", "create_time", "delete_time", "one_use" FROM "pastes"`,
)
if err != nil {
return pastes, err
}
// Read query
for rows.Next() {
var paste Paste
err = rows.Scan(&paste.ID, &paste.Title, &paste.Body, &paste.CreateTime, &paste.DeleteTime, &paste.OneUse)
if err != nil {
return pastes, err
}
pastes = append(pastes, paste)
}
return pastes, nil
}
func (dbInfo DB) PasteDeleteExpired() (int64, error) {
// Open DB
db, err := dbInfo.openDB()
if err != nil {
return 0, err
}
defer db.Close()
// Delete
result, err := db.Exec(
`DELETE FROM "pastes" WHERE delete_time < ?`,
time.Now().Unix(),
)
if err != nil {
return 0, err
}
// Check result
rowsAffected, err := result.RowsAffected()
if err != nil {
return rowsAffected, err
}
return rowsAffected, nil
}

@ -0,0 +1,41 @@
// Copyright (C) 2021-2022 Leonid Maslakov.
// This file is part of Lenpaste.
// Lenpaste is free software: you can redistribute it
// and/or modify it under the terms of the
// GNU Affero Public License as published by the
// Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
// Lenpaste is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero Public License for more details.
// You should have received a copy of the GNU Affero Public License along with Lenpaste.
// If not, see <https://www.gnu.org/licenses/>.
package web
import(
"os"
"io/ioutil"
)
func readFile(path string) ([]byte, error) {
// Open file
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
// Read file
fileByte, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
return fileByte, nil
}

@ -0,0 +1,109 @@
// Copyright (C) 2021-2022 Leonid Maslakov.
// This file is part of Lenpaste.
// Lenpaste is free software: you can redistribute it
// and/or modify it under the terms of the
// GNU Affero Public License as published by the
// Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
// Lenpaste is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero Public License for more details.
// You should have received a copy of the GNU Affero Public License along with Lenpaste.
// If not, see <https://www.gnu.org/licenses/>.
package web
import (
"git.lcomrade.su/root/lenpaste/internal/logger"
"git.lcomrade.su/root/lenpaste/internal/storage"
"html/template"
"path/filepath"
)
type Data struct {
DB storage.DB
Log logger.Config
StyleCSS []byte
Main *template.Template
Docs *template.Template
DocsApiV1 *template.Template
CreatePaste *template.Template
PastePage *template.Template
ErrorPage *template.Template
}
func Load(db storage.DB, webDir string) (Data, error) {
var data Data
var err error
// Save DB info
data.DB = db
// style.css file
data.StyleCSS, err = readFile(filepath.Join(webDir, "style.css"))
if err != nil {
return data, err
}
// main.tmpl
data.Main, err = template.ParseFiles(
filepath.Join(webDir, "base.tmpl"),
filepath.Join(webDir, "main.tmpl"),
)
if err != nil {
return data, err
}
// new.tmpl
data.CreatePaste, err = template.ParseFiles(
filepath.Join(webDir, "base.tmpl"),
filepath.Join(webDir, "new.tmpl"),
)
if err != nil {
return data, err
}
// paste.tmpl
data.PastePage, err = template.ParseFiles(
filepath.Join(webDir, "base.tmpl"),
filepath.Join(webDir, "paste.tmpl"),
)
if err != nil {
return data, err
}
// docs.tmpl
data.Docs, err = template.ParseFiles(
filepath.Join(webDir, "base.tmpl"),
filepath.Join(webDir, "docs.tmpl"),
)
if err != nil {
return data, err
}
// docs_apiv1.tmpl
data.DocsApiV1, err = template.ParseFiles(
filepath.Join(webDir, "base.tmpl"),
filepath.Join(webDir, "docs_apiv1.tmpl"),
)
if err != nil {
return data, err
}
// error.tmpl
data.ErrorPage, err = template.ParseFiles(
filepath.Join(webDir, "base.tmpl"),
filepath.Join(webDir, "error.tmpl"),
)
if err != nil {
return data, err
}
return data, nil
}

@ -0,0 +1,39 @@
// Copyright (C) 2021-2022 Leonid Maslakov.
// This file is part of Lenpaste.
// Lenpaste is free software: you can redistribute it
// and/or modify it under the terms of the
// GNU Affero Public License as published by the
// Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
// Lenpaste is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero Public License for more details.
// You should have received a copy of the GNU Affero Public License along with Lenpaste.
// If not, see <https://www.gnu.org/licenses/>.
package web
import(
"net/http"
)
// Pattern: /docs
func (data Data) DocsHand(rw http.ResponseWriter, req *http.Request) {
data.Log.HttpRequest(req)
rw.Header().Set("Content-Type", "text/html")
data.Docs.Execute(rw, "")
}
// Pattern: /docs/apiv1
func (data Data) DocsAPIV1Hand(rw http.ResponseWriter, req *http.Request) {
data.Log.HttpRequest(req)
rw.Header().Set("Content-Type", "text/html")
data.DocsApiV1.Execute(rw, "")
}

@ -0,0 +1,83 @@
// Copyright (C) 2021-2022 Leonid Maslakov.
// This file is part of Lenpaste.
// Lenpaste is free software: you can redistribute it
// and/or modify it under the terms of the
// GNU Affero Public License as published by the
// Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
// Lenpaste is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero Public License for more details.
// You should have received a copy of the GNU Affero Public License along with Lenpaste.
// If not, see <https://www.gnu.org/licenses/>.
package web
import(
"net/http"
)
type errorTmpl struct {
Code int
Error string
}
func (data Data) errorBadRequest(rw http.ResponseWriter, req *http.Request) {
// Write response header
rw.Header().Set("Content-type", "text/html")
rw.WriteHeader(400)
// Render template
errData := errorTmpl{
Error: "Bad Request",
Code: 400,
}
e := data.ErrorPage.Execute(rw, errData)
if e != nil {
data.Log.HttpError(req, e)
}
}
func (data Data) errorNotFound(rw http.ResponseWriter, req *http.Request) {
// Write response header
rw.Header().Set("Content-type", "text/html")
rw.WriteHeader(404)
// Render template
errData := errorTmpl{
Error: "Not Found",
Code: 404,
}
e := data.ErrorPage.Execute(rw, errData)
if e != nil {
data.Log.HttpError(req, e)
}
}
func (data Data) errorInternal(rw http.ResponseWriter, req *http.Request, err error) {
// Write to log
data.Log.HttpError(req, err)
// Write response header
rw.Header().Set("Content-type", "text/html")
rw.WriteHeader(500)
// Render template
errData := errorTmpl{
Error: "Internal Server Error",
Code: 500,
}
e := data.ErrorPage.Execute(rw, errData)
if e != nil {
data.Log.HttpError(req, e)
}
}

@ -0,0 +1,59 @@
// Copyright (C) 2021-2022 Leonid Maslakov.
// This file is part of Lenpaste.
// Lenpaste is free software: you can redistribute it
// and/or modify it under the terms of the
// GNU Affero Public License as published by the
// Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
// Lenpaste is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero Public License for more details.
// You should have received a copy of the GNU Affero Public License along with Lenpaste.
// If not, see <https://www.gnu.org/licenses/>.
package web
import (
"git.lcomrade.su/root/lenpaste/internal/storage"
"net/http"
)
// Pattern: /
func (data Data) MainHand(rw http.ResponseWriter, req *http.Request) {
// Log request
data.Log.HttpRequest(req)
// If main page
if req.URL.Path == "/" {
rw.Header().Set("Content-Type", "text/html")
data.Main.Execute(rw, "")
return
}
// Get paste
pasteID := string([]rune(req.URL.Path)[1:])
paste, err := data.DB.PasteGet(pasteID)
if err != nil {
if err == storage.ErrNotFoundID {
data.errorNotFound(rw, req)
return
} else {
data.errorInternal(rw, req, err)
return
}
}
// Show paste
err = data.PastePage.Execute(rw, paste)
if err != nil {
data.errorInternal(rw, req, err)
return
}
}

@ -0,0 +1,73 @@
// Copyright (C) 2021-2022 Leonid Maslakov.
// This file is part of Lenpaste.
// Lenpaste is free software: you can redistribute it
// and/or modify it under the terms of the
// GNU Affero Public License as published by the
// Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
// Lenpaste is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero Public License for more details.
// You should have received a copy of the GNU Affero Public License along with Lenpaste.
// If not, see <https://www.gnu.org/licenses/>.
package web
import(
"git.lcomrade.su/root/lenpaste/internal/storage"
"net/http"
"time"
)
// Pattern: /new
func (data Data) CreatePasteHand(rw http.ResponseWriter, req *http.Request) {
// Log request
data.Log.HttpRequest(req)
// Read request
req.ParseForm()
if req.PostForm.Get("body") != "" {
paste := storage.Paste{
Title: req.PostForm.Get("title"),
Body: req.PostForm.Get("body"),
OneUse: false,
}
// Get expiration time
expirTime, err := storage.ExpirationToTime(req.PostForm.Get("expiration"))
if err != nil {
data.errorBadRequest(rw, req)
return
}
paste.DeleteTime = time.Now().Unix() + expirTime
// Get "one use" parameter
if req.PostForm.Get("oneUse") == "true" {
paste.OneUse = true
}
// Create paste
_, err = data.DB.PasteAdd(paste)
if err != nil {
data.errorInternal(rw, req, err)
return
}
return
}
// Else show create page
rw.Header().Set("Content-Type", "text/html")
err := data.CreatePaste.Execute(rw, "")
if err != nil {
data.errorInternal(rw, req, err)
return
}
}

@ -0,0 +1,30 @@
// Copyright (C) 2021-2022 Leonid Maslakov.
// This file is part of Lenpaste.
// Lenpaste is free software: you can redistribute it
// and/or modify it under the terms of the
// GNU Affero Public License as published by the
// Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
// Lenpaste is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero Public License for more details.
// You should have received a copy of the GNU Affero Public License along with Lenpaste.
// If not, see <https://www.gnu.org/licenses/>.
package web
import(
"net/http"
)
func (data Data) StyleCSSHand(rw http.ResponseWriter, req *http.Request) {
data.Log.HttpRequest(req)
rw.Header().Set("Content-Type", "text/css")
rw.Write(data.StyleCSS)
}

@ -1,30 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Done | Lenpaste</title>
<link rel="stylesheet" type="text/css" href="/style.css"/>