protoype: list raw events on mock mode reports page

This commit is contained in:
2026-08-19 23:19:16 -06:00
parent e108e9da26
commit 002d7146d0
8 changed files with 252 additions and 128 deletions
+81
View File
@@ -0,0 +1,81 @@
package reports
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/jackc/pgx/v5"
"ruben/inventory2/consts"
"ruben/inventory2/domains/accounts"
)
type (
RawShopEvent struct {
Platform accounts.Platform
ShopID string
EventTimestamp time.Time
EventID string
RawPayload json.RawMessage
}
)
func (db *Store) GetRawShopEvents(ctx context.Context, acctID int64, platform accounts.Platform, shopID string) ([]RawShopEvent, error) {
if _, err := db.accts.GetMockShop(ctx, acctID, platform, shopID); err != nil {
if errors.Is(err, consts.ErrNotFound) {
return nil, fmt.Errorf("shop not found: %w", err)
}
return nil, fmt.Errorf("failed to look up shop: %w", err)
}
rows, err := db.db.Query(
ctx,
`
SELECT
event_timestamp,
event_id,
raw_payload
FROM
mock.raw_shop_events
WHERE
platform = @platform
AND shop_id = @shop_id
`,
pgx.NamedArgs{
"platform": platform,
"shop_id": shopID,
},
)
if err != nil {
return nil, fmt.Errorf("failed to perform query: %w", err)
}
vs, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct {
Event_timestamp time.Time
Event_id string
Raw_payload json.RawMessage
}])
if err != nil {
return nil, fmt.Errorf("failed to scan rows: %w", err)
}
fmt.Println("vs:", vs)
evts := make([]RawShopEvent, len(vs))
for i, v := range vs {
evts[i] = RawShopEvent{
Platform: platform,
ShopID: shopID,
EventTimestamp: v.Event_timestamp,
EventID: v.Event_id,
RawPayload: v.Raw_payload,
}
}
fmt.Println("evts:", evts)
return evts, nil
}
+32
View File
@@ -0,0 +1,32 @@
package reports
import (
"context"
"github.com/jackc/pgx/v5/pgxpool"
"ruben/inventory2/domains/accounts"
"ruben/inventory2/logging"
)
//go:generate concurry -s Store
type (
Store struct {
log *logging.Logger
db *pgxpool.Pool
accts *accounts.Store
}
)
func NewStore(logger *logging.Logger, db *pgxpool.Pool, accts *accounts.Store) *Store {
return &Store{
log: logger,
db: db,
accts: accts,
}
}
func (db *Store) WithContext(ctx context.Context) *StoreWithContext {
return NewStoreWithContext(ctx, db)
}
+25
View File
@@ -0,0 +1,25 @@
// Code generated by concurry DO NOT EDIT.
// https://github.com/angelbeltran/concurry
// concurry
package reports
import (
"context"
"ruben/inventory2/domains/accounts"
)
type StoreWithContext struct {
ctx context.Context
*Store
}
func NewStoreWithContext(ctx context.Context, v *Store) *StoreWithContext {
return &StoreWithContext{
ctx: ctx,
Store: v,
}
}
func (v_ctx *StoreWithContext) GetRawShopEvents(acctID int64, platform accounts.Platform, shopID string) ([]RawShopEvent, error) {
return v_ctx.Store.GetRawShopEvents(v_ctx.ctx, acctID, platform, shopID)
}
+5 -1
View File
@@ -25,6 +25,7 @@ import (
"ruben/inventory2/domains/authentication"
etsy_platform "ruben/inventory2/domains/platforms/etsy"
"ruben/inventory2/domains/raw_events"
"ruben/inventory2/domains/reports"
"ruben/inventory2/logging"
"ruben/inventory2/server"
)
@@ -151,11 +152,14 @@ func runAuthProcesses(ctx context.Context, auth *authentication.Authenticator) <
}
func runServer(ctx context.Context, logger *logging.Logger, connPool *pgxpool.Pool, auth *authentication.Authenticator) <-chan error {
accts := accounts.NewStore(logger, connPool)
r := server.NewRouter(
logger.WithGroup("server"),
"./",
raw_events.NewStore(logger.WithGroup("raw-event-store"), connPool),
accounts.NewStore(logger, connPool),
accts,
reports.NewStore(logger, connPool, accts),
etsy_platform.NewPlatform(
logger,
func(acctID int64) string {
+3
View File
@@ -12,6 +12,7 @@ import (
"ruben/inventory2/domains/authentication"
etsy_platform "ruben/inventory2/domains/platforms/etsy"
"ruben/inventory2/domains/raw_events"
"ruben/inventory2/domains/reports"
"ruben/inventory2/logging"
"ruben/inventory2/server/api"
"ruben/inventory2/server/auth"
@@ -30,6 +31,7 @@ func NewRouter(
contentDir string,
rawEvents *raw_events.Store,
accts *accounts.Store,
reps *reports.Store,
etsy *etsy_platform.Platform,
authr *authentication.Authenticator,
) *Router {
@@ -59,6 +61,7 @@ func NewRouter(
"/ui",
rawEvents,
accts,
reps,
etsy,
authM.Authenticate(),
)
+6
View File
@@ -16,6 +16,7 @@ import (
"ruben/inventory2/domains/accounts"
etsy_platform "ruben/inventory2/domains/platforms/etsy"
"ruben/inventory2/domains/raw_events"
"ruben/inventory2/domains/reports"
"ruben/inventory2/logging"
"ruben/inventory2/server/auth"
"ruben/inventory2/server/response"
@@ -28,6 +29,7 @@ type (
templater *templater.Templater
rawEvents *raw_events.Store
accts *accounts.Store
reports *reports.Store
etsy *etsy_platform.Platform
}
@@ -44,6 +46,7 @@ func Routes(
uiPath string,
rawEvents *raw_events.Store,
accts *accounts.Store,
reps *reports.Store,
etsy *etsy_platform.Platform,
authenticate gin.HandlerFunc,
) {
@@ -143,6 +146,7 @@ func Routes(
}),
rawEvents: rawEvents,
accts: accts,
reports: reps,
etsy: etsy,
}
@@ -189,6 +193,8 @@ func (s *webpageRouter) serveTemplate(c *gin.Context) (response.Response, error)
newURLCalculator(r.URL),
"Accounts",
s.accts.WithContext(ctx),
"Reports",
s.reports.WithContext(ctx),
"Etsy",
s.etsy.WithContext(ctx),
"MockMode",
+31 -127
View File
@@ -197,29 +197,6 @@
.bottom-0 {
bottom: calc(var(--spacing) * 0);
}
.grid-table-\[1fr_1fr_1fr_1fr_1fr_1fr\] {
&:is(table) {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
& > :is(thead, tbody, tfoot) {
grid-column-start: 1;
grid-column-end: -1;
display: grid;
grid-template-columns: subgrid;
& > tr {
grid-column-start: 1;
grid-column-end: -1;
display: grid;
grid-template-columns: subgrid;
& > th, & > td {
grid-column: span 1;
align-content: center;
text-align: center;
}
}
}
}
}
.grid-table-\[1fr_1fr_1fr_1fr_1fr_1fr_1fr\] {
&:is(table) {
display: grid;
@@ -297,6 +274,9 @@
.my-\[0\.5em\] {
margin-block: 0.5em;
}
.my-\[1em\] {
margin-block: 1em;
}
.my-\[1rem\] {
margin-block: 1rem;
}
@@ -312,9 +292,6 @@
.mt-\[3em\] {
margin-top: 3em;
}
.mb-0 {
margin-bottom: calc(var(--spacing) * 0);
}
.mb-\[0\.5em\] {
margin-bottom: 0.5em;
}
@@ -327,9 +304,6 @@
.mb-\[3em\] {
margin-bottom: 3em;
}
.ml-\[0\.5em\] {
margin-left: 0.5em;
}
.ml-\[1em\] {
margin-left: 1em;
}
@@ -399,9 +373,6 @@
.flex-shrink {
flex-shrink: 1;
}
.flex-grow {
flex-grow: 1;
}
.flex-grow-\[1\] {
flex-grow: 1;
}
@@ -417,12 +388,6 @@
.basis-full {
flex-basis: 100%;
}
.border-collapse {
border-collapse: collapse;
}
.transform {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
}
.animate-pulse {
animation: var(--animate-pulse);
}
@@ -533,15 +498,6 @@
.border-\[gray\] {
border-color: gray;
}
.border-\[red\] {
border-color: red;
}
.border-\[white\] {
border-color: white;
}
.border-accent {
border-color: var(--accent);
}
.border-border {
border-color: var(--border);
}
@@ -554,9 +510,6 @@
.border-sidebar-border {
border-color: var(--sidebar-border);
}
.bg-\[red\] {
background-color: red;
}
.bg-accent {
background-color: var(--accent);
}
@@ -569,9 +522,6 @@
.bg-card {
background-color: var(--card);
}
.bg-none {
background-image: none;
}
.p-\[0\.5em\] {
padding: 0.5em;
}
@@ -590,6 +540,9 @@
.py-\[1em\] {
padding-block: 1em;
}
.py-\[2em\] {
padding-block: 2em;
}
.pt-\[1em\] {
padding-top: 1em;
}
@@ -611,9 +564,6 @@
.font-display {
font-family: var(--display-family);
}
.font-text {
font-family: var(--text-family);
}
.text-lg {
font-size: var(--text-lg);
line-height: var(--tw-leading, var(--text-lg--line-height));
@@ -629,10 +579,6 @@
.text-\[1\.5em\] {
font-size: 1.5em;
}
.font-\[1\.5em\] {
--tw-font-weight: 1.5em;
font-weight: 1.5em;
}
.font-bold {
--tw-font-weight: var(--font-weight-bold);
font-weight: var(--font-weight-bold);
@@ -644,12 +590,6 @@
.text-nowrap {
text-wrap: nowrap;
}
.text-wrap {
text-wrap: wrap;
}
.text-inherit {
color: inherit;
}
.capitalize {
text-transform: capitalize;
}
@@ -659,24 +599,13 @@
.underline {
text-decoration-line: underline;
}
.outline {
outline-style: var(--tw-outline-style);
outline-width: 1px;
}
.outline-1 {
outline-style: var(--tw-outline-style);
outline-width: 1px;
}
.outline-\[1px\] {
outline-style: var(--tw-outline-style);
outline-width: 1px;
}
.outline-\(--table-tfoot\) {
outline-color: var(--table-tfoot);
}
.outline-\[red\] {
outline-color: red;
}
.filter {
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
}
@@ -690,16 +619,6 @@
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.\*\:bg-background {
:is(& > *) {
background-color: var(--background);
}
}
.\*\*\:bg-background {
:is(& *) {
background-color: var(--background);
}
}
.not-open\:mb-\[1em\] {
&:not(*:is([open], :popover-open, :open)) {
margin-bottom: 1em;
@@ -906,26 +825,11 @@
}
}
}
.active\:bg-accent {
&:active {
background-color: var(--accent);
}
}
.active\:bg-accent-secondary {
&:active {
background-color: var(--accent-secondary);
}
}
.active\:bg-muted {
&:active {
background-color: var(--muted);
}
}
.active\:bg-secondary {
&:active {
background-color: var(--secondary);
}
}
.disabled\:cursor-not-allowed {
&:disabled {
cursor: not-allowed;
@@ -1274,26 +1178,6 @@
}
}
}
@property --tw-rotate-x {
syntax: "*";
inherits: false;
}
@property --tw-rotate-y {
syntax: "*";
inherits: false;
}
@property --tw-rotate-z {
syntax: "*";
inherits: false;
}
@property --tw-skew-x {
syntax: "*";
inherits: false;
}
@property --tw-skew-y {
syntax: "*";
inherits: false;
}
@property --tw-border-style {
syntax: "*";
inherits: false;
@@ -1376,6 +1260,26 @@
inherits: false;
initial-value: 1;
}
@property --tw-rotate-x {
syntax: "*";
inherits: false;
}
@property --tw-rotate-y {
syntax: "*";
inherits: false;
}
@property --tw-rotate-z {
syntax: "*";
inherits: false;
}
@property --tw-skew-x {
syntax: "*";
inherits: false;
}
@property --tw-skew-y {
syntax: "*";
inherits: false;
}
@property --tw-content {
syntax: "*";
initial-value: "";
@@ -1393,11 +1297,6 @@
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop {
--tw-rotate-x: initial;
--tw-rotate-y: initial;
--tw-rotate-z: initial;
--tw-skew-x: initial;
--tw-skew-y: initial;
--tw-border-style: solid;
--tw-font-weight: initial;
--tw-outline-style: solid;
@@ -1417,6 +1316,11 @@
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-scale-z: 1;
--tw-rotate-x: initial;
--tw-rotate-y: initial;
--tw-rotate-z: initial;
--tw-skew-x: initial;
--tw-skew-y: initial;
--tw-content: "";
--tw-leading: initial;
}
@@ -0,0 +1,69 @@
{{- $acctID := .Identity.Account.AccountID }}
{{- $platform := .Platform }}
{{- if not $platform }}
{{- $platform = .Request.URL.Query.Get "platform" }}
{{- end }}
{{- $shopID := .ShopID }}
{{- if not $shopID }}
{{- $shopID = .Request.URL.Query.Get "shop-id" }}
{{- end }}
<label for="shop-selector">
Select a Shop
</label>
<select
id="shop-selector"
hx-swap=""
hx-get="/ui/accounts/{{$acctID}}/reports/mock"
hx-vals="js:{
platform: console.log(event.target.value.replace(/-.*/, '')) || event.target.value.replace(/-.*/, ''),
'shop-id': console.log(event.target.value.replace(/[^-]*-/, '')) || event.target.value.replace(/[^-]*-/, ''),
}"
hx-push-url="true"
hx-target="body"
>
{{- range $shop := .Accounts.GetMockShops $acctID }}
{{- $isSelected := and (eq $platform $shop.Platform) (eq $shopID $shop.ShopID) }}
<option
value="{{ $shop.Platform }}-{{ $shop.ShopID }}"
{{ if $isSelected }}selected{{end}}
>
{{ $shop.Name }}
</option>
{{- end }}
</select>
<h3 class="m-[1em]">
Platform: {{ $platform }}
</h3>
{{- $evts := "" }}
{{- if and $platform $shopID }}
{{- $parsedPlatform := parsePlatform $platform }}
{{- $evts = .Reports.GetRawShopEvents $acctID $parsedPlatform $shopID }}
{{- end }}
<table id="raw-event-table">
<thead>
<tr>
<td>Time</td>
{{/*<td>EventID</td>*/}}
<td>Event</td>
</tr>
</thead>
<tbody>
{{- if $evts }}
{{- range $evt := $evts }}
<tr>
<td>{{$evt.EventTimestamp}}</td>
{{/*<td>{{$evt.EventID}}</td>*/}}
<td>{{prettyPrintJSON $evt.RawPayload}}</td>
</tr>
{{- end }}
{{- end }}
</tbody>
</table>