parent
88717d87b9
commit
d5717f0e7d
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE invites;
|
|
@ -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
|
||||
);
|
|
@ -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)
|
||||
}
|
|
@ -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;
|
|
@ -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")
|
||||
}
|
|
@ -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"
|
2
flag.go
2
flag.go
|
@ -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
34
go.mod
|
@ -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
|
||||
)
|
||||
|
|
23
main.go
23
main.go
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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("")
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue