diff --git a/.dockerignore b/.dockerignore
index e892ae2..4750f0f 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,6 +1,11 @@
node_modules
+Dockerfile*
+docker-compose*
+.dockerignore
.git
.gitignore
-*.md
-build
-.vscode
\ No newline at end of file
+README.md
+LICENSE
+.vscode
+Makefile
+.env
\ No newline at end of file
diff --git a/.env b/.env.example
similarity index 62%
rename from .env
rename to .env.example
index f1a8b60..70fc022 100644
--- a/.env
+++ b/.env.example
@@ -1,5 +1,6 @@
# Salt used for encryption. tbh i dont know if this helps at all since if
# someone has access to this file they can just read the salt
+# openssl rand -hex 32
export PAWSTE_SALT="banana"
# the port the server will listen on
@@ -14,37 +15,36 @@ export PAWSTE_ADMIN_PASSWORD="admin"
# is the list public.
export PAWSTE_PUBLIC_LIST="true"
-# public url for the server
-export PAWSTE_PUBLIC_URL="http://localhost:9454"
-
# should file upload be enabled
export PAWSTE_FILE_UPLOAD="true"
-
# max bytes for a file upload
# if multiple files are uploaded the total size
# enter a number or a multiplication expression
export PAWSTE_MAX_FILE_SIZE="1024 * 1024 * 10"
+
export PAWSTE_MAX_ENCRYPTED_FILE_SIZE="1024 * 1024 * 10"
# max content length for a paste
-export PAWSTE_MAX_CONTENT_LENGTH="1024 * 1024 * 10"
+export PAWSTE_MAX_CONTENT_LENGTH="5000"
# is a password needed to upload a file
# "" means no password is needed
-export PAWSTE_UPLOADING_PASSWORD=""
+export PAWSTE_FILE_UPLOADING_PASSWORD=""
-# disable never expiring pastes
-export PAWSTE_DISABLE_ETERNAL_PASTE="false"
+# never expiring pastes
+export PAWSTE_ETERNAL_PASTE="false"
-# disable read count
-export PAWSTE_DISABLE_READ_COUNT="false"
+# enable read count
+export PAWSTE_READ_COUNT="true"
+# whether file fetches should count as a read
+export PAWSTE_COUNT_FILE_USAGE="false"
-# disable burn after
-export PAWSTE_DISABLE_BURN_AFTER="false"
+# enable burn after
+export PAWSTE_BURN_AFTER="true"
# default expiration time for pastes
-# valid options 10min, 1min, 1h, 6h, 24h, 72h, 1w, never
+# Options are parsed, so you can use 6h, 10d, 2w, 1M etc.
export PAWSTE_DEFAULT_EXPIRY="1w"
# animal-animal-animal to something shorter, random characters
@@ -53,7 +53,14 @@ export PAWSTE_SHORT_PASTE_NAMES="false"
# url redirects pastes will have shortened urls, so basically above but only for redirects
export PAWSTE_SHORTEN_REDIRECT_PASTES="false"
-# this will delete the database and create a new one
-# only set this to true if you understand what it does
-# mostly for testing i guess until i figure out a better way to do this
-# export PAWSTE_I_UNDERSTAND_THE_RISKS="false"
\ No newline at end of file
+# whether to anonymize file names
+export PAWSTE_ANONYMISE_FILE_NAMES="false"
+
+# whether to normalize file names e.g. remove spaces, special characters
+export PAWSTE_NORMALIZE_FILE_NAMES="true"
+
+export PAWSTE_ANIME_GIRL_MODE="false"
+
+export GIN_MODE="release"
+
+export LOG_LEVEL="info"
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 0bd41c3..c929916 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,14 +2,12 @@
js/speed-highlight
node_modules
pawste
+pkg/build/*
build
-
-
.svelte-kit
-build
.DS_Store
-Thumbs.db
-svelte-dev/.env
-svelte-dev/.env.*
-svelte-dev/vite.config.js.timestamp-*
-svelte-dev/vite.config.ts.timestamp-*
\ No newline at end of file
+*.upx
+pawste_data*
+*.png
+.env
+.env.dev
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
index 62e0c58..a10cc52 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,13 +1,16 @@
{
- "tabWidth": 4,
- "useTabs": false,
- "plugins": ["prettier-plugin-go-template"],
+ "tabWidth": 4,
+ "useTabs": false,
+ "plugins": [
+ "prettier-plugin-svelte"
+ ],
"overrides": [
- {
- "files": ["*.html"],
- "options": {
- "parser": "go-template"
+ {
+ "files": "*.svelte",
+ "options": {
+ "parser": "svelte"
+ }
}
- }
- ]
-}
+ ],
+ "svelteBracketNewLine": false
+}
\ No newline at end of file
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index a74ca5c..79cca03 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -16,4 +16,4 @@
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []
-}
+}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index ed3575a..b402c8d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,45 +1,36 @@
-FROM node:20 AS node-builder
+FROM node:20-slim AS base
+
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
-
WORKDIR /app
+COPY package.json pnpm-lock.yaml ./
+COPY golte.config.ts ./
+COPY svelte.config.js ./
+COPY ./web /app/web
-COPY package.json ./
-COPY pnpm-lock.yaml ./
-
-RUN pnpm install
-
-COPY ./web ./web
+FROM base AS build
+RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod
+RUN pnpm run build
-RUN npx golte dev
-
-FROM golang:1.22.4 AS go-builder
+FROM golang:1.22.5-alpine AS go-build
WORKDIR /app
-
-COPY --from=node-builder /app/build ./build
-
ENV GOCACHE=/root/.cache/go-build
+ENV CGO_ENABLED=1
-COPY go.mod go.sum ./
-
-RUN go mod download
+RUN apk add gcc musl-dev
-COPY . .
+COPY --from=build /app/pkg/build /app/pkg/build
+COPY ./pkg /app/pkg/
+COPY main.go ./
+COPY go.mod go.sum ./
-RUN --mount=type=cache,target="/root/.cache/go-build" go build -ldflags "-s -w"
+RUN --mount=type=cache,target="/root/.cache/go-build" go build -o pawste "-ldflags=-s -w"
FROM alpine:latest
-RUN apk --no-cache add ca-certificates
-
-WORKDIR /root/
-
-COPY --from=go-builder /app/pawste /root/pawste
-COPY --from=go-builder /app/build /root/build
-COPY ./web /root/web
-
-EXPOSE 9454
+WORKDIR /app
+COPY --from=go-build /app/pawste /app/pawste
-CMD ["/root/pawste"]
\ No newline at end of file
+ENTRYPOINT [ "./pawste" ]
diff --git a/Makefile b/Makefile
index cdd6729..da67a67 100644
--- a/Makefile
+++ b/Makefile
@@ -1,16 +1,8 @@
-binary_name="pawste"
-
-build:
- npx golte dev --use-pnpm && go build -o $(binary_name) "-ldflags=-s -w"
-
-run:
- npx golte dev --use-pnpm && go run .
-
-runnopnpm:
+runnoweb:
go run .
clean:
rm $(binary_name)
-newdb:
- npx golte dev --use-pnpm && PAWSTE_I_UNDERSTAND_THE_RISKS="true" go run .
+rundocker:
+ docker compose up -d --build
\ No newline at end of file
diff --git a/README.md b/README.md
index 275f7a6..a230c3c 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,38 @@
# pawste
-Simple pastebin made in go.
+This is my attempt at making pastebin, **pawste**!
-**Look at the the [svelte branch](https://github.com/Masterjoona/pawste/tree/svelte) for most recent development.**
\ No newline at end of file
+### Public instance: [pawst.eu](https://pawst.eu)
+
+Or if you're savvy enough, you can host your own instance of pawste!
+
+## Features
+
+- single binary (fat ass one at that (shiki insanity), upx it or smth)
+- encryption (server side, ~~soon on client too~~ horror)
+- configurable file uploads
+- url shortening/redirection
+- public, private, editable pastes
+- readcounts and burn after n reads
+- works with sharex ([examples](examples/))
+
+## Hosting your own instance
+
+Copy `.env.example` to `.env` and configure it to your liking.
+
+### Docker
+
+```sh
+git clone https://github.com/Masterjoona/pawste/
+cd pawste
+docker compose up -d --build
+# for whatever reason building go in docker takes so long...
+```
+
+### Manual
+
+```sh
+pnpm build && go build
+```
+
+Then make a [service file](examples/pawste.service) for it and run it with systemd or something.
diff --git a/database/create_paste.go b/database/create_paste.go
deleted file mode 100644
index d1e6e9c..0000000
--- a/database/create_paste.go
+++ /dev/null
@@ -1,107 +0,0 @@
-package database
-
-import (
- "database/sql"
- "os"
-
- "github.com/Masterjoona/pawste/paste"
- "github.com/Masterjoona/pawste/shared"
- "github.com/Masterjoona/pawste/shared/config"
- _ "github.com/mattn/go-sqlite3"
- "github.com/romana/rlog"
-)
-
-func CreatePaste(paste paste.Paste) error {
- tx, err := PasteDB.Begin()
- if err != nil {
- return err
- }
- defer rollbackAndClose(tx, &err)
-
- stmt, err := tx.Prepare(`
- INSERT INTO pastes(PasteName, Expire, Privacy, ReadCount, ReadLast, BurnAfter, Content, UrlRedirect, Syntax, Password, CreatedAt, UpdatedAt)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- `)
- if err != nil {
- return err
- }
- defer stmt.Close()
-
- encrypt := (paste.Privacy == "private" || paste.Privacy == "secret") &&
- paste.Password != ""
- if encrypt {
- err = paste.EncryptText()
- }
-
- NewPassword := shared.TernaryString(encrypt, HashPassword(paste.Password), "")
-
- _, err = stmt.Exec(
- paste.PasteName,
- paste.Expire,
- paste.Privacy,
- paste.ReadCount,
- paste.ReadLast,
- paste.BurnAfter,
- paste.Content,
- paste.UrlRedirect,
- paste.Syntax,
- NewPassword,
- paste.CreatedAt,
- paste.UpdatedAt,
- )
-
- if err != nil {
- return err
- }
-
- if len(paste.Files) > 0 {
- err = saveFiles(&paste, encrypt)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
-func saveFiles(paste *paste.Paste, encrypt bool) error {
- for _, file := range paste.Files {
- if encrypt {
- err := file.Encrypt(paste.Password)
- if err != nil {
- rlog.Error("Failed to encrypt file:", err)
- return err
- }
- }
- err := saveFileToDisk(&file, paste.PasteName)
- if err != nil {
- rlog.Error("Failed to save file to disk:", err)
- return err
- }
- }
- return nil
-}
-
-func saveFileToDisk(file *paste.File, pasteName string) error {
- err := os.WriteFile(
- config.Config.DataDir+pasteName+"/"+file.Name,
- file.Blob,
- 0644,
- )
- if err != nil {
- return err
- }
- return nil
-}
-
-func rollbackAndClose(tx *sql.Tx, err *error) {
- if *err != nil {
- if rollbackErr := tx.Rollback(); rollbackErr != nil {
- rlog.Error("Failed to rollback transaction:", rollbackErr)
- }
- return
- }
-
- if commitErr := tx.Commit(); commitErr != nil {
- *err = commitErr
- }
-}
diff --git a/database/create_tables.go b/database/create_tables.go
deleted file mode 100644
index 454f7b3..0000000
--- a/database/create_tables.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package database
-
-import (
- "database/sql"
- "fmt"
- "os"
-
- _ "github.com/mattn/go-sqlite3"
- "github.com/romana/rlog"
-)
-
-func CreateOrLoadDatabase(deleteOld bool) *sql.DB {
- createPasteTable := `
- create table pastes
- (ID integer not null primary key,
- PasteName text,
- Expire datetime,
- Privacy text,
- IsEncrypted integer,
- ReadCount integer,
- ReadLast datetime,
- BurnAfter integer,
- Content text,
- Syntax text,
- Password text,
- UrlRedirect integer,
- CreatedAt datetime,
- UpdatedAt datetime
- );
- `
-
- createFileTable := `
- create table files
- (ID integer not null primary key,
- PasteName text,
- Name text,
- Size integer
- );
- `
- if deleteOld {
- os.Remove("./pastes.db")
- }
- newDb := false
- sqldb, err := sql.Open("sqlite3", "./pastes.db")
- if err != nil {
- rlog.Critical("Could not open database", err)
- }
- if deleteOld || newDb {
- res, err := sqldb.Exec(createPasteTable)
- if err != nil {
- rlog.Critical("Could not create pastes table", err)
- }
- fmt.Println(res)
- _, err = sqldb.Exec(createFileTable)
- if err != nil {
- rlog.Critical("Could not create files table", err)
- }
- rlog.Info("Created new database")
- } else {
- rlog.Info("Loaded existing database")
- }
- return sqldb
-}
diff --git a/database/expiration.go b/database/expiration.go
deleted file mode 100644
index 91416d0..0000000
--- a/database/expiration.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package database
-
-import (
- "github.com/Masterjoona/pawste/paste"
- _ "github.com/mattn/go-sqlite3"
- "github.com/romana/rlog"
-)
-
-func CleanUpExpiredPastes() {
- pastes, err := PasteDB.Query(
- "select ID from pastes where Expire < datetime('now') or BurnAfter <= ReadCount and BurnAfter > 0",
- )
- if err != nil {
- rlog.Error("Could not clean up expired pastes", err)
- }
- defer pastes.Close()
- for pastes.Next() {
- var paste paste.Paste
- err = pastes.Scan(
- &paste.ID,
- )
- if err != nil {
- panic(err)
- }
- rlog.Info("Cleaning up paste " + paste.PasteName)
- }
-}
diff --git a/database/init.go b/database/init.go
deleted file mode 100644
index 28e9991..0000000
--- a/database/init.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package database
-
-import (
- "database/sql"
-
- "github.com/Masterjoona/pawste/shared/config"
- _ "github.com/mattn/go-sqlite3"
-)
-
-var PasteDB *sql.DB
-
-func init() {
- PasteDB = CreateOrLoadDatabase(config.Config.IUnderstandTheRisks)
-}
diff --git a/database/update_paste.go b/database/update_paste.go
deleted file mode 100644
index 6515271..0000000
--- a/database/update_paste.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package database
-
-import "github.com/Masterjoona/pawste/paste"
-
-func UpdateReadCount(pasteName string) {
- _, err := PasteDB.Exec(
- "update pastes set ReadCount = ReadCount + 1, ReadLast = datetime('now') where PasteName = ?",
- pasteName,
- )
- if err != nil {
- panic(err)
- }
-
- if isAtBurnAfter(pasteName) {
- println(pasteName, "should be deleted")
- }
-}
-
-func isAtBurnAfter(pasteName string) bool {
- row := PasteDB.QueryRow(
- "select case when BurnAfter <= ReadCount and BurnAfter > 0 then 1 else 0 end from pastes where PasteName = ?",
- pasteName,
- )
- var burned int
- err := row.Scan(&burned)
- if err != nil {
- panic(err)
- }
- return burned == 1
-}
-
-func updatePasteContent(paste paste.Paste) error {
- tx, err := PasteDB.Begin()
- if err != nil {
- return err
- }
-
- defer func() {
- if err != nil {
- tx.Rollback()
- return
- }
- err = tx.Commit()
- if err != nil {
- panic(err)
- }
- }()
-
- stmt, err := tx.Prepare(`
- update pastes set
- Content = ?,
- UpdatedAt = datetime('now')
- where PasteName = ?
- `)
- if err != nil {
- return err
- }
- defer stmt.Close()
-
- _, err = stmt.Exec(
- paste.Content,
- paste.PasteName,
- )
- if err != nil {
- return err
- }
- return nil
-}
-
-func updatePasteFiles(paste paste.Paste) error {
- return nil
-}
-
-func UpdatePaste(paste paste.Paste) error {
- err := updatePasteContent(paste)
- if err != nil {
- return err
- }
-
- err = updatePasteFiles(paste)
- if err != nil {
- return err
- }
-
- return nil
-}
diff --git a/database/utils.go b/database/utils.go
deleted file mode 100644
index b8b1624..0000000
--- a/database/utils.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package database
-
-import (
- "crypto/sha256"
- "fmt"
- "reflect"
-
- "github.com/Masterjoona/pawste/paste"
- _ "github.com/mattn/go-sqlite3"
-)
-
-func MakePastePointers(paste *paste.Paste, scanVariables []string) []interface{} {
- pastePointers := make([]interface{}, len(scanVariables))
- val := reflect.ValueOf(paste).Elem()
- for i, variable := range scanVariables {
- pastePointers[i] = val.FieldByName(variable).Addr().Interface()
- }
- return pastePointers
-}
-
-func pasteExists(name string) bool {
- var exists bool
- err := PasteDB.QueryRow("select exists(select 1 from pastes where PasteName = ?)", name).
- Scan(&exists)
- if err != nil {
- panic(err)
- }
- return exists
-}
-
-func HashPassword(password string) string {
- hash := sha256.New()
- hash.Write(paste.SecurePassword(password))
- return fmt.Sprintf("%x", hash.Sum(nil))
-}
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
deleted file mode 100644
index 012babf..0000000
--- a/docker-compose.dev.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-services:
- pawste:
- build:
- dockerfile: ./Dockerfile
- container_name: pawste
- env_file: .env
- ports:
- - "9454:9454"
- volumes:
- - ./pastes.db:/app/pastes.db
- - /pawste_data:/app/pawste_data
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index 012babf..523f7d2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,11 +1,13 @@
services:
- pawste:
- build:
- dockerfile: ./Dockerfile
- container_name: pawste
- env_file: .env
- ports:
- - "9454:9454"
- volumes:
- - ./pastes.db:/app/pastes.db
- - /pawste_data:/app/pawste_data
\ No newline at end of file
+ pawste:
+ build:
+ dockerfile: ./Dockerfile
+ container_name: pawste
+ env_file: .env
+ environment:
+ - GIN_MODE=release
+ ports:
+ - "9454:9454"
+ volumes:
+ - ./pawste_data:/app/pawste_data
+ user: "${UID}:${GID}"
\ No newline at end of file
diff --git a/examples/pawste.file.sxcu b/examples/pawste.file.sxcu
new file mode 100644
index 0000000..313f5e6
--- /dev/null
+++ b/examples/pawste.file.sxcu
@@ -0,0 +1,16 @@
+{
+ "Version": "15.0.0",
+ "Name": "pawste image/file",
+ "DestinationType": "ImageUploader, FileUploader",
+ "RequestMethod": "POST",
+ "RequestURL": "https://pawst.eu/p/new",
+ "Body": "MultipartFormData",
+ "Arguments": {
+ "expire": "1w",
+ "burnafter": "0",
+ "privacy": "unlisted",
+ "content": "{input}"
+ },
+ "FileFormName": "files[]",
+ "URL": "https://pawst.eu/p/{json:PasteName}"
+}
\ No newline at end of file
diff --git a/examples/pawste.service b/examples/pawste.service
new file mode 100644
index 0000000..885544e
--- /dev/null
+++ b/examples/pawste.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=
+
+[Service]
+Type=exec
+#Restart=always
+ExecStartPre=pnpm run build && go build
+ExecStart=%h/pawste/pawste
+WorkingDirectory=%h/pawste
+EnvironmentFile=%h/pawste/.env
+
+[Install]
+WantedBy=default.target
\ No newline at end of file
diff --git a/examples/pawste.text.sxcu b/examples/pawste.text.sxcu
new file mode 100644
index 0000000..a6d901a
--- /dev/null
+++ b/examples/pawste.text.sxcu
@@ -0,0 +1,15 @@
+{
+ "Version": "15.0.0",
+ "Name": "pawste text",
+ "DestinationType": "TextUploader",
+ "RequestMethod": "POST",
+ "RequestURL": "https://pawst.eu/p/new",
+ "Body": "MultipartFormData",
+ "Arguments": {
+ "expire": "1w",
+ "burnafter": "0",
+ "privacy": "unlisted",
+ "content": "{input}"
+ },
+ "URL": "https://pawst.eu/p/{json:PasteName}"
+}
\ No newline at end of file
diff --git a/examples/pawste.url.sxcu b/examples/pawste.url.sxcu
new file mode 100644
index 0000000..45e3f20
--- /dev/null
+++ b/examples/pawste.url.sxcu
@@ -0,0 +1,15 @@
+{
+ "Version": "15.0.0",
+ "Name": "pawste url",
+ "DestinationType": "TextUploader",
+ "RequestMethod": "POST",
+ "RequestURL": "https://pawst.eu/p/new",
+ "Body": "MultipartFormData",
+ "Arguments": {
+ "expire": "1w",
+ "burnafter": "0",
+ "privacy": "unlisted",
+ "content": "{input}"
+ },
+ "URL": "https://pawst.eu/u/{json:PasteName}"
+}
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 1f80974..1349106 100644
--- a/go.mod
+++ b/go.mod
@@ -1,45 +1,53 @@
module github.com/Masterjoona/pawste
-go 1.22.1
+go 1.22.5
-require github.com/gin-gonic/gin v1.9.1
+require (
+ github.com/gin-gonic/gin v1.9.1
+ go.uber.org/zap v1.27.0
+)
require (
- github.com/dlclark/regexp2 v1.10.0 // indirect
+ github.com/Code-Hex/dd v1.1.0 // indirect
+ github.com/bytedance/sonic/loader v0.1.1 // indirect
+ github.com/cloudwego/base64x v0.1.4 // indirect
+ github.com/cloudwego/iasm v0.2.0 // indirect
+ github.com/dlclark/regexp2 v1.11.1 // indirect
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d // indirect
github.com/dop251/goja_nodejs v0.0.0-20231022114343-5c1f9037c9ab // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
)
require (
- github.com/bytedance/sonic v1.10.2 // indirect
- github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
- github.com/chenzhuoyu/iasm v0.9.1 // indirect
+ github.com/bytedance/sonic v1.11.6 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/gin-contrib/zap v1.1.3
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
- github.com/go-playground/validator/v10 v10.18.0 // indirect
+ github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
- github.com/klauspost/cpuid/v2 v2.2.6 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.22
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nichady/golte v0.0.4
- github.com/pelletier/go-toml/v2 v2.1.1 // indirect
+ github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
- github.com/romana/rlog v0.0.0-20220412051723-c08f605858a9
+ github.com/thessem/zap-prettyconsole v0.5.1
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
- golang.org/x/arch v0.7.0 // indirect
- golang.org/x/crypto v0.19.0
- golang.org/x/net v0.21.0 // indirect
- golang.org/x/sys v0.17.0 // indirect
- golang.org/x/text v0.14.0 // indirect
- google.golang.org/protobuf v1.32.0 // indirect
+ golang.org/x/arch v0.8.0 // indirect
+ golang.org/x/crypto v0.22.0
+ golang.org/x/net v0.24.0 // indirect
+ golang.org/x/sys v0.20.0 // indirect
+ golang.org/x/text v0.15.0 // indirect
+ google.golang.org/protobuf v1.34.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 60b5f8c..68262b9 100644
--- a/go.sum
+++ b/go.sum
@@ -1,25 +1,24 @@
-github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
-github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
-github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
-github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
-github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
-github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
-github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
-github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
-github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
-github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
-github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
+github.com/Code-Hex/dd v1.1.0 h1:VEtTThnS9l7WhpKUIpdcWaf0B8Vp0LeeSEsxA1DZseI=
+github.com/Code-Hex/dd v1.1.0/go.mod h1:VaMyo/YjTJ3d4qm/bgtrUkT2w+aYwJ07Y7eCWyrJr1w=
+github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
+github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
+github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
+github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
+github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
-github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
-github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dlclark/regexp2 v1.11.1 h1:CJs78ewKXO9PuNf6Xwlw6eibMadBkXTRpOeUdv+IcWM=
+github.com/dlclark/regexp2 v1.11.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d h1:wi6jN5LVt/ljaBG4ue79Ekzb12QfJ52L9Q98tl8SWhw=
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
@@ -31,6 +30,8 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-contrib/zap v1.1.3 h1:9e/U9fYd4/OBfmSEBs5hHZq114uACn7bpuzvCkcJySA=
+github.com/gin-contrib/zap v1.1.3/go.mod h1:+BD/6NYZKJyUpqVoJEvgeq9GLz8pINEQvak9LHNOTSE=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -39,16 +40,16 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
-github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
+github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-rod/rod v0.114.5 h1:1x6oqnslwFVuXJbJifgxspJUd3O4ntaGhRLHt+4Er9c=
github.com/go-rod/rod v0.114.5/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0=
@@ -57,8 +58,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
-github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
+github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -81,26 +82,30 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nichady/golte v0.0.4 h1:+GOnhHYcC/w5IDyT1WUTO90AUTW8xjjM+pAHST60YS8=
github.com/nichady/golte v0.0.4/go.mod h1:OisheRqEA5g55bJ4r3rw0tthP4gD65VmegYQYihLgjI=
-github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
-github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
+github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+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/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
-github.com/romana/rlog v0.0.0-20220412051723-c08f605858a9 h1:8tVb/1pwM1HrrK4HuBJIWREOSJ5Z1oouS6nilsXrL+Q=
-github.com/romana/rlog v0.0.0-20220412051723-c08f605858a9/go.mod h1:kPzumBKm/AKQWtDbtf8w0s/R+LwoYT1rTjsOYGcS82k=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/thessem/zap-prettyconsole v0.5.1 h1:s0dZhXysdGIig1G4ubttF1/HR2SHdzCooQpAUZ3nt+8=
+github.com/thessem/zap-prettyconsole v0.5.1/go.mod h1:3qfsE7y+bLOq7EQ+fMZHD3HYEp24ULFf5nhLSx6rjrE=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
@@ -116,19 +121,25 @@ github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3R
github.com/ysmood/leakless v0.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak=
github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
-golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
-golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
+golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
-golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
+golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
-golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -139,8 +150,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
-golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -148,16 +159,16 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
-google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
+google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
diff --git a/golte.config.ts b/golte.config.ts
new file mode 100644
index 0000000..fe8e6ac
--- /dev/null
+++ b/golte.config.ts
@@ -0,0 +1,7 @@
+import { Config } from "golte/config";
+
+export default {
+ template: "web/app.html",
+ srcDir: "web/",
+ outDir: "pkg/build",
+} satisfies Config;
diff --git a/handling/handling.go b/handling/handling.go
deleted file mode 100644
index d3d053c..0000000
--- a/handling/handling.go
+++ /dev/null
@@ -1,136 +0,0 @@
-package handling
-
-import (
- "net/http"
-
- "github.com/Masterjoona/pawste/database"
- "github.com/Masterjoona/pawste/paste"
- "github.com/Masterjoona/pawste/shared"
- "github.com/Masterjoona/pawste/shared/config"
- "github.com/gin-gonic/gin"
- "github.com/romana/rlog"
-)
-
-func HandlePage(
- settings map[string]interface{},
- function func() interface{},
- value string,
-) gin.HandlerFunc {
- settings["config.Config"] = config.Config
- return func(c *gin.Context) {
- if function != nil {
- settings[value] = function()
- }
- c.HTML(http.StatusOK, "main.html", settings)
- }
-}
-
-func HandlePastePage(c *gin.Context) {
- paste, err := database.GetPasteByName(c.Param("pasteName"))
- if err != nil {
- c.HTML(http.StatusNotFound, "main.html", gin.H{
- "NotFound": true,
- "config.Config": config.Config,
- })
- return
- }
- database.UpdateReadCount(c.Param("pasteName"))
- c.HTML(http.StatusOK, "main.html", gin.H{
- "Paste": paste,
- "config.Config": config.Config,
- })
-}
-
-func HandlePasteJSON(c *gin.Context) {
- paste, err := database.GetPasteByName(c.Param("pasteName"))
- if err != nil {
- c.JSON(http.StatusNotFound, gin.H{"error": "paste not found"})
- return
- }
- database.UpdateReadCount(c.Param("pasteName"))
- c.JSON(http.StatusOK, paste)
-}
-
-func HandleUpdate(c *gin.Context) {
- paste, err := database.GetPasteByName(c.Param("pasteName"))
- if err != nil {
- c.JSON(http.StatusNotFound, gin.H{"error": "paste not found"})
- return
- }
- var newPaste shared.Submit
- if err := c.Bind(&newPaste); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
- if paste.Password != "" {
- if paste.Password != database.HashPassword(newPaste.Password) {
- c.JSON(http.StatusUnauthorized, gin.H{"error": "wrong password"})
- return
- }
- }
- paste.Content = newPaste.Text
- paste.UpdatedAt = shared.GetCurrentDate()
- err = database.UpdatePaste(paste)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
- rlog.Errorf("Failed to update paste: %s", err)
- return
- }
- c.JSON(http.StatusOK, paste)
-}
-
-func HandleEdit(c *gin.Context) {
- paste, err := database.GetPasteByName(c.Param("pasteName"))
- if err != nil {
- c.HTML(http.StatusNotFound, "main.html", gin.H{
- "NotFound": true,
- "config.Config": config.Config,
- })
- return
- }
- c.HTML(http.StatusOK, "main.html", gin.H{
- "Edit": paste,
- "config.Config": config.Config,
- })
-}
-
-func RedirectHome(c *gin.Context) {
- c.Redirect(http.StatusFound, "/")
-}
-
-func HandleRaw(c *gin.Context) {
- paste, err := database.GetPasteByName(c.Param("pasteName"))
- if err != nil {
- c.String(http.StatusNotFound, "Paste not found")
- return
- }
- database.UpdateReadCount(c.Param("pasteName"))
- c.String(http.StatusOK, paste.Content)
-}
-
-func Redirect(c *gin.Context) {
- paste, err := database.GetPasteByName(c.Param("pasteName"))
- if err != nil {
- c.Redirect(http.StatusFound, "/")
- return
- }
- if paste.UrlRedirect == 0 {
- c.Redirect(http.StatusFound, "/p/"+paste.PasteName)
- return
- }
- database.UpdateReadCount(c.Param("pasteName"))
- c.Redirect(http.StatusFound, paste.Content)
-
-}
-
-// funny
-func AdminHandler() interface{} {
- return database.GetAllPastes()
-}
-
-func ListHandler() interface{} {
- return paste.PasteLists{
- Pastes: database.GetPublicPastes(),
- Redirects: database.GetPublicRedirects(),
- }
-}
diff --git a/handling/submit.go b/handling/submit.go
deleted file mode 100644
index abde043..0000000
--- a/handling/submit.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package handling
-
-import (
- "errors"
- "net/http"
- "strconv"
-
- "github.com/Masterjoona/pawste/database"
- "github.com/Masterjoona/pawste/shared"
- "github.com/Masterjoona/pawste/shared/config"
- "github.com/gin-gonic/gin"
-)
-
-func HandleSubmit(c *gin.Context) {
- submit, err := parseSubmitForm(c)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
-
- if err := validateSubmit(&submit); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
-
- isRedirect := shared.IsContentJustUrl(submit.Text)
- pasteName := database.CreatePasteName(isRedirect)
-
- c.JSON(http.StatusOK, gin.H{
- "text": submit.Text,
- "expiration": submit.Expiration,
- "burn": submit.BurnAfter,
- "syntax": submit.Syntax,
- "privacy": submit.Privacy,
- "file": "file",
- "pasteUrl": pasteName,
- })
-
- paste := shared.SubmitToPaste(submit, pasteName, isRedirect)
- database.CreatePaste(paste)
-}
-
-func parseSubmitForm(c *gin.Context) (shared.Submit, error) {
- var submit shared.Submit
- submit.Text = c.PostForm("text")
- submit.Expiration = c.PostForm("expiration")
- submit.Password = c.PostForm("password")
- submit.Syntax = c.PostForm("syntax")
- submit.Privacy = c.PostForm("privacy")
- burnInt, err := strconv.Atoi(c.PostForm("burn"))
- if err != nil {
- return shared.Submit{}, errors.New("burn must be an integer")
- }
- submit.BurnAfter = burnInt
-
- form, err := c.MultipartForm()
- if err != nil {
- return shared.Submit{}, errors.New("form error: " + err.Error())
- }
-
- submit.Files = form.File["files"]
- return submit, nil
-}
-
-func validateSubmit(submit *shared.Submit) error {
- if submit.Text == "" && len(submit.Files) == 0 {
- return errors.New("text or file is required")
- }
- encrypt := (submit.Privacy == "private" || submit.Privacy == "secret")
- if submit.Password == "" && encrypt {
- return errors.New("password is required for private or secret pastes")
- }
-
- if shared.NotAllowedPrivacy(submit.Privacy) {
- return errors.New("invalid privacy")
- }
-
- if config.Config.DisableEternalPaste && submit.Expiration == "never" {
- submit.Expiration = "1w"
- }
-
- if config.Config.MaxContentLength > 0 && len(submit.Text) > config.Config.MaxContentLength {
- return errors.New("content is too long")
- }
-
- if config.Config.MaxFileSize > 0 && len(submit.Files) > 0 {
- totalSize := 0
- for _, file := range submit.Files {
- if file == nil {
- continue
- }
- totalSize += int(file.Size)
- }
- if totalSize > config.Config.MaxFileSize {
- return errors.New("file size is too large")
- }
- }
-
- if config.Config.MaxEncryptionSize > 0 && encrypt && len(submit.Files) > 0 {
- totalSize := 0
- for _, file := range submit.Files {
- if file == nil {
- continue
- }
- totalSize += int(file.Size)
- }
- if totalSize > config.Config.MaxEncryptionSize {
- return errors.New("file size is too large for encryption")
- }
- }
-
- return nil
-}
diff --git a/main.go b/main.go
index abc26f7..1bd92ab 100644
--- a/main.go
+++ b/main.go
@@ -1,84 +1,27 @@
package main
import (
- "database/sql"
- "net/http"
-
- "github.com/Masterjoona/pawste/build"
- "github.com/Masterjoona/pawste/database"
- "github.com/Masterjoona/pawste/handling"
- "github.com/Masterjoona/pawste/shared/config"
-
+ "github.com/Masterjoona/pawste/pkg/config"
+ "github.com/Masterjoona/pawste/pkg/database"
+ "github.com/Masterjoona/pawste/pkg/route"
"github.com/gin-gonic/gin"
- "github.com/nichady/golte"
- "github.com/romana/rlog"
)
-var PasteDB *sql.DB
-
-var wrapMiddleware = func(middleware func(http.Handler) http.Handler) func(ctx *gin.Context) {
- return func(ctx *gin.Context) {
- middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- ctx.Request = r
- ctx.Next()
- })).ServeHTTP(ctx.Writer, ctx.Request)
- if golte.GetRenderContext(ctx.Request) == nil {
- ctx.Abort()
- }
- }
-}
-
func main() {
- config.Config.InitConfig()
- rlog.Info("Starting Pawste " + config.PawsteVersion)
- PasteDB = database.CreateOrLoadDatabase(config.Config.IUnderstandTheRisks)
-
- page := func(c string) gin.HandlerFunc {
- return gin.WrapH(golte.Page(c))
- }
- layout := func(c string) gin.HandlerFunc {
- return wrapMiddleware(golte.Layout(c))
- }
-
- r := gin.Default()
-
- r.Use(wrapMiddleware(build.Golte))
- r.Use(layout("layout/main"))
-
- r.GET("/contact", page("page/contact"))
-
- r.LoadHTMLGlob("oldweb/templates/*")
-
- r.Static("/css", "./oldweb/css")
- r.Static("/js", "./oldweb/js")
- r.Static("/fonts", "./oldweb/fonts")
-
- r.StaticFile("/favicon.ico", "./oldweb/static/favicon.ico")
- r.StaticFile("/static/suzume.png", "./oldweb/static/suzume.png")
-
- r.GET("/", handling.HandlePage(gin.H{}, nil, ""))
-
- r.GET("/p/:pasteName", handling.HandlePastePage)
- r.GET("/p/:pasteName/json", handling.HandlePasteJSON)
- r.GET("/p", handling.RedirectHome)
-
- r.GET("/u/:pasteName", handling.Redirect)
- r.GET("/u", handling.RedirectHome)
-
- r.GET("/r/:pasteName", handling.HandleRaw)
- r.GET("/r", handling.RedirectHome)
+ config.Vars.InitConfig()
+ config.Logger.Info("Starting Pawste " + config.PawsteVersion)
+ database.CreateOrLoadDatabase()
- r.GET("/e/:pasteName", handling.HandleEdit)
- r.GET("/e", handling.RedirectHome)
+ r := gin.New()
- r.POST("/submit", handling.HandleSubmit)
- r.PATCH("/p/:pasteName", handling.HandleUpdate)
+ route.SetupMiddleware(r)
+ route.SetupErrorHandlers(r)
- r.GET("/guide", handling.HandlePage(gin.H{"Guide": true}, nil, ""))
- r.GET("/admin", handling.HandlePage(gin.H{"Admin": true}, handling.AdminHandler, "PasteLists"))
- r.POST("/admin/reload-config", config.Config.ReloadConfig)
- r.GET("/about", handling.HandlePage(gin.H{"About": true}, nil, ""))
- r.GET("/list", handling.HandlePage(gin.H{"List": true}, handling.ListHandler, "PasteLists"))
+ route.SetupPublicRoutes(r)
+ route.SetupPasteRoutes(r)
+ route.SetupRedirectRoutes(r)
+ route.SetupEditRoutes(r)
+ route.SetupAdminRoutes(r)
- r.Run(config.Config.Port)
+ r.Run(config.Vars.Port)
}
diff --git a/oldweb/css/admin.css b/oldweb/css/admin.css
deleted file mode 100644
index afbe34e..0000000
--- a/oldweb/css/admin.css
+++ /dev/null
@@ -1,38 +0,0 @@
-/* For .links */
-.links {
- font-family: "Fira code light";
- display: flex;
- flex-direction: column;
- margin-left: 5vw;
- text-align: left;
- align-items: flex-start; /* Align items to the start (left) */
-}
-
-.links a {
- margin-bottom: 5px;
- text-decoration: none;
- max-width: fit-content;
-}
-
-.links a:visited {
- text-decoration: none;
- color: blue;
-}
-
-.links a:hover {
- text-decoration: underline;
-}
-
-.info {
- font-family: "Fira code light";
- display: flex;
- flex-direction: column;
- margin-right: 5vw;
- text-align: right;
- align-items: flex-end;
-}
-
-.info-table {
- table-layout: fixed;
- text-align: left;
-}
diff --git a/oldweb/css/buttons.css b/oldweb/css/buttons.css
deleted file mode 100644
index c5e840e..0000000
--- a/oldweb/css/buttons.css
+++ /dev/null
@@ -1,50 +0,0 @@
-.submit-button,
-.attach-button,
-.remove-button,
-.copy-button {
- font-family: "Short Stack", cursive;
- background-color: #788db5;
- color: #292828;
- border: none;
- padding: 10px 20px;
- margin: 10px;
- border-radius: 7px;
- cursor: pointer;
- transition: background-color 0.3s ease;
-}
-
-.submit-button:hover,
-.attach-button:hover,
-.remove-button:hover,
-.copy-button:hover {
- background-color: #a3b9d1;
-}
-
-.buttons {
- display: flex;
- align-items: center;
- gap: 10px;
- justify-content: space-between;
- max-width: 60vw;
- margin: 0 auto;
-}
-
-#upload-password {
- display: none;
- background-color: #2a2a30; /* Match background color */
- color: aliceblue; /* Match text color */
- border: 2px solid #788db5; /* Match border color */
- border-radius: 7px;
- padding: 8px; /* Adjust padding */
- font-size: 16px; /* Adjust font size */
-}
-
-.left-buttons {
- display: flex;
- gap: 10px;
-}
-
-.right-buttons {
- display: flex;
- gap: 10px;
-}
diff --git a/oldweb/css/container.css b/oldweb/css/container.css
deleted file mode 100644
index 7469d0e..0000000
--- a/oldweb/css/container.css
+++ /dev/null
@@ -1,54 +0,0 @@
-.container {
- position: relative;
- flex-direction: column;
- margin: 4vh;
-}
-
-.input-textarea {
- width: 60vw;
- height: 40vh;
- resize: vertical;
- min-height: 20vh;
- background-color: #2a2a30;
- color: aliceblue;
- border: 5px solid #2a2a30;
- border-radius: 7px;
- transition: border-color 0.3s ease;
- padding: 10px;
- font-size: 16px;
-}
-
-.input-textarea:focus {
- border: 5px solid #788db5;
- outline: none;
-}
-
-.preview-button {
- font-family: "Short Stack", cursive;
- background-color: #788db5;
- color: #292828;
- border: none;
- padding: 10px 20px;
- margin: 10px;
- border-radius: 7px;
- cursor: pointer;
- transition: background-color 0.3s ease;
- width: 7vw;
-}
-
-.preview-button:hover {
- background-color: #a3b9d1;
-}
-
-.preview-container {
- display: none;
- text-align: left;
- width: 60vw;
- font-family: "Fira Code light";
-}
-
-.preview-container > pre {
- padding: 10px;
- border-radius: 7px;
- font-size: 1.3em;
-}
diff --git a/oldweb/css/footer.css b/oldweb/css/footer.css
deleted file mode 100644
index 1eac633..0000000
--- a/oldweb/css/footer.css
+++ /dev/null
@@ -1,6 +0,0 @@
-.social-links {
- justify-content: center;
- margin: 20px 10px;
- color: #788db5;
- font-family: "Fira Code light";
-}
diff --git a/oldweb/css/guide.css b/oldweb/css/guide.css
deleted file mode 100644
index 4434be6..0000000
--- a/oldweb/css/guide.css
+++ /dev/null
@@ -1,35 +0,0 @@
-/* hide list bullets */
-ul {
- list-style-type: none;
-}
-
-.guide-list {
- font-family: "Short Stack", cursive;
- color: #a3b9d1;
- margin: 20px;
- position: relative;
-}
-.guide-list strong {
- color: #7aa8ff;
- position: relative;
-}
-
-.guide-list ul {
- margin-top: 5px;
- margin-bottom: 10px;
-}
-
-.guide-list li {
- margin-bottom: 30px;
-}
-
-.image-container {
- position: absolute;
- bottom: 10px; /* Adjust this value to position the image vertically */
- right: 10px; /* Adjust this value to position the image horizontally */
-}
-
-.image-container img {
- width: 40vh; /* Adjust image width as needed */
- height: auto; /* Maintain aspect ratio */
-}
diff --git a/oldweb/css/header.css b/oldweb/css/header.css
deleted file mode 100644
index dedadad..0000000
--- a/oldweb/css/header.css
+++ /dev/null
@@ -1,10 +0,0 @@
-.pawste-title {
- color: #788db5;
- font-family: "Short Stack", cursive;
-}
-
-.nav-links {
- color: #788db5;
- margin: 0 10px;
- font-family: "Fira Code light";
-}
diff --git a/oldweb/css/list.css b/oldweb/css/list.css
deleted file mode 100644
index 252158b..0000000
--- a/oldweb/css/list.css
+++ /dev/null
@@ -1,44 +0,0 @@
-.table {
- width: 100%;
- max-width: 100%;
- margin-bottom: 20px;
- border-collapse: collapse;
- border-spacing: 0;
- background-color: transparent;
-}
-
-.table td {
- padding: 10px; /* Adjust the padding value as needed */
-}
-
-.paste-info {
- font-family: "Fira Code light";
- color: #007bcf;
-}
-
-.paste-link {
- font-family: "Fira Code light";
- color: #007bcf;
- text-decoration: none;
- position: relative;
-}
-
-.paste-link::before {
- content: "";
- position: absolute;
- bottom: -2px;
- left: 0;
- width: 0;
- height: 2px;
- background-color: #0056b3;
- transition: width 0.3s ease;
-}
-
-.paste-link:hover::before {
- width: 100%;
-}
-
-.table-titles {
- font-family: "Short Stack", cursive;
- color: #788db5;
-}
diff --git a/oldweb/css/main.css b/oldweb/css/main.css
deleted file mode 100644
index 2895aac..0000000
--- a/oldweb/css/main.css
+++ /dev/null
@@ -1,14 +0,0 @@
-@font-face {
- font-family: "Short Stack";
- src: url("../fonts/ShortStack-Regular.ttf");
-}
-
-@font-face {
- font-family: "Fira Code light";
- src: url("../fonts/FiraCode-Light.ttf");
-}
-
-body {
- background-color: #292828;
- text-align: center;
-}
diff --git a/oldweb/css/options.css b/oldweb/css/options.css
deleted file mode 100644
index 2cf93c2..0000000
--- a/oldweb/css/options.css
+++ /dev/null
@@ -1,49 +0,0 @@
-.options {
- display: flex;
- flex-direction: row;
- justify-content: center;
- margin-top: 10px; /* Adjust margin from the top */
-}
-
-.option-group {
- margin-bottom: 20px; /* Adjust spacing between option groups */
- margin: 0 20px; /* Adjust margin between option groups */
-}
-
-.options-label {
- font-family: "Short Stack", cursive;
- display: block;
- color: #a3b9d1; /* Match text color */
- margin-bottom: 5px;
-}
-
-.options select,
-.options input[type="password"] {
- background-color: #2a2a30; /* Match background color */
- color: aliceblue; /* Match text color */
- border: 2px solid #788db5; /* Match border color */
- border-radius: 7px;
- padding: 8px; /* Adjust padding */
- font-size: 16px; /* Adjust font size */
- font-family: "Fira Code light";
-}
-
-body > div.options > div:nth-child(5) {
- /* password / label */
- display: none;
-}
-
-body > div.options > div:nth-child(6) {
- /* theme / label */
- display: none;
-}
-
-/* both are dynamically shown */
-
-.options select {
- width: 160px; /* Adjust width */
-}
-
-.options input[type="password"] {
- width: 160px; /* Adjust width */
-}
diff --git a/oldweb/css/password.css b/oldweb/css/password.css
deleted file mode 100644
index d1e4a66..0000000
--- a/oldweb/css/password.css
+++ /dev/null
@@ -1,39 +0,0 @@
-.input-password {
- width: 200px;
- padding: 10px;
- border: 1px solid #ccc;
- border-radius: 3px;
- font-size: 16px;
- margin: 0 auto; /* Center the password input box */
- margin-top: 50px; /* Adjust the margin top to move the input box down */
- background-color: #f2f2f2;
-}
-
-.password-container {
- text-align: center; /* Center the password container */
-}
-
-.overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.5);
- z-index: 500;
- filter: blur(2px);
-}
-
-#submit-password {
- margin-top: 10px;
- padding: 10px 20px;
- border: none;
- background-color: #007bff;
- color: white;
- border-radius: 3px;
- cursor: pointer;
-}
-
-#submit-password:hover {
- background-color: #0056b3;
-}
diff --git a/oldweb/css/toast.css b/oldweb/css/toast.css
deleted file mode 100644
index 772a04a..0000000
--- a/oldweb/css/toast.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.toast {
- font-family: "Short Stack", cursive;
- position: fixed;
- bottom: 20px;
- right: 20px;
- background-color: #333;
- color: #fff;
- padding: 20px 30px;
- border-radius: 5px;
- opacity: 0;
- transition: opacity 0.5s ease-in-out;
-}
-
-.show-toast {
- opacity: 1;
-}
-/* Default toast */
-.default-toast {
- background-color: #333;
- color: #fff;
-}
-
-/* Warning toast */
-.warning-toast {
- background-color: #ff9800;
- color: #fff;
-}
-
-/* Info toast */
-.info-toast {
- background-color: #2196f3;
- color: #fff;
-}
diff --git a/oldweb/fonts/FiraCode-Light.ttf b/oldweb/fonts/FiraCode-Light.ttf
deleted file mode 100644
index 87ff012..0000000
Binary files a/oldweb/fonts/FiraCode-Light.ttf and /dev/null differ
diff --git a/oldweb/fonts/OFL.txt b/oldweb/fonts/OFL.txt
deleted file mode 100644
index 0e38b88..0000000
--- a/oldweb/fonts/OFL.txt
+++ /dev/null
@@ -1,93 +0,0 @@
-Copyright 2014-2020 The Fira Code Project Authors (https://github.com/tonsky/FiraCode)
-
-This Font Software is licensed under the SIL Open Font License, Version 1.1.
-This license is copied below, and is also available with a FAQ at:
-https://openfontlicense.org
-
-
------------------------------------------------------------
-SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
------------------------------------------------------------
-
-PREAMBLE
-The goals of the Open Font License (OFL) are to stimulate worldwide
-development of collaborative font projects, to support the font creation
-efforts of academic and linguistic communities, and to provide a free and
-open framework in which fonts may be shared and improved in partnership
-with others.
-
-The OFL allows the licensed fonts to be used, studied, modified and
-redistributed freely as long as they are not sold by themselves. The
-fonts, including any derivative works, can be bundled, embedded,
-redistributed and/or sold with any software provided that any reserved
-names are not used by derivative works. The fonts and derivatives,
-however, cannot be released under any other type of license. The
-requirement for fonts to remain under this license does not apply
-to any document created using the fonts or their derivatives.
-
-DEFINITIONS
-"Font Software" refers to the set of files released by the Copyright
-Holder(s) under this license and clearly marked as such. This may
-include source files, build scripts and documentation.
-
-"Reserved Font Name" refers to any names specified as such after the
-copyright statement(s).
-
-"Original Version" refers to the collection of Font Software components as
-distributed by the Copyright Holder(s).
-
-"Modified Version" refers to any derivative made by adding to, deleting,
-or substituting -- in part or in whole -- any of the components of the
-Original Version, by changing formats or by porting the Font Software to a
-new environment.
-
-"Author" refers to any designer, engineer, programmer, technical
-writer or other person who contributed to the Font Software.
-
-PERMISSION & CONDITIONS
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of the Font Software, to use, study, copy, merge, embed, modify,
-redistribute, and sell modified and unmodified copies of the Font
-Software, subject to the following conditions:
-
-1) Neither the Font Software nor any of its individual components,
-in Original or Modified Versions, may be sold by itself.
-
-2) Original or Modified Versions of the Font Software may be bundled,
-redistributed and/or sold with any software, provided that each copy
-contains the above copyright notice and this license. These can be
-included either as stand-alone text files, human-readable headers or
-in the appropriate machine-readable metadata fields within text or
-binary files as long as those fields can be easily viewed by the user.
-
-3) No Modified Version of the Font Software may use the Reserved Font
-Name(s) unless explicit written permission is granted by the corresponding
-Copyright Holder. This restriction only applies to the primary font name as
-presented to the users.
-
-4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
-Software shall not be used to promote, endorse or advertise any
-Modified Version, except to acknowledge the contribution(s) of the
-Copyright Holder(s) and the Author(s) or with their explicit written
-permission.
-
-5) The Font Software, modified or unmodified, in part or in whole,
-must be distributed entirely under this license, and must not be
-distributed under any other license. The requirement for fonts to
-remain under this license does not apply to any document created
-using the Font Software.
-
-TERMINATION
-This license becomes null and void if any of the above conditions are
-not met.
-
-DISCLAIMER
-THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
-OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
-COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
-DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
-OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/oldweb/fonts/ShortStack-Regular.ttf b/oldweb/fonts/ShortStack-Regular.ttf
deleted file mode 100644
index 671a4d3..0000000
Binary files a/oldweb/fonts/ShortStack-Regular.ttf and /dev/null differ
diff --git a/oldweb/js/admin.js b/oldweb/js/admin.js
deleted file mode 100644
index 85dd9f6..0000000
--- a/oldweb/js/admin.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import { showToast } from "./toast.js";
-import { waitForElementToDisplay } from "./helpers.js";
-
-function reloadConfig() {
- const formData = new FormData();
- formData.append("password", document.getElementById("password").value);
- fetch("/admin/reload-config", {
- method: "POST",
- body: formData,
- })
- .then((response) => {
- if (!response.ok) {
- throw new Error(
- `Error ${response.status}: ${response.statusText}`,
- );
- }
- return response.json();
- })
- .then((data) => {
- console.log(data);
- showToast("success", "Config reloaded");
- })
- .catch((error) => {
- console.error("Error:", error);
- showToast("warning", "Failed to reload the config");
- });
-}
-
-waitForElementToDisplay(
- "#reloadconfig",
- () => {
- document
- .getElementById("reloadconfig")
- .addEventListener("click", reloadConfig);
- },
- 500,
- 5000,
-);
diff --git a/oldweb/js/attach_file.js b/oldweb/js/attach_file.js
deleted file mode 100644
index 1f0a5ad..0000000
--- a/oldweb/js/attach_file.js
+++ /dev/null
@@ -1,36 +0,0 @@
-window.addEventListener("paste", (e) => {
- if (e.clipboardData.files.length === 0) return;
- const fileInput = document.getElementById("file-input");
- console.log(e.clipboardData.files);
- fileInput.files = e.clipboardData.files;
- const event = new Event("change");
- fileInput.dispatchEvent(event);
-});
-
-function handleFiles(files) {
- const filename = files[0].name;
- const fileButton = document.getElementById("attach-button");
- let prettyFilename = filename;
- if (filename.length > 10) {
- prettyFilename = filename.substr(0, 5) + "..." + filename.substr(-5);
- }
- fileButton.innerHTML = "attached: " + prettyFilename;
- fileButton.classList.add("attached");
-
- const removeButton = document.getElementById("remove-button");
- removeButton.style.display = "block";
-}
-
-function removeFiles() {
- const fileButton = document.getElementById("attach-button");
- fileButton.innerHTML = "Attach";
- fileButton.classList.remove("attached");
-
- const removeButton = document.getElementById("remove-button");
- removeButton.style.display = "none";
-
- const fileInput = document.getElementById("file-input");
- fileInput.value = "";
- const event = new Event("change");
- fileInput.dispatchEvent(event);
-}
diff --git a/oldweb/js/helpers.js b/oldweb/js/helpers.js
deleted file mode 100644
index f8a5ecc..0000000
--- a/oldweb/js/helpers.js
+++ /dev/null
@@ -1,20 +0,0 @@
-export function waitForElementToDisplay(
- selector,
- callback,
- checkFrequencyInMs,
- timeoutInMs,
-) {
- const startTimeInMs = Date.now();
- (function loopSearch() {
- if (document.querySelector(selector) != null) {
- callback();
- return;
- } else {
- setTimeout(function () {
- if (timeoutInMs && Date.now() - startTimeInMs > timeoutInMs)
- return;
- loopSearch();
- }, checkFrequencyInMs);
- }
- })();
-}
diff --git a/oldweb/js/options.js b/oldweb/js/options.js
deleted file mode 100644
index 9525240..0000000
--- a/oldweb/js/options.js
+++ /dev/null
@@ -1,8 +0,0 @@
-function togglePassword() {
- const password = document.getElementById("password");
- if (password.type === "password") {
- password.type = "text";
- } else {
- password.type = "password";
- }
-}
diff --git a/oldweb/js/password.js b/oldweb/js/password.js
deleted file mode 100644
index b51f57c..0000000
--- a/oldweb/js/password.js
+++ /dev/null
@@ -1,58 +0,0 @@
-// for node
-// const crypto = require("crypto");
-import { waitForElementToDisplay } from "./helpers.js";
-
-async function HashPassword(password) {
- // Convert the password string to an array buffer
- const passwordBuffer = new TextEncoder().encode(password);
-
- try {
- // Generate a cryptographic hash using the SHA-256 algorithm
- const hashBuffer = await crypto.subtle.digest(
- "SHA-256",
- passwordBuffer,
- );
-
- // Convert the hash buffer to a hexadecimal string
- const hashArray = Array.from(new Uint8Array(hashBuffer));
- const hashHex = hashArray
- .map((byte) => byte.toString(16).padStart(2, "0"))
- .join("");
-
- return hashHex;
- } catch (error) {
- console.error("Error hashing password:", error);
- return null;
- }
-}
-
-/*
-// Example usage:
-const password = "test";
-HashPassword(password).then((hash) => {
- console.log("Hashed password:", hash);
-});
-*/
-
-function togglePasswordBoxVisibility() {
- const privacy = document.querySelector(
- "body > div.options > div:nth-child(5)",
- );
- const privacySelect = document.getElementById("privacy");
- if (privacySelect.value === "private" || privacySelect.value === "secret") {
- privacy.style.display = "block";
- } else {
- privacy.style.display = "none";
- }
-}
-
-waitForElementToDisplay(
- "body > div.options > div:nth-child(4)",
- function () {
- document
- .getElementById("privacy")
- .addEventListener("change", togglePasswordBoxVisibility);
- },
- 500,
- 5000,
-);
diff --git a/oldweb/js/preview.js b/oldweb/js/preview.js
deleted file mode 100644
index e6d34c3..0000000
--- a/oldweb/js/preview.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import {
- codeToHtml,
- bundledThemes,
- bundledLanguages,
-} from "https://esm.sh/shiki@1.1.7";
-import { waitForElementToDisplay } from "./helpers.js";
-
-export function languageOption() {
- return document.getElementById("syntax").value;
-}
-
-async function togglePreview() {
- const previewButton = document.getElementById("preview-button");
- const previewTextArea = document.getElementById("preview");
- const textarea = document.getElementById("text-input");
- const themeBox = document.querySelector(
- "body > div.options > div:nth-child(6)",
- );
-
- if (textarea.style.display === "none") {
- textarea.style.display = "";
- previewTextArea.style.display = "none";
- previewButton.textContent = "preview";
- themeBox.style.display = "none";
- } else {
- textarea.style.display = "none";
- previewTextArea.innerHTML = await codeToHtml(textarea.value, {
- lang: languageOption(),
- theme: "solarized-dark",
- });
- previewTextArea.style.display = "inline-block";
- previewButton.textContent = "edit";
- themeBox.style.display = "block";
- }
-}
-
-function prettifyName(theme) {
- return theme
- .split("-")
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
- .join(" ");
-}
-
-function addThemeOptions() {
- const themeSelect = document.getElementById("theme");
- for (const theme in bundledThemes) {
- const option = document.createElement("option");
- option.value = theme;
- option.text = prettifyName(theme);
- themeSelect.add(option);
- }
-}
-
-function addLanguageOptions() {
- const syntaxSelect = document.getElementById("syntax");
- for (const language in bundledLanguages) {
- const option = document.createElement("option");
- option.value = language;
- option.text = prettifyName(language);
- syntaxSelect.add(option);
- }
-}
-
-waitForElementToDisplay(
- "body > div.buttons > div.right-buttons > button.preview-button",
- function () {
- document
- .querySelector(
- "body > div.buttons > div.right-buttons > button.preview-button",
- )
- .addEventListener("click", async () => {
- await togglePreview();
- });
- addThemeOptions();
- addLanguageOptions();
- },
- 500,
- 5000,
-);
diff --git a/oldweb/js/submit.js b/oldweb/js/submit.js
deleted file mode 100644
index d387102..0000000
--- a/oldweb/js/submit.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import { waitForElementToDisplay } from "./helpers.js";
-function submitFormData(formData) {
- fetch("/submit", {
- method: "POST",
- body: formData,
- })
- .then((response) => {
- if (!response.ok) {
- throw new Error(
- `Error ${response.status}: ${response.statusText}`,
- );
- }
- return response.json();
- })
- .then((data) => {
- console.log(data);
- location.href = "/p/" + data.pasteUrl;
- })
- .catch((error) => {
- console.error("Error:", error);
- showToast("warning", "Failed to submit the data");
- });
-}
-
-function submit() {
- const expiration = document.getElementById("expiration").value;
- const burn = document.getElementById("burn").value;
- const password = document.getElementById("password").value;
- const syntax = document.getElementById("syntax").value;
- const privacy = document.getElementById("privacy").value;
-
- const text = document.getElementById("text-input").value;
- const file = document.getElementById("file-input").files[0] || null;
-
- console.log(expiration, burn, password, syntax, privacy, text, file);
-
- const formData = new FormData();
- formData.append("expiration", expiration);
- formData.append("burn", burn);
- formData.append("password", password);
- formData.append("syntax", syntax);
- formData.append("privacy", privacy);
- formData.append("text", text);
- formData.append("file", file);
-
- submitFormData(formData);
-}
-
-waitForElementToDisplay(
- "body > div.buttons > div.right-buttons > button.submit-button",
- function () {
- document
- .querySelector(
- "body > div.buttons > div.right-buttons > button.submit-button",
- )
- .addEventListener("click", submit);
- },
- 500,
- 5000,
-);
diff --git a/oldweb/js/themechange.js b/oldweb/js/themechange.js
deleted file mode 100644
index 08db7c2..0000000
--- a/oldweb/js/themechange.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { showToast } from "./toast.js";
-import { waitForElementToDisplay } from "./helpers.js";
-import { languageOption } from "./preview.js";
-import { codeToHtml } from "https://esm.sh/shiki@1.1.7";
-
-async function changeTheme() {
- const theme = document.getElementById("theme").value;
- document.getElementById("preview").innerHTML = await codeToHtml(
- document.getElementById("text-input").value,
- {
- lang: languageOption(),
- theme: theme,
- },
- );
- showToast("info", `Theme changed to ${theme}`);
-}
-
-waitForElementToDisplay(
- "#theme",
- function () {
- document
- .getElementById("theme")
- .addEventListener("change", async () => {
- changeTheme();
- });
- },
- 500,
- 5000,
-);
diff --git a/oldweb/js/toast.js b/oldweb/js/toast.js
deleted file mode 100644
index bca7f08..0000000
--- a/oldweb/js/toast.js
+++ /dev/null
@@ -1,53 +0,0 @@
-export function showToast(type, message) {
- const toastContainer = document.getElementById("toastContainer");
- const existingToasts = toastContainer.querySelectorAll(".toast");
- if (existingToasts.length > 0) {
- moveToastUp(existingToasts);
- }
- const toast = document.createElement("div");
- toast.className = "toast";
-
- const span = document.createElement("span");
- span.textContent = message;
- toast.appendChild(span);
-
- toastContainer.appendChild(toast);
-
- if (type === "warning") {
- toast.classList.add("warning-toast");
- } else if (type === "info") {
- toast.classList.add("info-toast");
- } else {
- toast.classList.add("default-toast");
- }
-
- toastContainer.appendChild(toast);
- toast.offsetHeight;
- toast.classList.add("show-toast");
-
- setTimeout(() => {
- toast.classList.remove("show-toast");
- setTimeout(() => {
- toast.remove();
- if (!toastContainer.querySelector(".toast")) {
- resetToastPositions(existingToasts);
- }
- }, 500);
- }, 3000);
-}
-
-function moveToastUp(toasts) {
- toasts.forEach((toast) => {
- const newBottom =
- parseInt(window.getComputedStyle(toast).bottom) +
- toast.offsetHeight +
- 10;
- toast.style.bottom = newBottom + "px";
- });
-}
-
-function resetToastPositions(toasts) {
- toasts.forEach((toast) => {
- toast.style.bottom = "20px";
- });
-}
diff --git a/oldweb/templates/404.html b/oldweb/templates/404.html
deleted file mode 100644
index 6b16677..0000000
--- a/oldweb/templates/404.html
+++ /dev/null
@@ -1,5 +0,0 @@
-{{ define "404" }}
-
404
- Page not found
- home
-{{ end }}
diff --git a/oldweb/templates/about.html b/oldweb/templates/about.html
deleted file mode 100644
index cf87505..0000000
--- a/oldweb/templates/about.html
+++ /dev/null
@@ -1,15 +0,0 @@
-{{ define "about" }}
-
-
about
-
pawste tries to be a simplistic and lightweight pastebin service.
-
- some stylistic choices were taken from
- microbin
-
-
this is a hobby project to learn go some more and database things
-
- source code is available on
- github
-
-
-{{ end }}
diff --git a/oldweb/templates/admin.html b/oldweb/templates/admin.html
deleted file mode 100644
index 773020d..0000000
--- a/oldweb/templates/admin.html
+++ /dev/null
@@ -1,36 +0,0 @@
-{{ define "admin" }}
-
-
-
-
-
-
-
quick stuff
-
-
- version
- {{ .Version }}
-
-
- uptime
- {{ .Uptime }}
-
-
- uploads
- {{ .UploadCount }}
-
-
-
Reload config
-
-
- {{ template "list" . }}
-
-{{ end }}
diff --git a/oldweb/templates/auth_admin.html b/oldweb/templates/auth_admin.html
deleted file mode 100644
index e69de29..0000000
diff --git a/oldweb/templates/buttons.html b/oldweb/templates/buttons.html
deleted file mode 100644
index 98d7f4a..0000000
--- a/oldweb/templates/buttons.html
+++ /dev/null
@@ -1,76 +0,0 @@
-{{ define "buttons" }}
-
-
-
-
-
-{{ end }}
diff --git a/oldweb/templates/container.html b/oldweb/templates/container.html
deleted file mode 100644
index b5d4dfe..0000000
--- a/oldweb/templates/container.html
+++ /dev/null
@@ -1,9 +0,0 @@
-{{ define "container" }}
-
-
-{{ end }}
diff --git a/oldweb/templates/edit.html b/oldweb/templates/edit.html
deleted file mode 100644
index 9dd42fd..0000000
--- a/oldweb/templates/edit.html
+++ /dev/null
@@ -1,3 +0,0 @@
-{{ define "edit" }}
- todo
-{{ end }}
diff --git a/oldweb/templates/footer.html b/oldweb/templates/footer.html
deleted file mode 100644
index 51af814..0000000
--- a/oldweb/templates/footer.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{{ define "footer" }}
-
- source
- contact
- email
-{{ end }}
diff --git a/oldweb/templates/guide.html b/oldweb/templates/guide.html
deleted file mode 100644
index 7923262..0000000
--- a/oldweb/templates/guide.html
+++ /dev/null
@@ -1,53 +0,0 @@
-{{ define "guide" }}
-
-
-
-
-
Expiration:
-
Select how long the content will remain accessible.
-
-
-
Burn After:
-
- Choose the number of views after which the content will be
- deleted.
-
-
-
-
Syntax and theme:
-
- Specify the programming language or format for syntax
- highlighting. You can also choose a theme for the code.
-
-
-
-
Privacy:
-
Determine who can access the content.
-
-
- Public: Content is accessible to everyone.
- Meaning anyone can modify or remove the paste.
-
-
- Unlisted: Above but not listed publicly.
-
-
- Read-only: Above and cannot be modified /
- removed unless password is provided.
-
-
- Private: Above and password is required to
- see the paste. The content and attachments are encrypted on
- server.
-
-
- Secret: Above but contents and attachments
- are also encrypted on client.
-
-
-
-
-
-
-
-{{ end }}
diff --git a/oldweb/templates/header.html b/oldweb/templates/header.html
deleted file mode 100644
index 16820bb..0000000
--- a/oldweb/templates/header.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{{ define "pastebinHeader" }}
-
- pawste
- new
- list
- guide
- about
-{{ end }}
diff --git a/oldweb/templates/list.html b/oldweb/templates/list.html
deleted file mode 100644
index fe1f0f5..0000000
--- a/oldweb/templates/list.html
+++ /dev/null
@@ -1,29 +0,0 @@
-{{ define "list" }}
- pastes
-
-
- {{ range .PasteLists.Pastes }}
-
-
- {{ .PasteName }}
-
- {{ .Expire }}
-
- {{ end }}
-
- redirects
-
- {{ range .PasteLists.Redirects }}
-
-
- {{ .PasteName }}
-
- {{ .Expire }}
-
- {{ end }}
-
-{{ end }}
diff --git a/oldweb/templates/main.html b/oldweb/templates/main.html
deleted file mode 100644
index acc99c8..0000000
--- a/oldweb/templates/main.html
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
- pawste
-
-
-
- {{ template "pastebinHeader" . }}
- {{ if .Admin }}
- {{ template "admin" . }}
- {{ else if .List }}
- {{ template "list" . }}
- {{ else if .Guide }}
- {{ template "guide" . }}
- {{ else if .About }}
- {{ template "about" . }}
- {{ else if .NotFound }}
- {{ template "404" . }}
- {{ else if .Paste }}
- {{ template "view_paste" . }}
- {{ else if .Edit }}
- {{ template "edit" . }}
- {{ else }}
- {{ template "options" . }}
- {{ template "container" . }}
- {{ template "buttons" . }}
- {{ template "footer". }}
- {{ template "toast" . }}
- {{ end }}
-
-
diff --git a/oldweb/templates/options.html b/oldweb/templates/options.html
deleted file mode 100644
index fed5216..0000000
--- a/oldweb/templates/options.html
+++ /dev/null
@@ -1,60 +0,0 @@
-{{ define "options" }}
-
-
-
-
-
- Expiration:
-
- Never
- 1 min
- 10 min
- 1 Hours
- 6 Hours
- 1 Day
- 3 Days
- 1 Week
-
-
-
- Burn after:
-
- Never
- 5 Views
- 10 Views
- 100 Views
-
-
-
- Syntax:
-
- None
-
-
-
- Privacy:
-
- Public
- Unlisted
- Read-only
- Private
- Secret
-
-
-
-
-
-
- Paste syntax theme:
-
-
-
-{{ end }}
diff --git a/oldweb/templates/password.html b/oldweb/templates/password.html
deleted file mode 100644
index f57c2c5..0000000
--- a/oldweb/templates/password.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{{ define "password-input" }}
-
-
-{{ end }}
diff --git a/oldweb/templates/toast.html b/oldweb/templates/toast.html
deleted file mode 100644
index bacec82..0000000
--- a/oldweb/templates/toast.html
+++ /dev/null
@@ -1,4 +0,0 @@
-{{ define "toast" }}
-
-
-{{ end }}
diff --git a/oldweb/templates/view_paste.html b/oldweb/templates/view_paste.html
deleted file mode 100644
index 6cbe952..0000000
--- a/oldweb/templates/view_paste.html
+++ /dev/null
@@ -1,3 +0,0 @@
-{{ define "view_paste" }}
- {{ .Paste.Content }}
-{{ end }}
diff --git a/package.json b/package.json
index 35890f1..b0d732b 100644
--- a/package.json
+++ b/package.json
@@ -1,19 +1,33 @@
{
- "name": "pawste",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "keywords": [],
- "author": "Masterjoona",
- "license": "ISC",
- "type": "module",
- "dependencies": {
- "golte": "^0.0.4",
- "prettier": "^3.2.5",
- "prettier-plugin-go-template": "^0.0.15",
- "svelte": "^4.2.18"
- }
-}
\ No newline at end of file
+ "name": "pawste",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "dev": "golte dev && go run .",
+ "build": "golte"
+ },
+ "keywords": [],
+ "author": "Masterjoona",
+ "license": "ISC",
+ "type": "module",
+ "dependencies": {
+ "@fontsource-variable/fira-code": "^5.0.18",
+ "@fontsource/fira-mono": "^5.0.13",
+ "@fontsource/short-stack": "^5.0.12",
+ "@fortawesome/fontawesome-free": "^6.6.0",
+ "@sveltejs/adapter-auto": "^3.2.2",
+ "@sveltejs/kit": "^2.5.20",
+ "@sveltejs/vite-plugin-svelte": "^3.1.1",
+ "@zerodevx/svelte-toast": "^0.9.5",
+ "golte": "^0.1.0",
+ "shiki": "^1.12.1",
+ "svelte": "^4.2.18",
+ "svelte-copy": "^1.4.2"
+ },
+ "devDependencies": {
+ "prettier": "^3.3.3",
+ "prettier-plugin-svelte": "^3.2.6"
+ }
+}
diff --git a/paste/types.go b/paste/types.go
deleted file mode 100644
index 9d51830..0000000
--- a/paste/types.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package paste
-
-type File struct {
- ID int
- Name string
- Size int
- Blob []byte
-}
-
-type Paste struct {
- ID int
- PasteName string
- Expire string
- Privacy string
- IsEncrypted int
- ReadCount int
- ReadLast string
- BurnAfter int
- Content string
- UrlRedirect int
- Syntax string
- Password string
- Files []File
- CreatedAt string
- UpdatedAt string
-}
-
-type PasteLists struct {
- Pastes []Paste
- Redirects []Paste
-}
diff --git a/pkg/config/config.go b/pkg/config/config.go
new file mode 100644
index 0000000..9010d9b
--- /dev/null
+++ b/pkg/config/config.go
@@ -0,0 +1,135 @@
+package config
+
+import (
+ "net/http"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ prettyconsole "github.com/thessem/zap-prettyconsole"
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+var Vars ConfigEnv
+var Logger *zap.SugaredLogger
+
+const (
+ PawsteVersion = ""
+ envPrefix = "PAWSTE_"
+)
+
+func (ConfigEnv) InitConfig() {
+ Vars = ConfigEnv{
+ Salt: getEnv("SALT", "banana"),
+ Port: getEnv("PORT", ":9454"),
+ DataDir: getEnv("DATA_DIR", "pawste_data/"),
+ AdminPassword: getEnv("ADMIN_PASSWORD", "admin"),
+ PublicList: getEnv("PUBLIC_LIST", "true") == "true",
+ FileUpload: getEnv("FILE_UPLOAD", "true") == "true",
+ MaxFileSize: getEnvInt("MAX_FILE_SIZE", "1024 * 1024 * 10"),
+ MaxEncryptionSize: getEnvInt("MAX_ENCRYPTION_SIZE", "1024 * 1024 * 10"),
+ MaxContentLength: getEnvInt("MAX_CONTENT_LENGTH", "5000"),
+ FileUploadingPassword: getEnv("FILE_UPLOADING_PASSWORD", ""),
+ EternalPaste: getEnv("ETERNAL_PASTE", "false") == "true",
+ MaxExpiryTime: getEnv("MAX_EXPIRY_TIME", "1w"),
+ ReadCount: getEnv("READ_COUNT", "true") == "true",
+ BurnAfter: getEnv("BURN_AFTER", "true") == "true",
+ DefaultExpiry: getEnv("DEFAULT_EXPIRY", "1w"),
+ ShortPasteNames: getEnv("SHORT_PASTE_NAMES", "false") == "true",
+ ShortenRedirectPastes: getEnv("SHORTEN_REDIRECT_PASTES", "false") == "true",
+ CountFileUsage: getEnv("COUNT_FILE_USAGE", "true") == "true",
+ AnimeGirlMode: getEnv("ANIME_GIRL_MODE", "false") == "true",
+ LogLevel: getEnv("LOG_LEVEL", "info"),
+ AnonymiseFileNames: getEnv("ANONYMISE_FILE_NAMES", "false") == "true",
+ NormalizeFilenames: getEnv("NORMALIZE_FILE_NAMES", "false") == "true",
+ }
+
+ if _, err := os.Stat(Vars.DataDir); os.IsNotExist(err) {
+ os.Mkdir(Vars.DataDir, 0755)
+ }
+ parseLogger()
+}
+
+func getEnv(key, fallback string) string {
+ if value, exists := os.LookupEnv(envPrefix + key); exists {
+ println("Using environment variable", envPrefix+key, "with value", value)
+ return value
+ }
+ return fallback
+}
+
+func getEnvInt(key string, fallback string) int {
+ if value, exists := os.LookupEnv(envPrefix + key); exists {
+ println("Using environment variable", envPrefix+key, "with value", value)
+ return calculateIntFromString(value)
+ }
+ return calculateIntFromString(fallback)
+}
+
+func calculateIntFromString(s string) int {
+ parts := strings.Split(s, "*")
+ result := 1
+ for _, part := range parts {
+ num, err := strconv.Atoi(strings.TrimSpace(part))
+ if err != nil {
+ panic(err)
+ }
+ result *= num
+ }
+ return result
+}
+
+func (ConfigEnv) ReloadConfig(c *gin.Context) {
+ password := c.Request.Header.Get("password")
+ if password == "" || password != Vars.AdminPassword {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "wrong password"})
+ return
+ }
+ Vars.InitConfig()
+ c.JSON(http.StatusOK, gin.H{"message": "reloaded config"})
+}
+
+func ParseDuration(input string) time.Duration {
+ matches := TimeRegex.FindStringSubmatch(input)
+ if len(matches) != 3 {
+ Logger.Debug("Invalid duration:", input)
+ return time.Duration(OneWeek)
+ }
+
+ quantity, err := strconv.Atoi(matches[1])
+ if err != nil {
+ Logger.Debug("Invalid duration:", input)
+ return time.Duration(OneWeek)
+ }
+
+ unit := matches[2]
+ unitMultipliers := map[string]int{
+ "s": 1,
+ "m": 60,
+ "h": 3600,
+ "d": 86400,
+ "w": 604800,
+ "M": 2592000,
+ }
+
+ multiplier, exists := unitMultipliers[unit]
+ if !exists {
+ Logger.Debug("Invalid duration:", input)
+ return time.Duration(OneWeek)
+ }
+
+ return time.Duration(quantity*multiplier) * time.Second
+}
+
+func parseLogger() {
+ var logLevel zapcore.Level
+ err := logLevel.UnmarshalText([]byte(Vars.LogLevel))
+ if err != nil {
+ println("Invalid log level:", Vars.LogLevel)
+ logLevel = zap.InfoLevel
+ }
+ Logger = prettyconsole.NewLogger(logLevel).Sugar()
+}
diff --git a/pkg/config/types.go b/pkg/config/types.go
new file mode 100644
index 0000000..cc58a5a
--- /dev/null
+++ b/pkg/config/types.go
@@ -0,0 +1,39 @@
+package config
+
+import (
+ "regexp"
+ "time"
+
+ "math/rand"
+)
+
+type ConfigEnv struct {
+ Salt string
+ Port string
+ DataDir string
+ AdminPassword string
+ PublicList bool
+ FileUpload bool
+ MaxFileSize int
+ MaxEncryptionSize int
+ MaxContentLength int
+ FileUploadingPassword string
+ EternalPaste bool
+ MaxExpiryTime string
+ ReadCount bool
+ BurnAfter bool
+ DefaultExpiry string
+ ShortPasteNames bool
+ ShortenRedirectPastes bool
+ CountFileUsage bool
+ AnimeGirlMode bool
+ LogLevel string
+ AnonymiseFileNames bool
+ NormalizeFilenames bool
+}
+
+var TimeRegex = regexp.MustCompile(`^(\d+)([smhdwMy])$`)
+
+const OneWeek = time.Hour * 24 * 7
+
+var RandomSource = rand.New(rand.NewSource(time.Now().UnixNano()))
diff --git a/pkg/database/create_paste.go b/pkg/database/create_paste.go
new file mode 100644
index 0000000..eecdd01
--- /dev/null
+++ b/pkg/database/create_paste.go
@@ -0,0 +1,134 @@
+package database
+
+import (
+ "database/sql"
+ "os"
+
+ "github.com/Masterjoona/pawste/pkg/config"
+ "github.com/Masterjoona/pawste/pkg/paste"
+ "github.com/Masterjoona/pawste/pkg/utils"
+ _ "github.com/mattn/go-sqlite3"
+)
+
+func CreatePaste(newPaste paste.Paste) error {
+ tx, err := PasteDB.Begin()
+ if err != nil {
+ return err
+ }
+ defer rollbackAndClose(tx, &err)
+
+ stmt, err := tx.Prepare(`
+ INSERT INTO pastes(PasteName, Expire, Privacy, NeedsAuth, ReadCount, ReadLast, BurnAfter, Content, UrlRedirect, Syntax, Password, CreatedAt, UpdatedAt)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ `)
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ encrypt := (newPaste.Privacy == "private" || newPaste.Privacy == "secret") &&
+ newPaste.Password != ""
+ if encrypt {
+ newPaste.Content, err = paste.EncryptText(newPaste.Password, newPaste.Content)
+ if err != nil {
+ return err
+ }
+ }
+
+ NewPassword := utils.Ternary((encrypt || newPaste.Privacy == "readonly"), HashPassword(newPaste.Password), "")
+
+ _, err = stmt.Exec(
+ newPaste.PasteName,
+ newPaste.Expire,
+ newPaste.Privacy,
+ newPaste.NeedsAuth,
+ newPaste.ReadCount,
+ newPaste.ReadLast,
+ newPaste.BurnAfter,
+ newPaste.Content,
+ newPaste.UrlRedirect,
+ newPaste.Syntax,
+ NewPassword,
+ newPaste.CreatedAt,
+ newPaste.UpdatedAt,
+ )
+
+ if err != nil {
+ return err
+ }
+
+ err = saveFiles(tx, &newPaste, encrypt)
+ if err != nil {
+ return err
+ }
+
+ return tx.Commit()
+}
+
+func saveFiles(tx *sql.Tx, newPaste *paste.Paste, encrypt bool) error {
+ for _, file := range newPaste.Files {
+ if config.Vars.AnonymiseFileNames {
+ file.Name = createShortFileName(newPaste.PasteName) // we dont have collision problems
+ }
+ if config.Vars.NormalizeFilenames {
+ file.Name = utils.NormalizeFilename(file.Name)
+ }
+ if encrypt {
+ err := paste.Encrypt(newPaste.Password, &file.Blob)
+ if err != nil {
+ config.Logger.Error("Failed to encrypt file:", err)
+ return err
+ }
+ }
+ err := saveFileToDisk(&file, newPaste.PasteName)
+ if err != nil {
+ config.Logger.Error("Failed to save file to disk:", err)
+ return err
+ }
+
+ stmt, err := tx.Prepare(`
+ INSERT INTO files(PasteName, Name, Size, ContentType)
+ VALUES (?, ?, ?, ?)
+ `)
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ _, err = stmt.Exec(
+ newPaste.PasteName,
+ file.Name,
+ file.Size,
+ file.ContentType,
+ )
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func saveFileToDisk(file *paste.File, pasteName string) error {
+ err := os.MkdirAll(config.Vars.DataDir+pasteName, 0755)
+ if err != nil {
+ return err
+ }
+ return os.WriteFile(
+ config.Vars.DataDir+pasteName+"/"+file.Name,
+ file.Blob,
+ 0644,
+ )
+}
+
+func rollbackAndClose(tx *sql.Tx, err *error) {
+ if *err != nil {
+ if rollbackErr := tx.Rollback(); rollbackErr != nil {
+ config.Logger.Error("Failed to rollback transaction:", rollbackErr)
+ }
+ return
+ }
+
+ if commitErr := tx.Commit(); commitErr != nil {
+ *err = commitErr
+ }
+}
diff --git a/pkg/database/create_tables.go b/pkg/database/create_tables.go
new file mode 100644
index 0000000..8936162
--- /dev/null
+++ b/pkg/database/create_tables.go
@@ -0,0 +1,67 @@
+package database
+
+import (
+ "database/sql"
+ "os"
+ "time"
+
+ "github.com/Masterjoona/pawste/pkg/config"
+ _ "github.com/mattn/go-sqlite3"
+)
+
+var PasteDB *sql.DB
+
+func CreateOrLoadDatabase() {
+ createPasteTable := `
+ create table pastes
+ (ID integer not null primary key,
+ PasteName text,
+ Expire integer,
+ Privacy text,
+ NeedsAuth integer,
+ ReadCount integer,
+ ReadLast integer,
+ BurnAfter integer,
+ Content text,
+ Syntax text,
+ Password text,
+ UrlRedirect integer,
+ CreatedAt integer,
+ UpdatedAt integer
+ );
+ `
+
+ createFileTable := `
+ create table files
+ (ID integer not null primary key,
+ PasteName text,
+ Name text,
+ Size integer,
+ ContentType text
+ );
+ `
+
+ sqldb, err := sql.Open("sqlite3", config.Vars.DataDir+"pastes.db")
+ if err != nil {
+ config.Logger.Fatal("Could not open database", err)
+ }
+ if _, err := os.Stat(config.Vars.DataDir + "pastes.db"); os.IsNotExist(err) {
+ _, err := sqldb.Exec(createPasteTable)
+ if err != nil {
+ config.Logger.Fatal("Could not create pastes table", err)
+ }
+ _, err = sqldb.Exec(createFileTable)
+ if err != nil {
+ config.Logger.Fatal("Could not create files table", err)
+ }
+ config.Logger.Info("Created new database")
+ } else {
+ config.Logger.Info("Loaded existing database")
+ }
+ PasteDB = sqldb
+
+ go func() {
+ cleanUpExpiredPastes()
+ time.Sleep(1 * time.Hour)
+ }()
+}
diff --git a/pkg/database/expiration.go b/pkg/database/expiration.go
new file mode 100644
index 0000000..e90f781
--- /dev/null
+++ b/pkg/database/expiration.go
@@ -0,0 +1,60 @@
+package database
+
+import (
+ "os"
+
+ "github.com/Masterjoona/pawste/pkg/config"
+ _ "github.com/mattn/go-sqlite3"
+)
+
+func cleanUpExpiredPastes() {
+ pastes, err := PasteDB.Query(
+ "select PasteName from pastes where (Expire < strftime('%s', 'now') and 0 < Expire) or (BurnAfter <= ReadCount and BurnAfter > 0)",
+ )
+ if err != nil {
+ config.Logger.Error("Could not clean up expired pastes", err)
+ return
+ }
+ defer pastes.Close()
+
+ tx, err := PasteDB.Begin()
+ if err != nil {
+ config.Logger.Error("Could not start transaction", err)
+ return
+ }
+
+ for pastes.Next() {
+ var pasteName string
+ err = pastes.Scan(&pasteName)
+ if err != nil {
+ config.Logger.Error("Could not scan pasteName", err)
+ tx.Rollback()
+ return
+ }
+
+ config.Logger.Info("Cleaning up paste with name " + pasteName)
+ _, err = tx.Exec("delete from pastes where PasteName = ?", pasteName)
+ if err != nil {
+ config.Logger.Error("Could not delete paste with name "+pasteName, err)
+ tx.Rollback()
+ return
+ }
+ _, err = tx.Exec("delete from files where PasteName = ?", pasteName)
+ if err != nil {
+ config.Logger.Error("Could not delete files in db for paste with name "+pasteName, err)
+ tx.Rollback()
+ return
+ }
+ err = os.RemoveAll(config.Vars.DataDir + "/" + pasteName)
+ if err != nil {
+ config.Logger.Error("Could not delete files for paste with name "+pasteName, err)
+ }
+ }
+
+ if err := tx.Commit(); err != nil {
+ config.Logger.Error("Could not commit transaction", err)
+ return
+ }
+
+ config.Logger.Debug("Expired pastes cleaned up successfully")
+}
diff --git a/database/paste_name.go b/pkg/database/paste_name.go
similarity index 61%
rename from database/paste_name.go
rename to pkg/database/paste_name.go
index cc4d1ba..e17db0d 100644
--- a/database/paste_name.go
+++ b/pkg/database/paste_name.go
@@ -1,11 +1,9 @@
package database
import (
- "math/rand"
"strings"
- "time"
- "github.com/Masterjoona/pawste/shared/config"
+ "github.com/Masterjoona/pawste/pkg/config"
)
var AnimalNames = []string{
@@ -77,15 +75,14 @@ var AnimalNames = []string{
const characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
-func CreatePasteName(shorten int) string {
- r := rand.New(rand.NewSource(time.Now().UnixNano()))
- if config.Config.ShortPasteNames || shorten == 1 {
- return createShortPasteName(r)
+func createPasteName(shorten int) string {
+ if config.Vars.ShortPasteNames || (shorten == 1 && config.Vars.ShortenRedirectPastes) {
+ return createShortPasteName()
}
for {
var name strings.Builder
for i := 0; i < 3; i++ {
- name.WriteString(AnimalNames[r.Intn(len(AnimalNames))])
+ name.WriteString(AnimalNames[config.RandomSource.Intn(len(AnimalNames))])
name.WriteString("-")
}
trimmedName := name.String()[:name.Len()-1]
@@ -95,11 +92,11 @@ func CreatePasteName(shorten int) string {
}
}
-func createShortPasteName(r *rand.Rand) string {
+func createShortPasteName() string {
for {
var name strings.Builder
for i := 0; i < 6; i++ {
- name.WriteByte(characters[r.Intn(len(characters))])
+ name.WriteByte(characters[config.RandomSource.Intn(len(characters))])
}
trimmedName := name.String()
if !pasteExists(trimmedName) {
@@ -107,3 +104,16 @@ func createShortPasteName(r *rand.Rand) string {
}
}
}
+
+func createShortFileName(pasteName string) string {
+ for {
+ var name strings.Builder
+ for i := 0; i < 6; i++ {
+ name.WriteByte(characters[config.RandomSource.Intn(len(characters))])
+ }
+ trimmedName := name.String()
+ if !fileExists(pasteName, trimmedName) {
+ return trimmedName
+ }
+ }
+}
diff --git a/pkg/database/query_files.go b/pkg/database/query_files.go
new file mode 100644
index 0000000..cff4631
--- /dev/null
+++ b/pkg/database/query_files.go
@@ -0,0 +1,71 @@
+package database
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/Masterjoona/pawste/pkg/config"
+ "github.com/Masterjoona/pawste/pkg/paste"
+ _ "github.com/mattn/go-sqlite3"
+)
+
+func queryFiles(addQuery string, valueArgs []string, scanVariables []string) []paste.File {
+ valueInterfaces := make([]interface{}, len(valueArgs))
+ for i, v := range valueArgs {
+ valueInterfaces[i] = v
+ }
+ query := "select " + strings.Join(scanVariables, ", ") + " from files " + addQuery
+ rows, err := PasteDB.Query(
+ query,
+ valueInterfaces...,
+ )
+
+ if err != nil {
+ config.Logger.Error("Could not query files", err)
+ return nil
+ }
+ defer rows.Close()
+ var files []paste.File
+
+ for rows.Next() {
+ var file paste.File
+ if err := rows.Scan(MakeFilePointers(&file, scanVariables)...); err != nil {
+ config.Logger.Error("Could not scan file", err)
+ continue
+ }
+ files = append(files, file)
+ }
+ if err := rows.Err(); err != nil {
+ config.Logger.Error("Could not scan files", err)
+ return nil
+ }
+ return files
+}
+
+func GetFiles(pasteName string) []paste.File {
+ return queryFiles(
+ "where PasteName = ?",
+ []string{pasteName},
+ []string{
+ "Name",
+ "Size",
+ "ContentType",
+ },
+ )
+}
+
+func GetFile(pasteName string, fileName string) (paste.File, error) {
+ files := queryFiles(
+ "where PasteName = ? and Name = ?",
+ []string{pasteName, fileName},
+ []string{
+ "Name",
+ "Size",
+ "ContentType",
+ },
+ )
+ if len(files) == 0 {
+ return paste.File{}, errors.New("file not found")
+ }
+ return files[0], nil
+}
diff --git a/database/query_paste.go b/pkg/database/query_pastes.go
similarity index 67%
rename from database/query_paste.go
rename to pkg/database/query_pastes.go
index d19834f..9546791 100644
--- a/database/query_paste.go
+++ b/pkg/database/query_pastes.go
@@ -4,12 +4,13 @@ import (
"errors"
"strings"
- "github.com/Masterjoona/pawste/paste"
+ "github.com/Masterjoona/pawste/pkg/config"
+ "github.com/Masterjoona/pawste/pkg/paste"
_ "github.com/mattn/go-sqlite3"
)
-func getPastes(addQuery string, valueArgs []string, scanVariables []string) []paste.Paste {
- CleanUpExpiredPastes()
+func queryPastes(addQuery string, valueArgs []string, scanVariables []string) []paste.Paste {
+ cleanUpExpiredPastes()
valueInterfaces := make([]interface{}, len(valueArgs))
for i, v := range valueArgs {
@@ -22,7 +23,8 @@ func getPastes(addQuery string, valueArgs []string, scanVariables []string) []pa
)
if err != nil {
- panic(err)
+ config.Logger.Error("Could not query pastes", err)
+ return nil
}
defer rows.Close()
var pastes []paste.Paste
@@ -30,44 +32,36 @@ func getPastes(addQuery string, valueArgs []string, scanVariables []string) []pa
for rows.Next() {
var paste paste.Paste
if err := rows.Scan(MakePastePointers(&paste, scanVariables)...); err != nil {
- panic(err)
+ config.Logger.Error("Could not scan paste", err)
+ continue
}
pastes = append(pastes, paste)
}
if err := rows.Err(); err != nil {
- panic(err)
+ config.Logger.Error("Could not scan pastes", err)
+ return nil
}
return pastes
}
-func GetAllPastes() paste.PasteLists {
- pastes := getPastes(
- "where UrlRedirect = '0'",
- []string{},
- []string{
- "PasteName",
- "Expire",
- "Privacy",
- "BurnAfter",
- })
- redirects := getPastes(
- "where UrlRedirect != '0'",
+func GetAllPastes() []paste.Paste {
+ return queryPastes(
+ "",
[]string{},
[]string{
"PasteName",
"Expire",
"Privacy",
+ "ReadCount",
"BurnAfter",
- "Syntax",
+ "UrlRedirect",
+ "ReadLast",
+ "CreatedAt",
})
- return paste.PasteLists{
- Pastes: pastes,
- Redirects: redirects,
- }
}
func GetPublicPastes() []paste.Paste {
- return getPastes(
+ return queryPastes(
"where Privacy = 'public' and UrlRedirect = '0'",
[]string{},
[]string{
@@ -80,7 +74,7 @@ func GetPublicPastes() []paste.Paste {
}
func GetPublicRedirects() []paste.Paste {
- return getPastes(
+ return queryPastes(
"where Privacy = 'public' and UrlRedirect != '0'",
[]string{},
[]string{
@@ -88,20 +82,32 @@ func GetPublicRedirects() []paste.Paste {
"Expire",
"Privacy",
"BurnAfter",
- "Syntax",
+ },
+ )
+}
+
+func GetAllPublicPastes() []paste.Paste {
+ return queryPastes(
+ "where Privacy = 'public'",
+ []string{},
+ []string{
+ "PasteName",
+ "Expire",
+ "ReadCount",
+ "UrlRedirect",
},
)
}
func GetPasteByName(pasteName string) (paste.Paste, error) {
- pastes := getPastes(
+ pastes := queryPastes(
"where PasteName = ?",
[]string{pasteName},
[]string{
- "ID",
"PasteName",
"Expire",
"Privacy",
+ "NeedsAuth",
"ReadCount",
"ReadLast",
"BurnAfter",
diff --git a/database/remove_paste.go b/pkg/database/remove_paste.go
similarity index 55%
rename from database/remove_paste.go
rename to pkg/database/remove_paste.go
index b59780a..e970784 100644
--- a/database/remove_paste.go
+++ b/pkg/database/remove_paste.go
@@ -1,48 +1,60 @@
package database
import (
+ "os"
+
+ "github.com/Masterjoona/pawste/pkg/config"
_ "github.com/mattn/go-sqlite3"
- "github.com/romana/rlog"
)
-func RemovePaste(pasteName string) {
+func removePaste(pasteName string) error {
tx, err := PasteDB.Begin()
if err != nil {
- panic(err)
+ return err
}
stmt, err := tx.Prepare("delete from pastes where PasteName = ?")
if err != nil {
- panic(err)
+ return err
}
defer stmt.Close()
_, err = stmt.Exec(pasteName)
if err != nil {
- panic(err)
+ return err
}
err = tx.Commit()
if err != nil {
- panic(err)
+ return err
}
- rlog.Info("Removed paste from database", pasteName)
+ return nil
}
-func RemoveFiles(pasteName string) {
+func removeFiles(pasteName string) error {
tx, err := PasteDB.Begin()
if err != nil {
- panic(err)
+ return err
}
stmt, err := tx.Prepare("delete from files where PasteName = ?")
if err != nil {
- panic(err)
+ return err
}
defer stmt.Close()
_, err = stmt.Exec(pasteName)
if err != nil {
- panic(err)
+ return err
}
err = tx.Commit()
if err != nil {
- panic(err)
+ return err
}
- rlog.Info("Removed files from db for paste", pasteName)
+
+ return os.RemoveAll(config.Vars.DataDir + pasteName)
+}
+
+func DeletePaste(pasteName string) error {
+ err := removeFiles(pasteName)
+ if err != nil {
+ return err
+ }
+
+ return removePaste(pasteName)
}
diff --git a/pkg/database/submit.go b/pkg/database/submit.go
new file mode 100644
index 0000000..ea86498
--- /dev/null
+++ b/pkg/database/submit.go
@@ -0,0 +1,46 @@
+package database
+
+import (
+ "time"
+
+ "github.com/Masterjoona/pawste/pkg/config"
+ "github.com/Masterjoona/pawste/pkg/paste"
+ "github.com/Masterjoona/pawste/pkg/utils"
+)
+
+func SubmitToPaste(submit paste.Submit, isRedirect int) (paste.Paste, error) {
+ var files []paste.File
+ for _, file := range submit.Files {
+ if file == nil {
+ continue
+ }
+ fileName, fileSize, fileBlob, err := paste.ConvertMultipartFile(file)
+ if err != nil {
+ return paste.Paste{}, err
+ }
+ files = append(files, paste.File{
+ Name: fileName,
+ Size: fileSize,
+ Blob: fileBlob,
+ ContentType: file.Header.Get("Content-Type"),
+ })
+ }
+ todaysDate := time.Now().Unix()
+ pasteName := createPasteName(isRedirect)
+ return paste.Paste{
+ PasteName: pasteName,
+ Expire: utils.HumanTimeToUnix(submit.Expiration),
+ Privacy: submit.Privacy,
+ NeedsAuth: utils.Ternary((submit.Password == ""), 0, 1),
+ ReadCount: 0,
+ ReadLast: todaysDate,
+ BurnAfter: utils.Ternary(config.Vars.BurnAfter, submit.BurnAfter, 0),
+ Content: submit.Text,
+ Syntax: submit.Syntax,
+ Password: submit.Password,
+ Files: files,
+ UrlRedirect: isRedirect,
+ CreatedAt: todaysDate,
+ UpdatedAt: todaysDate,
+ }, nil
+}
diff --git a/pkg/database/update_paste.go b/pkg/database/update_paste.go
new file mode 100644
index 0000000..51cac23
--- /dev/null
+++ b/pkg/database/update_paste.go
@@ -0,0 +1,197 @@
+package database
+
+import (
+ "database/sql"
+ "os"
+
+ "github.com/Masterjoona/pawste/pkg/config"
+ "github.com/Masterjoona/pawste/pkg/paste"
+ "github.com/Masterjoona/pawste/pkg/utils"
+)
+
+func UpdateReadCount(pasteName string) {
+ amount := 1
+ if !config.Vars.ReadCount {
+ amount = 0
+ }
+ _, err := PasteDB.Exec(
+ "update pastes set ReadCount = ReadCount + ?, ReadLast = strftime('%s', 'now') where PasteName = ?",
+ amount,
+ pasteName,
+ )
+ if err != nil {
+ config.Logger.Error(err)
+ }
+
+ burnIfNeeded(pasteName)
+}
+
+func burnIfNeeded(pasteName string) {
+ row := PasteDB.QueryRow(
+ "select case when BurnAfter <= ReadCount and BurnAfter > 0 then 1 else 0 end from pastes where PasteName = ?",
+ pasteName,
+ )
+ var burned int
+ err := row.Scan(&burned)
+ if err != nil {
+ config.Logger.Error(err)
+ }
+ if burned == 1 {
+ cleanUpExpiredPastes()
+ }
+}
+
+func updatePasteContent(pasteName, password, content string) error {
+ tx, err := PasteDB.Begin()
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ if err != nil {
+ tx.Rollback()
+ config.Logger.Error(err)
+ }
+ err = tx.Commit()
+ if err != nil {
+ config.Logger.Error(err)
+ }
+ }()
+ stmt, err := tx.Prepare(`
+ update pastes set
+ Content = ?,
+ UrlRedirect = ?,
+ UpdatedAt = strftime('%s', 'now')
+ where PasteName = ?
+ `)
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ if password != "" {
+ content, err = paste.EncryptText(password, content)
+ if err != nil {
+ return err
+ }
+ }
+
+ _, err = stmt.Exec(
+ content,
+ utils.IsContentJustUrl(content),
+ pasteName,
+ )
+ return err
+}
+
+func updatePasteFiles(pasteName, password string, newPaste paste.PasteUpdate) error {
+ tx, err := PasteDB.Begin()
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ if err != nil {
+ tx.Rollback()
+ config.Logger.Error(err)
+ }
+ err = tx.Commit()
+ if err != nil {
+ config.Logger.Error(err)
+ }
+ }()
+
+ for _, file := range newPaste.RemovedFiles {
+ err = deleteFile(tx, pasteName, file)
+ if err != nil {
+ return err
+ }
+ }
+
+ if len(newPaste.Files) > 0 {
+ err = os.MkdirAll(config.Vars.DataDir+pasteName, 0755)
+ if err != nil {
+ return err
+ }
+ }
+
+ for _, file := range newPaste.Files {
+ err = insertFile(tx, pasteName, password, file)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func deleteFile(tx *sql.Tx, pasteName, fileName string) error {
+ stmt, err := tx.Prepare(`
+ delete from files where PasteName = ? and Name = ?
+ `)
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ _, err = stmt.Exec(
+ pasteName,
+ fileName,
+ )
+
+ if err != nil {
+ return err
+ }
+
+ return os.Remove(config.Vars.DataDir + pasteName + "/" + fileName)
+}
+
+func insertFile(tx *sql.Tx, pasteName, password string, file paste.File) error {
+ stmt, err := tx.Prepare(`
+ insert into files(PasteName, Name, Size, ContentType)
+ values (?, ?, ?, ?)
+ `)
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ if config.Vars.AnonymiseFileNames {
+ file.Name = createShortFileName(pasteName)
+ }
+ if config.Vars.NormalizeFilenames {
+ file.Name = utils.NormalizeFilename(file.Name)
+ }
+
+ _, err = stmt.Exec(
+ pasteName,
+ file.Name,
+ file.Size,
+ file.ContentType,
+ )
+
+ if err != nil {
+ return err
+ }
+
+ if password != "" {
+ err = paste.Encrypt(password, &file.Blob)
+ if err != nil {
+ return err
+ }
+ }
+
+ return os.WriteFile(
+ config.Vars.DataDir+pasteName+"/"+file.Name,
+ file.Blob,
+ 0644,
+ )
+}
+
+func UpdatePaste(pasteName, password string, newPaste paste.PasteUpdate) error {
+ err := updatePasteContent(pasteName, password, newPaste.Content)
+ if err != nil {
+ return err
+ }
+
+ return updatePasteFiles(pasteName, password, newPaste)
+}
diff --git a/pkg/database/utils.go b/pkg/database/utils.go
new file mode 100644
index 0000000..3165192
--- /dev/null
+++ b/pkg/database/utils.go
@@ -0,0 +1,53 @@
+package database
+
+import (
+ "crypto/sha256"
+ "fmt"
+ "reflect"
+
+ "github.com/Masterjoona/pawste/pkg/config"
+ "github.com/Masterjoona/pawste/pkg/paste"
+ _ "github.com/mattn/go-sqlite3"
+ "golang.org/x/crypto/pbkdf2"
+)
+
+func MakePastePointers(paste *paste.Paste, scanVariables []string) []interface{} {
+ pastePointers := make([]interface{}, len(scanVariables))
+ val := reflect.ValueOf(paste).Elem()
+ for i, variable := range scanVariables {
+ pastePointers[i] = val.FieldByName(variable).Addr().Interface()
+ }
+ return pastePointers
+}
+
+func MakeFilePointers(paste *paste.File, scanVariables []string) []interface{} {
+ filePointers := make([]interface{}, len(scanVariables))
+ val := reflect.ValueOf(paste).Elem()
+ for i, variable := range scanVariables {
+ filePointers[i] = val.FieldByName(variable).Addr().Interface()
+ }
+ return filePointers
+}
+
+func exists(query string, args ...interface{}) bool {
+ var exists bool
+ err := PasteDB.QueryRow(query, args...).Scan(&exists)
+ if err != nil {
+ config.Logger.Error("Could not check if exists", err)
+ return false
+ }
+ return exists
+}
+
+func pasteExists(name string) bool {
+ return exists("select exists(select 1 from pastes where PasteName = ?)", name)
+}
+
+func fileExists(pasteName, name string) bool {
+ return exists("select exists(select 1 from files where PasteName = ? and Name = ?)", pasteName, name)
+}
+
+func HashPassword(password string) string {
+ hash := pbkdf2.Key([]byte(password), []byte(config.Vars.Salt), 10000, 32, sha256.New)
+ return fmt.Sprintf("%x", hash)
+}
diff --git a/pkg/handling/handleJson.go b/pkg/handling/handleJson.go
new file mode 100644
index 0000000..07655c7
--- /dev/null
+++ b/pkg/handling/handleJson.go
@@ -0,0 +1,174 @@
+package handling
+
+import (
+ "net/http"
+
+ "github.com/Masterjoona/pawste/pkg/config"
+ "github.com/Masterjoona/pawste/pkg/database"
+ "github.com/Masterjoona/pawste/pkg/paste"
+ "github.com/Masterjoona/pawste/pkg/utils"
+ "github.com/gin-gonic/gin"
+)
+
+func HandlePasteJson(c *gin.Context) {
+ pasteName := c.Param("pasteName")
+ queriedPaste, err := database.GetPasteByName(pasteName)
+ if err != nil {
+ c.JSON(http.StatusNotFound, gin.H{"error": "paste not found"})
+ return
+ }
+ reqPassword := c.Request.Header.Get("password")
+ needsAuth := queriedPaste.NeedsAuth == 1
+ if needsAuth && !isValidPassword(reqPassword, queriedPaste.Password) {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "wrong password"})
+ return
+ }
+ if needsAuth && queriedPaste.Privacy != "readonly" {
+ queriedPaste.Content, err = paste.DecryptText(reqPassword, queriedPaste.Content)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
+ config.Logger.Error("Failed to decrypt paste:", err)
+ return
+ }
+ }
+
+ queriedPaste.Files = database.GetFiles(pasteName)
+
+ database.UpdateReadCount(pasteName)
+ c.JSON(http.StatusOK, queriedPaste)
+}
+
+func HandleEditJson(c *gin.Context) {
+ queriedPaste, err := database.GetPasteByName(c.Param("pasteName"))
+ if err != nil {
+ c.JSON(http.StatusNotFound, gin.H{"error": "paste not found"})
+ return
+ }
+ pasteFiles := database.GetFiles(queriedPaste.PasteName)
+
+ var newPaste paste.PasteUpdate
+ if err := c.Bind(&newPaste); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ form, err := c.MultipartForm()
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "form error: " + err.Error()})
+ return
+ }
+
+ newPaste.FilesMultiPart = form.File["files[]"]
+
+ needsAuth := queriedPaste.Privacy == "private" || queriedPaste.Privacy == "secret"
+ if needsAuth && queriedPaste.Password != database.HashPassword(newPaste.Password) {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "wrong password"})
+ return
+ }
+
+ if config.Vars.MaxContentLength < len(newPaste.Content) {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "content too long"})
+ return
+ }
+
+ currentFileSizeTotal := 0
+ for _, file := range pasteFiles {
+ currentFileSizeTotal += file.Size
+ }
+ filesToBeRemoved := newPaste.RemovedFiles
+ for _, file := range pasteFiles {
+ if filesToBeRemoved == nil {
+ break
+ }
+ for _, fileName := range filesToBeRemoved {
+ if fileName == file.Name {
+ currentFileSizeTotal -= file.Size
+ }
+ }
+ }
+ for _, file := range newPaste.FilesMultiPart {
+ currentFileSizeTotal += int(file.Size)
+ fileName, fileSize, fileBlob, err := paste.ConvertMultipartFile(file)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "internal server error"})
+ return
+ }
+ newPaste.Files = append(newPaste.Files, paste.File{
+ Name: fileName,
+ Size: fileSize,
+ Blob: fileBlob,
+ ContentType: file.Header.Get("Content-Type"),
+ })
+ }
+
+ if config.Vars.MaxFileSize > 0 && currentFileSizeTotal > config.Vars.MaxFileSize {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "file size too big"})
+ return
+ }
+
+ needsEncryption := queriedPaste.NeedsAuth == 1 && queriedPaste.Privacy != "readonly"
+
+ err = database.UpdatePaste(queriedPaste.PasteName, utils.Ternary(needsEncryption, newPaste.Password, ""), newPaste)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ config.Logger.Error("Failed to update paste:", err)
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{"message": "paste updated"})
+}
+
+func HandlePasteDelete(c *gin.Context) {
+ pasteName := c.Param("pasteName")
+ queriedPaste, err := database.GetPasteByName(pasteName)
+ if err != nil {
+ c.JSON(http.StatusNotFound, gin.H{"error": "paste not found"})
+ return
+ }
+
+ if queriedPaste.NeedsAuth == 1 {
+ password := c.Request.Header.Get("password")
+ if password == "" || !isValidPassword(password, queriedPaste.Password) && password != config.Vars.AdminPassword {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "wrong password"})
+ return
+ }
+ }
+
+ if err := database.DeletePaste(pasteName); err != nil {
+ config.Logger.Error("Failed to delete paste:", err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{"message": "paste deleted"})
+}
+
+func HandleFileJson(c *gin.Context) {
+ paste, err := database.GetPasteByName(c.Param("pasteName"))
+ if err != nil {
+ c.JSON(http.StatusNotFound, gin.H{"error": "paste not found"})
+ return
+ }
+
+ reqPassword := c.Request.Header.Get("password")
+ needsAuth := paste.NeedsAuth == 1
+ if needsAuth && paste.Privacy != "readonly" && !isValidPassword(reqPassword, paste.Password) {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "wrong password"})
+ return
+ }
+
+ file, err := database.GetFile(paste.PasteName, c.Param("fileName"))
+ if err != nil {
+ c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
+ return
+ }
+ c.JSON(http.StatusOK, file)
+}
+
+func HandleAdminJson(c *gin.Context) {
+ passwd := c.Request.Header.Get("password")
+ if passwd == "" || passwd != config.Vars.AdminPassword {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "wrong password"})
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{"config": config.Vars, "pastes": database.GetAllPastes()})
+}
diff --git a/pkg/handling/handlePages.go b/pkg/handling/handlePages.go
new file mode 100644
index 0000000..144d1ad
--- /dev/null
+++ b/pkg/handling/handlePages.go
@@ -0,0 +1,94 @@
+package handling
+
+import (
+ "net/http"
+
+ "github.com/Masterjoona/pawste/pkg/config"
+ "github.com/Masterjoona/pawste/pkg/database"
+ "github.com/Masterjoona/pawste/pkg/paste"
+ "github.com/Masterjoona/pawste/pkg/utils"
+ "github.com/gin-gonic/gin"
+ "github.com/nichady/golte"
+)
+
+func RedirectHome(c *gin.Context) {
+ c.Redirect(http.StatusFound, "/")
+}
+
+func Redirect(c *gin.Context) {
+ pasteName := c.Param("pasteName")
+ paste, err := database.GetPasteByName(pasteName)
+ if err != nil {
+ c.Redirect(http.StatusFound, "/")
+ return
+ }
+ if paste.UrlRedirect == 0 {
+ c.Redirect(http.StatusFound, "/p/"+pasteName)
+ return
+ }
+ database.UpdateReadCount(pasteName)
+ c.Redirect(http.StatusFound, paste.Content)
+}
+
+func HandleNewPage(c *gin.Context) {
+ golte.RenderPage(c.Writer, c.Request, "page/new", map[string]any{
+ "fileUpload": config.Vars.FileUpload,
+ "maxFileSize": config.Vars.MaxFileSize,
+ "maxEncryptionSize": config.Vars.MaxEncryptionSize,
+ "maxContentLength": config.Vars.MaxContentLength,
+ "fileUploadPassword": utils.Ternary(config.Vars.FileUploadingPassword == "", false, true),
+ })
+}
+
+func HandlePaste(c *gin.Context) {
+ pasteName := c.Param("pasteName")
+ queriedPaste, err := database.GetPasteByName(pasteName)
+ if err != nil {
+ c.Redirect(http.StatusFound, "/")
+ return
+ }
+
+ needsAuth := queriedPaste.NeedsAuth == 1
+ burnAfter := queriedPaste.BurnAfter == 1
+ if needsAuth && queriedPaste.Privacy != "readonly" {
+ golte.RenderPage(c.Writer, c.Request, "page/paste", map[string]any{
+ "needsAuth": true,
+ "paste": paste.Paste{},
+ "burnAfter": burnAfter,
+ })
+ return
+ }
+
+ if burnAfter && c.Query("read") == "" {
+ golte.RenderPage(c.Writer, c.Request, "page/oneview", nil)
+ return
+ }
+ queriedPaste.Files = database.GetFiles(pasteName)
+ golte.RenderPage(c.Writer, c.Request, "page/paste", map[string]any{
+ "paste": queriedPaste,
+ "needsAuth": needsAuth,
+ })
+ database.UpdateReadCount(pasteName)
+}
+
+func HandleEdit(c *gin.Context) {
+ queriedPaste, err := database.GetPasteByName(c.Param("pasteName"))
+ if err != nil {
+ c.Redirect(http.StatusFound, "/")
+ return
+ }
+ if queriedPaste.NeedsAuth == 1 {
+ tmpPaste := paste.Paste{}
+ tmpPaste.Files = []paste.File{}
+ golte.RenderPage(c.Writer, c.Request, "page/edit", map[string]any{
+ "needsAuth": queriedPaste.NeedsAuth == 1,
+ "paste": tmpPaste,
+ })
+ return
+ }
+ queriedPaste.Files = database.GetFiles(queriedPaste.PasteName)
+ golte.RenderPage(c.Writer, c.Request, "page/edit", map[string]any{
+ "paste": queriedPaste,
+ })
+
+}
diff --git a/pkg/handling/handleRaw.go b/pkg/handling/handleRaw.go
new file mode 100644
index 0000000..3950dc7
--- /dev/null
+++ b/pkg/handling/handleRaw.go
@@ -0,0 +1,90 @@
+package handling
+
+import (
+ "net/http"
+ "os"
+
+ "github.com/Masterjoona/pawste/pkg/config"
+ "github.com/Masterjoona/pawste/pkg/database"
+ "github.com/Masterjoona/pawste/pkg/paste"
+ "github.com/gin-gonic/gin"
+ "github.com/nichady/golte"
+)
+
+func HandlePasteRaw(c *gin.Context) {
+ pasteName := c.Param("pasteName")
+ queriedPaste, err := database.GetPasteByName(pasteName)
+ if err != nil {
+ c.String(http.StatusNotFound, "Paste not found")
+ return
+ }
+
+ reqPassword := c.Request.Header.Get("password")
+ needsAuth := queriedPaste.NeedsAuth == 1 && queriedPaste.Privacy != "readonly"
+ if needsAuth {
+ if !isValidPassword(reqPassword, queriedPaste.Password) {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "wrong password"})
+ return
+ }
+
+ queriedPaste.Content, err = paste.DecryptText(reqPassword, queriedPaste.Content)
+ if err != nil {
+ c.String(http.StatusInternalServerError, "Failed to decrypt paste")
+ config.Logger.Error("Failed to decrypt paste:", err)
+ return
+ }
+ }
+
+ database.UpdateReadCount(pasteName)
+ c.String(http.StatusOK, queriedPaste.Content)
+}
+
+func HandleFile(c *gin.Context) {
+ queriedPaste, err := database.GetPasteByName(c.Param("pasteName"))
+ if err != nil {
+ c.JSON(http.StatusNotFound, gin.H{"error": "paste not found"})
+ return
+ }
+
+ reqPassword := c.Request.Header.Get("password")
+ encrypted := queriedPaste.NeedsAuth == 1 && queriedPaste.Privacy != "readonly"
+ if encrypted && reqPassword == "" {
+ golte.RenderPage(c.Writer, c.Request, "page/auth_file", nil)
+ return
+ }
+ handleFileRaw(c, reqPassword, encrypted, queriedPaste)
+}
+
+func handleFileRaw(c *gin.Context, reqPassword string, encrypted bool, queriedPaste paste.Paste) {
+ if encrypted && !isValidPassword(reqPassword, queriedPaste.Password) {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "wrong password"})
+ return
+ }
+
+ fileDb, err := database.GetFile(queriedPaste.PasteName, c.Param("fileName"))
+ if err != nil {
+ c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
+ return
+ }
+ filePath := config.Vars.DataDir + "/" + queriedPaste.PasteName + "/" + fileDb.Name
+ if encrypted {
+ fileBlob, err := os.ReadFile(filePath)
+ if err != nil {
+ config.Logger.Error("Failed to read file", err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read file"})
+ return
+ }
+ fileBytes, err := paste.Decrypt(reqPassword, fileBlob)
+ if err != nil {
+ config.Logger.Error("Failed to decrypt file", err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to decrypt file"})
+ return
+ }
+ c.Data(http.StatusOK, fileDb.ContentType, fileBytes)
+ return
+ }
+ if config.Vars.CountFileUsage {
+ database.UpdateReadCount(queriedPaste.PasteName)
+ }
+ c.File(filePath)
+}
diff --git a/pkg/handling/submit.go b/pkg/handling/submit.go
new file mode 100644
index 0000000..a984883
--- /dev/null
+++ b/pkg/handling/submit.go
@@ -0,0 +1,125 @@
+package handling
+
+import (
+ "errors"
+ "net/http"
+ "strconv"
+
+ "github.com/Masterjoona/pawste/pkg/config"
+ "github.com/Masterjoona/pawste/pkg/database"
+ "github.com/Masterjoona/pawste/pkg/paste"
+ "github.com/Masterjoona/pawste/pkg/utils"
+ "github.com/gin-gonic/gin"
+)
+
+func HandleSubmit(c *gin.Context) {
+ submit, err := parseSubmitForm(c)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ if err = validateSubmit(&submit); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ isRedirect := utils.IsContentJustUrl(submit.Text)
+
+ paste, err := database.SubmitToPaste(submit, isRedirect)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
+ return
+ }
+ err = database.CreatePaste(paste)
+ if err != nil {
+ config.Logger.Error("Error creating paste", err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
+ return
+ }
+ for i := range paste.Files {
+ paste.Files[i].Blob = nil
+ }
+
+ c.JSON(http.StatusOK, paste)
+}
+
+func parseSubmitForm(c *gin.Context) (paste.Submit, error) {
+ var submit paste.Submit
+ submit.Text = c.PostForm("content")
+ submit.Expiration = c.PostForm("expire")
+ submit.Password = c.PostForm("password")
+ submit.UploadPassword = c.PostForm("upload_password")
+ submit.Syntax = c.PostForm("syntax")
+ submit.Privacy = c.PostForm("privacy")
+ burnAfterInt, err := strconv.Atoi(c.PostForm("burnafter"))
+ if err != nil {
+ return paste.Submit{}, errors.New("burnafter must be an integer")
+ }
+ submit.BurnAfter = burnAfterInt
+
+ form, err := c.MultipartForm()
+ if err != nil {
+ return paste.Submit{}, errors.New("form error: " + err.Error())
+ }
+
+ submit.Files = form.File["files[]"]
+ return submit, nil
+}
+
+func validateSubmit(submit *paste.Submit) error {
+ hasFiles := len(submit.Files) > 0
+ if submit.Text == "" && !hasFiles {
+ return errors.New("text or files is required")
+ }
+
+ if 0 < len(submit.Files) && !config.Vars.FileUpload {
+ return errors.New("file uploads are disabled")
+ }
+
+ if config.Vars.FileUploadingPassword != "" && submit.UploadPassword == "" || (submit.UploadPassword != config.Vars.FileUploadingPassword) {
+ return errors.New("invalid upload password")
+ }
+
+ needsAuth := (submit.Privacy == "private" || submit.Privacy == "secret" || submit.Privacy == "readonly")
+ if submit.Password == "" && needsAuth {
+ return errors.New("password is required for private, secret or readonly pastes")
+ }
+
+ if 128 < len(submit.Password) {
+ return errors.New("keep them passwords under sane lengths :)")
+ }
+
+ if !paste.PrivacyMap.Contains(submit.Privacy) {
+ return errors.New("invalid privacy")
+ }
+
+ if !paste.SyntaxMap.Contains(submit.Syntax) {
+ return errors.New("invalid syntax")
+ }
+
+ if !config.Vars.EternalPaste && submit.Expiration == "never" {
+ submit.Expiration = "1w"
+ }
+
+ if config.Vars.MaxContentLength < len(submit.Text) {
+ return errors.New("content is too long")
+ }
+
+ maxSizeFiles := utils.Ternary((needsAuth && submit.Privacy != "readonly"), config.Vars.MaxEncryptionSize, config.Vars.MaxFileSize)
+
+ if hasFiles {
+ totalSize := 0
+ for _, file := range submit.Files {
+ if file == nil {
+ continue
+ }
+ totalSize += int(file.Size)
+ }
+ if totalSize > maxSizeFiles {
+ return errors.New("files are too large")
+ }
+ }
+
+ return nil
+}
diff --git a/pkg/handling/utils.go b/pkg/handling/utils.go
new file mode 100644
index 0000000..56ac983
--- /dev/null
+++ b/pkg/handling/utils.go
@@ -0,0 +1,9 @@
+package handling
+
+import (
+ "github.com/Masterjoona/pawste/pkg/database"
+)
+
+func isValidPassword(inputPassword, storedPassword string) bool {
+ return database.HashPassword(inputPassword) == storedPassword
+}
diff --git a/paste/secret.go b/pkg/paste/secret.go
similarity index 68%
rename from paste/secret.go
rename to pkg/paste/secret.go
index 178e83c..9077e67 100644
--- a/paste/secret.go
+++ b/pkg/paste/secret.go
@@ -7,13 +7,16 @@ import (
"crypto/sha256"
"encoding/hex"
"io"
+ "math"
- "github.com/Masterjoona/pawste/shared/config"
+ "github.com/Masterjoona/pawste/pkg/config"
"golang.org/x/crypto/pbkdf2"
)
func SecurePassword(password string) []byte {
- return []byte(config.Config.Salt[:len(password)/2] + password + config.Config.Salt + password[:len(password)/2])
+ salt := config.Vars.Salt
+ halfLen := int(math.Ceil(float64(len(password)) / 2.0))
+ return []byte(salt[:halfLen] + password + salt + password[:halfLen])
}
func deriveKey(password string) []byte {
@@ -21,7 +24,7 @@ func deriveKey(password string) []byte {
return hash[:]
}
-func (f *File) Encrypt(password string) error {
+func Encrypt(password string, blob *[]byte) error {
key := deriveKey(password)
nonce := make([]byte, 12)
@@ -41,13 +44,13 @@ func (f *File) Encrypt(password string) error {
return err
}
- f.Blob = aesgcm.Seal(f.Blob[:0], nonce, f.Blob, nil)
- f.Blob = append(f.Blob, nonce...)
+ *blob = aesgcm.Seal((*blob)[:0], nonce, *blob, nil)
+ *blob = append(*blob, nonce...)
+
return nil
}
-func (f *File) Decrypt(password string) ([]byte, error) {
- fileBlob := f.Blob
+func Decrypt(password string, fileBlob []byte) ([]byte, error) {
key := deriveKey(password)
salt := fileBlob[len(fileBlob)-12:]
onlyFile := fileBlob[:len(fileBlob)-12]
@@ -80,64 +83,63 @@ func (f *File) Decrypt(password string) ([]byte, error) {
return fileBlob[:len(decryptedFileBytes)], nil
}
-func (p *Paste) EncryptText() error {
- key := deriveKey(p.Password)
+func EncryptText(password, content string) (string, error) {
+ key := deriveKey(password)
nonce := make([]byte, 12)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
- return err
+ return "", err
}
dk := pbkdf2.Key(key, nonce, 4096, 32, sha256.New)
block, err := aes.NewCipher(dk)
if err != nil {
- return err
+ return "", err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
- return err
+ return "", err
}
- ciphertext := aesgcm.Seal(nil, nonce, []byte(p.Content), nil)
+ ciphertext := aesgcm.Seal(nil, nonce, []byte(content), nil)
ciphertext = append(ciphertext, nonce...)
- p.Content = hex.EncodeToString(ciphertext)
- return nil
+ return hex.EncodeToString(ciphertext), nil
}
-func (p *Paste) DecryptText() string {
- ciphertext, err := hex.DecodeString(p.Content)
+func DecryptText(password, content string) (string, error) {
+ ciphertext, err := hex.DecodeString(content)
if err != nil {
- panic(err)
+ return "", err
}
- key := deriveKey(p.Password)
+ key := deriveKey(password)
salt := ciphertext[len(ciphertext)-12:]
str := hex.EncodeToString(salt)
nonce, err := hex.DecodeString(str)
if err != nil {
- panic(err)
+ return "", err
}
dk := pbkdf2.Key(key, nonce, 4096, 32, sha256.New)
block, err := aes.NewCipher(dk)
if err != nil {
- panic(err)
+ return "", err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
- panic(err)
+ return "", err
}
plaintext, err := aesgcm.Open(nil, nonce, ciphertext[:len(ciphertext)-12], nil)
if err != nil {
- panic(err)
+ return "", err
}
- return string(plaintext)
+ return string(plaintext), nil
}
diff --git a/pkg/paste/types.go b/pkg/paste/types.go
new file mode 100644
index 0000000..7e6e130
--- /dev/null
+++ b/pkg/paste/types.go
@@ -0,0 +1,292 @@
+package paste
+
+import (
+ "mime/multipart"
+)
+
+type File struct {
+ ID int
+ Name string
+ Size int
+ ContentType string
+ Blob []byte
+}
+
+type Paste struct {
+ ID int
+ PasteName string
+ Expire int64
+ Privacy string
+ NeedsAuth int
+ ReadCount int
+ ReadLast int64
+ BurnAfter int
+ Content string
+ UrlRedirect int
+ Syntax string
+ Password string
+ Files []File
+ CreatedAt int64
+ UpdatedAt int64
+}
+
+type Submit struct {
+ Text string
+ Expiration string
+ BurnAfter int
+ Password string
+ UploadPassword string
+ Syntax string
+ Privacy string
+ Files []*multipart.FileHeader
+}
+
+type PasteLists struct {
+ Pastes []Paste
+ Redirects []Paste
+}
+
+type PasteUpdate struct {
+ Content string `form:"content,omitempty"`
+ Password string `form:"password,omitempty"`
+ RemovedFiles []string `form:"removed_files,omitempty"`
+ FilesMultiPart []*multipart.FileHeader `form:"file,omitempty"`
+ Files []File
+}
+
+type LookUpMap struct {
+ Items map[string]struct{}
+}
+
+func createLookupMap(slice []string) *LookUpMap {
+ lookup := make(map[string]struct{}, len(slice))
+ for _, item := range slice {
+ lookup[item] = struct{}{}
+ }
+ return &LookUpMap{Items: lookup}
+}
+
+func (l *LookUpMap) Contains(str string) bool {
+ _, found := l.Items[str]
+ return found
+}
+
+var privacyOptions = []string{"public", "unlisted", "readonly", "private", "secret"}
+
+var syntaxOptions = []string{
+ "none",
+ "abap",
+ "actionscript-3",
+ "ada",
+ "angular-html",
+ "angular-ts",
+ "apache",
+ "apex",
+ "apl",
+ "applescript",
+ "ara",
+ "asciidoc",
+ "asm",
+ "astro",
+ "awk",
+ "ballerina",
+ "bat",
+ "beancount",
+ "berry",
+ "bibtex",
+ "bicep",
+ "blade",
+ "c",
+ "cadence",
+ "clarity",
+ "clojure",
+ "cmake",
+ "cobol",
+ "codeowners",
+ "codeql",
+ "coffee",
+ "common-lisp",
+ "cpp",
+ "crystal",
+ "csharp",
+ "css",
+ "csv",
+ "cue",
+ "cypher",
+ "d",
+ "dart",
+ "dax",
+ "desktop",
+ "diff",
+ "docker",
+ "dotenv",
+ "dream-maker",
+ "edge",
+ "elixir",
+ "elm",
+ "emacs-lisp",
+ "erb",
+ "erlang",
+ "fennel",
+ "fish",
+ "fluent",
+ "fortran-fixed-form",
+ "fortran-free-form",
+ "fsharp",
+ "gdresource",
+ "gdscript",
+ "gdshader",
+ "genie",
+ "gherkin",
+ "git-commit",
+ "git-rebase",
+ "gleam",
+ "glimmer-js",
+ "glimmer-ts",
+ "glsl",
+ "gnuplot",
+ "go",
+ "graphql",
+ "groovy",
+ "hack",
+ "haml",
+ "handlebars",
+ "haskell",
+ "haxe",
+ "hcl",
+ "hjson",
+ "hlsl",
+ "html",
+ "html-derivative",
+ "http",
+ "hxml",
+ "hy",
+ "imba",
+ "ini",
+ "java",
+ "javascript",
+ "jinja",
+ "jison",
+ "json",
+ "json5",
+ "jsonc",
+ "jsonl",
+ "jsonnet",
+ "jssm",
+ "jsx",
+ "julia",
+ "kotlin",
+ "kusto",
+ "latex",
+ "lean",
+ "less",
+ "liquid",
+ "log",
+ "logo",
+ "lua",
+ "luau",
+ "make",
+ "markdown",
+ "marko",
+ "matlab",
+ "mdc",
+ "mdx",
+ "mermaid",
+ "mojo",
+ "move",
+ "narrat",
+ "nextflow",
+ "nginx",
+ "nim",
+ "nix",
+ "nushell",
+ "objective-c",
+ "objective-cpp",
+ "ocaml",
+ "pascal",
+ "perl",
+ "php",
+ "plsql",
+ "po",
+ "postcss",
+ "powerquery",
+ "powershell",
+ "prisma",
+ "prolog",
+ "proto",
+ "pug",
+ "puppet",
+ "purescript",
+ "python",
+ "qml",
+ "qmldir",
+ "qss",
+ "r",
+ "racket",
+ "raku",
+ "razor",
+ "reg",
+ "regexp",
+ "rel",
+ "riscv",
+ "rst",
+ "ruby",
+ "rust",
+ "sas",
+ "sass",
+ "scala",
+ "scheme",
+ "scss",
+ "shaderlab",
+ "shellscript",
+ "shellsession",
+ "smalltalk",
+ "solidity",
+ "soy",
+ "sparql",
+ "splunk",
+ "sql",
+ "ssh-config",
+ "stata",
+ "stylus",
+ "svelte",
+ "swift",
+ "system-verilog",
+ "systemd",
+ "tasl",
+ "tcl",
+ "templ",
+ "terraform",
+ "tex",
+ "toml",
+ "ts-tags",
+ "tsv",
+ "tsx",
+ "turtle",
+ "twig",
+ "typescript",
+ "typespec",
+ "typst",
+ "v",
+ "vala",
+ "vb",
+ "verilog",
+ "vhdl",
+ "viml",
+ "vue",
+ "vue-html",
+ "vyper",
+ "wasm",
+ "wenyan",
+ "wgsl",
+ "wikitext",
+ "wolfram",
+ "xml",
+ "xsl",
+ "yaml",
+ "zenscript",
+ "zig",
+}
+
+var PrivacyMap = createLookupMap(privacyOptions)
+var SyntaxMap = createLookupMap(syntaxOptions)
diff --git a/pkg/paste/utils.go b/pkg/paste/utils.go
new file mode 100644
index 0000000..7e0a33f
--- /dev/null
+++ b/pkg/paste/utils.go
@@ -0,0 +1,24 @@
+package paste
+
+import (
+ "io"
+ "mime/multipart"
+
+ "github.com/Masterjoona/pawste/pkg/config"
+)
+
+func ConvertMultipartFile(file *multipart.FileHeader) (string, int, []byte, error) {
+ src, err := file.Open()
+ if err != nil {
+ config.Logger.Error("Could not open multipart file", err)
+ return "", 0, nil, err
+ }
+ defer src.Close()
+
+ fileBlob, err := io.ReadAll(src)
+ if err != nil {
+ config.Logger.Error("Could not read multipart file", err)
+ return "", 0, nil, err
+ }
+ return file.Filename, len(fileBlob), fileBlob, nil
+}
diff --git a/oldweb/static/favicon.ico b/pkg/route/favicon.ico
similarity index 100%
rename from oldweb/static/favicon.ico
rename to pkg/route/favicon.ico
diff --git a/pkg/route/route.go b/pkg/route/route.go
new file mode 100644
index 0000000..d20586c
--- /dev/null
+++ b/pkg/route/route.go
@@ -0,0 +1,129 @@
+package route
+
+import (
+ _ "embed"
+ "net/http"
+ "regexp"
+ "time"
+
+ "github.com/Masterjoona/pawste/pkg/build"
+ "github.com/Masterjoona/pawste/pkg/config"
+ "github.com/Masterjoona/pawste/pkg/database"
+ "github.com/Masterjoona/pawste/pkg/handling"
+ "github.com/gin-gonic/gin"
+ "github.com/nichady/golte"
+
+ ginzap "github.com/gin-contrib/zap"
+)
+
+//go:embed favicon.ico
+var favicon []byte
+
+var wrapMiddleware = func(middleware func(http.Handler) http.Handler) func(ctx *gin.Context) {
+ return func(ctx *gin.Context) {
+ middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ctx.Request = r
+ golte.AddLayout(r, "layout/main",
+ map[string]any{
+ "AnimeGirls": config.Vars.AnimeGirlMode,
+ "PublicList": config.Vars.PublicList,
+ },
+ )
+ ctx.Next()
+ })).ServeHTTP(ctx.Writer, ctx.Request)
+ if golte.GetRenderContext(ctx.Request) == nil {
+ ctx.Abort()
+ }
+ }
+}
+
+func page(c string) gin.HandlerFunc {
+ return gin.WrapH(golte.Page(c))
+}
+
+func SetupMiddleware(r *gin.Engine) {
+ var golteRegex = regexp.MustCompile(`/golte_/(?:entries|chunks|assets)/.{1,60}|/favicon`)
+ r.Use(ginzap.GinzapWithConfig(config.Logger.Desugar(), &ginzap.Config{
+ UTC: true,
+ TimeFormat: time.RFC3339,
+ SkipPathRegexps: []*regexp.Regexp{golteRegex},
+ }))
+ r.Use(ginzap.RecoveryWithZap(config.Logger.Desugar(), true))
+ r.Use(wrapMiddleware(build.Golte))
+}
+
+func SetupPublicRoutes(r *gin.Engine) {
+ r.GET("/", handling.HandleNewPage)
+ if config.Vars.PublicList {
+ r.GET("/list", func(c *gin.Context) {
+ golte.RenderPage(c.Writer, c.Request, "page/list", map[string]any{
+ "pastes": database.GetAllPublicPastes(),
+ })
+ })
+ }
+ r.GET("/about", page("page/about"))
+ r.GET("/guide", page("page/guide"))
+ r.GET("/favicon", func(c *gin.Context) {
+ c.Data(http.StatusOK, "image/x-icon", favicon)
+ })
+}
+
+func SetupPasteRoutes(r *gin.Engine) {
+ pasteGroup := r.Group("/p")
+ {
+ pasteGroup.GET("/:pasteName", handling.HandlePaste)
+ pasteGroup.GET("/:pasteName/raw", handling.HandlePasteRaw)
+ pasteGroup.GET("/:pasteName/json", handling.HandlePasteJson)
+ pasteGroup.DELETE("/:pasteName", handling.HandlePasteDelete)
+ pasteGroup.POST("/new", handling.HandleSubmit)
+ pasteGroup.PATCH("/:pasteName", handling.HandleEditJson)
+ pasteGroup.GET("/:pasteName/f/:fileName", handling.HandleFile)
+ pasteGroup.GET("/:pasteName/f/:fileName/json", handling.HandleFileJson)
+ }
+}
+
+func SetupRedirectRoutes(r *gin.Engine) {
+ redirectGroup := r.Group("/")
+ {
+ redirectGroup.GET("u/:pasteName", handling.Redirect)
+ redirectGroup.GET("u", handling.RedirectHome)
+ redirectGroup.GET("p", handling.RedirectHome)
+ redirectGroup.GET("r", handling.RedirectHome)
+ redirectGroup.GET("e", handling.RedirectHome)
+ }
+}
+
+func SetupEditRoutes(r *gin.Engine) {
+ r.GET("/e/:pasteName", handling.HandleEdit)
+}
+
+func SetupAdminRoutes(r *gin.Engine) {
+ adminGroup := r.Group("/admin")
+ {
+ adminGroup.GET("", page("page/admin"))
+ adminGroup.GET("/json", handling.HandleAdminJson)
+ adminGroup.POST("/reload-config", config.Vars.ReloadConfig)
+ }
+}
+
+func SetupErrorHandlers(r *gin.Engine) {
+ r.NoRoute(func(c *gin.Context) {
+ err := "Page not found"
+ config.Logger.Error(err)
+ golte.RenderPage(c.Writer, c.Request, "page/error", map[string]any{
+ "error": err,
+ })
+ })
+
+ r.Use(func(c *gin.Context) {
+ c.Next()
+ if len(c.Errors) > 0 {
+ err := c.Errors.Last().Error()
+ config.Logger.Error(err)
+ golte.RenderPage(c.Writer, c.Request, "page/error", map[string]any{
+ "error": err,
+ })
+ c.Abort()
+ }
+ })
+}
diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go
new file mode 100644
index 0000000..981387f
--- /dev/null
+++ b/pkg/utils/utils.go
@@ -0,0 +1,48 @@
+package utils
+
+import (
+ "regexp"
+ "time"
+ "unicode"
+
+ "golang.org/x/text/runes"
+ "golang.org/x/text/transform"
+ "golang.org/x/text/unicode/norm"
+
+ "github.com/Masterjoona/pawste/pkg/config"
+)
+
+func HumanTimeToUnix(humanTime string) int64 {
+ if humanTime == "never" {
+ return -1
+ }
+ duration := config.ParseDuration(humanTime)
+ if config.ParseDuration(config.Vars.MaxExpiryTime) < duration {
+ return time.Now().Add(time.Duration(config.OneWeek)).Unix()
+ }
+ return time.Now().Add(duration).Unix()
+}
+
+func IsContentJustUrl(content string) int {
+ if regexp.MustCompile(`^(?:http|https|magnet):\/\/[^\s/$.?#].[^\s]*$`).MatchString(content) {
+ return 1
+ }
+ return 0
+}
+
+func Ternary[T any](condition bool, trueVal, falseVal T) T {
+ if condition {
+ return trueVal
+ }
+ return falseVal
+}
+
+var stripRegex = regexp.MustCompile(`[^a-zA-Z0-9._-]+`)
+var trans = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
+
+func NormalizeFilename(str string) string {
+ //return stripRegex.ReplaceAllString(str, "")
+ t, _, _ := transform.String(trans, str)
+ t = stripRegex.ReplaceAllString(t, "")
+ return t
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1885ae0..40682f4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,18 +8,49 @@ importers:
.:
dependencies:
+ '@fontsource-variable/fira-code':
+ specifier: ^5.0.18
+ version: 5.0.18
+ '@fontsource/fira-mono':
+ specifier: ^5.0.13
+ version: 5.0.13
+ '@fontsource/short-stack':
+ specifier: ^5.0.12
+ version: 5.0.12
+ '@fortawesome/fontawesome-free':
+ specifier: ^6.6.0
+ version: 6.6.0
+ '@sveltejs/adapter-auto':
+ specifier: ^3.2.2
+ version: 3.2.2(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.4.0))(svelte@4.2.18)(vite@5.4.0))
+ '@sveltejs/kit':
+ specifier: ^2.5.20
+ version: 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.4.0))(svelte@4.2.18)(vite@5.4.0)
+ '@sveltejs/vite-plugin-svelte':
+ specifier: ^3.1.1
+ version: 3.1.1(svelte@4.2.18)(vite@5.4.0)
+ '@zerodevx/svelte-toast':
+ specifier: ^0.9.5
+ version: 0.9.5(svelte@4.2.18)
golte:
- specifier: ^0.0.4
- version: 0.0.4(rollup@4.18.0)(svelte@4.2.18)
- prettier:
- specifier: ^3.2.5
- version: 3.2.5
- prettier-plugin-go-template:
- specifier: ^0.0.15
- version: 0.0.15(prettier@3.2.5)
+ specifier: ^0.1.0
+ version: 0.1.0(rollup@4.20.0)(svelte@4.2.18)
+ shiki:
+ specifier: ^1.12.1
+ version: 1.12.1
svelte:
specifier: ^4.2.18
version: 4.2.18
+ svelte-copy:
+ specifier: ^1.4.2
+ version: 1.4.2(svelte@4.2.18)
+ devDependencies:
+ prettier:
+ specifier: ^3.3.3
+ version: 3.3.3
+ prettier-plugin-svelte:
+ specifier: ^3.2.6
+ version: 3.2.6(prettier@3.3.3)(svelte@4.2.18)
packages:
@@ -303,6 +334,19 @@ packages:
cpu: [x64]
os: [win32]
+ '@fontsource-variable/fira-code@5.0.18':
+ resolution: {integrity: sha512-Pmbnf4HukX4y928RY9g47hWyVECQglwV7HLv5taaDQwI3UBXyoLUnyRsjJxeviCPhdwJY5qyLT4GaCMlgC9oCQ==}
+
+ '@fontsource/fira-mono@5.0.13':
+ resolution: {integrity: sha512-fZDjR2BdAqmauEbTjcIT62zYzbOgDa5+IQH34D2k8Pxmy1T815mAqQkZciWZVQ9dc/BgdTtTUV9HJ2ulBNwchg==}
+
+ '@fontsource/short-stack@5.0.12':
+ resolution: {integrity: sha512-hcgYMQZ9f/vUznyfGc95SyyCPwq/cB71eFIDaDXRuk81ilfD9uwbuMIH69rzN8ENJY/Zk2vm8fh+Td+m0nLK8g==}
+
+ '@fortawesome/fontawesome-free@6.6.0':
+ resolution: {integrity: sha512-60G28ke/sXdtS9KZCpZSHHkCbdsOGEhIUGlwq6yhY74UpTiToIh8np7A8yphhM4BWsvNFtIvLpi4co+h9Mr9Ow==}
+ engines: {node: '>=6'}
+
'@jridgewell/gen-mapping@0.3.5':
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
engines: {node: '>=6.0.0'}
@@ -315,8 +359,8 @@ packages:
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
engines: {node: '>=6.0.0'}
- '@jridgewell/sourcemap-codec@1.4.15':
- resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
+ '@jridgewell/sourcemap-codec@1.5.0':
+ resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
@@ -333,6 +377,9 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
+ '@polka/url@1.0.0-next.25':
+ resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==}
+
'@rollup/plugin-replace@5.0.7':
resolution: {integrity: sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==}
engines: {node: '>=14.0.0'}
@@ -351,86 +398,103 @@ packages:
rollup:
optional: true
- '@rollup/rollup-android-arm-eabi@4.18.0':
- resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==}
+ '@rollup/rollup-android-arm-eabi@4.20.0':
+ resolution: {integrity: sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==}
cpu: [arm]
os: [android]
- '@rollup/rollup-android-arm64@4.18.0':
- resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==}
+ '@rollup/rollup-android-arm64@4.20.0':
+ resolution: {integrity: sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==}
cpu: [arm64]
os: [android]
- '@rollup/rollup-darwin-arm64@4.18.0':
- resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==}
+ '@rollup/rollup-darwin-arm64@4.20.0':
+ resolution: {integrity: sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==}
cpu: [arm64]
os: [darwin]
- '@rollup/rollup-darwin-x64@4.18.0':
- resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==}
+ '@rollup/rollup-darwin-x64@4.20.0':
+ resolution: {integrity: sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==}
cpu: [x64]
os: [darwin]
- '@rollup/rollup-linux-arm-gnueabihf@4.18.0':
- resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==}
+ '@rollup/rollup-linux-arm-gnueabihf@4.20.0':
+ resolution: {integrity: sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm-musleabihf@4.18.0':
- resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==}
+ '@rollup/rollup-linux-arm-musleabihf@4.20.0':
+ resolution: {integrity: sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm64-gnu@4.18.0':
- resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==}
+ '@rollup/rollup-linux-arm64-gnu@4.20.0':
+ resolution: {integrity: sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-arm64-musl@4.18.0':
- resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==}
+ '@rollup/rollup-linux-arm64-musl@4.20.0':
+ resolution: {integrity: sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-powerpc64le-gnu@4.18.0':
- resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==}
+ '@rollup/rollup-linux-powerpc64le-gnu@4.20.0':
+ resolution: {integrity: sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==}
cpu: [ppc64]
os: [linux]
- '@rollup/rollup-linux-riscv64-gnu@4.18.0':
- resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==}
+ '@rollup/rollup-linux-riscv64-gnu@4.20.0':
+ resolution: {integrity: sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==}
cpu: [riscv64]
os: [linux]
- '@rollup/rollup-linux-s390x-gnu@4.18.0':
- resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==}
+ '@rollup/rollup-linux-s390x-gnu@4.20.0':
+ resolution: {integrity: sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==}
cpu: [s390x]
os: [linux]
- '@rollup/rollup-linux-x64-gnu@4.18.0':
- resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==}
+ '@rollup/rollup-linux-x64-gnu@4.20.0':
+ resolution: {integrity: sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-linux-x64-musl@4.18.0':
- resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==}
+ '@rollup/rollup-linux-x64-musl@4.20.0':
+ resolution: {integrity: sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-win32-arm64-msvc@4.18.0':
- resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==}
+ '@rollup/rollup-win32-arm64-msvc@4.20.0':
+ resolution: {integrity: sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==}
cpu: [arm64]
os: [win32]
- '@rollup/rollup-win32-ia32-msvc@4.18.0':
- resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==}
+ '@rollup/rollup-win32-ia32-msvc@4.20.0':
+ resolution: {integrity: sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==}
cpu: [ia32]
os: [win32]
- '@rollup/rollup-win32-x64-msvc@4.18.0':
- resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==}
+ '@rollup/rollup-win32-x64-msvc@4.20.0':
+ resolution: {integrity: sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==}
cpu: [x64]
os: [win32]
+ '@shikijs/core@1.12.1':
+ resolution: {integrity: sha512-biCz/mnkMktImI6hMfMX3H9kOeqsInxWEyCHbSlL8C/2TR1FqfmGxTLRNwYCKsyCyxWLbB8rEqXRVZuyxuLFmA==}
+
+ '@sveltejs/adapter-auto@3.2.2':
+ resolution: {integrity: sha512-Mso5xPCA8zgcKrv+QioVlqMZkyUQ5MjDJiEPuG/Z7cV/5tmwV7LmcVWk5tZ+H0NCOV1x12AsoSpt/CwFwuVXMA==}
+ peerDependencies:
+ '@sveltejs/kit': ^2.0.0
+
+ '@sveltejs/kit@2.5.20':
+ resolution: {integrity: sha512-47rJ5BoYwURE/Rp7FNMLp3NzdbWC9DQ/PmKd0mebxT2D/PrPxZxcLImcD3zsWdX2iS6oJk8ITJbO/N2lWnnUqA==}
+ engines: {node: '>=18.13'}
+ hasBin: true
+ peerDependencies:
+ '@sveltejs/vite-plugin-svelte': ^3.0.0
+ svelte: ^4.0.0 || ^5.0.0-next.0
+ vite: ^5.0.3
+
'@sveltejs/vite-plugin-svelte-inspector@2.1.0':
resolution: {integrity: sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==}
engines: {node: ^18.0.0 || >=20}
@@ -446,19 +510,34 @@ packages:
svelte: ^4.0.0 || ^5.0.0-next.0
vite: ^5.0.0
+ '@types/cookie@0.6.0':
+ resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
+
'@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
- acorn@8.12.0:
- resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==}
+ '@types/hast@3.0.4':
+ resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
+
+ '@types/unist@3.0.2':
+ resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==}
+
+ '@zerodevx/svelte-toast@0.9.5':
+ resolution: {integrity: sha512-JLeB/oRdJfT+dz9A5bgd3Z7TuQnBQbeUtXrGIrNWMGqWbabpepBF2KxtWVhL2qtxpRqhae2f6NAOzH7xs4jUSw==}
+ peerDependencies:
+ svelte: ^3.57.0 || ^4.0.0
+
+ acorn@8.12.1:
+ resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==}
engines: {node: '>=0.4.0'}
hasBin: true
aria-query@5.3.0:
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
- axobject-query@4.0.0:
- resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==}
+ axobject-query@4.1.0:
+ resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
+ engines: {node: '>= 0.4'}
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
@@ -467,12 +546,16 @@ packages:
code-red@1.0.4:
resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==}
+ cookie@0.6.0:
+ resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
+ engines: {node: '>= 0.6'}
+
css-tree@2.3.1:
resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
- debug@4.3.5:
- resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==}
+ debug@4.3.6:
+ resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
@@ -488,6 +571,9 @@ packages:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
+ devalue@5.0.0:
+ resolution: {integrity: sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA==}
+
esbuild@0.19.12:
resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==}
engines: {node: '>=12'}
@@ -498,6 +584,9 @@ packages:
engines: {node: '>=12'}
hasBin: true
+ esm-env@1.0.0:
+ resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==}
+
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
@@ -524,12 +613,21 @@ packages:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
- golte@0.0.4:
- resolution: {integrity: sha512-7NEpXBpc1AdhoUN48m9D7quypxg6dIcruN2iChR9y5HrNTXCKX+9LU14EsXbJ4zMgZyqaExy8inyr3TxxZ2LDQ==}
+ globalyzer@0.1.0:
+ resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==}
+
+ globrex@0.1.2:
+ resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
+
+ golte@0.1.0:
+ resolution: {integrity: sha512-t5iLHsceP7BZBUSIRtbxTwJkjGUquMZRP313Cc5AAbpjC5GO7x+eX1YeBHHcjfhZkLxZn6+luw4YXynarKxi6g==}
hasBin: true
peerDependencies:
svelte: ^4.2.7
+ import-meta-resolve@4.1.0:
+ resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==}
+
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@@ -552,8 +650,8 @@ packages:
locate-character@3.0.0:
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
- magic-string@0.30.10:
- resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
+ magic-string@0.30.11:
+ resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
mdn-data@2.0.30:
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
@@ -566,6 +664,14 @@ packages:
resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
engines: {node: '>=8.6'}
+ mri@1.2.0:
+ resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
+ engines: {node: '>=4'}
+
+ mrmime@2.0.0:
+ resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==}
+ engines: {node: '>=10'}
+
ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
@@ -584,18 +690,18 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
- postcss@8.4.38:
- resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
+ postcss@8.4.41:
+ resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==}
engines: {node: ^10 || ^12 || >=14}
- prettier-plugin-go-template@0.0.15:
- resolution: {integrity: sha512-WqU92E1NokWYNZ9mLE6ijoRg6LtIGdLMePt2C7UBDjXeDH9okcRI3zRqtnWR4s5AloiqyvZ66jNBAa9tmRY5EQ==}
- engines: {node: '>=14.0.0'}
+ prettier-plugin-svelte@3.2.6:
+ resolution: {integrity: sha512-Y1XWLw7vXUQQZmgv1JAEiLcErqUniAF2wO7QJsw8BVMvpLET2dI5WpEIEJx1r11iHVdSMzQxivyfrH9On9t2IQ==}
peerDependencies:
prettier: ^3.0.0
+ svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0
- prettier@3.2.5:
- resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
+ prettier@3.3.3:
+ resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==}
engines: {node: '>=14'}
hasBin: true
@@ -606,18 +712,37 @@ packages:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
- rollup@4.18.0:
- resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==}
+ rollup@4.20.0:
+ resolution: {integrity: sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ sade@1.8.1:
+ resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
+ engines: {node: '>=6'}
+
+ set-cookie-parser@2.7.0:
+ resolution: {integrity: sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==}
+
+ shiki@1.12.1:
+ resolution: {integrity: sha512-nwmjbHKnOYYAe1aaQyEBHvQymJgfm86ZSS7fT8OaPRr4sbAcBNz7PbfAikMEFSDQ6se2j2zobkXvVKcBOm0ysg==}
+
+ sirv@2.0.4:
+ resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==}
+ engines: {node: '>= 10'}
+
source-map-js@1.2.0:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'}
+ svelte-copy@1.4.2:
+ resolution: {integrity: sha512-Q99JvWDzB58JlbVWX2VxjHX/CQ5ayDLUGGJgL2EqU1+3c9CDOtxLrKFZx5CVN5bN/DDl62nxtazz0j3nGX1Xew==}
+ peerDependencies:
+ svelte: ^3.55.0 || ^4.0.0
+
svelte-hmr@0.16.0:
resolution: {integrity: sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==}
engines: {node: ^12.20 || ^14.13.1 || >= 16}
@@ -628,16 +753,19 @@ packages:
resolution: {integrity: sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==}
engines: {node: '>=16'}
+ tiny-glob@0.2.9:
+ resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}
+
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
- ulid@2.3.0:
- resolution: {integrity: sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==}
- hasBin: true
+ totalist@3.0.1:
+ resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
+ engines: {node: '>=6'}
- vite@5.3.1:
- resolution: {integrity: sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==}
+ vite@5.4.0:
+ resolution: {integrity: sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
@@ -645,6 +773,7 @@ packages:
less: '*'
lightningcss: ^1.21.0
sass: '*'
+ sass-embedded: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
@@ -657,6 +786,8 @@ packages:
optional: true
sass:
optional: true
+ sass-embedded:
+ optional: true
stylus:
optional: true
sugarss:
@@ -817,22 +948,30 @@ snapshots:
'@esbuild/win32-x64@0.21.5':
optional: true
+ '@fontsource-variable/fira-code@5.0.18': {}
+
+ '@fontsource/fira-mono@5.0.13': {}
+
+ '@fontsource/short-stack@5.0.12': {}
+
+ '@fortawesome/fontawesome-free@6.6.0': {}
+
'@jridgewell/gen-mapping@0.3.5':
dependencies:
'@jridgewell/set-array': 1.2.1
- '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/sourcemap-codec': 1.5.0
'@jridgewell/trace-mapping': 0.3.25
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/set-array@1.2.1': {}
- '@jridgewell/sourcemap-codec@1.4.15': {}
+ '@jridgewell/sourcemap-codec@1.5.0': {}
'@jridgewell/trace-mapping@0.3.25':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
- '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/sourcemap-codec': 1.5.0
'@nodelib/fs.scandir@2.1.5':
dependencies:
@@ -846,122 +985,163 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.17.1
- '@rollup/plugin-replace@5.0.7(rollup@4.18.0)':
+ '@polka/url@1.0.0-next.25': {}
+
+ '@rollup/plugin-replace@5.0.7(rollup@4.20.0)':
dependencies:
- '@rollup/pluginutils': 5.1.0(rollup@4.18.0)
- magic-string: 0.30.10
+ '@rollup/pluginutils': 5.1.0(rollup@4.20.0)
+ magic-string: 0.30.11
optionalDependencies:
- rollup: 4.18.0
+ rollup: 4.20.0
- '@rollup/pluginutils@5.1.0(rollup@4.18.0)':
+ '@rollup/pluginutils@5.1.0(rollup@4.20.0)':
dependencies:
'@types/estree': 1.0.5
estree-walker: 2.0.2
picomatch: 2.3.1
optionalDependencies:
- rollup: 4.18.0
+ rollup: 4.20.0
- '@rollup/rollup-android-arm-eabi@4.18.0':
+ '@rollup/rollup-android-arm-eabi@4.20.0':
optional: true
- '@rollup/rollup-android-arm64@4.18.0':
+ '@rollup/rollup-android-arm64@4.20.0':
optional: true
- '@rollup/rollup-darwin-arm64@4.18.0':
+ '@rollup/rollup-darwin-arm64@4.20.0':
optional: true
- '@rollup/rollup-darwin-x64@4.18.0':
+ '@rollup/rollup-darwin-x64@4.20.0':
optional: true
- '@rollup/rollup-linux-arm-gnueabihf@4.18.0':
+ '@rollup/rollup-linux-arm-gnueabihf@4.20.0':
optional: true
- '@rollup/rollup-linux-arm-musleabihf@4.18.0':
+ '@rollup/rollup-linux-arm-musleabihf@4.20.0':
optional: true
- '@rollup/rollup-linux-arm64-gnu@4.18.0':
+ '@rollup/rollup-linux-arm64-gnu@4.20.0':
optional: true
- '@rollup/rollup-linux-arm64-musl@4.18.0':
+ '@rollup/rollup-linux-arm64-musl@4.20.0':
optional: true
- '@rollup/rollup-linux-powerpc64le-gnu@4.18.0':
+ '@rollup/rollup-linux-powerpc64le-gnu@4.20.0':
optional: true
- '@rollup/rollup-linux-riscv64-gnu@4.18.0':
+ '@rollup/rollup-linux-riscv64-gnu@4.20.0':
optional: true
- '@rollup/rollup-linux-s390x-gnu@4.18.0':
+ '@rollup/rollup-linux-s390x-gnu@4.20.0':
optional: true
- '@rollup/rollup-linux-x64-gnu@4.18.0':
+ '@rollup/rollup-linux-x64-gnu@4.20.0':
optional: true
- '@rollup/rollup-linux-x64-musl@4.18.0':
+ '@rollup/rollup-linux-x64-musl@4.20.0':
optional: true
- '@rollup/rollup-win32-arm64-msvc@4.18.0':
+ '@rollup/rollup-win32-arm64-msvc@4.20.0':
optional: true
- '@rollup/rollup-win32-ia32-msvc@4.18.0':
+ '@rollup/rollup-win32-ia32-msvc@4.20.0':
optional: true
- '@rollup/rollup-win32-x64-msvc@4.18.0':
+ '@rollup/rollup-win32-x64-msvc@4.20.0':
optional: true
- '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1)':
+ '@shikijs/core@1.12.1':
+ dependencies:
+ '@types/hast': 3.0.4
+
+ '@sveltejs/adapter-auto@3.2.2(@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.4.0))(svelte@4.2.18)(vite@5.4.0))':
dependencies:
- '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.18)(vite@5.3.1)
- debug: 4.3.5
+ '@sveltejs/kit': 2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.4.0))(svelte@4.2.18)(vite@5.4.0)
+ import-meta-resolve: 4.1.0
+
+ '@sveltejs/kit@2.5.20(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.4.0))(svelte@4.2.18)(vite@5.4.0)':
+ dependencies:
+ '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.18)(vite@5.4.0)
+ '@types/cookie': 0.6.0
+ cookie: 0.6.0
+ devalue: 5.0.0
+ esm-env: 1.0.0
+ import-meta-resolve: 4.1.0
+ kleur: 4.1.5
+ magic-string: 0.30.11
+ mrmime: 2.0.0
+ sade: 1.8.1
+ set-cookie-parser: 2.7.0
+ sirv: 2.0.4
svelte: 4.2.18
- vite: 5.3.1
+ tiny-glob: 0.2.9
+ vite: 5.4.0
+
+ '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.4.0))(svelte@4.2.18)(vite@5.4.0)':
+ dependencies:
+ '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.18)(vite@5.4.0)
+ debug: 4.3.6
+ svelte: 4.2.18
+ vite: 5.4.0
transitivePeerDependencies:
- supports-color
- '@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1)':
+ '@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.4.0)':
dependencies:
- '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1)
- debug: 4.3.5
+ '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.4.0))(svelte@4.2.18)(vite@5.4.0)
+ debug: 4.3.6
deepmerge: 4.3.1
kleur: 4.1.5
- magic-string: 0.30.10
+ magic-string: 0.30.11
svelte: 4.2.18
svelte-hmr: 0.16.0(svelte@4.2.18)
- vite: 5.3.1
- vitefu: 0.2.5(vite@5.3.1)
+ vite: 5.4.0
+ vitefu: 0.2.5(vite@5.4.0)
transitivePeerDependencies:
- supports-color
+ '@types/cookie@0.6.0': {}
+
'@types/estree@1.0.5': {}
- acorn@8.12.0: {}
+ '@types/hast@3.0.4':
+ dependencies:
+ '@types/unist': 3.0.2
- aria-query@5.3.0:
+ '@types/unist@3.0.2': {}
+
+ '@zerodevx/svelte-toast@0.9.5(svelte@4.2.18)':
dependencies:
- dequal: 2.0.3
+ svelte: 4.2.18
+
+ acorn@8.12.1: {}
- axobject-query@4.0.0:
+ aria-query@5.3.0:
dependencies:
dequal: 2.0.3
+ axobject-query@4.1.0: {}
+
braces@3.0.3:
dependencies:
fill-range: 7.1.1
code-red@1.0.4:
dependencies:
- '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/sourcemap-codec': 1.5.0
'@types/estree': 1.0.5
- acorn: 8.12.0
+ acorn: 8.12.1
estree-walker: 3.0.3
periscopic: 3.1.0
+ cookie@0.6.0: {}
+
css-tree@2.3.1:
dependencies:
mdn-data: 2.0.30
source-map-js: 1.2.0
- debug@4.3.5:
+ debug@4.3.6:
dependencies:
ms: 2.1.2
@@ -969,6 +1149,8 @@ snapshots:
dequal@2.0.3: {}
+ devalue@5.0.0: {}
+
esbuild@0.19.12:
optionalDependencies:
'@esbuild/aix-ppc64': 0.19.12
@@ -1021,6 +1203,8 @@ snapshots:
'@esbuild/win32-ia32': 0.21.5
'@esbuild/win32-x64': 0.21.5
+ esm-env@1.0.0: {}
+
estree-walker@2.0.2: {}
estree-walker@3.0.3:
@@ -1050,26 +1234,33 @@ snapshots:
dependencies:
is-glob: 4.0.3
- golte@0.0.4(rollup@4.18.0)(svelte@4.2.18):
+ globalyzer@0.1.0: {}
+
+ globrex@0.1.2: {}
+
+ golte@0.1.0(rollup@4.20.0)(svelte@4.2.18):
dependencies:
- '@rollup/plugin-replace': 5.0.7(rollup@4.18.0)
- '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.18)(vite@5.3.1)
+ '@rollup/plugin-replace': 5.0.7(rollup@4.20.0)
+ '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.18)(vite@5.4.0)
deepmerge: 4.3.1
esbuild: 0.19.12
fast-glob: 3.3.2
svelte: 4.2.18
- vite: 5.3.1
+ vite: 5.4.0
transitivePeerDependencies:
- '@types/node'
- less
- lightningcss
- rollup
- sass
+ - sass-embedded
- stylus
- sugarss
- supports-color
- terser
+ import-meta-resolve@4.1.0: {}
+
is-extglob@2.1.1: {}
is-glob@4.0.3:
@@ -1086,9 +1277,9 @@ snapshots:
locate-character@3.0.0: {}
- magic-string@0.30.10:
+ magic-string@0.30.11:
dependencies:
- '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/sourcemap-codec': 1.5.0
mdn-data@2.0.30: {}
@@ -1099,6 +1290,10 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
+ mri@1.2.0: {}
+
+ mrmime@2.0.0: {}
+
ms@2.1.2: {}
nanoid@3.3.7: {}
@@ -1113,51 +1308,72 @@ snapshots:
picomatch@2.3.1: {}
- postcss@8.4.38:
+ postcss@8.4.41:
dependencies:
nanoid: 3.3.7
picocolors: 1.0.1
source-map-js: 1.2.0
- prettier-plugin-go-template@0.0.15(prettier@3.2.5):
+ prettier-plugin-svelte@3.2.6(prettier@3.3.3)(svelte@4.2.18):
dependencies:
- prettier: 3.2.5
- ulid: 2.3.0
+ prettier: 3.3.3
+ svelte: 4.2.18
- prettier@3.2.5: {}
+ prettier@3.3.3: {}
queue-microtask@1.2.3: {}
reusify@1.0.4: {}
- rollup@4.18.0:
+ rollup@4.20.0:
dependencies:
'@types/estree': 1.0.5
optionalDependencies:
- '@rollup/rollup-android-arm-eabi': 4.18.0
- '@rollup/rollup-android-arm64': 4.18.0
- '@rollup/rollup-darwin-arm64': 4.18.0
- '@rollup/rollup-darwin-x64': 4.18.0
- '@rollup/rollup-linux-arm-gnueabihf': 4.18.0
- '@rollup/rollup-linux-arm-musleabihf': 4.18.0
- '@rollup/rollup-linux-arm64-gnu': 4.18.0
- '@rollup/rollup-linux-arm64-musl': 4.18.0
- '@rollup/rollup-linux-powerpc64le-gnu': 4.18.0
- '@rollup/rollup-linux-riscv64-gnu': 4.18.0
- '@rollup/rollup-linux-s390x-gnu': 4.18.0
- '@rollup/rollup-linux-x64-gnu': 4.18.0
- '@rollup/rollup-linux-x64-musl': 4.18.0
- '@rollup/rollup-win32-arm64-msvc': 4.18.0
- '@rollup/rollup-win32-ia32-msvc': 4.18.0
- '@rollup/rollup-win32-x64-msvc': 4.18.0
+ '@rollup/rollup-android-arm-eabi': 4.20.0
+ '@rollup/rollup-android-arm64': 4.20.0
+ '@rollup/rollup-darwin-arm64': 4.20.0
+ '@rollup/rollup-darwin-x64': 4.20.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.20.0
+ '@rollup/rollup-linux-arm-musleabihf': 4.20.0
+ '@rollup/rollup-linux-arm64-gnu': 4.20.0
+ '@rollup/rollup-linux-arm64-musl': 4.20.0
+ '@rollup/rollup-linux-powerpc64le-gnu': 4.20.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.20.0
+ '@rollup/rollup-linux-s390x-gnu': 4.20.0
+ '@rollup/rollup-linux-x64-gnu': 4.20.0
+ '@rollup/rollup-linux-x64-musl': 4.20.0
+ '@rollup/rollup-win32-arm64-msvc': 4.20.0
+ '@rollup/rollup-win32-ia32-msvc': 4.20.0
+ '@rollup/rollup-win32-x64-msvc': 4.20.0
fsevents: 2.3.3
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
+ sade@1.8.1:
+ dependencies:
+ mri: 1.2.0
+
+ set-cookie-parser@2.7.0: {}
+
+ shiki@1.12.1:
+ dependencies:
+ '@shikijs/core': 1.12.1
+ '@types/hast': 3.0.4
+
+ sirv@2.0.4:
+ dependencies:
+ '@polka/url': 1.0.0-next.25
+ mrmime: 2.0.0
+ totalist: 3.0.1
+
source-map-js@1.2.0: {}
+ svelte-copy@1.4.2(svelte@4.2.18):
+ dependencies:
+ svelte: 4.2.18
+
svelte-hmr@0.16.0(svelte@4.2.18):
dependencies:
svelte: 4.2.18
@@ -1165,34 +1381,39 @@ snapshots:
svelte@4.2.18:
dependencies:
'@ampproject/remapping': 2.3.0
- '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/sourcemap-codec': 1.5.0
'@jridgewell/trace-mapping': 0.3.25
'@types/estree': 1.0.5
- acorn: 8.12.0
+ acorn: 8.12.1
aria-query: 5.3.0
- axobject-query: 4.0.0
+ axobject-query: 4.1.0
code-red: 1.0.4
css-tree: 2.3.1
estree-walker: 3.0.3
is-reference: 3.0.2
locate-character: 3.0.0
- magic-string: 0.30.10
+ magic-string: 0.30.11
periscopic: 3.1.0
+ tiny-glob@0.2.9:
+ dependencies:
+ globalyzer: 0.1.0
+ globrex: 0.1.2
+
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
- ulid@2.3.0: {}
+ totalist@3.0.1: {}
- vite@5.3.1:
+ vite@5.4.0:
dependencies:
esbuild: 0.21.5
- postcss: 8.4.38
- rollup: 4.18.0
+ postcss: 8.4.41
+ rollup: 4.20.0
optionalDependencies:
fsevents: 2.3.3
- vitefu@0.2.5(vite@5.3.1):
+ vitefu@0.2.5(vite@5.4.0):
optionalDependencies:
- vite: 5.3.1
+ vite: 5.4.0
diff --git a/shared/config/config.go b/shared/config/config.go
deleted file mode 100644
index 5bb6355..0000000
--- a/shared/config/config.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package config
-
-import (
- "net/http"
- "os"
- "strconv"
- "strings"
-
- "github.com/gin-gonic/gin"
- "github.com/romana/rlog"
-)
-
-var Config ConfigEnv
-
-const (
- PawsteVersion = ""
- envPrefix = "PAWSTE_"
-)
-
-func (ConfigEnv) InitConfig() {
- port := getEnv("PORT", ":9454")
- Config = ConfigEnv{
- Salt: getEnv("SALT", "banana"),
- Port: port,
- DataDir: getEnv("DATA_DIR", "pawste_data/"),
- AdminPassword: getEnv("ADMIN_PASSWORD", "admin"),
- PublicList: getEnv("PUBLIC_LIST", "true") == "true",
- PublicURL: getEnv("PUBLIC_URL", "http://localhost"+port),
- NoFileUpload: getEnv("FILE_UPLOAD", "true") == "true",
- MaxFileSize: getEnvInt("MAX_FILE_SIZE", "1024 * 1024 * 10"),
- MaxEncryptionSize: getEnvInt("MAX_ENCRYPTION_SIZE", "1024 * 1024 * 10"),
- MaxContentLength: getEnvInt("MAX_CONTENT_LENGTH", "1024 * 1024"),
- UploadingPassword: getEnv("UPLOADING_PASSWORD", ""),
- DisableEternalPaste: getEnv("DISABLE_ETERNAL_PASTE", "false") == "true",
- DisableReadCount: getEnv("DISABLE_READ_COUNT", "false") == "true",
- DisableBurnAfter: getEnv("DISABLE_BURN_AFTER", "false") == "true",
- DefaultExpiry: getEnv("DEFAULT_EXPIRY", "1w"),
- ShortPasteNames: getEnv("SHORT_PASTE_NAMES", "false") == "true",
- ShortenRedirectPastes: getEnv("SHORTEN_REDIRECT_PASTES", "false") == "true",
- IUnderstandTheRisks: getEnv("I_UNDERSTAND_THE_RISKS", "false") == "true",
- }
-}
-
-func getEnv(key, fallback string) string {
- if value, exists := os.LookupEnv(envPrefix + key); exists {
- rlog.Info("Using environment variable", envPrefix+key, "with value", value)
- return value
- }
- return fallback
-}
-
-func getEnvInt(key string, fallback string) int {
- if value, exists := os.LookupEnv(envPrefix + key); exists {
- return calculateIntFromString(value)
- }
- return calculateIntFromString(fallback)
-}
-
-func calculateIntFromString(s string) int {
- parts := strings.Split(s, "*")
- result := 1
- for _, part := range parts {
- num, err := strconv.Atoi(strings.TrimSpace(part))
- if err != nil {
- panic(err)
- }
- result *= num
- }
- return result
-}
-
-type PasswordSubmission struct {
- Password string `json:"password"`
-}
-
-func (ConfigEnv) ReloadConfig(c *gin.Context) {
- var password PasswordSubmission
- if err := c.Bind(&password); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "give a password dumbass"})
- return
- }
- if password.Password != Config.AdminPassword {
- c.JSON(http.StatusUnauthorized, gin.H{"error": "wrong password"})
- return
- }
- Config.InitConfig()
- c.JSON(http.StatusOK, gin.H{"message": "reloaded config"})
-}
diff --git a/shared/config/types.go b/shared/config/types.go
deleted file mode 100644
index dc42318..0000000
--- a/shared/config/types.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package config
-
-type ConfigEnv struct {
- Salt string
- Port string
- DataDir string
- AdminPassword string
-
- PublicList bool
- PublicURL string
-
- DefaultExpiryTime string
- NoFileUpload bool
- MaxFileSize int
- MaxEncryptionSize int
- MaxContentLength int
- UploadingPassword string
- DisableEternalPaste bool
- DisableReadCount bool
- DisableBurnAfter bool
- DefaultExpiry string
- ShortPasteNames bool
- ShortenRedirectPastes bool
- IUnderstandTheRisks bool
-}
diff --git a/shared/types.go b/shared/types.go
deleted file mode 100644
index ea86b8d..0000000
--- a/shared/types.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package shared
-
-import (
- "mime/multipart"
-)
-
-type Submit struct {
- Text string `form:"text,omitempty"`
- Expiration string `form:"expiration,omitempty"`
- BurnAfter int `form:"burn,omitempty"`
- Password string `form:"password,omitempty"`
- Syntax string `form:"syntax,omitempty"`
- Privacy string `form:"privacy,omitempty"`
- Files []*multipart.FileHeader `form:"file,omitempty"`
-}
diff --git a/shared/utils.go b/shared/utils.go
deleted file mode 100644
index 51a60fe..0000000
--- a/shared/utils.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package shared
-
-import (
- "io"
- "mime/multipart"
- "regexp"
- "time"
-
- "github.com/Masterjoona/pawste/paste"
-)
-
-func SubmitToPaste(submit Submit, pasteName string, isRedirect int) paste.Paste {
- var files []paste.File
- for _, file := range submit.Files {
- if file == nil {
- continue
- }
- fileName, fileSize, fileBlob := convertMultipartFile(file)
- files = append(files, paste.File{
- Name: fileName,
- Size: fileSize,
- Blob: fileBlob,
- })
- }
-
- return paste.Paste{
- PasteName: pasteName,
- Expire: HumanTimeToSQLTime(submit.Expiration),
- Privacy: submit.Privacy,
- IsEncrypted: 0,
- ReadCount: 1,
- ReadLast: GetCurrentDate(),
- BurnAfter: submit.BurnAfter,
- Content: submit.Text,
- Syntax: submit.Syntax,
- Password: submit.Password,
- Files: files,
- UrlRedirect: isRedirect,
- CreatedAt: GetCurrentDate(),
- UpdatedAt: GetCurrentDate(),
- }
-}
-
-func convertMultipartFile(file *multipart.FileHeader) (string, int, []byte) {
- src, err := file.Open()
- if err != nil {
- panic(err)
- }
- defer src.Close()
-
- fileBlob, err := io.ReadAll(src)
- if err != nil {
- panic(err)
- }
- return file.Filename, len(fileBlob), fileBlob
-}
-
-func HumanTimeToSQLTime(humanTime string) string {
- var duration time.Duration
- duration = 7 * 24 * time.Hour
- switch humanTime {
- case "10min":
- duration = 10 * time.Minute
- case "1min":
- duration = 1 * time.Minute
- case "1h":
- duration = 1 * time.Hour
- case "6h":
- duration = 6 * time.Hour
- case "24h":
- duration = 24 * time.Hour
- case "72h":
- duration = 72 * time.Hour
- case "never":
- duration = 100 * 365 * 24 * time.Hour // cope if you're still using this in 100 years
- }
-
- return time.Now().Add(duration).Format("2006-01-02 15:04:05")
-}
-
-func IsContentJustUrl(content string) int {
- if regexp.MustCompile(`^(?:http|https|magnet):\/\/[^\s/$.?#].[^\s]*$`).MatchString(content) {
- return 1
- }
- return 0
-}
-
-func GetCurrentDate() string {
- return time.Now().Format("2006-01-02 15:04:05")
-}
-
-func NotAllowedPrivacy(x string) bool {
- for _, item := range []string{"public", "unlisted", "readonly", "private", "secret"} {
- if item == x {
- return false
- }
- }
- return true
-}
-
-func TernaryString(condition bool, trueVal, falseVal string) string {
- if condition {
- return trueVal
- }
- return falseVal
-}
diff --git a/svelte.config.js b/svelte.config.js
new file mode 100644
index 0000000..3e3ee3a
--- /dev/null
+++ b/svelte.config.js
@@ -0,0 +1,5 @@
+import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
+
+export default {
+ preprocess: [vitePreprocess()],
+};
diff --git a/web/app.css b/web/app.css
index 37098e1..2b2350b 100644
--- a/web/app.css
+++ b/web/app.css
@@ -1,6 +1,44 @@
+:root {
+ --bg-color: #151515;
+ --default-font: "Short Stack";
+ --code-font: "Fira Code Variable";
+ --font-size: 1.2em;
+
+ --main-color: #788db5;
+ --main-color-dark: #546483;
+}
+
body {
- color: #333333;
- background: #eef1f8;
- padding: 0;
- margin: 0;
-}
\ No newline at end of file
+ margin: 0px;
+ padding: 0px;
+ width: 100vw;
+ height: 100vh;
+ font-family: var(--default-font), monospace;
+ color: var(--main-color);
+ background-color: var(--bg-color);
+ font-display: swap;
+}
+
+::-webkit-scrollbar {
+ width: 2px;
+}
+
+::-webkit-scrollbar-track {
+ box-shadow: inset 0 0 5px #151515;
+ border-radius: 2px;
+}
+::-webkit-scrollbar-thumb {
+ background: var(--main-color);
+ border-radius: 2px;
+}
+::-webkit-scrollbar-thumb:hover {
+ background: var(--main-color-dark);
+}
+
+a {
+ color: var(--main-color);
+ text-decoration: underline;
+}
+a:hover {
+ color: aliceblue;
+}
diff --git a/web/app.html b/web/app.html
index a5f39c5..0eca5b6 100644
--- a/web/app.html
+++ b/web/app.html
@@ -1,12 +1,12 @@
-
+
-
-
-
-
- {{ .Head }}
-
-
- {{ .Body }}
-
+
+
+
+
+ {{ .Head }}
+
+
+ {{ .Body }}
+
diff --git a/web/assets/asuka.png b/web/assets/asuka.png
new file mode 100644
index 0000000..c76366d
Binary files /dev/null and b/web/assets/asuka.png differ
diff --git a/web/assets/bocchi.png b/web/assets/bocchi.png
new file mode 100644
index 0000000..92bafdf
Binary files /dev/null and b/web/assets/bocchi.png differ
diff --git a/web/assets/faye.png b/web/assets/faye.png
new file mode 100644
index 0000000..c40b82b
Binary files /dev/null and b/web/assets/faye.png differ
diff --git a/web/assets/mai.png b/web/assets/mai.png
new file mode 100644
index 0000000..11551bb
Binary files /dev/null and b/web/assets/mai.png differ
diff --git a/web/assets/marin.png b/web/assets/marin.png
new file mode 100644
index 0000000..a56fc0c
Binary files /dev/null and b/web/assets/marin.png differ
diff --git a/web/assets/nanami.png b/web/assets/nanami.png
new file mode 100644
index 0000000..491e239
Binary files /dev/null and b/web/assets/nanami.png differ
diff --git a/oldweb/static/suzume.png b/web/assets/suzume.png
similarity index 100%
rename from oldweb/static/suzume.png
rename to web/assets/suzume.png
diff --git a/web/layout/main.svelte b/web/layout/main.svelte
index 78d73e6..7946bfe 100644
--- a/web/layout/main.svelte
+++ b/web/layout/main.svelte
@@ -1,84 +1,45 @@
-
+
- ul {
- margin: 0;
- height: 60px;
+
+
- list-style: none;
- background: #ccddf8;
+
+
- display: flex;
- gap: 10px;
- justify-content: center;
- align-items: center;
- }
+
+
- li {
- height: 40px;
- padding: 0 10px;
- }
-
- li[aria-current="page"] {
- border-bottom: solid 2px orangered;
- }
+
+ {#if AnimeGirls}
+
+ {/if}
+
+
- a {
+
diff --git a/web/layout/secondary.svelte b/web/layout/secondary.svelte
deleted file mode 100644
index ff662c2..0000000
--- a/web/layout/secondary.svelte
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
diff --git a/web/lib/types.ts b/web/lib/types.ts
new file mode 100644
index 0000000..7e7c72b
--- /dev/null
+++ b/web/lib/types.ts
@@ -0,0 +1,46 @@
+export interface Paste {
+ PasteName: string;
+ Expire: number;
+ Privacy: string;
+ NeedsAuth: number;
+ ReadCount: number;
+ ReadLast: number;
+ BurnAfter: number;
+ Content: string;
+ UrlRedirect: number;
+ Syntax: string;
+ Password: string;
+ Files: FileDb[];
+ CreatedAt: number;
+ UpdatedAt: number;
+}
+
+export interface FileDb {
+ ID: number;
+ Name: string;
+ Size: number;
+ ContentType: string;
+ Blob: any;
+}
+
+export type FileType = FileDb | File;
+
+export interface Config {
+ Salt: string;
+ Port: string;
+ DataDir: string;
+ AdminPassword: string;
+ PublicList: boolean;
+ FileUpload: boolean;
+ MaxFileSize: number | string;
+ MaxEncryptionSize: number | string;
+ MaxContentLength: number;
+ UploadingPassword: string;
+ EternalPaste: boolean;
+ ReadCount: boolean;
+ BurnAfter: boolean;
+ DefaultExpiry: string;
+ ShortPasteNames: boolean;
+ ShortenRedirectPastes: boolean;
+ CountFileUsage: boolean;
+}
diff --git a/web/lib/ui/Animegirl.svelte b/web/lib/ui/Animegirl.svelte
new file mode 100644
index 0000000..5d4404e
--- /dev/null
+++ b/web/lib/ui/Animegirl.svelte
@@ -0,0 +1,41 @@
+
+
+
+
+
diff --git a/web/lib/ui/ConfigInput.svelte b/web/lib/ui/ConfigInput.svelte
new file mode 100644
index 0000000..b3b7dac
--- /dev/null
+++ b/web/lib/ui/ConfigInput.svelte
@@ -0,0 +1,11 @@
+
+
+
diff --git a/web/lib/ui/FileList.svelte b/web/lib/ui/FileList.svelte
new file mode 100644
index 0000000..c5c3a78
--- /dev/null
+++ b/web/lib/ui/FileList.svelte
@@ -0,0 +1,43 @@
+
+
+
+ {#each files as file, index}
+ {@const dataFromDb = FileDatafromDb(file)}
+
+ {#if !dataFromDb && file.type.startsWith("image/")}
+
+ {/if}
+
+ {truncateFilename(dataFromDb ? file.Name : file.name)}
+ ({prettifyFileSize(dataFromDb ? file.Size : file.size)})
+
+ {#if removeFile}
+
+ removeFile(dataFromDb ? file.Name : file.name)}
+ >Remove
+ {/if}
+ {#if dataFromDb}
+
View
+ {/if}
+
+ {/each}
+
diff --git a/web/lib/ui/FileSizeInput.svelte b/web/lib/ui/FileSizeInput.svelte
new file mode 100644
index 0000000..edce615
--- /dev/null
+++ b/web/lib/ui/FileSizeInput.svelte
@@ -0,0 +1,20 @@
+
+
+
+ (editableValue = value)} />
+
diff --git a/web/lib/ui/Footer.svelte b/web/lib/ui/Footer.svelte
new file mode 100644
index 0000000..1bb0d97
--- /dev/null
+++ b/web/lib/ui/Footer.svelte
@@ -0,0 +1,13 @@
+
+
+
diff --git a/web/lib/ui/Header.svelte b/web/lib/ui/Header.svelte
new file mode 100644
index 0000000..ef09364
--- /dev/null
+++ b/web/lib/ui/Header.svelte
@@ -0,0 +1,25 @@
+
+
+
+ pawste
+ **currently in development expect bugs and data being wiped**
+ new
+ {#if PublicList}
+ list
+ {/if}
+ guide
+ about
+ Sponsor!
+
+
+
diff --git a/web/lib/ui/Password.svelte b/web/lib/ui/Password.svelte
new file mode 100644
index 0000000..af1418f
--- /dev/null
+++ b/web/lib/ui/Password.svelte
@@ -0,0 +1,29 @@
+
+
+
+
+ {question}
+
+ Submit
+
diff --git a/web/lib/ui/PasteList.svelte b/web/lib/ui/PasteList.svelte
new file mode 100644
index 0000000..4186757
--- /dev/null
+++ b/web/lib/ui/PasteList.svelte
@@ -0,0 +1,67 @@
+
+
+
+
+ {#each tableHeaders as header}
+ {header}
+ {/each}
+
+ {#if pastes === undefined || pastes.length === 0}
+
+ No pastes
+
+ {:else}
+ {#each pastes as { PasteName, ReadCount, ReadLast, Privacy, Expire, UrlRedirect }}
+
+ {PasteName}
+ {timeDifference(Expire)}
+
+ {#if Privacy}
+ {timeDifference(ReadLast)}
+
+ {Privacy}
+ {ReadCount}
+ {/if}
+
+ {#if UrlRedirect === 1}
+ Go to URL
+ {/if}
+ View
+
+ {#if password}
+
+
+ deletePaste(PasteName, password, () =>
+ successToast(`Deleted ${PasteName}!`),
+ )}
+ >Delete
+
+
+ {/if}
+
+ {/each}
+ {/if}
+
+
+
diff --git a/web/lib/ui/Properties.svelte b/web/lib/ui/Properties.svelte
new file mode 100644
index 0000000..b57d751
--- /dev/null
+++ b/web/lib/ui/Properties.svelte
@@ -0,0 +1,29 @@
+
+
+
+
{paste.PasteName}
+
+
+
{paste.ReadCount}
+
+ {timeDifference(paste.ReadLast, "Read last")}
+
+ {#if paste.BurnAfter !== 0}
+
+ Burn after {paste.BurnAfter} views
+
+ {/if}
+
+ {paste.Content.length}
+
+
+
+ {timeDifference(paste.Expire, "Expires in")}
+
+
+
+
diff --git a/web/lib/ui/Switch.svelte b/web/lib/ui/Switch.svelte
new file mode 100644
index 0000000..bea3171
--- /dev/null
+++ b/web/lib/ui/Switch.svelte
@@ -0,0 +1,56 @@
+
+
+
+ onChange(checked)} />
+
+
+
+
diff --git a/web/lib/utils.ts b/web/lib/utils.ts
new file mode 100644
index 0000000..7afda97
--- /dev/null
+++ b/web/lib/utils.ts
@@ -0,0 +1,159 @@
+import { toast } from "@zerodevx/svelte-toast";
+import { FileDb, FileType, Paste } from "./types";
+
+export function truncateFilename(filename: string, maxLength = 30) {
+ const extIndex = filename.lastIndexOf(".");
+ const name = filename.substring(0, extIndex);
+ const ext = filename.substring(extIndex);
+
+ if (name.length + ext.length <= maxLength) {
+ return filename;
+ }
+
+ const charsToShow = maxLength - ext.length - 3;
+ const startChars = Math.ceil(charsToShow / 2);
+ const endChars = Math.floor(charsToShow / 2);
+
+ return (
+ name.substring(0, startChars) +
+ "..." +
+ name.substring(name.length - endChars) +
+ ext
+ );
+}
+export function isFileDb(file: FileType): file is FileDb {
+ return (file as FileDb)?.Name !== undefined;
+}
+
+export function timeDifference(timestamp: number, prepend: string = "") {
+ if (timestamp === -1) return "never";
+ timestamp *= 1000;
+
+ const now = new Date();
+ const target = new Date(timestamp);
+ const diff = target.getTime() - now.getTime();
+
+ const absDiff = Math.abs(diff);
+
+ const seconds = Math.floor(absDiff / 1000);
+ const minutes = Math.floor(seconds / 60);
+ const hours = Math.floor(minutes / 60);
+ const days = Math.floor(hours / 24);
+ const weeks = Math.floor(days / 7);
+
+ let timeUnit: string;
+ let timeValue: number;
+
+ if (weeks > 0) {
+ timeUnit = weeks === 1 ? "week" : "weeks";
+ timeValue = weeks;
+ } else if (days > 0) {
+ timeUnit = days === 1 ? "day" : "days";
+ timeValue = days;
+ } else if (hours > 0) {
+ timeUnit = hours === 1 ? "hour" : "hours";
+ timeValue = hours;
+ } else if (minutes > 0) {
+ timeUnit = minutes === 1 ? "minute" : "minutes";
+ timeValue = minutes;
+ } else if (seconds > 0) {
+ timeUnit = seconds === 1 ? "second" : "seconds";
+ timeValue = seconds;
+ } else {
+ return "just now";
+ }
+
+ const suffix = diff > 0 ? "" : "ago";
+
+ return `${prepend} ${timeValue} ${timeUnit} ${suffix}`;
+}
+
+export function prettifyFileSize(size: number) {
+ if (size < 1024) return size + " B";
+ if (size < 1024 * 1024) return (size / 1024).toFixed(2) + " KB";
+ if (size < 1024 * 1024 * 1024)
+ return (size / (1024 * 1024)).toFixed(2) + " MB";
+ return (size / (1024 * 1024 * 1024)).toFixed(2) + " GB";
+}
+
+export async function deletePaste(
+ pasteName: string,
+ password: string,
+ onSuccess: () => void,
+) {
+ const resp = await fetch(`/p/${pasteName}`, {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ password,
+ },
+ });
+ if (!resp.ok) {
+ failToast("Failed to delete paste!");
+ return;
+ }
+ onSuccess();
+}
+
+export function handleAttachFiles(
+ event: any,
+ pushNewFile: (file: File) => void,
+ pushImageSource: (source: string) => void
+) {
+ const files = event.target.files;
+
+ for (let file of files) {
+ if (file.type.startsWith("image/")) {
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ pushImageSource(e.target?.result as string);
+ };
+ reader.readAsDataURL(file);
+ }
+ pushNewFile(file);
+ }
+}
+
+export async function savePaste(method: "PATCH" | "POST", formData: FormData, endpoint: string, saveButton: HTMLElement) {
+ const xhr = new XMLHttpRequest();
+ xhr.open(method, endpoint);
+ xhr.upload.addEventListener("progress", (event) => {
+ if (event.lengthComputable) {
+ const progress = Math.round((event.loaded / event.total) * 100);
+ saveButton.textContent = `Uploading... ${progress}%`;
+ }
+ });
+
+ xhr.onload = async () => {
+ const resp = JSON.parse(xhr.responseText) as Paste | { error: string } | { message: string };
+ if ("error" in resp) {
+ failToast(resp.error);
+ } else if ("message" in resp) {
+ window.location.href = window.location.href.replace("/e/", "/p/")
+ } else {
+ window.location.href = `/p/${resp.PasteName}`;
+ }
+ };
+
+ xhr.send(formData);
+}
+
+export const successToast = (msg: string) => {
+ toast.push(msg, {
+ theme: {
+ "--toastColor": "mintcream",
+ "--toastBackground": "rgba(72,187,120,0.9)",
+ "--toastBarBackground": "#2F855A",
+ },
+ });
+};
+
+export const failToast = (msg: string) => {
+ toast.push(msg, {
+ theme: {
+ "--toastColor": "mintcream",
+ "--toastBackground": "rgba(255,0,0,0.9)",
+ "--toastBarBackground": "red",
+ },
+ });
+};
diff --git a/web/page/about.svelte b/web/page/about.svelte
index 8e5f669..6250260 100644
--- a/web/page/about.svelte
+++ b/web/page/about.svelte
@@ -1,2 +1,42 @@
-Sample About Page
-Dignissim diam quis enim lobortis scelerisque fermentum dui. Ornare aenean euismod elementum nisi quis eleifend quam. Laoreet sit amet cursus sit amet dictum sit. Velit aliquet sagittis id consectetur. Euismod lacinia at quis risus sed vulputate odio ut. Mauris vitae ultricies leo integer malesuada. Sit amet risus nullam eget felis eget nunc lobortis mattis. Aliquet nec ullamcorper sit amet risus nullam eget. Luctus venenatis lectus magna fringilla urna porttitor rhoncus. In cursus turpis massa tincidunt dui ut ornare lectus. Phasellus vestibulum lorem sed risus ultricies tristique nulla aliquet.
+
+ pawste -- about
+
+
+
+
About us ♡
+
+ This project is open source and you can find the source code on GitHub ! The backend is done by
+ Joona
+ and is written in Go and the basis for the frontend is done in Svelte by a friend.
+
+
+ Send us an email at team@pawst.eu !
+
+
+
+
diff --git a/web/page/admin.svelte b/web/page/admin.svelte
new file mode 100644
index 0000000..1662170
--- /dev/null
+++ b/web/page/admin.svelte
@@ -0,0 +1,210 @@
+
+
+{#if !adminPassword}
+
+{/if}
+
+
+
pawste admin
+
+
+
+
+
Info
+
+ Version dev
+ Status nop
+ Uploads morbillion
+ Update nop
+
+
+
+
+
+
+
Environmental Variables
+
+
+ Argument
+ Value
+ Toggle
+
+ {#if config === undefined}
+ Loading...
+ {:else}
+ {#each Object.entries(config) as [key, value]}
+ {#if typeof value === "boolean"}
+
+ {key}
+ {value}
+
+
+ updateConfigValue(key, value)} />
+
+
+ {:else if typeof value === "string"}
+
+ {key}
+ {value}
+
+
+ updateConfigValue(key, newValue)} />
+
+
+ {:else if key === "MaxEncryptionSize" || key === "MaxFileSize"}
+
+ {key}
+
+ {prettifyFileSize(value)}
+
+
+ updateConfigValue(
+ key,
+ newValue,
+ )} />
+
+ {:else if typeof value === "number"}
+
+ {key}
+ {value}
+
+
+ updateConfigValue(key, newValue)} />
+
+
+ {/if}
+ {/each}
+ {/if}
+
+
+
+
+
diff --git a/web/page/auth_file.svelte b/web/page/auth_file.svelte
new file mode 100644
index 0000000..e2fccdd
--- /dev/null
+++ b/web/page/auth_file.svelte
@@ -0,0 +1,84 @@
+
+
+
+ {#if hideContent}
+
+ {:else if fileMetaData}
+
{fileMetaData.Name}
+
Size: {fileMetaData.Size}
+
Content Type: {fileMetaData.ContentType}
+ {#if isVideo}
+
+
+
+
+ {:else if isImage}
+
+ {:else}
+
File type not supported for preview
+
Download
+ {/if}
+ {/if}
+
diff --git a/web/page/contact.svelte b/web/page/contact.svelte
deleted file mode 100644
index 0c0d358..0000000
--- a/web/page/contact.svelte
+++ /dev/null
@@ -1,2 +0,0 @@
-Sample Contact Page
-Sit amet cursus sit amet dictum sit amet justo. Sed sed risus pretium quam vulputate dignissim suspendisse. Lorem sed risus ultricies tristique nulla aliquet enim tortor at. Eu turpis egestas pretium aenean pharetra magna ac. Curabitur gravida arcu ac tortor dignissim. Vulputate ut pharetra sit amet aliquam id diam maecenas. At imperdiet dui accumsan sit amet nulla facilisi. Ornare quam viverra orci sagittis eu volutpat odio facilisis mauris. Ut sem nulla pharetra diam sit. Nisl pretium fusce id velit. Tempor orci dapibus ultrices in. Felis eget velit aliquet sagittis id consectetur.
diff --git a/web/page/edit.svelte b/web/page/edit.svelte
new file mode 100644
index 0000000..03a3a10
--- /dev/null
+++ b/web/page/edit.svelte
@@ -0,0 +1,153 @@
+
+
+{#if needsAuth && hideContent}
+
+{/if}
+
+
+
+
+
+
+
+ document.getElementById("file-input").click()}
+ >Attach Files
+ Save
+
+
Current Files:
+ {#if paste.Files}
+
+ {/if}
+
New Files:
+
+
+
diff --git a/web/page/error.svelte b/web/page/error.svelte
new file mode 100644
index 0000000..980ece5
--- /dev/null
+++ b/web/page/error.svelte
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/web/page/guide.svelte b/web/page/guide.svelte
new file mode 100644
index 0000000..5672afd
--- /dev/null
+++ b/web/page/guide.svelte
@@ -0,0 +1,54 @@
+
+ pawste -- guide
+
+
+
+
+ Expiry is the time the paste will be available for viewing. After
+ this time, the paste will be deleted.
+
+
+ Burn after is the number of times the paste can be viewed before it
+ is deleted.
+
+
+ Privacy determines who can see your paste and if it has encryption.
+
+
+
+ Public - Anyone can see the paste and edit it at their will.
+
+
+ Unlisted - Only people with the link can see the paste and can
+ edit it.
+
+
+ Readonly - Only people with the link can see the paste but they
+ need the password to edit it.
+
+
+ Private - A password is needed to view and edit the paste. The
+ paste is encrypted on server.
+
+
+
+
+
diff --git a/web/page/home.svelte b/web/page/home.svelte
deleted file mode 100644
index 2bfffb8..0000000
--- a/web/page/home.svelte
+++ /dev/null
@@ -1,2 +0,0 @@
-Sample Home Page
-Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sit amet est placerat in egestas erat imperdiet sed. Facilisis sed odio morbi quis commodo odio aenean sed. Ultrices tincidunt arcu non sodales neque sodales ut etiam sit. Sit amet nisl purus in mollis nunc sed id semper. Sodales neque sodales ut etiam sit. Integer enim neque volutpat ac. Aliquet sagittis id consectetur purus ut faucibus pulvinar elementum integer. Tincidunt tortor aliquam nulla facilisi. Urna et pharetra pharetra massa massa ultricies mi quis hendrerit. Pulvinar elementum integer enim neque volutpat ac tincidunt vitae. Placerat duis ultricies lacus sed turpis tincidunt id aliquet. Et tortor at risus viverra adipiscing at in tellus. Cursus risus at ultrices mi tempus imperdiet. Nullam vehicula ipsum a arcu cursus vitae congue mauris rhoncus. Nisl tincidunt eget nullam non nisi est sit amet facilisis. Lorem dolor sed viverra ipsum nunc aliquet bibendum. Id neque aliquam vestibulum morbi blandit cursus risus at. Aliquet porttitor lacus luctus accumsan tortor posuere ac.
diff --git a/web/page/list.svelte b/web/page/list.svelte
new file mode 100644
index 0000000..9f7d88f
--- /dev/null
+++ b/web/page/list.svelte
@@ -0,0 +1,32 @@
+
+
+
+ pawste -- paste list
+
+
+
+
+
+
diff --git a/web/page/login.svelte b/web/page/login.svelte
deleted file mode 100644
index 5544f9c..0000000
--- a/web/page/login.svelte
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
- Login
-
-
-
diff --git a/web/page/new.svelte b/web/page/new.svelte
new file mode 100644
index 0000000..d261792
--- /dev/null
+++ b/web/page/new.svelte
@@ -0,0 +1,290 @@
+
+
+
+ pawste -- new paste
+
+
+
+
+
+ Expiration:
+
+ Never
+ 1 Hour
+ 6 Hours
+ 1 Day
+ 3 Days
+ 1 week
+
+
+
+ Burn After:
+
+ Never
+ 1 View
+ 10 Views
+ 100 Views
+ 1000 Views
+
+
+
+ Syntax:
+
+ None
+ {#each bundledLanguagesInfo as lang}
+ {lang.name}
+ {/each}
+
+
+
+ Privacy:
+
+ Public
+ Unlisted
+ Read-only
+ Private
+
+
+
+ Password:
+
+
+
+
+
+
+ {#if fileUpload}
+
+
+ document.getElementById("file-input").click()}
+ >Attach Files
+ {/if}
+ {#if fileUploadPassword && attachedFiles.length > 0}
+
+ {/if}
+
+ Save
+
+
+ {#if fileUpload}
+
+ {/if}
+
+
+
+
diff --git a/web/page/oneview.svelte b/web/page/oneview.svelte
new file mode 100644
index 0000000..c67627b
--- /dev/null
+++ b/web/page/oneview.svelte
@@ -0,0 +1,27 @@
+
+
+
+
This paste will be deleted after this, do you want to see this?
+
+ No
+ Yes
+
+
+
+
diff --git a/web/page/paste.svelte b/web/page/paste.svelte
new file mode 100644
index 0000000..77c43d0
--- /dev/null
+++ b/web/page/paste.svelte
@@ -0,0 +1,149 @@
+
+
+
+ pawste -- {paste.PasteName}
+
+
+
+ {#if paste.Files !== null && paste.Files.length > 0}
+
+
+
+ {/if}
+
+
+{#if needsAuth && hideContent}
+
+{/if}
+
+
+
+
+ {#if paste.Content}
+ {#if paste.Syntax === "none"}
+
+ {:else}
+ {#await highlightCode(paste.Content, paste.Syntax)}
+
Loading...
+ {:then highlighted}
+ {@html highlighted}
+ {:catch _}
+
+ {/await}
+ {/if}
+ {/if}
+
+
+ (window.location.href = "/e/" + paste.PasteName)}
+ >Edit
+ Delete
+ {#if paste.Content}
+ {
+ successToast("Text copied!");
+ }}>Copy Text
+ {/if}
+ {
+ successToast("URL copied!");
+ }}>Copy URL
+
+
+
+
+
+
diff --git a/web/page/profile.svelte b/web/page/profile.svelte
deleted file mode 100644
index 222af4d..0000000
--- a/web/page/profile.svelte
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
{realname}
- {occupation}
-
-
-
-
-
- Username
- {username}
-
-
- Age
- {age}
-
-
- Email
- {email}
-
-
- Site
- {site}
-
-
- Looking for job?
- {searching ? "Yes" : "No"}
-
-
-
-
-
diff --git a/web/static/favicon.ico b/web/static/favicon.ico
new file mode 100644
index 0000000..f4936e7
Binary files /dev/null and b/web/static/favicon.ico differ
diff --git a/web/static/favicon.png b/web/static/favicon.png
new file mode 100644
index 0000000..179d22d
Binary files /dev/null and b/web/static/favicon.png differ
diff --git a/web/static/robots.txt b/web/static/robots.txt
new file mode 100644
index 0000000..1f53798
--- /dev/null
+++ b/web/static/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /
diff --git a/web/styles/buttons.css b/web/styles/buttons.css
new file mode 100644
index 0000000..9e4716d
--- /dev/null
+++ b/web/styles/buttons.css
@@ -0,0 +1,27 @@
+.buttons {
+ display: flex;
+ flex-wrap: wrap;
+ width: 100%;
+ justify-content: space-evenly;
+}
+
+button {
+ background-color: var(--main-color);
+ color: white;
+ border: none;
+ padding: 10px 10px;
+ border-radius: 5px;
+ cursor: pointer;
+ font-family: var(--main-font);
+ font-size: var(--font-size);
+}
+
+button:hover {
+ background-color: var(--main-color-dark);
+}
+
+@media (max-width: 600px) {
+ .buttons {
+ justify-content: center;
+ }
+}
diff --git a/web/styles/file.css b/web/styles/file.css
new file mode 100644
index 0000000..f749f27
--- /dev/null
+++ b/web/styles/file.css
@@ -0,0 +1,42 @@
+.file-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ margin-top: 1%;
+}
+
+.file-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background-color: #3a3a3a;
+ border-radius: 5px;
+ padding: 10px;
+ color: white;
+}
+
+.file-item img.thumbnail {
+ max-width: 50px;
+ max-height: 50px;
+ margin-right: 10px;
+}
+
+.file-item span {
+ font-family: var(--code-font);
+ flex-grow: 1;
+ margin-right: 10px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.file-item button {
+ background-color: var(--main-color);
+ color: white;
+ border: none;
+ padding: 5px 10px;
+ border-radius: 5px;
+ cursor: pointer;
+ font-family: var(--main-font);
+ font-size: var(--font-size);
+}
diff --git a/web/styles/password.css b/web/styles/password.css
new file mode 100644
index 0000000..ef3b079
--- /dev/null
+++ b/web/styles/password.css
@@ -0,0 +1,37 @@
+.password-prompt {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: #222222;
+ padding: 20px;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
+ z-index: 1000;
+ border-radius: 8px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.password-prompt label {
+ margin-bottom: 10px;
+}
+
+.password-prompt input {
+ padding: 8px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.5);
+ z-index: 999;
+ backdrop-filter: blur(10px);
+}
diff --git a/web/styles/paste.css b/web/styles/paste.css
new file mode 100644
index 0000000..d63c72b
--- /dev/null
+++ b/web/styles/paste.css
@@ -0,0 +1,50 @@
+#container {
+ height: 100%;
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-family: var(--main-font);
+}
+
+.card {
+ margin-top: 2%;
+ height: 90%;
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ background-color: #2a2a2a;
+ border-radius: 10px;
+ padding: 16px;
+}
+
+.properties {
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-evenly;
+ margin-top: 0px;
+}
+
+.spacer {
+ flex-grow: 1;
+}
+
+.icon-container {
+ display: flex;
+ flex-direction: row;
+ gap: 10px;
+}
+
+textarea {
+ width: 99%;
+ height: 40vh;
+ font-family: var(--code-font);
+ background-color: #1b1b22;
+ color: white;
+ border: none;
+ border-radius: 10px;
+ padding: 10px;
+ resize: vertical;
+ margin-bottom: 10px;
+}