Refactor SQL queries and schema to use OIDC subject instead of Keycloak ID for user identification

This commit is contained in:
Christian Galo 2025-06-01 18:10:17 -05:00
parent d2162067ab
commit 77b2e6c24e
11 changed files with 539 additions and 19 deletions

31
internal/db/db.go Normal file
View File

@ -0,0 +1,31 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
package db
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,
}
}

39
internal/db/models.go Normal file
View File

@ -0,0 +1,39 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
package db
import (
"database/sql"
"time"
)
type Payment struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
Amount int64 `json:"amount"`
Currency string `json:"currency"`
Status string `json:"status"`
PaymentProcessorID sql.NullString `json:"payment_processor_id"`
PaidAt sql.NullTime `json:"paid_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type Site struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
Domain string `json:"domain"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type User struct {
ID int64 `json:"id"`
OidcSubject string `json:"oidc_subject"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

147
internal/db/payments.sql.go Normal file
View File

@ -0,0 +1,147 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: payments.sql
package db
import (
"context"
"database/sql"
)
const createPayment = `-- name: CreatePayment :one
INSERT INTO payments (user_id, amount, currency, status, payment_processor_id, paid_at)
VALUES (?, ?, ?, ?, ?, ?)
RETURNING id, user_id, amount, currency, status, payment_processor_id, paid_at, created_at, updated_at
`
type CreatePaymentParams struct {
UserID int64 `json:"user_id"`
Amount int64 `json:"amount"`
Currency string `json:"currency"`
Status string `json:"status"`
PaymentProcessorID sql.NullString `json:"payment_processor_id"`
PaidAt sql.NullTime `json:"paid_at"`
}
func (q *Queries) CreatePayment(ctx context.Context, arg CreatePaymentParams) (Payment, error) {
row := q.db.QueryRowContext(ctx, createPayment,
arg.UserID,
arg.Amount,
arg.Currency,
arg.Status,
arg.PaymentProcessorID,
arg.PaidAt,
)
var i Payment
err := row.Scan(
&i.ID,
&i.UserID,
&i.Amount,
&i.Currency,
&i.Status,
&i.PaymentProcessorID,
&i.PaidAt,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getPaymentByID = `-- name: GetPaymentByID :one
SELECT id, user_id, amount, currency, status, payment_processor_id, paid_at, created_at, updated_at FROM payments
WHERE id = ?
`
func (q *Queries) GetPaymentByID(ctx context.Context, id int64) (Payment, error) {
row := q.db.QueryRowContext(ctx, getPaymentByID, id)
var i Payment
err := row.Scan(
&i.ID,
&i.UserID,
&i.Amount,
&i.Currency,
&i.Status,
&i.PaymentProcessorID,
&i.PaidAt,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getPaymentsByUserID = `-- name: GetPaymentsByUserID :many
SELECT id, user_id, amount, currency, status, payment_processor_id, paid_at, created_at, updated_at FROM payments
WHERE user_id = ?
ORDER BY created_at DESC
`
func (q *Queries) GetPaymentsByUserID(ctx context.Context, userID int64) ([]Payment, error) {
rows, err := q.db.QueryContext(ctx, getPaymentsByUserID, userID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Payment{}
for rows.Next() {
var i Payment
if err := rows.Scan(
&i.ID,
&i.UserID,
&i.Amount,
&i.Currency,
&i.Status,
&i.PaymentProcessorID,
&i.PaidAt,
&i.CreatedAt,
&i.UpdatedAt,
); 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
}
const updatePaymentStatus = `-- name: UpdatePaymentStatus :one
UPDATE payments
SET status = ?, paid_at = CASE WHEN ? IS NOT NULL THEN ? ELSE paid_at END
WHERE id = ?
RETURNING id, user_id, amount, currency, status, payment_processor_id, paid_at, created_at, updated_at
`
type UpdatePaymentStatusParams struct {
Status string `json:"status"`
Column2 interface{} `json:"column_2"`
PaidAt sql.NullTime `json:"paid_at"`
ID int64 `json:"id"`
}
func (q *Queries) UpdatePaymentStatus(ctx context.Context, arg UpdatePaymentStatusParams) (Payment, error) {
row := q.db.QueryRowContext(ctx, updatePaymentStatus,
arg.Status,
arg.Column2,
arg.PaidAt,
arg.ID,
)
var i Payment
err := row.Scan(
&i.ID,
&i.UserID,
&i.Amount,
&i.Currency,
&i.Status,
&i.PaymentProcessorID,
&i.PaidAt,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

35
internal/db/querier.go Normal file
View File

@ -0,0 +1,35 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
package db
import (
"context"
)
type Querier interface {
CreatePayment(ctx context.Context, arg CreatePaymentParams) (Payment, error)
CreateSite(ctx context.Context, arg CreateSiteParams) (Site, error)
CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
// THIS IS NOT USED YET
// -- name: UpdateSiteDomain :one
// UPDATE sites
// SET domain = ?
// WHERE id = ?
// RETURNING *;
DeleteSite(ctx context.Context, id int64) error
DeleteUser(ctx context.Context, id int64) error
GetPaymentByID(ctx context.Context, id int64) (Payment, error)
GetPaymentsByUserID(ctx context.Context, userID int64) ([]Payment, error)
GetSiteByDomain(ctx context.Context, domain string) (Site, error)
GetSiteByID(ctx context.Context, id int64) (Site, error)
GetSitesByUserID(ctx context.Context, userID int64) ([]Site, error)
GetUserByID(ctx context.Context, id int64) (User, error)
GetUserByOIDCSubject(ctx context.Context, oidcSubject string) (User, error)
ListUsers(ctx context.Context) ([]User, error)
UpdatePaymentStatus(ctx context.Context, arg UpdatePaymentStatusParams) (Payment, error)
UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error)
}
var _ Querier = (*Queries)(nil)

View File

@ -16,4 +16,4 @@ ORDER BY created_at DESC;
UPDATE payments
SET status = ?, paid_at = CASE WHEN ? IS NOT NULL THEN ? ELSE paid_at END
WHERE id = ?
RETURNING *;
RETURNING *;

View File

@ -16,12 +16,13 @@ ORDER BY created_at DESC;
SELECT * FROM sites
WHERE domain = ?;
-- name: UpdateSiteDomain :one
UPDATE sites
SET domain = ?
WHERE id = ?
RETURNING *;
-- THIS IS NOT USED YET
-- -- name: UpdateSiteDomain :one
-- UPDATE sites
-- SET domain = ?
-- WHERE id = ?
-- RETURNING *;
-- name: DeleteSite :exec
DELETE FROM sites
WHERE id = ?;
WHERE id = ?;

View File

@ -1,5 +1,5 @@
-- name: CreateUser :one
INSERT INTO users (keycloak_id, name, email)
INSERT INTO users (oidc_subject, name, email)
VALUES (?, ?, ?)
RETURNING *;
@ -7,9 +7,9 @@ RETURNING *;
SELECT * FROM users
WHERE id = ?;
-- name: GetUserByKeycloakID :one
-- name: GetUserByOIDCSubject :one
SELECT * FROM users
WHERE keycloak_id = ?;
WHERE oidc_subject = ?;
-- name: UpdateUser :one
UPDATE users

View File

@ -2,7 +2,7 @@
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
keycloak_id TEXT UNIQUE NOT NULL,
oidc_subject TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -34,7 +34,7 @@ CREATE TABLE payments (
-- Create indexes for foreign keys and frequently queried columns
CREATE INDEX idx_sites_user_id ON sites(user_id);
CREATE INDEX idx_payments_user_id ON payments(user_id);
CREATE INDEX idx_users_keycloak_id ON users(keycloak_id);
CREATE INDEX idx_users_oidc_subject ON users(oidc_subject);
CREATE INDEX idx_sites_domain ON sites(domain);
CREATE INDEX idx_payments_payment_processor_id ON payments(payment_processor_id);
@ -58,4 +58,4 @@ AFTER UPDATE ON payments
FOR EACH ROW
BEGIN
UPDATE payments SET updated_at = CURRENT_TIMESTAMP WHERE id = OLD.id;
END;
END;

122
internal/db/sites.sql.go Normal file
View File

@ -0,0 +1,122 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: sites.sql
package db
import (
"context"
)
const createSite = `-- name: CreateSite :one
INSERT INTO sites (user_id, domain)
VALUES (?, ?)
RETURNING id, user_id, domain, created_at, updated_at
`
type CreateSiteParams struct {
UserID int64 `json:"user_id"`
Domain string `json:"domain"`
}
func (q *Queries) CreateSite(ctx context.Context, arg CreateSiteParams) (Site, error) {
row := q.db.QueryRowContext(ctx, createSite, arg.UserID, arg.Domain)
var i Site
err := row.Scan(
&i.ID,
&i.UserID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const deleteSite = `-- name: DeleteSite :exec
DELETE FROM sites
WHERE id = ?
`
// THIS IS NOT USED YET
// -- name: UpdateSiteDomain :one
// UPDATE sites
// SET domain = ?
// WHERE id = ?
// RETURNING *;
func (q *Queries) DeleteSite(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteSite, id)
return err
}
const getSiteByDomain = `-- name: GetSiteByDomain :one
SELECT id, user_id, domain, created_at, updated_at FROM sites
WHERE domain = ?
`
func (q *Queries) GetSiteByDomain(ctx context.Context, domain string) (Site, error) {
row := q.db.QueryRowContext(ctx, getSiteByDomain, domain)
var i Site
err := row.Scan(
&i.ID,
&i.UserID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getSiteByID = `-- name: GetSiteByID :one
SELECT id, user_id, domain, created_at, updated_at FROM sites
WHERE id = ?
`
func (q *Queries) GetSiteByID(ctx context.Context, id int64) (Site, error) {
row := q.db.QueryRowContext(ctx, getSiteByID, id)
var i Site
err := row.Scan(
&i.ID,
&i.UserID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getSitesByUserID = `-- name: GetSitesByUserID :many
SELECT id, user_id, domain, created_at, updated_at FROM sites
WHERE user_id = ?
ORDER BY created_at DESC
`
func (q *Queries) GetSitesByUserID(ctx context.Context, userID int64) ([]Site, error) {
rows, err := q.db.QueryContext(ctx, getSitesByUserID, userID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Site{}
for rows.Next() {
var i Site
if err := rows.Scan(
&i.ID,
&i.UserID,
&i.Domain,
&i.CreatedAt,
&i.UpdatedAt,
); 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
}

View File

@ -2,16 +2,15 @@
version: "2"
sql:
- engine: "sqlite"
# Paths are relative to this sqlc.yaml file
schema: "schema.sql" # Will look for internal/db/schema.sql
queries: "queries/" # Will look for internal/db/queries/
schema: "schema.sql"
queries: "queries/"
gen:
go:
package: "db" # Package name for the generated Go code
out: "." # Output directory for generated Go code (i.e., internal/db/)
package: "db"
out: "."
sql_package: "database/sql"
emit_json_tags: true
emit_prepared_queries: false
emit_interface: true # Generates a Querier interface
emit_interface: true
emit_exact_table_names: false
emit_empty_slices: true

146
internal/db/users.sql.go Normal file
View File

@ -0,0 +1,146 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: users.sql
package db
import (
"context"
)
const createUser = `-- name: CreateUser :one
INSERT INTO users (oidc_subject, name, email)
VALUES (?, ?, ?)
RETURNING id, oidc_subject, name, email, created_at, updated_at
`
type CreateUserParams struct {
OidcSubject string `json:"oidc_subject"`
Name string `json:"name"`
Email string `json:"email"`
}
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
row := q.db.QueryRowContext(ctx, createUser, arg.OidcSubject, arg.Name, arg.Email)
var i User
err := row.Scan(
&i.ID,
&i.OidcSubject,
&i.Name,
&i.Email,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const deleteUser = `-- name: DeleteUser :exec
DELETE FROM users
WHERE id = ?
`
func (q *Queries) DeleteUser(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteUser, id)
return err
}
const getUserByID = `-- name: GetUserByID :one
SELECT id, oidc_subject, name, email, created_at, updated_at FROM users
WHERE id = ?
`
func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
row := q.db.QueryRowContext(ctx, getUserByID, id)
var i User
err := row.Scan(
&i.ID,
&i.OidcSubject,
&i.Name,
&i.Email,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getUserByOIDCSubject = `-- name: GetUserByOIDCSubject :one
SELECT id, oidc_subject, name, email, created_at, updated_at FROM users
WHERE oidc_subject = ?
`
func (q *Queries) GetUserByOIDCSubject(ctx context.Context, oidcSubject string) (User, error) {
row := q.db.QueryRowContext(ctx, getUserByOIDCSubject, oidcSubject)
var i User
err := row.Scan(
&i.ID,
&i.OidcSubject,
&i.Name,
&i.Email,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const listUsers = `-- name: ListUsers :many
SELECT id, oidc_subject, name, email, created_at, updated_at FROM users
ORDER BY name
`
func (q *Queries) ListUsers(ctx context.Context) ([]User, error) {
rows, err := q.db.QueryContext(ctx, listUsers)
if err != nil {
return nil, err
}
defer rows.Close()
items := []User{}
for rows.Next() {
var i User
if err := rows.Scan(
&i.ID,
&i.OidcSubject,
&i.Name,
&i.Email,
&i.CreatedAt,
&i.UpdatedAt,
); 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
}
const updateUser = `-- name: UpdateUser :one
UPDATE users
SET name = ?, email = ?
WHERE id = ?
RETURNING id, oidc_subject, name, email, created_at, updated_at
`
type UpdateUserParams struct {
Name string `json:"name"`
Email string `json:"email"`
ID int64 `json:"id"`
}
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error) {
row := q.db.QueryRowContext(ctx, updateUser, arg.Name, arg.Email, arg.ID)
var i User
err := row.Scan(
&i.ID,
&i.OidcSubject,
&i.Name,
&i.Email,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}