mirror of
https://github.com/lus/pasty.git
synced 2023-08-10 21:13:09 +03:00
Rework project structure
This commit is contained in:
102
internal/config/config.go
Normal file
102
internal/config/config.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/lus/pasty/internal/env"
|
||||
"github.com/lus/pasty/internal/shared"
|
||||
)
|
||||
|
||||
// Config represents the general application configuration structure
|
||||
type Config struct {
|
||||
WebAddress string
|
||||
StorageType shared.StorageType
|
||||
HastebinSupport bool
|
||||
IDLength int
|
||||
DeletionTokenLength int
|
||||
RateLimit string
|
||||
AutoDelete *AutoDeleteConfig
|
||||
File *FileConfig
|
||||
Postgres *PostgresConfig
|
||||
MongoDB *MongoDBConfig
|
||||
S3 *S3Config
|
||||
}
|
||||
|
||||
// AutoDeleteConfig represents the configuration specific for the AutoDelete behaviour
|
||||
type AutoDeleteConfig struct {
|
||||
Enabled bool
|
||||
Lifetime time.Duration
|
||||
TaskInterval time.Duration
|
||||
}
|
||||
|
||||
// FileConfig represents the configuration specific for the file storage driver
|
||||
type FileConfig struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
// PostgresConfig represents the configuration specific for the Postgres storage driver
|
||||
type PostgresConfig struct {
|
||||
DSN string
|
||||
}
|
||||
|
||||
// MongoDBConfig represents the configuration specific for the MongoDB storage driver
|
||||
type MongoDBConfig struct {
|
||||
DSN string
|
||||
Database string
|
||||
Collection string
|
||||
}
|
||||
|
||||
// S3Config represents the configuration specific for the S3 storage driver
|
||||
type S3Config struct {
|
||||
Endpoint string
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
SecretToken string
|
||||
Secure bool
|
||||
Region string
|
||||
Bucket string
|
||||
}
|
||||
|
||||
// Current holds the currently loaded config
|
||||
var Current *Config
|
||||
|
||||
// Load loads the current config from environment variables and an optional .env file
|
||||
func Load() {
|
||||
godotenv.Load()
|
||||
|
||||
Current = &Config{
|
||||
WebAddress: env.MustString("PASTY_WEB_ADDRESS", ":8080"),
|
||||
StorageType: shared.StorageType(strings.ToLower(env.MustString("PASTY_STORAGE_TYPE", "file"))),
|
||||
HastebinSupport: env.MustBool("PASTY_HASTEBIN_SUPPORT", false),
|
||||
IDLength: env.MustInt("PASTY_ID_LENGTH", 6),
|
||||
DeletionTokenLength: env.MustInt("PASTY_DELETION_TOKEN_LENGTH", 12),
|
||||
RateLimit: env.MustString("PASTY_RATE_LIMIT", "30-M"),
|
||||
AutoDelete: &AutoDeleteConfig{
|
||||
Enabled: env.MustBool("PASTY_AUTODELETE", false),
|
||||
Lifetime: env.MustDuration("PASTY_AUTODELETE_LIFETIME", 720*time.Hour),
|
||||
TaskInterval: env.MustDuration("PASTY_AUTODELETE_TASK_INTERVAL", 5*time.Minute),
|
||||
},
|
||||
File: &FileConfig{
|
||||
Path: env.MustString("PASTY_STORAGE_FILE_PATH", "./data"),
|
||||
},
|
||||
Postgres: &PostgresConfig{
|
||||
DSN: env.MustString("PASTY_STORAGE_POSTGRES_DSN", "postgres://pasty:pasty@localhost/pasty"),
|
||||
},
|
||||
MongoDB: &MongoDBConfig{
|
||||
DSN: env.MustString("PASTY_STORAGE_MONGODB_CONNECTION_STRING", "mongodb://pasty:pasty@localhost/pasty"),
|
||||
Database: env.MustString("PASTY_STORAGE_MONGODB_DATABASE", "pasty"),
|
||||
Collection: env.MustString("PASTY_STORAGE_MONGODB_COLLECTION", "pastes"),
|
||||
},
|
||||
S3: &S3Config{
|
||||
Endpoint: env.MustString("PASTY_STORAGE_S3_ENDPOINT", ""),
|
||||
AccessKeyID: env.MustString("PASTY_STORAGE_S3_ACCESS_KEY_ID", ""),
|
||||
SecretAccessKey: env.MustString("PASTY_STORAGE_S3_SECRET_ACCESS_KEY", ""),
|
||||
SecretToken: env.MustString("PASTY_STORAGE_S3_SECRET_TOKEN", ""),
|
||||
Secure: env.MustBool("PASTY_STORAGE_S3_SECURE", true),
|
||||
Region: env.MustString("PASTY_STORAGE_S3_REGION", ""),
|
||||
Bucket: env.MustString("PASTY_STORAGE_S3_BUCKET", "pasty"),
|
||||
},
|
||||
}
|
||||
}
|
22
internal/env/env.go
vendored
22
internal/env/env.go
vendored
@@ -14,8 +14,8 @@ func Load() {
|
||||
godotenv.Load()
|
||||
}
|
||||
|
||||
// Get returns the content of the environment variable with the given key or the given fallback
|
||||
func Get(key, fallback string) string {
|
||||
// MustString returns the content of the environment variable with the given key or the given fallback
|
||||
func MustString(key, fallback string) string {
|
||||
found := os.Getenv(static.EnvironmentVariablePrefix + key)
|
||||
if found == "" {
|
||||
return fallback
|
||||
@@ -23,14 +23,20 @@ func Get(key, fallback string) string {
|
||||
return found
|
||||
}
|
||||
|
||||
// Bool uses Get and parses it into a boolean
|
||||
func Bool(key string, fallback bool) bool {
|
||||
parsed, _ := strconv.ParseBool(Get(key, strconv.FormatBool(fallback)))
|
||||
// MustBool uses MustString and parses it into a boolean
|
||||
func MustBool(key string, fallback bool) bool {
|
||||
parsed, _ := strconv.ParseBool(MustString(key, strconv.FormatBool(fallback)))
|
||||
return parsed
|
||||
}
|
||||
|
||||
// Duration uses Get and parses it into a duration
|
||||
func Duration(key string, fallback time.Duration) time.Duration {
|
||||
parsed, _ := time.ParseDuration(Get(key, fallback.String()))
|
||||
// MustInt uses MustString and parses it into an integer
|
||||
func MustInt(key string, fallback int) int {
|
||||
parsed, _ := strconv.Atoi(MustString(key, strconv.Itoa(fallback)))
|
||||
return parsed
|
||||
}
|
||||
|
||||
// MustDuration uses MustString and parses it into a duration
|
||||
func MustDuration(key string, fallback time.Duration) time.Duration {
|
||||
parsed, _ := time.ParseDuration(MustString(key, fallback.String()))
|
||||
return parsed
|
||||
}
|
||||
|
@@ -1,21 +0,0 @@
|
||||
package pastes
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/lus/pasty/internal/env"
|
||||
"github.com/lus/pasty/internal/utils"
|
||||
)
|
||||
|
||||
// generateDeletionToken generates a new deletion token
|
||||
func generateDeletionToken() (string, error) {
|
||||
// Read the deletion token length
|
||||
rawLength := env.Get("DELETION_TOKEN_LENGTH", "12")
|
||||
length, err := strconv.Atoi(rawLength)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Generate the deletion token
|
||||
return utils.RandomString(length), nil
|
||||
}
|
@@ -1,56 +0,0 @@
|
||||
package pastes
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/alexedwards/argon2id"
|
||||
"github.com/lus/pasty/internal/env"
|
||||
)
|
||||
|
||||
// Paste represents a saved paste
|
||||
type Paste struct {
|
||||
ID string `json:"id" bson:"_id"`
|
||||
Content string `json:"content" bson:"content"`
|
||||
SuggestedSyntaxType string `json:"suggestedSyntaxType" bson:"suggestedSyntaxType"`
|
||||
DeletionToken string `json:"deletionToken" bson:"deletionToken,omitempty"`
|
||||
Created int64 `json:"created" bson:"created"`
|
||||
AutoDelete bool `json:"autoDelete" bson:"autoDelete"`
|
||||
}
|
||||
|
||||
// Create creates a new paste object using the given content
|
||||
func Create(id, content string) (*Paste, error) {
|
||||
// TODO: Generate the suggested syntax type
|
||||
suggestedSyntaxType := ""
|
||||
|
||||
// Generate the deletion token
|
||||
deletionToken, err := generateDeletionToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return the paste object
|
||||
return &Paste{
|
||||
ID: id,
|
||||
Content: content,
|
||||
SuggestedSyntaxType: suggestedSyntaxType,
|
||||
DeletionToken: deletionToken,
|
||||
Created: time.Now().Unix(),
|
||||
AutoDelete: env.Bool("AUTODELETE", false),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HashDeletionToken hashes the current deletion token of a paste
|
||||
func (paste *Paste) HashDeletionToken() error {
|
||||
hash, err := argon2id.CreateHash(paste.DeletionToken, argon2id.DefaultParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paste.DeletionToken = hash
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckDeletionToken checks whether or not the given deletion token is correct
|
||||
func (paste *Paste) CheckDeletionToken(deletionToken string) bool {
|
||||
match, err := argon2id.ComparePasswordAndHash(deletionToken, paste.DeletionToken)
|
||||
return err == nil && match
|
||||
}
|
30
internal/shared/paste.go
Normal file
30
internal/shared/paste.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"github.com/alexedwards/argon2id"
|
||||
)
|
||||
|
||||
// Paste represents a saved paste
|
||||
type Paste struct {
|
||||
ID string `json:"id" bson:"_id"`
|
||||
Content string `json:"content" bson:"content"`
|
||||
DeletionToken string `json:"deletionToken,omitempty" bson:"deletionToken"`
|
||||
Created int64 `json:"created" bson:"created"`
|
||||
AutoDelete bool `json:"autoDelete" bson:"autoDelete"`
|
||||
}
|
||||
|
||||
// HashDeletionToken hashes the current deletion token of a paste
|
||||
func (paste *Paste) HashDeletionToken() error {
|
||||
hash, err := argon2id.CreateHash(paste.DeletionToken, argon2id.DefaultParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paste.DeletionToken = hash
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckDeletionToken checks whether or not the given deletion token is correct
|
||||
func (paste *Paste) CheckDeletionToken(deletionToken string) bool {
|
||||
match, err := argon2id.ComparePasswordAndHash(deletionToken, paste.DeletionToken)
|
||||
return err == nil && match
|
||||
}
|
11
internal/shared/storage_type.go
Normal file
11
internal/shared/storage_type.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package shared
|
||||
|
||||
// StorageType represents a type of storage a paste can be stored with
|
||||
type StorageType string
|
||||
|
||||
const (
|
||||
StorageTypeFile = StorageType("file")
|
||||
StorageTypePostgres = StorageType("postgres")
|
||||
StorageTypeMongoDB = StorageType("mongodb")
|
||||
StorageTypeS3 = StorageType("s3")
|
||||
)
|
@@ -1,11 +1,11 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/lus/pasty/internal/env"
|
||||
"github.com/lus/pasty/internal/pastes"
|
||||
"github.com/lus/pasty/internal/config"
|
||||
"github.com/lus/pasty/internal/shared"
|
||||
)
|
||||
|
||||
// Current holds the current storage driver
|
||||
@@ -16,8 +16,8 @@ type Driver interface {
|
||||
Initialize() error
|
||||
Terminate() error
|
||||
ListIDs() ([]string, error)
|
||||
Get(id string) (*pastes.Paste, error)
|
||||
Save(paste *pastes.Paste) error
|
||||
Get(id string) (*shared.Paste, error)
|
||||
Save(paste *shared.Paste) error
|
||||
Delete(id string) error
|
||||
Cleanup() (int, error)
|
||||
}
|
||||
@@ -25,8 +25,7 @@ type Driver interface {
|
||||
// Load loads the current storage driver
|
||||
func Load() error {
|
||||
// Define the driver to use
|
||||
storageType := env.Get("STORAGE_TYPE", "file")
|
||||
driver, err := GetDriver(storageType)
|
||||
driver, err := GetDriver(config.Current.StorageType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -40,17 +39,18 @@ func Load() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDriver returns the driver with the given type string if it exists
|
||||
func GetDriver(storageType string) (Driver, error) {
|
||||
switch strings.ToLower(storageType) {
|
||||
case "file":
|
||||
// GetDriver returns the driver with the given type if it exists
|
||||
func GetDriver(storageType shared.StorageType) (Driver, error) {
|
||||
switch storageType {
|
||||
case shared.StorageTypeFile:
|
||||
return new(FileDriver), nil
|
||||
case "s3":
|
||||
return new(S3Driver), nil
|
||||
case "mongodb":
|
||||
case shared.StorageTypePostgres:
|
||||
// TODO: Implement Postgres driver
|
||||
return nil, errors.New("TODO")
|
||||
case shared.StorageTypeMongoDB:
|
||||
return new(MongoDBDriver), nil
|
||||
case "sql":
|
||||
return new(SQLDriver), nil
|
||||
case shared.StorageTypeS3:
|
||||
return new(S3Driver), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid storage type '%s'", storageType)
|
||||
}
|
||||
|
@@ -9,8 +9,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lus/pasty/internal/env"
|
||||
"github.com/lus/pasty/internal/pastes"
|
||||
"github.com/lus/pasty/internal/config"
|
||||
"github.com/lus/pasty/internal/shared"
|
||||
)
|
||||
|
||||
// FileDriver represents the file storage driver
|
||||
@@ -20,7 +20,7 @@ type FileDriver struct {
|
||||
|
||||
// Initialize initializes the file storage driver
|
||||
func (driver *FileDriver) Initialize() error {
|
||||
driver.filePath = env.Get("STORAGE_FILE_PATH", "./data")
|
||||
driver.filePath = config.Current.File.Path
|
||||
return os.MkdirAll(driver.filePath, os.ModePerm)
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func (driver *FileDriver) ListIDs() ([]string, error) {
|
||||
}
|
||||
|
||||
// Get loads a paste
|
||||
func (driver *FileDriver) Get(id string) (*pastes.Paste, error) {
|
||||
func (driver *FileDriver) Get(id string) (*shared.Paste, error) {
|
||||
// Read the file
|
||||
id = base64.StdEncoding.EncodeToString([]byte(id))
|
||||
data, err := ioutil.ReadFile(filepath.Join(driver.filePath, id+".json"))
|
||||
@@ -72,7 +72,7 @@ func (driver *FileDriver) Get(id string) (*pastes.Paste, error) {
|
||||
}
|
||||
|
||||
// Unmarshal the file into a paste
|
||||
paste := new(pastes.Paste)
|
||||
paste := new(shared.Paste)
|
||||
err = json.Unmarshal(data, &paste)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -81,7 +81,7 @@ func (driver *FileDriver) Get(id string) (*pastes.Paste, error) {
|
||||
}
|
||||
|
||||
// Save saves a paste
|
||||
func (driver *FileDriver) Save(paste *pastes.Paste) error {
|
||||
func (driver *FileDriver) Save(paste *shared.Paste) error {
|
||||
// Marshal the paste
|
||||
jsonBytes, err := json.Marshal(paste)
|
||||
if err != nil {
|
||||
@@ -123,15 +123,15 @@ func (driver *FileDriver) Cleanup() (int, error) {
|
||||
// Retrieve the paste object
|
||||
paste, err := driver.Get(id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return deleted, err
|
||||
}
|
||||
|
||||
// Delete the paste if it is expired
|
||||
lifetime := env.Duration("AUTODELETE_LIFETIME", 30*24*time.Hour)
|
||||
lifetime := config.Current.AutoDelete.Lifetime
|
||||
if paste.AutoDelete && paste.Created+int64(lifetime.Seconds()) < time.Now().Unix() {
|
||||
err = driver.Delete(id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return deleted, err
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
|
@@ -1,24 +1,14 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/lus/pasty/internal/env"
|
||||
"github.com/lus/pasty/internal/config"
|
||||
"github.com/lus/pasty/internal/utils"
|
||||
)
|
||||
|
||||
// AcquireID generates a new unique ID
|
||||
func AcquireID() (string, error) {
|
||||
// Read the ID length
|
||||
rawLength := env.Get("ID_LENGTH", "6")
|
||||
length, err := strconv.Atoi(rawLength)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Generate the unique ID
|
||||
for {
|
||||
id := utils.RandomString(length)
|
||||
id := utils.RandomString(config.Current.IDLength)
|
||||
paste, err := Current.Get(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/lus/pasty/internal/env"
|
||||
"github.com/lus/pasty/internal/pastes"
|
||||
"github.com/lus/pasty/internal/config"
|
||||
"github.com/lus/pasty/internal/shared"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
@@ -26,7 +26,7 @@ func (driver *MongoDBDriver) Initialize() error {
|
||||
defer cancel()
|
||||
|
||||
// Connect to the MongoDB host
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(env.Get("STORAGE_MONGODB_CONNECTION_STRING", "mongodb://pasty:pasty@example.host/pasty")))
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(config.Current.MongoDB.DSN))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -39,8 +39,8 @@ func (driver *MongoDBDriver) Initialize() error {
|
||||
|
||||
// Set the driver attributes
|
||||
driver.client = client
|
||||
driver.database = env.Get("STORAGE_MONGODB_DATABASE", "pasty")
|
||||
driver.collection = env.Get("STORAGE_MONGODB_COLLECTION", "pastes")
|
||||
driver.database = config.Current.MongoDB.Database
|
||||
driver.collection = config.Current.MongoDB.Collection
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func (driver *MongoDBDriver) ListIDs() ([]string, error) {
|
||||
}
|
||||
|
||||
// Decode all paste documents
|
||||
var pasteSlice []pastes.Paste
|
||||
var pasteSlice []shared.Paste
|
||||
err = result.All(ctx, &pasteSlice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -80,7 +80,7 @@ func (driver *MongoDBDriver) ListIDs() ([]string, error) {
|
||||
}
|
||||
|
||||
// Get loads a paste
|
||||
func (driver *MongoDBDriver) Get(id string) (*pastes.Paste, error) {
|
||||
func (driver *MongoDBDriver) Get(id string) (*shared.Paste, error) {
|
||||
// Define the collection to use for this database operation
|
||||
collection := driver.client.Database(driver.database).Collection(driver.collection)
|
||||
|
||||
@@ -100,7 +100,7 @@ func (driver *MongoDBDriver) Get(id string) (*pastes.Paste, error) {
|
||||
}
|
||||
|
||||
// Return the retrieved paste object
|
||||
paste := new(pastes.Paste)
|
||||
paste := new(shared.Paste)
|
||||
err = result.Decode(paste)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -109,7 +109,7 @@ func (driver *MongoDBDriver) Get(id string) (*pastes.Paste, error) {
|
||||
}
|
||||
|
||||
// Save saves a paste
|
||||
func (driver *MongoDBDriver) Save(paste *pastes.Paste) error {
|
||||
func (driver *MongoDBDriver) Save(paste *shared.Paste) error {
|
||||
// Define the collection to use for this database operation
|
||||
collection := driver.client.Database(driver.database).Collection(driver.collection)
|
||||
|
||||
@@ -157,7 +157,7 @@ func (driver *MongoDBDriver) Cleanup() (int, error) {
|
||||
}
|
||||
|
||||
// Delete the paste if it is expired
|
||||
lifetime := env.Duration("AUTODELETE_LIFETIME", 30*24*time.Hour)
|
||||
lifetime := config.Current.AutoDelete.Lifetime
|
||||
if paste.AutoDelete && paste.Created+int64(lifetime.Seconds()) < time.Now().Unix() {
|
||||
err = driver.Delete(id)
|
||||
if err != nil {
|
||||
|
@@ -8,8 +8,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lus/pasty/internal/env"
|
||||
"github.com/lus/pasty/internal/pastes"
|
||||
"github.com/lus/pasty/internal/config"
|
||||
"github.com/lus/pasty/internal/shared"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
)
|
||||
@@ -22,16 +22,16 @@ type S3Driver struct {
|
||||
|
||||
// Initialize initializes the AWS S3 storage driver
|
||||
func (driver *S3Driver) Initialize() error {
|
||||
client, err := minio.New(env.Get("STORAGE_S3_ENDPOINT", ""), &minio.Options{
|
||||
Creds: credentials.NewStaticV4(env.Get("STORAGE_S3_ACCESS_KEY_ID", ""), env.Get("STORAGE_S3_SECRET_ACCESS_KEY", ""), env.Get("STORAGE_S3_SECRET_TOKEN", "")),
|
||||
Secure: env.Bool("STORAGE_S3_SECURE", true),
|
||||
Region: env.Get("STORAGE_S3_REGION", ""),
|
||||
client, err := minio.New(config.Current.S3.Endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(config.Current.S3.AccessKeyID, config.Current.S3.SecretAccessKey, config.Current.S3.SecretToken),
|
||||
Secure: config.Current.S3.Secure,
|
||||
Region: config.Current.S3.Region,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
driver.client = client
|
||||
driver.bucket = env.Get("STORAGE_S3_BUCKET", "pasty")
|
||||
driver.bucket = config.Current.S3.Bucket
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func (driver *S3Driver) ListIDs() ([]string, error) {
|
||||
}
|
||||
|
||||
// Get loads a paste
|
||||
func (driver *S3Driver) Get(id string) (*pastes.Paste, error) {
|
||||
func (driver *S3Driver) Get(id string) (*shared.Paste, error) {
|
||||
// Read the object
|
||||
object, err := driver.client.GetObject(context.Background(), driver.bucket, id+".json", minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
@@ -74,7 +74,7 @@ func (driver *S3Driver) Get(id string) (*pastes.Paste, error) {
|
||||
}
|
||||
|
||||
// Unmarshal the object into a paste
|
||||
paste := new(pastes.Paste)
|
||||
paste := new(shared.Paste)
|
||||
err = json.Unmarshal(data, &paste)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -83,7 +83,7 @@ func (driver *S3Driver) Get(id string) (*pastes.Paste, error) {
|
||||
}
|
||||
|
||||
// Save saves a paste
|
||||
func (driver *S3Driver) Save(paste *pastes.Paste) error {
|
||||
func (driver *S3Driver) Save(paste *shared.Paste) error {
|
||||
// Marshal the paste
|
||||
jsonBytes, err := json.Marshal(paste)
|
||||
if err != nil {
|
||||
@@ -123,7 +123,7 @@ func (driver *S3Driver) Cleanup() (int, error) {
|
||||
}
|
||||
|
||||
// Delete the paste if it is expired
|
||||
lifetime := env.Duration("AUTODELETE_LIFETIME", 30*24*time.Hour)
|
||||
lifetime := config.Current.AutoDelete.Lifetime
|
||||
if paste.AutoDelete && paste.Created+int64(lifetime.Seconds()) < time.Now().Unix() {
|
||||
err = driver.Delete(id)
|
||||
if err != nil {
|
||||
|
@@ -1,144 +0,0 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/lus/pasty/internal/env"
|
||||
"github.com/lus/pasty/internal/pastes"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
// SQLDriver represents the SQL storage driver
|
||||
type SQLDriver struct {
|
||||
database *sql.DB
|
||||
table string
|
||||
}
|
||||
|
||||
// Initialize initializes the SQL storage driver
|
||||
func (driver *SQLDriver) Initialize() error {
|
||||
// Parse the DSN and create a database object
|
||||
db, err := sql.Open(env.Get("STORAGE_SQL_DRIVER", "sqlite3"), env.Get("STORAGE_SQL_DSN", "./db"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ping the database
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Migrate the database
|
||||
table := env.Get("STORAGE_SQL_TABLE", "pasty")
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS ? (
|
||||
id varchar NOT NULL PRIMARY KEY,
|
||||
content varchar NOT NULL,
|
||||
suggestedSyntaxType varchar NOT NULL,
|
||||
deletionToken varchar NOT NULL,
|
||||
created bigint NOT NULL,
|
||||
autoDelete bool NOT NULL
|
||||
);
|
||||
`, table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the database object and table name of the SQL driver
|
||||
driver.database = db
|
||||
driver.table = table
|
||||
return nil
|
||||
}
|
||||
|
||||
// Terminate terminates the SQL storage driver
|
||||
func (driver *SQLDriver) Terminate() error {
|
||||
return driver.database.Close()
|
||||
}
|
||||
|
||||
// ListIDs returns a list of all existing paste IDs
|
||||
func (driver *SQLDriver) ListIDs() ([]string, error) {
|
||||
// Execute a SELECT query to retrieve all the paste IDs
|
||||
rows, err := driver.database.Query("SELECT id FROM ?", driver.table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// Scan the rows into a slice of IDs and return it
|
||||
var ids []string
|
||||
err = rows.Scan(&ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// Get loads a paste
|
||||
func (driver *SQLDriver) Get(id string) (*pastes.Paste, error) {
|
||||
// Execute a SELECT query to retrieve the paste
|
||||
row := driver.database.QueryRow("SELECT * FROM ? WHERE id = ?", driver.table, id)
|
||||
err := row.Err()
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Scan the row into a paste and return it
|
||||
paste := new(pastes.Paste)
|
||||
err = row.Scan(&paste)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return paste, nil
|
||||
}
|
||||
|
||||
// Save saves a paste
|
||||
func (driver *SQLDriver) Save(paste *pastes.Paste) error {
|
||||
// Execute an INSERT statement to create the paste
|
||||
_, err := driver.database.Exec("INSERT INTO ? (?, ?, ?, ?, ?, ?)", driver.table, paste.ID, paste.Content, paste.SuggestedSyntaxType, paste.DeletionToken, paste.Created, paste.AutoDelete)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete deletes a paste
|
||||
func (driver *SQLDriver) Delete(id string) error {
|
||||
// Execute a DELETE statement to delete the paste
|
||||
_, err := driver.database.Exec("DELETE FROM ? WHERE id = ?", driver.table, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Cleanup cleans up the expired pastes
|
||||
func (driver *SQLDriver) Cleanup() (int, error) {
|
||||
// Retrieve all paste IDs
|
||||
ids, err := driver.ListIDs()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Define the amount of deleted items
|
||||
deleted := 0
|
||||
|
||||
// Loop through all pastes
|
||||
for _, id := range ids {
|
||||
// Retrieve the paste object
|
||||
paste, err := driver.Get(id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Delete the paste if it is expired
|
||||
lifetime := env.Duration("AUTODELETE_LIFETIME", 30*24*time.Hour)
|
||||
if paste.AutoDelete && paste.Created+int64(lifetime.Seconds()) < time.Now().Unix() {
|
||||
err = driver.Delete(id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
}
|
||||
return deleted, nil
|
||||
}
|
@@ -2,9 +2,12 @@ package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/lus/pasty/internal/pastes"
|
||||
"github.com/lus/pasty/internal/config"
|
||||
"github.com/lus/pasty/internal/shared"
|
||||
"github.com/lus/pasty/internal/storage"
|
||||
"github.com/lus/pasty/internal/utils"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
@@ -15,10 +18,8 @@ func HastebinSupportHandler(ctx *fasthttp.RequestCtx) {
|
||||
switch string(ctx.Request.Header.ContentType()) {
|
||||
case "text/plain":
|
||||
content = string(ctx.PostBody())
|
||||
break
|
||||
case "multipart/form-data":
|
||||
content = string(ctx.FormValue("data"))
|
||||
break
|
||||
default:
|
||||
ctx.SetStatusCode(fasthttp.StatusBadRequest)
|
||||
ctx.SetBodyString("invalid content type")
|
||||
@@ -34,11 +35,12 @@ func HastebinSupportHandler(ctx *fasthttp.RequestCtx) {
|
||||
}
|
||||
|
||||
// Create the paste object
|
||||
paste, err := pastes.Create(id, content)
|
||||
if err != nil {
|
||||
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
||||
ctx.SetBodyString(err.Error())
|
||||
return
|
||||
paste := &shared.Paste{
|
||||
ID: id,
|
||||
Content: content,
|
||||
DeletionToken: utils.RandomString(config.Current.DeletionTokenLength),
|
||||
Created: time.Now().Unix(),
|
||||
AutoDelete: config.Current.AutoDelete.Enabled,
|
||||
}
|
||||
|
||||
// Hash the deletion token
|
||||
|
@@ -2,10 +2,13 @@ package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/fasthttp/router"
|
||||
"github.com/lus/pasty/internal/pastes"
|
||||
"github.com/lus/pasty/internal/config"
|
||||
"github.com/lus/pasty/internal/shared"
|
||||
"github.com/lus/pasty/internal/storage"
|
||||
"github.com/lus/pasty/internal/utils"
|
||||
limitFasthttp "github.com/ulule/limiter/v3/drivers/middleware/fasthttp"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
@@ -73,11 +76,12 @@ func v1PostPaste(ctx *fasthttp.RequestCtx) {
|
||||
}
|
||||
|
||||
// Create the paste object
|
||||
paste, err := pastes.Create(id, values["content"])
|
||||
if err != nil {
|
||||
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
||||
ctx.SetBodyString(err.Error())
|
||||
return
|
||||
paste := &shared.Paste{
|
||||
ID: id,
|
||||
Content: values["content"],
|
||||
DeletionToken: utils.RandomString(config.Current.DeletionTokenLength),
|
||||
Created: time.Now().Unix(),
|
||||
AutoDelete: config.Current.AutoDelete.Enabled,
|
||||
}
|
||||
|
||||
// Hash the deletion token
|
||||
|
@@ -6,5 +6,4 @@ type nilLogger struct {
|
||||
|
||||
// Printf prints nothing
|
||||
func (logger *nilLogger) Printf(string, ...interface{}) {
|
||||
return
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
routing "github.com/fasthttp/router"
|
||||
"github.com/lus/pasty/internal/env"
|
||||
"github.com/lus/pasty/internal/config"
|
||||
"github.com/lus/pasty/internal/static"
|
||||
v1 "github.com/lus/pasty/internal/web/controllers/v1"
|
||||
"github.com/ulule/limiter/v3"
|
||||
@@ -38,7 +38,7 @@ func Serve() error {
|
||||
})
|
||||
|
||||
// Set up the rate limiter
|
||||
rate, err := limiter.NewRateFromFormatted(env.Get("RATE_LIMIT", "30-M"))
|
||||
rate, err := limiter.NewRateFromFormatted(config.Current.RateLimit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -61,12 +61,11 @@ func Serve() error {
|
||||
}
|
||||
|
||||
// Route the hastebin documents route if hastebin support is enabled
|
||||
if env.Bool("HASTEBIN_SUPPORT", false) {
|
||||
if config.Current.HastebinSupport {
|
||||
router.POST("/documents", rateLimiterMiddleware.Handle(v1.HastebinSupportHandler))
|
||||
}
|
||||
|
||||
// Serve the web resources
|
||||
address := env.Get("WEB_ADDRESS", ":8080")
|
||||
return (&fasthttp.Server{
|
||||
Handler: func(ctx *fasthttp.RequestCtx) {
|
||||
// Add the CORS headers
|
||||
@@ -77,7 +76,7 @@ func Serve() error {
|
||||
router.Handler(ctx)
|
||||
},
|
||||
Logger: new(nilLogger),
|
||||
}).ListenAndServe(address)
|
||||
}).ListenAndServe(config.Current.WebAddress)
|
||||
}
|
||||
|
||||
// frontendHandler handles the frontend routing
|
||||
|
Reference in New Issue
Block a user