|  | 
|  | 1 | +using Microsoft.AspNetCore.Hosting.Server; | 
|  | 2 | +using Microsoft.AspNetCore.Hosting.Server.Features; | 
|  | 3 | +using Npgsql; | 
|  | 4 | + | 
|  | 5 | +namespace TodosApi; | 
|  | 6 | + | 
|  | 7 | +internal class DatabaseInitializer : IHostedService | 
|  | 8 | +{ | 
|  | 9 | +    private readonly NpgsqlDataSource _db; | 
|  | 10 | +    private readonly ILogger<DatabaseInitializer> _logger; | 
|  | 11 | +    private readonly bool _initDatabase; | 
|  | 12 | + | 
|  | 13 | +    public DatabaseInitializer(NpgsqlDataSource db, IServer server, ILogger<DatabaseInitializer> logger) | 
|  | 14 | +    { | 
|  | 15 | +        _db = db; | 
|  | 16 | +        _logger = logger; | 
|  | 17 | +        _initDatabase = Environment.GetEnvironmentVariable("SUPPRESS_DB_INIT") != "true" | 
|  | 18 | +            // Only run if this is an actual IServer implementation with addresses to listen on. | 
|  | 19 | +            // Will not be the case for TestServer, NoopServer injected by the OpenAPI doc generator tool, etc. | 
|  | 20 | +            && server.Features.Get<IServerAddressesFeature>() is { Addresses.Count: >0 }; | 
|  | 21 | +    } | 
|  | 22 | + | 
|  | 23 | +    public Task StartAsync(CancellationToken cancellationToken) | 
|  | 24 | +    { | 
|  | 25 | +        if (_initDatabase) | 
|  | 26 | +        { | 
|  | 27 | +            return Initialize(cancellationToken); | 
|  | 28 | +        } | 
|  | 29 | + | 
|  | 30 | +        _logger.LogInformation("Database initialization disabled for connection string '{connectionString}'", _db.ConnectionString); | 
|  | 31 | +        return Task.CompletedTask; | 
|  | 32 | +    } | 
|  | 33 | + | 
|  | 34 | +    public Task StopAsync(CancellationToken cancellationToken) | 
|  | 35 | +    { | 
|  | 36 | +        return Task.CompletedTask; | 
|  | 37 | +    } | 
|  | 38 | + | 
|  | 39 | +    private async Task Initialize(CancellationToken cancellationToken = default) | 
|  | 40 | +    { | 
|  | 41 | +        // NOTE: Npgsql removes the password from the connection string | 
|  | 42 | +        _logger.LogInformation("Ensuring database exists and is up to date at connection string '{connectionString}'", _db.ConnectionString); | 
|  | 43 | + | 
|  | 44 | +        var sql = $""" | 
|  | 45 | +                CREATE TABLE IF NOT EXISTS public.todos | 
|  | 46 | +                ( | 
|  | 47 | +                    {nameof(Todo.Id)} SERIAL PRIMARY KEY, | 
|  | 48 | +                    {nameof(Todo.Title)} text NOT NULL, | 
|  | 49 | +                    {nameof(Todo.DueBy)} date NULL, | 
|  | 50 | +                    {nameof(Todo.IsComplete)} boolean NOT NULL DEFAULT false | 
|  | 51 | +                ); | 
|  | 52 | +                DELETE FROM public.todos; | 
|  | 53 | +                INSERT INTO | 
|  | 54 | +                    public.todos ({nameof(Todo.Title)}, {nameof(Todo.DueBy)}, {nameof(Todo.IsComplete)}) | 
|  | 55 | +                VALUES | 
|  | 56 | +                    ('Wash the dishes.', CURRENT_DATE, true), | 
|  | 57 | +                    ('Dry the dishes.', CURRENT_DATE, true), | 
|  | 58 | +                    ('Turn the dishes over.', CURRENT_DATE, false), | 
|  | 59 | +                    ('Walk the kangaroo.', CURRENT_DATE + INTERVAL '1 day', false), | 
|  | 60 | +                    ('Call Grandma.', CURRENT_DATE + INTERVAL '1 day', false); | 
|  | 61 | +                """; | 
|  | 62 | +        await _db.ExecuteAsync(sql, cancellationToken); | 
|  | 63 | +    } | 
|  | 64 | +} | 
0 commit comments