added cargo files
This commit is contained in:
538
PinePods-0.8.2/gpodder-api/internal/db/migrations.go
Normal file
538
PinePods-0.8.2/gpodder-api/internal/db/migrations.go
Normal file
@@ -0,0 +1,538 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Migration represents a database migration
|
||||
type Migration struct {
|
||||
Version int
|
||||
Description string
|
||||
PostgreSQLSQL string
|
||||
MySQLSQL string
|
||||
}
|
||||
|
||||
// MigrationRecord represents a record of an applied migration
|
||||
type MigrationRecord struct {
|
||||
Version int
|
||||
Description string
|
||||
AppliedAt time.Time
|
||||
}
|
||||
|
||||
// EnsureMigrationsTable creates the migrations table if it doesn't exist
|
||||
func EnsureMigrationsTable(db *sql.DB, dbType string) error {
|
||||
log.Println("Creating GpodderSyncMigrations table if it doesn't exist...")
|
||||
|
||||
var query string
|
||||
if dbType == "postgresql" {
|
||||
query = `
|
||||
CREATE TABLE IF NOT EXISTS "GpodderSyncMigrations" (
|
||||
Version INT PRIMARY KEY,
|
||||
Description TEXT NOT NULL,
|
||||
AppliedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`
|
||||
} else {
|
||||
query = `
|
||||
CREATE TABLE IF NOT EXISTS GpodderSyncMigrations (
|
||||
Version INT PRIMARY KEY,
|
||||
Description TEXT NOT NULL,
|
||||
AppliedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`
|
||||
}
|
||||
|
||||
_, err := db.Exec(query)
|
||||
if err != nil {
|
||||
log.Printf("Error creating migrations table: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Println("GpodderSyncMigrations table is ready")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAppliedMigrations returns a list of already applied migrations
|
||||
func GetAppliedMigrations(db *sql.DB, dbType string) ([]MigrationRecord, error) {
|
||||
log.Println("Checking previously applied migrations...")
|
||||
|
||||
var query string
|
||||
if dbType == "postgresql" {
|
||||
query = `
|
||||
SELECT Version, Description, AppliedAt
|
||||
FROM "GpodderSyncMigrations"
|
||||
ORDER BY Version ASC
|
||||
`
|
||||
} else {
|
||||
query = `
|
||||
SELECT Version, Description, AppliedAt
|
||||
FROM GpodderSyncMigrations
|
||||
ORDER BY Version ASC
|
||||
`
|
||||
}
|
||||
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
log.Printf("Error checking applied migrations: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var migrations []MigrationRecord
|
||||
for rows.Next() {
|
||||
var m MigrationRecord
|
||||
if err := rows.Scan(&m.Version, &m.Description, &m.AppliedAt); err != nil {
|
||||
log.Printf("Error scanning migration record: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
migrations = append(migrations, m)
|
||||
}
|
||||
|
||||
if len(migrations) > 0 {
|
||||
log.Printf("Found %d previously applied migrations", len(migrations))
|
||||
} else {
|
||||
log.Println("No previously applied migrations found")
|
||||
}
|
||||
return migrations, nil
|
||||
}
|
||||
|
||||
// ApplyMigration applies a single migration
|
||||
func ApplyMigration(db *sql.DB, migration Migration, dbType string) error {
|
||||
log.Printf("Applying migration %d: %s", migration.Version, migration.Description)
|
||||
|
||||
// Select the appropriate SQL based on database type
|
||||
var sql string
|
||||
if dbType == "postgresql" {
|
||||
sql = migration.PostgreSQLSQL
|
||||
} else {
|
||||
sql = migration.MySQLSQL
|
||||
}
|
||||
|
||||
// Begin transaction
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
log.Printf("Error beginning transaction for migration %d: %v", migration.Version, err)
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Printf("Rolling back migration %d due to error", migration.Version)
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Execute the migration SQL
|
||||
_, err = tx.Exec(sql)
|
||||
if err != nil {
|
||||
log.Printf("Failed to apply migration %d: %v", migration.Version, err)
|
||||
return fmt.Errorf("failed to apply migration %d: %w", migration.Version, err)
|
||||
}
|
||||
|
||||
// Record the migration
|
||||
var insertQuery string
|
||||
if dbType == "postgresql" {
|
||||
insertQuery = `
|
||||
INSERT INTO "GpodderSyncMigrations" (Version, Description)
|
||||
VALUES ($1, $2)
|
||||
`
|
||||
} else {
|
||||
insertQuery = `
|
||||
INSERT INTO GpodderSyncMigrations (Version, Description)
|
||||
VALUES (?, ?)
|
||||
`
|
||||
}
|
||||
|
||||
_, err = tx.Exec(insertQuery, migration.Version, migration.Description)
|
||||
if err != nil {
|
||||
log.Printf("Failed to record migration %d: %v", migration.Version, err)
|
||||
return fmt.Errorf("failed to record migration %d: %w", migration.Version, err)
|
||||
}
|
||||
|
||||
// Commit the transaction
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
log.Printf("Failed to commit migration %d: %v", migration.Version, err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Successfully applied migration %d", migration.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkRequiredTables verifies that required PinePods tables exist before running migrations
|
||||
func checkRequiredTables(db *sql.DB, dbType string) error {
|
||||
log.Println("Checking for required PinePods tables...")
|
||||
|
||||
requiredTables := []string{"Users", "GpodderDevices"}
|
||||
|
||||
for _, table := range requiredTables {
|
||||
var query string
|
||||
if dbType == "postgresql" {
|
||||
query = `SELECT 1 FROM "` + table + `" LIMIT 1`
|
||||
} else {
|
||||
query = `SELECT 1 FROM ` + table + ` LIMIT 1`
|
||||
}
|
||||
|
||||
_, err := db.Exec(query)
|
||||
if err != nil {
|
||||
log.Printf("Required table %s does not exist or is not accessible: %v", table, err)
|
||||
return fmt.Errorf("required table %s does not exist - please ensure PinePods main migrations have run first", table)
|
||||
}
|
||||
log.Printf("Required table %s exists", table)
|
||||
}
|
||||
|
||||
log.Println("All required tables found")
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunMigrations runs all pending migrations
|
||||
func RunMigrations(db *sql.DB, dbType string) error {
|
||||
log.Println("Starting gpodder API migrations...")
|
||||
|
||||
// Check that required PinePods tables exist first
|
||||
if err := checkRequiredTables(db, dbType); err != nil {
|
||||
return fmt.Errorf("prerequisite check failed: %w", err)
|
||||
}
|
||||
|
||||
// Ensure migrations table exists
|
||||
if err := EnsureMigrationsTable(db, dbType); err != nil {
|
||||
return fmt.Errorf("failed to create migrations table: %w", err)
|
||||
}
|
||||
|
||||
// Get applied migrations
|
||||
appliedMigrations, err := GetAppliedMigrations(db, dbType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get applied migrations: %w", err)
|
||||
}
|
||||
|
||||
// Build a map of applied migration versions for quick lookup
|
||||
appliedVersions := make(map[int]bool)
|
||||
for _, m := range appliedMigrations {
|
||||
appliedVersions[m.Version] = true
|
||||
}
|
||||
|
||||
// Get all migrations
|
||||
migrations := GetMigrations()
|
||||
log.Printf("Found %d total migrations to check", len(migrations))
|
||||
|
||||
// Apply pending migrations
|
||||
appliedCount := 0
|
||||
for _, migration := range migrations {
|
||||
if appliedVersions[migration.Version] {
|
||||
// Migration already applied, skip
|
||||
log.Printf("Migration %d already applied, skipping", migration.Version)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("Applying migration %d: %s", migration.Version, migration.Description)
|
||||
if err := ApplyMigration(db, migration, dbType); err != nil {
|
||||
return err
|
||||
}
|
||||
appliedCount++
|
||||
}
|
||||
|
||||
if appliedCount > 0 {
|
||||
log.Printf("Successfully applied %d new migrations", appliedCount)
|
||||
} else {
|
||||
log.Println("No new migrations to apply")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMigrations returns all migrations with SQL variants for both database types
|
||||
func GetMigrations() []Migration {
|
||||
return []Migration{
|
||||
{
|
||||
Version: 1,
|
||||
Description: "Initial schema creation",
|
||||
PostgreSQLSQL: `
|
||||
-- Device sync state for the API
|
||||
CREATE TABLE IF NOT EXISTS "GpodderSyncDeviceState" (
|
||||
DeviceStateID SERIAL PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
DeviceID INT NOT NULL,
|
||||
SubscriptionCount INT DEFAULT 0,
|
||||
LastUpdated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (DeviceID) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE,
|
||||
UNIQUE(UserID, DeviceID)
|
||||
);
|
||||
|
||||
-- Subscription changes
|
||||
CREATE TABLE IF NOT EXISTS "GpodderSyncSubscriptions" (
|
||||
SubscriptionID SERIAL PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
DeviceID INT NOT NULL,
|
||||
PodcastURL TEXT NOT NULL,
|
||||
Action VARCHAR(10) NOT NULL,
|
||||
Timestamp BIGINT NOT NULL,
|
||||
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (DeviceID) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Episode actions
|
||||
CREATE TABLE IF NOT EXISTS "GpodderSyncEpisodeActions" (
|
||||
ActionID SERIAL PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
DeviceID INT,
|
||||
PodcastURL TEXT NOT NULL,
|
||||
EpisodeURL TEXT NOT NULL,
|
||||
Action VARCHAR(20) NOT NULL,
|
||||
Timestamp BIGINT NOT NULL,
|
||||
Started INT,
|
||||
Position INT,
|
||||
Total INT,
|
||||
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (DeviceID) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Podcast lists
|
||||
CREATE TABLE IF NOT EXISTS "GpodderSyncPodcastLists" (
|
||||
ListID SERIAL PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
Name VARCHAR(255) NOT NULL,
|
||||
Title VARCHAR(255) NOT NULL,
|
||||
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
||||
UNIQUE(UserID, Name)
|
||||
);
|
||||
|
||||
-- Podcast list entries
|
||||
CREATE TABLE IF NOT EXISTS "GpodderSyncPodcastListEntries" (
|
||||
EntryID SERIAL PRIMARY KEY,
|
||||
ListID INT NOT NULL,
|
||||
PodcastURL TEXT NOT NULL,
|
||||
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (ListID) REFERENCES "GpodderSyncPodcastLists"(ListID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Synchronization relationships between devices
|
||||
CREATE TABLE IF NOT EXISTS "GpodderSyncDevicePairs" (
|
||||
PairID SERIAL PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
DeviceID1 INT NOT NULL,
|
||||
DeviceID2 INT NOT NULL,
|
||||
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (DeviceID1) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (DeviceID2) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE,
|
||||
UNIQUE(UserID, DeviceID1, DeviceID2)
|
||||
);
|
||||
|
||||
-- Settings storage
|
||||
CREATE TABLE IF NOT EXISTS "GpodderSyncSettings" (
|
||||
SettingID SERIAL PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
Scope VARCHAR(20) NOT NULL,
|
||||
DeviceID INT,
|
||||
PodcastURL TEXT,
|
||||
EpisodeURL TEXT,
|
||||
SettingKey VARCHAR(255) NOT NULL,
|
||||
SettingValue TEXT,
|
||||
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
LastUpdated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (DeviceID) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Create indexes for faster queries
|
||||
CREATE INDEX IF NOT EXISTS idx_gpodder_sync_subscriptions_userid ON "GpodderSyncSubscriptions"(UserID);
|
||||
CREATE INDEX IF NOT EXISTS idx_gpodder_sync_subscriptions_deviceid ON "GpodderSyncSubscriptions"(DeviceID);
|
||||
CREATE INDEX IF NOT EXISTS idx_gpodder_sync_episode_actions_userid ON "GpodderSyncEpisodeActions"(UserID);
|
||||
CREATE INDEX IF NOT EXISTS idx_gpodder_sync_podcast_lists_userid ON "GpodderSyncPodcastLists"(UserID);
|
||||
`,
|
||||
MySQLSQL: `
|
||||
-- Device sync state for the API
|
||||
CREATE TABLE IF NOT EXISTS GpodderSyncDeviceState (
|
||||
DeviceStateID INT AUTO_INCREMENT PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
DeviceID INT NOT NULL,
|
||||
SubscriptionCount INT DEFAULT 0,
|
||||
LastUpdated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (DeviceID) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE,
|
||||
UNIQUE(UserID, DeviceID)
|
||||
);
|
||||
|
||||
-- Subscription changes
|
||||
CREATE TABLE IF NOT EXISTS GpodderSyncSubscriptions (
|
||||
SubscriptionID INT AUTO_INCREMENT PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
DeviceID INT NOT NULL,
|
||||
PodcastURL TEXT NOT NULL,
|
||||
Action VARCHAR(10) NOT NULL,
|
||||
Timestamp BIGINT NOT NULL,
|
||||
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (DeviceID) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Episode actions
|
||||
CREATE TABLE IF NOT EXISTS GpodderSyncEpisodeActions (
|
||||
ActionID INT AUTO_INCREMENT PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
DeviceID INT,
|
||||
PodcastURL TEXT NOT NULL,
|
||||
EpisodeURL TEXT NOT NULL,
|
||||
Action VARCHAR(20) NOT NULL,
|
||||
Timestamp BIGINT NOT NULL,
|
||||
Started INT,
|
||||
Position INT,
|
||||
Total INT,
|
||||
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (DeviceID) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Podcast lists
|
||||
CREATE TABLE IF NOT EXISTS GpodderSyncPodcastLists (
|
||||
ListID INT AUTO_INCREMENT PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
Name VARCHAR(255) NOT NULL,
|
||||
Title VARCHAR(255) NOT NULL,
|
||||
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
||||
UNIQUE(UserID, Name)
|
||||
);
|
||||
|
||||
-- Podcast list entries
|
||||
CREATE TABLE IF NOT EXISTS GpodderSyncPodcastListEntries (
|
||||
EntryID INT AUTO_INCREMENT PRIMARY KEY,
|
||||
ListID INT NOT NULL,
|
||||
PodcastURL TEXT NOT NULL,
|
||||
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (ListID) REFERENCES GpodderSyncPodcastLists(ListID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Synchronization relationships between devices
|
||||
CREATE TABLE IF NOT EXISTS GpodderSyncDevicePairs (
|
||||
PairID INT AUTO_INCREMENT PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
DeviceID1 INT NOT NULL,
|
||||
DeviceID2 INT NOT NULL,
|
||||
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (DeviceID1) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (DeviceID2) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE,
|
||||
UNIQUE(UserID, DeviceID1, DeviceID2)
|
||||
);
|
||||
|
||||
-- Settings storage
|
||||
CREATE TABLE IF NOT EXISTS GpodderSyncSettings (
|
||||
SettingID INT AUTO_INCREMENT PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
Scope VARCHAR(20) NOT NULL,
|
||||
DeviceID INT,
|
||||
PodcastURL TEXT,
|
||||
EpisodeURL TEXT,
|
||||
SettingKey VARCHAR(255) NOT NULL,
|
||||
SettingValue TEXT,
|
||||
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
LastUpdated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (DeviceID) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Create indexes for faster queries
|
||||
CREATE INDEX idx_gpodder_sync_subscriptions_userid ON GpodderSyncSubscriptions(UserID);
|
||||
CREATE INDEX idx_gpodder_sync_subscriptions_deviceid ON GpodderSyncSubscriptions(DeviceID);
|
||||
CREATE INDEX idx_gpodder_sync_episode_actions_userid ON GpodderSyncEpisodeActions(UserID);
|
||||
CREATE INDEX idx_gpodder_sync_podcast_lists_userid ON GpodderSyncPodcastLists(UserID);
|
||||
`,
|
||||
},
|
||||
{
|
||||
Version: 2,
|
||||
Description: "Add API version column to GpodderSyncSettings",
|
||||
PostgreSQLSQL: `
|
||||
ALTER TABLE "GpodderSyncSettings"
|
||||
ADD COLUMN IF NOT EXISTS APIVersion VARCHAR(10) DEFAULT '2.0';
|
||||
`,
|
||||
MySQLSQL: `
|
||||
-- Check if column exists first
|
||||
SET @s = (SELECT IF(
|
||||
COUNT(*) = 0,
|
||||
'ALTER TABLE GpodderSyncSettings ADD COLUMN APIVersion VARCHAR(10) DEFAULT "2.0"',
|
||||
'SELECT 1'
|
||||
) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_NAME = 'GpodderSyncSettings'
|
||||
AND COLUMN_NAME = 'APIVersion');
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
`,
|
||||
},
|
||||
{
|
||||
Version: 3,
|
||||
Description: "Create GpodderSessions table for API sessions",
|
||||
PostgreSQLSQL: `
|
||||
CREATE TABLE IF NOT EXISTS "GpodderSessions" (
|
||||
SessionID SERIAL PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
SessionToken TEXT NOT NULL,
|
||||
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
ExpiresAt TIMESTAMP NOT NULL,
|
||||
LastActive TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UserAgent TEXT,
|
||||
ClientIP TEXT,
|
||||
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
||||
UNIQUE(SessionToken)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_gpodder_sessions_token ON "GpodderSessions"(SessionToken);
|
||||
CREATE INDEX IF NOT EXISTS idx_gpodder_sessions_userid ON "GpodderSessions"(UserID);
|
||||
CREATE INDEX IF NOT EXISTS idx_gpodder_sessions_expires ON "GpodderSessions"(ExpiresAt);
|
||||
`,
|
||||
MySQLSQL: `
|
||||
CREATE TABLE IF NOT EXISTS GpodderSessions (
|
||||
SessionID INT AUTO_INCREMENT PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
SessionToken TEXT NOT NULL,
|
||||
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
ExpiresAt TIMESTAMP NOT NULL,
|
||||
LastActive TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UserAgent TEXT,
|
||||
ClientIP TEXT,
|
||||
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_gpodder_sessions_userid ON GpodderSessions(UserID);
|
||||
CREATE INDEX idx_gpodder_sessions_expires ON GpodderSessions(ExpiresAt);
|
||||
`,
|
||||
},
|
||||
{
|
||||
Version: 4,
|
||||
Description: "Add sync state table for tracking device sync status",
|
||||
PostgreSQLSQL: `
|
||||
CREATE TABLE IF NOT EXISTS "GpodderSyncState" (
|
||||
SyncStateID SERIAL PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
DeviceID INT NOT NULL,
|
||||
LastTimestamp BIGINT DEFAULT 0,
|
||||
LastSync TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (DeviceID) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE,
|
||||
UNIQUE(UserID, DeviceID)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_gpodder_syncstate_userid_deviceid ON "GpodderSyncState"(UserID, DeviceID);
|
||||
`,
|
||||
MySQLSQL: `
|
||||
CREATE TABLE IF NOT EXISTS GpodderSyncState (
|
||||
SyncStateID INT AUTO_INCREMENT PRIMARY KEY,
|
||||
UserID INT NOT NULL,
|
||||
DeviceID INT NOT NULL,
|
||||
LastTimestamp BIGINT DEFAULT 0,
|
||||
LastSync TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (DeviceID) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE,
|
||||
UNIQUE(UserID, DeviceID)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_gpodder_syncstate_userid_deviceid ON GpodderSyncState(UserID, DeviceID);
|
||||
`,
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user