From a8a9141ede8b9c03561cad1a46819ea9045731ee Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Wed, 5 Aug 2026 11:52:17 -0400 Subject: [PATCH] Getting the database part to work, but found a bug in the data model reflection. --- .ozai.json | 5 - data/models.go | 45 +++-- features/fakepay/api.go | 13 -- features/fakepay/db.go | 9 - features/fakepay/init.go | 10 -- features/fakepay/views.go | 11 -- features/init.go | 6 - features/paypal/api.go | 78 --------- features/paypal/api.js | 160 ------------------ features/paypal/db.go | 9 - features/paypal/init.go | 10 -- features/paypal/views.go | 11 -- features/shopping/api.go | 31 ---- features/shopping/db.go | 9 - features/shopping/init.go | 10 -- features/shopping/views.go | 11 -- features/survey/db.go | 39 ++++- features/survey/views.go | 54 ------ migrations/20260805143821_surveys.sql | 21 +++ .../20260112190943_shopping_init.sql | 0 .../20260112195518_shopping_cart.sql | 0 21 files changed, 78 insertions(+), 464 deletions(-) delete mode 100644 features/fakepay/api.go delete mode 100644 features/fakepay/db.go delete mode 100644 features/fakepay/init.go delete mode 100644 features/fakepay/views.go delete mode 100644 features/paypal/api.go delete mode 100644 features/paypal/api.js delete mode 100644 features/paypal/db.go delete mode 100644 features/paypal/init.go delete mode 100644 features/paypal/views.go delete mode 100644 features/shopping/api.go delete mode 100644 features/shopping/db.go delete mode 100644 features/shopping/init.go delete mode 100644 features/shopping/views.go create mode 100644 migrations/20260805143821_surveys.sql rename migrations/{ => saved}/20260112190943_shopping_init.sql (100%) rename migrations/{ => saved}/20260112195518_shopping_cart.sql (100%) diff --git a/.ozai.json b/.ozai.json index de400e4..5c91ada 100644 --- a/.ozai.json +++ b/.ozai.json @@ -11,11 +11,6 @@ "Command": "go", "Args": ["tool", "ssgod", "watch"] }, - "mailhog": { - "URL": "/mailhog", - "Command": "MailHog", - "Args": [] - }, "tailwind": { "URL": "/tailwind", "Command": "tailwindcss", diff --git a/data/models.go b/data/models.go index 88c491d..a2a3793 100644 --- a/data/models.go +++ b/data/models.go @@ -11,12 +11,29 @@ type Login struct { } type User struct { - Id int `db:"id" validate:"numeric"` + Id int64 `db:"id" validate:"numeric"` Username string `db:"username" validate:"required,max=30"` Email string `db:"email" validate:"required,email,max=128"` Password string `db:"password" validate:"required,min=8,max=64"` } +type Question struct { + Id int64 `db:"id" validate:"numeric"` + Question string `db:"question"` + Result int `db:"result"` + Answer bool // move to session +} + +type Survey struct { + Id int64 `db:"id" validate:"numeric"` + Title string `db:"title"` + Description string `db:"description"` + CreatedOn string `db:"created_on"` + Respondents int `db:"respondents"` + State string `db:"state"` + Questions []Question +} + /* * Example of using the null library to do optional fields. */ @@ -25,32 +42,10 @@ type NullExample struct { HasMaybe null.Int `db:"replying_to" validate:"omitempty,numeric"` } -type Product struct { - Id int `db:"id" validate:"numeric"` - Title string `db:"title" validate:"required"` - Description string `db:"description" validate:"required"` - Price float64 `db:"price" validate:"required"` - Slug string `db:"slug" validate:"required"` -} - -type CartItem struct { - Id int `db:"id" validate:"numeric"` - UserId int `db:"user_id" validate:"numeric,required"` - ProductId int `db:"product_id" validate:"required"` - Quantity int `db:"quantity" validate:"numeric"` -} - -type Cart struct { - Items []CartItem -} - func Models() map[string]reflect.Type { return map[string]reflect.Type{ "user": reflect.TypeFor[User](), - "product": reflect.TypeFor[Product](), - "cart_item": reflect.TypeFor[CartItem](), - "null_example": reflect.TypeFor[NullExample](), + "question": reflect.TypeFor[Question](), + "survey": reflect.TypeFor[Survey](), } } - - diff --git a/features/fakepay/api.go b/features/fakepay/api.go deleted file mode 100644 index 070e133..0000000 --- a/features/fakepay/api.go +++ /dev/null @@ -1,13 +0,0 @@ -package features_fakepay - -import ( - "github.com/gofiber/fiber/v2" -) - -func PostApiPay(c *fiber.Ctx) error { - return c.Redirect("/fakepay/complete") -} - -func SetupApi(app *fiber.App) { - app.Post("/api/fakepay/pay", PostApiPay) -} diff --git a/features/fakepay/db.go b/features/fakepay/db.go deleted file mode 100644 index 3b488aa..0000000 --- a/features/fakepay/db.go +++ /dev/null @@ -1,9 +0,0 @@ -package features_fakepay - -import ( -// "MY/webapp/data" -// _ "github.com/mattn/go-sqlite3" -// sq "github.com/Masterminds/squirrel" -) - - diff --git a/features/fakepay/init.go b/features/fakepay/init.go deleted file mode 100644 index c96dea7..0000000 --- a/features/fakepay/init.go +++ /dev/null @@ -1,10 +0,0 @@ -package features_fakepay - -import ( - "github.com/gofiber/fiber/v2" -) - -func Setup(app *fiber.App) { - SetupApi(app) - SetupViews(app) -} diff --git a/features/fakepay/views.go b/features/fakepay/views.go deleted file mode 100644 index 4614a2a..0000000 --- a/features/fakepay/views.go +++ /dev/null @@ -1,11 +0,0 @@ -package features_fakepay - -import ( - "github.com/gofiber/fiber/v2" - . "MY/webapp/common" -) - -func SetupViews(app *fiber.App) { - err := ConfigViews(app, "views/fakepay") - if err != nil { panic(err) } -} diff --git a/features/init.go b/features/init.go index 1a4a6c1..d5d0327 100644 --- a/features/init.go +++ b/features/init.go @@ -3,9 +3,6 @@ package features import ( "github.com/gofiber/fiber/v2" "MY/webapp/features/email" - "MY/webapp/features/paypal" - "MY/webapp/features/shopping" - "MY/webapp/features/fakepay" "MY/webapp/features/admin" "MY/webapp/features/auth" "MY/webapp/features/survey" @@ -15,8 +12,5 @@ func Setup(app *fiber.App) { features_auth.Setup(app) features_admin.Setup(app) features_email.Setup(app) - features_paypal.Setup(app) - features_shopping.Setup(app) - features_fakepay.Setup(app) features_survey.Setup(app) } diff --git a/features/paypal/api.go b/features/paypal/api.go deleted file mode 100644 index a55fc30..0000000 --- a/features/paypal/api.go +++ /dev/null @@ -1,78 +0,0 @@ -package features_paypal - -import ( - "github.com/gofiber/fiber/v2" - "github.com/plutov/paypal/v4" - "os" - "context" - . "MY/webapp/common" - config "MY/webapp/config" - "fmt" -) - -func CreatePaypal() (*paypal.Client, error) { - return paypal.NewClient( - config.Settings.Paypal.ClientID, - config.Settings.Paypal.SecretID, - config.Settings.Paypal.URL) // or paypal.APIBaseLive -} - -func PostApiOrder(c *fiber.Ctx) error { - pay, err := CreatePaypal() - - if err != nil { return IfErrNil(err, c) } - - pay.SetLog(os.Stdout) - - units := []paypal.PurchaseUnitRequest{ - { - ReferenceID: "myinternalid1", - Amount: &paypal.PurchaseUnitAmount{ - Currency: "USD", - Value: "10.99", - }, - Description: "Product description", - Items: []paypal.Item{ - { - Name: "Learn Go the Hard Way", - UnitAmount: &paypal.Money{ - Currency: "USD", - Value: "10.99", - }, - Quantity: "1", - }, - }, - }, - } - - source := &paypal.PaymentSource{} - appCtx := &paypal.ApplicationContext{} - - order, err := pay.CreateOrder(context.TODO(), paypal.OrderIntentCapture, units, source, appCtx) - - fmt.Println("ORDER", order) - - return c.JSON(order) -} - -func PostApiOrderCapture(c *fiber.Ctx) error { - orderID := c.Params("orderID") - - fmt.Println("POST ORDER CAPTURE", orderID) - pay, err := CreatePaypal() - if err != nil { return IfErrNil(err, c) } - - capture, err := pay.CaptureOrder(context.TODO(), orderID, paypal.CaptureOrderRequest{}) - if err != nil { return IfErrNil(err, c) } - - fmt.Println("CAPTURE", capture) - - return c.JSON(fiber.Map{"status": "ok"}) -} - -func SetupApi(app *fiber.App) { - app.Post("/api/paypal/order", PostApiOrder) - app.Post("/api/paypal/order/:orderID/capture", PostApiOrderCapture) -} - - diff --git a/features/paypal/api.js b/features/paypal/api.js deleted file mode 100644 index f8c704e..0000000 --- a/features/paypal/api.js +++ /dev/null @@ -1,160 +0,0 @@ -import express from "express"; -import "dotenv/config"; -import { - ApiError, - CheckoutPaymentIntent, - Client, - Environment, - LogLevel, - OrdersController, - PaymentsController, - PaypalExperienceLandingPage, - PaypalExperienceUserAction, - ShippingPreference, -} from "@paypal/paypal-server-sdk"; -import bodyParser from "body-parser"; - -const app = express(); -app.use(bodyParser.json()); - -const { - PAYPAL_CLIENT_ID, - PAYPAL_CLIENT_SECRET, - PORT = 8080, -} = process.env; - -const client = new Client({ - clientCredentialsAuthCredentials: { - oAuthClientId: PAYPAL_CLIENT_ID, - oAuthClientSecret: PAYPAL_CLIENT_SECRET, - }, - timeout: 0, - environment: Environment.Sandbox, - logging: { - logLevel: LogLevel.Info, - logRequest: { logBody: true }, - logResponse: { logHeaders: true }, - }, -}); - -const ordersController = new OrdersController(client); -const paymentsController = new PaymentsController(client); - - -/** - * Create an order to start the transaction. - * @see https://developer.paypal.com/docs/api/orders/v2/#orders_create - */ -const createOrder = async (cart) => { - const collect = { - body: { - intent: "CAPTURE", - purchaseUnits: [ - { - amount: { - currencyCode: "USD", - value: "100", - breakdown: { - itemTotal: { - currencyCode: "USD", - value: "100", - }, - }, - }, - // lookup item details in `cart` from database - items: [ - { - name: "T-Shirt", - unitAmount: { - currencyCode: "USD", - value: "100", - }, - quantity: "1", - description: "Super Fresh Shirt", - sku: "sku01", - }, - ], - }, - ], - }, - prefer: "return=minimal", - }; - - - try { - const { body, ...httpResponse } = await ordersController.createOrder( - collect - ); - // Get more response info... - // const { statusCode, headers } = httpResponse; - return { - jsonResponse: JSON.parse(body), - httpStatusCode: httpResponse.statusCode, - }; - } catch (error) { - if (error instanceof ApiError) { - // const { statusCode, headers } = error; - throw new Error(error.message); - } - } -}; - -// createOrder route -app.post("/api/orders", async (req, res) => { - try { - // use the cart information passed from the front-end to calculate the order amount detals - const { cart } = req.body; - const { jsonResponse, httpStatusCode } = await createOrder(cart); - res.status(httpStatusCode).json(jsonResponse); - } catch (error) { - console.error("Failed to create order:", error); - res.status(500).json({ error: "Failed to create order." }); - } -}); - - -/** - * Capture payment for the created order to complete the transaction. - * @see https://developer.paypal.com/docs/api/orders/v2/#orders_capture - */ -const captureOrder = async (orderID) => { - const collect = { - id: orderID, - prefer: "return=minimal", - }; - - try { - const { body, ...httpResponse } = await ordersController.captureOrder( - collect - ); - // Get more response info... - // const { statusCode, headers } = httpResponse; - return { - jsonResponse: JSON.parse(body), - httpStatusCode: httpResponse.statusCode, - }; - } catch (error) { - if (error instanceof ApiError) { - // const { statusCode, headers } = error; - throw new Error(error.message); - } - } -}; - -// captureOrder route -app.post("/api/orders/:orderID/capture", async (req, res) => { - try { - const { orderID } = req.params; - const { jsonResponse, httpStatusCode } = await captureOrder(orderID); - res.status(httpStatusCode).json(jsonResponse); - } catch (error) { - console.error("Failed to create order:", error); - res.status(500).json({ error: "Failed to capture order." }); - } -}); - - -app.listen(PORT, () => { - console.log(`Node server listening at http://localhost:${PORT}/`); -}); - diff --git a/features/paypal/db.go b/features/paypal/db.go deleted file mode 100644 index abcf93a..0000000 --- a/features/paypal/db.go +++ /dev/null @@ -1,9 +0,0 @@ -package features_paypal - -import ( -// "MY/webapp/data" -// _ "github.com/mattn/go-sqlite3" -// sq "github.com/Masterminds/squirrel" -) - - diff --git a/features/paypal/init.go b/features/paypal/init.go deleted file mode 100644 index 0958d4a..0000000 --- a/features/paypal/init.go +++ /dev/null @@ -1,10 +0,0 @@ -package features_paypal - -import ( - "github.com/gofiber/fiber/v2" -) - -func Setup(app *fiber.App) { - SetupApi(app) - SetupViews(app) -} diff --git a/features/paypal/views.go b/features/paypal/views.go deleted file mode 100644 index 4f8f490..0000000 --- a/features/paypal/views.go +++ /dev/null @@ -1,11 +0,0 @@ -package features_paypal - -import ( - "github.com/gofiber/fiber/v2" - . "MY/webapp/common" -) - -func SetupViews(app *fiber.App) { - err := ConfigViews(app, "views/paypal") - if err != nil { panic(err) } -} diff --git a/features/shopping/api.go b/features/shopping/api.go deleted file mode 100644 index 299de2b..0000000 --- a/features/shopping/api.go +++ /dev/null @@ -1,31 +0,0 @@ -package features_shopping - -import ( - "github.com/gofiber/fiber/v2" - "MY/webapp/data" - _ "github.com/mattn/go-sqlite3" - sq "github.com/Masterminds/squirrel" - "fmt" -) - -func GetApiProducts(c *fiber.Ctx) error { - sql, args, err := sq.Select("*").From("Product").ToSql() - - fmt.Println("SQL:", sql, args, err) - - return data.SelectJson[data.Product](c, err, sql, args...) -} - -func GetApiCart(c *fiber.Ctx) error { - return c.JSON(fiber.Map{}) -} - -func GetApiCartRemove(c *fiber.Ctx) error { - return c.Redirect("/shopping/checkout") -} - -func SetupApi(app *fiber.App) { - app.Get("/api/shopping/products", GetApiProducts) - app.Get("/api/shopping/cart", GetApiCart) - app.Get("/api/shopping/cart/remove/:item_id", GetApiCartRemove) -} diff --git a/features/shopping/db.go b/features/shopping/db.go deleted file mode 100644 index dc88ed5..0000000 --- a/features/shopping/db.go +++ /dev/null @@ -1,9 +0,0 @@ -package features_shopping - -import ( -// "MY/webapp/data" -// _ "github.com/mattn/go-sqlite3" -// sq "github.com/Masterminds/squirrel" -) - - diff --git a/features/shopping/init.go b/features/shopping/init.go deleted file mode 100644 index 520b0fa..0000000 --- a/features/shopping/init.go +++ /dev/null @@ -1,10 +0,0 @@ -package features_shopping - -import ( - "github.com/gofiber/fiber/v2" -) - -func Setup(app *fiber.App) { - SetupApi(app) - SetupViews(app) -} diff --git a/features/shopping/views.go b/features/shopping/views.go deleted file mode 100644 index 0c8af99..0000000 --- a/features/shopping/views.go +++ /dev/null @@ -1,11 +0,0 @@ -package features_shopping - -import ( - "github.com/gofiber/fiber/v2" - . "MY/webapp/common" -) - -func SetupViews(app *fiber.App) { - err := ConfigViews(app, "views/shopping") - if err != nil { panic(err) } -} diff --git a/features/survey/db.go b/features/survey/db.go index 9f5a74b..9b985f0 100644 --- a/features/survey/db.go +++ b/features/survey/db.go @@ -1,9 +1,44 @@ package features_survey import ( -// "MY/webapp/data" + "MY/webapp/data" // _ "github.com/mattn/go-sqlite3" // sq "github.com/Masterminds/squirrel" ) - +func QueryData() data.Survey { + return data.Survey{ + Title: "Sample Survey", + Description: "The description.", + CreatedOn: "01/20/26", + Respondents: 200, + State: "Finished", + Questions: []data.Question{ + { + Id: 1, + Question: "Do you like HTML?", + Answer: true, + // TODO: just for prototyping, needs to be a better model + Result: 20, + }, + { + Id: 2, + Question: "Do you like CSS?", + Answer: false, + Result: 11, + }, + { + Id: 3, + Question: "Do you like JavaScript?", + Answer: true, + Result: 43, + }, + { + Id: 4, + Question: "Are you a programmer?", + Answer: true, + Result: 28, + }, + }, + } +} diff --git a/features/survey/views.go b/features/survey/views.go index 15e9451..da3c842 100644 --- a/features/survey/views.go +++ b/features/survey/views.go @@ -5,60 +5,6 @@ import ( // . "MY/webapp/common" ) -type Question struct { - Id int64 - Question string - Answer bool - Result int -} - -type Survey struct { - Id int64 - Title string - Description string - Date string - Respondents int - State string - Questions []Question -} - -func QueryData() Survey { - return Survey{ - Title: "Sample Survey", - Description: "The description.", - Date: "01/20/26", - Respondents: 200, - State: "Finished", - Questions: []Question{ - { - Id: 1, - Question: "Do you like HTML?", - Answer: true, - // TODO: just for prototyping, needs to be a better model - Result: 20, - }, - { - Id: 2, - Question: "Do you like CSS?", - Answer: false, - Result: 11, - }, - { - Id: 3, - Question: "Do you like JavaScript?", - Answer: true, - Result: 43, - }, - { - Id: 4, - Question: "Are you a programmer?", - Answer: true, - Result: 28, - }, - }, - } -} - func GetPageIndex(c *fiber.Ctx) error { return c.Render("survey/index", fiber.Map{ "Survey": QueryData(), diff --git a/migrations/20260805143821_surveys.sql b/migrations/20260805143821_surveys.sql new file mode 100644 index 0000000..1d23b61 --- /dev/null +++ b/migrations/20260805143821_surveys.sql @@ -0,0 +1,21 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE question ( + id INTEGER PRIMARY KEY, + question TEXT UNIQUE NOT NULL, + result INTEGER DEFAULT 0); + +CREATE TABLE survey ( + id INTEGER PRIMARY KEY, + title TEXT UNIQUE NOT NULL, + description TEXT UNIQUE NOT NULL, + created_on DATETIME DEFAULT CURRENT_TIMESTAMP, + respondents INTEGER DEFAULT 0, + active BOOLEAN INTEGER DEFAULT false); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE IF EXISTS question; +DROP TABLE IF EXISTS survey; +-- +goose StatementEnd diff --git a/migrations/20260112190943_shopping_init.sql b/migrations/saved/20260112190943_shopping_init.sql similarity index 100% rename from migrations/20260112190943_shopping_init.sql rename to migrations/saved/20260112190943_shopping_init.sql diff --git a/migrations/20260112195518_shopping_cart.sql b/migrations/saved/20260112195518_shopping_cart.sql similarity index 100% rename from migrations/20260112195518_shopping_cart.sql rename to migrations/saved/20260112195518_shopping_cart.sql