feat: implement Live/Remix filtering and add CI/CD pipeline
Some checks failed
Build and Push Docker Image / build (pull_request) Failing after 38s

This commit includes:

1. Live/Remix Filtering Feature:
   - Added ignore_live and ignore_remix columns to artist_settings table (migration 010)
   - Updated ArtistSettings struct with IgnoreLive and IgnoreRemix fields
   - Modified SaveArtistSettings and UpdateArtistSettings to handle new fields
   - Extended FilterOptions struct with IgnoreLive and IgnoreRemix
   - Updated ApplyTypeToggles and ApplyTypeTogglesToReleaseGroups to filter Live/Remix types
   - Added toggleIgnoreLive and toggleIgnoreRemix handlers in web layer
   - Updated ArtistData view model and artist.html template with new toggle UI
   - Comprehensive test coverage for all new functionality

2. CI/CD Pipeline with Gitea Actions:
   - Added .gitea/workflows/docker-build.yml for automated Docker builds
   - Workflow triggers on pushes to main/master and tags, plus PRs
   - Runs Go tests before building
   - Builds and pushes multi-architecture Docker images to gitea.mrixs.me
   - Includes caching for faster subsequent builds
   - Proper tagging strategy (branch, semver, SHA)
   - CI-CD-GUIDE.md documentation

3. Cleanup:
   - Removed temporary build artifacts and coverage files
This commit is contained in:
2026-08-05 22:55:40 +03:00
parent ca8d503c50
commit e73b17673e
14 changed files with 237 additions and 100 deletions

View File

@@ -16,49 +16,16 @@ type MissingRelease struct {
ReleaseDate string `json:"release_date"`
}
// TypeFilter carries the per-artist type toggles that suppress whole release
// categories from the missing set. It mirrors the ignore_singles /
// ignore_compilations columns on artist_settings.
//
// These toggles are applied at scan/read time (not only when the MusicBrainz
// discography is synced) so a user flipping a toggle takes effect immediately on
// the dashboard, artist page, and Telegram digest — rather than waiting for the
// artist's MusicBrainz cache to expire and the rows to be pruned on the next
// cache-miss re-sync.
//
// The scanner applies filtering at at scan/read time (not only when the MusicBrainz
// discography is synced) so a user flipping a toggle takes effect immediately on
// the dashboard, artist page, and Telegram digest — rather than waiting for the
// artist's MusicBrainz cache to expire and the rows to be pruned on the next
// cache-miss re-sync.
//
// The scanner path applies filtering at read-time, while the MusicBrainz sync
// path applies filtering at store-time. This dual-path approach ensures:
// 1. Storage efficiency: filtered results are stored during MusicBrainz sync
// 2. Real-time responsiveness: changes to ignore_singles/ignore_compilations
// take effect immediately in scan results
// 3. Consistency: both paths use the same filtering logic via
// musicbrainz.ApplyTypeToggles
type TypeFilter struct {
IgnoreSingles bool
IgnoreCompilations bool
}
// suppressed reports whether an external release is dropped by the type toggles.
// FilterIsSuppressed reports whether an external release is dropped by the type toggles.
// A release counts as a Single/Compilation via either its primary Type or its
// secondary types, matching musicbrainz.FilterReleaseGroups so both the
// cache-miss (store-time) and read-time paths agree.
//
// This method reuses the centralized filtering logic from the musicbrainz
// This function reuses the centralized filtering logic from the musicbrainz
// package to ensure consistency between the scanner's read-time filtering
// and the MusicBrainz sync's store-time filtering.
func (f TypeFilter) suppressed(ext database.ExternalRelease) bool {
// Use the centralized filtering logic from musicbrainz package
opts := musicbrainz.FilterOptions{
IgnoreSingles: f.IgnoreSingles,
IgnoreCompilations: f.IgnoreCompilations,
}
filtered := musicbrainz.ApplyTypeToggles([]database.ExternalRelease{ext}, opts)
func FilterIsSuppressed(filter musicbrainz.FilterOptions, ext database.ExternalRelease) bool {
filtered := musicbrainz.ApplyTypeToggles([]database.ExternalRelease{ext}, filter)
return len(filtered) == 0
}
@@ -77,7 +44,7 @@ func (f TypeFilter) suppressed(ext database.ExternalRelease) bool {
// The filter.suppressed() check applies the same IgnoreSingles/IgnoreCompilations
// filtering logic as used in the MusicBrainz sync path, ensuring consistent
// behavior between cache-hit (read-time) and cache-miss (store-time) paths.
func FindMissingReleases(local []database.LocalAlbum, external []database.ExternalRelease, threshold float64, filter TypeFilter) []MissingRelease {
func FindMissingReleases(local []database.LocalAlbum, external []database.ExternalRelease, threshold float64, filter musicbrainz.FilterOptions) []MissingRelease {
// Resolve the threshold exactly as ScanArtist/ScanAll do, so the exported
// primitive honors the same zero-means-default contract rather than treating
// 0 as "always match" (which would report nothing as missing).
@@ -94,7 +61,7 @@ func FindMissingReleases(local []database.LocalAlbum, external []database.Extern
if ext.IsIgnored {
continue
}
if filter.suppressed(ext) {
if FilterIsSuppressed(filter, ext) {
continue
}