Add ability to configure the application; reset db; better logging

This commit is contained in:
Vikas
2024-06-02 19:42:26 +05:30
parent bc11d72720
commit 0f23e11414
12 changed files with 113 additions and 28 deletions

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@
*.dll
*.so
*.dylib
bin/
# Test binary, built with `go test -c`
*.test

8
config/config.go Normal file
View File

@@ -0,0 +1,8 @@
package config
var (
ServerAddr = ":8080"
AppName = "PastePass"
DBPath = "pastes.boltdb"
ResetDB = false
)

View File

@@ -3,7 +3,7 @@ package db
import (
"errors"
"fmt"
"log"
"log/slog"
"time"
"encoding/json"
@@ -26,15 +26,6 @@ type DB struct {
boltDB *bolt.DB
}
func NewDB(name string) (*DB, error) {
boltDB, err := bolt.Open(name, 0600, &bolt.Options{Timeout: 1 * time.Second})
if err != nil {
return nil, err
}
return &DB{boltDB: boltDB}, nil
}
func (d *DB) Close() error {
return d.boltDB.Close()
}
@@ -106,7 +97,7 @@ func (d *DB) Get(id string) (*Paste, error) {
func (d *DB) Decrypt(id string, key string) (string, error) {
// delete paste if expired
if _, err := d.Get(id); err == ErrPasteExpired {
if _, err := d.Get(id); errors.Is(err, ErrPasteExpired) {
return "", d.Delete(id)
}
@@ -186,7 +177,7 @@ func (d *DB) DeleteExpired() error {
for _, id := range expiredPastes {
if err := d.Delete(id); err != nil {
log.Println(fmt.Errorf("error deleting expired paste %s: %v", id, err))
slog.Error("error_deleting_expired_paste", "id", id, "error", err)
}
}
@@ -199,7 +190,7 @@ func (d *DB) DeleteExpiredPeriodically(interval time.Duration) {
for range ticker.C {
if err := d.DeleteExpired(); err != nil {
log.Println(fmt.Errorf("error deleting expired pastes: %v", err))
slog.Error("error_starting_expired_paste_job", "error", err)
}
}
}

36
db/utils.go Normal file
View File

@@ -0,0 +1,36 @@
package db
import (
"github.com/boltdb/bolt"
"log/slog"
"os"
"time"
)
func NewDB(path string, reset bool) (*DB, error) {
if reset {
removeDB(path)
}
boltDB, err := bolt.Open(path, 0600, &bolt.Options{Timeout: 1 * time.Second})
if err != nil {
return nil, err
}
return &DB{boltDB: boltDB}, nil
}
func removeDB(path string) {
slog.Info("resetting_db", "path", path)
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
slog.Error("db_does_not_exist", "path", path, "error", err)
return
}
if err := os.Remove(path); err != nil {
slog.Error("error_removing_db", "path", path, "error", err)
return
}
slog.Info("db_removed", "path", path)
}

15
main.go
View File

@@ -1,7 +1,10 @@
package main
import (
"flag"
"github.com/v1k45/pastepass/config"
"log"
"log/slog"
"net/http"
"time"
@@ -10,14 +13,22 @@ import (
)
func main() {
flag.StringVar(&config.ServerAddr, "server-addr", config.ServerAddr, "The server address to listen on")
flag.StringVar(&config.AppName, "app-name", config.AppName, "The name of the application (e.g. ACME PastePass)")
flag.StringVar(&config.DBPath, "db-path", config.DBPath, "The path to the database file")
flag.BoolVar(&config.ResetDB, "reset-db", config.ResetDB, "Reset the database on startup")
flag.Parse()
// Open the database
boltdb, err := db.NewDB("pastes.boltdb")
boltdb, err := db.NewDB(config.DBPath, config.ResetDB)
if err != nil {
log.Fatalf("failed to open database: %v", err)
}
go boltdb.DeleteExpiredPeriodically(time.Minute * 5)
slog.Info("starting_server", "server_addr", config.ServerAddr, "app_name", config.AppName, "db_name", config.DBPath)
// Start the web server
handler := web.NewHandler(boltdb)
http.ListenAndServe(":8080", handler.Router())
http.ListenAndServe(config.ServerAddr, handler.Router())
}

View File

@@ -1,5 +1,7 @@
package views
import "github.com/v1k45/pastepass/config"
templ base() {
<!doctype html>
<html lang="en">
@@ -8,14 +10,14 @@ templ base() {
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark" />
<link rel="stylesheet" href="/static/pico.min.css"/>
<title>Paste</title>
<title>{ config.AppName } - secure, one-time paste bin.</title>
</head>
<body>
<main class="container">
<nav>
<ul>
<li>
<a href="/">PastePass</a> &mdash; secure one-time paste bin.
<a href="/">{ config.AppName }</a> &mdash; secure one-time paste bin.
</li>
</ul>
</nav>
@@ -25,7 +27,7 @@ templ base() {
<footer>
<small>
<p style="color: #8891A4;">
PastePass is open-source and free to use. <a href="https://github.com/v1k45/pastepass">View source on github</a>.
<a href="https://github.com/v1k45/pastepass">PastePass</a> is open-source and free to use. Created by <a href="https://github.com/v1k45/pastepass">v1k45</a>.
</p>
<p style="color: #8891A4;">
Pasted content is encrypted and stored with an expiration time. Once the content is read, it is deleted from the server. <br/>

View File

@@ -10,6 +10,8 @@ import "context"
import "io"
import "bytes"
import "github.com/v1k45/pastepass/config"
func base() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
@@ -23,7 +25,33 @@ func base() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"color-scheme\" content=\"light dark\"><link rel=\"stylesheet\" href=\"/static/pico.min.css\"><title>Paste</title></head><body><main class=\"container\"><nav><ul><li><a href=\"/\">PastePass</a> &mdash; secure one-time paste bin.</li></ul></nav><hr>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"color-scheme\" content=\"light dark\"><link rel=\"stylesheet\" href=\"/static/pico.min.css\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(config.AppName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/base.templ`, Line: 13, Col: 31}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" - secure, one-time paste bin.</title></head><body><main class=\"container\"><nav><ul><li><a href=\"/\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(config.AppName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/base.templ`, Line: 20, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a> &mdash; secure one-time paste bin.</li></ul></nav><hr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -31,7 +59,7 @@ func base() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<hr><footer><small><p style=\"color: #8891A4;\">PastePass is open-source and free to use. <a href=\"https://github.com/v1k45/pastepass\">View source on github</a>.</p><p style=\"color: #8891A4;\">Pasted content is encrypted and stored with an expiration time. Once the content is read, it is deleted from the server. <br></p></small></footer></main><script src=\"/static/pastepass.js\"></script></body></html>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<hr><footer><small><p style=\"color: #8891A4;\"><a href=\"https://github.com/v1k45/pastepass\">PastePass</a> is open-source and free to use. Created by <a href=\"https://github.com/v1k45/pastepass\">v1k45</a>.</p><p style=\"color: #8891A4;\">Pasted content is encrypted and stored with an expiration time. Once the content is read, it is deleted from the server. <br></p></small></footer></main><script src=\"/static/pastepass.js\"></script></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -13,7 +13,7 @@ templ Decrypt(text string) {
</p>
</hgroup>
<div>
<pre id="pastedContent" style="padding: 1rem; min-height: 10rem;">{text}</pre>
<pre id="pastedContent" style="padding: 1rem; min-height: 10rem; max-height: 30rem;">{text}</pre>
<div>
<button onclick="copyText(this, '#pastedContent')" data-tooltip="Click to copy">Copy content</button>
</div>

View File

@@ -29,14 +29,14 @@ func Decrypt(text string) templ.Component {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"padding-bottom: 5rem;\"><hgroup><h3>View Paste</h3><p><small style=\"color: #8891A4;\">Please make sure to save the content before closing this page. This paste has been deleted and will no longer be available for viewing again.</small></p></hgroup><div><pre id=\"pastedContent\" style=\"padding: 1rem; min-height: 10rem;\">")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div style=\"padding-bottom: 5rem;\"><hgroup><h3>View Paste</h3><p><small style=\"color: #8891A4;\">Please make sure to save the content before closing this page. This paste has been deleted and will no longer be available for viewing again.</small></p></hgroup><div><pre id=\"pastedContent\" style=\"padding: 1rem; min-height: 10rem; max-height: 30rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/decrypt.templ`, Line: 16, Col: 87}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/decrypt.templ`, Line: 16, Col: 106}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {

View File

@@ -5,8 +5,8 @@ templ Index() {
<form method="post">
<textarea
name="text"
placeholder="Paste your secret here, select expiration time and click 'Submit'"
aria-label="Paste your secret here"
placeholder="Paste your secret here, select expiration time and click 'Paste'"
aria-label="Paste your secret here, select expiration time and click 'Paste'"
rows="10"
required
autofocus

View File

@@ -29,7 +29,7 @@ func Index() templ.Component {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form method=\"post\"><textarea name=\"text\" placeholder=\"Paste your secret here, select expiration time and click &#39;Submit&#39;\" aria-label=\"Paste your secret here\" rows=\"10\" required autofocus></textarea><div style=\"display: flex; align-items: end; justify-content: space-between;\"><div style=\"min-width: 33.33%\"><label for=\"expiration\">Expires In</label> <select id=\"expiration\" name=\"expiration\" aria-label=\"Expires In\"><option value=\"1h\" selected>1 Hour</option> <option value=\"1d\">1 Day</option> <option value=\"1w\">1 Week</option> <option value=\"2w\">2 weeks</option> <option value=\"4w\">4 weeks</option></select></div><div style=\"min-width: 33.33%\"><button type=\"submit\">Paste</button></div></div></form>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form method=\"post\"><textarea name=\"text\" placeholder=\"Paste your secret here, select expiration time and click &#39;Paste&#39;\" aria-label=\"Paste your secret here, select expiration time and click &#39;Paste&#39;\" rows=\"10\" required autofocus></textarea><div style=\"display: flex; align-items: end; justify-content: space-between;\"><div style=\"min-width: 33.33%\"><label for=\"expiration\">Expires In</label> <select id=\"expiration\" name=\"expiration\" aria-label=\"Expires In\"><option value=\"1h\" selected>1 Hour</option> <option value=\"1d\">1 Day</option> <option value=\"1w\">1 Week</option> <option value=\"2w\">2 weeks</option> <option value=\"4w\">4 weeks</option></select></div><div style=\"min-width: 33.33%\"><button type=\"submit\">Paste</button></div></div></form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -3,6 +3,7 @@ package web
import (
"context"
"fmt"
"log/slog"
"net/http"
"github.com/v1k45/pastepass/db"
@@ -26,18 +27,21 @@ func (h *Handler) Index(w http.ResponseWriter, r *http.Request) {
func (h *Handler) Paste(w http.ResponseWriter, r *http.Request) {
pastedText := r.FormValue("text")
if pastedText == "" {
slog.Error("validation_error", "error", "paste content is required")
errorResponse(w, http.StatusBadRequest, "Invalid Data", "Paste content is required.")
return
}
expiresAt, err := getExpiresAt(r.FormValue("expiration"))
if err != nil {
slog.Error("validation_error", "error", err)
errorResponse(w, http.StatusBadRequest, "Invalid Data", "Invalid expiration time.")
return
}
paste, err := h.DB.NewPaste(pastedText, expiresAt)
if err != nil {
slog.Error("cannot_create_paste", "error", err)
errorResponse(w, http.StatusInternalServerError, "Internal Server Error", "Failed to create paste, please try again later.")
return
}
@@ -55,7 +59,9 @@ func (h *Handler) Paste(w http.ResponseWriter, r *http.Request) {
}
func (h *Handler) View(w http.ResponseWriter, r *http.Request) {
if _, err := h.DB.Get(r.PathValue("id")); err != nil {
id := r.PathValue("id")
if _, err := h.DB.Get(id); err != nil {
slog.Error("cannot_view_paste", "error", err, "id", id)
errorResponse(w, http.StatusNotFound, "Not Found", "The paste you are looking for is either expired or does not exist.")
return
}
@@ -65,8 +71,10 @@ func (h *Handler) View(w http.ResponseWriter, r *http.Request) {
}
func (h *Handler) Decrypt(w http.ResponseWriter, r *http.Request) {
decryptedText, err := h.DB.Decrypt(r.PathValue("id"), r.PathValue("key"))
id, key := r.PathValue("id"), r.PathValue("key")
decryptedText, err := h.DB.Decrypt(id, key)
if err != nil {
slog.Error("cannot_decrypt_paste", "error", err, "id", id)
errorResponse(
w, http.StatusInternalServerError,
"Internal Server Error", "The paste you are looking for is either expired, corrputed or does not exist.")