Add public feed, admin area, self-registration, visibility & hashtags
Some checks failed
Deploy to NAS / deploy (push) Failing after 26s

- Public feed (/) with infinite scroll via Intersection Observer
- Self-registration (/register)
- Admin area (/admin/entries, /admin/users) with user management
- journal_entries: visibility (public/private) + hashtags fields
- users: is_admin flag
- DB schema updated (recreate DB to apply)
- CI: run go test via docker run (golang:1.25-alpine) — fixes 'go not found'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christoph K.
2026-04-07 20:53:31 +02:00
parent 034d16e059
commit 86627f94b1
20 changed files with 783 additions and 92 deletions

View File

@@ -21,6 +21,7 @@ const sessionDuration = 24 * time.Hour
var ErrInvalidCredentials = errors.New("invalid username or password")
var ErrSessionNotFound = errors.New("session not found or expired")
var ErrUsernameTaken = errors.New("username already taken")
type Store struct {
pool *pgxpool.Pool
@@ -128,6 +129,40 @@ func (s *Store) Logout(ctx context.Context, sessionID string) error {
return nil
}
// Register creates a new user account. Returns ErrUsernameTaken if the username is already in use.
func (s *Store) Register(ctx context.Context, username, password string) error {
hash, err := HashPassword(password)
if err != nil {
return fmt.Errorf("hash password: %w", err)
}
_, err = s.pool.Exec(ctx,
`INSERT INTO users (username, password_hash) VALUES ($1, $2)`,
username, hash,
)
if err != nil && strings.Contains(err.Error(), "unique") {
return ErrUsernameTaken
}
return err
}
// GetUserBySession returns the full user (including is_admin) for a session.
func (s *Store) GetUserBySession(ctx context.Context, sessionID string) (domain.User, error) {
var u domain.User
err := s.pool.QueryRow(ctx,
`SELECT u.user_id, u.username, u.is_admin, u.created_at
FROM sessions s JOIN users u ON s.user_id = u.user_id
WHERE s.session_id = $1 AND s.expires_at > NOW()`,
sessionID,
).Scan(&u.UserID, &u.Username, &u.IsAdmin, &u.CreatedAt)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return domain.User{}, ErrSessionNotFound
}
return domain.User{}, err
}
return u, nil
}
func newSessionID() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {