mirror of
https://github.com/imgfloat/server.git
synced 2026-05-08 10:19:35 +00:00
refactor: move SchemaMigration responsibilities to Flyway migrations V13 and V14
- V13: create oauth2_authorized_client table and SPRING_SESSION_ATTRIBUTES upsert trigger - V14: normalize oauth2_authorized_client timestamp columns (one-time data migration) - SchemaMigration now only retains legacy backward-compat code for pre-Flyway databases - Remove normalizeAuthorizedClientTimestamps() and ensureSessionAttributeUpsertTrigger() from SchemaMigration - Add clear comment explaining SchemaMigration is legacy code only
This commit is contained in:
@@ -12,7 +12,9 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class SchemaMigration implements ApplicationRunner {
|
||||
|
||||
// TODO: Code smell Runtime schema migration logic duplicates Flyway responsibilities and is difficult to reason about/test.
|
||||
// Legacy backward-compatibility runner for databases that predate the Flyway migration
|
||||
// baseline. Each method is idempotent so it is harmless on up-to-date databases. New
|
||||
// schema changes must go into a versioned Flyway script under db/migration, not here.
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SchemaMigration.class);
|
||||
|
||||
@@ -24,32 +26,10 @@ public class SchemaMigration implements ApplicationRunner {
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
ensureSessionAttributeUpsertTrigger();
|
||||
ensureChannelCanvasColumns();
|
||||
ensureAssetTables();
|
||||
ensureMarketplaceScriptHeartsTable();
|
||||
ensureAuthorizedClientTable();
|
||||
normalizeAuthorizedClientTimestamps();
|
||||
}
|
||||
|
||||
private void ensureSessionAttributeUpsertTrigger() {
|
||||
try {
|
||||
jdbcTemplate.execute(
|
||||
"""
|
||||
CREATE TRIGGER IF NOT EXISTS SPRING_SESSION_ATTRIBUTES_UPSERT
|
||||
BEFORE INSERT ON SPRING_SESSION_ATTRIBUTES
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
DELETE FROM SPRING_SESSION_ATTRIBUTES
|
||||
WHERE SESSION_PRIMARY_ID = NEW.SESSION_PRIMARY_ID
|
||||
AND ATTRIBUTE_NAME = NEW.ATTRIBUTE_NAME;
|
||||
END;
|
||||
"""
|
||||
);
|
||||
LOG.info("Ensured SPRING_SESSION_ATTRIBUTES upsert trigger exists");
|
||||
} catch (DataAccessException ex) {
|
||||
LOG.warn("Unable to ensure SPRING_SESSION_ATTRIBUTES upsert trigger", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureChannelCanvasColumns() {
|
||||
@@ -333,6 +313,7 @@ public class SchemaMigration implements ApplicationRunner {
|
||||
}
|
||||
|
||||
private void ensureAuthorizedClientTable() {
|
||||
// Retained for databases predating V13. V13 creates this table for new installations.
|
||||
try {
|
||||
jdbcTemplate.execute(
|
||||
"""
|
||||
@@ -350,54 +331,8 @@ public class SchemaMigration implements ApplicationRunner {
|
||||
)
|
||||
"""
|
||||
);
|
||||
LOG.info("Ensured oauth2_authorized_client table exists");
|
||||
} catch (DataAccessException ex) {
|
||||
LOG.warn("Unable to ensure oauth2_authorized_client table", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void normalizeAuthorizedClientTimestamps() {
|
||||
normalizeTimestampColumn("access_token_issued_at");
|
||||
normalizeTimestampColumn("access_token_expires_at");
|
||||
normalizeTimestampColumn("refresh_token_issued_at");
|
||||
}
|
||||
|
||||
private void normalizeTimestampColumn(String column) {
|
||||
try {
|
||||
int updated = jdbcTemplate.update(
|
||||
"UPDATE oauth2_authorized_client " +
|
||||
"SET " +
|
||||
column +
|
||||
" = CASE " +
|
||||
"WHEN " +
|
||||
column +
|
||||
" LIKE '%-%' THEN CAST(strftime('%s', " +
|
||||
column +
|
||||
") AS INTEGER) * 1000 " +
|
||||
"WHEN typeof(" +
|
||||
column +
|
||||
") = 'text' AND " +
|
||||
column +
|
||||
" GLOB '[0-9]*' THEN CAST(" +
|
||||
column +
|
||||
" AS INTEGER) " +
|
||||
"WHEN typeof(" +
|
||||
column +
|
||||
") = 'integer' THEN " +
|
||||
column +
|
||||
" " +
|
||||
"ELSE " +
|
||||
column +
|
||||
" END " +
|
||||
"WHERE " +
|
||||
column +
|
||||
" IS NOT NULL"
|
||||
);
|
||||
if (updated > 0) {
|
||||
LOG.info("Normalized {} rows in oauth2_authorized_client.{}", updated, column);
|
||||
}
|
||||
} catch (DataAccessException ex) {
|
||||
LOG.warn("Unable to normalize oauth2_authorized_client.{} timestamps", column, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
-- OAuth2 authorized client table for storing Twitch OAuth tokens at rest (AES-256-GCM encrypted).
|
||||
-- This table is managed by SQLiteOAuth2AuthorizedClientService; Spring Session's JDBC
|
||||
-- auto-initialization is disabled (spring.session.jdbc.initialize-schema=never) so the
|
||||
-- table must be created here.
|
||||
CREATE TABLE IF NOT EXISTS oauth2_authorized_client (
|
||||
client_registration_id VARCHAR(100) NOT NULL,
|
||||
principal_name VARCHAR(200) NOT NULL,
|
||||
access_token_type VARCHAR(100),
|
||||
access_token_value TEXT,
|
||||
access_token_issued_at INTEGER,
|
||||
access_token_expires_at INTEGER,
|
||||
access_token_scopes VARCHAR(1000),
|
||||
refresh_token_value TEXT,
|
||||
refresh_token_issued_at INTEGER,
|
||||
PRIMARY KEY (client_registration_id, principal_name)
|
||||
);
|
||||
|
||||
-- SQLite does not support INSERT OR REPLACE semantics for Spring Session's attribute writes.
|
||||
-- This trigger deletes any existing row for the same (session, attribute) pair before each
|
||||
-- INSERT, effectively converting INSERT to an UPSERT.
|
||||
CREATE TRIGGER IF NOT EXISTS SPRING_SESSION_ATTRIBUTES_UPSERT
|
||||
BEFORE INSERT ON spring_session_attributes
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
DELETE FROM spring_session_attributes
|
||||
WHERE session_primary_id = NEW.session_primary_id
|
||||
AND attribute_name = NEW.attribute_name;
|
||||
END;
|
||||
@@ -0,0 +1,27 @@
|
||||
-- One-time data migration: normalize oauth2_authorized_client timestamp columns from
|
||||
-- ISO-8601 strings or text epoch values to integer milliseconds-since-epoch.
|
||||
-- This corrects records written by earlier versions of the application that stored
|
||||
-- Instant values as strings rather than longs.
|
||||
UPDATE oauth2_authorized_client
|
||||
SET access_token_issued_at = CASE
|
||||
WHEN access_token_issued_at LIKE '%-%' THEN CAST(strftime('%s', access_token_issued_at) AS INTEGER) * 1000
|
||||
WHEN typeof(access_token_issued_at) = 'text' AND access_token_issued_at GLOB '[0-9]*' THEN CAST(access_token_issued_at AS INTEGER)
|
||||
ELSE access_token_issued_at
|
||||
END
|
||||
WHERE access_token_issued_at IS NOT NULL;
|
||||
|
||||
UPDATE oauth2_authorized_client
|
||||
SET access_token_expires_at = CASE
|
||||
WHEN access_token_expires_at LIKE '%-%' THEN CAST(strftime('%s', access_token_expires_at) AS INTEGER) * 1000
|
||||
WHEN typeof(access_token_expires_at) = 'text' AND access_token_expires_at GLOB '[0-9]*' THEN CAST(access_token_expires_at AS INTEGER)
|
||||
ELSE access_token_expires_at
|
||||
END
|
||||
WHERE access_token_expires_at IS NOT NULL;
|
||||
|
||||
UPDATE oauth2_authorized_client
|
||||
SET refresh_token_issued_at = CASE
|
||||
WHEN refresh_token_issued_at LIKE '%-%' THEN CAST(strftime('%s', refresh_token_issued_at) AS INTEGER) * 1000
|
||||
WHEN typeof(refresh_token_issued_at) = 'text' AND refresh_token_issued_at GLOB '[0-9]*' THEN CAST(refresh_token_issued_at AS INTEGER)
|
||||
ELSE refresh_token_issued_at
|
||||
END
|
||||
WHERE refresh_token_issued_at IS NOT NULL;
|
||||
Reference in New Issue
Block a user