diff options
| author | Bobby <[email protected]> | 2026-02-04 16:57:56 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2026-02-04 16:57:56 +0530 |
| commit | f97c435046a266b169b63b8c71559096c1b2d3dc (patch) | |
| tree | bac820dfa172654b456695e856ae1743cfac8117 /database | |
| parent | ebf9a2e91d946a28a797c7317cf73536e1f44033 (diff) | |
| download | metachan-f97c435046a266b169b63b8c71559096c1b2d3dc.tar.xz metachan-f97c435046a266b169b63b8c71559096c1b2d3dc.zip | |
Refactor database initialization and migration: update driver handling and streamline migration process
Diffstat (limited to 'database')
| -rw-r--r-- | database/anime.go | 852 | ||||
| -rw-r--r-- | database/database.go | 43 | ||||
| -rw-r--r-- | database/migrate.go | 80 | ||||
| -rw-r--r-- | database/status.go | 18 |
4 files changed, 62 insertions, 931 deletions
diff --git a/database/anime.go b/database/anime.go deleted file mode 100644 index 891896e..0000000 --- a/database/anime.go +++ /dev/null @@ -1,852 +0,0 @@ -package database - -import ( - "fmt" - "metachan/entities" - "metachan/types" - "strings" - "time" - - "gorm.io/gorm" -) - -func GetAnimeByMALID(malID int) (*types.Anime, error) { - var anime entities.Anime - result := DB.Preload("Images"). - Preload("Logos"). - Preload("Covers"). - Preload("Scores"). - Preload("AiringStatus"). - Preload("AiringStatus.From"). - Preload("AiringStatus.To"). - Preload("Broadcast"). - Preload("Genres"). - Preload("Producers"). - Preload("Studios"). - Preload("Licensors"). - Preload("Episodes"). - Preload("Episodes.Titles"). - Preload("Characters"). - Preload("Characters.VoiceActors"). - Preload("AiringSchedule"). - Preload("NextAiringEpisode"). - Preload("Seasons"). - Preload("Seasons.Images"). - Preload("Seasons.Scores"). - Preload("Seasons.AiringStatus"). - Preload("Seasons.AiringStatus.From"). - Preload("Seasons.AiringStatus.To"). - Where("mal_id = ?", malID).First(&anime) - - if result.Error != nil { - return nil, result.Error - } - - return ConvertToTypesAnime(&anime), nil -} - -func SaveAnimeToDatabase(animeData *types.Anime) error { - if animeData == nil { - return fmt.Errorf("anime data is nil") - } - - var tx *gorm.DB = DB.Begin() - if tx.Error != nil { - return tx.Error - } - - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - - var existingAnime entities.Anime - result := tx.Where("mal_id = ?", animeData.MALID).First(&existingAnime) - if result.Error == nil { - // Delete all related records first to avoid UNIQUE constraint errors - tx.Where("anime_id = ?", existingAnime.ID).Delete(&entities.AnimeSingleEpisode{}) - tx.Where("anime_id = ?", existingAnime.ID).Delete(&entities.AnimeCharacter{}) - tx.Where("anime_id = ?", existingAnime.ID).Delete(&entities.ScheduleEpisode{}) - tx.Where("anime_id = ?", existingAnime.ID).Delete(&entities.AnimeGenre{}) - tx.Where("anime_id = ?", existingAnime.ID).Delete(&entities.AnimeProducer{}) - tx.Where("anime_id = ?", existingAnime.ID).Delete(&entities.AnimeStudio{}) - tx.Where("anime_id = ?", existingAnime.ID).Delete(&entities.AnimeLicensor{}) - tx.Where("anime_id = ?", existingAnime.ID).Delete(&entities.AnimeSeason{}) - tx.Where("anime_id = ?", existingAnime.ID).Delete(&entities.AnimeImages{}) - tx.Where("anime_id = ?", existingAnime.ID).Delete(&entities.AnimeLogos{}) - tx.Where("anime_id = ?", existingAnime.ID).Delete(&entities.AnimeCovers{}) - tx.Where("anime_id = ?", existingAnime.ID).Delete(&entities.AnimeScores{}) - tx.Where("anime_id = ?", existingAnime.ID).Delete(&entities.AiringStatus{}) - tx.Where("anime_id = ?", existingAnime.ID).Delete(&entities.AnimeBroadcast{}) - - // Now delete the anime itself - if err := tx.Delete(&existingAnime).Error; err != nil { - tx.Rollback() - return err - } - } - - anime := &entities.Anime{ - MALID: animeData.MALID, - TitleRomaji: animeData.Titles.Romaji, - TitleEnglish: animeData.Titles.English, - TitleJapanese: animeData.Titles.Japanese, - TitleSynonyms: strings.Join(animeData.Titles.Synonyms, ","), - Synopsis: animeData.Synopsis, - Type: string(animeData.Type), - Source: animeData.Source, - Airing: animeData.Airing, - Status: animeData.Status, - Duration: animeData.Duration, - Color: animeData.Color, - Season: animeData.Season, - Year: animeData.Year, - SubbedCount: animeData.Episodes.Subbed, - DubbedCount: animeData.Episodes.Dubbed, - TotalEpisodes: animeData.Episodes.Total, - AiredEpisodes: animeData.Episodes.Aired, - LastUpdated: time.Now(), - } - - // Save images - if animeData.Images.Small != "" || animeData.Images.Large != "" || animeData.Images.Original != "" { - anime.Images = &entities.AnimeImages{ - Small: animeData.Images.Small, - Large: animeData.Images.Large, - Original: animeData.Images.Original, - } - } - - // Save logos - if animeData.Logos.Small != "" || animeData.Logos.Medium != "" || animeData.Logos.Large != "" { - anime.Logos = &entities.AnimeLogos{ - Small: animeData.Logos.Small, - Medium: animeData.Logos.Medium, - Large: animeData.Logos.Large, - XLarge: animeData.Logos.XLarge, - Original: animeData.Logos.Original, - } - } - - // Save covers - if animeData.Covers.Small != "" || animeData.Covers.Large != "" || animeData.Covers.Original != "" { - anime.Covers = &entities.AnimeCovers{ - Small: animeData.Covers.Small, - Large: animeData.Covers.Large, - Original: animeData.Covers.Original, - } - } - - // Save scores - if animeData.Scores.Score > 0 || animeData.Scores.ScoredBy > 0 { - anime.Scores = &entities.AnimeScores{ - Score: animeData.Scores.Score, - ScoredBy: animeData.Scores.ScoredBy, - Rank: animeData.Scores.Rank, - Popularity: animeData.Scores.Popularity, - Members: animeData.Scores.Members, - Favorites: animeData.Scores.Favorites, - } - } - - // Save airing status - if animeData.AiringStatus.String != "" { - airingStatus := &entities.AiringStatus{ - String: animeData.AiringStatus.String, - } - - if animeData.AiringStatus.From.Year > 0 { - airingStatus.From = &entities.AiringStatusDates{ - Day: animeData.AiringStatus.From.Day, - Month: animeData.AiringStatus.From.Month, - Year: animeData.AiringStatus.From.Year, - String: animeData.AiringStatus.From.String, - } - } - - if animeData.AiringStatus.To.Year > 0 { - airingStatus.To = &entities.AiringStatusDates{ - Day: animeData.AiringStatus.To.Day, - Month: animeData.AiringStatus.To.Month, - Year: animeData.AiringStatus.To.Year, - String: animeData.AiringStatus.To.String, - } - } - - anime.AiringStatus = airingStatus - } - - // Save broadcast info - if animeData.Broadcast.String != "" { - anime.Broadcast = &entities.AnimeBroadcast{ - Day: animeData.Broadcast.Day, - Time: animeData.Broadcast.Time, - Timezone: animeData.Broadcast.Timezone, - String: animeData.Broadcast.String, - } - } - - // Save genres - link to master genres instead of creating duplicates - if len(animeData.Genres) > 0 { - anime.Genres = make([]entities.AnimeGenre, 0, len(animeData.Genres)) - for _, genre := range animeData.Genres { - // Check if master genre exists - var masterGenre entities.AnimeGenre - err := DB.Where("genre_id = ? AND anime_id = 0", genre.GenreID).First(&masterGenre).Error - - // Create anime-specific genre link - animeGenre := entities.AnimeGenre{ - Name: genre.Name, - GenreID: genre.GenreID, - URL: genre.URL, - Count: 0, // Count is only for master genres - } - - // If master genre doesn't exist, the link will still work - // Genre sync will create the master genre later - if err != nil { - // Master genre doesn't exist yet, that's okay - animeGenre.Name = genre.Name - animeGenre.URL = genre.URL - } else { - // Use data from master genre for consistency - animeGenre.Name = masterGenre.Name - animeGenre.URL = masterGenre.URL - } - - anime.Genres = append(anime.Genres, animeGenre) - } - } - - // Save producers - if len(animeData.Producers) > 0 { - anime.Producers = make([]entities.AnimeProducer, len(animeData.Producers)) - for i, producer := range animeData.Producers { - anime.Producers[i] = entities.AnimeProducer{ - Name: producer.Name, - ProducerID: producer.ProducerID, - URL: producer.URL, - } - } - } - - // Save studios - if len(animeData.Studios) > 0 { - anime.Studios = make([]entities.AnimeStudio, len(animeData.Studios)) - for i, studio := range animeData.Studios { - anime.Studios[i] = entities.AnimeStudio{ - Name: studio.Name, - StudioID: studio.StudioID, - URL: studio.URL, - } - } - } - - // Save licensors - if len(animeData.Licensors) > 0 { - anime.Licensors = make([]entities.AnimeLicensor, len(animeData.Licensors)) - for i, licensor := range animeData.Licensors { - anime.Licensors[i] = entities.AnimeLicensor{ - Name: licensor.Name, - ProducerID: licensor.ProducerID, - URL: licensor.URL, - } - } - } - - // Save seasons - if len(animeData.Seasons) > 0 { - anime.Seasons = make([]entities.AnimeSeason, len(animeData.Seasons)) - for i, season := range animeData.Seasons { - animeSeason := entities.AnimeSeason{ - MALID: season.MALID, - TitleRomaji: season.Titles.Romaji, - TitleEnglish: season.Titles.English, - TitleJapanese: season.Titles.Japanese, - TitleSynonyms: strings.Join(season.Titles.Synonyms, ","), - Synopsis: season.Synopsis, - Type: string(season.Type), - Source: season.Source, - Airing: season.Airing, - Status: season.Status, - Duration: season.Duration, - Season: season.Season, - Year: season.Year, - Current: season.Current, - } - - // Save season images - if season.Images.Small != "" || season.Images.Large != "" { - animeSeason.Images = &entities.AnimeImages{ - Small: season.Images.Small, - Large: season.Images.Large, - Original: season.Images.Original, - } - } - - // Save season scores - if season.Scores.Score > 0 { - animeSeason.Scores = &entities.AnimeScores{ - Score: season.Scores.Score, - ScoredBy: season.Scores.ScoredBy, - Rank: season.Scores.Rank, - Popularity: season.Scores.Popularity, - Members: season.Scores.Members, - Favorites: season.Scores.Favorites, - } - } - - anime.Seasons[i] = animeSeason - } - } - - if len(animeData.Episodes.Episodes) > 0 { - anime.Episodes = make([]entities.AnimeSingleEpisode, len(animeData.Episodes.Episodes)) - for i, episode := range animeData.Episodes.Episodes { - titles := &entities.EpisodeTitles{ - English: episode.Titles.English, - Japanese: episode.Titles.Japanese, - Romaji: episode.Titles.Romaji, - } - - anime.Episodes[i] = entities.AnimeSingleEpisode{ - EpisodeID: episode.ID, - Description: episode.Description, - Aired: episode.Aired, - Score: episode.Score, - Filler: episode.Filler, - Recap: episode.Recap, - ForumURL: episode.ForumURL, - URL: episode.URL, - ThumbnailURL: episode.ThumbnailURL, - Titles: titles, - } - } - } - - // Save characters data - if len(animeData.Characters) > 0 { - anime.Characters = make([]entities.AnimeCharacter, len(animeData.Characters)) - for i, character := range animeData.Characters { - anime.Characters[i] = entities.AnimeCharacter{ - MALID: character.MALID, - URL: character.URL, - ImageURL: character.ImageURL, - Name: character.Name, - Role: character.Role, - } - - // Save voice actors for this character - if len(character.VoiceActors) > 0 { - anime.Characters[i].VoiceActors = make([]entities.AnimeVoiceActor, len(character.VoiceActors)) - for j, va := range character.VoiceActors { - anime.Characters[i].VoiceActors[j] = entities.AnimeVoiceActor{ - MALID: va.MALID, - URL: va.URL, - Image: va.Image, - Name: va.Name, - Language: va.Language, - } - } - } - } - } - - // Save airing schedule data - if len(animeData.AiringSchedule) > 0 { - anime.AiringSchedule = make([]entities.ScheduleEpisode, len(animeData.AiringSchedule)) - for i, schedule := range animeData.AiringSchedule { - anime.AiringSchedule[i] = entities.ScheduleEpisode{ - AiringAt: schedule.AiringAt, - Episode: schedule.Episode, - IsNext: false, // We'll set this based on next airing episode if available - } - } - } - - // Set next airing episode data - if animeData.NextAiringEpisode.Episode > 0 { - anime.NextAiringEpisode = &entities.NextEpisode{ - AiringAt: animeData.NextAiringEpisode.AiringAt, - Episode: animeData.NextAiringEpisode.Episode, - } - - // Mark the next airing episode in the schedule as IsNext - for i := range anime.AiringSchedule { - if anime.AiringSchedule[i].Episode == animeData.NextAiringEpisode.Episode && - anime.AiringSchedule[i].AiringAt == animeData.NextAiringEpisode.AiringAt { - anime.AiringSchedule[i].IsNext = true - break - } - } - } - - if err := tx.Create(anime).Error; err != nil { - tx.Rollback() - return err - } - - return tx.Commit().Error -} - -func ConvertToTypesAnime(anime *entities.Anime) *types.Anime { - if anime == nil { - return nil - } - - result := &types.Anime{ - MALID: anime.MALID, - Titles: types.AnimeTitles{ - Romaji: anime.TitleRomaji, - English: anime.TitleEnglish, - Japanese: anime.TitleJapanese, - Synonyms: strings.Split(anime.TitleSynonyms, ","), - }, - Synopsis: anime.Synopsis, - Type: types.AniSyncType(anime.Type), - Source: anime.Source, - Airing: anime.Airing, - Status: anime.Status, - Duration: anime.Duration, - Color: anime.Color, - Season: anime.Season, - Year: anime.Year, - Episodes: types.AnimeEpisodes{ - Total: anime.TotalEpisodes, - Aired: anime.AiredEpisodes, - Subbed: anime.SubbedCount, - Dubbed: anime.DubbedCount, - }, - } - - // Convert images - if anime.Images != nil { - result.Images = types.AnimeImages{ - Small: anime.Images.Small, - Large: anime.Images.Large, - Original: anime.Images.Original, - } - } - - // Convert logos - if anime.Logos != nil { - result.Logos = types.AnimeLogos{ - Small: anime.Logos.Small, - Medium: anime.Logos.Medium, - Large: anime.Logos.Large, - XLarge: anime.Logos.XLarge, - Original: anime.Logos.Original, - } - } - - // Convert covers - if anime.Covers != nil { - result.Covers = types.AnimeImages{ - Small: anime.Covers.Small, - Large: anime.Covers.Large, - Original: anime.Covers.Original, - } - } - - // Convert scores - if anime.Scores != nil { - result.Scores = types.AnimeScores{ - Score: anime.Scores.Score, - ScoredBy: anime.Scores.ScoredBy, - Rank: anime.Scores.Rank, - Popularity: anime.Scores.Popularity, - Members: anime.Scores.Members, - Favorites: anime.Scores.Favorites, - } - } - - // Convert airing status - if anime.AiringStatus != nil { - result.AiringStatus = types.AiringStatus{ - String: anime.AiringStatus.String, - } - - if anime.AiringStatus.From != nil { - result.AiringStatus.From = types.AiringStatusDates{ - Day: anime.AiringStatus.From.Day, - Month: anime.AiringStatus.From.Month, - Year: anime.AiringStatus.From.Year, - String: anime.AiringStatus.From.String, - } - } - - if anime.AiringStatus.To != nil { - result.AiringStatus.To = types.AiringStatusDates{ - Day: anime.AiringStatus.To.Day, - Month: anime.AiringStatus.To.Month, - Year: anime.AiringStatus.To.Year, - String: anime.AiringStatus.To.String, - } - } - } - - // Convert broadcast - if anime.Broadcast != nil { - result.Broadcast = types.AnimeBroadcast{ - Day: anime.Broadcast.Day, - Time: anime.Broadcast.Time, - Timezone: anime.Broadcast.Timezone, - String: anime.Broadcast.String, - } - } - - // Convert genres - if len(anime.Genres) > 0 { - result.Genres = make([]types.AnimeGenres, len(anime.Genres)) - for i, genre := range anime.Genres { - result.Genres[i] = types.AnimeGenres{ - Name: genre.Name, - GenreID: genre.GenreID, - URL: genre.URL, - } - } - } - - // Convert producers - if len(anime.Producers) > 0 { - result.Producers = make([]types.AnimeProducer, len(anime.Producers)) - for i, producer := range anime.Producers { - result.Producers[i] = types.AnimeProducer{ - Name: producer.Name, - ProducerID: producer.ProducerID, - URL: producer.URL, - } - } - } - - // Convert studios - if len(anime.Studios) > 0 { - result.Studios = make([]types.AnimeStudio, len(anime.Studios)) - for i, studio := range anime.Studios { - result.Studios[i] = types.AnimeStudio{ - Name: studio.Name, - StudioID: studio.StudioID, - URL: studio.URL, - } - } - } - - // Convert licensors - if len(anime.Licensors) > 0 { - result.Licensors = make([]types.AnimeLicensor, len(anime.Licensors)) - for i, licensor := range anime.Licensors { - result.Licensors[i] = types.AnimeLicensor{ - Name: licensor.Name, - ProducerID: licensor.ProducerID, - URL: licensor.URL, - } - } - } - - // Convert seasons - if len(anime.Seasons) > 0 { - result.Seasons = make([]types.AnimeSeason, len(anime.Seasons)) - for i, season := range anime.Seasons { - result.Seasons[i] = types.AnimeSeason{ - MALID: season.MALID, - Titles: types.AnimeTitles{ - Romaji: season.TitleRomaji, - English: season.TitleEnglish, - Japanese: season.TitleJapanese, - Synonyms: strings.Split(season.TitleSynonyms, ","), - }, - Synopsis: season.Synopsis, - Type: types.AniSyncType(season.Type), - Source: season.Source, - Airing: season.Airing, - Status: season.Status, - Duration: season.Duration, - Season: season.Season, - Year: season.Year, - Current: season.Current, - } - - // Convert season images - if season.Images != nil { - result.Seasons[i].Images = types.AnimeImages{ - Small: season.Images.Small, - Large: season.Images.Large, - Original: season.Images.Original, - } - } - - // Convert season scores - if season.Scores != nil { - result.Seasons[i].Scores = types.AnimeScores{ - Score: season.Scores.Score, - ScoredBy: season.Scores.ScoredBy, - Rank: season.Scores.Rank, - Popularity: season.Scores.Popularity, - Members: season.Scores.Members, - Favorites: season.Scores.Favorites, - } - } - - // Convert season airing status - if season.AiringStatus != nil { - result.Seasons[i].AiringStatus = types.AiringStatus{ - String: season.AiringStatus.String, - } - - if season.AiringStatus.From != nil { - result.Seasons[i].AiringStatus.From = types.AiringStatusDates{ - Day: season.AiringStatus.From.Day, - Month: season.AiringStatus.From.Month, - Year: season.AiringStatus.From.Year, - String: season.AiringStatus.From.String, - } - } - - if season.AiringStatus.To != nil { - result.Seasons[i].AiringStatus.To = types.AiringStatusDates{ - Day: season.AiringStatus.To.Day, - Month: season.AiringStatus.To.Month, - Year: season.AiringStatus.To.Year, - String: season.AiringStatus.To.String, - } - } - } - } - } - - if len(anime.Episodes) > 0 { - result.Episodes.Episodes = make([]types.AnimeSingleEpisode, len(anime.Episodes)) - for i, episode := range anime.Episodes { - episodeData := types.AnimeSingleEpisode{ - ID: episode.EpisodeID, - Description: episode.Description, - Aired: episode.Aired, - Score: episode.Score, - Filler: episode.Filler, - Recap: episode.Recap, - ForumURL: episode.ForumURL, - URL: episode.URL, - ThumbnailURL: episode.ThumbnailURL, - } - - if episode.Titles != nil { - episodeData.Titles = types.EpisodeTitles{ - English: episode.Titles.English, - Japanese: episode.Titles.Japanese, - Romaji: episode.Titles.Romaji, - } - } - - result.Episodes.Episodes[i] = episodeData - } - } - - // Convert characters - if len(anime.Characters) > 0 { - result.Characters = make([]types.AnimeCharacter, len(anime.Characters)) - for i, character := range anime.Characters { - result.Characters[i] = types.AnimeCharacter{ - MALID: character.MALID, - URL: character.URL, - ImageURL: character.ImageURL, - Name: character.Name, - Role: character.Role, - } - - // Convert voice actors for this character - if len(character.VoiceActors) > 0 { - result.Characters[i].VoiceActors = make([]types.AnimeVoiceActor, len(character.VoiceActors)) - for j, va := range character.VoiceActors { - result.Characters[i].VoiceActors[j] = types.AnimeVoiceActor{ - MALID: va.MALID, - URL: va.URL, - Image: va.Image, - Name: va.Name, - Language: va.Language, - } - } - } - } - } - - // Convert airing schedule - if len(anime.AiringSchedule) > 0 { - result.AiringSchedule = make([]types.AnimeAiringEpisode, len(anime.AiringSchedule)) - for i, schedule := range anime.AiringSchedule { - result.AiringSchedule[i] = types.AnimeAiringEpisode{ - AiringAt: schedule.AiringAt, - Episode: schedule.Episode, - } - } - } - - // Convert next airing episode - if anime.NextAiringEpisode != nil { - result.NextAiringEpisode = types.AnimeAiringEpisode{ - AiringAt: anime.NextAiringEpisode.AiringAt, - Episode: anime.NextAiringEpisode.Episode, - } - } - - var mapping entities.AnimeMapping - if err := DB.Where("mal = ?", anime.MALID).First(&mapping).Error; err == nil { - result.Mappings = types.AnimeMappings{ - AniDB: mapping.AniDB, - Anilist: mapping.Anilist, - AnimeCountdown: mapping.AnimeCountdown, - AnimePlanet: mapping.AnimePlanet, - AniSearch: mapping.AniSearch, - IMDB: mapping.IMDB, - Kitsu: mapping.Kitsu, - LiveChart: mapping.LiveChart, - NotifyMoe: mapping.NotifyMoe, - Simkl: mapping.Simkl, - TMDB: mapping.TMDB, - TVDB: mapping.TVDB, - } - } - - return result -} - -func GetAnimeMappingViaMALID(malID int) (*entities.AnimeMapping, error) { - var mapping entities.AnimeMapping - result := DB.Where("mal = ?", malID).First(&mapping) - if result.Error != nil { - return nil, result.Error - } - return &mapping, nil -} - -func GetAnimeMappingViaAnilistID(anilistID int) (*entities.AnimeMapping, error) { - var mapping entities.AnimeMapping - result := DB.Where("anilist = ?", anilistID).First(&mapping) - if result.Error != nil { - return nil, result.Error - } - return &mapping, nil -} - -func GetAnimeMappingsByTVDBID(tvdbID int) ([]entities.AnimeMapping, error) { - var mappings []entities.AnimeMapping - result := DB.Where("tvdb = ?", tvdbID).Find(&mappings) - if result.Error != nil { - return nil, result.Error - } - return mappings, nil -} - -// GetEpisodeStreaming retrieves cached streaming data for an episode -func GetEpisodeStreaming(episodeID string, animeID uint) (*entities.EpisodeStreaming, error) { - var streaming entities.EpisodeStreaming - result := DB.Preload("SubSources"). - Preload("DubSources"). - Where("episode_id = ? AND anime_id = ?", episodeID, animeID). - First(&streaming) - - if result.Error != nil { - return nil, result.Error - } - - // Check if data is stale (older than 7 days) - if time.Since(streaming.LastFetch) > 7*24*time.Hour { - return nil, fmt.Errorf("streaming data is stale") - } - - return &streaming, nil -} - -// GetAllGenres retrieves all master genres (AnimeID = 0) with MAL counts -func GetAllGenres() ([]map[string]interface{}, error) { - var results []entities.AnimeGenre - - err := DB.Where("anime_id = 0"). - Order("count DESC, name ASC"). - Find(&results).Error - - if err != nil { - return nil, err - } - - // Convert to map format - genres := make([]map[string]interface{}, len(results)) - for i, r := range results { - genres[i] = map[string]interface{}{ - "id": r.GenreID, - "name": r.Name, - "url": r.URL, - "count": r.Count, - } - } - - return genres, nil -} - -// SaveEpisodeStreaming saves streaming data to the database -func SaveEpisodeStreaming(episodeID string, animeID uint, subSources, dubSources []types.AnimeStreamingSource) error { - tx := DB.Begin() - if tx.Error != nil { - return tx.Error - } - - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - - // Delete existing streaming data for this episode - var existing entities.EpisodeStreaming - if err := tx.Where("episode_id = ? AND anime_id = ?", episodeID, animeID).First(&existing).Error; err == nil { - if err := tx.Delete(&existing).Error; err != nil { - tx.Rollback() - return err - } - } - - // Create new streaming record - streaming := &entities.EpisodeStreaming{ - EpisodeID: episodeID, - AnimeID: animeID, - LastFetch: time.Now(), - } - - // Save the main record first - if err := tx.Create(streaming).Error; err != nil { - tx.Rollback() - return err - } - - // Save sub sources - for _, source := range subSources { - subSource := entities.EpisodeStreamingSource{ - EpisodeStreamingID: streaming.ID, - URL: source.URL, - Server: source.Server, - Type: source.Type, - } - if err := tx.Create(&subSource).Error; err != nil { - tx.Rollback() - return err - } - streaming.SubSources = append(streaming.SubSources, subSource) - } - - // Save dub sources - for _, source := range dubSources { - dubSource := entities.EpisodeStreamingSource{ - EpisodeStreamingID: streaming.ID, - URL: source.URL, - Server: source.Server, - Type: source.Type, - } - if err := tx.Create(&dubSource).Error; err != nil { - tx.Rollback() - return err - } - streaming.DubSources = append(streaming.DubSources, dubSource) - } - - return tx.Commit().Error -} diff --git a/database/database.go b/database/database.go index 382cb1d..a9215b7 100644 --- a/database/database.go +++ b/database/database.go @@ -1,9 +1,8 @@ package database import ( - "fmt" "metachan/config" - "metachan/types" + "metachan/enums" "metachan/utils/logger" "gorm.io/driver/mysql" @@ -19,37 +18,29 @@ var DB *gorm.DB func init() { var dialector gorm.Dialector - switch config.Config.DatabaseDriver { - case types.Postgres: - dialector = postgres.Open(config.Config.DataSourceName) - case types.SQLite: - dialector = sqlite.Open(config.Config.DataSourceName) - case types.MySQL: - dialector = mysql.Open(config.Config.DataSourceName) - case types.SQLServer: - dialector = sqlserver.Open(config.Config.DataSourceName) + switch enums.DatabaseDriver(config.Database.Driver) { + case enums.SQLite: + dialector = sqlite.Open(config.Database.DSN) + case enums.MySQL: + dialector = mysql.Open(config.Database.DSN) + case enums.Postgres: + dialector = postgres.Open(config.Database.DSN) + case enums.SQLServer: + dialector = sqlserver.Open(config.Database.DSN) default: - logger.Log(fmt.Sprintf("Invalid database driver: %s", config.Config.DatabaseDriver), logger.LogOptions{ - Prefix: "Database", - Level: logger.Error, - Fatal: true, - }) + logger.Fatalf("Database", "Invalid database driver: %s", config.Database.Driver) } var err error DB, err = gorm.Open(dialector, &gorm.Config{ Logger: gormlogger.Default.LogMode(gormlogger.Silent), }) + if err != nil { - logger.Log(fmt.Sprintf("Error connecting to database: %v", err), logger.LogOptions{ - Prefix: "Database", - Level: logger.Error, - Fatal: true, - }) - } else { - logger.Log("Database connection established successfully", logger.LogOptions{ - Prefix: "Database", - Level: logger.Success, - }) + logger.Fatalf("Database", "Error connecting to database: %v", err) } + + logger.Successf("Database", "Database connection established successfully") + + migrate() } diff --git a/database/migrate.go b/database/migrate.go index 9e7c1b9..9dad67f 100644 --- a/database/migrate.go +++ b/database/migrate.go @@ -1,58 +1,56 @@ package database import ( - "fmt" "metachan/entities" "metachan/utils/logger" ) -func AutoMigrate() { +func migrate() { err := DB.AutoMigrate( - // Base entities + // Task entities &entities.TaskLog{}, - &entities.AnimeMapping{}, + &entities.TaskStatus{}, - // Anime entities - &entities.Anime{}, - &entities.AnimeImages{}, - &entities.AnimeLogos{}, - &entities.AnimeCovers{}, - &entities.AnimeScores{}, - &entities.AiringStatusDates{}, + // Mapping entity + &entities.Mapping{}, + + // Meta entities (shared/reusable) + &entities.Title{}, + &entities.Scores{}, + &entities.Date{}, &entities.AiringStatus{}, - &entities.AnimeBroadcast{}, - &entities.AnimeGenre{}, - &entities.AnimeProducer{}, - &entities.AnimeStudio{}, - &entities.AnimeLicensor{}, + &entities.Broadcast{}, + &entities.Images{}, + &entities.Logos{}, + &entities.ExternalURL{}, + &entities.SimpleTitle{}, + &entities.SimpleImage{}, + + // Genre entity + &entities.Genre{}, + + // Producer entity + &entities.Producer{}, + + // Anime entity + &entities.Anime{}, + + // Episode entities + &entities.Episode{}, + &entities.StreamingSource{}, + &entities.EpisodeSchedule{}, &entities.NextEpisode{}, - &entities.ScheduleEpisode{}, - &entities.EpisodeTitles{}, - &entities.AnimeSingleEpisode{}, - &entities.AnimeCharacter{}, - &entities.AnimeVoiceActor{}, - &entities.AnimeSeason{}, - - // Streaming entities - &entities.EpisodeStreaming{}, - &entities.EpisodeStreamingSource{}, + + // Season entity + &entities.Season{}, + + // Character/Persona entities + &entities.Character{}, + &entities.VoiceActor{}, ) if err != nil { - logger.Log(fmt.Sprintf("Failed to migrate database: %v", err), logger.LogOptions{ - Level: logger.Error, - Prefix: "Database", - }) - panic(err) + logger.Fatalf("Database", "Error during database migration: %v", err) } - logger.Log("Database migration completed successfully", logger.LogOptions{ - Level: logger.Info, - Prefix: "Database", - }) -} - -// Migrate creates and migrations all tables -func Migrate() { - // Use AutoMigrate to ensure consistent behavior - AutoMigrate() + logger.Successf("Database", "Database migration completed successfully") } diff --git a/database/status.go b/database/status.go index 48b94d5..10abe9b 100644 --- a/database/status.go +++ b/database/status.go @@ -2,34 +2,28 @@ package database import ( "context" - "fmt" "metachan/utils/logger" "time" ) -func DatabaseConnectionStatus() bool { +func GetConnectionStatus() bool { if DB == nil { return false } - sqlDB, err := DB.DB() + instance, err := DB.DB() + if err != nil { - logger.Log(fmt.Sprintf("Unable to get SQL DB: %v", err), logger.LogOptions{ - Prefix: "Database", - Level: logger.Error, - }) + logger.Errorf("Database", "Failed to get DB instance: %v", err) return false } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - err = sqlDB.PingContext(ctx) + err = instance.PingContext(ctx) if err != nil { - logger.Log(fmt.Sprintf("Database connection error: %v", err), logger.LogOptions{ - Prefix: "Database", - Level: logger.Error, - }) + logger.Errorf("Database", "Database connection error: %v", err) return false } return true |
