Skip to content

Commit 2f0228f

Browse files
committed
Change NewHandler to New and update README
1 parent 0411020 commit 2f0228f

File tree

8 files changed

+93
-86
lines changed

8 files changed

+93
-86
lines changed

README.md

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22

33
[![Build](https://github.com/joelseq/sqliteadmin-go/actions/workflows/build.yml/badge.svg)](https://github.com/joelseq/sqliteadmin-go/actions/workflows/build.yml)
44

5-
SQLite Admin provides a lightweight web server to interact with a SQLite database. It allows you to:
5+
SQLite Admin is a Golang library and binary which enables you to easily interact with a SQLite database. It allows you to:
66

77
- Browse tables and their schemas.
88
- View table data along with adding filters, limits and offsets.
99
- Modify individual columns in existing rows.
1010

11-
It can either by installed as a binary or embedded into an existing Golang backend as a library.
11+
![screenshot](assets/sqlite-admin-filtering.png)
12+
13+
It can either be integrated into an existing Golang backend as a library or installed as a binary.
1214

1315
The web server can be interacted with by going to https://sqliteadmin.dev.
1416

1517
The source code for the web UI can be found at https://github.com/joelseq/sqliteadmin-ui
1618

17-
1819
## Motivation
1920

2021
SQLite is very easy to add as an embedded database but it's difficult to manage the database once it's deployed in an application.
@@ -23,7 +24,41 @@ Existing tools primarily focus on local SQLite files, requiring manual interacti
2324

2425
The alternative is to use a cloud-hosted version like those provided by [Turso](https://turso.tech/) which enables interacting with the database using tools like [Drizzle Studio](https://orm.drizzle.team/drizzle-studio/overview). This adds complexity to the setup and deployment of your application and you lose out on the value of having an embedded database.
2526

26-
This project fills that gap by providing an easy way to view and manage an embedded SQLite database via a web UI—no need to migrate to a cloud provider or use ad-hoc solutions.
27+
This project fills that gap by providing an easy way to view and manage an embedded SQLite database via a web UI — no need to migrate to a cloud provider or use ad-hoc solutions.
28+
29+
## Using as a library
30+
31+
```go
32+
config := sqliteadmin.Config{
33+
DB: db, // *sql.DB
34+
Username: "username", // username to use to login from https://sqliteadmin.dev
35+
Password: "password", // password to use to login from https://sqliteadmin.dev
36+
Logger: logger, // optional, implements the Logger interface
37+
}
38+
admin := sqliteadmin.New(config)
39+
40+
// HandlePost is a HandlerFunc that you can pass in to your router
41+
router.Post("/admin", admin.HandlePost)
42+
```
43+
44+
Check out the full code at `examples/chi/main.go`.
45+
46+
You can also run the example to test out the admin UI:
47+
48+
```bash
49+
go run examples/chi/main.go
50+
```
51+
52+
This will spin up a server on `http://localhost:8080`. You can then interact with your local server by going to `https://sqliteadmin.dev` and passing in the following credentials:
53+
54+
```
55+
username: user
56+
password: password
57+
endpoint: http://localhost:8080/admin
58+
```
59+
60+
> [!NOTE]
61+
> If you are seeing "An unexpected error occurred" when trying to connect, try disabling your adblock.
2762
2863
## Installing as a binary
2964

@@ -38,13 +73,15 @@ go install github.com/joelseq/sqliteadmin-go/cmd/sqliteadmin@latest
3873
```bash
3974
make build
4075
```
76+
4177
This will add the `sqliteadmin` binary to `/tmp/bin`
4278

4379
### Usage
4480

4581
In order to add authentication, the following environment variables are required: `SQLITEADMIN_USERNAME`, `SQLITEADMIN_PASSWORD`.
4682

4783
e.g.:
84+
4885
```bash
4986
export SQLITEADMIN_USERNAME=user
5087
export SQLITEADMIN_PASSWORD=password
@@ -58,27 +95,6 @@ sqliteadmin serve <path to sqlite db> -p 8080
5895

5996
Your SQLite database can now be accessed by visiting https://sqliteadmin.dev and providing the credentials and endpoint (including port).
6097

61-
## Using as a library
62-
63-
Check out the `examples/` directory to see how to integrate it into an existing Golang backend.
64-
65-
You can also run the examples to test out the admin UI:
66-
67-
```bash
68-
go run examples/chi/main.go
69-
```
70-
71-
This will spin up a server on `http://localhost:8080`. You can then interact with your local server by going to `https://sqliteadmin.dev` and passing in the following credentials:
72-
73-
```
74-
username: user
75-
password: password
76-
endpoint: http://localhost:8080/admin
77-
```
78-
79-
> [!NOTE]
80-
> If you are seeing "An unexpected error occurred" when trying to connect, try disabling your adblock.
81-
8298
## Inspiration
8399

84100
The UI is heavily inspired by [Drizzle Studio](https://orm.drizzle.team/drizzle-studio/overview).

assets/sqlite-admin-filtering.png

71.7 KB
Loading

cmd/sqliteadmin/serve.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func getRouter(dbPath, username, password string) *chi.Mux {
8484
Password: password,
8585
Logger: logger,
8686
}
87-
sh := sqliteadmin.NewHandler(config)
87+
admin := sqliteadmin.New(config)
8888

8989
r := chi.NewRouter()
9090
r.Use(middleware.Logger)
@@ -95,7 +95,7 @@ func getRouter(dbPath, username, password string) *chi.Mux {
9595
AllowCredentials: true,
9696
MaxAge: 300,
9797
}))
98-
r.Post("/", sh.HandlePost)
98+
r.Post("/", admin.HandlePost)
9999

100100
return r
101101
}

examples/chi/main.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,13 @@ func main() {
2424

2525
logger := slog.Default()
2626

27-
// Setup the handler for SQLiteAdmin
2827
config := sqliteadmin.Config{
2928
DB: db,
3029
Username: "user",
3130
Password: "password",
3231
Logger: logger,
3332
}
34-
sh := sqliteadmin.NewHandler(config)
33+
admin := sqliteadmin.New(config)
3534

3635
r := chi.NewRouter()
3736
r.Use(middleware.Logger)
@@ -46,7 +45,7 @@ func main() {
4645
w.Write([]byte("welcome"))
4746
})
4847
// Setup the handler for SQLiteAdmin
49-
r.Post("/admin", sh.HandlePost)
48+
r.Post("/admin", admin.HandlePost)
5049

5150
fmt.Printf("--> Starting server on %s\n", addr)
5251
http.ListenAndServe(":8080", r)

examples/stdlib/stdlib.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"github.com/joelseq/sqliteadmin-go"
1515
"github.com/rs/cors"
1616
_ "modernc.org/sqlite"
17-
// "github.com/rs/cors"
1817
)
1918

2019
func main() {
@@ -30,7 +29,7 @@ func main() {
3029
Password: "password",
3130
Logger: logger,
3231
}
33-
sHandler := sqliteadmin.NewHandler(config)
32+
admin := sqliteadmin.New(config)
3433

3534
mux := http.NewServeMux()
3635

@@ -40,7 +39,7 @@ func main() {
4039

4140
mux.HandleFunc("/admin", func(w http.ResponseWriter, r *http.Request) {
4241
if r.Method == "POST" {
43-
sHandler.HandlePost(w, r)
42+
admin.HandlePost(w, r)
4443
}
4544
})
4645

@@ -92,10 +91,3 @@ func gracefulShutdown(apiServer *http.Server, done chan bool) {
9291
// Notify the main goroutine that the shutdown is complete
9392
done <- true
9493
}
95-
96-
// func enableCors(w http.ResponseWriter) {
97-
// w.Header().Set("Access-Control-Allow-Origin", "*")
98-
// w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
99-
// w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type")
100-
// w.Header().Set("Access-Control-Allow-Credentials", "true")
101-
// }

queryhandlers.go

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@ import (
1111
"github.com/mitchellh/mapstructure"
1212
)
1313

14-
func (h *Handler) ping(w http.ResponseWriter) {
15-
h.logger.Info("Command: Ping")
14+
func (a *Admin) ping(w http.ResponseWriter) {
15+
a.logger.Info("Command: Ping")
1616
w.WriteHeader(http.StatusOK)
1717
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
1818
}
1919

20-
func (h *Handler) listTables(w http.ResponseWriter) {
21-
h.logger.Info("Command: ListTables")
22-
rows, err := h.db.Query("SELECT name FROM sqlite_master WHERE type='table';")
20+
func (a *Admin) listTables(w http.ResponseWriter) {
21+
a.logger.Info("Command: ListTables")
22+
rows, err := a.db.Query("SELECT name FROM sqlite_master WHERE type='table';")
2323
if err != nil {
24-
h.logger.Error(fmt.Sprintf("Error listing tables: %v", err))
24+
a.logger.Error(fmt.Sprintf("Error listing tables: %v", err))
2525
writeError(w, apiErrSomethingWentWrong())
2626
return
2727
}
@@ -31,7 +31,7 @@ func (h *Handler) listTables(w http.ResponseWriter) {
3131
for rows.Next() {
3232
var table string
3333
if err := rows.Scan(&table); err != nil {
34-
h.logger.Error(fmt.Sprintf("Error scanning rows: %v", err))
34+
a.logger.Error(fmt.Sprintf("Error scanning rows: %v", err))
3535
writeError(w, apiErrSomethingWentWrong())
3636
return
3737
}
@@ -41,7 +41,7 @@ func (h *Handler) listTables(w http.ResponseWriter) {
4141
json.NewEncoder(w).Encode(map[string][]string{"tables": tables})
4242
}
4343

44-
func (h *Handler) getTable(w http.ResponseWriter, params map[string]interface{}) {
44+
func (a *Admin) getTable(w http.ResponseWriter, params map[string]interface{}) {
4545
// Parse table name
4646
table, ok := params["tableName"].(string)
4747
if !ok {
@@ -69,44 +69,44 @@ func (h *Handler) getTable(w http.ResponseWriter, params map[string]interface{})
6969
}
7070
}
7171

72-
h.logger.Info(fmt.Sprintf("Command: GetTable, table=%s, limit=%d, offset=%d", table, limit, offset))
72+
a.logger.Info(fmt.Sprintf("Command: GetTable, table=%s, limit=%d, offset=%d", table, limit, offset))
7373

7474
var condition *Condition
7575
conditionParam, ok := params["condition"]
7676
if ok {
77-
condition, ok = toCondition(conditionParam, h.logger)
77+
condition, ok = toCondition(conditionParam, a.logger)
7878
if !ok {
7979
writeError(w, apiErrBadRequest("Invalid condition"))
8080
return
8181
}
82-
h.logger.Debug(fmt.Sprintf("Condition provided: %v", condition))
82+
a.logger.Debug(fmt.Sprintf("Condition provided: %v", condition))
8383
} else {
84-
h.logger.Debug("No condition provided")
84+
a.logger.Debug("No condition provided")
8585
}
8686

87-
data, err := queryTable(h.db, table, condition, limit, offset, h.logger)
87+
data, err := queryTable(a.db, table, condition, limit, offset, a.logger)
8888
if err != nil {
89-
h.logger.Error(fmt.Sprintf("Error querying table: %v", err))
89+
a.logger.Error(fmt.Sprintf("Error querying table: %v", err))
9090
writeError(w, apiErrSomethingWentWrong())
9191
return
9292
}
9393
response := map[string]interface{}{"rows": data}
9494

9595
if params["includeInfo"] == true {
96-
tableInfo, err := getTableInfo(h.db, table)
96+
tableInfo, err := getTableInfo(a.db, table)
9797
if err != nil {
98-
h.logger.Error(fmt.Sprintf("Error getting table info: %v", err))
98+
a.logger.Error(fmt.Sprintf("Error getting table info: %v", err))
9999
writeError(w, apiErrSomethingWentWrong())
100100
return
101101
}
102102
response["tableInfo"] = tableInfo
103103
}
104-
h.logger.Info(fmt.Sprintf("Fetched %d rows", len(data)))
104+
a.logger.Info(fmt.Sprintf("Fetched %d rows", len(data)))
105105

106106
json.NewEncoder(w).Encode(response)
107107
}
108108

109-
func (h *Handler) deleteRows(w http.ResponseWriter, params map[string]interface{}) {
109+
func (a *Admin) deleteRows(w http.ResponseWriter, params map[string]interface{}) {
110110
table, ok := params["tableName"].(string)
111111
if !ok {
112112
writeError(w, apiErrBadRequest(ErrMissingTableName.Error()))
@@ -119,32 +119,32 @@ func (h *Handler) deleteRows(w http.ResponseWriter, params map[string]interface{
119119
return
120120
}
121121

122-
h.logger.Info(fmt.Sprintf("Command: DeleteRows, table=%s, ids=%v", table, ids))
122+
a.logger.Info(fmt.Sprintf("Command: DeleteRows, table=%s, ids=%v", table, ids))
123123

124-
exists, err := checkTableExists(h.db, table)
124+
exists, err := checkTableExists(a.db, table)
125125
if err != nil {
126-
h.logger.Error(fmt.Sprintf("Error checking table existence: %v", err))
126+
a.logger.Error(fmt.Sprintf("Error checking table existence: %v", err))
127127
writeError(w, apiErrSomethingWentWrong())
128128
return
129129
}
130130
if !exists {
131-
h.logger.Error(fmt.Sprintf("Error table does not exist: %s", table))
131+
a.logger.Error(fmt.Sprintf("Error table does not exist: %s", table))
132132
writeError(w, apiErrBadRequest(ErrInvalidInput.Error()))
133133
return
134134
}
135135

136-
rowsAffected, err := batchDelete(h.db, table, ids)
136+
rowsAffected, err := batchDelete(a.db, table, ids)
137137
if err != nil {
138-
h.logger.Error(fmt.Sprintf("Error deleting rows from table: %v", err))
138+
a.logger.Error(fmt.Sprintf("Error deleting rows from table: %v", err))
139139
writeError(w, apiErrSomethingWentWrong())
140140
return
141141
}
142-
h.logger.Info(fmt.Sprintf("Deleted %d row(s)", rowsAffected))
142+
a.logger.Info(fmt.Sprintf("Deleted %d row(s)", rowsAffected))
143143

144144
json.NewEncoder(w).Encode(map[string]string{"rowsAffected": fmt.Sprintf("%d", rowsAffected)})
145145
}
146146

147-
func (h *Handler) updateRow(w http.ResponseWriter, params map[string]interface{}) {
147+
func (a *Admin) updateRow(w http.ResponseWriter, params map[string]interface{}) {
148148
table, ok := params["tableName"].(string)
149149
if !ok {
150150
writeError(w, apiErrBadRequest(ErrMissingTableName.Error()))
@@ -157,15 +157,15 @@ func (h *Handler) updateRow(w http.ResponseWriter, params map[string]interface{}
157157
return
158158
}
159159

160-
h.logger.Info(fmt.Sprintf("Command: UpdateRow, table=%s, row=%v", table, row))
160+
a.logger.Info(fmt.Sprintf("Command: UpdateRow, table=%s, row=%v", table, row))
161161

162-
err := editRow(h.db, table, row)
162+
err := editRow(a.db, table, row)
163163
if err != nil {
164-
h.logger.Error(fmt.Sprintf("Error editing row: %v", err))
164+
a.logger.Error(fmt.Sprintf("Error editing row: %v", err))
165165
writeError(w, apiErrSomethingWentWrong())
166166
return
167167
}
168-
h.logger.Info("Row updated")
168+
a.logger.Info("Row updated")
169169

170170
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
171171
}

0 commit comments

Comments
 (0)