package store import ( "database/sql" "time" ) type FileRecord struct { ID string Filename string R2Key string SizeBytes int64 ContentType string UploadedAt time.Time ExpiresAt *time.Time PasswordHash *string DeleteToken string DownloadCount int64 BatchID *string } type Store struct { db *sql.DB } func New(db *sql.DB) *Store { return &Store{db: db} } func (s *Store) Create(f *FileRecord) error { _, err := s.db.Exec( `INSERT INTO files (id, filename, r2_key, size_bytes, content_type, expires_at, password_hash, delete_token, batch_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, f.ID, f.Filename, f.R2Key, f.SizeBytes, f.ContentType, f.ExpiresAt, f.PasswordHash, f.DeleteToken, f.BatchID, ) return err } func (s *Store) Get(id string) (*FileRecord, error) { f := &FileRecord{} err := s.db.QueryRow( `SELECT id, filename, r2_key, size_bytes, content_type, uploaded_at, expires_at, password_hash, delete_token, download_count, batch_id FROM files WHERE id = ?`, id, ).Scan(&f.ID, &f.Filename, &f.R2Key, &f.SizeBytes, &f.ContentType, &f.UploadedAt, &f.ExpiresAt, &f.PasswordHash, &f.DeleteToken, &f.DownloadCount, &f.BatchID) if err != nil { return nil, err } return f, nil } func (s *Store) IncrementDownloads(id string) error { _, err := s.db.Exec(`UPDATE files SET download_count = download_count + 1 WHERE id = ?`, id) return err } func (s *Store) Delete(id string) error { _, err := s.db.Exec(`DELETE FROM files WHERE id = ?`, id) return err } func (s *Store) DeleteByToken(token string) (*FileRecord, error) { f := &FileRecord{} err := s.db.QueryRow( `SELECT id, filename, r2_key, size_bytes, content_type, uploaded_at, expires_at, password_hash, delete_token, download_count, batch_id FROM files WHERE delete_token = ?`, token, ).Scan(&f.ID, &f.Filename, &f.R2Key, &f.SizeBytes, &f.ContentType, &f.UploadedAt, &f.ExpiresAt, &f.PasswordHash, &f.DeleteToken, &f.DownloadCount, &f.BatchID) if err != nil { return nil, err } _, err = s.db.Exec(`DELETE FROM files WHERE delete_token = ?`, token) if err != nil { return nil, err } return f, nil } func (s *Store) ListExpired() ([]*FileRecord, error) { rows, err := s.db.Query( `SELECT id, filename, r2_key, size_bytes, content_type, uploaded_at, expires_at, password_hash, delete_token, download_count, batch_id FROM files WHERE expires_at IS NOT NULL AND expires_at <= datetime('now')`, ) if err != nil { return nil, err } defer rows.Close() var files []*FileRecord for rows.Next() { f := &FileRecord{} if err := rows.Scan(&f.ID, &f.Filename, &f.R2Key, &f.SizeBytes, &f.ContentType, &f.UploadedAt, &f.ExpiresAt, &f.PasswordHash, &f.DeleteToken, &f.DownloadCount, &f.BatchID); err != nil { return nil, err } files = append(files, f) } return files, rows.Err() } func (s *Store) GetBatch(batchID string) ([]*FileRecord, error) { rows, err := s.db.Query( `SELECT id, filename, r2_key, size_bytes, content_type, uploaded_at, expires_at, password_hash, delete_token, download_count, batch_id FROM files WHERE batch_id = ? ORDER BY filename`, batchID, ) if err != nil { return nil, err } defer rows.Close() var files []*FileRecord for rows.Next() { f := &FileRecord{} if err := rows.Scan(&f.ID, &f.Filename, &f.R2Key, &f.SizeBytes, &f.ContentType, &f.UploadedAt, &f.ExpiresAt, &f.PasswordHash, &f.DeleteToken, &f.DownloadCount, &f.BatchID); err != nil { return nil, err } files = append(files, f) } return files, rows.Err() }