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
|
@Component
|
||||||
public class SchemaMigration implements ApplicationRunner {
|
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);
|
private static final Logger LOG = LoggerFactory.getLogger(SchemaMigration.class);
|
||||||
|
|
||||||
@@ -24,32 +26,10 @@ public class SchemaMigration implements ApplicationRunner {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(ApplicationArguments args) {
|
public void run(ApplicationArguments args) {
|
||||||
ensureSessionAttributeUpsertTrigger();
|
|
||||||
ensureChannelCanvasColumns();
|
ensureChannelCanvasColumns();
|
||||||
ensureAssetTables();
|
ensureAssetTables();
|
||||||
ensureMarketplaceScriptHeartsTable();
|
ensureMarketplaceScriptHeartsTable();
|
||||||
ensureAuthorizedClientTable();
|
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() {
|
private void ensureChannelCanvasColumns() {
|
||||||
@@ -333,6 +313,7 @@ public class SchemaMigration implements ApplicationRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void ensureAuthorizedClientTable() {
|
private void ensureAuthorizedClientTable() {
|
||||||
|
// Retained for databases predating V13. V13 creates this table for new installations.
|
||||||
try {
|
try {
|
||||||
jdbcTemplate.execute(
|
jdbcTemplate.execute(
|
||||||
"""
|
"""
|
||||||
@@ -350,54 +331,8 @@ public class SchemaMigration implements ApplicationRunner {
|
|||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
);
|
);
|
||||||
LOG.info("Ensured oauth2_authorized_client table exists");
|
|
||||||
} catch (DataAccessException ex) {
|
} catch (DataAccessException ex) {
|
||||||
LOG.warn("Unable to ensure oauth2_authorized_client table", 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