-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAdminAnnouncementInboxWriter.cs
More file actions
147 lines (124 loc) · 7 KB
/
Copy pathAdminAnnouncementInboxWriter.cs
File metadata and controls
147 lines (124 loc) · 7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
using Admin.Application.Consumers;
using BuildingBlocks.Infrastructure.Persistence;
using Npgsql;
namespace Admin.Infrastructure.Inbox;
internal sealed class AdminAnnouncementInboxWriter : IAdminAnnouncementInbox, IDatabaseMigration
{
private readonly string _connectionString;
private readonly NpgsqlDataSource _dataSource;
public AdminAnnouncementInboxWriter(IPostgresDataSourceResolver dataSourceResolver)
{
ArgumentNullException.ThrowIfNull(dataSourceResolver);
_connectionString = dataSourceResolver.GetConnectionString(AdminPersistenceDefaults.ConnectionStringName)
?? throw new InvalidOperationException(
string.Equals(AdminPersistenceDefaults.ConnectionStringName, SharedRuntimePersistenceDefaults.ConnectionStringName, StringComparison.Ordinal)
? SharedRuntimePersistenceDefaults.MissingConnectionStringMessage
: $"Admin inbox persistence requires ConnectionStrings:{AdminPersistenceDefaults.ConnectionStringName}.");
_dataSource = dataSourceResolver.GetRequiredDataSource(AdminPersistenceDefaults.ConnectionStringName);
}
public string Name => "admin_announcements";
public string? ConnectionString => _connectionString;
public async ValueTask StoreAsync(AdminAnnouncementProjection announcement, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(announcement);
var sql = $"""
INSERT INTO {AdminPersistenceDefaults.SchemaName}.{AdminPersistenceDefaults.AnnouncementsTableName}
(announcement_id, title, body, published_utc, published_by_actor_id, source_module_key, source_reference)
VALUES
(@announcementId, @title, @body, @publishedUtc, @publishedByActorId, @sourceModuleKey, @sourceReference)
ON CONFLICT (announcement_id) DO NOTHING;
""";
await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
await using var command = new NpgsqlCommand(sql, connection);
command.Parameters.AddWithValue("announcementId", announcement.AnnouncementId);
command.Parameters.AddWithValue("title", announcement.Title);
command.Parameters.AddWithValue("body", announcement.Body);
command.Parameters.AddWithValue("publishedUtc", announcement.PublishedUtc);
command.Parameters.AddWithValue("publishedByActorId", announcement.PublishedByActorId);
command.Parameters.AddWithValue("sourceModuleKey", announcement.SourceModuleKey);
command.Parameters.AddWithValue("sourceReference", (object?)announcement.SourceReference ?? DBNull.Value);
await command.ExecuteNonQueryAsync(cancellationToken);
}
public async Task<IReadOnlyCollection<string>> GetPendingMigrationsAsync(CancellationToken cancellationToken)
{
const string tableSql = """
SELECT EXISTS (
SELECT 1
FROM information_schema.tables
WHERE table_schema = @schemaName
AND table_name = @tableName);
""";
const string columnSql = """
SELECT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = @schemaName
AND table_name = @tableName
AND column_name = @columnName);
""";
await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
var pending = new List<string>();
await using (var tableCommand = new NpgsqlCommand(tableSql, connection))
{
tableCommand.Parameters.AddWithValue("schemaName", AdminPersistenceDefaults.SchemaName);
tableCommand.Parameters.AddWithValue("tableName", AdminPersistenceDefaults.AnnouncementsTableName);
var exists = (bool)(await tableCommand.ExecuteScalarAsync(cancellationToken) ?? false);
if (!exists)
{
pending.Add($"{AdminPersistenceDefaults.SchemaName}.{AdminPersistenceDefaults.AnnouncementsTableName}");
return pending;
}
}
foreach (var columnName in new[] { "source_module_key", "source_reference" })
{
await using var columnCommand = new NpgsqlCommand(columnSql, connection);
columnCommand.Parameters.AddWithValue("schemaName", AdminPersistenceDefaults.SchemaName);
columnCommand.Parameters.AddWithValue("tableName", AdminPersistenceDefaults.AnnouncementsTableName);
columnCommand.Parameters.AddWithValue("columnName", columnName);
var exists = (bool)(await columnCommand.ExecuteScalarAsync(cancellationToken) ?? false);
if (!exists)
{
pending.Add($"{AdminPersistenceDefaults.SchemaName}.{AdminPersistenceDefaults.AnnouncementsTableName}.{columnName}");
}
}
return pending;
}
public async Task ApplyAsync(CancellationToken cancellationToken)
{
var sql = $"""
CREATE SCHEMA IF NOT EXISTS {AdminPersistenceDefaults.SchemaName};
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = '{AdminPersistenceDefaults.SchemaName}'
AND table_name = '{AdminPersistenceDefaults.LegacyAnnouncementsTableName}'
) AND NOT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = '{AdminPersistenceDefaults.SchemaName}'
AND table_name = '{AdminPersistenceDefaults.AnnouncementsTableName}'
) THEN
ALTER TABLE {AdminPersistenceDefaults.SchemaName}.{AdminPersistenceDefaults.LegacyAnnouncementsTableName}
RENAME TO {AdminPersistenceDefaults.AnnouncementsTableName};
END IF;
END $$;
CREATE TABLE IF NOT EXISTS {AdminPersistenceDefaults.SchemaName}.{AdminPersistenceDefaults.AnnouncementsTableName}
(
announcement_id UUID PRIMARY KEY,
title TEXT NOT NULL,
body TEXT NOT NULL,
published_utc TIMESTAMPTZ NOT NULL,
published_by_actor_id TEXT NOT NULL,
source_module_key TEXT NOT NULL DEFAULT 'sample-feature',
source_reference TEXT NULL
);
ALTER TABLE {AdminPersistenceDefaults.SchemaName}.{AdminPersistenceDefaults.AnnouncementsTableName}
ADD COLUMN IF NOT EXISTS source_module_key TEXT NOT NULL DEFAULT 'sample-feature';
ALTER TABLE {AdminPersistenceDefaults.SchemaName}.{AdminPersistenceDefaults.AnnouncementsTableName}
ADD COLUMN IF NOT EXISTS source_reference TEXT NULL;
""";
await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
await using var command = new NpgsqlCommand(sql, connection);
await command.ExecuteNonQueryAsync(cancellationToken);
}
}