SHA256
Перевести deploy и PostgreSQL схемы на актуальное состояние
This commit is contained in:
@@ -18,7 +18,9 @@ public final class DatabaseInitializer {
|
|||||||
|
|
||||||
public static final String DB_SCHEMA_VERSION_TABLE = "db_schema_version";
|
public static final String DB_SCHEMA_VERSION_TABLE = "db_schema_version";
|
||||||
public static final int SCHEMA_VERSION_1 = 1;
|
public static final int SCHEMA_VERSION_1 = 1;
|
||||||
|
public static final int SCHEMA_VERSION_2 = 2;
|
||||||
public static final String POSTGRES_SCHEMA_RESOURCE = "postgres/schema_v1.sql";
|
public static final String POSTGRES_SCHEMA_RESOURCE = "postgres/schema_v1.sql";
|
||||||
|
public static final String POSTGRES_MIGRATION_V2_RESOURCE = "postgres/migration_v2.sql";
|
||||||
|
|
||||||
private DatabaseInitializer() {}
|
private DatabaseInitializer() {}
|
||||||
|
|
||||||
@@ -73,10 +75,14 @@ public final class DatabaseInitializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try (Connection conn = openConnection(jdbcUrl, user, password)) {
|
try (Connection conn = openConnection(jdbcUrl, user, password)) {
|
||||||
if (postgresSchemaVersionTableExists(conn)) {
|
if (!postgresSchemaVersionTableExists(conn)) {
|
||||||
|
runSqlScript(conn, POSTGRES_SCHEMA_RESOURCE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
runPostgresSchemaScript(conn);
|
int currentVersion = readCurrentSchemaVersion(conn);
|
||||||
|
if (currentVersion < SCHEMA_VERSION_2) {
|
||||||
|
runSqlScript(conn, POSTGRES_MIGRATION_V2_RESOURCE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,8 +110,23 @@ public final class DatabaseInitializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void runPostgresSchemaScript(Connection conn) throws SQLException {
|
private static int readCurrentSchemaVersion(Connection conn) throws SQLException {
|
||||||
String sqlScript = loadClasspathResource(POSTGRES_SCHEMA_RESOURCE);
|
String sql = """
|
||||||
|
SELECT schema_version
|
||||||
|
FROM db_schema_version
|
||||||
|
WHERE id = 1
|
||||||
|
""";
|
||||||
|
try (Statement st = conn.createStatement();
|
||||||
|
ResultSet rs = st.executeQuery(sql)) {
|
||||||
|
if (!rs.next()) {
|
||||||
|
return SCHEMA_VERSION_1;
|
||||||
|
}
|
||||||
|
return rs.getInt(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void runSqlScript(Connection conn, String resourcePath) throws SQLException {
|
||||||
|
String sqlScript = loadClasspathResource(resourcePath);
|
||||||
List<String> statements = splitSqlStatements(sqlScript);
|
List<String> statements = splitSqlStatements(sqlScript);
|
||||||
|
|
||||||
boolean previousAutoCommit = conn.getAutoCommit();
|
boolean previousAutoCommit = conn.getAutoCommit();
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user_access_servers_current (
|
||||||
|
user_login TEXT NOT NULL REFERENCES solana_user_pda_current(login) ON DELETE CASCADE,
|
||||||
|
server_login TEXT NOT NULL REFERENCES solana_user_pda_current(login) ON DELETE CASCADE,
|
||||||
|
server_url TEXT NOT NULL,
|
||||||
|
server_client_key TEXT NOT NULL,
|
||||||
|
user_record_number INTEGER NOT NULL,
|
||||||
|
user_updated_at_ms BIGINT NOT NULL,
|
||||||
|
server_record_number INTEGER NOT NULL,
|
||||||
|
server_updated_at_ms BIGINT NOT NULL,
|
||||||
|
refreshed_at_ms BIGINT NOT NULL,
|
||||||
|
PRIMARY KEY (user_login, server_login)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_access_servers_user
|
||||||
|
ON user_access_servers_current(user_login);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_access_servers_server
|
||||||
|
ON user_access_servers_current(server_login);
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_for_user(p_user_login TEXT)
|
||||||
|
RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
IF p_user_login IS NULL OR btrim(p_user_login) = '' THEN
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DELETE FROM user_access_servers_current
|
||||||
|
WHERE LOWER(user_login) = LOWER(p_user_login);
|
||||||
|
|
||||||
|
INSERT INTO user_access_servers_current (
|
||||||
|
user_login,
|
||||||
|
server_login,
|
||||||
|
server_url,
|
||||||
|
server_client_key,
|
||||||
|
user_record_number,
|
||||||
|
user_updated_at_ms,
|
||||||
|
server_record_number,
|
||||||
|
server_updated_at_ms,
|
||||||
|
refreshed_at_ms
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
u.login,
|
||||||
|
s.login,
|
||||||
|
s.server_address,
|
||||||
|
s.client_key,
|
||||||
|
u.record_number,
|
||||||
|
u.updated_at_ms,
|
||||||
|
s.record_number,
|
||||||
|
s.updated_at_ms,
|
||||||
|
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||||
|
FROM solana_user_pda_current u
|
||||||
|
CROSS JOIN LATERAL jsonb_array_elements_text(
|
||||||
|
CASE
|
||||||
|
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
|
||||||
|
ELSE u.access_servers_json::jsonb
|
||||||
|
END
|
||||||
|
) AS access_server(login_value)
|
||||||
|
JOIN solana_user_pda_current s
|
||||||
|
ON LOWER(s.login) = LOWER(btrim(access_server.login_value))
|
||||||
|
AND s.is_server = TRUE
|
||||||
|
AND btrim(COALESCE(s.server_address, '')) <> ''
|
||||||
|
WHERE LOWER(u.login) = LOWER(p_user_login)
|
||||||
|
ON CONFLICT (user_login, server_login) DO UPDATE SET
|
||||||
|
server_url = EXCLUDED.server_url,
|
||||||
|
server_client_key = EXCLUDED.server_client_key,
|
||||||
|
user_record_number = EXCLUDED.user_record_number,
|
||||||
|
user_updated_at_ms = EXCLUDED.user_updated_at_ms,
|
||||||
|
server_record_number = EXCLUDED.server_record_number,
|
||||||
|
server_updated_at_ms = EXCLUDED.server_updated_at_ms,
|
||||||
|
refreshed_at_ms = EXCLUDED.refreshed_at_ms;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_for_server(p_server_login TEXT)
|
||||||
|
RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
affected_user RECORD;
|
||||||
|
BEGIN
|
||||||
|
IF p_server_login IS NULL OR btrim(p_server_login) = '' THEN
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DELETE FROM user_access_servers_current
|
||||||
|
WHERE LOWER(server_login) = LOWER(p_server_login);
|
||||||
|
|
||||||
|
FOR affected_user IN
|
||||||
|
SELECT u.login
|
||||||
|
FROM solana_user_pda_current u
|
||||||
|
CROSS JOIN LATERAL jsonb_array_elements_text(
|
||||||
|
CASE
|
||||||
|
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
|
||||||
|
ELSE u.access_servers_json::jsonb
|
||||||
|
END
|
||||||
|
) AS access_server(login_value)
|
||||||
|
WHERE LOWER(btrim(access_server.login_value)) = LOWER(p_server_login)
|
||||||
|
LOOP
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_all()
|
||||||
|
RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
affected_user RECORD;
|
||||||
|
BEGIN
|
||||||
|
TRUNCATE TABLE user_access_servers_current;
|
||||||
|
|
||||||
|
FOR affected_user IN
|
||||||
|
SELECT login
|
||||||
|
FROM solana_user_pda_current
|
||||||
|
LOOP
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION trg_refresh_user_access_servers_from_user_pda_row()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
IF TG_OP = 'DELETE' THEN
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(OLD.login);
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_server(OLD.login);
|
||||||
|
RETURN OLD;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF TG_OP = 'UPDATE' AND LOWER(OLD.login) <> LOWER(NEW.login) THEN
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(OLD.login);
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_server(OLD.login);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(NEW.login);
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_server(NEW.login);
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION trg_refresh_user_access_servers_from_user_pda_truncate()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
TRUNCATE TABLE user_access_servers_current;
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS trg_refresh_user_access_servers_row ON solana_user_pda_current;
|
||||||
|
CREATE TRIGGER trg_refresh_user_access_servers_row
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE ON solana_user_pda_current
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION trg_refresh_user_access_servers_from_user_pda_row();
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS trg_refresh_user_access_servers_truncate ON solana_user_pda_current;
|
||||||
|
CREATE TRIGGER trg_refresh_user_access_servers_truncate
|
||||||
|
AFTER TRUNCATE ON solana_user_pda_current
|
||||||
|
FOR EACH STATEMENT
|
||||||
|
EXECUTE FUNCTION trg_refresh_user_access_servers_from_user_pda_truncate();
|
||||||
|
|
||||||
|
SELECT shine_refresh_user_access_servers_all();
|
||||||
|
|
||||||
|
INSERT INTO db_schema_version (id, schema_version, updated_at_ms)
|
||||||
|
VALUES (1, 2, CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT))
|
||||||
|
ON CONFLICT (id) DO UPDATE SET
|
||||||
|
schema_version = EXCLUDED.schema_version,
|
||||||
|
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
-- SHiNE PostgreSQL runtime schema v1
|
-- SHiNE PostgreSQL runtime schema v1
|
||||||
-- Дата: 2026-07-24
|
-- Дата: 2026-07-28
|
||||||
--
|
--
|
||||||
-- Назначение:
|
-- Назначение:
|
||||||
-- - поднять пустую PostgreSQL БД сервера SHiNE с нуля;
|
-- - поднять пустую PostgreSQL БД сервера SHiNE с нуля;
|
||||||
@@ -22,7 +22,7 @@ CREATE TABLE IF NOT EXISTS db_schema_version (
|
|||||||
);
|
);
|
||||||
|
|
||||||
INSERT INTO db_schema_version (id, schema_version, updated_at_ms)
|
INSERT INTO db_schema_version (id, schema_version, updated_at_ms)
|
||||||
VALUES (1, 1, CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT))
|
VALUES (1, 2, CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT))
|
||||||
ON CONFLICT (id) DO UPDATE SET
|
ON CONFLICT (id) DO UPDATE SET
|
||||||
schema_version = EXCLUDED.schema_version,
|
schema_version = EXCLUDED.schema_version,
|
||||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||||
@@ -109,6 +109,173 @@ CREATE TABLE IF NOT EXISTS solana_user_pda_current (
|
|||||||
CREATE INDEX IF NOT EXISTS idx_user_pda_current_slot
|
CREATE INDEX IF NOT EXISTS idx_user_pda_current_slot
|
||||||
ON solana_user_pda_current(slot);
|
ON solana_user_pda_current(slot);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user_access_servers_current (
|
||||||
|
user_login TEXT NOT NULL REFERENCES solana_user_pda_current(login) ON DELETE CASCADE,
|
||||||
|
server_login TEXT NOT NULL REFERENCES solana_user_pda_current(login) ON DELETE CASCADE,
|
||||||
|
server_url TEXT NOT NULL,
|
||||||
|
server_client_key TEXT NOT NULL,
|
||||||
|
user_record_number INTEGER NOT NULL,
|
||||||
|
user_updated_at_ms BIGINT NOT NULL,
|
||||||
|
server_record_number INTEGER NOT NULL,
|
||||||
|
server_updated_at_ms BIGINT NOT NULL,
|
||||||
|
refreshed_at_ms BIGINT NOT NULL,
|
||||||
|
PRIMARY KEY (user_login, server_login)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_access_servers_user
|
||||||
|
ON user_access_servers_current(user_login);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_access_servers_server
|
||||||
|
ON user_access_servers_current(server_login);
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_for_user(p_user_login TEXT)
|
||||||
|
RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
IF p_user_login IS NULL OR btrim(p_user_login) = '' THEN
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DELETE FROM user_access_servers_current
|
||||||
|
WHERE LOWER(user_login) = LOWER(p_user_login);
|
||||||
|
|
||||||
|
INSERT INTO user_access_servers_current (
|
||||||
|
user_login,
|
||||||
|
server_login,
|
||||||
|
server_url,
|
||||||
|
server_client_key,
|
||||||
|
user_record_number,
|
||||||
|
user_updated_at_ms,
|
||||||
|
server_record_number,
|
||||||
|
server_updated_at_ms,
|
||||||
|
refreshed_at_ms
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
u.login,
|
||||||
|
s.login,
|
||||||
|
s.server_address,
|
||||||
|
s.client_key,
|
||||||
|
u.record_number,
|
||||||
|
u.updated_at_ms,
|
||||||
|
s.record_number,
|
||||||
|
s.updated_at_ms,
|
||||||
|
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||||
|
FROM solana_user_pda_current u
|
||||||
|
CROSS JOIN LATERAL jsonb_array_elements_text(
|
||||||
|
CASE
|
||||||
|
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
|
||||||
|
ELSE u.access_servers_json::jsonb
|
||||||
|
END
|
||||||
|
) AS access_server(login_value)
|
||||||
|
JOIN solana_user_pda_current s
|
||||||
|
ON LOWER(s.login) = LOWER(btrim(access_server.login_value))
|
||||||
|
AND s.is_server = TRUE
|
||||||
|
AND btrim(COALESCE(s.server_address, '')) <> ''
|
||||||
|
WHERE LOWER(u.login) = LOWER(p_user_login)
|
||||||
|
ON CONFLICT (user_login, server_login) DO UPDATE SET
|
||||||
|
server_url = EXCLUDED.server_url,
|
||||||
|
server_client_key = EXCLUDED.server_client_key,
|
||||||
|
user_record_number = EXCLUDED.user_record_number,
|
||||||
|
user_updated_at_ms = EXCLUDED.user_updated_at_ms,
|
||||||
|
server_record_number = EXCLUDED.server_record_number,
|
||||||
|
server_updated_at_ms = EXCLUDED.server_updated_at_ms,
|
||||||
|
refreshed_at_ms = EXCLUDED.refreshed_at_ms;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_for_server(p_server_login TEXT)
|
||||||
|
RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
affected_user RECORD;
|
||||||
|
BEGIN
|
||||||
|
IF p_server_login IS NULL OR btrim(p_server_login) = '' THEN
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DELETE FROM user_access_servers_current
|
||||||
|
WHERE LOWER(server_login) = LOWER(p_server_login);
|
||||||
|
|
||||||
|
FOR affected_user IN
|
||||||
|
SELECT u.login
|
||||||
|
FROM solana_user_pda_current u
|
||||||
|
CROSS JOIN LATERAL jsonb_array_elements_text(
|
||||||
|
CASE
|
||||||
|
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
|
||||||
|
ELSE u.access_servers_json::jsonb
|
||||||
|
END
|
||||||
|
) AS access_server(login_value)
|
||||||
|
WHERE LOWER(btrim(access_server.login_value)) = LOWER(p_server_login)
|
||||||
|
LOOP
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_all()
|
||||||
|
RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
affected_user RECORD;
|
||||||
|
BEGIN
|
||||||
|
TRUNCATE TABLE user_access_servers_current;
|
||||||
|
|
||||||
|
FOR affected_user IN
|
||||||
|
SELECT login
|
||||||
|
FROM solana_user_pda_current
|
||||||
|
LOOP
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION trg_refresh_user_access_servers_from_user_pda_row()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
IF TG_OP = 'DELETE' THEN
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(OLD.login);
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_server(OLD.login);
|
||||||
|
RETURN OLD;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF TG_OP = 'UPDATE' AND LOWER(OLD.login) <> LOWER(NEW.login) THEN
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(OLD.login);
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_server(OLD.login);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(NEW.login);
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_server(NEW.login);
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION trg_refresh_user_access_servers_from_user_pda_truncate()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
TRUNCATE TABLE user_access_servers_current;
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS trg_refresh_user_access_servers_row ON solana_user_pda_current;
|
||||||
|
CREATE TRIGGER trg_refresh_user_access_servers_row
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE ON solana_user_pda_current
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION trg_refresh_user_access_servers_from_user_pda_row();
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS trg_refresh_user_access_servers_truncate ON solana_user_pda_current;
|
||||||
|
CREATE TRIGGER trg_refresh_user_access_servers_truncate
|
||||||
|
AFTER TRUNCATE ON solana_user_pda_current
|
||||||
|
FOR EACH STATEMENT
|
||||||
|
EXECUTE FUNCTION trg_refresh_user_access_servers_from_user_pda_truncate();
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS solana_user_pda_history (
|
CREATE TABLE IF NOT EXISTS solana_user_pda_history (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
tx_signature TEXT NOT NULL,
|
tx_signature TEXT NOT NULL,
|
||||||
|
|||||||
+202
@@ -996,6 +996,208 @@ public final class PostgresStorageRepository
|
|||||||
"CREATE INDEX IF NOT EXISTS idx_user_pda_history_slot " +
|
"CREATE INDEX IF NOT EXISTS idx_user_pda_history_slot " +
|
||||||
"ON solana_user_pda_history(slot)"
|
"ON solana_user_pda_history(slot)"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
statement.executeUpdate(
|
||||||
|
"CREATE TABLE IF NOT EXISTS user_access_servers_current (" +
|
||||||
|
"user_login TEXT NOT NULL REFERENCES solana_user_pda_current(login) ON DELETE CASCADE, " +
|
||||||
|
"server_login TEXT NOT NULL REFERENCES solana_user_pda_current(login) ON DELETE CASCADE, " +
|
||||||
|
"server_url TEXT NOT NULL, " +
|
||||||
|
"server_client_key TEXT NOT NULL, " +
|
||||||
|
"user_record_number INTEGER NOT NULL, " +
|
||||||
|
"user_updated_at_ms BIGINT NOT NULL, " +
|
||||||
|
"server_record_number INTEGER NOT NULL, " +
|
||||||
|
"server_updated_at_ms BIGINT NOT NULL, " +
|
||||||
|
"refreshed_at_ms BIGINT NOT NULL, " +
|
||||||
|
"PRIMARY KEY (user_login, server_login)" +
|
||||||
|
")"
|
||||||
|
);
|
||||||
|
|
||||||
|
statement.executeUpdate(
|
||||||
|
"CREATE INDEX IF NOT EXISTS idx_user_access_servers_user " +
|
||||||
|
"ON user_access_servers_current(user_login)"
|
||||||
|
);
|
||||||
|
statement.executeUpdate(
|
||||||
|
"CREATE INDEX IF NOT EXISTS idx_user_access_servers_server " +
|
||||||
|
"ON user_access_servers_current(server_login)"
|
||||||
|
);
|
||||||
|
|
||||||
|
statement.execute(
|
||||||
|
"""
|
||||||
|
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_for_user(p_user_login TEXT)
|
||||||
|
RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
IF p_user_login IS NULL OR btrim(p_user_login) = '' THEN
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DELETE FROM user_access_servers_current
|
||||||
|
WHERE LOWER(user_login) = LOWER(p_user_login);
|
||||||
|
|
||||||
|
INSERT INTO user_access_servers_current (
|
||||||
|
user_login,
|
||||||
|
server_login,
|
||||||
|
server_url,
|
||||||
|
server_client_key,
|
||||||
|
user_record_number,
|
||||||
|
user_updated_at_ms,
|
||||||
|
server_record_number,
|
||||||
|
server_updated_at_ms,
|
||||||
|
refreshed_at_ms
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
u.login,
|
||||||
|
s.login,
|
||||||
|
s.server_address,
|
||||||
|
s.client_key,
|
||||||
|
u.record_number,
|
||||||
|
u.updated_at_ms,
|
||||||
|
s.record_number,
|
||||||
|
s.updated_at_ms,
|
||||||
|
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||||
|
FROM solana_user_pda_current u
|
||||||
|
CROSS JOIN LATERAL jsonb_array_elements_text(
|
||||||
|
CASE
|
||||||
|
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
|
||||||
|
ELSE u.access_servers_json::jsonb
|
||||||
|
END
|
||||||
|
) AS access_server(login_value)
|
||||||
|
JOIN solana_user_pda_current s
|
||||||
|
ON LOWER(s.login) = LOWER(btrim(access_server.login_value))
|
||||||
|
AND s.is_server = TRUE
|
||||||
|
AND btrim(COALESCE(s.server_address, '')) <> ''
|
||||||
|
WHERE LOWER(u.login) = LOWER(p_user_login)
|
||||||
|
ON CONFLICT (user_login, server_login) DO UPDATE SET
|
||||||
|
server_url = EXCLUDED.server_url,
|
||||||
|
server_client_key = EXCLUDED.server_client_key,
|
||||||
|
user_record_number = EXCLUDED.user_record_number,
|
||||||
|
user_updated_at_ms = EXCLUDED.user_updated_at_ms,
|
||||||
|
server_record_number = EXCLUDED.server_record_number,
|
||||||
|
server_updated_at_ms = EXCLUDED.server_updated_at_ms,
|
||||||
|
refreshed_at_ms = EXCLUDED.refreshed_at_ms;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
|
||||||
|
statement.execute(
|
||||||
|
"""
|
||||||
|
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_for_server(p_server_login TEXT)
|
||||||
|
RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
affected_user RECORD;
|
||||||
|
BEGIN
|
||||||
|
IF p_server_login IS NULL OR btrim(p_server_login) = '' THEN
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DELETE FROM user_access_servers_current
|
||||||
|
WHERE LOWER(server_login) = LOWER(p_server_login);
|
||||||
|
|
||||||
|
FOR affected_user IN
|
||||||
|
SELECT u.login
|
||||||
|
FROM solana_user_pda_current u
|
||||||
|
CROSS JOIN LATERAL jsonb_array_elements_text(
|
||||||
|
CASE
|
||||||
|
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
|
||||||
|
ELSE u.access_servers_json::jsonb
|
||||||
|
END
|
||||||
|
) AS access_server(login_value)
|
||||||
|
WHERE LOWER(btrim(access_server.login_value)) = LOWER(p_server_login)
|
||||||
|
LOOP
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
|
||||||
|
statement.execute(
|
||||||
|
"""
|
||||||
|
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_all()
|
||||||
|
RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
affected_user RECORD;
|
||||||
|
BEGIN
|
||||||
|
TRUNCATE TABLE user_access_servers_current;
|
||||||
|
|
||||||
|
FOR affected_user IN
|
||||||
|
SELECT login
|
||||||
|
FROM solana_user_pda_current
|
||||||
|
LOOP
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
|
||||||
|
statement.execute(
|
||||||
|
"""
|
||||||
|
CREATE OR REPLACE FUNCTION trg_refresh_user_access_servers_from_user_pda_row()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
IF TG_OP = 'DELETE' THEN
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(OLD.login);
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_server(OLD.login);
|
||||||
|
RETURN OLD;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF TG_OP = 'UPDATE' AND LOWER(OLD.login) <> LOWER(NEW.login) THEN
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(OLD.login);
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_server(OLD.login);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_user(NEW.login);
|
||||||
|
PERFORM shine_refresh_user_access_servers_for_server(NEW.login);
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
|
||||||
|
statement.execute(
|
||||||
|
"""
|
||||||
|
CREATE OR REPLACE FUNCTION trg_refresh_user_access_servers_from_user_pda_truncate()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
TRUNCATE TABLE user_access_servers_current;
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
|
||||||
|
statement.executeUpdate(
|
||||||
|
"DROP TRIGGER IF EXISTS trg_refresh_user_access_servers_row ON solana_user_pda_current"
|
||||||
|
);
|
||||||
|
statement.executeUpdate(
|
||||||
|
"CREATE TRIGGER trg_refresh_user_access_servers_row " +
|
||||||
|
"AFTER INSERT OR UPDATE OR DELETE ON solana_user_pda_current " +
|
||||||
|
"FOR EACH ROW " +
|
||||||
|
"EXECUTE FUNCTION trg_refresh_user_access_servers_from_user_pda_row()"
|
||||||
|
);
|
||||||
|
|
||||||
|
statement.executeUpdate(
|
||||||
|
"DROP TRIGGER IF EXISTS trg_refresh_user_access_servers_truncate ON solana_user_pda_current"
|
||||||
|
);
|
||||||
|
statement.executeUpdate(
|
||||||
|
"CREATE TRIGGER trg_refresh_user_access_servers_truncate " +
|
||||||
|
"AFTER TRUNCATE ON solana_user_pda_current " +
|
||||||
|
"FOR EACH STATEMENT " +
|
||||||
|
"EXECUTE FUNCTION trg_refresh_user_access_servers_from_user_pda_truncate()"
|
||||||
|
);
|
||||||
|
|
||||||
|
statement.execute("SELECT shine_refresh_user_access_servers_all()");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.355
|
client.version=1.2.355
|
||||||
server.version=1.2.338
|
server.version=1.2.339
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
Этот документ фиксирует целевой стандарт PostgreSQL, который должен использоваться на всех серверных контурах SHiNE после ухода от SQLite.
|
Этот документ фиксирует целевой стандарт PostgreSQL, который должен использоваться на всех серверных контурах SHiNE после ухода от SQLite.
|
||||||
|
|
||||||
Пока это не текущий production-state, а согласованная целевая схема, под которую можно писать миграции, deploy-скрипты и server-конфиги.
|
Важно:
|
||||||
|
|
||||||
|
- целевой стандарт для SHiNE: `PostgreSQL 18.x`;
|
||||||
|
- текущий факт на production `shineup.me` и `server2.shineup.me` на дату `2026-07-28`: `PostgreSQL 18.4`;
|
||||||
|
- текущий факт на test/devnet `t1` / `t2` / `t3` / `t4` на дату `2026-07-28`: `PostgreSQL 18.4`.
|
||||||
|
|
||||||
## Цель
|
## Цель
|
||||||
|
|
||||||
@@ -71,19 +75,19 @@ PostgreSQL не должна быть доступна из интернета.
|
|||||||
Production:
|
Production:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/home/player/SHiNE/postgres/shine_server_db
|
/home/player/SHiNE/postgres/data
|
||||||
```
|
```
|
||||||
|
|
||||||
Test/devnet:
|
Test/devnet:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/home/player/tX/postgres/shine_server_db
|
/home/player/tX/postgres/data
|
||||||
```
|
```
|
||||||
|
|
||||||
Если на одном сервере появится несколько баз SHiNE-сервисов, раскладывать их по отдельным каталогам:
|
Если на одном сервере появится несколько баз SHiNE-сервисов, раскладывать их по отдельным каталогам:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/home/player/SHiNE/postgres/shine_server_db
|
/home/player/SHiNE/postgres/data
|
||||||
/home/player/SHiNE/postgres/analytics_db
|
/home/player/SHiNE/postgres/analytics_db
|
||||||
/home/player/SHiNE/postgres/other_service_db
|
/home/player/SHiNE/postgres/other_service_db
|
||||||
```
|
```
|
||||||
@@ -142,11 +146,10 @@ services:
|
|||||||
POSTGRES_DB: ${POSTGRES_SUPERUSER_DB}
|
POSTGRES_DB: ${POSTGRES_SUPERUSER_DB}
|
||||||
POSTGRES_USER: ${POSTGRES_SUPERUSER}
|
POSTGRES_USER: ${POSTGRES_SUPERUSER}
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_SUPERUSER_PASSWORD}
|
POSTGRES_PASSWORD: ${POSTGRES_SUPERUSER_PASSWORD}
|
||||||
PGDATA: /var/lib/postgresql/data/pgdata
|
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:5432:5432"
|
- "127.0.0.1:5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- ${SHINE_POSTGRES_DATA_DIR}:/var/lib/postgresql/data
|
- ${SHINE_POSTGRES_DATA_DIR}:/var/lib/postgresql
|
||||||
- ./initdb:/docker-entrypoint-initdb.d:ro
|
- ./initdb:/docker-entrypoint-initdb.d:ro
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test:
|
test:
|
||||||
@@ -167,15 +170,21 @@ POSTGRES_SUPERUSER_PASSWORD=change_me
|
|||||||
SHINE_APP_DB=shine_server_db
|
SHINE_APP_DB=shine_server_db
|
||||||
SHINE_APP_USER=shine_server
|
SHINE_APP_USER=shine_server
|
||||||
SHINE_APP_PASSWORD=change_me_too
|
SHINE_APP_PASSWORD=change_me_too
|
||||||
SHINE_POSTGRES_DATA_DIR=/home/player/SHiNE/postgres/shine_server_db
|
SHINE_POSTGRES_DATA_DIR=/home/player/SHiNE/postgres/data
|
||||||
```
|
```
|
||||||
|
|
||||||
Для test/devnet путь адаптировать под конкретный контур:
|
Для test/devnet путь адаптировать под конкретный контур:
|
||||||
|
|
||||||
```dotenv
|
```dotenv
|
||||||
SHINE_POSTGRES_DATA_DIR=/home/player/t2/postgres/shine_server_db
|
SHINE_POSTGRES_DATA_DIR=/home/player/t2/postgres/data
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Важно для `postgres:18+`:
|
||||||
|
|
||||||
|
- bind-mount нужно делать на `/var/lib/postgresql`, а не на `/var/lib/postgresql/data`;
|
||||||
|
- если смонтировать старый путь `/var/lib/postgresql/data`, контейнер `postgres:18` может уйти в restart-loop ещё до инициализации БД;
|
||||||
|
- после первого старта PostgreSQL сам создаст внутри host-каталога структуру вида `18/docker`.
|
||||||
|
|
||||||
## Подключение приложения
|
## Подключение приложения
|
||||||
|
|
||||||
SHiNE server должен подключаться именно к:
|
SHiNE server должен подключаться именно к:
|
||||||
@@ -203,11 +212,12 @@ SHiNE server должен подключаться именно к:
|
|||||||
|
|
||||||
## Для будущей миграции SHiNE
|
## Для будущей миграции SHiNE
|
||||||
|
|
||||||
При переводе SHiNE server с SQLite на PostgreSQL считать обязательным:
|
При переводе нового серверного контура SHiNE с SQLite на PostgreSQL считать обязательным:
|
||||||
|
|
||||||
- сначала поднять PostgreSQL по этому стандарту;
|
- сначала поднять PostgreSQL по этому стандарту;
|
||||||
- затем добавить серверные конфиги подключения;
|
- затем добавить серверные конфиги подключения;
|
||||||
- затем прогнать миграции схемы;
|
- затем прогнать миграции схемы;
|
||||||
- только потом переключать runtime приложения на PostgreSQL.
|
- только потом переключать runtime приложения на PostgreSQL.
|
||||||
|
|
||||||
До фактического rollout на конкретный сервер эта БД может ещё отсутствовать. Этот документ описывает не текущее наличие БД, а обязательный целевой стандарт для всех серверов SHiNE.
|
Для новых серверов этот документ использовать как обязательный шаблон.
|
||||||
|
На `2026-07-28` и production, и test/devnet контуры SHiNE уже работают на `PostgreSQL 18.4`.
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
Production-контуров два. В документах и скриптах использовать домены, а не IP: физический VPS можно заменить без изменения deploy-логики.
|
Production-контуров два. В документах и скриптах использовать домены, а не IP: физический VPS можно заменить без изменения deploy-логики.
|
||||||
|
|
||||||
|
Текущее фактическое состояние на `2026-07-28`:
|
||||||
|
|
||||||
|
- `shineup.me` и `server2.shineup.me` оба уже переведены на `PostgreSQL 18.4`;
|
||||||
|
- оба production используют локальный Docker-контейнер `shine-postgres`;
|
||||||
|
- оба production работают без `SQLite`;
|
||||||
|
- оба production используют Helius mainnet RPC/WS для Solana users sync и runtime Solana RPC.
|
||||||
|
|
||||||
## Основной production
|
## Основной production
|
||||||
|
|
||||||
- Домен: `shineup.me`
|
- Домен: `shineup.me`
|
||||||
@@ -16,6 +23,17 @@ Production-контуров два. В документах и скриптах
|
|||||||
- Caddy site: `shineup.me`
|
- Caddy site: `shineup.me`
|
||||||
- WebSocket: `/ws` -> `127.0.0.1:7070`
|
- WebSocket: `/ws` -> `127.0.0.1:7070`
|
||||||
- Solana cluster: `mainnet-beta`
|
- Solana cluster: `mainnet-beta`
|
||||||
|
- Solana RPC/WS: Helius mainnet
|
||||||
|
- Runtime DB: PostgreSQL в Docker
|
||||||
|
- PostgreSQL container: `shine-postgres`
|
||||||
|
- PostgreSQL image: `postgres:18`
|
||||||
|
- PostgreSQL engine version на `2026-07-28`: `18.4`
|
||||||
|
- PostgreSQL data dir: `/home/player/SHiNE/postgres`
|
||||||
|
- PostgreSQL app DB: `shine_main_db`
|
||||||
|
- PostgreSQL app user: `shine_main`
|
||||||
|
- Web push: отдельный production VAPID key для `shineup.me`
|
||||||
|
- TURN для звонков: `turn1.shineup.me`, `turn2.shineup.me`
|
||||||
|
- SQLite runtime больше не используется
|
||||||
|
|
||||||
Deploy:
|
Deploy:
|
||||||
|
|
||||||
@@ -38,6 +56,17 @@ bash deploy/scripts/production_shineupme_ui.sh
|
|||||||
- Caddy site: `server2.shineup.me`
|
- Caddy site: `server2.shineup.me`
|
||||||
- WebSocket: `/ws` -> `127.0.0.1:7070`
|
- WebSocket: `/ws` -> `127.0.0.1:7070`
|
||||||
- Solana cluster: `mainnet-beta`
|
- Solana cluster: `mainnet-beta`
|
||||||
|
- Solana RPC/WS: Helius mainnet
|
||||||
|
- Runtime DB: PostgreSQL в Docker
|
||||||
|
- PostgreSQL container: `shine-postgres`
|
||||||
|
- PostgreSQL image: `postgres:18`
|
||||||
|
- PostgreSQL engine version на `2026-07-28`: `18.4`
|
||||||
|
- PostgreSQL data dir: `/home/player/SHiNE/postgres`
|
||||||
|
- PostgreSQL app DB: `shine_server2_db`
|
||||||
|
- PostgreSQL app user: `shine_server2`
|
||||||
|
- Web push: отдельный production VAPID key для `server2.shineup.me`
|
||||||
|
- TURN для звонков: `turn1.shineup.me`, `turn3.shineup.me`
|
||||||
|
- SQLite runtime больше не используется
|
||||||
|
|
||||||
Deploy:
|
Deploy:
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,14 @@
|
|||||||
- Второй production: `server2.shineup.me`.
|
- Второй production: `server2.shineup.me`.
|
||||||
- Test/devnet стенд: `t1.shineup.me`, `t2.shineup.me`, `t3.shineup.me`, `t4.shineup.me`.
|
- Test/devnet стенд: `t1.shineup.me`, `t2.shineup.me`, `t3.shineup.me`, `t4.shineup.me`.
|
||||||
|
|
||||||
|
Краткое текущее состояние на `2026-07-28`:
|
||||||
|
|
||||||
|
- оба production уже на `PostgreSQL 18.4` в отдельных Docker-контейнерах `shine-postgres`;
|
||||||
|
- весь test/devnet стенд `t1`-`t4` тоже уже на `PostgreSQL 18.4`;
|
||||||
|
- тестовые контуры используют общий Docker-контейнер `shine-test-postgres`;
|
||||||
|
- production использует Helius mainnet, test/devnet использует Helius devnet;
|
||||||
|
- актуальные подробности по всем шести серверам смотреть в `PRODUCTION_SERVERS.md` и `TEST_SERVERS.md`.
|
||||||
|
|
||||||
## Основные документы
|
## Основные документы
|
||||||
|
|
||||||
- `PRODUCTION_SERVERS.md` — production-контуры.
|
- `PRODUCTION_SERVERS.md` — production-контуры.
|
||||||
|
|||||||
@@ -41,16 +41,19 @@ mkdir -p /home/player/tX/UI
|
|||||||
|
|
||||||
Каждый сервер должен иметь локальный внешний конфиг в рабочей директории сервиса.
|
Каждый сервер должен иметь локальный внешний конфиг в рабочей директории сервиса.
|
||||||
|
|
||||||
|
Актуальные production-значения для `shineup.me` и `server2.shineup.me` смотреть в `deploy/PRODUCTION_SERVERS.md`.
|
||||||
|
Ниже именно шаблон для нового контура, а не снимок текущего production.
|
||||||
|
|
||||||
Production пример:
|
Production пример:
|
||||||
|
|
||||||
```properties
|
```properties
|
||||||
server.port=7070
|
server.port=7070
|
||||||
server.SHiNE.login=shineupme
|
server.SHiNE.login=<SERVER_LOGIN>
|
||||||
db.url=jdbc:postgresql://127.0.0.1:5432/shine_server_db
|
db.url=jdbc:postgresql://127.0.0.1:5432/<APP_DB>
|
||||||
db.user=shine_server
|
db.user=<APP_DB_USER>
|
||||||
db.password=CHANGE_ME
|
db.password=CHANGE_ME
|
||||||
server.ui.indexPath=/home/player/SHiNE/shine-ui/index.html
|
server.ui.indexPath=/home/player/SHiNE/shine-ui/index.html
|
||||||
server.info.url=https://shineup.me
|
server.info.url=https://<DOMAIN>
|
||||||
server.info.origin=production
|
server.info.origin=production
|
||||||
solana.cluster=mainnet-beta
|
solana.cluster=mainnet-beta
|
||||||
```
|
```
|
||||||
@@ -59,17 +62,20 @@ Test/devnet пример:
|
|||||||
|
|
||||||
```properties
|
```properties
|
||||||
server.port=7102
|
server.port=7102
|
||||||
server.SHiNE.login=server_t2
|
server.SHiNE.login=<SERVER_LOGIN>
|
||||||
db.url=jdbc:postgresql://127.0.0.1:5432/shine_t2_db
|
db.url=jdbc:postgresql://127.0.0.1:5432/<APP_DB>
|
||||||
db.user=shine_t2
|
db.user=<APP_DB_USER>
|
||||||
db.password=CHANGE_ME
|
db.password=CHANGE_ME
|
||||||
server.ui.indexPath=/home/player/t2/UI/index.html
|
server.ui.indexPath=/home/player/t2/UI/index.html
|
||||||
server.info.url=https://t2.shineup.me
|
server.info.url=https://t2.shineup.me
|
||||||
server.info.origin=devnet
|
server.info.origin=devnet
|
||||||
solana.cluster=devnet
|
solana.cluster=devnet
|
||||||
solana.rpcUrl=https://api.devnet.solana.com
|
solana.rpcUrl=https://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Рекомендуемый образ PostgreSQL для новых контуров: `postgres:18`.
|
||||||
|
Для `postgres:18+` bind-mount нужно делать на `/var/lib/postgresql`, а не на старый путь `/var/lib/postgresql/data`.
|
||||||
|
|
||||||
Если указан `db.url` и в выбранной БД ещё нет таблицы `db_schema_version`,
|
Если указан `db.url` и в выбранной БД ещё нет таблицы `db_schema_version`,
|
||||||
сервер сам создаст runtime-схему PostgreSQL при первом старте.
|
сервер сам создаст runtime-схему PostgreSQL при первом старте.
|
||||||
|
|
||||||
|
|||||||
@@ -10,17 +10,23 @@
|
|||||||
- `solana_sync_state`
|
- `solana_sync_state`
|
||||||
- `solana_sync_tx_history`
|
- `solana_sync_tx_history`
|
||||||
- `solana_user_pda_current`
|
- `solana_user_pda_current`
|
||||||
|
- `user_access_servers_current`
|
||||||
- `solana_user_pda_history`
|
- `solana_user_pda_history`
|
||||||
- источник истины по пользовательским PDA: `solana_user_pda_current`.
|
- источник истины по пользовательским PDA: `solana_user_pda_current`.
|
||||||
|
- `user_access_servers_current` — это вторичная локальная проекция для быстрого роутинга DM по access servers;
|
||||||
|
она автоматически пересобирается из `solana_user_pda_current`, включая backfill для уже существующих пользователей.
|
||||||
|
|
||||||
## Что должно быть настроено в `application.properties`
|
## Что должно быть настроено в `application.properties`
|
||||||
|
|
||||||
|
Для текущих production-значений `shineup.me` и `server2.shineup.me` смотреть `deploy/PRODUCTION_SERVERS.md`.
|
||||||
|
Ниже универсальный пример для нового контура.
|
||||||
|
|
||||||
```properties
|
```properties
|
||||||
solana.users.sync.enabled=true
|
solana.users.sync.enabled=true
|
||||||
solana.users.sync.rpcUrl=https://api.devnet.solana.com
|
solana.users.sync.rpcUrl=https://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4
|
||||||
solana.users.sync.wsUrl=wss://api.devnet.solana.com/
|
solana.users.sync.wsUrl=wss://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4
|
||||||
solana.users.sync.databaseUrl=jdbc:postgresql://127.0.0.1:5432/shine_server_db
|
solana.users.sync.databaseUrl=jdbc:postgresql://127.0.0.1:5432/<APP_DB>
|
||||||
solana.users.sync.dbUser=shine_server
|
solana.users.sync.dbUser=<APP_DB_USER>
|
||||||
solana.users.sync.dbPassword=CHANGE_ME
|
solana.users.sync.dbPassword=CHANGE_ME
|
||||||
solana.users.sync.pollIntervalSeconds=300
|
solana.users.sync.pollIntervalSeconds=300
|
||||||
```
|
```
|
||||||
@@ -43,16 +49,17 @@ SHiNE-server/shine-server-db/src/main/resources/postgres/schema_v1.sql
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
psql \
|
psql \
|
||||||
"postgresql://shine_server:CHANGE_ME@127.0.0.1:5432/shine_server_db" \
|
"postgresql://<APP_DB_USER>:CHANGE_ME@127.0.0.1:5432/<APP_DB>" \
|
||||||
-f SHiNE-server/shine-server-db/src/main/resources/postgres/schema_v1.sql
|
-f SHiNE-server/shine-server-db/src/main/resources/postgres/schema_v1.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
Скрипт:
|
Скрипт:
|
||||||
|
|
||||||
- создаёт таблицу версии схемы `db_schema_version`;
|
- создаёт таблицу версии схемы `db_schema_version`;
|
||||||
- ставит `schema_version = 1`;
|
- ставит `schema_version = 2`;
|
||||||
- создаёт таблицы sync-модуля Solana users;
|
- создаёт таблицы sync-модуля Solana users;
|
||||||
- создаёт server runtime tables;
|
- создаёт server runtime tables;
|
||||||
|
- создаёт триггеры и функции автоматической актуализации `user_access_servers_current`;
|
||||||
- не создаёт удалённые legacy-таблицы старого runtime для пользователей и DM;
|
- не создаёт удалённые legacy-таблицы старого runtime для пользователей и DM;
|
||||||
- использует `signed_messages` как единственную таблицу серверных DM.
|
- использует `signed_messages` как единственную таблицу серверных DM.
|
||||||
|
|
||||||
@@ -75,6 +82,9 @@ cd /home/player/SHiNE/postgres
|
|||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Шаблон `deploy/postgres/docker-compose.yml.example` рассчитан на `postgres:18`.
|
||||||
|
Для `postgres:18+` он монтирует host-каталог в `/var/lib/postgresql`, это важно для корректного старта контейнера.
|
||||||
|
|
||||||
После старта контейнера:
|
После старта контейнера:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+28
-8
@@ -9,21 +9,34 @@
|
|||||||
|
|
||||||
Это не production. Все deploy-цели задаются через домены.
|
Это не production. Все deploy-цели задаются через домены.
|
||||||
|
|
||||||
|
Текущее фактическое состояние на `2026-07-28`:
|
||||||
|
|
||||||
|
- все четыре тестовых контура `t1` / `t2` / `t3` / `t4` уже работают без `SQLite`;
|
||||||
|
- все четыре контура используют `PostgreSQL 18.4`;
|
||||||
|
- PostgreSQL на test-хосте общий для всех четырёх контуров и запущен в Docker-контейнере `shine-test-postgres`;
|
||||||
|
- все четыре контура используют один и тот же Helius devnet RPC/WS endpoint;
|
||||||
|
- во всех четырёх UI включён web push client-код и встроен один и тот же test `VAPID` public key.
|
||||||
|
|
||||||
## Общие параметры
|
## Общие параметры
|
||||||
|
|
||||||
- SSH: `player@tX.shineup.me`
|
- SSH: `player@tX.shineup.me`
|
||||||
- Solana cluster: `devnet`
|
- Solana cluster: `devnet`
|
||||||
- Solana RPC: `https://api.devnet.solana.com`
|
- Solana RPC: `https://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4`
|
||||||
|
- Solana WS: `wss://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4`
|
||||||
- Caddy config: `/etc/caddy/Caddyfile`
|
- Caddy config: `/etc/caddy/Caddyfile`
|
||||||
|
- Общий PostgreSQL container: `shine-test-postgres`
|
||||||
|
- PostgreSQL image: `postgres:18`
|
||||||
|
- PostgreSQL engine version на `2026-07-28`: `18.4`
|
||||||
|
- Web push в UI: общий test `VAPID` public key `BOdoWZndZRaNe9kyUFsJ5-xEfFABXNKennAKg15Z7ycAwUIQ7yDV_sIWWYJCwJriN4g9oU-CyJPrn1U6lfxuDbI`
|
||||||
|
|
||||||
## Инстансы
|
## Инстансы
|
||||||
|
|
||||||
| Домен | Логин сервера | Сервер | UI | Порт | systemd |
|
| Домен | Логин сервера | Сервер | UI | Порт | systemd | PostgreSQL DB | PostgreSQL user |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|---|
|
||||||
| `t1.shineup.me` | `server_t1` | `/home/player/t1/server` | `/home/player/t1/UI` | `7101` | `shine-t1.service` |
|
| `t1.shineup.me` | `server_t1` | `/home/player/t1/server` | `/home/player/t1/UI` | `7101` | `shine-t1.service` | `shine_t1_db` | `shine_t1` |
|
||||||
| `t2.shineup.me` | `server_t2` | `/home/player/t2/server` | `/home/player/t2/UI` | `7102` | `shine-t2.service` |
|
| `t2.shineup.me` | `server_t2` | `/home/player/t2/server` | `/home/player/t2/UI` | `7102` | `shine-t2.service` | `shine_t2_db` | `shine_t2` |
|
||||||
| `t3.shineup.me` | `server_t3` | `/home/player/t3/server` | `/home/player/t3/UI` | `7103` | `shine-t3.service` |
|
| `t3.shineup.me` | `server_t3` | `/home/player/t3/server` | `/home/player/t3/UI` | `7103` | `shine-t3.service` | `shine_t3_db` | `shine_t3` |
|
||||||
| `t4.shineup.me` | `server_t4` | `/home/player/t4/server` | `/home/player/t4/UI` | `7104` | `shine-t4.service` |
|
| `t4.shineup.me` | `server_t4` | `/home/player/t4/server` | `/home/player/t4/UI` | `7104` | `shine-t4.service` | `shine_t4_db` | `shine_t4` |
|
||||||
|
|
||||||
## Deploy
|
## Deploy
|
||||||
|
|
||||||
@@ -54,8 +67,15 @@ curl -I https://t4.shineup.me
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl --no-pager --full status caddy shine-t1 shine-t2 shine-t3 shine-t4
|
sudo systemctl --no-pager --full status caddy shine-t1 shine-t2 shine-t3 shine-t4
|
||||||
|
sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Operational-нюанс
|
## Operational-нюанс
|
||||||
|
|
||||||
Официальный `https://api.devnet.solana.com` может отдавать `HTTP 429`, если несколько инстансов одновременно делают bootstrap PDA/sync-серверов. Если после деплоя какой-то инстанс не подтянул `sync_servers`, рестартовать сервисы по одному с паузой.
|
Общий Helius devnet endpoint тоже может начать ограничивать запросы, если несколько инстансов одновременно делают тяжёлый bootstrap. Если после деплоя какой-то инстанс не подтянул `sync_servers` или завис на startup sync, рестартовать сервисы по одному с паузой.
|
||||||
|
|
||||||
|
Отдельный нюанс по web push:
|
||||||
|
|
||||||
|
- у `t1`, `t2`, `t3`, `t4` в UI уже есть одинаковый встроенный `VAPID` public key;
|
||||||
|
- то есть `t3` и `t4` используют тот же web push клиентский код, что и `t1` / `t2`;
|
||||||
|
- если позже понадобится разделить push-ключи по тестовым контурам, это нужно делать отдельным UI deploy с новым `DEPLOY_WEBPUSH_VAPID_PUBLIC`.
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ POSTGRES_SUPERUSER_PASSWORD=CHANGE_ME_SUPERUSER
|
|||||||
SHINE_APP_DB=shine_server_db
|
SHINE_APP_DB=shine_server_db
|
||||||
SHINE_APP_USER=shine_server
|
SHINE_APP_USER=shine_server
|
||||||
SHINE_APP_PASSWORD=CHANGE_ME_APP
|
SHINE_APP_PASSWORD=CHANGE_ME_APP
|
||||||
SHINE_POSTGRES_DATA_DIR=/home/player/SHiNE/postgres/shine_server_db
|
SHINE_POSTGRES_DATA_DIR=/home/player/SHiNE/postgres/data
|
||||||
|
|||||||
@@ -9,11 +9,10 @@ services:
|
|||||||
POSTGRES_DB: ${POSTGRES_SUPERUSER_DB}
|
POSTGRES_DB: ${POSTGRES_SUPERUSER_DB}
|
||||||
POSTGRES_USER: ${POSTGRES_SUPERUSER}
|
POSTGRES_USER: ${POSTGRES_SUPERUSER}
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_SUPERUSER_PASSWORD}
|
POSTGRES_PASSWORD: ${POSTGRES_SUPERUSER_PASSWORD}
|
||||||
PGDATA: /var/lib/postgresql/data/pgdata
|
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:5432:5432"
|
- "127.0.0.1:5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- ${SHINE_POSTGRES_DATA_DIR}:/var/lib/postgresql/data
|
- ${SHINE_POSTGRES_DATA_DIR}:/var/lib/postgresql
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test:
|
test:
|
||||||
- CMD-SHELL
|
- CMD-SHELL
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ EXPECTED_CADDY_SITE="t1.shineup.me" \
|
|||||||
DEPLOY_SERVER_LOGIN="server_t1" \
|
DEPLOY_SERVER_LOGIN="server_t1" \
|
||||||
DEPLOY_SERVER_ADDRESS="t1.shineup.me" \
|
DEPLOY_SERVER_ADDRESS="t1.shineup.me" \
|
||||||
DEPLOY_SOLANA_CLUSTER="devnet" \
|
DEPLOY_SOLANA_CLUSTER="devnet" \
|
||||||
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
|
DEPLOY_SOLANA_ENDPOINT="https://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4" \
|
||||||
TARGET_URL="https://t1.shineup.me" \
|
TARGET_URL="https://t1.shineup.me" \
|
||||||
bash "$(dirname "$0")/deploy_ui.sh"
|
bash "$(dirname "$0")/deploy_ui.sh"
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ EXPECTED_CADDY_SITE="t2.shineup.me" \
|
|||||||
DEPLOY_SERVER_LOGIN="server_t2" \
|
DEPLOY_SERVER_LOGIN="server_t2" \
|
||||||
DEPLOY_SERVER_ADDRESS="t2.shineup.me" \
|
DEPLOY_SERVER_ADDRESS="t2.shineup.me" \
|
||||||
DEPLOY_SOLANA_CLUSTER="devnet" \
|
DEPLOY_SOLANA_CLUSTER="devnet" \
|
||||||
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
|
DEPLOY_SOLANA_ENDPOINT="https://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4" \
|
||||||
TARGET_URL="https://t2.shineup.me" \
|
TARGET_URL="https://t2.shineup.me" \
|
||||||
bash "$(dirname "$0")/deploy_ui.sh"
|
bash "$(dirname "$0")/deploy_ui.sh"
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ EXPECTED_CADDY_SITE="t3.shineup.me" \
|
|||||||
DEPLOY_SERVER_LOGIN="server_t3" \
|
DEPLOY_SERVER_LOGIN="server_t3" \
|
||||||
DEPLOY_SERVER_ADDRESS="t3.shineup.me" \
|
DEPLOY_SERVER_ADDRESS="t3.shineup.me" \
|
||||||
DEPLOY_SOLANA_CLUSTER="devnet" \
|
DEPLOY_SOLANA_CLUSTER="devnet" \
|
||||||
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
|
DEPLOY_SOLANA_ENDPOINT="https://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4" \
|
||||||
TARGET_URL="https://t3.shineup.me" \
|
TARGET_URL="https://t3.shineup.me" \
|
||||||
bash "$(dirname "$0")/deploy_ui.sh"
|
bash "$(dirname "$0")/deploy_ui.sh"
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ EXPECTED_CADDY_SITE="t4.shineup.me" \
|
|||||||
DEPLOY_SERVER_LOGIN="server_t4" \
|
DEPLOY_SERVER_LOGIN="server_t4" \
|
||||||
DEPLOY_SERVER_ADDRESS="t4.shineup.me" \
|
DEPLOY_SERVER_ADDRESS="t4.shineup.me" \
|
||||||
DEPLOY_SOLANA_CLUSTER="devnet" \
|
DEPLOY_SOLANA_CLUSTER="devnet" \
|
||||||
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \
|
DEPLOY_SOLANA_ENDPOINT="https://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4" \
|
||||||
TARGET_URL="https://t4.shineup.me" \
|
TARGET_URL="https://t4.shineup.me" \
|
||||||
bash "$(dirname "$0")/deploy_ui.sh"
|
bash "$(dirname "$0")/deploy_ui.sh"
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
- сервер запускает sync-модуль до продолжения собственного startup;
|
- сервер запускает sync-модуль до продолжения собственного startup;
|
||||||
- server startup ждёт входа модуля в состояние `READY`;
|
- server startup ждёт входа модуля в состояние `READY`;
|
||||||
- актуальный источник истины по пользователям для нового PostgreSQL runtime-слоя: `solana_user_pda_current`.
|
- актуальный источник истины по пользователям для нового PostgreSQL runtime-слоя: `solana_user_pda_current`.
|
||||||
|
- для быстрого локального маршрутизационного lookup серверов доступа поддерживается вторичная проекция
|
||||||
|
`user_access_servers_current`, которая автоматически пересобирается из `solana_user_pda_current`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -193,6 +195,7 @@ WebSocket `programSubscribe` хорош для realtime, но недостато
|
|||||||
6. подтянуть актуальное состояние затронутых PDA;
|
6. подтянуть актуальное состояние затронутых PDA;
|
||||||
7. обновить:
|
7. обновить:
|
||||||
- `solana_user_pda_current`
|
- `solana_user_pda_current`
|
||||||
|
- `user_access_servers_current`
|
||||||
- `solana_user_pda_history`
|
- `solana_user_pda_history`
|
||||||
- `solana_sync_state`
|
- `solana_sync_state`
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user