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:
2026-04-23 08:09:09 +02:00
parent f3ccbc95c1
commit a8862721cd
18 changed files with 1048 additions and 0 deletions
+32
View File
@@ -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,
}
}
+11
View File
@@ -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"`
}
+19
View File
@@ -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)
+96
View File
@@ -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
}
+19
View File
@@ -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,
}
}
+33
View File
@@ -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>&copy; <span id="year"></span></p>
</footer>
</body>
<script>
document.queryselector('#year').innerhtml = new date().getfullyear();
</script>
</html>
{{end}}
+5
View File
@@ -0,0 +1,5 @@
{{define "content"}}
<section class="hero">
<h1>NFeeder</h1>
</section>
{{end}}
+14
View File
@@ -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)
}
}
+1
View File
@@ -0,0 +1 @@
package web
+28
View File
@@ -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
}
+35
View File
@@ -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)
}
+32
View File
@@ -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)
}