first commit

This commit is contained in:
2026-07-30 09:51:25 +07:00
commit e3aca176b7
82 changed files with 7847 additions and 0 deletions

484
internal/database/seeder.go Normal file
View File

@@ -0,0 +1,484 @@
package database
import (
"cardverse/internal/modules/cardmaster"
"cardverse/internal/modules/seriessetmaster"
"cardverse/internal/modules/user"
"cardverse/internal/pkg/utils"
"encoding/json"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"gorm.io/gorm"
)
func Seed(db *gorm.DB) error {
if err := seedAdminUser(db); err != nil {
return err
}
if err := seedSeriesData(db); err != nil {
return err
}
if err := seedSetData(db); err != nil {
return err
}
if err := seedCardData(db); err != nil {
return err
}
return nil
}
func seedAdminUser(db *gorm.DB) error {
repo := user.NewRepository(db)
const adminEmail = "admin@example.com"
const adminPassword = "admin12345"
existing, _ := repo.FindByEmail(adminEmail)
if existing != nil {
log.Println("seed: admin user sudah ada, skip")
return nil
}
hashed, err := utils.HashPassword(adminPassword)
if err != nil {
return err
}
admin := &user.User{
Name: "Administrator",
Email: adminEmail,
Password: hashed,
Role: user.RoleSuperAdmin,
Status: user.StatusActive,
IsEmailVerified: true,
}
if err := repo.Create(admin); err != nil {
return err
}
log.Printf("seed: admin user berhasil dibuat (email: %s / password: %s)\n", adminEmail, adminPassword)
return nil
}
// seriesJSON merepresentasikan struktur JSON dari file series.json
type seriesJSON struct {
Name string `json:"name"`
ImageLocalPath string `json:"image_local_path"`
ExpansionCount string `json:"expansion_count"`
CardCount string `json:"card_count"`
}
func seedSeriesData(db *gorm.DB) error {
var count int64
db.Model(&seriessetmaster.SeriesSetMaster{}).Where("parent_id IS NULL").Count(&count)
if count > 0 {
log.Println("seed: data series sudah ada, skip")
return nil
}
file, err := os.ReadFile("public/json/series.json")
if err != nil {
return err
}
var rawSeries []seriesJSON
if err := json.Unmarshal(file, &rawSeries); err != nil {
return err
}
records := make([]seriessetmaster.SeriesSetMaster, 0, len(rawSeries))
for _, s := range rawSeries {
expansionCount, _ := strconv.Atoi(s.ExpansionCount)
cardCount, _ := strconv.Atoi(s.CardCount)
records = append(records, seriessetmaster.SeriesSetMaster{
SeriesName: s.Name,
Image: s.ImageLocalPath,
ExpansionCount: expansionCount,
CardCount: cardCount,
})
}
if err := db.Create(&records).Error; err != nil {
return err
}
log.Printf("seed: %d data series berhasil dimasukkan\n", len(records))
return nil
}
// setJSON merepresentasikan struktur JSON dari file set (misal MA1.json)
type setJSON struct {
SetCode string `json:"set_code"`
SetName string `json:"set_name"`
SeriesName string `json:"series_name"`
CoverImageLocalPath string `json:"cover_image_local_path"`
LogoImageLocalPath string `json:"logo_image_local_path"`
ReleaseDate string `json:"release_date"`
TotalCards string `json:"total_cards"`
Cards []json.RawMessage `json:"cards"`
}
const imagesBasePath = "public/images"
// folder yang harus di-skip saat scan series
var skipFolders = map[string]bool{
"elements": true,
"series": true,
}
func seedSetData(db *gorm.DB) error {
var count int64
db.Model(&seriessetmaster.SeriesSetMaster{}).Where("parent_id IS NOT NULL").Count(&count)
if count > 0 {
log.Println("seed: data set sudah ada, skip")
return nil
}
var allSeries []seriessetmaster.SeriesSetMaster
db.Where("parent_id IS NULL").Find(&allSeries)
seriesMap := make(map[string]uint, len(allSeries))
for _, s := range allSeries {
seriesMap[s.SeriesName] = s.ID
}
seriesFolders, err := os.ReadDir(imagesBasePath)
if err != nil {
return err
}
var totalSetsInserted int
for _, seriesFolder := range seriesFolders {
if !seriesFolder.IsDir() || skipFolders[seriesFolder.Name()] {
continue
}
seriesPath := filepath.Join(imagesBasePath, seriesFolder.Name())
setFolders, err := os.ReadDir(seriesPath)
if err != nil {
log.Printf("seed: gagal baca folder %s: %v\n", seriesPath, err)
continue
}
var setRecords []seriessetmaster.SeriesSetMaster
var totalCardsInSeries int
for _, setFolder := range setFolders {
if !setFolder.IsDir() {
continue
}
setPath := filepath.Join(seriesPath, setFolder.Name())
jsonFiles, err := filepath.Glob(filepath.Join(setPath, "*.json"))
if err != nil || len(jsonFiles) == 0 {
continue
}
rawFile, err := os.ReadFile(jsonFiles[0])
if err != nil {
log.Printf("seed: gagal baca file %s: %v\n", jsonFiles[0], err)
continue
}
var setData setJSON
if err := json.Unmarshal(rawFile, &setData); err != nil {
log.Printf("seed: gagal parse JSON %s: %v\n", jsonFiles[0], err)
continue
}
parentID, exists := seriesMap[setData.SeriesName]
if !exists {
log.Printf("seed: series '%s' tidak ditemukan di DB, skip set '%s'\n", setData.SeriesName, setData.SetCode)
continue
}
cardCount := len(setData.Cards)
totalCardsInSeries += cardCount
var releaseDate *time.Time
if setData.ReleaseDate != "" {
parsed, err := time.Parse("2006-01-02", setData.ReleaseDate)
if err == nil {
releaseDate = &parsed
}
}
setRecords = append(setRecords, seriessetmaster.SeriesSetMaster{
ParentID: &parentID,
SetCode: setData.SetCode,
SetName: setData.SetName,
SeriesName: setData.SeriesName,
Image: setData.CoverImageLocalPath,
Logo: setData.LogoImageLocalPath,
ReleaseDate: releaseDate,
TotalCards: cardCount,
})
}
if len(setRecords) == 0 {
continue
}
if err := db.Create(&setRecords).Error; err != nil {
return err
}
seriesName := setRecords[0].SeriesName
if parentID, exists := seriesMap[seriesName]; exists {
db.Model(&seriessetmaster.SeriesSetMaster{}).
Where("id = ?", parentID).
Updates(map[string]interface{}{
"expansion_count": len(setRecords),
"card_count": totalCardsInSeries,
})
}
totalSetsInserted += len(setRecords)
log.Printf("seed: %d set untuk series '%s' berhasil dimasukkan (total kartu: %d)\n",
len(setRecords), seriesName, totalCardsInSeries)
}
log.Printf("seed: total %d data set berhasil dimasukkan\n", totalSetsInserted)
return nil
}
// cardJSON merepresentasikan struktur card dari JSON
type cardJSON struct {
Name string `json:"name"`
Number string `json:"number"`
ImageLocalPath string `json:"image_local_path"`
Stage string `json:"stage"`
Element string `json:"element"`
EvolvesFrom string `json:"evolves_from"`
Illustrator string `json:"illustrator"`
Regulation string `json:"regulation"`
Rarity string `json:"rarity"`
IDPriceChartingEng string `json:"id_price_charting_eng"`
LinkPriceChartingEng string `json:"link_price_charting_eng"`
IDPriceChartingJpn string `json:"id_price_charting_jpn"`
LinkPriceChartingJpn string `json:"link_price_charting_jpn"`
HP json.RawMessage `json:"hp"`
Attacks json.RawMessage `json:"attacks"`
Abilities json.RawMessage `json:"abilities"`
Battle json.RawMessage `json:"battle"`
EvolutionLine json.RawMessage `json:"evolution_line"`
PokedexInfo json.RawMessage `json:"pokedex_info"`
Effects json.RawMessage `json:"effects"`
}
const cardBatchSize = 100
func seedCardData(db *gorm.DB) error {
var count int64
db.Model(&cardmaster.CardMaster{}).Count(&count)
if count > 0 {
log.Println("seed: data card sudah ada, skip")
return nil
}
var allSets []seriessetmaster.SeriesSetMaster
db.Where("parent_id IS NOT NULL").Find(&allSets)
setMap := make(map[string]uint, len(allSets))
for _, s := range allSets {
setMap[s.SetCode] = s.ID
}
seriesFolders, err := os.ReadDir(imagesBasePath)
if err != nil {
return err
}
var totalCardsInserted int
for _, seriesFolder := range seriesFolders {
if !seriesFolder.IsDir() || skipFolders[seriesFolder.Name()] {
continue
}
seriesPath := filepath.Join(imagesBasePath, seriesFolder.Name())
setFolders, err := os.ReadDir(seriesPath)
if err != nil {
continue
}
for _, setFolder := range setFolders {
if !setFolder.IsDir() {
continue
}
setPath := filepath.Join(seriesPath, setFolder.Name())
jsonFiles, err := filepath.Glob(filepath.Join(setPath, "*.json"))
if err != nil || len(jsonFiles) == 0 {
continue
}
rawFile, err := os.ReadFile(jsonFiles[0])
if err != nil {
continue
}
var setData setJSON
if err := json.Unmarshal(rawFile, &setData); err != nil {
continue
}
setID, exists := setMap[setData.SetCode]
if !exists {
continue
}
cardRecords := make([]cardmaster.CardMaster, 0, len(setData.Cards))
for _, rawCard := range setData.Cards {
var c cardJSON
if err := json.Unmarshal(rawCard, &c); err != nil {
continue
}
enrichedHP := enrichHP(c.HP)
enrichedAttacks := enrichAttacks(c.Attacks)
enrichedBattle := enrichBattle(c.Battle)
cardRecords = append(cardRecords, cardmaster.CardMaster{
IDSet: setID,
Name: c.Name,
Number: c.Number,
Image: c.ImageLocalPath,
Stage: c.Stage,
Element: c.Element,
ElementImage: cardmaster.GetElementImage(c.Element),
EvolvesFrom: c.EvolvesFrom,
Illustrator: c.Illustrator,
Regulation: c.Regulation,
Rarity: c.Rarity,
IDPriceChartingEng: c.IDPriceChartingEng,
LinkPriceChartingEng: c.LinkPriceChartingEng,
IDPriceChartingJpn: c.IDPriceChartingJpn,
LinkPriceChartingJpn: c.LinkPriceChartingJpn,
HP: cardmaster.JSONB(enrichedHP),
Attacks: cardmaster.JSONB(enrichedAttacks),
Abilities: cardmaster.JSONB(c.Abilities),
Battle: cardmaster.JSONB(enrichedBattle),
EvolutionLine: cardmaster.JSONB(c.EvolutionLine),
PokedexInfo: cardmaster.JSONB(c.PokedexInfo),
Effects: cardmaster.JSONB(c.Effects),
})
}
if len(cardRecords) == 0 {
continue
}
if err := db.CreateInBatches(&cardRecords, cardBatchSize).Error; err != nil {
return err
}
totalCardsInserted += len(cardRecords)
log.Printf("seed: %d kartu untuk set '%s' berhasil dimasukkan\n", len(cardRecords), setData.SetCode)
}
}
log.Printf("seed: total %d data kartu berhasil dimasukkan\n", totalCardsInserted)
return nil
}
func enrichHP(raw json.RawMessage) json.RawMessage {
if len(raw) == 0 || string(raw) == "null" {
return raw
}
var hp cardmaster.HP
if err := json.Unmarshal(raw, &hp); err != nil {
return raw
}
hp.ElementImage = cardmaster.GetElementImage(hp.Element)
enriched, err := json.Marshal(hp)
if err != nil {
return raw
}
return enriched
}
func enrichAttacks(raw json.RawMessage) json.RawMessage {
if len(raw) == 0 || string(raw) == "null" {
return raw
}
var rawAttacks []struct {
Cost []string `json:"cost"`
Name string `json:"name"`
Damage string `json:"damage,omitempty"`
Description string `json:"description,omitempty"`
}
if err := json.Unmarshal(raw, &rawAttacks); err != nil {
return raw
}
enriched := make([]cardmaster.Attack, 0, len(rawAttacks))
for _, a := range rawAttacks {
costs := make([]cardmaster.AttackCost, 0, len(a.Cost))
for _, c := range a.Cost {
costs = append(costs, cardmaster.AttackCost{
Element: c,
Image: cardmaster.GetElementImage(c),
})
}
enriched = append(enriched, cardmaster.Attack{
Cost: costs,
Name: a.Name,
Damage: a.Damage,
Description: a.Description,
})
}
result, err := json.Marshal(enriched)
if err != nil {
return raw
}
return result
}
func enrichBattle(raw json.RawMessage) json.RawMessage {
if len(raw) == 0 || string(raw) == "null" {
return raw
}
var battle cardmaster.Battle
if err := json.Unmarshal(raw, &battle); err != nil {
return raw
}
for i := range battle.Weakness {
battle.Weakness[i].ElementImage = cardmaster.GetElementImage(battle.Weakness[i].Element)
}
for i := range battle.Resistance {
battle.Resistance[i].ElementImage = cardmaster.GetElementImage(battle.Resistance[i].Element)
}
for i := range battle.Retreat {
battle.Retreat[i].ElementImage = cardmaster.GetElementImage(battle.Retreat[i].Element)
}
enriched, err := json.Marshal(battle)
if err != nil {
return raw
}
return enriched
}
func seriesFolderName(name string) string {
lower := strings.ToLower(name)
lower = strings.ReplaceAll(lower, " & ", "-")
lower = strings.ReplaceAll(lower, " ", "-")
return lower
}