diff --git a/.env b/.env new file mode 100644 index 0000000..35a9fbc --- /dev/null +++ b/.env @@ -0,0 +1,36 @@ +COMPOSE_RESTART_POLICY=unless-stopped + +MYSQL_IMAGE=mysql:8.4 +MYSQL_CONTAINER_NAME=social-raiting-mysql +MYSQL_HOST_PORT=3306 +MYSQL_VOLUME_NAME=mysql_data +MYSQL_HEALTHCHECK_INTERVAL=10s +MYSQL_HEALTHCHECK_TIMEOUT=5s +MYSQL_HEALTHCHECK_RETRIES=10 + +MYSQL_DATABASE=social_raiting +MYSQL_USER=social_raiting +MYSQL_PASSWORD=change-db-password +MYSQL_ROOT_PASSWORD=change-root-password + +BACKEND_BUILD_CONTEXT=./backend +BACKEND_DOCKERFILE=Dockerfile +BACKEND_CONTAINER_NAME=social-raiting-backend +BACKEND_HOST_PORT=8080 + +SWAGGER_UI_IMAGE=swaggerapi/swagger-ui +SWAGGER_UI_CONTAINER_NAME=social-raiting-swagger-ui +SWAGGER_UI_HOST_PORT=8081 +SWAGGER_SPEC_PATH=./backend/docs/swagger.yaml + +SERVER_PORT=8080 +JWT_SECRET=replace-with-a-long-random-secret + +DB_HOST=mysql +DB_PORT=3306 +DB_NAME=social_raiting +DB_USER=social_raiting +DB_PASSWORD=change-db-password + +DEFAULT_ADMIN_EMAIL=admin@example.com +DEFAULT_ADMIN_PASSWORD=change-admin-password diff --git a/backend/internal/database/mysql.go b/backend/internal/database/mysql.go index 9229219..89d4f30 100644 --- a/backend/internal/database/mysql.go +++ b/backend/internal/database/mysql.go @@ -1,6 +1,7 @@ package database import ( + "errors" "fmt" "log" "strings" @@ -12,6 +13,7 @@ import ( "gorm.io/driver/mysql" "gorm.io/gorm" + "gorm.io/gorm/clause" ) func Connect(cfg config.Config) *gorm.DB { @@ -74,7 +76,7 @@ func ensureDefaultAdmin(db *gorm.DB, cfg config.Config) { return } - if err != gorm.ErrRecordNotFound { + if !errors.Is(err, gorm.ErrRecordNotFound) { log.Fatalf("failed to check default admin user: %v", err) } @@ -88,7 +90,20 @@ func ensureDefaultAdmin(db *gorm.DB, cfg config.Config) { PasswordHash: string(passwordHash), IsAdmin: true, } - if err := db.Create(&user).Error; err != nil { + if err := db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "email"}}, + DoNothing: true, + }).Create(&user).Error; err != nil { log.Fatalf("failed to create default admin user: %v", err) } + + if err := db.Where("email = ?", email).First(&user).Error; err != nil { + log.Fatalf("failed to load default admin user after bootstrap: %v", err) + } + + if !user.IsAdmin { + if err := db.Model(&user).Update("is_admin", true).Error; err != nil { + log.Fatalf("failed to ensure default admin privileges: %v", err) + } + } } diff --git a/backend/internal/models/user.go b/backend/internal/models/user.go index 1f7cd11..2d7510a 100644 --- a/backend/internal/models/user.go +++ b/backend/internal/models/user.go @@ -4,6 +4,7 @@ import ( "time" "gorm.io/gorm" + "gorm.io/gorm/clause" ) type User struct { @@ -17,7 +18,10 @@ type User struct { } func (u *User) AfterCreate(tx *gorm.DB) error { - return tx.FirstOrCreate(&UserSocialRating{}, UserSocialRating{ - UserID: u.ID, - }).Error + rating := UserSocialRating{UserID: u.ID} + + return tx. + Omit(clause.Associations). + Where("user_id = ?", u.ID). + FirstOrCreate(&rating).Error } diff --git a/docker-compose.yml b/docker-compose.yml index df8531b..9c25e74 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: ports: - "${MYSQL_HOST_PORT}:3306" volumes: - - ${MYSQL_VOLUME_NAME}:/var/lib/mysql + - volume:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"] interval: ${MYSQL_HEALTHCHECK_INTERVAL} @@ -43,4 +43,4 @@ services: - ${SWAGGER_SPEC_PATH}:/usr/share/nginx/html/swagger.yaml:ro volumes: - ${MYSQL_VOLUME_NAME}: + volume: