feat: import review step + admin notifications
Some checks failed
Release / Test backend (push) Failing after 16s
Release / Check ui (push) Failing after 33s
Release / Docker (push) Has been skipped
Release / Gitea Release (push) Has been skipped

- Import page: add file upload with review step before committing
- Backend: add analyze endpoint to preview chapters before import
- Add notifications collection + API for admin alerts
- Add bell icon in header with notification dropdown (admin only)
- Runner creates notification on import completion
- Notifications link to /admin/import for easy review
This commit is contained in:
root
2026-04-09 10:30:36 +05:00
parent bb55afb562
commit ab92bf84bb
7 changed files with 297 additions and 34 deletions

View File

@@ -667,6 +667,46 @@ func (s *Store) CreateImportTask(ctx context.Context, slug, title, fileType, obj
return rec.ID, nil
}
// CreateNotification creates a notification record in PocketBase.
func (s *Store) CreateNotification(ctx context.Context, userID, title, message, link string) error {
payload := map[string]any{
"user_id": userID,
"title": title,
"message": message,
"link": link,
"read": false,
"created": time.Now().UTC().Format(time.RFC3339),
}
return s.pb.post(ctx, "/api/collections/notifications/records", payload, nil)
}
// ListNotifications returns notifications for a user.
func (s *Store) ListNotifications(ctx context.Context, userID string, limit int) ([]map[string]any, error) {
filter := fmt.Sprintf("user_id='%s'", userID)
items, err := s.pb.listAll(ctx, "notifications", filter, "-created")
if err != nil {
return nil, err
}
// Parse each json.RawMessage into a map
results := make([]map[string]any, 0, len(items))
for _, raw := range items {
var m map[string]any
if json.Unmarshal(raw, &m) == nil {
results = append(results, m)
}
}
if limit > 0 && len(results) > limit {
results = results[:limit]
}
return results, nil
}
// MarkNotificationRead marks a notification as read.
func (s *Store) MarkNotificationRead(ctx context.Context, id string) error {
return s.pb.patch(ctx, fmt.Sprintf("/api/collections/notifications/records/%s", id),
map[string]any{"read": true})
}
func (s *Store) CancelTask(ctx context.Context, id string) error {
// Try scraping_tasks first, then audio_jobs, then translation_jobs.
if err := s.pb.patch(ctx, fmt.Sprintf("/api/collections/scraping_tasks/records/%s", id),