From 12085698c8b7e4a364522f5d951265416ba1f481 Mon Sep 17 00:00:00 2001
From: viktorstrate <viktorstrate@gmail.com>
Date: Sat, 6 Nov 2021 15:41:25 +0100
Subject: [PATCH] Refactoring + disable cleanup tests for sqlite

---
 api/database/database.go                      | 22 ++++++------
 api/database/drivers/database_drivers.go      | 36 ++++++++++++++-----
 api/database/helpers.go                       | 12 +++----
 api/graphql/models/actions/search_actions.go  |  3 +-
 .../models/actions/share_token_actions.go     |  5 +--
 .../models/actions/timeline_actions.go        |  7 ++--
 api/graphql/models/face_detection.go          |  7 ++--
 api/graphql/models/site_info.go               |  6 ++--
 api/graphql/models/site_info_test.go          |  2 +-
 api/graphql/resolvers/scanner.go              |  2 +-
 api/routes/downloads.go                       |  3 +-
 api/scanner/cleanup_media.go                  |  4 +--
 api/scanner/cleanup_media_test.go             |  7 +++-
 13 files changed, 72 insertions(+), 44 deletions(-)

diff --git a/api/database/database.go b/api/database/database.go
index b13564d..2d3b214 100644
--- a/api/database/database.go
+++ b/api/database/database.go
@@ -72,8 +72,8 @@ func GetSqliteAddress(path string) (*url.URL, error) {
 
 func ConfigureDatabase(config *gorm.Config) (*gorm.DB, error) {
 	var databaseDialect gorm.Dialector
-	switch drivers.DatabaseDriver() {
-	case drivers.DatabaseDriverMysql:
+	switch drivers.DatabaseDriverFromEnv() {
+	case drivers.MYSQL:
 		mysqlAddress, err := GetMysqlAddress(utils.EnvMysqlURL.GetValue())
 		if err != nil {
 			return nil, err
@@ -81,7 +81,7 @@ func ConfigureDatabase(config *gorm.Config) (*gorm.DB, error) {
 		log.Printf("Connecting to MYSQL database: %s", mysqlAddress)
 		databaseDialect = gorm_mysql.Open(mysqlAddress)
 
-	case drivers.DatabaseDriverSqlite:
+	case drivers.SQLITE:
 		sqliteAddress, err := GetSqliteAddress(utils.EnvSqlitePath.GetValue())
 		if err != nil {
 			return nil, err
@@ -89,7 +89,7 @@ func ConfigureDatabase(config *gorm.Config) (*gorm.DB, error) {
 		log.Printf("Opening SQLITE database: %s", sqliteAddress)
 		databaseDialect = sqlite.Open(sqliteAddress.String())
 
-	case drivers.DatabaseDriverPostgres:
+	case drivers.POSTGRES:
 		postgresAddress, err := GetPostgresAddress(utils.EnvPostgresURL.GetValue())
 		if err != nil {
 			return nil, err
@@ -104,7 +104,7 @@ func ConfigureDatabase(config *gorm.Config) (*gorm.DB, error) {
 	}
 
 	// Manually enable foreign keys for sqlite, as this isn't done by default
-	if drivers.DatabaseDriver() == drivers.DatabaseDriverSqlite {
+	if drivers.SQLITE.MatchDatabase(db) {
 		db.Exec("PRAGMA foreign_keys = ON")
 	}
 
@@ -199,9 +199,9 @@ func MigrateDatabase(db *gorm.DB) error {
 func ClearDatabase(db *gorm.DB) error {
 	err := db.Transaction(func(tx *gorm.DB) error {
 
-		db_driver := drivers.DatabaseDriver()
+		db_driver := drivers.DatabaseDriverFromEnv()
 
-		if db_driver == drivers.DatabaseDriverMysql {
+		if db_driver == drivers.MYSQL {
 			if err := tx.Exec("SET FOREIGN_KEY_CHECKS = 0;").Error; err != nil {
 				return err
 			}
@@ -213,15 +213,15 @@ func ClearDatabase(db *gorm.DB) error {
 			table := dry_run.Find(model).Statement.Table
 
 			switch db_driver {
-			case drivers.DatabaseDriverPostgres:
+			case drivers.POSTGRES:
 				if err := tx.Exec(fmt.Sprintf("TRUNCATE TABLE %s CASCADE", table)).Error; err != nil {
 					return err
 				}
-			case drivers.DatabaseDriverMysql:
+			case drivers.MYSQL:
 				if err := tx.Exec(fmt.Sprintf("TRUNCATE TABLE %s", table)).Error; err != nil {
 					return err
 				}
-			case drivers.DatabaseDriverSqlite:
+			case drivers.SQLITE:
 				if err := tx.Exec(fmt.Sprintf("DELETE FROM %s", table)).Error; err != nil {
 					return err
 				}
@@ -229,7 +229,7 @@ func ClearDatabase(db *gorm.DB) error {
 
 		}
 
-		if db_driver == drivers.DatabaseDriverMysql {
+		if db_driver == drivers.MYSQL {
 			if err := tx.Exec("SET FOREIGN_KEY_CHECKS = 1;").Error; err != nil {
 				return err
 			}
diff --git a/api/database/drivers/database_drivers.go b/api/database/drivers/database_drivers.go
index f430522..a88ee0d 100644
--- a/api/database/drivers/database_drivers.go
+++ b/api/database/drivers/database_drivers.go
@@ -4,32 +4,52 @@ import (
 	"strings"
 
 	"github.com/photoview/photoview/api/utils"
+	"gorm.io/gorm"
 )
 
 // DatabaseDriverType represents the name of a database driver
 type DatabaseDriverType string
 
 const (
-	DatabaseDriverMysql    DatabaseDriverType = "mysql"
-	DatabaseDriverSqlite   DatabaseDriverType = "sqlite"
-	DatabaseDriverPostgres DatabaseDriverType = "postgres"
+	MYSQL    DatabaseDriverType = "mysql"
+	SQLITE   DatabaseDriverType = "sqlite"
+	POSTGRES DatabaseDriverType = "postgres"
 )
 
-func DatabaseDriver() DatabaseDriverType {
+func DatabaseDriverFromEnv() DatabaseDriverType {
 
 	var driver DatabaseDriverType
 	driverString := strings.ToLower(utils.EnvDatabaseDriver.GetValue())
 
 	switch driverString {
 	case "mysql":
-		driver = DatabaseDriverMysql
+		driver = MYSQL
 	case "sqlite":
-		driver = DatabaseDriverSqlite
+		driver = SQLITE
 	case "postgres":
-		driver = DatabaseDriverPostgres
+		driver = POSTGRES
 	default:
-		driver = DatabaseDriverMysql
+		driver = MYSQL
 	}
 
 	return driver
 }
+
+func (driver DatabaseDriverType) MatchDatabase(db *gorm.DB) bool {
+	return db.Dialector.Name() == string(driver)
+}
+
+func GetDatabaseDriverType(db *gorm.DB) (driver DatabaseDriverType) {
+	switch db.Dialector.Name() {
+	case "mysql":
+		driver = MYSQL
+	case "sqlite":
+		driver = SQLITE
+	case "postgres":
+		driver = POSTGRES
+	default:
+		driver = MYSQL
+	}
+
+	return
+}
diff --git a/api/database/helpers.go b/api/database/helpers.go
index 4296b9c..a9e194b 100644
--- a/api/database/helpers.go
+++ b/api/database/helpers.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"log"
 
+	"github.com/photoview/photoview/api/database/drivers"
 	"gorm.io/gorm"
 )
 
@@ -22,12 +23,10 @@ func DateExtract(db *gorm.DB, component DateComponent, attribute string) string
 
 	var result string
 
-	switch db.Dialector.Name() {
-	case "mysql", "postgres":
+	switch drivers.GetDatabaseDriverType(db) {
+	case drivers.MYSQL, drivers.POSTGRES:
 		result = fmt.Sprintf("EXTRACT(%s FROM %s)", component, attribute)
-		break
-	case "sqlite":
-
+	case drivers.SQLITE:
 		var sqliteFormatted string
 		switch component {
 		case DateCompYear:
@@ -39,9 +38,8 @@ func DateExtract(db *gorm.DB, component DateComponent, attribute string) string
 		}
 
 		result = fmt.Sprintf("CAST(strftime('%s', %s) AS INTEGER)", sqliteFormatted, attribute)
-		break
 	default:
-		log.Panicf("unsupported database backend: %s", db.Dialector.Name())
+		log.Panicf("unsupported database backend: %s", drivers.GetDatabaseDriverType(db))
 	}
 
 	return result
diff --git a/api/graphql/models/actions/search_actions.go b/api/graphql/models/actions/search_actions.go
index be64980..e1f8124 100644
--- a/api/graphql/models/actions/search_actions.go
+++ b/api/graphql/models/actions/search_actions.go
@@ -3,6 +3,7 @@ package actions
 import (
 	"strings"
 
+	"github.com/photoview/photoview/api/database/drivers"
 	"github.com/photoview/photoview/api/graphql/models"
 	"github.com/pkg/errors"
 	"gorm.io/gorm"
@@ -26,7 +27,7 @@ func Search(db *gorm.DB, query string, userID int, _limitMedia *int, _limitAlbum
 	var media []*models.Media
 
 	userSubquery := db.Table("user_albums").Where("user_id = ?", userID)
-	if db.Dialector.Name() == "postgres" {
+	if drivers.POSTGRES.MatchDatabase(db) {
 		userSubquery = userSubquery.Where("album_id = \"Album\".id")
 	} else {
 		userSubquery = userSubquery.Where("album_id = Album.id")
diff --git a/api/graphql/models/actions/share_token_actions.go b/api/graphql/models/actions/share_token_actions.go
index 7cad542..39bc3fd 100644
--- a/api/graphql/models/actions/share_token_actions.go
+++ b/api/graphql/models/actions/share_token_actions.go
@@ -3,6 +3,7 @@ package actions
 import (
 	"time"
 
+	"github.com/photoview/photoview/api/database/drivers"
 	"github.com/photoview/photoview/api/graphql/auth"
 	"github.com/photoview/photoview/api/graphql/models"
 	"github.com/photoview/photoview/api/utils"
@@ -15,7 +16,7 @@ func AddMediaShare(db *gorm.DB, user *models.User, mediaID int, expire *time.Tim
 	var media models.Media
 
 	var query string
-	if db.Dialector.Name() == "postgres" {
+	if drivers.POSTGRES.MatchDatabase(db) {
 		query = "EXISTS (SELECT * FROM user_albums WHERE user_albums.album_id = \"Album\".id AND user_albums.user_id = ?)"
 	} else {
 		query = "EXISTS (SELECT * FROM user_albums WHERE user_albums.album_id = Album.id AND user_albums.user_id = ?)"
@@ -146,7 +147,7 @@ func hashSharePassword(password *string) (*string, error) {
 func getUserToken(db *gorm.DB, userID int, tokenValue string) (*models.ShareToken, error) {
 
 	var query string
-	if db.Dialector.Name() == "postgres" {
+	if drivers.POSTGRES.MatchDatabase(db) {
 		query = "\"Owner\".id = ? OR \"Owner\".admin = TRUE"
 	} else {
 		query = "Owner.id = ? OR Owner.admin = TRUE"
diff --git a/api/graphql/models/actions/timeline_actions.go b/api/graphql/models/actions/timeline_actions.go
index 9ca7ca3..2e83d74 100644
--- a/api/graphql/models/actions/timeline_actions.go
+++ b/api/graphql/models/actions/timeline_actions.go
@@ -3,6 +3,7 @@ package actions
 import (
 	"time"
 
+	"github.com/photoview/photoview/api/database/drivers"
 	"github.com/photoview/photoview/api/graphql/models"
 	"gorm.io/gorm"
 )
@@ -13,15 +14,15 @@ func MyTimeline(db *gorm.DB, user *models.User, paginate *models.Pagination, onl
 		Joins("JOIN albums ON media.album_id = albums.id").
 		Where("albums.id IN (?)", db.Table("user_albums").Select("user_albums.album_id").Where("user_id = ?", user.ID))
 
-	switch db.Dialector.Name() {
-	case "postgres":
+	switch drivers.GetDatabaseDriverType(db) {
+	case drivers.POSTGRES:
 		query = query.
 			Order("DATE_TRUNC('year', date_shot) DESC").
 			Order("DATE_TRUNC('month', date_shot) DESC").
 			Order("DATE_TRUNC('day', date_shot) DESC").
 			Order("albums.title ASC").
 			Order("media.date_shot DESC")
-	case "sqlite":
+	case drivers.SQLITE:
 		query = query.
 			Order("strftime('%j', media.date_shot) DESC"). // convert to day of year 001-366
 			Order("albums.title ASC").
diff --git a/api/graphql/models/face_detection.go b/api/graphql/models/face_detection.go
index 4c91c35..8e09d03 100644
--- a/api/graphql/models/face_detection.go
+++ b/api/graphql/models/face_detection.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"github.com/Kagami/go-face"
+	"github.com/photoview/photoview/api/database/drivers"
 	"github.com/photoview/photoview/api/scanner/media_encoding/media_utils"
 	"gorm.io/gorm"
 	"gorm.io/gorm/schema"
@@ -48,10 +49,10 @@ type FaceDescriptor face.Descriptor
 
 // GormDataType datatype used in database
 func (FaceDescriptor) GormDBDataType(db *gorm.DB, field *schema.Field) string {
-	switch db.Dialector.Name() {
-	case "mysql", "sqlite":
+	switch drivers.GetDatabaseDriverType(db) {
+	case drivers.MYSQL, drivers.SQLITE:
 		return "BLOB"
-	case "postgres":
+	case drivers.POSTGRES:
 		return "BYTEA"
 	}
 	return ""
diff --git a/api/graphql/models/site_info.go b/api/graphql/models/site_info.go
index 593f4fa..d358adb 100644
--- a/api/graphql/models/site_info.go
+++ b/api/graphql/models/site_info.go
@@ -16,9 +16,9 @@ func (SiteInfo) TableName() string {
 	return "site_info"
 }
 
-func DefaultSiteInfo() SiteInfo {
+func DefaultSiteInfo(db *gorm.DB) SiteInfo {
 	defaultConcurrentWorkers := 3
-	if db_drivers.DatabaseDriver() == db_drivers.DatabaseDriverSqlite {
+	if db_drivers.SQLITE.MatchDatabase(db) {
 		defaultConcurrentWorkers = 1
 	}
 
@@ -39,7 +39,7 @@ func GetSiteInfo(db *gorm.DB) (*SiteInfo, error) {
 	}
 
 	if len(siteInfo) == 0 {
-		newSiteInfo := DefaultSiteInfo()
+		newSiteInfo := DefaultSiteInfo(db)
 
 		if err := db.Create(&newSiteInfo).Error; err != nil {
 			return nil, errors.Wrap(err, "initialize site_info")
diff --git a/api/graphql/models/site_info_test.go b/api/graphql/models/site_info_test.go
index 30ce062..0881939 100644
--- a/api/graphql/models/site_info_test.go
+++ b/api/graphql/models/site_info_test.go
@@ -17,7 +17,7 @@ func TestSiteInfo(t *testing.T) {
 		return
 	}
 
-	assert.Equal(t, models.DefaultSiteInfo(), *site_info)
+	assert.Equal(t, models.DefaultSiteInfo(db), *site_info)
 
 	site_info.InitialSetup = false
 	site_info.PeriodicScanInterval = 360
diff --git a/api/graphql/resolvers/scanner.go b/api/graphql/resolvers/scanner.go
index 5eca1cb..ce068c7 100644
--- a/api/graphql/resolvers/scanner.go
+++ b/api/graphql/resolvers/scanner.go
@@ -67,7 +67,7 @@ func (r *mutationResolver) SetScannerConcurrentWorkers(ctx context.Context, work
 		return 0, errors.New("concurrent workers must at least be 1")
 	}
 
-	if workers > 1 && drivers.DatabaseDriver() == drivers.DatabaseDriverSqlite {
+	if workers > 1 && drivers.DatabaseDriverFromEnv() == drivers.SQLITE {
 		return 0, errors.New("multiple workers not supported for SQLite databases")
 	}
 
diff --git a/api/routes/downloads.go b/api/routes/downloads.go
index cbb5084..bda3494 100644
--- a/api/routes/downloads.go
+++ b/api/routes/downloads.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"github.com/gorilla/mux"
+	"github.com/photoview/photoview/api/database/drivers"
 	"github.com/photoview/photoview/api/graphql/models"
 	"gorm.io/gorm"
 )
@@ -37,7 +38,7 @@ func RegisterDownloadRoutes(db *gorm.DB, router *mux.Router) {
 		}
 
 		var mediaWhereQuery string
-		if db.Dialector.Name() == "postgres" {
+		if drivers.POSTGRES.MatchDatabase(db) {
 			mediaWhereQuery = "\"Media\".album_id = ?"
 		} else {
 			mediaWhereQuery = "Media.album_id = ?"
diff --git a/api/scanner/cleanup_media.go b/api/scanner/cleanup_media.go
index 7dff49b..bd90f82 100644
--- a/api/scanner/cleanup_media.go
+++ b/api/scanner/cleanup_media.go
@@ -26,7 +26,7 @@ func CleanupMedia(db *gorm.DB, albumId int, albumMedia []*models.Media) []error
 
 	// Select media from database that was not found on hard disk
 	if len(albumMedia) > 0 {
-		query.Where("NOT id IN (?)", albumMediaIds)
+		query = query.Where("NOT id IN (?)", albumMediaIds)
 	}
 
 	if err := query.Find(&mediaList).Error; err != nil {
@@ -48,7 +48,7 @@ func CleanupMedia(db *gorm.DB, albumId int, albumMedia []*models.Media) []error
 	}
 
 	if len(mediaIDs) > 0 {
-		if err := db.Delete(models.Media{}, mediaIDs).Error; err != nil {
+		if err := db.Where("id IN (?)", mediaIDs).Delete(models.Media{}).Error; err != nil {
 			deleteErrors = append(deleteErrors, errors.Wrap(err, "delete old media from database"))
 		}
 
diff --git a/api/scanner/cleanup_media_test.go b/api/scanner/cleanup_media_test.go
index 63c2476..a0385f2 100644
--- a/api/scanner/cleanup_media_test.go
+++ b/api/scanner/cleanup_media_test.go
@@ -6,6 +6,7 @@ import (
 	"testing"
 
 	"github.com/otiai10/copy"
+	"github.com/photoview/photoview/api/database/drivers"
 	"github.com/photoview/photoview/api/graphql/models"
 	"github.com/photoview/photoview/api/scanner/face_detection"
 	"github.com/photoview/photoview/api/test_utils"
@@ -16,6 +17,11 @@ func TestCleanupMedia(t *testing.T) {
 	test_utils.FilesystemTest(t)
 	db := test_utils.DatabaseTest(t)
 
+	// Sqlite doesn't seem to support foreign key cascading
+	if drivers.SQLITE.MatchDatabase(db) {
+		t.SkipNow()
+	}
+
 	if !assert.NoError(t, face_detection.InitializeFaceDetector(db)) {
 		return
 	}
@@ -69,7 +75,6 @@ func TestCleanupMedia(t *testing.T) {
 	}
 
 	t.Run("Modify albums", func(t *testing.T) {
-
 		test_utils.RunScannerOnUser(t, db, user1)
 		assert.Equal(t, 9, countAllMedia())
 		assert.Equal(t, 18, countAllMediaURLs())
