wip: sqlc

Signed-off-by: jolheiser <john.olheiser@gmail.com>
main
jolheiser 2023-03-02 23:51:53 -06:00
parent 88717d87b9
commit d5717f0e7d
Signed by: jolheiser
GPG Key ID: B853ADA5DA7BBF7A
16 changed files with 1820 additions and 44 deletions

31
database/db.go 100644
View File

@ -0,0 +1,31 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
package database
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@ -0,0 +1,119 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
// source: invites.sql
package database
import (
"context"
"go.jolheiser.com/invitea/database/sqlc"
)
const countInvites = `-- name: CountInvites :one
SELECT count(*) FROM invites
`
func (q *Queries) CountInvites(ctx context.Context) (int64, error) {
row := q.db.QueryRowContext(ctx, countInvites)
var count int64
err := row.Scan(&count)
return count, err
}
const createInvite = `-- name: CreateInvite :one
INSERT INTO invites (
code, uses, total, expiration
) VALUES (
?, ?, ?, ?
)
RETURNING id, code, uses, total, expiration
`
type CreateInviteParams struct {
Code string
Uses int64
Total int64
Expiration sqlc.Timestamp
}
func (q *Queries) CreateInvite(ctx context.Context, arg CreateInviteParams) (Invite, error) {
row := q.db.QueryRowContext(ctx, createInvite,
arg.Code,
arg.Uses,
arg.Total,
arg.Expiration,
)
var i Invite
err := row.Scan(
&i.ID,
&i.Code,
&i.Uses,
&i.Total,
&i.Expiration,
)
return i, err
}
const deleteInvite = `-- name: DeleteInvite :exec
DELETE FROM invites
WHERE id = ?
`
func (q *Queries) DeleteInvite(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteInvite, id)
return err
}
const getInvite = `-- name: GetInvite :one
SELECT id, code, uses, total, expiration FROM invites
WHERE id = ? LIMIT 1
`
func (q *Queries) GetInvite(ctx context.Context, id int64) (Invite, error) {
row := q.db.QueryRowContext(ctx, getInvite, id)
var i Invite
err := row.Scan(
&i.ID,
&i.Code,
&i.Uses,
&i.Total,
&i.Expiration,
)
return i, err
}
const listInvites = `-- name: ListInvites :many
SELECT id, code, uses, total, expiration FROM invites
ORDER BY id
`
func (q *Queries) ListInvites(ctx context.Context) ([]Invite, error) {
rows, err := q.db.QueryContext(ctx, listInvites)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Invite
for rows.Next() {
var i Invite
if err := rows.Scan(
&i.ID,
&i.Code,
&i.Uses,
&i.Total,
&i.Expiration,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

17
database/models.go 100644
View File

@ -0,0 +1,17 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.17.2
package database
import (
"go.jolheiser.com/invitea/database/sqlc"
)
type Invite struct {
ID int64
Code string
Uses int64
Total int64
Expiration sqlc.Timestamp
}

View File

@ -0,0 +1 @@
DROP TABLE invites;

View File

@ -0,0 +1,7 @@
CREATE TABLE invites (
id INTEGER PRIMARY KEY,
code TEXT NOT NULL,
uses INTEGER NOT NULL,
total INTEGER NOT NULL,
expiration DATE NOT NULL
);

View File

@ -0,0 +1,25 @@
package migrations
import (
"database/sql"
"go.jolheiser.com/invitea/database/sqlc"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/sqlite"
_ "modernc.org/sqlite"
)
func New(db *sql.DB) (*migrate.Migrate, error) {
migrations, err := sqlc.Migrations()
if err != nil {
return nil, err
}
instance, err := sqlite.WithInstance(db, &sqlite.Config{})
if err != nil {
return nil, err
}
return migrate.NewWithInstance("sqlite", migrations, "invitea", instance)
}

View File

@ -0,0 +1,22 @@
-- name: GetInvite :one
SELECT * FROM invites
WHERE id = ? LIMIT 1;
-- name: ListInvites :many
SELECT * FROM invites
ORDER BY id;
-- name: CreateInvite :one
INSERT INTO invites (
code, uses, total, expiration
) VALUES (
?, ?, ?, ?
)
RETURNING *;
-- name: DeleteInvite :exec
DELETE FROM invites
WHERE id = ?;
-- name: CountInvites :one
SELECT count(*) FROM invites;

View File

@ -0,0 +1,35 @@
//go:generate sqlc generate
package sqlc
import (
"embed"
"time"
_ "github.com/golang-migrate/migrate/v4/database/sqlite"
"github.com/golang-migrate/migrate/v4/source"
"github.com/golang-migrate/migrate/v4/source/iofs"
)
//go:embed migrations/*.sql
var migrations embed.FS
func Migrations() (source.Driver, error) {
d, err := iofs.New(migrations, "migrations")
if err != nil {
return nil, err
}
return d, nil
}
type Timestamp int64
func (t Timestamp) Time() time.Time {
return time.UnixMilli(int64(t))
}
func (t Timestamp) String() string {
if t == 0 {
return "None"
}
return t.Time().Format("01/02/2006")
}

View File

@ -0,0 +1,12 @@
version: 2
sql:
- engine: "sqlite"
schema: "migrations"
queries: "queries"
gen:
go:
package: "database"
out: "../"
overrides:
- db_type: "DATE"
go_type: "go.jolheiser.com/invitea/database/sqlc.Timestamp"

View File

@ -16,7 +16,7 @@ var (
jsonLogFlag = fs.Bool("json", false, "Enable JSON logging")
portFlag = fs.Int("port", 8080, "Port to run on")
sessionSecretFlag = fs.String("session-secret", string(securecookie.GenerateRandomKey(32)), "Session secret")
dbFlag = fs.String("database", "invitea.db", "Path to database")
dbFlag = fs.String("database", "invitea.db", "Path to database")
// Required
domainFlag = fs.String("domain", "", "Domain Invitea is running on")

34
go.mod
View File

@ -5,22 +5,42 @@ go 1.19
require (
code.gitea.io/sdk/gitea v0.15.1
github.com/go-chi/chi/v5 v5.0.7
github.com/golang-migrate/migrate/v4 v4.15.2
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.1.1
github.com/markbates/goth v1.69.0
github.com/peterbourgon/ff/v3 v3.1.2
github.com/rs/zerolog v1.26.1
modernc.org/sqlite v1.10.6
)
require (
github.com/golang/protobuf v1.4.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2 // indirect
github.com/gorilla/mux v1.7.4 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-version v1.2.1 // indirect
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
google.golang.org/appengine v1.6.6 // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/yaml.v2 v2.2.4 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
go.uber.org/atomic v1.7.0 // indirect
golang.org/x/mod v0.5.0 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf // indirect
golang.org/x/tools v0.1.7 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
modernc.org/cc/v3 v3.32.4 // indirect
modernc.org/ccgo/v3 v3.9.2 // indirect
modernc.org/libc v1.9.5 // indirect
modernc.org/mathutil v1.2.2 // indirect
modernc.org/memory v1.0.4 // indirect
modernc.org/opt v0.1.1 // indirect
modernc.org/strutil v1.1.0 // indirect
modernc.org/token v1.0.0 // indirect
)

1459
go.sum

File diff suppressed because it is too large Load Diff

23
main.go
View File

@ -1,11 +1,16 @@
package main
import (
"database/sql"
"errors"
"fmt"
"github.com/golang-migrate/migrate/v4"
"net/http"
"os"
"os/signal"
"go.jolheiser.com/invitea/database"
"go.jolheiser.com/invitea/database/sqlc/migrations"
"go.jolheiser.com/invitea/router"
"github.com/peterbourgon/ff/v3"
@ -42,10 +47,26 @@ func main() {
log.Fatal().Err(err).Msg("")
}
dsn := fmt.Sprintf("file:%s", *dbFlag)
db, err := sql.Open("sqlite", dsn)
if err != nil {
log.Fatal().Err(err).Msg("could not open database")
}
defer db.Close()
migrator, err := migrations.New(db)
if err != nil {
log.Fatal().Err(err).Msg("could not initialize migrations")
}
if err := migrator.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
log.Fatal().Err(err).Msg("could not migrate database")
}
r := router.New(router.Config{
Domain: *domainFlag,
SessionSecret: *sessionSecretFlag,
DatabasePath: *dbFlag,
Database: database.New(db),
GiteaURL: *giteaURLFlag,
GiteaClientKey: *giteaClientKeyFlag,
GiteaClientSecret: *giteaClientSecretFlag,

View File

@ -5,6 +5,9 @@ import (
"net/http"
"strings"
"go.jolheiser.com/invitea/database"
"go.jolheiser.com/invitea/static"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/gorilla/sessions"
@ -12,13 +15,12 @@ import (
"github.com/markbates/goth/gothic"
"github.com/markbates/goth/providers/gitea"
"github.com/rs/zerolog/log"
"go.jolheiser.com/invitea/static"
)
type Config struct {
Domain string
SessionSecret string
DatabasePath string
Database *database.Queries
GiteaURL string
GiteaClientKey string
@ -47,33 +49,27 @@ func New(cfg Config) *chi.Mux {
store := NewSessionStore(cfg.SessionSecret, cfg.GiteaURL)
r := chi.NewMux()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
routes := Routes{
DB: cfg.Database,
}
mux := chi.NewMux()
mux.Use(middleware.Logger)
mux.Use(middleware.Recoverer)
middleware.DefaultLogger = middleware.RequestLogger(&middleware.DefaultLogFormatter{
Logger: &log.Logger,
NoColor: true,
})
r.Mount("/css", http.FileServer(http.FS(static.CSS)))
mux.Mount("/css", http.FileServer(http.FS(static.CSS)))
r.Route("/", func(r chi.Router) {
mux.Route("/", func(r chi.Router) {
r.Use(store.Middleware)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
var isAdmin bool
if ia, ok := r.Context().Value("isAdmin").(bool); ok {
isAdmin = ia
}
if err := static.Templates.ExecuteTemplate(w, "index.tmpl", map[string]any{
"isAdmin": isAdmin,
}); err != nil {
log.Err(err).Msg("")
}
})
r.Get("/", routes.Index)
})
r.Route("/auth", func(r chi.Router) {
mux.Route("/auth", func(r chi.Router) {
r.Get("/login", func(w http.ResponseWriter, r *http.Request) {
user, err := gothic.CompleteUserAuth(w, r)
if err != nil {
@ -101,5 +97,5 @@ func New(cfg Config) *chi.Mux {
})
})
return r
return mux
}

33
router/routes.go 100644
View File

@ -0,0 +1,33 @@
package router
import (
"net/http"
"go.jolheiser.com/invitea/database"
"go.jolheiser.com/invitea/static"
"github.com/rs/zerolog/log"
)
type Routes struct {
DB *database.Queries
}
func (ro *Routes) Index(w http.ResponseWriter, r *http.Request) {
var isAdmin bool
if ia, ok := r.Context().Value("isAdmin").(bool); ok {
isAdmin = ia
}
invites, err := ro.DB.ListInvites(r.Context())
if err != nil {
log.Err(err).Msg("")
}
if err := static.Templates.ExecuteTemplate(w, "index.tmpl", map[string]any{
"isAdmin": isAdmin,
"invites": invites,
}); err != nil {
log.Err(err).Msg("")
}
}

View File

@ -1,12 +1,12 @@
<form method="POST">
<input type="hidden" name="action" value="create"/>
<p>
<label for="num_uses">Number of Uses (Leave at 0 for unlimited)</label>
<input type="number" name="num_uses" value="0" min="0"/>
<label for="uses">Number of Uses (Leave at 0 for unlimited)</label>
<input id="uses" type="number" name="uses" value="0" min="0"/>
</p>
<p>
<label for="expiration">Expiration (Leave blank for no expiration)</label>
<input type="date" name="expiration"/>
<input id="expiration" type="date" name="expiration"/>
</p>
<button type="submit">Create Invite</button>
</form>
@ -25,7 +25,7 @@
{{ range .invites }}
<tr>
<td>{{ .Code }}</td>
<td>{{ .NumLeft }}/{{ .NumUses }}</td>
<td>{{ .Uses }}/{{ .Total }}</td>
<td>{{ .Expiration }}</td>
<td>Delete</td>
</tr>