REST API for managing mobile app pages and widgets.
- Go
- Gin
- PostgreSQL
- GORM
.
|-- .env.example
|-- .gitignore
|-- README.md
|-- go.mod
|-- cmd
| |-- api
| | `-- main.go
| `-- migrate
| `-- main.go
`-- internal
|-- config
| `-- config.go
|-- database
| `-- database.go
|-- handlers
| |-- helpers.go
| |-- page_handler.go
| `-- widget_handler.go
|-- middleware
| |-- logging.go
| `-- recovery.go
|-- migrations
| |-- migrate.go
| `-- sql
| `-- 001_init.sql
|-- models
| |-- page.go
| `-- widget.go
|-- response
| `-- response.go
|-- router
| `-- router.go
`-- validation
`-- validation.go
Copy .env.example and update values:
PORT=8080
DATABASE_URL=postgres://postgres:postgres@localhost:5432/appdrop?sslmode=disable
AUTO_MIGRATE=true
GIN_MODE=debug- Create database:
CREATE DATABASE appdrop;- Run migrations:
go run ./cmd/migrate001_init.sql includes:
pagestable with unique route- partial unique index enforcing single home page (
is_home = true) widgetstable withconfig JSONB- PostgreSQL enum type for widgets (
widget_type:banner,product_grid,text,image,spacer) - foreign key cascade delete (
widgets.page_id -> pages.id ON DELETE CASCADE)
go run ./cmd/apiServer starts on http://localhost:8080.
All errors are returned as:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Page route already exists"
}
}- Page route is unique
- Page name is required and non-empty
- Page route is required and non-empty
- Only one page can have
is_home = true - Home page cannot be deleted
- Widget type must be one of:
bannerproduct_gridtextimagespacer
- Widget config is optional, but if provided it must be valid JSON object
GET /pagesGET /pages/:idPOST /pagesPUT /pages/:idDELETE /pages/:id
POST /pages/:id/widgetsPUT /widgets/:idDELETE /widgets/:idPOST /pages/:id/widgets/reorder
curl -X POST http://localhost:8080/pages \
-H "Content-Type: application/json" \
-d '{
"name": "Home",
"route": "/home",
"is_home": true
}'curl http://localhost:8080/pagescurl http://localhost:8080/pages/<PAGE_ID>curl -X PUT http://localhost:8080/pages/<PAGE_ID> \
-H "Content-Type: application/json" \
-d '{
"name": "Homepage",
"route": "/home",
"is_home": true
}'curl -X POST http://localhost:8080/pages/<PAGE_ID>/widgets \
-H "Content-Type: application/json" \
-d '{
"type": "banner",
"position": 1,
"config": {
"title": "Sale Banner",
"image_url": "https://example.com/banner.jpg"
}
}'curl -X PUT http://localhost:8080/widgets/<WIDGET_ID> \
-H "Content-Type: application/json" \
-d '{
"type": "image",
"position": 2,
"config": {
"url": "https://example.com/image.png"
}
}'curl -X POST http://localhost:8080/pages/<PAGE_ID>/widgets/reorder \
-H "Content-Type: application/json" \
-d '[
"11111111-1111-1111-1111-111111111111",
"22222222-2222-2222-2222-222222222222",
"33333333-3333-3333-3333-333333333333"
]'curl -X DELETE http://localhost:8080/widgets/<WIDGET_ID>curl -X DELETE http://localhost:8080/pages/<PAGE_ID>200 OK201 Created400 Bad Request404 Not Found409 Conflict500 Internal Server Error