Skip to content

Commit 6786184

Browse files
authored
refactor: merge pr #450
Add MySQL driver
2 parents bcd986f + 5e2807f commit 6786184

File tree

8 files changed

+112
-22
lines changed

8 files changed

+112
-22
lines changed

.github/workflows/release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,6 @@ jobs:
5050
- name: Generate artifact attestation
5151
uses: actions/attest-build-provenance@v1
5252
with:
53-
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
53+
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
5454
subject-digest: ${{ steps.push.outputs.digest }}
5555
push-to-registry: true

README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Pastebins are a type of online content storage service where users can store pla
2929
- [x] Configurable ratelimiting, expiration, compression, etc.
3030
- [x] Modern, JavaScript-free user interface
3131
- [x] Syntax highlighting for all the most popular languages and Raw text mode
32-
- [x] SQLite and PostgreSQL Support
32+
- [x] SQLite, MySQL, and PostgreSQL Support
3333
- [x] Basic Auth for private instances
3434
- [ ] Password-protected encrypted pastes
3535
- [ ] Paste collections
@@ -106,7 +106,7 @@ volumes:
106106
#### Manually
107107
108108
> [!IMPORTANT]
109-
> **Requires: [Git](https://git-scm.com/downloads), [Go 1.22.4](https://go.dev/doc/install), [GNU Makefile](https://www.gnu.org/software/make/#download), and a SQLite database or [PostgreSQL](https://www.postgresql.org/download/) [server](https://m.do.co/c/beaf675c3e00).**
109+
> **Requires: [Git](https://git-scm.com/downloads), [Go 1.22.4](https://go.dev/doc/install), [GNU Makefile](https://www.gnu.org/software/make/#download), and a SQLite, MySQL, or [PostgreSQL](https://www.postgresql.org/download/) [server](https://m.do.co/c/beaf675c3e00).**
110110
111111
```sh
112112
# Clone the Github repository
@@ -118,6 +118,7 @@ $ make spirit
118118

119119
# Start Spacebin
120120
$ SPIRIT_CONNECTION_URI="sqlite://database.sqlite" ./bin/spirit # SQLite
121+
$ SPIRIT_CONNECTION_URI="mysql://<your instance URI>?parseTime=true" ./bin/spirit
121122
$ SPIRIT_CONNECTION_URI="postgres://<your PostgreSQL instance URI>" ./bin/spirit # PostgreSQL
122123

123124
# Success! Spacebin is now available at port 9000 on your machine.
@@ -149,6 +150,8 @@ Spacebin supports two database formats: **SQLite** and **Postgres**
149150
- For SQLite, use either the scheme `file://` or `sqlite://` and a file name.
150151
- Example: `file://database.db`
151152
- For PostgreSQL, use [the standard PostgreSQL URI format](https://stackoverflow.com/questions/3582552/what-is-the-format-for-the-postgresql-connection-string-url#20722229).
153+
- For MySQL, use the [DSN format](https://github.com/go-sql-driver/mysql?tab=readme-ov-file#dsn-data-source-name) prefixed with `mysql://` or `mariadb://`
154+
- You must set the `parseTime` option to true; append `?parseTime=true` to the end of the URI
152155

153156
### Usage
154157

cmd/spacebin/main.go

+15-15
Original file line numberDiff line numberDiff line change
@@ -61,29 +61,27 @@ func main() {
6161
// Connect either to SQLite or PostgreSQL
6262
switch uri.Scheme {
6363
case "file", "sqlite":
64-
sq, err := database.NewSQLite(uri.Host)
65-
if err != nil {
66-
log.Fatal().
67-
Err(err).
68-
Msg("Could not connect to database")
69-
}
70-
db = sq
71-
case "postgresql":
72-
pg, err := database.NewPostgres(uri.String())
73-
if err != nil {
74-
log.Fatal().
75-
Err(err).
76-
Msg("Could not connect to database")
77-
}
78-
db = pg
64+
db, err = database.NewSQLite(uri)
65+
case "postgresql", "postgres":
66+
db, err = database.NewPostgres(uri)
67+
case "mysql", "mariadb":
68+
db, err = database.NewMySQL(uri)
69+
}
70+
71+
if err != nil {
72+
log.Fatal().
73+
Err(err).
74+
Msg("Could not connect to database")
7975
}
8076

77+
// Perform migrations
8178
if err := db.Migrate(context.Background()); err != nil {
8279
log.Fatal().
8380
Err(err).
8481
Msg("Failed migrations; Could not create DOCUMENTS tables.")
8582
}
8683

84+
// Create a new server and register middleware, security headers, static files, and handlers
8785
m := server.NewServer(&config.Config, db)
8886

8987
m.MountMiddleware()
@@ -95,11 +93,13 @@ func main() {
9593

9694
m.MountHandlers()
9795

96+
// Create the server on the specified host and port
9897
srv := &http.Server{
9998
Addr: fmt.Sprintf("%s:%d", config.Config.Host, config.Config.Port),
10099
Handler: m.Router,
101100
}
102101

102+
// Graceful shutdown
103103
srvCtx, srvStopCtx := context.WithCancel(context.Background())
104104

105105
// Watch for OS signals

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ require (
1616
)
1717

1818
require (
19+
filippo.io/edwards25519 v1.1.0 // indirect
1920
github.com/dlclark/regexp2 v1.11.0 // indirect
2021
github.com/dustin/go-humanize v1.0.1 // indirect
2122
github.com/google/uuid v1.6.0 // indirect
@@ -35,6 +36,7 @@ require (
3536
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
3637
github.com/cespare/xxhash/v2 v2.3.0 // indirect
3738
github.com/davecgh/go-spew v1.1.1 // indirect
39+
github.com/go-sql-driver/mysql v1.8.1
3840
github.com/kr/pretty v0.3.1 // indirect
3941
github.com/mattn/go-colorable v0.1.13 // indirect
4042
github.com/mattn/go-isatty v0.0.20 // indirect

go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
2+
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
13
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
24
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
35
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
@@ -28,6 +30,8 @@ github.com/go-chi/httprate v0.14.1 h1:EKZHYEZ58Cg6hWcYzoZILsv7ppb46Wt4uQ738IRtpZ
2830
github.com/go-chi/httprate v0.14.1/go.mod h1:TUepLXaz/pCjmCtf/obgOQJ2Sz6rC8fSf5cAt5cnTt0=
2931
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
3032
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
33+
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
34+
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
3135
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
3236
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
3337
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=

internal/database/database_mysql.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2020-2024 Luke Whritenour
3+
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package database
18+
19+
import (
20+
"context"
21+
"database/sql"
22+
"net/url"
23+
"strings"
24+
"time"
25+
26+
_ "github.com/go-sql-driver/mysql"
27+
)
28+
29+
type MySQL struct {
30+
*sql.DB
31+
}
32+
33+
func NewMySQL(uri *url.URL) (Database, error) {
34+
_, uriTrimmed, _ := strings.Cut(uri.String(), uri.Scheme+"://")
35+
db, err := sql.Open("mysql", uriTrimmed)
36+
37+
db.SetConnMaxLifetime(time.Minute * 3)
38+
db.SetMaxOpenConns(10)
39+
db.SetMaxIdleConns(10)
40+
41+
return &MySQL{db}, err
42+
}
43+
44+
func (m *MySQL) Migrate(ctx context.Context) error {
45+
_, err := m.Exec(`
46+
CREATE TABLE IF NOT EXISTS documents (
47+
id VARCHAR(255) PRIMARY KEY,
48+
content TEXT NOT NULL,
49+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
50+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
51+
)`)
52+
53+
return err
54+
}
55+
56+
func (m *MySQL) GetDocument(ctx context.Context, id string) (Document, error) {
57+
doc := new(Document)
58+
row := m.QueryRow("SELECT * FROM documents WHERE id=?", id)
59+
err := row.Scan(&doc.ID, &doc.Content, &doc.CreatedAt, &doc.UpdatedAt)
60+
61+
return *doc, err
62+
}
63+
64+
func (m *MySQL) CreateDocument(ctx context.Context, id, content string) error {
65+
tx, err := m.Begin()
66+
67+
if err != nil {
68+
return err
69+
}
70+
71+
_, err = tx.Exec("INSERT INTO documents (id, content) VALUES (?, ?)",
72+
id, content) // created_at and updated_at are auto-generated
73+
74+
if err != nil {
75+
return err
76+
}
77+
78+
return tx.Commit()
79+
}

internal/database/database_pg.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package database
1919
import (
2020
"context"
2121
"database/sql"
22+
"net/url"
2223

2324
_ "github.com/lib/pq"
2425
)
@@ -27,8 +28,8 @@ type Postgres struct {
2728
*sql.DB
2829
}
2930

30-
func NewPostgres(uri string) (Database, error) {
31-
db, err := sql.Open("postgres", uri)
31+
func NewPostgres(uri *url.URL) (Database, error) {
32+
db, err := sql.Open("postgres", uri.String())
3233

3334
return &Postgres{db}, err
3435
}

internal/database/database_sqlite.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package database
1919
import (
2020
"context"
2121
"database/sql"
22+
"net/url"
2223
"sync"
2324

2425
_ "modernc.org/sqlite"
@@ -29,8 +30,8 @@ type SQLite struct {
2930
sync.RWMutex
3031
}
3132

32-
func NewSQLite(filesath string) (Database, error) {
33-
db, err := sql.Open("sqlite", filesath)
33+
func NewSQLite(uri *url.URL) (Database, error) {
34+
db, err := sql.Open("sqlite", uri.Host)
3435

3536
return &SQLite{db, sync.RWMutex{}}, err
3637
}

0 commit comments

Comments
 (0)