Add user sessions and login
This isn't quite perfect, but gets much of the hard work done.
This commit is contained in:
parent
e311464b51
commit
486c148bf7
28 changed files with 1701 additions and 30 deletions
52
.air.toml
Normal file
52
.air.toml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./tmp/nidus-sync"
|
||||
cmd = "go build -o ./tmp/nidus-sync ."
|
||||
delay = 1000
|
||||
exclude_dir = ["static", "tmp"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = false
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
silent = false
|
||||
time = true
|
||||
|
||||
[misc]
|
||||
clean_on_exit = true
|
||||
|
||||
[proxy]
|
||||
app_port = 0
|
||||
enabled = false
|
||||
proxy_port = 0
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
157
auth.go
Normal file
157
auth.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/aarondl/opt/omitnull"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/enums"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/sql"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type NoCredentialsError struct{}
|
||||
func (e NoCredentialsError) Error() string { return "No credentials were present in the request" }
|
||||
|
||||
type InvalidCredentials struct{}
|
||||
func (e InvalidCredentials) Error() string { return "No username with that password exists" }
|
||||
|
||||
type InvalidUsername struct{}
|
||||
func (e InvalidUsername) Error() string { return "That username doesn't exist" }
|
||||
|
||||
func addUserSession(r *http.Request, user *models.User) {
|
||||
id := strconv.Itoa(int(user.ID))
|
||||
sessionManager.Put(r.Context(), "user_id", id)
|
||||
sessionManager.Put(r.Context(), "username", user.Username)
|
||||
slog.Info("Created new user session",
|
||||
slog.String("username", user.Username),
|
||||
slog.String("user_id", id))
|
||||
}
|
||||
|
||||
func getAuthenticatedUser(r *http.Request) (*models.User, error) {
|
||||
//user_id := sessionManager.GetInt(r.Context(), "user_id")
|
||||
user_id_str := sessionManager.GetString(r.Context(), "user_id")
|
||||
user_id, err := strconv.Atoi(user_id_str)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to convert user_id to int: %v", err)
|
||||
}
|
||||
username := sessionManager.GetString(r.Context(), "username")
|
||||
slog.Info("Current session info",
|
||||
slog.Int("user_id", user_id),
|
||||
slog.String("username", username))
|
||||
if user_id > 0 && username != "" {
|
||||
return models.FindUser(r.Context(), PGInstance.BobDB, int32(user_id))
|
||||
}
|
||||
// If we can't get the user from the session try to get from auth headers
|
||||
username, password, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
return nil, &NoCredentialsError{}
|
||||
}
|
||||
user, err := validateUser(r.Context(), username, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addUserSession(r, user)
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func hashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
func signinUser(r *http.Request, username string, password string) (*models.User, error) {
|
||||
user, err := validateUser(r.Context(), username, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
return nil, errors.New("No matching user")
|
||||
}
|
||||
addUserSession(r, user)
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func signupUser(username string, name string, password string) (*models.User, error) {
|
||||
passwordHash, err := hashPassword(password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot signup user: %v", err)
|
||||
}
|
||||
setter := models.UserSetter{
|
||||
DisplayName: omitnull.From(name),
|
||||
PasswordHash: omitnull.From(passwordHash),
|
||||
PasswordHashType: omitnull.From(enums.HashtypeBcrypt14),
|
||||
Username: omit.From(username),
|
||||
}
|
||||
u, err := models.Users.Insert(&setter).One(context.TODO(), PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to create user: %v", err)
|
||||
}
|
||||
slog.Info("Created user",
|
||||
slog.Int("ID", int(u.ID)),
|
||||
slog.String("username", u.Username))
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func validatePassword(password, hash string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func validateUser(ctx context.Context, username string, password string) (*models.User, error) {
|
||||
passwordHash, err := hashPassword(password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to hash password: %v", err)
|
||||
}
|
||||
slog.Info("Validating user",
|
||||
slog.String("username", username),
|
||||
slog.String("password", password),
|
||||
slog.String("hash", passwordHash))
|
||||
result, err := sql.UserByUsername(username).All(ctx, PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to query for user: %v", err)
|
||||
}
|
||||
switch len(result) {
|
||||
case 0:
|
||||
return nil, InvalidUsername{}
|
||||
case 1:
|
||||
row := result[0]
|
||||
hash, err := row.PasswordHash.Value()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hash == nil {
|
||||
return nil, errors.New("Hash is nil")
|
||||
}
|
||||
hashStr, ok := hash.(string);
|
||||
if !ok {
|
||||
return nil, errors.New("Hash isn't a string")
|
||||
}
|
||||
if !validatePassword(password, hashStr) {
|
||||
return nil, InvalidCredentials{}
|
||||
}
|
||||
user := models.User{
|
||||
ID: row.ID,
|
||||
ArcgisAccessToken: row.ArcgisAccessToken,
|
||||
ArcgisLicense: row.ArcgisLicense,
|
||||
ArcgisRefreshToken: row.ArcgisRefreshToken,
|
||||
ArcgisRefreshTokenExpires: row.ArcgisRefreshTokenExpires,
|
||||
ArcgisRole: row.ArcgisRole,
|
||||
DisplayName: row.DisplayName,
|
||||
Email: row.Email,
|
||||
OrganizationID: row.OrganizationID,
|
||||
Username: row.Username,
|
||||
}
|
||||
return &user, nil
|
||||
default:
|
||||
return nil, errors.New("More than one matching row, this should be impossible.")
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ var embedMigrations embed.FS
|
|||
|
||||
type postgres struct {
|
||||
BobDB bob.DB
|
||||
PGXPool *pgxpool.Pool
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -96,7 +97,7 @@ func initializeDatabase(ctx context.Context, uri string) error {
|
|||
pgOnce.Do(func() {
|
||||
db, e := pgxpool.New(ctx, uri)
|
||||
bobDB := bob.NewDB(stdlib.OpenDBFromPool(db))
|
||||
PGInstance = &postgres{bobDB}
|
||||
PGInstance = &postgres{bobDB, db}
|
||||
err = e
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
|||
17
dberrors/sessions.bob.go
Normal file
17
dberrors/sessions.bob.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Code generated by BobGen psql v0.41.1. DO NOT EDIT.
|
||||
// This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
package dberrors
|
||||
|
||||
var SessionErrors = &sessionErrors{
|
||||
ErrUniqueSessionsPkey: &UniqueConstraintError{
|
||||
schema: "",
|
||||
table: "sessions",
|
||||
columns: []string{"token"},
|
||||
s: "sessions_pkey",
|
||||
},
|
||||
}
|
||||
|
||||
type sessionErrors struct {
|
||||
ErrUniqueSessionsPkey *UniqueConstraintError
|
||||
}
|
||||
130
dbinfo/sessions.bob.go
Normal file
130
dbinfo/sessions.bob.go
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
// Code generated by BobGen psql v0.41.1. DO NOT EDIT.
|
||||
// This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
package dbinfo
|
||||
|
||||
import "github.com/aarondl/opt/null"
|
||||
|
||||
var Sessions = Table[
|
||||
sessionColumns,
|
||||
sessionIndexes,
|
||||
sessionForeignKeys,
|
||||
sessionUniques,
|
||||
sessionChecks,
|
||||
]{
|
||||
Schema: "",
|
||||
Name: "sessions",
|
||||
Columns: sessionColumns{
|
||||
Token: column{
|
||||
Name: "token",
|
||||
DBType: "text",
|
||||
Default: "",
|
||||
Comment: "",
|
||||
Nullable: false,
|
||||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
Data: column{
|
||||
Name: "data",
|
||||
DBType: "bytea",
|
||||
Default: "",
|
||||
Comment: "",
|
||||
Nullable: false,
|
||||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
Expiry: column{
|
||||
Name: "expiry",
|
||||
DBType: "timestamp with time zone",
|
||||
Default: "",
|
||||
Comment: "",
|
||||
Nullable: false,
|
||||
Generated: false,
|
||||
AutoIncr: false,
|
||||
},
|
||||
},
|
||||
Indexes: sessionIndexes{
|
||||
SessionsPkey: index{
|
||||
Type: "btree",
|
||||
Name: "sessions_pkey",
|
||||
Columns: []indexColumn{
|
||||
{
|
||||
Name: "token",
|
||||
Desc: null.FromCond(false, true),
|
||||
IsExpression: false,
|
||||
},
|
||||
},
|
||||
Unique: true,
|
||||
Comment: "",
|
||||
NullsFirst: []bool{false},
|
||||
NullsDistinct: false,
|
||||
Where: "",
|
||||
Include: []string{},
|
||||
},
|
||||
SessionsExpiryIdx: index{
|
||||
Type: "btree",
|
||||
Name: "sessions_expiry_idx",
|
||||
Columns: []indexColumn{
|
||||
{
|
||||
Name: "expiry",
|
||||
Desc: null.FromCond(false, true),
|
||||
IsExpression: false,
|
||||
},
|
||||
},
|
||||
Unique: false,
|
||||
Comment: "",
|
||||
NullsFirst: []bool{false},
|
||||
NullsDistinct: false,
|
||||
Where: "",
|
||||
Include: []string{},
|
||||
},
|
||||
},
|
||||
PrimaryKey: &constraint{
|
||||
Name: "sessions_pkey",
|
||||
Columns: []string{"token"},
|
||||
Comment: "",
|
||||
},
|
||||
|
||||
Comment: "",
|
||||
}
|
||||
|
||||
type sessionColumns struct {
|
||||
Token column
|
||||
Data column
|
||||
Expiry column
|
||||
}
|
||||
|
||||
func (c sessionColumns) AsSlice() []column {
|
||||
return []column{
|
||||
c.Token, c.Data, c.Expiry,
|
||||
}
|
||||
}
|
||||
|
||||
type sessionIndexes struct {
|
||||
SessionsPkey index
|
||||
SessionsExpiryIdx index
|
||||
}
|
||||
|
||||
func (i sessionIndexes) AsSlice() []index {
|
||||
return []index{
|
||||
i.SessionsPkey, i.SessionsExpiryIdx,
|
||||
}
|
||||
}
|
||||
|
||||
type sessionForeignKeys struct{}
|
||||
|
||||
func (f sessionForeignKeys) AsSlice() []foreignKey {
|
||||
return []foreignKey{}
|
||||
}
|
||||
|
||||
type sessionUniques struct{}
|
||||
|
||||
func (u sessionUniques) AsSlice() []constraint {
|
||||
return []constraint{}
|
||||
}
|
||||
|
||||
type sessionChecks struct{}
|
||||
|
||||
func (c sessionChecks) AsSlice() []check {
|
||||
return []check{}
|
||||
}
|
||||
51
endpoint.go
51
endpoint.go
|
|
@ -1,8 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
func getFavicon(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-type", "image/x-icon")
|
||||
|
|
@ -11,9 +13,19 @@ func getFavicon(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func getRoot(w http.ResponseWriter, r *http.Request) {
|
||||
err := htmlRoot(w, r.URL.Path)
|
||||
user, err := getAuthenticatedUser(r)
|
||||
if err != nil && !errors.Is(err, &NoCredentialsError{}) {
|
||||
respondError(w, "Failed to get root", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if user == nil {
|
||||
errorCode := r.URL.Query().Get("error")
|
||||
err = htmlSignin(w, errorCode)
|
||||
} else {
|
||||
err = htmlDashboard(w, user)
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
respondError(w, "Failed to render root", err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
func getSignup(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -28,6 +40,33 @@ func respondError(w http.ResponseWriter, m string, e error, s int) {
|
|||
http.Error(w, m, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func postSignin(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
respondError(w, "Could not parse form", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
|
||||
slog.Info("Signin",
|
||||
slog.String("username", username),
|
||||
slog.String("password", strings.Repeat("*", len(password))))
|
||||
|
||||
|
||||
_, err := signinUser(r, username, password)
|
||||
if err != nil {
|
||||
if errors.Is(err, InvalidCredentials{}) {
|
||||
http.Redirect(w, r, "/?error=invalid-credentials", http.StatusFound)
|
||||
return
|
||||
}
|
||||
respondError(w, "Failed to signin user", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
}
|
||||
|
||||
func postSignup(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
respondError(w, "Could not parse form", err, http.StatusBadRequest)
|
||||
|
|
@ -41,7 +80,8 @@ func postSignup(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
slog.Info("Signup",
|
||||
slog.String("username", username),
|
||||
slog.String("name", name))
|
||||
slog.String("name", name),
|
||||
slog.String("password", strings.Repeat("*", len(password))))
|
||||
|
||||
if terms != "on" {
|
||||
slog.Error("Terms not agreed", slog.String("terms", terms))
|
||||
|
|
@ -49,10 +89,13 @@ func postSignup(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := signupUser(username, name, password); err != nil {
|
||||
user, err := signupUser(username, name, password)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to signup user", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
addUserSession(r, user)
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ var (
|
|||
organizationWithParentsCascadingCtx = newContextual[bool]("organizationWithParentsCascading")
|
||||
organizationRelUserCtx = newContextual[bool]("organization.user_.user_.user__organization_id_fkey")
|
||||
|
||||
// Relationship Contexts for sessions
|
||||
sessionWithParentsCascadingCtx = newContextual[bool]("sessionWithParentsCascading")
|
||||
|
||||
// Relationship Contexts for user_
|
||||
userWithParentsCascadingCtx = newContextual[bool]("userWithParentsCascading")
|
||||
userRelOrganizationCtx = newContextual[bool]("organization.user_.user_.user__organization_id_fkey")
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
type Factory struct {
|
||||
baseGooseDBVersionMods GooseDBVersionModSlice
|
||||
baseOrganizationMods OrganizationModSlice
|
||||
baseSessionMods SessionModSlice
|
||||
baseUserMods UserModSlice
|
||||
}
|
||||
|
||||
|
|
@ -79,6 +80,32 @@ func (f *Factory) FromExistingOrganization(m *models.Organization) *Organization
|
|||
return o
|
||||
}
|
||||
|
||||
func (f *Factory) NewSession(mods ...SessionMod) *SessionTemplate {
|
||||
return f.NewSessionWithContext(context.Background(), mods...)
|
||||
}
|
||||
|
||||
func (f *Factory) NewSessionWithContext(ctx context.Context, mods ...SessionMod) *SessionTemplate {
|
||||
o := &SessionTemplate{f: f}
|
||||
|
||||
if f != nil {
|
||||
f.baseSessionMods.Apply(ctx, o)
|
||||
}
|
||||
|
||||
SessionModSlice(mods).Apply(ctx, o)
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
func (f *Factory) FromExistingSession(m *models.Session) *SessionTemplate {
|
||||
o := &SessionTemplate{f: f, alreadyPersisted: true}
|
||||
|
||||
o.Token = func() string { return m.Token }
|
||||
o.Data = func() []byte { return m.Data }
|
||||
o.Expiry = func() time.Time { return m.Expiry }
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
func (f *Factory) NewUser(mods ...UserMod) *UserTemplate {
|
||||
return f.NewUserWithContext(context.Background(), mods...)
|
||||
}
|
||||
|
|
@ -135,6 +162,14 @@ func (f *Factory) AddBaseOrganizationMod(mods ...OrganizationMod) {
|
|||
f.baseOrganizationMods = append(f.baseOrganizationMods, mods...)
|
||||
}
|
||||
|
||||
func (f *Factory) ClearBaseSessionMods() {
|
||||
f.baseSessionMods = nil
|
||||
}
|
||||
|
||||
func (f *Factory) AddBaseSessionMod(mods ...SessionMod) {
|
||||
f.baseSessionMods = append(f.baseSessionMods, mods...)
|
||||
}
|
||||
|
||||
func (f *Factory) ClearBaseUserMods() {
|
||||
f.baseUserMods = nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,30 @@ func TestCreateOrganization(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCreateSession(t *testing.T) {
|
||||
if testDB == nil {
|
||||
t.Skip("skipping test, no DSN provided")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
tx, err := testDB.Begin(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Error starting transaction: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := tx.Rollback(ctx); err != nil {
|
||||
t.Fatalf("Error rolling back transaction: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := New().NewSessionWithContext(ctx).Create(ctx, tx); err != nil {
|
||||
t.Fatalf("Error creating Session: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
if testDB == nil {
|
||||
t.Skip("skipping test, no DSN provided")
|
||||
|
|
|
|||
|
|
@ -14,6 +14,14 @@ import (
|
|||
|
||||
var defaultFaker = faker.New()
|
||||
|
||||
func random___byte(f *faker.Faker, limits ...string) []byte {
|
||||
if f == nil {
|
||||
f = &defaultFaker
|
||||
}
|
||||
|
||||
return []byte(random_string(f, limits...))
|
||||
}
|
||||
|
||||
func random_bool(f *faker.Faker, limits ...string) bool {
|
||||
if f == nil {
|
||||
f = &defaultFaker
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
package factory
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stephenafamo/bob"
|
||||
|
|
@ -12,6 +13,17 @@ import (
|
|||
// Set the testDB to enable tests that use the database
|
||||
var testDB bob.Transactor[bob.Tx]
|
||||
|
||||
func TestRandom___byte(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
val1 := random___byte(nil)
|
||||
val2 := random___byte(nil)
|
||||
|
||||
if bytes.Equal(val1, val2) {
|
||||
t.Fatalf("random___byte() returned the same value twice: %v", val1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandom_int32(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
|||
344
factory/sessions.bob.go
Normal file
344
factory/sessions.bob.go
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
// Code generated by BobGen psql v0.41.1. DO NOT EDIT.
|
||||
// This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
package factory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
models "github.com/Gleipnir-Technology/nidus-sync/models"
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/jaswdr/faker/v2"
|
||||
"github.com/stephenafamo/bob"
|
||||
)
|
||||
|
||||
type SessionMod interface {
|
||||
Apply(context.Context, *SessionTemplate)
|
||||
}
|
||||
|
||||
type SessionModFunc func(context.Context, *SessionTemplate)
|
||||
|
||||
func (f SessionModFunc) Apply(ctx context.Context, n *SessionTemplate) {
|
||||
f(ctx, n)
|
||||
}
|
||||
|
||||
type SessionModSlice []SessionMod
|
||||
|
||||
func (mods SessionModSlice) Apply(ctx context.Context, n *SessionTemplate) {
|
||||
for _, f := range mods {
|
||||
f.Apply(ctx, n)
|
||||
}
|
||||
}
|
||||
|
||||
// SessionTemplate is an object representing the database table.
|
||||
// all columns are optional and should be set by mods
|
||||
type SessionTemplate struct {
|
||||
Token func() string
|
||||
Data func() []byte
|
||||
Expiry func() time.Time
|
||||
|
||||
f *Factory
|
||||
|
||||
alreadyPersisted bool
|
||||
}
|
||||
|
||||
// Apply mods to the SessionTemplate
|
||||
func (o *SessionTemplate) Apply(ctx context.Context, mods ...SessionMod) {
|
||||
for _, mod := range mods {
|
||||
mod.Apply(ctx, o)
|
||||
}
|
||||
}
|
||||
|
||||
// setModelRels creates and sets the relationships on *models.Session
|
||||
// according to the relationships in the template. Nothing is inserted into the db
|
||||
func (t SessionTemplate) setModelRels(o *models.Session) {}
|
||||
|
||||
// BuildSetter returns an *models.SessionSetter
|
||||
// this does nothing with the relationship templates
|
||||
func (o SessionTemplate) BuildSetter() *models.SessionSetter {
|
||||
m := &models.SessionSetter{}
|
||||
|
||||
if o.Token != nil {
|
||||
val := o.Token()
|
||||
m.Token = omit.From(val)
|
||||
}
|
||||
if o.Data != nil {
|
||||
val := o.Data()
|
||||
m.Data = omit.From(val)
|
||||
}
|
||||
if o.Expiry != nil {
|
||||
val := o.Expiry()
|
||||
m.Expiry = omit.From(val)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// BuildManySetter returns an []*models.SessionSetter
|
||||
// this does nothing with the relationship templates
|
||||
func (o SessionTemplate) BuildManySetter(number int) []*models.SessionSetter {
|
||||
m := make([]*models.SessionSetter, number)
|
||||
|
||||
for i := range m {
|
||||
m[i] = o.BuildSetter()
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Build returns an *models.Session
|
||||
// Related objects are also created and placed in the .R field
|
||||
// NOTE: Objects are not inserted into the database. Use SessionTemplate.Create
|
||||
func (o SessionTemplate) Build() *models.Session {
|
||||
m := &models.Session{}
|
||||
|
||||
if o.Token != nil {
|
||||
m.Token = o.Token()
|
||||
}
|
||||
if o.Data != nil {
|
||||
m.Data = o.Data()
|
||||
}
|
||||
if o.Expiry != nil {
|
||||
m.Expiry = o.Expiry()
|
||||
}
|
||||
|
||||
o.setModelRels(m)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// BuildMany returns an models.SessionSlice
|
||||
// Related objects are also created and placed in the .R field
|
||||
// NOTE: Objects are not inserted into the database. Use SessionTemplate.CreateMany
|
||||
func (o SessionTemplate) BuildMany(number int) models.SessionSlice {
|
||||
m := make(models.SessionSlice, number)
|
||||
|
||||
for i := range m {
|
||||
m[i] = o.Build()
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func ensureCreatableSession(m *models.SessionSetter) {
|
||||
if !(m.Token.IsValue()) {
|
||||
val := random_string(nil)
|
||||
m.Token = omit.From(val)
|
||||
}
|
||||
if !(m.Data.IsValue()) {
|
||||
val := random___byte(nil)
|
||||
m.Data = omit.From(val)
|
||||
}
|
||||
if !(m.Expiry.IsValue()) {
|
||||
val := random_time_Time(nil)
|
||||
m.Expiry = omit.From(val)
|
||||
}
|
||||
}
|
||||
|
||||
// insertOptRels creates and inserts any optional the relationships on *models.Session
|
||||
// according to the relationships in the template.
|
||||
// any required relationship should have already exist on the model
|
||||
func (o *SessionTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.Session) error {
|
||||
var err error
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Create builds a session and inserts it into the database
|
||||
// Relations objects are also inserted and placed in the .R field
|
||||
func (o *SessionTemplate) Create(ctx context.Context, exec bob.Executor) (*models.Session, error) {
|
||||
var err error
|
||||
opt := o.BuildSetter()
|
||||
ensureCreatableSession(opt)
|
||||
|
||||
m, err := models.Sessions.Insert(opt).One(ctx, exec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := o.insertOptRels(ctx, exec, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
// MustCreate builds a session and inserts it into the database
|
||||
// Relations objects are also inserted and placed in the .R field
|
||||
// panics if an error occurs
|
||||
func (o *SessionTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.Session {
|
||||
m, err := o.Create(ctx, exec)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// CreateOrFail builds a session and inserts it into the database
|
||||
// Relations objects are also inserted and placed in the .R field
|
||||
// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs
|
||||
func (o *SessionTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.Session {
|
||||
tb.Helper()
|
||||
m, err := o.Create(ctx, exec)
|
||||
if err != nil {
|
||||
tb.Fatal(err)
|
||||
return nil
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// CreateMany builds multiple sessions and inserts them into the database
|
||||
// Relations objects are also inserted and placed in the .R field
|
||||
func (o SessionTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.SessionSlice, error) {
|
||||
var err error
|
||||
m := make(models.SessionSlice, number)
|
||||
|
||||
for i := range m {
|
||||
m[i], err = o.Create(ctx, exec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// MustCreateMany builds multiple sessions and inserts them into the database
|
||||
// Relations objects are also inserted and placed in the .R field
|
||||
// panics if an error occurs
|
||||
func (o SessionTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.SessionSlice {
|
||||
m, err := o.CreateMany(ctx, exec, number)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// CreateManyOrFail builds multiple sessions and inserts them into the database
|
||||
// Relations objects are also inserted and placed in the .R field
|
||||
// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs
|
||||
func (o SessionTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.SessionSlice {
|
||||
tb.Helper()
|
||||
m, err := o.CreateMany(ctx, exec, number)
|
||||
if err != nil {
|
||||
tb.Fatal(err)
|
||||
return nil
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Session has methods that act as mods for the SessionTemplate
|
||||
var SessionMods sessionMods
|
||||
|
||||
type sessionMods struct{}
|
||||
|
||||
func (m sessionMods) RandomizeAllColumns(f *faker.Faker) SessionMod {
|
||||
return SessionModSlice{
|
||||
SessionMods.RandomToken(f),
|
||||
SessionMods.RandomData(f),
|
||||
SessionMods.RandomExpiry(f),
|
||||
}
|
||||
}
|
||||
|
||||
// Set the model columns to this value
|
||||
func (m sessionMods) Token(val string) SessionMod {
|
||||
return SessionModFunc(func(_ context.Context, o *SessionTemplate) {
|
||||
o.Token = func() string { return val }
|
||||
})
|
||||
}
|
||||
|
||||
// Set the Column from the function
|
||||
func (m sessionMods) TokenFunc(f func() string) SessionMod {
|
||||
return SessionModFunc(func(_ context.Context, o *SessionTemplate) {
|
||||
o.Token = f
|
||||
})
|
||||
}
|
||||
|
||||
// Clear any values for the column
|
||||
func (m sessionMods) UnsetToken() SessionMod {
|
||||
return SessionModFunc(func(_ context.Context, o *SessionTemplate) {
|
||||
o.Token = nil
|
||||
})
|
||||
}
|
||||
|
||||
// Generates a random value for the column using the given faker
|
||||
// if faker is nil, a default faker is used
|
||||
func (m sessionMods) RandomToken(f *faker.Faker) SessionMod {
|
||||
return SessionModFunc(func(_ context.Context, o *SessionTemplate) {
|
||||
o.Token = func() string {
|
||||
return random_string(f)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Set the model columns to this value
|
||||
func (m sessionMods) Data(val []byte) SessionMod {
|
||||
return SessionModFunc(func(_ context.Context, o *SessionTemplate) {
|
||||
o.Data = func() []byte { return val }
|
||||
})
|
||||
}
|
||||
|
||||
// Set the Column from the function
|
||||
func (m sessionMods) DataFunc(f func() []byte) SessionMod {
|
||||
return SessionModFunc(func(_ context.Context, o *SessionTemplate) {
|
||||
o.Data = f
|
||||
})
|
||||
}
|
||||
|
||||
// Clear any values for the column
|
||||
func (m sessionMods) UnsetData() SessionMod {
|
||||
return SessionModFunc(func(_ context.Context, o *SessionTemplate) {
|
||||
o.Data = nil
|
||||
})
|
||||
}
|
||||
|
||||
// Generates a random value for the column using the given faker
|
||||
// if faker is nil, a default faker is used
|
||||
func (m sessionMods) RandomData(f *faker.Faker) SessionMod {
|
||||
return SessionModFunc(func(_ context.Context, o *SessionTemplate) {
|
||||
o.Data = func() []byte {
|
||||
return random___byte(f)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Set the model columns to this value
|
||||
func (m sessionMods) Expiry(val time.Time) SessionMod {
|
||||
return SessionModFunc(func(_ context.Context, o *SessionTemplate) {
|
||||
o.Expiry = func() time.Time { return val }
|
||||
})
|
||||
}
|
||||
|
||||
// Set the Column from the function
|
||||
func (m sessionMods) ExpiryFunc(f func() time.Time) SessionMod {
|
||||
return SessionModFunc(func(_ context.Context, o *SessionTemplate) {
|
||||
o.Expiry = f
|
||||
})
|
||||
}
|
||||
|
||||
// Clear any values for the column
|
||||
func (m sessionMods) UnsetExpiry() SessionMod {
|
||||
return SessionModFunc(func(_ context.Context, o *SessionTemplate) {
|
||||
o.Expiry = nil
|
||||
})
|
||||
}
|
||||
|
||||
// Generates a random value for the column using the given faker
|
||||
// if faker is nil, a default faker is used
|
||||
func (m sessionMods) RandomExpiry(f *faker.Faker) SessionMod {
|
||||
return SessionModFunc(func(_ context.Context, o *SessionTemplate) {
|
||||
o.Expiry = func() time.Time {
|
||||
return random_time_Time(f)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (m sessionMods) WithParentsCascading() SessionMod {
|
||||
return SessionModFunc(func(ctx context.Context, o *SessionTemplate) {
|
||||
if isDone, _ := sessionWithParentsCascadingCtx.Value(ctx); isDone {
|
||||
return
|
||||
}
|
||||
ctx = sessionWithParentsCascadingCtx.WithValue(ctx, true)
|
||||
})
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
# Development shell configuration
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.air
|
||||
pkgs.go
|
||||
pkgs.goose
|
||||
pkgs.gotools
|
||||
|
|
|
|||
3
go.mod
3
go.mod
|
|
@ -4,6 +4,7 @@ go 1.24.9
|
|||
|
||||
require (
|
||||
github.com/aarondl/opt v0.0.0-20250607033636-982744e1bd65
|
||||
github.com/alexedwards/scs/pgxstore v0.0.0-20251002162104-209de6e426de
|
||||
github.com/alexedwards/scs/v2 v2.9.0
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-webauthn/webauthn v0.14.0
|
||||
|
|
@ -14,6 +15,7 @@ require (
|
|||
github.com/pressly/goose/v3 v3.26.0
|
||||
github.com/stephenafamo/bob v0.41.1
|
||||
github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07
|
||||
golang.org/x/crypto v0.42.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
@ -34,7 +36,6 @@ require (
|
|||
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
|
|
|
|||
59
go.sum
59
go.sum
|
|
@ -6,6 +6,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
|
|||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/aarondl/opt v0.0.0-20250607033636-982744e1bd65 h1:lbdPe4LBNmNDzeQFwNhEc88w90841qv737MI4+aXSYU=
|
||||
github.com/aarondl/opt v0.0.0-20250607033636-982744e1bd65/go.mod h1:+xKBXrTAUOvrDXO5PRwIr4E1wciHY3Glgl+6OkCXknU=
|
||||
github.com/alexedwards/scs/pgxstore v0.0.0-20251002162104-209de6e426de h1:wNJVpr0ag/BL2nRGBIESdLe1qoljXIolF/qPi1gleRA=
|
||||
github.com/alexedwards/scs/pgxstore v0.0.0-20251002162104-209de6e426de/go.mod h1:hwveArYcjyOK66EViVgVU5Iqj7zyEsWjKXMQhDJrTLI=
|
||||
github.com/alexedwards/scs/v2 v2.9.0 h1:xa05mVpwTBm1iLeTMNFfAWpKUm4fXAW7CeAViqBVS90=
|
||||
github.com/alexedwards/scs/v2 v2.9.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
|
|
@ -20,6 +22,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS
|
|||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
|
@ -65,16 +68,25 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jaswdr/faker/v2 v2.8.1 h1:2AcPgHDBXYQregFUH9LgVZKfFupc4SIquYhp29sf5wQ=
|
||||
github.com/jaswdr/faker/v2 v2.8.1/go.mod h1:jZq+qzNQr8/P+5fHd9t3txe2GNPnthrTfohtnJ7B+68=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
|
|
@ -123,6 +135,7 @@ github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 h1:wSmWgpuccqS2IOfmY
|
|||
github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494/go.mod h1:yipyliwI08eQ6XwDm1fEwKPdF/xdbkiHtrU+1Hg+vc4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
||||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||
github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
|
||||
|
|
@ -136,9 +149,14 @@ github.com/stephenafamo/fakedb v0.0.0-20221230081958-0b86f816ed97/go.mod h1:bM3V
|
|||
github.com/stephenafamo/scan v0.7.0 h1:lfFiD9H5+n4AdK3qNzXQjj2M3NfTOpmWBIA39NwB94c=
|
||||
github.com/stephenafamo/scan v0.7.0/go.mod h1:FhIUJ8pLNyex36xGFiazDJJ5Xry0UkAi+RkWRrEcRMg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw=
|
||||
|
|
@ -157,6 +175,7 @@ github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8S
|
|||
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
|
|
@ -173,22 +192,62 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
|||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
|
|
|||
38
html.go
38
html.go
|
|
@ -2,15 +2,17 @@ package main
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/models"
|
||||
)
|
||||
|
||||
var (
|
||||
dashboard = newBuiltTemplate("dashboard", "base")
|
||||
root = newBuiltTemplate("root", "base")
|
||||
signin = newBuiltTemplate("signin", "base")
|
||||
signup = newBuiltTemplate("signup", "base")
|
||||
)
|
||||
|
||||
|
|
@ -27,18 +29,20 @@ type ContentDashboard struct {
|
|||
BabbleLinks []Link
|
||||
Username string
|
||||
}
|
||||
type ContentRoot struct {
|
||||
BabbleLinks []Link
|
||||
}
|
||||
type ContentSignup struct {
|
||||
type ContentSignin struct {
|
||||
InvalidCredentials bool
|
||||
}
|
||||
type ContentSignup struct { }
|
||||
|
||||
func (bt *BuiltTemplate) ExecuteTemplate(w io.Writer, data any) error {
|
||||
name := bt.files[0] + ".html"
|
||||
if bt.template == nil {
|
||||
templ := parseFromDisk(bt.files)
|
||||
templ, err := parseFromDisk(bt.files)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse template file: %v", err)
|
||||
}
|
||||
if templ == nil {
|
||||
w.Write([]byte("Failed to read from disk"))
|
||||
w.Write([]byte("Failed to read from disk: "))
|
||||
return errors.New("Template parsing failed")
|
||||
}
|
||||
return templ.ExecuteTemplate(w, name, data)
|
||||
|
|
@ -47,17 +51,18 @@ func (bt *BuiltTemplate) ExecuteTemplate(w io.Writer, data any) error {
|
|||
}
|
||||
}
|
||||
|
||||
func htmlDashboard(w io.Writer, path string, username string) error {
|
||||
func htmlDashboard(w io.Writer, user *models.User) error {
|
||||
data := ContentDashboard{
|
||||
Username: username,
|
||||
Username: user.Username,
|
||||
}
|
||||
return dashboard.ExecuteTemplate(w, data)
|
||||
}
|
||||
|
||||
func htmlRoot(w io.Writer, path string) error {
|
||||
data := ContentRoot{
|
||||
func htmlSignin(w io.Writer, errorCode string) error {
|
||||
data := ContentSignin{
|
||||
InvalidCredentials: errorCode == "invalid-credentials",
|
||||
}
|
||||
return root.ExecuteTemplate(w, data)
|
||||
return signin.ExecuteTemplate(w, data)
|
||||
}
|
||||
|
||||
func htmlSignup(w io.Writer, path string) error {
|
||||
|
|
@ -96,7 +101,7 @@ func parseEmbedded(files []string) *template.Template {
|
|||
return nil
|
||||
}
|
||||
|
||||
func parseFromDisk(files []string) *template.Template {
|
||||
func parseFromDisk(files []string) (*template.Template, error) {
|
||||
funcMap := makeFuncMap()
|
||||
paths := make([]string, 0)
|
||||
for _, f := range files {
|
||||
|
|
@ -105,8 +110,7 @@ func parseFromDisk(files []string) *template.Template {
|
|||
name := files[0] + ".html"
|
||||
templ, err := template.New(name).Funcs(funcMap).ParseFiles(paths...)
|
||||
if err != nil {
|
||||
log.Println("TEMPLATE FAILED", err)
|
||||
return nil
|
||||
return nil, fmt.Errorf("Failed to parse %s: %v", paths, err)
|
||||
}
|
||||
return templ
|
||||
return templ, nil
|
||||
}
|
||||
|
|
|
|||
3
main.go
3
main.go
|
|
@ -7,6 +7,7 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/alexedwards/scs/pgxstore"
|
||||
"github.com/alexedwards/scs/v2"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
|
|
@ -49,6 +50,7 @@ func main() {
|
|||
os.Exit(2)
|
||||
}
|
||||
sessionManager = scs.New()
|
||||
sessionManager.Store = pgxstore.New(PGInstance.PGXPool)
|
||||
sessionManager.Lifetime = 24 * time.Hour
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
|
@ -56,6 +58,7 @@ func main() {
|
|||
r.Use(sessionManager.LoadAndSave)
|
||||
|
||||
r.Get("/", getRoot)
|
||||
r.Post("/signin", postSignin)
|
||||
r.Get("/signup", getSignup)
|
||||
r.Post("/signup", postSignup)
|
||||
r.Get("/favicon.ico", getFavicon)
|
||||
|
|
|
|||
12
migrations/00003_add_session.sql
Normal file
12
migrations/00003_add_session.sql
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
-- +goose Up
|
||||
CREATE TABLE sessions (
|
||||
token TEXT PRIMARY KEY,
|
||||
data BYTEA NOT NULL,
|
||||
expiry TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX sessions_expiry_idx ON sessions (expiry);
|
||||
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE sessions;
|
||||
|
|
@ -20,6 +20,9 @@ var _ bob.HookableType = &GooseDBVersion{}
|
|||
// Make sure the type Organization runs hooks after queries
|
||||
var _ bob.HookableType = &Organization{}
|
||||
|
||||
// Make sure the type Session runs hooks after queries
|
||||
var _ bob.HookableType = &Session{}
|
||||
|
||||
// Make sure the type User runs hooks after queries
|
||||
var _ bob.HookableType = &User{}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,15 +19,18 @@ var (
|
|||
func Where[Q psql.Filterable]() struct {
|
||||
GooseDBVersions gooseDBVersionWhere[Q]
|
||||
Organizations organizationWhere[Q]
|
||||
Sessions sessionWhere[Q]
|
||||
Users userWhere[Q]
|
||||
} {
|
||||
return struct {
|
||||
GooseDBVersions gooseDBVersionWhere[Q]
|
||||
Organizations organizationWhere[Q]
|
||||
Sessions sessionWhere[Q]
|
||||
Users userWhere[Q]
|
||||
}{
|
||||
GooseDBVersions: buildGooseDBVersionWhere[Q](GooseDBVersions.Columns),
|
||||
Organizations: buildOrganizationWhere[Q](Organizations.Columns),
|
||||
Sessions: buildSessionWhere[Q](Sessions.Columns),
|
||||
Users: buildUserWhere[Q](Users.Columns),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
399
models/sessions.bob.go
Normal file
399
models/sessions.bob.go
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
// Code generated by BobGen psql v0.41.1. DO NOT EDIT.
|
||||
// This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/stephenafamo/bob"
|
||||
"github.com/stephenafamo/bob/dialect/psql"
|
||||
"github.com/stephenafamo/bob/dialect/psql/dialect"
|
||||
"github.com/stephenafamo/bob/dialect/psql/dm"
|
||||
"github.com/stephenafamo/bob/dialect/psql/sm"
|
||||
"github.com/stephenafamo/bob/dialect/psql/um"
|
||||
"github.com/stephenafamo/bob/expr"
|
||||
)
|
||||
|
||||
// Session is an object representing the database table.
|
||||
type Session struct {
|
||||
Token string `db:"token,pk" `
|
||||
Data []byte `db:"data" `
|
||||
Expiry time.Time `db:"expiry" `
|
||||
}
|
||||
|
||||
// SessionSlice is an alias for a slice of pointers to Session.
|
||||
// This should almost always be used instead of []*Session.
|
||||
type SessionSlice []*Session
|
||||
|
||||
// Sessions contains methods to work with the sessions table
|
||||
var Sessions = psql.NewTablex[*Session, SessionSlice, *SessionSetter]("", "sessions", buildSessionColumns("sessions"))
|
||||
|
||||
// SessionsQuery is a query on the sessions table
|
||||
type SessionsQuery = *psql.ViewQuery[*Session, SessionSlice]
|
||||
|
||||
func buildSessionColumns(alias string) sessionColumns {
|
||||
return sessionColumns{
|
||||
ColumnsExpr: expr.NewColumnsExpr(
|
||||
"token", "data", "expiry",
|
||||
).WithParent("sessions"),
|
||||
tableAlias: alias,
|
||||
Token: psql.Quote(alias, "token"),
|
||||
Data: psql.Quote(alias, "data"),
|
||||
Expiry: psql.Quote(alias, "expiry"),
|
||||
}
|
||||
}
|
||||
|
||||
type sessionColumns struct {
|
||||
expr.ColumnsExpr
|
||||
tableAlias string
|
||||
Token psql.Expression
|
||||
Data psql.Expression
|
||||
Expiry psql.Expression
|
||||
}
|
||||
|
||||
func (c sessionColumns) Alias() string {
|
||||
return c.tableAlias
|
||||
}
|
||||
|
||||
func (sessionColumns) AliasedAs(alias string) sessionColumns {
|
||||
return buildSessionColumns(alias)
|
||||
}
|
||||
|
||||
// SessionSetter is used for insert/upsert/update operations
|
||||
// All values are optional, and do not have to be set
|
||||
// Generated columns are not included
|
||||
type SessionSetter struct {
|
||||
Token omit.Val[string] `db:"token,pk" `
|
||||
Data omit.Val[[]byte] `db:"data" `
|
||||
Expiry omit.Val[time.Time] `db:"expiry" `
|
||||
}
|
||||
|
||||
func (s SessionSetter) SetColumns() []string {
|
||||
vals := make([]string, 0, 3)
|
||||
if s.Token.IsValue() {
|
||||
vals = append(vals, "token")
|
||||
}
|
||||
if s.Data.IsValue() {
|
||||
vals = append(vals, "data")
|
||||
}
|
||||
if s.Expiry.IsValue() {
|
||||
vals = append(vals, "expiry")
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
func (s SessionSetter) Overwrite(t *Session) {
|
||||
if s.Token.IsValue() {
|
||||
t.Token = s.Token.MustGet()
|
||||
}
|
||||
if s.Data.IsValue() {
|
||||
t.Data = s.Data.MustGet()
|
||||
}
|
||||
if s.Expiry.IsValue() {
|
||||
t.Expiry = s.Expiry.MustGet()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SessionSetter) Apply(q *dialect.InsertQuery) {
|
||||
q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) {
|
||||
return Sessions.BeforeInsertHooks.RunHooks(ctx, exec, s)
|
||||
})
|
||||
|
||||
q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
|
||||
vals := make([]bob.Expression, 3)
|
||||
if s.Token.IsValue() {
|
||||
vals[0] = psql.Arg(s.Token.MustGet())
|
||||
} else {
|
||||
vals[0] = psql.Raw("DEFAULT")
|
||||
}
|
||||
|
||||
if s.Data.IsValue() {
|
||||
vals[1] = psql.Arg(s.Data.MustGet())
|
||||
} else {
|
||||
vals[1] = psql.Raw("DEFAULT")
|
||||
}
|
||||
|
||||
if s.Expiry.IsValue() {
|
||||
vals[2] = psql.Arg(s.Expiry.MustGet())
|
||||
} else {
|
||||
vals[2] = psql.Raw("DEFAULT")
|
||||
}
|
||||
|
||||
return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "")
|
||||
}))
|
||||
}
|
||||
|
||||
func (s SessionSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] {
|
||||
return um.Set(s.Expressions()...)
|
||||
}
|
||||
|
||||
func (s SessionSetter) Expressions(prefix ...string) []bob.Expression {
|
||||
exprs := make([]bob.Expression, 0, 3)
|
||||
|
||||
if s.Token.IsValue() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
psql.Quote(append(prefix, "token")...),
|
||||
psql.Arg(s.Token),
|
||||
}})
|
||||
}
|
||||
|
||||
if s.Data.IsValue() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
psql.Quote(append(prefix, "data")...),
|
||||
psql.Arg(s.Data),
|
||||
}})
|
||||
}
|
||||
|
||||
if s.Expiry.IsValue() {
|
||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||
psql.Quote(append(prefix, "expiry")...),
|
||||
psql.Arg(s.Expiry),
|
||||
}})
|
||||
}
|
||||
|
||||
return exprs
|
||||
}
|
||||
|
||||
// FindSession retrieves a single record by primary key
|
||||
// If cols is empty Find will return all columns.
|
||||
func FindSession(ctx context.Context, exec bob.Executor, TokenPK string, cols ...string) (*Session, error) {
|
||||
if len(cols) == 0 {
|
||||
return Sessions.Query(
|
||||
sm.Where(Sessions.Columns.Token.EQ(psql.Arg(TokenPK))),
|
||||
).One(ctx, exec)
|
||||
}
|
||||
|
||||
return Sessions.Query(
|
||||
sm.Where(Sessions.Columns.Token.EQ(psql.Arg(TokenPK))),
|
||||
sm.Columns(Sessions.Columns.Only(cols...)),
|
||||
).One(ctx, exec)
|
||||
}
|
||||
|
||||
// SessionExists checks the presence of a single record by primary key
|
||||
func SessionExists(ctx context.Context, exec bob.Executor, TokenPK string) (bool, error) {
|
||||
return Sessions.Query(
|
||||
sm.Where(Sessions.Columns.Token.EQ(psql.Arg(TokenPK))),
|
||||
).Exists(ctx, exec)
|
||||
}
|
||||
|
||||
// AfterQueryHook is called after Session is retrieved from the database
|
||||
func (o *Session) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error {
|
||||
var err error
|
||||
|
||||
switch queryType {
|
||||
case bob.QueryTypeSelect:
|
||||
ctx, err = Sessions.AfterSelectHooks.RunHooks(ctx, exec, SessionSlice{o})
|
||||
case bob.QueryTypeInsert:
|
||||
ctx, err = Sessions.AfterInsertHooks.RunHooks(ctx, exec, SessionSlice{o})
|
||||
case bob.QueryTypeUpdate:
|
||||
ctx, err = Sessions.AfterUpdateHooks.RunHooks(ctx, exec, SessionSlice{o})
|
||||
case bob.QueryTypeDelete:
|
||||
ctx, err = Sessions.AfterDeleteHooks.RunHooks(ctx, exec, SessionSlice{o})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// primaryKeyVals returns the primary key values of the Session
|
||||
func (o *Session) primaryKeyVals() bob.Expression {
|
||||
return psql.Arg(o.Token)
|
||||
}
|
||||
|
||||
func (o *Session) pkEQ() dialect.Expression {
|
||||
return psql.Quote("sessions", "token").EQ(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
|
||||
return o.primaryKeyVals().WriteSQL(ctx, w, d, start)
|
||||
}))
|
||||
}
|
||||
|
||||
// Update uses an executor to update the Session
|
||||
func (o *Session) Update(ctx context.Context, exec bob.Executor, s *SessionSetter) error {
|
||||
v, err := Sessions.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*o = *v
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a single Session record with an executor
|
||||
func (o *Session) Delete(ctx context.Context, exec bob.Executor) error {
|
||||
_, err := Sessions.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec)
|
||||
return err
|
||||
}
|
||||
|
||||
// Reload refreshes the Session using the executor
|
||||
func (o *Session) Reload(ctx context.Context, exec bob.Executor) error {
|
||||
o2, err := Sessions.Query(
|
||||
sm.Where(Sessions.Columns.Token.EQ(psql.Arg(o.Token))),
|
||||
).One(ctx, exec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*o = *o2
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AfterQueryHook is called after SessionSlice is retrieved from the database
|
||||
func (o SessionSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error {
|
||||
var err error
|
||||
|
||||
switch queryType {
|
||||
case bob.QueryTypeSelect:
|
||||
ctx, err = Sessions.AfterSelectHooks.RunHooks(ctx, exec, o)
|
||||
case bob.QueryTypeInsert:
|
||||
ctx, err = Sessions.AfterInsertHooks.RunHooks(ctx, exec, o)
|
||||
case bob.QueryTypeUpdate:
|
||||
ctx, err = Sessions.AfterUpdateHooks.RunHooks(ctx, exec, o)
|
||||
case bob.QueryTypeDelete:
|
||||
ctx, err = Sessions.AfterDeleteHooks.RunHooks(ctx, exec, o)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (o SessionSlice) pkIN() dialect.Expression {
|
||||
if len(o) == 0 {
|
||||
return psql.Raw("NULL")
|
||||
}
|
||||
|
||||
return psql.Quote("sessions", "token").In(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
|
||||
pkPairs := make([]bob.Expression, len(o))
|
||||
for i, row := range o {
|
||||
pkPairs[i] = row.primaryKeyVals()
|
||||
}
|
||||
return bob.ExpressSlice(ctx, w, d, start, pkPairs, "", ", ", "")
|
||||
}))
|
||||
}
|
||||
|
||||
// copyMatchingRows finds models in the given slice that have the same primary key
|
||||
// then it first copies the existing relationships from the old model to the new model
|
||||
// and then replaces the old model in the slice with the new model
|
||||
func (o SessionSlice) copyMatchingRows(from ...*Session) {
|
||||
for i, old := range o {
|
||||
for _, new := range from {
|
||||
if new.Token != old.Token {
|
||||
continue
|
||||
}
|
||||
|
||||
o[i] = new
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateMod modifies an update query with "WHERE primary_key IN (o...)"
|
||||
func (o SessionSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] {
|
||||
return bob.ModFunc[*dialect.UpdateQuery](func(q *dialect.UpdateQuery) {
|
||||
q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) {
|
||||
return Sessions.BeforeUpdateHooks.RunHooks(ctx, exec, o)
|
||||
})
|
||||
|
||||
q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error {
|
||||
var err error
|
||||
switch retrieved := retrieved.(type) {
|
||||
case *Session:
|
||||
o.copyMatchingRows(retrieved)
|
||||
case []*Session:
|
||||
o.copyMatchingRows(retrieved...)
|
||||
case SessionSlice:
|
||||
o.copyMatchingRows(retrieved...)
|
||||
default:
|
||||
// If the retrieved value is not a Session or a slice of Session
|
||||
// then run the AfterUpdateHooks on the slice
|
||||
_, err = Sessions.AfterUpdateHooks.RunHooks(ctx, exec, o)
|
||||
}
|
||||
|
||||
return err
|
||||
}))
|
||||
|
||||
q.AppendWhere(o.pkIN())
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)"
|
||||
func (o SessionSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] {
|
||||
return bob.ModFunc[*dialect.DeleteQuery](func(q *dialect.DeleteQuery) {
|
||||
q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) {
|
||||
return Sessions.BeforeDeleteHooks.RunHooks(ctx, exec, o)
|
||||
})
|
||||
|
||||
q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error {
|
||||
var err error
|
||||
switch retrieved := retrieved.(type) {
|
||||
case *Session:
|
||||
o.copyMatchingRows(retrieved)
|
||||
case []*Session:
|
||||
o.copyMatchingRows(retrieved...)
|
||||
case SessionSlice:
|
||||
o.copyMatchingRows(retrieved...)
|
||||
default:
|
||||
// If the retrieved value is not a Session or a slice of Session
|
||||
// then run the AfterDeleteHooks on the slice
|
||||
_, err = Sessions.AfterDeleteHooks.RunHooks(ctx, exec, o)
|
||||
}
|
||||
|
||||
return err
|
||||
}))
|
||||
|
||||
q.AppendWhere(o.pkIN())
|
||||
})
|
||||
}
|
||||
|
||||
func (o SessionSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals SessionSetter) error {
|
||||
if len(o) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := Sessions.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec)
|
||||
return err
|
||||
}
|
||||
|
||||
func (o SessionSlice) DeleteAll(ctx context.Context, exec bob.Executor) error {
|
||||
if len(o) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := Sessions.Delete(o.DeleteMod()).Exec(ctx, exec)
|
||||
return err
|
||||
}
|
||||
|
||||
func (o SessionSlice) ReloadAll(ctx context.Context, exec bob.Executor) error {
|
||||
if len(o) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
o2, err := Sessions.Query(sm.Where(o.pkIN())).All(ctx, exec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.copyMatchingRows(o2...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type sessionWhere[Q psql.Filterable] struct {
|
||||
Token psql.WhereMod[Q, string]
|
||||
Data psql.WhereMod[Q, []byte]
|
||||
Expiry psql.WhereMod[Q, time.Time]
|
||||
}
|
||||
|
||||
func (sessionWhere[Q]) AliasedAs(alias string) sessionWhere[Q] {
|
||||
return buildSessionWhere[Q](buildSessionColumns(alias))
|
||||
}
|
||||
|
||||
func buildSessionWhere[Q psql.Filterable](cols sessionColumns) sessionWhere[Q] {
|
||||
return sessionWhere[Q]{
|
||||
Token: psql.Where[Q, string](cols.Token),
|
||||
Data: psql.Where[Q, []byte](cols.Data),
|
||||
Expiry: psql.Where[Q, time.Time](cols.Expiry),
|
||||
}
|
||||
}
|
||||
84
sql/test_utils.bob_test.go
Normal file
84
sql/test_utils.bob_test.go
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// Code generated by BobGen psql v0.41.1. DO NOT EDIT.
|
||||
// This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
package sql
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
enums "github.com/Gleipnir-Technology/nidus-sync/enums"
|
||||
"github.com/jaswdr/faker/v2"
|
||||
"github.com/stephenafamo/bob"
|
||||
pg_query "github.com/wasilibs/go-pgquery"
|
||||
)
|
||||
|
||||
// Set the testDB to enable tests that use the database
|
||||
var testDB bob.Transactor[bob.Tx]
|
||||
|
||||
func formatQuery(s string) (string, error) {
|
||||
aTree, err := pg_query.Parse(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return pg_query.Deparse(aTree)
|
||||
}
|
||||
|
||||
var defaultFaker = faker.New()
|
||||
|
||||
func random_enums_ArcgisLicenseType(f *faker.Faker, limits ...string) enums.ArcgisLicenseType {
|
||||
if f == nil {
|
||||
f = &defaultFaker
|
||||
}
|
||||
|
||||
var e enums.ArcgisLicenseType
|
||||
all := e.All()
|
||||
return all[f.IntBetween(0, len(all)-1)]
|
||||
}
|
||||
|
||||
func random_enums_Hashtype(f *faker.Faker, limits ...string) enums.Hashtype {
|
||||
if f == nil {
|
||||
f = &defaultFaker
|
||||
}
|
||||
|
||||
var e enums.Hashtype
|
||||
all := e.All()
|
||||
return all[f.IntBetween(0, len(all)-1)]
|
||||
}
|
||||
|
||||
func random_int32(f *faker.Faker, limits ...string) int32 {
|
||||
if f == nil {
|
||||
f = &defaultFaker
|
||||
}
|
||||
|
||||
return f.Int32()
|
||||
}
|
||||
|
||||
func random_string(f *faker.Faker, limits ...string) string {
|
||||
if f == nil {
|
||||
f = &defaultFaker
|
||||
}
|
||||
|
||||
val := strings.Join(f.Lorem().Words(f.IntBetween(1, 5)), " ")
|
||||
if len(limits) == 0 {
|
||||
return val
|
||||
}
|
||||
limitInt, _ := strconv.Atoi(limits[0])
|
||||
if limitInt > 0 && limitInt < len(val) {
|
||||
val = val[:limitInt]
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func random_time_Time(f *faker.Faker, limits ...string) time.Time {
|
||||
if f == nil {
|
||||
f = &defaultFaker
|
||||
}
|
||||
|
||||
year := time.Hour * 24 * 365
|
||||
min := time.Now().Add(-year)
|
||||
max := time.Now().Add(year)
|
||||
return f.Time().TimeBetween(min, max)
|
||||
}
|
||||
116
sql/user_by_username.bob.go
Normal file
116
sql/user_by_username.bob.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// Code generated by BobGen psql v0.41.1. DO NOT EDIT.
|
||||
// This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"io"
|
||||
"iter"
|
||||
"time"
|
||||
|
||||
enums "github.com/Gleipnir-Technology/nidus-sync/enums"
|
||||
"github.com/aarondl/opt/null"
|
||||
"github.com/stephenafamo/bob"
|
||||
"github.com/stephenafamo/bob/dialect/psql"
|
||||
"github.com/stephenafamo/bob/dialect/psql/dialect"
|
||||
"github.com/stephenafamo/bob/orm"
|
||||
"github.com/stephenafamo/scan"
|
||||
)
|
||||
|
||||
//go:embed user_by_username.bob.sql
|
||||
var formattedQueries_user_by_username string
|
||||
|
||||
var userByUsernameSQL = formattedQueries_user_by_username[152:773]
|
||||
|
||||
type UserByUsernameQuery = orm.ModQuery[*dialect.SelectQuery, userByUsername, UserByUsernameRow, []UserByUsernameRow, userByUsernameTransformer]
|
||||
|
||||
func UserByUsername(Username string) *UserByUsernameQuery {
|
||||
var expressionTypArgs userByUsername
|
||||
|
||||
expressionTypArgs.Username = psql.Arg(Username)
|
||||
|
||||
return &UserByUsernameQuery{
|
||||
Query: orm.Query[userByUsername, UserByUsernameRow, []UserByUsernameRow, userByUsernameTransformer]{
|
||||
ExecQuery: orm.ExecQuery[userByUsername]{
|
||||
BaseQuery: bob.BaseQuery[userByUsername]{
|
||||
Expression: expressionTypArgs,
|
||||
Dialect: dialect.Dialect,
|
||||
QueryType: bob.QueryTypeSelect,
|
||||
},
|
||||
},
|
||||
Scanner: func(context.Context, []string) (func(*scan.Row) (any, error), func(any) (UserByUsernameRow, error)) {
|
||||
return func(row *scan.Row) (any, error) {
|
||||
var t UserByUsernameRow
|
||||
row.ScheduleScanByIndex(0, &t.ID)
|
||||
row.ScheduleScanByIndex(1, &t.ArcgisAccessToken)
|
||||
row.ScheduleScanByIndex(2, &t.ArcgisLicense)
|
||||
row.ScheduleScanByIndex(3, &t.ArcgisRefreshToken)
|
||||
row.ScheduleScanByIndex(4, &t.ArcgisRefreshTokenExpires)
|
||||
row.ScheduleScanByIndex(5, &t.ArcgisRole)
|
||||
row.ScheduleScanByIndex(6, &t.DisplayName)
|
||||
row.ScheduleScanByIndex(7, &t.Email)
|
||||
row.ScheduleScanByIndex(8, &t.OrganizationID)
|
||||
row.ScheduleScanByIndex(9, &t.Username)
|
||||
row.ScheduleScanByIndex(10, &t.PasswordHashType)
|
||||
row.ScheduleScanByIndex(11, &t.PasswordHash)
|
||||
return &t, nil
|
||||
}, func(v any) (UserByUsernameRow, error) {
|
||||
return *(v.(*UserByUsernameRow)), nil
|
||||
}
|
||||
},
|
||||
},
|
||||
Mod: bob.ModFunc[*dialect.SelectQuery](func(q *dialect.SelectQuery) {
|
||||
q.AppendSelect(expressionTypArgs.subExpr(7, 551))
|
||||
q.SetTable(expressionTypArgs.subExpr(557, 562))
|
||||
q.AppendWhere(expressionTypArgs.subExpr(570, 621))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
type UserByUsernameRow = struct {
|
||||
ID int32 `db:"id"`
|
||||
ArcgisAccessToken null.Val[string] `db:"arcgis_access_token"`
|
||||
ArcgisLicense null.Val[enums.ArcgisLicenseType] `db:"arcgis_license"`
|
||||
ArcgisRefreshToken null.Val[string] `db:"arcgis_refresh_token"`
|
||||
ArcgisRefreshTokenExpires null.Val[time.Time] `db:"arcgis_refresh_token_expires"`
|
||||
ArcgisRole null.Val[string] `db:"arcgis_role"`
|
||||
DisplayName null.Val[string] `db:"display_name"`
|
||||
Email null.Val[string] `db:"email"`
|
||||
OrganizationID null.Val[int32] `db:"organization_id"`
|
||||
Username string `db:"username"`
|
||||
PasswordHashType null.Val[enums.Hashtype] `db:"password_hash_type"`
|
||||
PasswordHash null.Val[string] `db:"password_hash"`
|
||||
}
|
||||
|
||||
type userByUsernameTransformer = bob.SliceTransformer[UserByUsernameRow, []UserByUsernameRow]
|
||||
|
||||
type userByUsername struct {
|
||||
Username bob.Expression
|
||||
}
|
||||
|
||||
func (o userByUsername) args() iter.Seq[orm.ArgWithPosition] {
|
||||
return func(yield func(arg orm.ArgWithPosition) bool) {
|
||||
if !yield(orm.ArgWithPosition{
|
||||
Name: "username",
|
||||
Start: 581,
|
||||
Stop: 583,
|
||||
Expression: o.Username,
|
||||
}) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o userByUsername) raw(from, to int) string {
|
||||
return userByUsernameSQL[from:to]
|
||||
}
|
||||
|
||||
func (o userByUsername) subExpr(from, to int) bob.Expression {
|
||||
return orm.ArgsToExpression(userByUsernameSQL, from, to, o.args())
|
||||
}
|
||||
|
||||
func (o userByUsername) WriteSQL(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
|
||||
return o.subExpr(0, len(userByUsernameSQL)).WriteSQL(ctx, w, d, start)
|
||||
}
|
||||
7
sql/user_by_username.bob.sql
Normal file
7
sql/user_by_username.bob.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
-- Code generated by BobGen psql v0.41.1. DO NOT EDIT.
|
||||
-- This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
-- UserByUsername
|
||||
SELECT "user_"."id" AS "id", "user_"."arcgis_access_token" AS "arcgis_access_token", "user_"."arcgis_license" AS "arcgis_license", "user_"."arcgis_refresh_token" AS "arcgis_refresh_token", "user_"."arcgis_refresh_token_expires" AS "arcgis_refresh_token_expires", "user_"."arcgis_role" AS "arcgis_role", "user_"."display_name" AS "display_name", "user_"."email" AS "email", "user_"."organization_id" AS "organization_id", "user_"."username" AS "username", "user_"."password_hash_type" AS "password_hash_type", "user_"."password_hash" AS "password_hash" FROM user_ WHERE
|
||||
username = $1 AND
|
||||
password_hash_type = 'bcrypt-14';
|
||||
139
sql/user_by_username.bob_test.go
Normal file
139
sql/user_by_username.bob_test.go
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
// Code generated by BobGen psql v0.41.1. DO NOT EDIT.
|
||||
// This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stephenafamo/bob"
|
||||
"github.com/stephenafamo/bob/dialect/psql"
|
||||
testutils "github.com/stephenafamo/bob/test/utils"
|
||||
)
|
||||
|
||||
func TestUserByUsername(t *testing.T) {
|
||||
t.Run("Base", func(t *testing.T) {
|
||||
var sb strings.Builder
|
||||
|
||||
query := UserByUsername(random_string(nil))
|
||||
|
||||
if _, err := query.WriteQuery(t.Context(), &sb, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(userByUsernameSQL, sb.String()); diff != "" {
|
||||
t.Fatalf("unexpected result (-got +want):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Mod", func(t *testing.T) {
|
||||
var sb strings.Builder
|
||||
|
||||
query := UserByUsername(random_string(nil))
|
||||
|
||||
if _, err := psql.Select(query).WriteQuery(t.Context(), &sb, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
queryDiff, err := testutils.QueryDiff(userByUsernameSQL, sb.String(), formatQuery)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if queryDiff != "" {
|
||||
fmt.Println(sb.String())
|
||||
t.Fatalf("unexpected result (-got +want):\n%s", queryDiff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Scanning", func(t *testing.T) {
|
||||
if testDB == nil {
|
||||
t.Skip("skipping test, no DSN provided")
|
||||
}
|
||||
|
||||
ctxTx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
|
||||
tx, err := testDB.Begin(ctxTx)
|
||||
if err != nil {
|
||||
t.Fatalf("Error starting transaction: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := tx.Rollback(ctxTx); err != nil {
|
||||
t.Fatalf("Error rolling back transaction: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
query, args, err := bob.Build(ctxTx, psql.Select(UserByUsername(random_string(nil))))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctxTx, query, args...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(columns) != 12 {
|
||||
t.Fatalf("expected %d columns, got %d", 12, len(columns))
|
||||
}
|
||||
|
||||
if columns[0] != "id" {
|
||||
t.Fatalf("expected column %d to be %s, got %s", 0, "id", columns[0])
|
||||
}
|
||||
|
||||
if columns[1] != "arcgis_access_token" {
|
||||
t.Fatalf("expected column %d to be %s, got %s", 1, "arcgis_access_token", columns[1])
|
||||
}
|
||||
|
||||
if columns[2] != "arcgis_license" {
|
||||
t.Fatalf("expected column %d to be %s, got %s", 2, "arcgis_license", columns[2])
|
||||
}
|
||||
|
||||
if columns[3] != "arcgis_refresh_token" {
|
||||
t.Fatalf("expected column %d to be %s, got %s", 3, "arcgis_refresh_token", columns[3])
|
||||
}
|
||||
|
||||
if columns[4] != "arcgis_refresh_token_expires" {
|
||||
t.Fatalf("expected column %d to be %s, got %s", 4, "arcgis_refresh_token_expires", columns[4])
|
||||
}
|
||||
|
||||
if columns[5] != "arcgis_role" {
|
||||
t.Fatalf("expected column %d to be %s, got %s", 5, "arcgis_role", columns[5])
|
||||
}
|
||||
|
||||
if columns[6] != "display_name" {
|
||||
t.Fatalf("expected column %d to be %s, got %s", 6, "display_name", columns[6])
|
||||
}
|
||||
|
||||
if columns[7] != "email" {
|
||||
t.Fatalf("expected column %d to be %s, got %s", 7, "email", columns[7])
|
||||
}
|
||||
|
||||
if columns[8] != "organization_id" {
|
||||
t.Fatalf("expected column %d to be %s, got %s", 8, "organization_id", columns[8])
|
||||
}
|
||||
|
||||
if columns[9] != "username" {
|
||||
t.Fatalf("expected column %d to be %s, got %s", 9, "username", columns[9])
|
||||
}
|
||||
|
||||
if columns[10] != "password_hash_type" {
|
||||
t.Fatalf("expected column %d to be %s, got %s", 10, "password_hash_type", columns[10])
|
||||
}
|
||||
|
||||
if columns[11] != "password_hash" {
|
||||
t.Fatalf("expected column %d to be %s, got %s", 11, "password_hash", columns[11])
|
||||
}
|
||||
})
|
||||
}
|
||||
4
sql/user_by_username.sql
Normal file
4
sql/user_by_username.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
-- UserByUsername
|
||||
SELECT * FROM user_ WHERE
|
||||
username = $1 AND
|
||||
password_hash_type = 'bcrypt-14';
|
||||
9
templates/dashboard.html
Normal file
9
templates/dashboard.html
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "style"}}
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<h1>Hey {{ .Username }}</h1>
|
||||
<p>At this point, pretend I'm showing you the result of some ArcGIS data.</p>
|
||||
{{end}}
|
||||
|
|
@ -33,22 +33,23 @@
|
|||
<p class="text-muted">Please enter your credentials</p>
|
||||
</div>
|
||||
|
||||
<form>
|
||||
<form method="POST" action="/signin">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" required>
|
||||
<input type="text" class="form-control" name="username" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" required>
|
||||
<input type="password" class="form-control" name="password" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="remember">
|
||||
<label class="form-check-label" for="remember">Remember me</label>
|
||||
{{ if .InvalidCredentials }}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
The credentials you provided weren't recognized.
|
||||
</div>
|
||||
|
||||
{{ end }}
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue