From 44fda86a72933847a1937999d5608b4d1dac0a16 Mon Sep 17 00:00:00 2001 From: Katie Campbell Downie Date: Mon, 16 Sep 2024 09:51:18 -0500 Subject: [PATCH] Load Hot Topics from database (#49) --- go.mod | 1 + go.sum | 2 + internal/app/handlers.go | 40 +++++ internal/app/routes.go | 1 + internal/database/models/hottopic.go | 27 +++ .../database/models/repository/repository.go | 9 + internal/database/models/xo/hottopic.xo.go | 155 ++++++++++++++++++ internal/ui/components/home.templ | 55 +------ internal/ui/components/hot-topic.templ | 10 ++ 9 files changed, 251 insertions(+), 49 deletions(-) create mode 100644 internal/database/models/hottopic.go create mode 100644 internal/database/models/xo/hottopic.xo.go create mode 100644 internal/ui/components/hot-topic.templ diff --git a/go.mod b/go.mod index 6060a02..ee838dd 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.23.0 require ( github.com/a-h/templ v0.2.771 + github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.6.0 github.com/joho/godotenv v1.5.1 github.com/justinas/alice v1.2.0 diff --git a/go.sum b/go.sum index e0c5b04..75a2295 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ 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= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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 h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= diff --git a/internal/app/handlers.go b/internal/app/handlers.go index 9c222df..c356079 100644 --- a/internal/app/handlers.go +++ b/internal/app/handlers.go @@ -7,6 +7,7 @@ import ( "fmt" "log/slog" "net/http" + "strconv" "github.com/skylight-hq/phinvads-go/internal/database/models" "github.com/skylight-hq/phinvads-go/internal/database/models/xo" @@ -415,6 +416,45 @@ func (app *Application) getValueSetConceptsByCodeSystemOID(w http.ResponseWriter json.NewEncoder(w).Encode(valueSetConcepts) } +func (app *Application) getAllHotTopics(w http.ResponseWriter, r *http.Request) { + rp := app.repository + + hotTopics, err := rp.GetAllHotTopics(r.Context()) + + if err != nil { + var ( + method = r.Method + uri = r.URL.RequestURI() + ) + if errors.Is(err, sql.ErrNoRows) { + errorString := "Error: No Hot Topics found" + dbErr := &customErrors.DatabaseError{ + Err: err, + Msg: errorString, + Method: "home: Get Hot Topics", + } + dbErr.NoRows(w, r, err, app.logger) + } else { + customErrors.ServerError(w, r, err, app.logger) + } + + app.logger.Error(err.Error(), slog.String("method", method), slog.String("uri", uri)) + return + } + + for _, t := range *hotTopics { + // skip sending system config to the frontend + if t.HotTopicName == "SYSTEM CONFIG" { + continue + } + // format the sequence id to align with the uswds js controls + divId := fmt.Sprintf("m-a%s", strconv.Itoa(t.Seq)) + + component := components.HotTopic(t.HotTopicName, t.HotTopicDescription, divId, t.HotTopicID.String()) + component.Render(r.Context(), w) + } +} + func (app *Application) home(w http.ResponseWriter, r *http.Request) { component := components.Home() component.Render(r.Context(), w) diff --git a/internal/app/routes.go b/internal/app/routes.go index c02c2cf..5250469 100644 --- a/internal/app/routes.go +++ b/internal/app/routes.go @@ -42,6 +42,7 @@ func (app *Application) routes() http.Handler { mux.HandleFunc("GET /api/value-set-concepts/code-system/{codeSystemOid}", app.getValueSetConceptsByCodeSystemOID) mux.HandleFunc("GET /toggle-banner/{action}", app.handleBannerToggle) + mux.HandleFunc("GET /load-hot-topics", app.getAllHotTopics) standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders) diff --git a/internal/database/models/hottopic.go b/internal/database/models/hottopic.go new file mode 100644 index 0000000..fe1c34c --- /dev/null +++ b/internal/database/models/hottopic.go @@ -0,0 +1,27 @@ +package models + +import ( + "context" + + "github.com/skylight-hq/phinvads-go/internal/database/models/xo" +) + +// All retrieves all rows from 'public.hot_topic' +func GetAllHotTopics(ctx context.Context, db xo.DB) (*[]xo.HotTopic, error) { + const sqlstr = `SELECT * FROM public.hot_topic` + hotTopics := []xo.HotTopic{} + rows, err := db.QueryContext(ctx, sqlstr) + if err != nil { + return nil, err + } + + for rows.Next() { + ht := xo.HotTopic{} + err := rows.Scan(&ht.HotTopicID, &ht.HotTopicName, &ht.HotTopicDescription, &ht.Seq, &ht.EffectiveDate, &ht.ExpiryDate, &ht.DeploymentDate, &ht.CreatedDate, &ht.UpdatedDate) + if err != nil { + return nil, err + } + hotTopics = append(hotTopics, ht) + } + return &hotTopics, nil +} diff --git a/internal/database/models/repository/repository.go b/internal/database/models/repository/repository.go index 6f55bd3..49c9065 100644 --- a/internal/database/models/repository/repository.go +++ b/internal/database/models/repository/repository.go @@ -168,3 +168,12 @@ func (r *Repository) GetValueSetVersionByVscVsvId(ctx context.Context, vsc *xo.V func (r *Repository) GetValueSetVersionByVvsvVsvId(ctx context.Context, vvsv *xo.ViewValueSetVersion) (*xo.ValueSetVersion, error) { return vvsv.ValueSetVersion(ctx, r.database) } + +// =============================== // +// ==== Other resource methods === // +// =============================== // + +func (r *Repository) GetAllHotTopics(ctx context.Context) (*[]xo.HotTopic, error) { + return models.GetAllHotTopics(ctx, r.database) + +} diff --git a/internal/database/models/xo/hottopic.xo.go b/internal/database/models/xo/hottopic.xo.go new file mode 100644 index 0000000..208a078 --- /dev/null +++ b/internal/database/models/xo/hottopic.xo.go @@ -0,0 +1,155 @@ +package xo + +// Code generated by xo. DO NOT EDIT. + +import ( + "context" + "database/sql" + "time" + + "github.com/google/uuid" +) + +// HotTopic represents a row from 'public.hot_topic'. +type HotTopic struct { + HotTopicID uuid.UUID `json:"hot_topic_id"` // hot_topic_id + HotTopicName string `json:"hot_topic_name"` // hot_topic_name + HotTopicDescription string `json:"hot_topic_description"` // hot_topic_description + Seq int `json:"seq"` // seq + EffectiveDate time.Time `json:"effective_date"` // effective_date + ExpiryDate sql.NullTime `json:"expiry_date"` // expiry_date + DeploymentDate time.Time `json:"deployment_date"` // deployment_date + CreatedDate time.Time `json:"created_date"` // created_date + UpdatedDate time.Time `json:"updated_date"` // updated_date + // xo fields + _exists, _deleted bool +} + +// Exists returns true when the [HotTopic] exists in the database. +func (ht *HotTopic) Exists() bool { + return ht._exists +} + +// Deleted returns true when the [HotTopic] has been marked for deletion +// from the database. +func (ht *HotTopic) Deleted() bool { + return ht._deleted +} + +// Insert inserts the [HotTopic] to the database. +func (ht *HotTopic) Insert(ctx context.Context, db DB) error { + switch { + case ht._exists: // already exists + return logerror(&ErrInsertFailed{ErrAlreadyExists}) + case ht._deleted: // deleted + return logerror(&ErrInsertFailed{ErrMarkedForDeletion}) + } + // insert (manual) + const sqlstr = `INSERT INTO public.hot_topic (` + + `hot_topic_id, hot_topic_name, hot_topic_description, seq, effective_date, expiry_date, deployment_date, created_date, updated_date` + + `) VALUES (` + + `$1, $2, $3, $4, $5, $6, $7, $8, $9` + + `)` + // run + logf(sqlstr, ht.HotTopicID, ht.HotTopicName, ht.HotTopicDescription, ht.Seq, ht.EffectiveDate, ht.ExpiryDate, ht.DeploymentDate, ht.CreatedDate, ht.UpdatedDate) + if _, err := db.ExecContext(ctx, sqlstr, ht.HotTopicID, ht.HotTopicName, ht.HotTopicDescription, ht.Seq, ht.EffectiveDate, ht.ExpiryDate, ht.DeploymentDate, ht.CreatedDate, ht.UpdatedDate); err != nil { + return logerror(err) + } + // set exists + ht._exists = true + return nil +} + +// Update updates a [HotTopic] in the database. +func (ht *HotTopic) Update(ctx context.Context, db DB) error { + switch { + case !ht._exists: // doesn't exist + return logerror(&ErrUpdateFailed{ErrDoesNotExist}) + case ht._deleted: // deleted + return logerror(&ErrUpdateFailed{ErrMarkedForDeletion}) + } + // update with composite primary key + const sqlstr = `UPDATE public.hot_topic SET ` + + `hot_topic_name = $1, hot_topic_description = $2, seq = $3, effective_date = $4, expiry_date = $5, deployment_date = $6, created_date = $7, updated_date = $8 ` + + `WHERE hot_topic_id = $9` + // run + logf(sqlstr, ht.HotTopicName, ht.HotTopicDescription, ht.Seq, ht.EffectiveDate, ht.ExpiryDate, ht.DeploymentDate, ht.CreatedDate, ht.UpdatedDate, ht.HotTopicID) + if _, err := db.ExecContext(ctx, sqlstr, ht.HotTopicName, ht.HotTopicDescription, ht.Seq, ht.EffectiveDate, ht.ExpiryDate, ht.DeploymentDate, ht.CreatedDate, ht.UpdatedDate, ht.HotTopicID); err != nil { + return logerror(err) + } + return nil +} + +// Save saves the [HotTopic] to the database. +func (ht *HotTopic) Save(ctx context.Context, db DB) error { + if ht.Exists() { + return ht.Update(ctx, db) + } + return ht.Insert(ctx, db) +} + +// Upsert performs an upsert for [HotTopic]. +func (ht *HotTopic) Upsert(ctx context.Context, db DB) error { + switch { + case ht._deleted: // deleted + return logerror(&ErrUpsertFailed{ErrMarkedForDeletion}) + } + // upsert + const sqlstr = `INSERT INTO public.hot_topic (` + + `hot_topic_id, hot_topic_name, hot_topic_description, seq, effective_date, expiry_date, deployment_date, created_date, updated_date` + + `) VALUES (` + + `$1, $2, $3, $4, $5, $6, $7, $8, $9` + + `)` + + ` ON CONFLICT (hot_topic_id) DO ` + + `UPDATE SET ` + + `hot_topic_name = EXCLUDED.hot_topic_name, hot_topic_description = EXCLUDED.hot_topic_description, seq = EXCLUDED.seq, effective_date = EXCLUDED.effective_date, expiry_date = EXCLUDED.expiry_date, deployment_date = EXCLUDED.deployment_date, created_date = EXCLUDED.created_date, updated_date = EXCLUDED.updated_date ` + // run + logf(sqlstr, ht.HotTopicID, ht.HotTopicName, ht.HotTopicDescription, ht.Seq, ht.EffectiveDate, ht.ExpiryDate, ht.DeploymentDate, ht.CreatedDate, ht.UpdatedDate) + if _, err := db.ExecContext(ctx, sqlstr, ht.HotTopicID, ht.HotTopicName, ht.HotTopicDescription, ht.Seq, ht.EffectiveDate, ht.ExpiryDate, ht.DeploymentDate, ht.CreatedDate, ht.UpdatedDate); err != nil { + return logerror(err) + } + // set exists + ht._exists = true + return nil +} + +// Delete deletes the [HotTopic] from the database. +func (ht *HotTopic) Delete(ctx context.Context, db DB) error { + switch { + case !ht._exists: // doesn't exist + return nil + case ht._deleted: // deleted + return nil + } + // delete with single primary key + const sqlstr = `DELETE FROM public.hot_topic ` + + `WHERE hot_topic_id = $1` + // run + logf(sqlstr, ht.HotTopicID) + if _, err := db.ExecContext(ctx, sqlstr, ht.HotTopicID); err != nil { + return logerror(err) + } + // set deleted + ht._deleted = true + return nil +} + +// HotTopicByHotTopicID retrieves a row from 'public.hot_topic' as a [HotTopic]. +// +// Generated from index 'hot_topic_pkey'. +func HotTopicByHotTopicID(ctx context.Context, db DB, hotTopicID uuid.UUID) (*HotTopic, error) { + // query + const sqlstr = `SELECT ` + + `hot_topic_id, hot_topic_name, hot_topic_description, seq, effective_date, expiry_date, deployment_date, created_date, updated_date ` + + `FROM public.hot_topic ` + + `WHERE hot_topic_id = $1` + // run + logf(sqlstr, hotTopicID) + ht := HotTopic{ + _exists: true, + } + if err := db.QueryRowContext(ctx, sqlstr, hotTopicID).Scan(&ht.HotTopicID, &ht.HotTopicName, &ht.HotTopicDescription, &ht.Seq, &ht.EffectiveDate, &ht.ExpiryDate, &ht.DeploymentDate, &ht.CreatedDate, &ht.UpdatedDate); err != nil { + return nil, logerror(err) + } + return &ht, nil +} diff --git a/internal/ui/components/home.templ b/internal/ui/components/home.templ index a4daae4..0caf13e 100644 --- a/internal/ui/components/home.templ +++ b/internal/ui/components/home.templ @@ -41,55 +41,12 @@ templ Home() {

Hot Topics

-
- -
-

Lorem ipsum...

-
- -
-

Lorem ipsum...

-
- -
-

Lorem ipsum...

-
- -
-

Lorem ipsum...

-
- -
-

Lorem ipsum...

-
- -
-

Lorem ipsum...

-
- -
-

Lorem ipsum...

-
- -
-

Lorem ipsum...

-
- -
-

Lorem ipsum...

-
- -
-

Lorem ipsum...

-
- -
-

Lorem ipsum...

-
- -
-

Lorem ipsum...

-
+






diff --git a/internal/ui/components/hot-topic.templ b/internal/ui/components/hot-topic.templ new file mode 100644 index 0000000..9076c47 --- /dev/null +++ b/internal/ui/components/hot-topic.templ @@ -0,0 +1,10 @@ +package components + +templ HotTopic(topic_name string, topic_description, sequence, topic_id string ) { + + +} \ No newline at end of file