sms-metering-location-link: per-tag SMS credits (allocated/used, admin top-up), short URLs + owner location page (map embed + finder details) — 13/13 scenarios

This commit is contained in:
2026-08-08 14:38:32 +10:00
parent db1c358fe7
commit 5487cee966
12 changed files with 297 additions and 37 deletions

View File

@@ -39,21 +39,30 @@ type Scan struct {
}
type Tag struct {
ID int64 `json:"id"`
TagCode string `json:"tag_code"`
OwnerID pgtype.Int8 `json:"owner_id"`
Status string `json:"status"`
ItemType pgtype.Text `json:"item_type"`
Description pgtype.Text `json:"description"`
PhotoUrl pgtype.Text `json:"photo_url"`
Phone pgtype.Text `json:"phone"`
Address pgtype.Text `json:"address"`
Notes pgtype.Text `json:"notes"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
SmsEnabled bool `json:"sms_enabled"`
ProductID pgtype.Int8 `json:"product_id"`
OrderID pgtype.Int8 `json:"order_id"`
ID int64 `json:"id"`
TagCode string `json:"tag_code"`
OwnerID pgtype.Int8 `json:"owner_id"`
Status string `json:"status"`
ItemType pgtype.Text `json:"item_type"`
Description pgtype.Text `json:"description"`
PhotoUrl pgtype.Text `json:"photo_url"`
Phone pgtype.Text `json:"phone"`
Address pgtype.Text `json:"address"`
Notes pgtype.Text `json:"notes"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
SmsEnabled bool `json:"sms_enabled"`
ProductID pgtype.Int8 `json:"product_id"`
OrderID pgtype.Int8 `json:"order_id"`
SmsAllocated int32 `json:"sms_allocated"`
SmsUsed int32 `json:"sms_used"`
SmsPeriodStart pgtype.Date `json:"sms_period_start"`
}
type UrlShortened struct {
Code string `json:"code"`
ScanID int64 `json:"scan_id"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type User struct {

View File

@@ -11,6 +11,7 @@ import (
)
type Querier interface {
AddSmsUsed(ctx context.Context, id int64) error
BindTag(ctx context.Context, arg BindTagParams) (Tag, error)
ClearTagOwner(ctx context.Context, id int64) (Tag, error)
CountAlertsByIPSince(ctx context.Context, arg CountAlertsByIPSinceParams) (int64, error)
@@ -21,11 +22,14 @@ type Querier interface {
GetLatestScanByTag(ctx context.Context, tagID int64) (Scan, error)
GetOrderByID(ctx context.Context, id int64) (Order, error)
GetRecentScanByFingerprint(ctx context.Context, arg GetRecentScanByFingerprintParams) (Scan, error)
GetScanByID(ctx context.Context, id int64) (Scan, error)
GetShortCode(ctx context.Context, code string) (UrlShortened, error)
GetTagByCode(ctx context.Context, tagCode string) (Tag, error)
GetTagByID(ctx context.Context, id int64) (Tag, error)
GetUserByEmail(ctx context.Context, email string) (User, error)
GetUserByID(ctx context.Context, id int64) (User, error)
InsertScan(ctx context.Context, arg InsertScanParams) (Scan, error)
InsertShortCode(ctx context.Context, arg InsertShortCodeParams) (UrlShortened, error)
InsertTag(ctx context.Context, tagCode string) (Tag, error)
ListTagsByOwner(ctx context.Context, ownerID pgtype.Int8) ([]Tag, error)
SetScanAlertSent(ctx context.Context, arg SetScanAlertSentParams) error

View File

@@ -85,3 +85,15 @@ WHERE tag_id = $1 AND alert_sent = TRUE AND scanned_at > $2;
-- name: CountAlertsByIPSince :one
SELECT count(*) FROM scans
WHERE ip = $1 AND alert_sent = TRUE AND scanned_at > $2;
-- name: AddSmsUsed :exec
UPDATE tags SET sms_used = sms_used + 1, updated_at = now() WHERE id = $1;
-- name: InsertShortCode :one
INSERT INTO url_shortened (code, scan_id) VALUES ($1, $2) RETURNING *;
-- name: GetShortCode :one
SELECT * FROM url_shortened WHERE code = $1;
-- name: GetScanByID :one
SELECT * FROM scans WHERE id = $1;

View File

@@ -11,11 +11,20 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
const addSmsUsed = `-- name: AddSmsUsed :exec
UPDATE tags SET sms_used = sms_used + 1, updated_at = now() WHERE id = $1
`
func (q *Queries) AddSmsUsed(ctx context.Context, id int64) error {
_, err := q.db.Exec(ctx, addSmsUsed, id)
return err
}
const bindTag = `-- name: BindTag :one
UPDATE tags
SET owner_id = $1, updated_at = now()
WHERE tag_code = $2 AND owner_id IS NULL AND status = 'unset'
RETURNING id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id
RETURNING id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id, sms_allocated, sms_used, sms_period_start
`
type BindTagParams struct {
@@ -42,6 +51,9 @@ func (q *Queries) BindTag(ctx context.Context, arg BindTagParams) (Tag, error) {
&i.SmsEnabled,
&i.ProductID,
&i.OrderID,
&i.SmsAllocated,
&i.SmsUsed,
&i.SmsPeriodStart,
)
return i, err
}
@@ -50,7 +62,7 @@ const clearTagOwner = `-- name: ClearTagOwner :one
UPDATE tags
SET owner_id=NULL, status='unset', item_type=NULL, description=NULL, photo_url=NULL, phone=NULL, address=NULL, notes=NULL, updated_at=now()
WHERE id=$1
RETURNING id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id
RETURNING id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id, sms_allocated, sms_used, sms_period_start
`
func (q *Queries) ClearTagOwner(ctx context.Context, id int64) (Tag, error) {
@@ -72,6 +84,9 @@ func (q *Queries) ClearTagOwner(ctx context.Context, id int64) (Tag, error) {
&i.SmsEnabled,
&i.ProductID,
&i.OrderID,
&i.SmsAllocated,
&i.SmsUsed,
&i.SmsPeriodStart,
)
return i, err
}
@@ -252,8 +267,41 @@ func (q *Queries) GetRecentScanByFingerprint(ctx context.Context, arg GetRecentS
return i, err
}
const getScanByID = `-- name: GetScanByID :one
SELECT id, tag_id, scanned_at, lat, lng, location_shared, scanner_phone, alert_sent, fingerprint, ip FROM scans WHERE id = $1
`
func (q *Queries) GetScanByID(ctx context.Context, id int64) (Scan, error) {
row := q.db.QueryRow(ctx, getScanByID, id)
var i Scan
err := row.Scan(
&i.ID,
&i.TagID,
&i.ScannedAt,
&i.Lat,
&i.Lng,
&i.LocationShared,
&i.ScannerPhone,
&i.AlertSent,
&i.Fingerprint,
&i.Ip,
)
return i, err
}
const getShortCode = `-- name: GetShortCode :one
SELECT code, scan_id, created_at FROM url_shortened WHERE code = $1
`
func (q *Queries) GetShortCode(ctx context.Context, code string) (UrlShortened, error) {
row := q.db.QueryRow(ctx, getShortCode, code)
var i UrlShortened
err := row.Scan(&i.Code, &i.ScanID, &i.CreatedAt)
return i, err
}
const getTagByCode = `-- name: GetTagByCode :one
SELECT id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id FROM tags WHERE tag_code = $1
SELECT id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id, sms_allocated, sms_used, sms_period_start FROM tags WHERE tag_code = $1
`
func (q *Queries) GetTagByCode(ctx context.Context, tagCode string) (Tag, error) {
@@ -275,12 +323,15 @@ func (q *Queries) GetTagByCode(ctx context.Context, tagCode string) (Tag, error)
&i.SmsEnabled,
&i.ProductID,
&i.OrderID,
&i.SmsAllocated,
&i.SmsUsed,
&i.SmsPeriodStart,
)
return i, err
}
const getTagByID = `-- name: GetTagByID :one
SELECT id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id FROM tags WHERE id = $1
SELECT id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id, sms_allocated, sms_used, sms_period_start FROM tags WHERE id = $1
`
func (q *Queries) GetTagByID(ctx context.Context, id int64) (Tag, error) {
@@ -302,6 +353,9 @@ func (q *Queries) GetTagByID(ctx context.Context, id int64) (Tag, error) {
&i.SmsEnabled,
&i.ProductID,
&i.OrderID,
&i.SmsAllocated,
&i.SmsUsed,
&i.SmsPeriodStart,
)
return i, err
}
@@ -388,9 +442,25 @@ func (q *Queries) InsertScan(ctx context.Context, arg InsertScanParams) (Scan, e
return i, err
}
const insertShortCode = `-- name: InsertShortCode :one
INSERT INTO url_shortened (code, scan_id) VALUES ($1, $2) RETURNING code, scan_id, created_at
`
type InsertShortCodeParams struct {
Code string `json:"code"`
ScanID int64 `json:"scan_id"`
}
func (q *Queries) InsertShortCode(ctx context.Context, arg InsertShortCodeParams) (UrlShortened, error) {
row := q.db.QueryRow(ctx, insertShortCode, arg.Code, arg.ScanID)
var i UrlShortened
err := row.Scan(&i.Code, &i.ScanID, &i.CreatedAt)
return i, err
}
const insertTag = `-- name: InsertTag :one
INSERT INTO tags (tag_code) VALUES ($1)
RETURNING id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id
RETURNING id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id, sms_allocated, sms_used, sms_period_start
`
func (q *Queries) InsertTag(ctx context.Context, tagCode string) (Tag, error) {
@@ -412,12 +482,15 @@ func (q *Queries) InsertTag(ctx context.Context, tagCode string) (Tag, error) {
&i.SmsEnabled,
&i.ProductID,
&i.OrderID,
&i.SmsAllocated,
&i.SmsUsed,
&i.SmsPeriodStart,
)
return i, err
}
const listTagsByOwner = `-- name: ListTagsByOwner :many
SELECT id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id FROM tags WHERE owner_id = $1 ORDER BY created_at DESC
SELECT id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id, sms_allocated, sms_used, sms_period_start FROM tags WHERE owner_id = $1 ORDER BY created_at DESC
`
func (q *Queries) ListTagsByOwner(ctx context.Context, ownerID pgtype.Int8) ([]Tag, error) {
@@ -445,6 +518,9 @@ func (q *Queries) ListTagsByOwner(ctx context.Context, ownerID pgtype.Int8) ([]T
&i.SmsEnabled,
&i.ProductID,
&i.OrderID,
&i.SmsAllocated,
&i.SmsUsed,
&i.SmsPeriodStart,
); err != nil {
return nil, err
}
@@ -503,7 +579,7 @@ const updateTagDetails = `-- name: UpdateTagDetails :one
UPDATE tags
SET item_type=$2, description=$3, photo_url=$4, phone=$5, address=$6, notes=$7, sms_enabled=$8, status='active', updated_at=now()
WHERE id=$1
RETURNING id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id
RETURNING id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id, sms_allocated, sms_used, sms_period_start
`
type UpdateTagDetailsParams struct {
@@ -545,6 +621,9 @@ func (q *Queries) UpdateTagDetails(ctx context.Context, arg UpdateTagDetailsPara
&i.SmsEnabled,
&i.ProductID,
&i.OrderID,
&i.SmsAllocated,
&i.SmsUsed,
&i.SmsPeriodStart,
)
return i, err
}