mirror of
https://github.com/v1k45/pastepass.git
synced 2026-08-21 21:26:02 +00:00
initial commit; working state
This commit is contained in:
+28
@@ -0,0 +1,28 @@
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
|
||||
# database
|
||||
*.boltdb
|
||||
@@ -0,0 +1,67 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
random "math/rand"
|
||||
)
|
||||
|
||||
func encrypt(text string, key string) ([]byte, error) {
|
||||
c, err := aes.NewCipher([]byte(key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gcm.Seal(nonce, nonce, []byte(text), nil), nil
|
||||
}
|
||||
|
||||
func decrypt(ciphertext []byte, key string) (string, error) {
|
||||
c, err := aes.NewCipher([]byte(key))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create cipher: %w", err)
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(c)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create GCM: %w", err)
|
||||
}
|
||||
|
||||
nonceSize := gcm.NonceSize()
|
||||
if len(ciphertext) < nonceSize {
|
||||
return "", errors.New("ciphertext too short")
|
||||
}
|
||||
|
||||
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
||||
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decrypt: %w", err)
|
||||
}
|
||||
|
||||
return string(plaintext), nil
|
||||
}
|
||||
|
||||
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
|
||||
const keyLength = 32
|
||||
|
||||
func randomKey() string {
|
||||
b := make([]rune, keyLength)
|
||||
for i := range b {
|
||||
b[i] = letterRunes[random.Intn(len(letterRunes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
var (
|
||||
pastesBucketName = []byte("pastes")
|
||||
metadataBucketName = []byte("metadata")
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPasteNotFound = errors.New("paste not found")
|
||||
ErrBucketNotFound = errors.New("bucket not found")
|
||||
ErrPasteExpired = errors.New("paste expired")
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
func (d *DB) NewPaste(text string, expiresAt time.Time) (*Paste, error) {
|
||||
paste, err := NewEncryptedPaste(text, expiresAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return paste, d.save(paste)
|
||||
}
|
||||
|
||||
func (d *DB) save(paste *Paste) error {
|
||||
return d.boltDB.Update(func(tx *bolt.Tx) error {
|
||||
// Save encrypted paste
|
||||
pasteBucket, err := tx.CreateBucketIfNotExists(pastesBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = pasteBucket.Put([]byte(paste.ID), paste.EncryptedBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save metadata to check expiration
|
||||
pasteJson, err := json.Marshal(paste)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
metadataBucket, err := tx.CreateBucketIfNotExists(metadataBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return metadataBucket.Put([]byte(paste.ID), pasteJson)
|
||||
})
|
||||
}
|
||||
|
||||
func (d *DB) Get(id string) (*Paste, error) {
|
||||
var paste Paste
|
||||
|
||||
err := d.boltDB.View(func(tx *bolt.Tx) error {
|
||||
// get metadata
|
||||
bucket := tx.Bucket(metadataBucketName)
|
||||
if bucket == nil {
|
||||
return ErrBucketNotFound
|
||||
}
|
||||
|
||||
// unmarshal metadata
|
||||
jsonPaste := bucket.Get([]byte(id))
|
||||
if jsonPaste == nil {
|
||||
return ErrPasteNotFound
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(jsonPaste, &paste); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure paste is not expired
|
||||
if time.Now().After(paste.ExpiresAt) {
|
||||
return ErrPasteExpired
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return &paste, err
|
||||
}
|
||||
|
||||
func (d *DB) Decrypt(id string, key string) (string, error) {
|
||||
// delete paste if expired
|
||||
if _, err := d.Get(id); err == ErrPasteExpired {
|
||||
return "", d.Delete(id)
|
||||
}
|
||||
|
||||
var decryptedText string
|
||||
err := d.boltDB.Update(func(tx *bolt.Tx) error {
|
||||
pasteBucket := tx.Bucket(pastesBucketName)
|
||||
if pasteBucket == nil {
|
||||
return ErrBucketNotFound
|
||||
}
|
||||
|
||||
encryptedPaste := pasteBucket.Get([]byte(id))
|
||||
if encryptedPaste == nil {
|
||||
return ErrPasteNotFound
|
||||
}
|
||||
|
||||
var err error
|
||||
decryptedText, err = decrypt(encryptedPaste, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return decryptedText, d.Delete(id)
|
||||
}
|
||||
|
||||
func (d *DB) Delete(id string) error {
|
||||
return d.boltDB.Update(func(tx *bolt.Tx) error {
|
||||
pasteBucket := tx.Bucket(pastesBucketName)
|
||||
if pasteBucket == nil {
|
||||
return ErrBucketNotFound
|
||||
}
|
||||
|
||||
if err := pasteBucket.Delete([]byte(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
metadataBucket := tx.Bucket(metadataBucketName)
|
||||
if metadataBucket == nil {
|
||||
return ErrBucketNotFound
|
||||
}
|
||||
|
||||
return metadataBucket.Delete([]byte(id))
|
||||
})
|
||||
}
|
||||
|
||||
func (d *DB) DeleteExpired() error {
|
||||
var expiredPastes []string
|
||||
err := d.boltDB.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(metadataBucketName)
|
||||
if bucket == nil {
|
||||
return ErrBucketNotFound
|
||||
}
|
||||
|
||||
return bucket.ForEach(func(k, v []byte) error {
|
||||
var paste Paste
|
||||
if err := json.Unmarshal(v, &paste); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if time.Now().After(paste.ExpiresAt) {
|
||||
expiredPastes = append(expiredPastes, string(k))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting expired pastes: %v", err)
|
||||
}
|
||||
|
||||
for _, id := range expiredPastes {
|
||||
if err := d.Delete(id); err != nil {
|
||||
log.Println(fmt.Errorf("error deleting expired paste %s: %v", id, err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DB) DeleteExpiredPeriodically(interval time.Duration) {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
if err := d.DeleteExpired(); err != nil {
|
||||
log.Println(fmt.Errorf("error deleting expired pastes: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Paste struct {
|
||||
ID string `json:"id"`
|
||||
Text string `json:"-"`
|
||||
EncryptedBytes []byte `json:"-"`
|
||||
Key string `json:"-"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
}
|
||||
|
||||
func NewEncryptedPaste(text string, expiresAt time.Time) (*Paste, error) {
|
||||
key := randomKey()
|
||||
encryptedText, err := encrypt(text, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Paste{
|
||||
ID: randomKey(),
|
||||
Text: text,
|
||||
EncryptedBytes: encryptedText,
|
||||
Key: key,
|
||||
CreatedAt: time.Now(),
|
||||
ExpiresAt: expiresAt,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
module github.com/v1k45/paste
|
||||
|
||||
go 1.22.3
|
||||
|
||||
require (
|
||||
github.com/a-h/templ v0.2.707
|
||||
github.com/boltdb/bolt v1.3.1
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.19.0 // indirect
|
||||
@@ -0,0 +1,8 @@
|
||||
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
|
||||
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
|
||||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/v1k45/paste/db"
|
||||
"github.com/v1k45/paste/web"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Open the database
|
||||
boltdb, err := db.NewDB("pastes.boltdb")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open database: %v", err)
|
||||
}
|
||||
go boltdb.DeleteExpiredPeriodically(time.Minute * 5)
|
||||
|
||||
// Start the web server
|
||||
handler := web.NewHandler(boltdb)
|
||||
http.ListenAndServe(":8080", handler.Router())
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package views
|
||||
|
||||
templ base() {
|
||||
<!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="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"/>
|
||||
<title>Paste</title>
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/">Paste</a> — secure one-time paste bin.
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<hr />
|
||||
{ children... }
|
||||
<hr />
|
||||
<footer>
|
||||
<small>
|
||||
<p style="color: #8891A4;">
|
||||
Paste is open-source and free to use. <a href="https://github.com/v1k45/paste">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>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.707
|
||||
package views
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
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)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
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=\"https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css\"><title>Paste</title></head><body><main class=\"container\"><nav><ul><li><a href=\"/\">Paste</a> — secure one-time paste bin.</li></ul></nav><hr>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<hr><footer><small><p style=\"color: #8891A4;\">Paste is open-source and free to use. <a href=\"https://github.com/v1k45/paste\">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></body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package views
|
||||
|
||||
templ Decrypt(text string) {
|
||||
@base() {
|
||||
<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;">{text}</pre>
|
||||
<div>
|
||||
<button onclick="copyText(this, '#pastedContent')" data-tooltip="Click to copy">Copy content</button>
|
||||
</div>
|
||||
</div>
|
||||
@copyTextScript()
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.707
|
||||
package views
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
func Decrypt(text string) 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)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var2 := 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)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
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;\">")
|
||||
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}
|
||||
}
|
||||
_, 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("</pre><div><button onclick=\"copyText(this, '#pastedContent')\" data-tooltip=\"Click to copy\">Copy content</button></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = copyTextScript().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
templ_7745c5c3_Err = base().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package views
|
||||
|
||||
templ Error(title, message string) {
|
||||
@base() {
|
||||
<div style="padding-bottom: 5rem;">
|
||||
<hgroup>
|
||||
<h3>{title}</h3>
|
||||
<small style="color: #8891A4;">
|
||||
{message}
|
||||
</small>
|
||||
</hgroup>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.707
|
||||
package views
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
func Error(title, message string) 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)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var2 := 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)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
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>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/error.templ`, Line: 7, Col: 22}
|
||||
}
|
||||
_, 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("</h3><small style=\"color: #8891A4;\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(message)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/error.templ`, Line: 9, Col: 24}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</small></hgroup></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
templ_7745c5c3_Err = base().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package views
|
||||
|
||||
templ Index() {
|
||||
@base() {
|
||||
<form method="post">
|
||||
<textarea
|
||||
name="text"
|
||||
placeholder="Paste your secret here, select expiration time and click 'Submit'"
|
||||
aria-label="Paste your secret here"
|
||||
rows="10"
|
||||
required
|
||||
autofocus
|
||||
>
|
||||
</textarea>
|
||||
<div style="display: flex; align-items: end; justify-content: space-between;">
|
||||
<div style="width: 33.33%">
|
||||
<label for="expiration">Expires In</label>
|
||||
<select id="expiration" expired 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="width: 33.33%">
|
||||
<button type="submit">Paste</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.707
|
||||
package views
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
func Index() 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)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var2 := 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)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
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 'Submit'\" aria-label=\"Paste your secret here\" rows=\"10\" required autofocus></textarea><div style=\"display: flex; align-items: end; justify-content: space-between;\"><div style=\"width: 33.33%\"><label for=\"expiration\">Expires In</label> <select id=\"expiration\" expired 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=\"width: 33.33%\"><button type=\"submit\">Paste</button></div></div></form>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
templ_7745c5c3_Err = base().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package views
|
||||
|
||||
templ PasteSuccess(url string) {
|
||||
@base() {
|
||||
<div style="padding-bottom: 5rem;">
|
||||
<hgroup>
|
||||
<h3>Paste Created</h3>
|
||||
<p>
|
||||
<small style="color: #8891A4;">
|
||||
Your secret paste has been created. Share the following link with the recipient:
|
||||
</small>
|
||||
</p>
|
||||
</hgroup>
|
||||
<div>
|
||||
<pre id="url" style="padding: 1rem; min-height: 3rem;">{url}</pre>
|
||||
<div>
|
||||
<button onclick="copyText(this, '#url')" data-tooltip="Click to copy">Copy content</button>
|
||||
<a href={ templ.SafeURL(url) } style="margin-left: 1rem;">View Paste</a>
|
||||
</div>
|
||||
</div>
|
||||
@copyTextScript()
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.707
|
||||
package views
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
func PasteSuccess(url string) 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)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var2 := 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)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
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>Paste Created</h3><p><small style=\"color: #8891A4;\">Your secret paste has been created. Share the following link with the recipient:</small></p></hgroup><div><pre id=\"url\" style=\"padding: 1rem; min-height: 3rem;\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(url)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/paste.templ`, Line: 15, Col: 71}
|
||||
}
|
||||
_, 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("</pre><div><button onclick=\"copyText(this, '#url')\" data-tooltip=\"Click to copy\">Copy content</button> <a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 templ.SafeURL = templ.SafeURL(url)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" style=\"margin-left: 1rem;\">View Paste</a></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = copyTextScript().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
templ_7745c5c3_Err = base().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package views
|
||||
|
||||
templ copyTextScript() {
|
||||
<script>
|
||||
function copyText(event, selector) {
|
||||
var pastedContent = document.querySelector(selector);
|
||||
|
||||
// Create a range and select the text
|
||||
var range = document.createRange();
|
||||
range.selectNode(pastedContent);
|
||||
window.getSelection().removeAllRanges();
|
||||
window.getSelection().addRange(range);
|
||||
|
||||
// Copy the selected text
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(pastedContent.innerText).then(function() {
|
||||
event.dataset.tooltip = 'Copied!';
|
||||
event.innerText = 'Copied!';
|
||||
setTimeout(function() {
|
||||
event.innerText = 'Copy content';
|
||||
event.dataset.tooltip = 'Click to copy';
|
||||
event.blur();
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.707
|
||||
package views
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
func copyTextScript() 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)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<script>\n function copyText(event, selector) {\n var pastedContent = document.querySelector(selector);\n\n // Create a range and select the text\n var range = document.createRange();\n range.selectNode(pastedContent);\n window.getSelection().removeAllRanges();\n window.getSelection().addRange(range);\n\n // Copy the selected text\n if (navigator.clipboard) {\n navigator.clipboard.writeText(pastedContent.innerText).then(function() {\n event.dataset.tooltip = 'Copied!';\n event.innerText = 'Copied!';\n setTimeout(function() {\n event.innerText = 'Copy content';\n event.dataset.tooltip = 'Click to copy';\n event.blur();\n }, 1000);\n });\n }\n }\n </script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package views
|
||||
|
||||
templ View() {
|
||||
@base() {
|
||||
<div style="padding-bottom: 5rem;">
|
||||
<hgroup>
|
||||
<h3>View Paste</h3>
|
||||
<p>
|
||||
<small style="color: #8891A4;">
|
||||
You can only view this paste once. Make sure to copy it before you close this page.
|
||||
</small>
|
||||
</p>
|
||||
</hgroup>
|
||||
<form style="width: 33.33%;" method="post">
|
||||
<button type="submit">Show Paste</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.707
|
||||
package views
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
func View() 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)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var2 := 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)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
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;\">You can only view this paste once. Make sure to copy it before you close this page.</small></p></hgroup><form style=\"width: 33.33%;\" method=\"post\"><button type=\"submit\">Show Paste</button></form></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
templ_7745c5c3_Err = base().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/v1k45/paste/db"
|
||||
"github.com/v1k45/paste/views"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
DB *db.DB
|
||||
}
|
||||
|
||||
func NewHandler(db *db.DB) *Handler {
|
||||
return &Handler{DB: db}
|
||||
}
|
||||
|
||||
func (h *Handler) Index(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
component := views.Index()
|
||||
component.Render(context.Background(), w)
|
||||
}
|
||||
|
||||
func (h *Handler) Paste(w http.ResponseWriter, r *http.Request) {
|
||||
pastedText := r.FormValue("text")
|
||||
if pastedText == "" {
|
||||
errorResponse(w, http.StatusBadRequest, "Invalid Data", "Paste content is required.")
|
||||
return
|
||||
}
|
||||
|
||||
expiresAt, err := getExpiresAt(r.FormValue("expiration"))
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusBadRequest, "Invalid Data", "Invalid expiration time.")
|
||||
return
|
||||
}
|
||||
|
||||
paste, err := h.DB.NewPaste(pastedText, expiresAt)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "Internal Server Error", "Failed to create paste, please try again later.")
|
||||
return
|
||||
}
|
||||
|
||||
var scheme string
|
||||
if r.TLS == nil {
|
||||
scheme = "http"
|
||||
} else {
|
||||
scheme = "https"
|
||||
}
|
||||
url := fmt.Sprintf("%s://%s/p/%s/%s", scheme, r.Host, paste.ID, paste.Key)
|
||||
|
||||
component := views.PasteSuccess(url)
|
||||
component.Render(context.Background(), w)
|
||||
}
|
||||
|
||||
func (h *Handler) View(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := h.DB.Get(r.PathValue("id")); err != nil {
|
||||
errorResponse(w, http.StatusNotFound, "Not Found", "The paste you are looking for is either expired or does not exist.")
|
||||
return
|
||||
}
|
||||
|
||||
component := views.View()
|
||||
component.Render(context.Background(), w)
|
||||
}
|
||||
|
||||
func (h *Handler) Decrypt(w http.ResponseWriter, r *http.Request) {
|
||||
decryptedText, err := h.DB.Decrypt(r.PathValue("id"), r.PathValue("key"))
|
||||
if err != nil {
|
||||
errorResponse(
|
||||
w, http.StatusInternalServerError,
|
||||
"Internal Server Error", "The paste you are looking for is either expired, corrputed or does not exist.")
|
||||
return
|
||||
}
|
||||
|
||||
component := views.Decrypt(decryptedText)
|
||||
component.Render(context.Background(), w)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package web
|
||||
|
||||
import "net/http"
|
||||
|
||||
func (h *Handler) Router() http.Handler {
|
||||
router := http.NewServeMux()
|
||||
router.HandleFunc("GET /", h.Index)
|
||||
router.HandleFunc("POST /", h.Paste)
|
||||
router.HandleFunc("GET /p/{id}/{key}", h.View)
|
||||
router.HandleFunc("POST /p/{id}/{key}", h.Decrypt)
|
||||
return router
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/v1k45/paste/views"
|
||||
)
|
||||
|
||||
var (
|
||||
expirationTimes = map[string]time.Duration{
|
||||
"1h": time.Hour,
|
||||
"1d": 24 * time.Hour,
|
||||
"1w": 7 * 24 * time.Hour,
|
||||
"2w": 2 * 7 * 24 * time.Hour,
|
||||
"4w": 4 * 7 * 24 * time.Hour,
|
||||
}
|
||||
)
|
||||
|
||||
func getExpiresAt(expiresAt string) (time.Time, error) {
|
||||
expiresDuration, found := expirationTimes[expiresAt]
|
||||
if !found {
|
||||
return time.Time{}, errors.New("invalid expiration time")
|
||||
}
|
||||
|
||||
return time.Now().Add(expiresDuration), nil
|
||||
}
|
||||
|
||||
func errorResponse(w http.ResponseWriter, status int, title, message string) {
|
||||
w.WriteHeader(status)
|
||||
component := views.Error(title, message)
|
||||
component.Render(context.Background(), w)
|
||||
}
|
||||
Reference in New Issue
Block a user