Getting the database part to work, but found a bug in the data model reflection.

master
Zed A. Shaw 6 hours ago
parent 28a2c991a2
commit a8a9141ede
  1. 5
      .ozai.json
  2. 45
      data/models.go
  3. 13
      features/fakepay/api.go
  4. 9
      features/fakepay/db.go
  5. 10
      features/fakepay/init.go
  6. 11
      features/fakepay/views.go
  7. 6
      features/init.go
  8. 78
      features/paypal/api.go
  9. 160
      features/paypal/api.js
  10. 9
      features/paypal/db.go
  11. 10
      features/paypal/init.go
  12. 11
      features/paypal/views.go
  13. 31
      features/shopping/api.go
  14. 9
      features/shopping/db.go
  15. 10
      features/shopping/init.go
  16. 11
      features/shopping/views.go
  17. 39
      features/survey/db.go
  18. 54
      features/survey/views.go
  19. 21
      migrations/20260805143821_surveys.sql
  20. 0
      migrations/saved/20260112190943_shopping_init.sql
  21. 0
      migrations/saved/20260112195518_shopping_cart.sql

@ -11,11 +11,6 @@
"Command": "go",
"Args": ["tool", "ssgod", "watch"]
},
"mailhog": {
"URL": "/mailhog",
"Command": "MailHog",
"Args": []
},
"tailwind": {
"URL": "/tailwind",
"Command": "tailwindcss",

@ -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](),
}
}

@ -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)
}

@ -1,9 +0,0 @@
package features_fakepay
import (
// "MY/webapp/data"
// _ "github.com/mattn/go-sqlite3"
// sq "github.com/Masterminds/squirrel"
)

@ -1,10 +0,0 @@
package features_fakepay
import (
"github.com/gofiber/fiber/v2"
)
func Setup(app *fiber.App) {
SetupApi(app)
SetupViews(app)
}

@ -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) }
}

@ -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)
}

@ -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)
}

@ -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}/`);
});

@ -1,9 +0,0 @@
package features_paypal
import (
// "MY/webapp/data"
// _ "github.com/mattn/go-sqlite3"
// sq "github.com/Masterminds/squirrel"
)

@ -1,10 +0,0 @@
package features_paypal
import (
"github.com/gofiber/fiber/v2"
)
func Setup(app *fiber.App) {
SetupApi(app)
SetupViews(app)
}

@ -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) }
}

@ -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)
}

@ -1,9 +0,0 @@
package features_shopping
import (
// "MY/webapp/data"
// _ "github.com/mattn/go-sqlite3"
// sq "github.com/Masterminds/squirrel"
)

@ -1,10 +0,0 @@
package features_shopping
import (
"github.com/gofiber/fiber/v2"
)
func Setup(app *fiber.App) {
SetupApi(app)
SetupViews(app)
}

@ -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) }
}

@ -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,
},
},
}
}

@ -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(),

@ -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
Loading…
Cancel
Save