# mal

personal anime tracking platform. go/htmx rewrite.

## stack

- go standard library (`net/http`)
- htmx + templ templates
- sqlite + sqlc
- tailwind (dark theme)
- jikan api (myanimelist)

## structure

```
cmd/server/           main entry
internal/
  auth/               sessions, passwords
  database/           sqlc generated, migrations
  handlers/           http handlers by domain
  middleware/         auth, logging
  jikan/              api client
  templates/          templ components
migrations/           sql files
static/               css, js
```

## go patterns

### errors

always handle errors explicitly. wrap with context using `fmt.Errorf`:

```go
if err != nil {
    return fmt.Errorf("failed to fetch user: %w", err)
}
```

use `errors.Is` and `errors.As` to check wrapped errors.

### early returns

reduce nesting. check errors first, return early:

```go
func getUser(id string) (*User, error) {
    if id == "" {
        return nil, ErrInvalidID
    }
    
    user, err := db.FindUser(id)
    if err != nil {
        return nil, err
    }
    
    return user, nil
}
```

### defer for cleanup

always close resources with defer:

```go
resp, err := http.Get(url)
if err != nil {
    return err
}
defer resp.Body.Close()
```

### interfaces

accept interfaces, return structs. keep interfaces small:

```go
type Reader interface {
    Read(p []byte) (n int, err error)
}
```

### naming

- short, lowercase package names: `auth`, `jikan`, `db`
- `MixedCaps` for exports, `mixedCaps` for internal
- getters: `Owner()` not `GetOwner()`
- interfaces: single method = method name + `er` suffix (`Reader`, `Writer`)

### zero values

design structs so zero value is useful:

```go
var buf bytes.Buffer // ready to use, no init needed
buf.WriteString("hello")
```

### composition over inheritance

embed types to compose behavior:

```go
type Handler struct {
    db     *database.Queries
    jikan  *jikan.Client
    logger *slog.Logger
}
```

## htmx

check for htmx requests:

```go
func isHTMX(r *http.Request) bool {
    return r.Header.Get("HX-Request") == "true"
}
```

return partials for htmx, full pages otherwise. use `hx-swap-oob` for multiple updates. trigger toasts with `HX-Trigger` header.

## templ

render components directly to response:

```go
func (h *Handler) Home(w http.ResponseWriter, r *http.Request) {
    templates.HomePage(data).Render(r.Context(), w)
}
```

pass data explicitly. keep components focused. use layouts.

## database

sqlc generates type-safe queries. always use parameterized queries.

tables: `user`, `session`, `account`, `anime`, `watch_list_entry`

watch statuses: `watching`, `completed`, `on_hold`, `dropped`, `plan_to_watch`

## jikan api

rate limit: 3 req/sec max. stagger batch requests. cache in local db.

## commands

```bash
make dev          # hot reload (air)
make build        # binary
make test         # tests
make migrate      # run migrations
make sqlc         # generate code
make create-user  # cli user creation
```

## env

```bash
DATABASE_FILE=mal.db
SESSION_SECRET=min_32_chars_random
PORT=3000
```

## avoid

- panics in handlers
- forgetting `defer resp.Body.Close()`
- unstaggered jikan requests (429 errors)
- globals for config/state
- large monolithic templates
