Initial project structure commit.
Static directory for public folders and business logic for the app within internal, split into domain specific folders to keep clear seperation of concerns.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
)
|
||||
|
||||
type DBTX interface {
|
||||
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||
}
|
||||
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
db DBTX
|
||||
}
|
||||
|
||||
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
||||
return &Queries{
|
||||
db: tx,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
|
||||
package db
|
||||
|
||||
type User struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Age int32 `json:"age"`
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type Querier interface {
|
||||
CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
|
||||
DeleteUser(ctx context.Context, id int64) error
|
||||
GetUser(ctx context.Context, id int64) (User, error)
|
||||
ListUsers(ctx context.Context) ([]User, error)
|
||||
UpdateUser(ctx context.Context, arg UpdateUserParams) error
|
||||
}
|
||||
|
||||
var _ Querier = (*Queries)(nil)
|
||||
@@ -0,0 +1,96 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// source: queries.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const createUser = `-- name: CreateUser :one
|
||||
INSERT INTO users (
|
||||
name, age
|
||||
) VALUES (
|
||||
$1, $2
|
||||
)
|
||||
RETURNING id, name, age
|
||||
`
|
||||
|
||||
type CreateUserParams struct {
|
||||
Name string `json:"name"`
|
||||
Age int32 `json:"age"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
|
||||
row := q.db.QueryRow(ctx, createUser, arg.Name, arg.Age)
|
||||
var i User
|
||||
err := row.Scan(&i.ID, &i.Name, &i.Age)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const deleteUser = `-- name: DeleteUser :exec
|
||||
DELETE FROM users
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteUser(ctx context.Context, id int64) error {
|
||||
_, err := q.db.Exec(ctx, deleteUser, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const getUser = `-- name: GetUser :one
|
||||
SELECT id, name, age FROM users
|
||||
WHERE id = $1 LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetUser(ctx context.Context, id int64) (User, error) {
|
||||
row := q.db.QueryRow(ctx, getUser, id)
|
||||
var i User
|
||||
err := row.Scan(&i.ID, &i.Name, &i.Age)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listUsers = `-- name: ListUsers :many
|
||||
SELECT id, name, age FROM users
|
||||
ORDER BY name
|
||||
`
|
||||
|
||||
func (q *Queries) ListUsers(ctx context.Context) ([]User, error) {
|
||||
rows, err := q.db.Query(ctx, listUsers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []User
|
||||
for rows.Next() {
|
||||
var i User
|
||||
if err := rows.Scan(&i.ID, &i.Name, &i.Age); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateUser = `-- name: UpdateUser :exec
|
||||
UPDATE users
|
||||
set name = $2,
|
||||
age = $3
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type UpdateUserParams struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Age int32 `json:"age"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
|
||||
_, err := q.db.Exec(ctx, updateUser, arg.ID, arg.Name, arg.Age)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
// embedded field, for easier access ie store.GetUser
|
||||
*Queries
|
||||
// named field for explicit calles ie db.Begin
|
||||
db *pgxpool.Pool
|
||||
}
|
||||
|
||||
func NewStore(db *pgxpool.Pool) *Store {
|
||||
return &Store{
|
||||
Queries: New(db),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{{define "base"}}
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.Title}}</title>
|
||||
<link rel="stylesheet" href="/static/css/reset.css">
|
||||
<link rel="stylesheet" href="/static/css/index.css">
|
||||
<link rel="stylesheet" href="/static/js/index.js" defer>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<a href="/">home</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{{template "content" .}}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© <span id="year"></span></p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
document.queryselector('#year').innerhtml = new date().getfullyear();
|
||||
</script>
|
||||
</html>
|
||||
{{end}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{{define "content"}}
|
||||
<section class="hero">
|
||||
<h1>NFeeder</h1>
|
||||
</section>
|
||||
{{end}}
|
||||
@@ -0,0 +1,14 @@
|
||||
package web
|
||||
|
||||
import "net/http"
|
||||
|
||||
type HomeData struct {
|
||||
Title string
|
||||
}
|
||||
|
||||
func (s *Server) handleHome() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
data := HomeData{Title: "NFeeder"}
|
||||
view(w, r, "home", data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package web
|
||||
@@ -0,0 +1,28 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
)
|
||||
|
||||
func (s *Server) setupRoutes() *chi.Mux {
|
||||
router := chi.NewRouter()
|
||||
|
||||
router.Use(middleware.Logger)
|
||||
router.Use(middleware.Recoverer)
|
||||
|
||||
// Setup basic file server nothing fancy
|
||||
router.Handle("/static/*", http.StripPrefix("/static", http.FileServer(http.Dir("static"))))
|
||||
|
||||
// Public routes
|
||||
router.Group(func(r chi.Router) {
|
||||
r.Get("/", s.handleHome())
|
||||
})
|
||||
|
||||
//s.router.Get("/", s.handleIndex())
|
||||
//s.router.Post("/users", s.handleCreateUser())
|
||||
|
||||
return router
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"nfeeder/internal/db"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
httpServer *http.Server
|
||||
store *db.Store
|
||||
}
|
||||
|
||||
func NewServer(store *db.Store) *Server {
|
||||
s := &Server {
|
||||
store: store,
|
||||
}
|
||||
|
||||
s.httpServer = &http.Server {
|
||||
Handler: s.setupRoutes(),
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Server) Start(addr string) error {
|
||||
s.httpServer.Addr = addr
|
||||
return s.httpServer.ListenAndServe()
|
||||
}
|
||||
|
||||
func (s *Server) Shutdown(ctx context.Context) error {
|
||||
return s.httpServer.Shutdown(ctx)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Renders a full page by combining the base template with a page template
|
||||
// Parsed together so the page can define blocks needed for base template
|
||||
func render(w http.ResponseWriter, _ *http.Request, page string, data any) {
|
||||
files := []string{
|
||||
filepath.Join("internal", "templates", "layouts", "base.html"),
|
||||
filepath.Join("internal", "templates", "pages", page+".html"),
|
||||
}
|
||||
|
||||
tmpl, err := template.ParseFiles(files...)
|
||||
if err != nil {
|
||||
http.Error(w, "template error: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
|
||||
if err := tmpl.ExecuteTemplate(w, "base", data); err != nil {
|
||||
http.Error(w, "render error: "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func view(w http.ResponseWriter, r *http.Request, page string, data any) {
|
||||
render(w, r, page, data)
|
||||
}
|
||||
Reference in New Issue
Block a user