Per-user podcasts, file uploads, RSS images, Docker support
Build and Deploy / build-and-deploy (push) Failing after 13s
Build and Deploy / build-and-deploy (push) Failing after 13s
- Each user owns a podcast: podcast title/author/image on users table - Episodes scoped by user_id with context-based auth - Audio file upload replaces URL linking, served from /uploads/ - Podcast and per-episode images with itunes:image in RSS - RSS per-user via ?user=N, dashboard shows user-specific feed URL - Settings form for title + author + image per user - Docker multi-stage build (golang:1.25-alpine / alpine:3.21) - Removed PODSTALK_TITLE/AUTHOR env vars
This commit is contained in:
+39
-35
@@ -8,14 +8,18 @@ import (
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int64
|
||||
Email string
|
||||
PasswordHash string
|
||||
CreatedAt time.Time
|
||||
ID int64
|
||||
Email string
|
||||
PasswordHash string
|
||||
PodcastTitle string
|
||||
PodcastImage string
|
||||
PodcastAuthor string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type Episode struct {
|
||||
ID int64
|
||||
UserID int64
|
||||
Title string
|
||||
Description string
|
||||
AudioPath string
|
||||
@@ -48,11 +52,15 @@ func migrate(db *sql.DB) error {
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
podcast_title TEXT NOT NULL DEFAULT '',
|
||||
podcast_image TEXT NOT NULL DEFAULT '',
|
||||
podcast_author TEXT NOT NULL DEFAULT '',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS episodes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
title TEXT NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
audio_path TEXT NOT NULL DEFAULT '',
|
||||
@@ -60,11 +68,6 @@ func migrate(db *sql.DB) error {
|
||||
published_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
`
|
||||
_, err := db.Exec(schema)
|
||||
return err
|
||||
@@ -92,17 +95,34 @@ func (s *Store) CreateUser(email, passwordHash string) (int64, error) {
|
||||
|
||||
func (s *Store) UserByEmail(email string) (*User, error) {
|
||||
u := &User{}
|
||||
err := s.db.QueryRow("SELECT id, email, password_hash, created_at FROM users WHERE email = ?", email).
|
||||
Scan(&u.ID, &u.Email, &u.PasswordHash, &u.CreatedAt)
|
||||
err := s.db.QueryRow("SELECT id, email, password_hash, podcast_title, podcast_image, podcast_author, created_at FROM users WHERE email = ?", email).
|
||||
Scan(&u.ID, &u.Email, &u.PasswordHash, &u.PodcastTitle, &u.PodcastImage, &u.PodcastAuthor, &u.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetUser(id int64) (*User, error) {
|
||||
u := &User{}
|
||||
err := s.db.QueryRow("SELECT id, email, password_hash, podcast_title, podcast_image, podcast_author, created_at FROM users WHERE id = ?", id).
|
||||
Scan(&u.ID, &u.Email, &u.PasswordHash, &u.PodcastTitle, &u.PodcastImage, &u.PodcastAuthor, &u.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateUserPodcast(id int64, title, image, author string) error {
|
||||
_, err := s.db.Exec("UPDATE users SET podcast_title=?, podcast_image=?, podcast_author=? WHERE id=?",
|
||||
title, image, author, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// --- Episodes ---
|
||||
func (s *Store) ListEpisodes() ([]Episode, error) {
|
||||
rows, err := s.db.Query("SELECT id, title, description, audio_path, image_path, published_at, created_at FROM episodes ORDER BY published_at DESC")
|
||||
|
||||
func (s *Store) ListEpisodes(userID int64) ([]Episode, error) {
|
||||
rows, err := s.db.Query("SELECT id, user_id, title, description, audio_path, image_path, published_at, created_at FROM episodes WHERE user_id = ? ORDER BY published_at DESC", userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -111,7 +131,7 @@ func (s *Store) ListEpisodes() ([]Episode, error) {
|
||||
var eps []Episode
|
||||
for rows.Next() {
|
||||
var e Episode
|
||||
if err := rows.Scan(&e.ID, &e.Title, &e.Description, &e.AudioPath, &e.ImagePath, &e.PublishedAt, &e.CreatedAt); err != nil {
|
||||
if err := rows.Scan(&e.ID, &e.UserID, &e.Title, &e.Description, &e.AudioPath, &e.ImagePath, &e.PublishedAt, &e.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eps = append(eps, e)
|
||||
@@ -121,17 +141,17 @@ func (s *Store) ListEpisodes() ([]Episode, error) {
|
||||
|
||||
func (s *Store) GetEpisode(id int64) (*Episode, error) {
|
||||
e := &Episode{}
|
||||
err := s.db.QueryRow("SELECT id, title, description, audio_path, image_path, published_at, created_at FROM episodes WHERE id = ?", id).
|
||||
Scan(&e.ID, &e.Title, &e.Description, &e.AudioPath, &e.ImagePath, &e.PublishedAt, &e.CreatedAt)
|
||||
err := s.db.QueryRow("SELECT id, user_id, title, description, audio_path, image_path, published_at, created_at FROM episodes WHERE id = ?", id).
|
||||
Scan(&e.ID, &e.UserID, &e.Title, &e.Description, &e.AudioPath, &e.ImagePath, &e.PublishedAt, &e.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateEpisode(title, description, audioPath, imagePath string, publishedAt time.Time) (int64, error) {
|
||||
res, err := s.db.Exec("INSERT INTO episodes (title, description, audio_path, image_path, published_at) VALUES (?, ?, ?, ?, ?)",
|
||||
title, description, audioPath, imagePath, publishedAt)
|
||||
func (s *Store) CreateEpisode(userID int64, title, description, audioPath, imagePath string, publishedAt time.Time) (int64, error) {
|
||||
res, err := s.db.Exec("INSERT INTO episodes (user_id, title, description, audio_path, image_path, published_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
userID, title, description, audioPath, imagePath, publishedAt)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -148,19 +168,3 @@ func (s *Store) DeleteEpisode(id int64) error {
|
||||
_, err := s.db.Exec("DELETE FROM episodes WHERE id = ?", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// --- Settings ---
|
||||
|
||||
func (s *Store) GetSetting(key string) (string, error) {
|
||||
var val string
|
||||
err := s.db.QueryRow("SELECT value FROM settings WHERE key = ?", key).Scan(&val)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (s *Store) SetSetting(key, value string) error {
|
||||
_, err := s.db.Exec("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", key, value)
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user