diff --git a/README.md b/README.md index db6aeb0..2ba87e3 100644 --- a/README.md +++ b/README.md @@ -40,4 +40,13 @@ Every single one of them has its own configuration variables: |-------------------------------------|--------------------------------------------|----------|-----------------------------------------------------------------| | `STORAGE_MONGODB_CONNECTION_STRING` | `mongodb://pasty:pasty@example.host/pasty` | `string` | Defines the connection string to use for the MongoDB connection | | `STORAGE_MONGODB_DATABASE` | `pasty` | `string` | Defines the name of the database to use | -| `STORAGE_MONGODB_COLLECTION` | `pastes` | `string` | Defines the name of the collection to use | \ No newline at end of file +| `STORAGE_MONGODB_COLLECTION` | `pastes` | `string` | Defines the name of the collection to use | + +--- + +### SQL (`sql`) +| Environment Variable | Default Value | Type | Description | +|----------------------|---------------|----------|-------------------------------------------------------------------------------------| +| `STORAGE_SQL_DRIVER` | `sqlite3` | `string` | Defines the driver to use for the SQL connection (`sqlite3`, `postgres` or `mysql`) | +| `STORAGE_SQL_DSN` | `./db` | `string` | Defines the DSN to use for the SQL connection | +| `STORAGE_SQL_TABLE` | `pasty` | `string` | Defines the table name to use for the SQL connection | \ No newline at end of file diff --git a/go.mod b/go.mod index 67e7e19..5378902 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,11 @@ go 1.15 require ( github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b github.com/fasthttp/router v1.2.4 + github.com/go-sql-driver/mysql v1.5.0 github.com/joho/godotenv v1.3.0 github.com/klauspost/compress v1.10.11 // indirect + github.com/lib/pq v1.8.0 + github.com/mattn/go-sqlite3 v1.14.2 github.com/minio/minio-go/v7 v7.0.5 github.com/ulule/limiter/v3 v3.5.0 github.com/valyala/fasthttp v1.16.0 diff --git a/go.sum b/go.sum index 04e41fa..1ae1ead 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,10 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b h1:rcCpjI1OMGtBY8nnBvExeM1pXNoaM35zqmXBGpgJR2o= github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b/go.mod h1:GFtu6vaWaRJV5EvSFaVqgq/3Iq95xyYElBV/aupGzUo= github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/aws/aws-sdk-go v1.29.15 h1:0ms/213murpsujhsnxnNKNeVouW60aJqSd992Ks3mxs= github.com/aws/aws-sdk-go v1.29.15/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -18,6 +20,7 @@ github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTM github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -89,9 +92,13 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-sqlite3 v1.14.2 h1:A2EQLwjYf/hfYaM20FVjs1UewCTTFR7RmjEHkLjldIA= +github.com/mattn/go-sqlite3 v1.14.2/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= github.com/minio/minio-go/v7 v7.0.5 h1:I2NIJ2ojwJqD/YByemC1M59e1b4FW9kS7NlOar7HPV4= @@ -168,12 +175,14 @@ golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= diff --git a/internal/storage/driver.go b/internal/storage/driver.go index 595de61..21bac74 100644 --- a/internal/storage/driver.go +++ b/internal/storage/driver.go @@ -47,6 +47,8 @@ func GetDriver(storageType string) (Driver, error) { return new(S3Driver), nil case "mongodb": return new(MongoDBDriver), nil + case "sql": + return new(SQLDriver), nil default: return nil, fmt.Errorf("invalid storage type '%s'", storageType) } diff --git a/internal/storage/s3_driver.go b/internal/storage/s3_driver.go index 8925b0f..67bb957 100644 --- a/internal/storage/s3_driver.go +++ b/internal/storage/s3_driver.go @@ -63,6 +63,9 @@ func (driver *S3Driver) Get(id string) (*pastes.Paste, error) { if err != nil { return nil, err } + if object == nil { + return nil, nil + } data, err := ioutil.ReadAll(object) if err != nil { return nil, err diff --git a/internal/storage/sql_driver.go b/internal/storage/sql_driver.go new file mode 100644 index 0000000..d57c466 --- /dev/null +++ b/internal/storage/sql_driver.go @@ -0,0 +1,108 @@ +package storage + +import ( + "database/sql" + "github.com/Lukaesebrot/pasty/internal/env" + "github.com/Lukaesebrot/pasty/internal/pastes" + _ "github.com/go-sql-driver/mysql" + _ "github.com/lib/pq" + _ "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 + ); + `, 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) + 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 +}