PI06 i PI06-1. Docker definitions for MSSQL and Postgres. Data seeder/generator for countries and people. Entity Framework example with variants for Postgres and MSSQL

This commit is contained in:
Boris Milašinović
2026-04-19 16:49:07 +02:00
parent 44a663e170
commit 6f56d107a2
89 changed files with 7305 additions and 0 deletions

25
DataAccess/DataAccess.sln Normal file
View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.5.11709.299
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EF_Demo", "EF_Demo\EF_Demo.csproj", "{26C4670D-F5D0-420C-8730-F76E53BAA639}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{26C4670D-F5D0-420C-8730-F76E53BAA639}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{26C4670D-F5D0-420C-8730-F76E53BAA639}.Debug|Any CPU.Build.0 = Debug|Any CPU
{26C4670D-F5D0-420C-8730-F76E53BAA639}.Release|Any CPU.ActiveCfg = Release|Any CPU
{26C4670D-F5D0-420C-8730-F76E53BAA639}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2C6663EA-3869-40E6-8F2A-0F122E9CDE02}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,32 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace EF_Demo;
internal class DISetup
{
public static ServiceProvider BuildDI()
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddUserSecrets<Program>()
.Build();
IServiceCollection services = new ServiceCollection();
var provider = services.AddLogging(configure => {
configure.AddConfiguration(configuration.GetSection("Logging"));
configure.AddConsole();
})
.AddDbContext<Data.MSSQL.EventsContext>(options => {
options.UseSqlServer(configuration.GetConnectionString("EventsMssql"));
}, contextLifetime: ServiceLifetime.Transient)
.AddDbContext<Data.Postgres.EventsContext>(options => {
options.UseNpgsql(configuration.GetConnectionString("EventsPostgres"));
}, contextLifetime: ServiceLifetime.Transient)
.BuildServiceProvider();
return provider;
}
}

View File

@@ -0,0 +1,143 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
using EF_Demo.Models;
using Microsoft.EntityFrameworkCore;
namespace EF_Demo.Data.MSSQL;
public partial class EventsContext : DbContext
{
public EventsContext(DbContextOptions<EventsContext> options)
: base(options)
{
}
public virtual DbSet<Country> Countries { get; set; }
public virtual DbSet<Event> Events { get; set; }
public virtual DbSet<Person> People { get; set; }
public virtual DbSet<Registration> Registrations { get; set; }
public virtual DbSet<Sport> Sports { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Country>(entity =>
{
entity.HasKey(e => e.Code);
entity.ToTable("Country");
entity.HasIndex(e => e.Name, "UQ_Country_Name").IsUnique();
entity.Property(e => e.Code)
.HasMaxLength(3)
.IsUnicode(false);
entity.Property(e => e.Alpha3)
.HasMaxLength(3)
.IsUnicode(false)
.IsFixedLength();
entity.Property(e => e.Name)
.HasMaxLength(100)
.IsUnicode(false);
});
modelBuilder.Entity<Event>(entity =>
{
entity.ToTable("Event");
entity.Property(e => e.Name)
.HasMaxLength(150)
.IsUnicode(false);
});
modelBuilder.Entity<Person>(entity =>
{
entity.ToTable("Person");
entity.HasIndex(e => new { e.DocumentNumber, e.CountryCode }, "UQ_Person_DocumentNumber_CountryCode").IsUnique();
entity.Property(e => e.AddressCountry)
.HasMaxLength(100)
.IsUnicode(false);
entity.Property(e => e.AddressLine)
.HasMaxLength(200)
.IsUnicode(false);
entity.Property(e => e.City)
.HasMaxLength(100)
.IsUnicode(false);
entity.Property(e => e.ContactPhone)
.HasMaxLength(50)
.IsUnicode(false);
entity.Property(e => e.CountryCode)
.HasMaxLength(3)
.IsUnicode(false);
entity.Property(e => e.DocumentNumber)
.HasMaxLength(50)
.IsUnicode(false);
entity.Property(e => e.Email)
.HasMaxLength(255)
.IsUnicode(false);
entity.Property(e => e.FirstName)
.HasMaxLength(100)
.IsUnicode(false);
entity.Property(e => e.FirstNameTranscription)
.HasMaxLength(100)
.IsUnicode(false);
entity.Property(e => e.LastName)
.HasMaxLength(100)
.IsUnicode(false);
entity.Property(e => e.LastNameTranscription)
.HasMaxLength(100)
.IsUnicode(false);
entity.Property(e => e.PostalCode)
.HasMaxLength(20)
.IsUnicode(false);
entity.HasOne(d => d.CountryCodeNavigation).WithMany(p => p.People)
.HasForeignKey(d => d.CountryCode)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Person_Country");
});
modelBuilder.Entity<Registration>(entity =>
{
entity.ToTable("Registration");
entity.HasIndex(e => new { e.PersonId, e.SportId, e.EventId }, "UQ_Registration_PersonId_SportId_EventId").IsUnique();
entity.Property(e => e.RegisteredAt).HasDefaultValueSql("(sysdatetime())", "DF_Registration_RegisteredAt");
entity.HasOne(d => d.Event).WithMany(p => p.Registrations)
.HasForeignKey(d => d.EventId)
.HasConstraintName("FK_Registration_Event");
entity.HasOne(d => d.Person).WithMany(p => p.Registrations)
.HasForeignKey(d => d.PersonId)
.HasConstraintName("FK_Registration_Person");
entity.HasOne(d => d.Sport).WithMany(p => p.Registrations)
.HasForeignKey(d => d.SportId)
.HasConstraintName("FK_Registration_Sport");
});
modelBuilder.Entity<Sport>(entity =>
{
entity.ToTable("Sport");
entity.HasIndex(e => e.Name, "UQ_Sport_Name").IsUnique();
entity.Property(e => e.Name)
.HasMaxLength(100)
.IsUnicode(false);
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

View File

@@ -0,0 +1,166 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
using EF_Demo.Models;
using Microsoft.EntityFrameworkCore;
namespace EF_Demo.Data.Postgres;
public partial class EventsContext : DbContext
{
public EventsContext(DbContextOptions<EventsContext> options)
: base(options)
{
}
public virtual DbSet<Country> Countries { get; set; }
public virtual DbSet<Event> Events { get; set; }
public virtual DbSet<Person> People { get; set; }
public virtual DbSet<Registration> Registrations { get; set; }
public virtual DbSet<Sport> Sports { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Country>(entity =>
{
entity.HasKey(e => e.Code).HasName("country_pkey");
entity.ToTable("country");
entity.HasIndex(e => e.Name, "country_name_key").IsUnique();
entity.Property(e => e.Code)
.HasMaxLength(3)
.HasColumnName("code");
entity.Property(e => e.Alpha3)
.HasMaxLength(3)
.IsFixedLength()
.HasColumnName("alpha3");
entity.Property(e => e.Name)
.HasMaxLength(100)
.HasColumnName("name");
entity.Property(e => e.Translations)
.HasColumnType("jsonb")
.HasColumnName("translations");
});
modelBuilder.Entity<Event>(entity =>
{
entity.HasKey(e => e.Id).HasName("event_pkey");
entity.ToTable("event");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.EventDate).HasColumnName("event_date");
entity.Property(e => e.Name)
.HasMaxLength(150)
.HasColumnName("name");
});
modelBuilder.Entity<Person>(entity =>
{
entity.HasKey(e => e.Id).HasName("person_pkey");
entity.ToTable("person");
entity.HasIndex(e => new { e.DocumentNumber, e.CountryCode }, "person_document_number_country_code_key").IsUnique();
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.AddressCountry)
.HasMaxLength(100)
.HasColumnName("address_country");
entity.Property(e => e.AddressLine)
.HasMaxLength(200)
.HasColumnName("address_line");
entity.Property(e => e.BirthDate).HasColumnName("birth_date");
entity.Property(e => e.City)
.HasMaxLength(100)
.HasColumnName("city");
entity.Property(e => e.ContactPhone)
.HasMaxLength(50)
.HasColumnName("contact_phone");
entity.Property(e => e.CountryCode)
.HasMaxLength(3)
.HasColumnName("country_code");
entity.Property(e => e.DocumentNumber)
.HasMaxLength(50)
.HasColumnName("document_number");
entity.Property(e => e.Email)
.HasMaxLength(255)
.HasColumnName("email");
entity.Property(e => e.FirstName)
.HasMaxLength(100)
.HasColumnName("first_name");
entity.Property(e => e.FirstNameTranscription)
.HasMaxLength(100)
.HasColumnName("first_name_transcription");
entity.Property(e => e.LastName)
.HasMaxLength(100)
.HasColumnName("last_name");
entity.Property(e => e.LastNameTranscription)
.HasMaxLength(100)
.HasColumnName("last_name_transcription");
entity.Property(e => e.PostalCode)
.HasMaxLength(20)
.HasColumnName("postal_code");
entity.HasOne(d => d.CountryCodeNavigation).WithMany(p => p.People)
.HasForeignKey(d => d.CountryCode)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("person_country_code_fkey");
});
modelBuilder.Entity<Registration>(entity =>
{
entity.HasKey(e => e.Id).HasName("registration_pkey");
entity.ToTable("registration");
entity.HasIndex(e => new { e.PersonId, e.SportId, e.EventId }, "registration_person_id_sport_id_event_id_key").IsUnique();
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.EventId).HasColumnName("event_id");
entity.Property(e => e.PersonId).HasColumnName("person_id");
entity.Property(e => e.RegisteredAt)
.HasDefaultValueSql("CURRENT_TIMESTAMP")
.HasColumnType("timestamp without time zone")
.HasColumnName("registered_at");
entity.Property(e => e.SportId).HasColumnName("sport_id");
entity.HasOne(d => d.Event).WithMany(p => p.Registrations)
.HasForeignKey(d => d.EventId)
.HasConstraintName("registration_event_id_fkey");
entity.HasOne(d => d.Person).WithMany(p => p.Registrations)
.HasForeignKey(d => d.PersonId)
.HasConstraintName("registration_person_id_fkey");
entity.HasOne(d => d.Sport).WithMany(p => p.Registrations)
.HasForeignKey(d => d.SportId)
.HasConstraintName("registration_sport_id_fkey");
});
modelBuilder.Entity<Sport>(entity =>
{
entity.HasKey(e => e.Id).HasName("sport_pkey");
entity.ToTable("sport");
entity.HasIndex(e => e.Name, "sport_name_key").IsUnique();
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Name)
.HasMaxLength(100)
.HasColumnName("name");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

110
DataAccess/EF_Demo/Demo.cs Normal file
View File

@@ -0,0 +1,110 @@
#if POSTGRES
using EF_Demo.Data.Postgres;
#else
using EF_Demo.Data.MSSQL;
#endif
using EF_Demo.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace EF_Demo;
internal class Demo
{
internal static int? AddEvent(IServiceProvider serviceProvider, string eventName, DateOnly eventDate)
{
ILogger logger = serviceProvider.GetRequiredService<ILogger<Demo>>();
try
{
using var ctx = serviceProvider.GetRequiredService<EventsContext>();
Event @event = new()
{
Name = eventName,
EventDate = eventDate
};
ctx.Add(@event); //or ctx.Events.Add(@event) or ctx.Set<Event>().Add(@event);
ctx.SaveChanges();
logger.LogInformation($"Event {eventName} successfully added with id {@event.Id}");
return @event.Id;
}
catch (Exception exc)
{
logger.LogError($"Error adding event #{eventName}: {exc.CompleteExceptionMessage()}");
return null;
}
}
internal static void DeleteEvent(IServiceProvider serviceProvider, int eventId)
{
ILogger logger = serviceProvider.GetRequiredService<ILogger<Demo>>();
try
{
using var ctx = serviceProvider.GetRequiredService<EventsContext>();
Event? @event = ctx.Events.Find(eventId);
if (@event != null)
{
ctx.Remove(@event); //or ctx.Entry(product).State = EntityState.Deleted;
ctx.SaveChanges();
logger.LogInformation($"Event {@event.Name} deleted");
}
else
{
logger.LogWarning($"Event #{eventId} does not exists");
}
}
catch (Exception exc)
{
logger.LogError($"Error deleting event #{eventId}: {exc.CompleteExceptionMessage()}");
}
}
internal static void PostponeEvent(IServiceProvider serviceProvider, int eventId, int days)
{
ILogger logger = serviceProvider.GetRequiredService<ILogger<Demo>>();
try
{
using var ctx = serviceProvider.GetRequiredService<EventsContext>();
Event? @event = ctx.Events.Find(eventId);
if (@event != null)
{
@event.EventDate = @event.EventDate.AddDays(days);
ctx.SaveChanges();
logger.LogInformation($"Event {@event.Name} postoponed to {@event.EventDate:dd.MM.yyyy.}");
}
else
{
logger?.LogWarning($"Event #{eventId} does not exists");
}
}
catch (Exception exc)
{
logger?.LogError($"Error trying to change evenbt #{eventId}: {exc.CompleteExceptionMessage()}");
}
}
/// <summary>
/// Print all people born in the provided year, ordered by country, and then by transliterated last name, and first name
/// query is projected to an anonymous class
/// </summary>
internal static void PrintPeople(IServiceProvider serviceProvider, int year)
{
using var ctx = serviceProvider.GetRequiredService<EventsContext>();
var query = ctx.People
.Where(p => p.BirthDate.Year == year)
.OrderBy(p => p.CountryCodeNavigation.Name)
.ThenBy(p => p.LastNameTranscription)
.ThenBy(p => p.FirstNameTranscription)
.Select(p => new
{
p.FirstName, p.LastName,
p.FirstNameTranscription, p.LastNameTranscription,
p.BirthDate,
Country = p.CountryCodeNavigation.Name
});
foreach (var p in query)
{
Console.WriteLine($"{p.FirstName} {p.LastName} ({p.FirstNameTranscription} {p.LastNameTranscription}), {p.Country} {p.BirthDate:dd.MM.yyyy.}");
}
}
}

View File

@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<UserSecretsId>PI</UserSecretsId>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>$(DefineConstants);POSTGRES</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.6" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,18 @@
using System.Text;
namespace EF_Demo;
public static class ExceptionExtensions
{
public static string CompleteExceptionMessage(this Exception exc)
{
StringBuilder sb = new StringBuilder(exc.Message);
while (exc.InnerException != null)
{
exc = exc.InnerException;
sb.AppendLine();
sb.Append(exc.Message);
}
return sb.ToString();
}
}

View File

@@ -0,0 +1,19 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
namespace EF_Demo.Models;
public partial class Country
{
public string Code { get; set; } = null!;
public string Alpha3 { get; set; } = null!;
public string Name { get; set; } = null!;
public string? Translations { get; set; }
public virtual ICollection<Person> People { get; set; } = new List<Person>();
}

View File

@@ -0,0 +1,17 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
namespace EF_Demo.Models;
public partial class Event
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public DateOnly EventDate { get; set; }
public virtual ICollection<Registration> Registrations { get; set; } = new List<Registration>();
}

View File

@@ -0,0 +1,41 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
namespace EF_Demo.Models;
public partial class Person
{
public int Id { get; set; }
public string FirstName { get; set; } = null!;
public string LastName { get; set; } = null!;
public string FirstNameTranscription { get; set; } = null!;
public string LastNameTranscription { get; set; } = null!;
public string AddressLine { get; set; } = null!;
public string PostalCode { get; set; } = null!;
public string City { get; set; } = null!;
public string AddressCountry { get; set; } = null!;
public string Email { get; set; } = null!;
public string ContactPhone { get; set; } = null!;
public DateOnly BirthDate { get; set; }
public string DocumentNumber { get; set; } = null!;
public string CountryCode { get; set; } = null!;
public virtual Country CountryCodeNavigation { get; set; } = null!;
public virtual ICollection<Registration> Registrations { get; set; } = new List<Registration>();
}

View File

@@ -0,0 +1,25 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
namespace EF_Demo.Models;
public partial class Registration
{
public int Id { get; set; }
public int PersonId { get; set; }
public int SportId { get; set; }
public int EventId { get; set; }
public DateTime RegisteredAt { get; set; }
public virtual Event Event { get; set; } = null!;
public virtual Person Person { get; set; } = null!;
public virtual Sport Sport { get; set; } = null!;
}

View File

@@ -0,0 +1,15 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
namespace EF_Demo.Models;
public partial class Sport
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public virtual ICollection<Registration> Registrations { get; set; } = new List<Registration>();
}

View File

@@ -0,0 +1,21 @@
using EF_Demo;
using Microsoft.Extensions.DependencyInjection;
Console.OutputEncoding = System.Text.Encoding.UTF8;
using ServiceProvider serviceProvider = DISetup.BuildDI();
Demo.PrintPeople(serviceProvider, 1976);
Util.EnterToContinue();
int? eventId = Demo.AddEvent(serviceProvider, "Spring festival", new DateOnly(2026, 4, 30));
Util.EnterToContinue();
if (eventId.HasValue)
{
Demo.PostponeEvent(serviceProvider, eventId.Value, days: 5);
Util.EnterToContinue();
Demo.DeleteEvent(serviceProvider, eventId.Value);
Util.EnterToContinue(clearConsole: false);
}

View File

@@ -0,0 +1,21 @@
namespace EF_Demo;
public static class Util
{
public static void EnterToContinue(bool clearConsole = true)
{
Console.WriteLine("ENTER to continue");
Console.ReadLine();
if (clearConsole)
{
Console.Clear();
}
}
public static int ReadNumber()
{
int number;
while (!int.TryParse(Console.ReadLine(), out number)) ;
return number;
}
}

View File

@@ -0,0 +1,16 @@
{
"ConnectionStrings": {
"EventsMssql": "Data Source=.,3030;Initial Catalog=Events;use-secrets.json-to-set-username-and-password;TrustServerCertificate=True",
"EventsPostgres": "Host=localhost;Database=events;use-secrets.json-to-set-username-and-password;Persist Security Info=True"
},
"Logging": {
"LogLevel": {
"Default": "Warning",
"System": "Error",
"Microsoft": "Error",
"Microsoft.EntityFrameworkCore.Database.Command": "Information",
"EF_Demo.Demo": "Information"
}
}
}

View File

@@ -0,0 +1,70 @@
{
"CodeGenerationMode": 6,
"ContextClassName": "EventsContext",
"ContextNamespace": null,
"FilterSchemas": false,
"IncludeConnectionString": false,
"IrregularWords": null,
"MinimumProductVersion": "2.6.1465",
"ModelNamespace": null,
"OutputContextPath": "Data\/MSSQL",
"OutputPath": "Models",
"PluralRules": null,
"PreserveCasingWithRegex": true,
"ProjectRootNamespace": "EF_Demo",
"Schemas": null,
"SelectedHandlebarsLanguage": 2,
"SelectedToBeGenerated": 0,
"SingularRules": null,
"T4TemplatePath": null,
"Tables": [
{
"Name": "[dbo].[Country]",
"ObjectType": 0
},
{
"Name": "[dbo].[Event]",
"ObjectType": 0
},
{
"Name": "[dbo].[Person]",
"ObjectType": 0
},
{
"Name": "[dbo].[Registration]",
"ObjectType": 0
},
{
"Name": "[dbo].[Sport]",
"ObjectType": 0
}
],
"UiHint": null,
"UncountableWords": null,
"UseAsyncStoredProcedureCalls": true,
"UseBoolPropertiesWithoutDefaultSql": false,
"UseDatabaseNames": false,
"UseDatabaseNamesForRoutines": true,
"UseDateOnlyTimeOnly": true,
"UseDbContextSplitting": false,
"UseDecimalDataAnnotationForSprocResult": true,
"UseFluentApiOnly": true,
"UseHandleBars": false,
"UseHierarchyId": false,
"UseInflector": true,
"UseInternalAccessModifiersForSprocsAndFunctions": false,
"UseLegacyPluralizer": false,
"UseManyToManyEntity": false,
"UseNoDefaultConstructor": true,
"UseNoNavigations": false,
"UseNoObjectFilter": false,
"UseNodaTime": false,
"UseNullableReferences": true,
"UsePrefixNavigationNaming": false,
"UseSchemaFolders": false,
"UseSchemaNamespaces": false,
"UseSpatial": false,
"UseT4": false,
"UseT4Split": false,
"UseTypedTvpParameters": true
}

View File

@@ -0,0 +1,70 @@
{
"CodeGenerationMode": 6,
"ContextClassName": "EventsContext",
"ContextNamespace": null,
"FilterSchemas": false,
"IncludeConnectionString": false,
"IrregularWords": null,
"MinimumProductVersion": "2.6.1465",
"ModelNamespace": null,
"OutputContextPath": "Data\/Postgres",
"OutputPath": "Models",
"PluralRules": null,
"PreserveCasingWithRegex": true,
"ProjectRootNamespace": "EF_Demo",
"Schemas": null,
"SelectedHandlebarsLanguage": 2,
"SelectedToBeGenerated": 0,
"SingularRules": null,
"T4TemplatePath": null,
"Tables": [
{
"Name": "public.country",
"ObjectType": 0
},
{
"Name": "public.event",
"ObjectType": 0
},
{
"Name": "public.person",
"ObjectType": 0
},
{
"Name": "public.registration",
"ObjectType": 0
},
{
"Name": "public.sport",
"ObjectType": 0
}
],
"UiHint": null,
"UncountableWords": null,
"UseAsyncStoredProcedureCalls": true,
"UseBoolPropertiesWithoutDefaultSql": false,
"UseDatabaseNames": false,
"UseDatabaseNamesForRoutines": true,
"UseDateOnlyTimeOnly": true,
"UseDbContextSplitting": false,
"UseDecimalDataAnnotationForSprocResult": true,
"UseFluentApiOnly": true,
"UseHandleBars": false,
"UseHierarchyId": false,
"UseInflector": true,
"UseInternalAccessModifiersForSprocsAndFunctions": false,
"UseLegacyPluralizer": false,
"UseManyToManyEntity": false,
"UseNoDefaultConstructor": true,
"UseNoNavigations": false,
"UseNoObjectFilter": false,
"UseNodaTime": false,
"UseNullableReferences": true,
"UsePrefixNavigationNaming": false,
"UseSchemaFolders": false,
"UseSchemaNamespaces": false,
"UseSpatial": false,
"UseT4": false,
"UseT4Split": false,
"UseTypedTvpParameters": true
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,53 @@
using System.Diagnostics;
namespace Barricades;
class Course(string name)
{
public string Name { get; private set; } = name;
//Consider why is dictionary private variable, and why not a property!
//indexer serves as a barricade
private Dictionary<string, int> grades = new Dictionary<string, int>();
/// <summary>
/// Assign the new grade to the student.
/// Old value (if had existed) is replaced.
/// </summary>
/// <param name="name">Student's name</param>
/// <returns>The student's grade or -1 if the student does not have a grade</returns>
/// <exception cref="System.ArgumentOutOfRangeException">Thrown if value is not valid grade [1-5]</exception>
public int this[string name]
{
get
{
//what if there is no student's name in the dictionary
//throw exception, return special value? See error handling techniques
bool exists = grades.TryGetValue(name, out int grade);
return exists ? grade : -1;
}
set
{
if (value < 1 || value > 5)
{
throw new ArgumentOutOfRangeException($"Invalid grade {value}. It shoud be from 1 to 5");
}
grades[name] = value;
}
}
public double AverageGrade()
{
int sum = 0;
foreach (var pair in grades)
{
int grade = pair.Value;
Debug.Assert(grade >= 1 && grade <= 5,
$"Invalid grade in dictionary {pair.Key} = {pair.Value}. This should never happens!");
sum += grade;
}
return grades.Count > 0 ? (double)sum / grades.Count : 0;
}
}

View File

@@ -0,0 +1,14 @@
using Barricades;
using System.Text;
Console.OutputEncoding = Encoding.UTF8;
Course c = new Course("PI");
c["Pero"] = 2;
c["Ana"] = 5;
c["Ivan"] = 5;
c["Marko"] = 1;
c["Luka"] = 3;
//c["Marija"] = -7; //should throw an exception
Console.WriteLine("Average grade: " + c.AverageGrade());
Console.WriteLine("Šime has grade: " + c["Šime"]); //prints -1

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;DEMO</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,20 @@
#define DEMO2
using System.Diagnostics;
Console.WriteLine("Start");
#if DEMO
Console.WriteLine("Print something...");
#endif
CheckSomething();
Console.WriteLine("End");
[Conditional("DEMO")]
static void CheckSomething()
{
Console.WriteLine("Print from CheckSomething method");
}

View File

@@ -0,0 +1,49 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30413.136
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Barricades", "Barricades\Barricades.csproj", "{EC679AD5-7BC4-49E3-B44C-DB4750FBC179}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Conditional", "Conditional\Conditional.csproj", "{997943C5-5BA1-4BC4-A2C9-AAE0CA3BB934}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Using", "Using\Using.csproj", "{227D0323-D09C-44D5-B1CD-C2DD27CFC4DB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Logging", "Logging\Logging.csproj", "{F7534F26-8855-4CA0-BEBC-C60ED2D1E7B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Secrets", "Secrets\Secrets.csproj", "{78BFB6E2-A394-4CA1-A947-0CC2D8CDE421}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EC679AD5-7BC4-49E3-B44C-DB4750FBC179}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC679AD5-7BC4-49E3-B44C-DB4750FBC179}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC679AD5-7BC4-49E3-B44C-DB4750FBC179}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC679AD5-7BC4-49E3-B44C-DB4750FBC179}.Release|Any CPU.Build.0 = Release|Any CPU
{997943C5-5BA1-4BC4-A2C9-AAE0CA3BB934}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{997943C5-5BA1-4BC4-A2C9-AAE0CA3BB934}.Debug|Any CPU.Build.0 = Debug|Any CPU
{997943C5-5BA1-4BC4-A2C9-AAE0CA3BB934}.Release|Any CPU.ActiveCfg = Release|Any CPU
{997943C5-5BA1-4BC4-A2C9-AAE0CA3BB934}.Release|Any CPU.Build.0 = Release|Any CPU
{227D0323-D09C-44D5-B1CD-C2DD27CFC4DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{227D0323-D09C-44D5-B1CD-C2DD27CFC4DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{227D0323-D09C-44D5-B1CD-C2DD27CFC4DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{227D0323-D09C-44D5-B1CD-C2DD27CFC4DB}.Release|Any CPU.Build.0 = Release|Any CPU
{F7534F26-8855-4CA0-BEBC-C60ED2D1E7B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7534F26-8855-4CA0-BEBC-C60ED2D1E7B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7534F26-8855-4CA0-BEBC-C60ED2D1E7B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7534F26-8855-4CA0-BEBC-C60ED2D1E7B1}.Release|Any CPU.Build.0 = Release|Any CPU
{78BFB6E2-A394-4CA1-A947-0CC2D8CDE421}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{78BFB6E2-A394-4CA1-A947-0CC2D8CDE421}.Debug|Any CPU.Build.0 = Debug|Any CPU
{78BFB6E2-A394-4CA1-A947-0CC2D8CDE421}.Release|Any CPU.ActiveCfg = Release|Any CPU
{78BFB6E2-A394-4CA1-A947-0CC2D8CDE421}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0C808F1C-DC45-4A56-8D42-13C593C59A78}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,60 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
namespace Logging;
public class DataLoader : IDataLoader
{
private readonly ILogger<DataLoader> logger;
public DataLoader(ILogger<DataLoader> logger)
{
this.logger = logger;
}
public List<(string, DateOnly)> LoadData(string filename)
{
List<(string, DateOnly)> list = new();
if (!File.Exists(filename))
{
string message = $"File {filename} does not exist";
logger.LogError(message);
throw new FileNotFoundException(message);
}
else
{
//use File.ReadAllLines for small files
using (var reader = new StreamReader(File.OpenRead(filename)))
{
string? line;
while((line = reader.ReadLine()) != null) {
string pattern = "[0-3]?[0-9].[0-3]?[0-9].[0-9]{4}.";
var match = Regex.Match(line, pattern);
if (match.Success)
{
string datetext = line.Substring(match.Index, match.Length);
if (DateOnly.TryParseExact(datetext, "d.M.yyyy.", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly date))
{
var item = (line.Remove(match.Index, match.Length).Trim(), date);
logger.LogTrace(item.ToString());
list.Add(item);
}
else
{
logger.LogWarning("Invalid date in line: " + line);
}
}
else
{
logger.LogWarning("No date found in line: " + line);
}
}
}
}
return list;
}
}

View File

@@ -0,0 +1,9 @@
using System;
using System.Collections.Generic;
namespace Logging;
public interface IDataLoader
{
List<(string, DateOnly)> LoadData(string filename);
}

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.6" />
<PackageReference Include="NLog" Version="6.1.2" />
<PackageReference Include="NLog.Extensions.Logging" Version="6.1.2" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="data.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="nlog.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,45 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NLog.Extensions.Logging;
using System;
using System.Linq;
namespace Logging;
class Program
{
static void Main(string[] args)
{
using (var serviceProvider = BuildDI())
{
var dataLoader = serviceProvider.GetRequiredService<IDataLoader>();
var list = dataLoader.LoadData("data.txt");
var sortQuery = list.OrderByDescending(t => t.Item2)
.ThenBy(t => t.Item1);
Console.WriteLine("Valid data: ");
foreach (var item in sortQuery)
{
Console.WriteLine($"{item.Item2:yyyy-MM-dd} {item.Item1}");
}
}
}
private static ServiceProvider BuildDI()
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
IServiceCollection services = new ServiceCollection();
var provider = services.AddLogging(configure => {
configure.AddConfiguration(configuration.GetSection("Logging"));
configure.AddConsole();
configure.AddNLog(new NLogProviderOptions { RemoveLoggerFactoryFilter = false });
})
.AddTransient<IDataLoader, DataLoader>()
.BuildServiceProvider();
return provider;
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Trace",
"System": "Warning",
"Microsoft": "Trace"
}
}
}

View File

@@ -0,0 +1,5 @@
Ana 22.5.1988.
01.03.1995. Ivan John
Ema
Mario 15.13.1980.
Klara 11.07.1998.

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Warn"
internalLogFile="logs/internal-nlog.txt">
<!-- the targets to write to -->
<targets>
<!-- write logs to file -->
<target xsi:type="File" name="allfile" fileName="logs/nlog-all-${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${logger}|${uppercase:${level}}|${message} ${exception}" />
<!-- another file log, only own logs. Uses some ASP.NET core renderers -->
<target xsi:type="File" name="ownFile" fileName="logs/nlog-own-${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${logger}|${uppercase:${level}}| ${message} ${exception}" />
<!-- write to the void aka just remove -->
<target xsi:type="Null" name="blackhole" />
</targets>
<!-- rules to map from logger name to target -->
<rules>
<!--All logs, including from Microsoft-->
<logger name="*" minlevel="Trace" writeTo="allfile" />
<!--Skip Microsoft logs and so log only own logs-->
<logger name="Microsoft.*" minlevel="Trace" writeTo="blackhole" final="true" />
<logger name="*" minlevel="Information" writeTo="ownFile" />
</rules>
</nlog>

View File

@@ -0,0 +1,8 @@
namespace Secrets;
public class Demo
{
public int Key0 { get; set; }
public string? Key1 { get; set; }
public string? Key2 { get; set; }
}

View File

@@ -0,0 +1,23 @@
using Microsoft.Extensions.Configuration;
using Secrets;
var enumerator = Environment.GetEnvironmentVariables().GetEnumerator();
while (enumerator.MoveNext())
{
Console.WriteLine($"{enumerator.Key} = {enumerator.Value}");
}
Console.WriteLine("----------------------------");
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json") //order is important!
.AddJsonFile("missing_one.json", optional: true) //
.AddEnvironmentVariables() // include Package Microsoft.Extensions.Configuration.EnvironmentVariables
.AddUserSecrets("PI-Secrets") //package Microsoft.Extensions.Configuration.UserSecrets
.Build();
Console.WriteLine($"Custom env variable using configuration = " + configuration["CustomEnvValue"]);
Console.WriteLine($"Name = " + configuration["Name"]);
Console.WriteLine($"Key0 = " + configuration["Demo:Key0"]); //or Demo__Key0
Demo? demo = configuration.GetSection("Demo").Get<Demo>(); // package Microsoft.Extensions.Configuration.Binder
Console.WriteLine($"Key1 = " + demo?.Key1);
Console.WriteLine($"Key2 = " + demo?.Key2);

View File

@@ -0,0 +1,10 @@
{
"profiles": {
"Secrets": {
"commandName": "Project",
"environmentVariables": {
"CustomEnvValue": "Demo"
}
}
}
}

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>PI</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.6" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,7 @@
{
"Name": "Bob",
"Demo": {
"Key0": 123,
"Key1": "something that should not be publicly available"
}
}

View File

@@ -0,0 +1,16 @@
namespace Using;
class C : IDisposable
{
public string Id { get; set; }
public void Dispose()
{
Console.WriteLine("** {0} : Dispose **", Id);
}
public C(string id)
{
Id = id;
Console.WriteLine("----> {0} : Ctor", Id);
}
}

View File

@@ -0,0 +1,20 @@
using Using;
try
{
C a1 = new C("A1");
using (C b2 = new C("B2"))
using (C d4 = new C("D4"))
{
C c3 = new C("C3");
throw new Exception("It is time for an exception");
}
a1.Dispose();
}
catch (Exception exc)
{
Console.WriteLine("Exc: " + exc.Message);
//throw exc;
//throw;
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Inheritance\Inheritance.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,49 @@
using Inheritance;
namespace CovarianceContravariance;
class Program
{
static void Main(string[] args)
{
List<Car> cars = new List<Car>{
new Car("Coupe", 200, 3),
new Car("Wagon", 100, 5),
new Car("Limousine", 150, 4),
new ElectricCar("Electric Hatchback", 148, 5, 40)
};
PrintVehicles(cars);
Comparison<Vehicle> comparisonFunction = (a, b) => -a.HorsePower.CompareTo(b.HorsePower);
IComparer<Vehicle> comparer = Comparer<Vehicle>.Create(comparisonFunction);
PrintBetterCar(cars[2], cars[3], comparer);
PrintBetterCar(cars.Skip(2).First(), cars.Last(), comparisonFunction);
PrintBetterCar(cars[2], cars[3], (a, b) => b.Doors - a.Doors);
}
static void PrintVehicles(IEnumerable<Vehicle> vehicles)
{
foreach (var vehicle in vehicles)
{
Console.WriteLine("\t " + vehicle.Model);
}
}
static void PrintBetterCar(Car a, Car b, IComparer<Car> comparer)
{
int result = comparer.Compare(a, b);
string betterModel = result <= 0 ? a.Model : b.Model;
string worseModel = result <= 0 ? b.Model : a.Model;
Console.WriteLine($"\t{betterModel} is better or equal than {worseModel}");
}
static void PrintBetterCar(Car a, Car b, Comparison<Car> comparer)
{
int result = comparer(a, b);
string betterModel = result <= 0 ? a.Model : b.Model;
string worseModel = result <= 0 ? b.Model : a.Model;
Console.WriteLine($"\t{betterModel} is better or equal than {worseModel}");
}
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,26 @@
using System;
namespace Delegates;
public class MathTool
{
public static int Sum(int x, int y)
{
return x + y;
}
public static int Diff(int x, int y)
{
return x - y;
}
public static void PrintSquare(int x)
{
Console.WriteLine("x^2 = " + x * x);
}
public static void PrintSquareRoot(int x)
{
Console.WriteLine("sqrt(x) = " + Math.Sqrt(x));
}
}

View File

@@ -0,0 +1,31 @@
namespace Delegates;
class Program
{
public delegate int MathFunction(int a, int b);
public delegate void PrintFunction(int n);
static void Main(string[] args)
{
int x = 16, y = 2;
MathFunction mf = MathTool.Sum;
Console.WriteLine("mf({0}, {1}) = {2}", x, y, mf(x, y));
mf = MathTool.Diff;
Console.WriteLine("mf({0}, {1}) = {2}", x, y, mf(x, y));
PrintFunction pf = MathTool.PrintSquare;
pf += MathTool.PrintSquareRoot;
pf(x);
#pragma warning disable CS8601 // Possible null reference assignment.
pf -= MathTool.PrintSquare;
#pragma warning restore CS8601 // Possible null reference assignment.
Console.WriteLine();
#pragma warning disable CS8602 // Dereference of a possibly null reference.
pf(y); //instead of pragma pf?.Invoke(y); can be used
#pragma warning restore CS8602 // Dereference of a possibly null reference.
Func<int, int, int> func = MathTool.Sum;
Console.WriteLine("func({0}, {1}) = {2}", x, y, func(x, y));
Action<int> action = MathTool.PrintSquare;
action(x);
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace Extensions;
[Flags]
public enum DayPeriod
{
Morning = 1, Evening = 2, Afternoon = 4, Night = 8
}
public class Activity
{
public required string Person { get; set; }
public int Hours { get; set; }
public DayPeriod Period { get; set; }
}

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace Extensions;
public static class Extensions
{
public static V GetOrCreate<K, V>(this Dictionary<K, V> dict, K key)
where V : new()
where K: notnull
{
if (!dict.TryGetValue(key, out V? value))
{
value = new V();
dict[key] = value;
}
return value;
}
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,61 @@
namespace Extensions;
public enum Days
{
Monday, Tuesday, Wednesday,
Thursday, Friday, Saturday, Sunday
}
class Program
{
static void Main(string[] args)
{
var dict = new Dictionary<Days, List<Activity>>();
Activity a = new Activity
{
Person = "John",
Hours = 3,
Period = DayPeriod.Morning
};
dict.GetOrCreate(Days.Monday).Add(a);
a = new Activity
{
Person = "Mary",
Hours = 12,
Period = DayPeriod.Evening | DayPeriod.Night
};
dict.GetOrCreate(Days.Monday).Add(a);
a = new Activity
{
Person = "Peter",
Hours = 8,
Period = DayPeriod.Night
};
dict.GetOrCreate(Days.Tuesday).Add(a);
a = new Activity
{
Person = "Lisa",
Hours = 5,
Period = DayPeriod.Night | DayPeriod.Morning
};
dict.GetOrCreate(Days.Monday).Add(a);
int sum = SumNightActivity(dict);
Console.WriteLine(sum);
}
private static int SumNightActivity(Dictionary<Days, List<Activity>> dict)
{
int sum = 0;
foreach(var list in dict.Values)
{
sum += list.Where(a => (a.Period & DayPeriod.Night) == DayPeriod.Night)
.Sum(a => a.Hours);
}
return sum;
}
}

View File

@@ -0,0 +1,31 @@
using System;
namespace Inheritance;
public class Car : Vehicle
{
public int Doors { get; set; }
public string Color { get; set; } = "White";
public Car(string model, double horsePower, int doors)
: base(model, horsePower)
{
Console.WriteLine($"Creating car {model}");
this.Doors = doors;
}
public void BuckleSeatBelt()
{
Console.WriteLine("Car {0} - seat belt buckled", Model);
}
public override void Start()
{
Console.WriteLine("Start Car " + Model);
}
public new void Stop()
{
Console.WriteLine("Stop Car " + Model);
}
}

View File

@@ -0,0 +1,13 @@
namespace Inheritance;
public class ElectricCar : Car
{
public int BatteryCapacity { get; set; }
public int? Range { get; set; }
public ElectricCar(string model, double horsePower, int doors, int batteryCapacity) :
base(model, horsePower, doors)
{
this.BatteryCapacity = batteryCapacity;
}
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,18 @@
using System;
namespace Inheritance;
public class Motorbike : Vehicle
{
public bool HasSidecar { get; set; }
public Motorbike(string model, double horsePower, bool sidecar)
: base(model, horsePower)
{
Console.WriteLine($"Creating car {model}");
this.HasSidecar = sidecar;
}
public override void Start()
{
Console.WriteLine("Start Motorbike " + Model);
}
}

View File

@@ -0,0 +1,39 @@
using Inheritance;
Vehicle v = new Vehicle("BMW Isseta", 9.5);
v.Start();
Car car = new ElectricCar("Nissan Leaf", 148, 5, 40)
{
Range = 270
};
Console.WriteLine("car model = " + car.Model);
Console.WriteLine("car horse power = " + car.HorsePower);
Console.WriteLine("car number of doors = " + car.Doors);
if (car is ElectricCar electric)
{
Console.WriteLine("car battery = " + electric.BatteryCapacity);
if (electric.Range.HasValue)
{
Console.WriteLine("car range = " + electric.Range);
}
}
car.BuckleSeatBelt();
car.Start();
car.Stop();
((Vehicle)car).Start();
((Vehicle)car).Stop();
// objekt tipa Motocikl
Motorbike moto = new Motorbike("Ural", 41, true);
Console.WriteLine("motorbike model {0} ", moto.Model);
Console.WriteLine("motorbike horse power = " + moto.HorsePower);
Console.WriteLine($"motorbike has sidecar? = {moto.HasSidecar}");
moto.Start();
moto.Stop();
((Vehicle)moto).Start();
((Vehicle)moto).Stop();

View File

@@ -0,0 +1,26 @@
using System;
namespace Inheritance;
public class Vehicle
{
public string Model { get; private set; }
public double HorsePower { get; set; }
public Vehicle(string model, double horsePower)
{
Console.WriteLine("Creating vehicle " + model);
this.Model = model;
this.HorsePower = horsePower;
}
public void Stop()
{
Console.WriteLine("Stop vehicle " + Model);
}
public virtual void Start()
{
Console.WriteLine("Start vehicle " + Model);
}
}

View File

@@ -0,0 +1,48 @@
using PropertiesIndexersRefOut;
Triple t1 = new Triple
{
First = 456,
Second = 789
};
Triple t2 = new Triple
{
First = 1000,
Second = 2000
};
Console.WriteLine("t1 + t2 = " + (t1 + t2));
CreateRandomTriple(t1, ref t2, out Triple t3);
Console.WriteLine("t1 = " + t1);
Console.WriteLine("t2 = " + t2);
Console.WriteLine("t3 = " + t3);
Console.WriteLine(t1["A", 1]);
Console.WriteLine(t1["B", 2]);
Console.WriteLine(t1["B", 3]);
PrintTriples(t1, t2, t3);
void PrintTriples(params Triple[] triples)
{
foreach (var t in triples)
{
Console.WriteLine(t);
}
}
void CreateRandomTriple(Triple t1, ref Triple t2, out Triple t3)
{
Random r = new Random();
t3 = new Triple
{
First = r.Next(t1.First, t2.Second),
Second = r.Next(maxValue: t2.Second, minValue: t1.First)
};
t1 = t3;
t2 = t3;
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,73 @@
namespace PropertiesIndexersRefOut;
public class Triple
{
private int first;
public int First
{
get
{
return first;
}
set
{
first = value;
}
}
public int Second { get; set; }
public int Third => First + Second;
public override string ToString()
{
return $"({First}, {Second}, {Third})";
}
#region Indexer
public int this[string s, int position]
{
get
{
int num;
switch (s)
{
case "A":
num = this.First;
break;
case "B":
num = this.Second;
break;
case "C":
num = this.Third;
break;
default:
throw new ArgumentException("Invalid position in triple (should be A, B, or C)");
}
int digit = 0;
while(num > 0 && position > 0)
{
digit = num % 10;
num /= 10;
position--;
}
return digit;
}
}
#endregion
#region Operator overloading
public static Triple operator +(Triple x, Triple y)
{
return new Triple
{
First = x.First + y.First,
Second = x.Second + y.Second
};
}
#endregion
}

View File

@@ -0,0 +1,55 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33424.131
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Delegates", "Delegates\Delegates.csproj", "{EABA8FC4-42FB-4DAD-96E9-47A5B1057CFB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extensions", "Extensions\Extensions.csproj", "{47B09D5D-50E4-4827-B954-A40B96052B83}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CovarianceContravariance", "CovarianceContravariance\CovarianceContravariance.csproj", "{007533F4-FE60-4E0C-AF11-004BF8A2C8F8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Inheritance", "Inheritance\Inheritance.csproj", "{BEB733F2-BBB4-4170-B530-EFF7C5797559}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PropertiesIndexersRefOut", "PropertiesIndexersRefOut\PropertiesIndexersRefOut.csproj", "{C6F8925F-2947-4A78-8E1A-543D13B7ED6B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValueTuplesDeconstruction", "ValueTuplesDeconstruction\ValueTuplesDeconstruction.csproj", "{2518859E-D8D5-418A-A92B-411277F86479}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EABA8FC4-42FB-4DAD-96E9-47A5B1057CFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EABA8FC4-42FB-4DAD-96E9-47A5B1057CFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EABA8FC4-42FB-4DAD-96E9-47A5B1057CFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EABA8FC4-42FB-4DAD-96E9-47A5B1057CFB}.Release|Any CPU.Build.0 = Release|Any CPU
{47B09D5D-50E4-4827-B954-A40B96052B83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47B09D5D-50E4-4827-B954-A40B96052B83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47B09D5D-50E4-4827-B954-A40B96052B83}.Release|Any CPU.ActiveCfg = Release|Any CPU
{47B09D5D-50E4-4827-B954-A40B96052B83}.Release|Any CPU.Build.0 = Release|Any CPU
{007533F4-FE60-4E0C-AF11-004BF8A2C8F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{007533F4-FE60-4E0C-AF11-004BF8A2C8F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{007533F4-FE60-4E0C-AF11-004BF8A2C8F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{007533F4-FE60-4E0C-AF11-004BF8A2C8F8}.Release|Any CPU.Build.0 = Release|Any CPU
{BEB733F2-BBB4-4170-B530-EFF7C5797559}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BEB733F2-BBB4-4170-B530-EFF7C5797559}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BEB733F2-BBB4-4170-B530-EFF7C5797559}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BEB733F2-BBB4-4170-B530-EFF7C5797559}.Release|Any CPU.Build.0 = Release|Any CPU
{C6F8925F-2947-4A78-8E1A-543D13B7ED6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C6F8925F-2947-4A78-8E1A-543D13B7ED6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C6F8925F-2947-4A78-8E1A-543D13B7ED6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C6F8925F-2947-4A78-8E1A-543D13B7ED6B}.Release|Any CPU.Build.0 = Release|Any CPU
{2518859E-D8D5-418A-A92B-411277F86479}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2518859E-D8D5-418A-A92B-411277F86479}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2518859E-D8D5-418A-A92B-411277F86479}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2518859E-D8D5-418A-A92B-411277F86479}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {88C158C1-838B-42EB-A159-F5F83BF2DDEA}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,22 @@
//ValueTuples https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/deconstruct
(int a, int b, int _) t1 = CreateRandomTriple();
Console.WriteLine(t1);
Console.WriteLine(t1.b);
var (_, b, _) = CreateRandomTriple();
Console.WriteLine(b);
var t2 = CreateRandomTriple();
Console.WriteLine(t2);
Console.WriteLine(t2.Item2);
(int, int, int) CreateRandomTriple()
{
Random r = new Random();
var triple = (r.Next(100), r.Next(100), r.Next(100));
Console.WriteLine($"Created {triple}");
return triple;
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,21 @@
# CountrySeedGenerator
This folder contains the Python helper for generating `04-countries.sql` seed scripts for PostgreSQL and Microsoft SQL Server
(defined as Docker containers using docker-compose.yml)
The commands below assume you are running them from inside the `CountrySeedGenerator` folder.
Install dependencies:
```powershell
python -m pip install -r requirements.txt
```
Run the generator:
```powershell
python create_countries_sql.py postgres
python create_countries_sql.py mssql
```
The script shows the default output path based on the selected target and asks you to confirm it before writing the file.

View File

@@ -0,0 +1,143 @@
from __future__ import annotations
from pathlib import Path
import json
import sys
import pycountry
from babel import Locale
SCRIPT_DIR = Path(__file__).resolve().parent
DOCKER_DEFINITIONS_DIR = SCRIPT_DIR.parent
MANUAL_COUNTRIES = [
{
"alpha_2": "XK",
"alpha_3": "XKX",
"name": "Kosovo",
"translations": {
"hr": "Kosovo",
"mk": "Косово",
},
}
]
def prompt_target_database(argv: list[str]) -> str:
if len(argv) > 1:
return parse_target_database(argv[1])
raw_value = input("Generate schema for [postgres/mssql] (default: mssql): ").strip()
return "mssql" if not raw_value else parse_target_database(raw_value)
def parse_target_database(value: str) -> str:
normalized = value.strip().lower()
if normalized in {"postgres", "postgresql", "pg"}:
return "postgres"
if normalized in {"mssql", "sqlserver", "sql-server", "sql"}:
return "mssql"
raise ValueError(f"Unsupported target database '{value}'. Use 'postgres' or 'mssql'.")
def get_output_path(target_database: str) -> Path:
target_folder = "postgres-eventsdb" if target_database == "postgres" else "mssql-eventsdb"
return DOCKER_DEFINITIONS_DIR / target_folder / "init" / "04-countries.sql"
def get_insert_target(target_database: str) -> tuple[str, str]:
if target_database == "postgres":
return "country", "(code, alpha3, name, translations)"
return "dbo.Country", "(Code, Alpha3, Name, Translations)"
def get_script_prefix(target_database: str) -> str:
if target_database == "mssql":
return "USE [$(MSSQL_DB)];\nGO\n\n"
return ""
def confirm_output_path(output_path: Path) -> Path:
print(f"Output path: {output_path}")
raw_value = input("Press Enter to confirm or type a different path: ").strip()
if not raw_value:
return output_path
custom_path = Path(raw_value)
return custom_path if custom_path.is_absolute() else (SCRIPT_DIR / custom_path).resolve()
def generate_minimal_sql(target_database: str) -> None:
default_output_path = get_output_path(target_database)
output_path = confirm_output_path(default_output_path)
table_name, column_list = get_insert_target(target_database)
script_prefix = get_script_prefix(target_database)
sql_header = f"INSERT INTO {table_name} {column_list} VALUES\n"
print(f"Generating SQL for {target_database} countries script...")
try:
lang_hr = Locale("hr")
lang_mk = Locale("mk")
except Exception as exc:
print(f"Babel error: {exc}. Check whether you installed the 'Babel' package.")
return
rows: list[str] = []
countries = sorted(pycountry.countries, key=lambda country: country.alpha_2)
for country in countries:
code = country.alpha_2
alpha3 = country.alpha_3
english_name = getattr(country, "common_name", country.name)
croatian_name = lang_hr.territories.get(code, english_name)
macedonian_name = lang_mk.territories.get(code, english_name)
s_en = english_name.replace("'", "''")
s_hr = croatian_name.replace("'", "''")
s_mk = macedonian_name.replace("'", "''")
trans_dict = {
"hr": s_hr,
"mk": s_mk,
}
trans_json = json.dumps(trans_dict, ensure_ascii=False).replace("'", "''")
rows.append(f"('{code}', '{alpha3}', '{s_en}', '{trans_json}')")
for country in MANUAL_COUNTRIES:
code = country["alpha_2"]
alpha3 = country["alpha_3"]
english_name = country["name"]
croatian_name = country["translations"]["hr"]
macedonian_name = country["translations"]["mk"]
s_en = english_name.replace("'", "''")
s_hr = croatian_name.replace("'", "''")
s_mk = macedonian_name.replace("'", "''")
trans_dict = {
"hr": s_hr,
"mk": s_mk,
}
trans_json = json.dumps(trans_dict, ensure_ascii=False).replace("'", "''")
rows.append(f"('{code}', '{alpha3}', '{s_en}', '{trans_json}')")
rows.sort()
final_sql = script_prefix + sql_header + ",\n".join(rows) + ";"
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(final_sql, encoding="utf-8")
print(f"Success! Generated {len(rows)} countries in '{output_path}'.")
if __name__ == "__main__":
generate_minimal_sql(prompt_target_database(sys.argv))

View File

@@ -0,0 +1,2 @@
pycountry
Babel

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Bogus" Version="35.6.5" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.37027.9 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PeopleDataGenerator", "PeopleDataGenerator.csproj", "{08786A49-9D47-4F1D-AE9D-47AB02CE8954}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{08786A49-9D47-4F1D-AE9D-47AB02CE8954}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08786A49-9D47-4F1D-AE9D-47AB02CE8954}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08786A49-9D47-4F1D-AE9D-47AB02CE8954}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08786A49-9D47-4F1D-AE9D-47AB02CE8954}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EC9B8434-351C-4169-A527-51408CB7ABED}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,230 @@
using System.Text;
using Bogus;
using Bogus.Extensions;
const int peoplePerCountry = 50;
var generatorDirectory = GetGeneratorDirectory();
Console.OutputEncoding = Encoding.UTF8;
var targetDatabase = PromptForTargetDatabase(args);
var defaultOutputPath = GetDefaultPeopleOutputPath(generatorDirectory, targetDatabase);
var outputPath = PromptForPath("Enter people SQL output", defaultOutputPath);
var fullOutputPath = Path.GetFullPath(outputPath, Environment.CurrentDirectory);
var insertStatements = BuildPersonInsertStatements(targetDatabase);
var fileContent = BuildOutputContent(targetDatabase, insertStatements);
Directory.CreateDirectory(Path.GetDirectoryName(fullOutputPath)!);
await File.WriteAllTextAsync(fullOutputPath, fileContent, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
Console.WriteLine($"Target database: {targetDatabase.DisplayName()}");
Console.WriteLine($"SQL script saved to: {fullOutputPath}");
static DatabaseTarget PromptForTargetDatabase(string[] args)
{
if (args.Length > 0)
{
return ParseTargetDatabase(args[0]);
}
Console.Write("Generate schema for [postgres/mssql] (default: mssql): ");
var input = Console.ReadLine();
return string.IsNullOrWhiteSpace(input) ? DatabaseTarget.MsSql : ParseTargetDatabase(input.Trim());
}
static DatabaseTarget ParseTargetDatabase(string value)
{
return value.Trim().ToLowerInvariant() switch
{
"postgres" or "postgresql" or "pg" => DatabaseTarget.Postgres,
"mssql" or "sqlserver" or "sql-server" or "sql" => DatabaseTarget.MsSql,
_ => throw new ArgumentException($"Unsupported target database '{value}'. Use 'postgres' or 'mssql'.")
};
}
static string GetDefaultPeopleOutputPath(string generatorDirectory, DatabaseTarget targetDatabase) =>
targetDatabase == DatabaseTarget.Postgres
? Path.GetFullPath(@"..\postgres-eventsdb\init\06-people.sql", generatorDirectory)
: Path.GetFullPath(@"..\mssql-eventsdb\init\06-people.sql", generatorDirectory);
static string GetGeneratorDirectory()
{
var currentDirectory = new DirectoryInfo(AppContext.BaseDirectory);
while (currentDirectory is not null)
{
if (File.Exists(Path.Combine(currentDirectory.FullName, "PeopleDataGenerator.csproj")))
{
return currentDirectory.FullName;
}
currentDirectory = currentDirectory.Parent;
}
throw new InvalidOperationException("Could not locate the PeopleDataGenerator project directory.");
}
static string PromptForPath(string prompt, string defaultPath)
{
Console.Write($"{prompt} [{defaultPath}]: ");
var input = Console.ReadLine();
return string.IsNullOrWhiteSpace(input) ? defaultPath : input.Trim();
}
static string BuildOutputContent(DatabaseTarget targetDatabase, IReadOnlyList<string> insertStatements)
{
var content = string.Join("\n", insertStatements) + "\n";
if (targetDatabase == DatabaseTarget.MsSql)
{
return $"USE [$(MSSQL_DB)];\nGO\n\n{content}";
}
return content;
}
static List<string> BuildPersonInsertStatements(DatabaseTarget targetDatabase)
{
var statements = new List<string>();
var nonLatinLocales = new HashSet<string>
{
"ar",
"el",
"fa",
"ge",
"ne",
"ru",
"uk"
};
var localeToCountryCode = new (string Locale, string CountryCode)[]
{
("af_ZA", "ZA"),
("ar", "SA"),
("az", "AZ"),
("cz", "CZ"),
("de", "DE"),
("de_AT", "AT"),
("de_CH", "CH"),
("el", "GR"),
("en_AU", "AU"),
("en_CA", "CA"),
("en_GB", "GB"),
("en_IE", "IE"),
("en_IND", "IN"),
("en_NG", "NG"),
("en_US", "US"),
("es", "ES"),
("es_MX", "MX"),
("fa", "IR"),
("fi", "FI"),
("fr", "FR"),
("ge", "GE"),
("hr", "HR"),
("id_ID", "ID"),
("it", "IT"),
("lv", "LV"),
("nb_NO", "NO"),
("ne", "NP"),
("nl", "NL"),
("nl_BE", "BE"),
("pl", "PL"),
("pt_BR", "BR"),
("pt_PT", "PT"),
("ro", "RO"),
("ru", "RU"),
("sk", "SK"),
("sv", "SE"),
("tr", "TR"),
("uk", "UA"),
("vi", "VN")
};
foreach (var (locale, countryCode) in localeToCountryCode)
{
var faker = new Faker(locale);
var transliterationLanguage = locale.Split('_')[0];
var useTranscriptionForAddress = nonLatinLocales.Contains(transliterationLanguage);
for (var i = 0; i < peoplePerCountry; i++)
{
var firstName = faker.Name.FirstName();
var lastName = faker.Name.LastName();
var firstNameTranscription = firstName.Transliterate(transliterationLanguage);
var lastNameTranscription = lastName.Transliterate(transliterationLanguage);
var city = NormalizeForStorage(faker.Address.City(), transliterationLanguage, useTranscriptionForAddress);
var addressLine = NormalizeForStorage(faker.Address.StreetAddress(), transliterationLanguage, useTranscriptionForAddress);
var postalCode = NormalizeForStorage(faker.Address.ZipCode(), transliterationLanguage, useTranscriptionForAddress);
var addressCountry = NormalizeForStorage(faker.Address.Country(), transliterationLanguage, useTranscriptionForAddress);
var email = faker.Internet.Email(firstNameTranscription, lastNameTranscription);
var contactPhone = NormalizePhoneNumber(faker.Phone.PhoneNumber());
var birthDate = faker.Date.BetweenDateOnly(new DateOnly(1950, 1, 1), new DateOnly(2010, 12, 31));
var documentNumber = $"{countryCode}-{faker.Random.Replace("########")}";
statements.Add(CreateInsertStatement(
targetDatabase,
firstName,
lastName,
firstNameTranscription,
lastNameTranscription,
addressLine,
postalCode,
city,
addressCountry,
email,
contactPhone,
birthDate,
documentNumber,
countryCode));
}
}
return statements;
}
static string CreateInsertStatement(
DatabaseTarget targetDatabase,
string firstName,
string lastName,
string firstNameTranscription,
string lastNameTranscription,
string addressLine,
string postalCode,
string city,
string addressCountry,
string email,
string contactPhone,
DateOnly birthDate,
string documentNumber,
string countryCode)
{
var tableName = targetDatabase == DatabaseTarget.Postgres ? "person" : "dbo.Person";
var columnList = targetDatabase == DatabaseTarget.Postgres
? "(first_name, last_name, first_name_transcription, last_name_transcription, address_line, postal_code, city, address_country, email, contact_phone, birth_date, document_number, country_code)"
: "(FirstName, LastName, FirstNameTranscription, LastNameTranscription, AddressLine, PostalCode, City, AddressCountry, Email, ContactPhone, BirthDate, DocumentNumber, CountryCode)";
return $"INSERT INTO {tableName} {columnList} VALUES ('{EscapeSql(firstName)}', '{EscapeSql(lastName)}', '{EscapeSql(firstNameTranscription)}', '{EscapeSql(lastNameTranscription)}', '{EscapeSql(addressLine)}', '{EscapeSql(postalCode)}', '{EscapeSql(city)}', '{EscapeSql(addressCountry)}', '{EscapeSql(email)}', '{EscapeSql(contactPhone)}', '{birthDate:yyyy-MM-dd}', '{EscapeSql(documentNumber)}', '{countryCode}');";
}
static string EscapeSql(string value) => value.Replace("'", "''");
static string NormalizeForStorage(string value, string transliterationLanguage, bool useTranscriptionForAddress) =>
useTranscriptionForAddress ? value.Transliterate(transliterationLanguage) : value;
static string NormalizePhoneNumber(string value) =>
value.Replace("\r", string.Empty).Replace("\n", " ").Trim();
enum DatabaseTarget
{
Postgres,
MsSql
}
static class DatabaseTargetExtensions
{
public static string DisplayName(this DatabaseTarget targetDatabase) =>
targetDatabase == DatabaseTarget.Postgres ? "PostgreSQL" : "Microsoft SQL Server";
}

View File

@@ -0,0 +1,10 @@
{
"profiles": {
"PeopleDataGenerator": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,43 @@
# PeopleDataGenerator
`PeopleDataGenerator` contains the .NET helper for generating person seed SQL for both PostgreSQL and Microsoft SQL Server.
It is used as supporting material for the teaching examples in this repository and helps produce realistic multilingual seed data for the sports-events domain.
The commands below assume you are running them from inside the `PeopleDataGenerator` folder.
## What It Does
- generates fake participant data with locale-specific names and transliterations
- asks whether the target should be PostgreSQL or Microsoft SQL Server
- writes SQL insert statements to the matching `init` folder
By default, the people generator writes to:
```text
docker-definitions\postgres-eventsdb\init\06-people.sql
docker-definitions\mssql-eventsdb\init\06-people.sql
```
## Prerequisites
- .NET SDK 10.0
## Running
```powershell
dotnet run --project PeopleDataGenerator.csproj
```
You can also pass the target as the first argument:
```powershell
dotnet run --project PeopleDataGenerator.csproj -- postgres
dotnet run --project PeopleDataGenerator.csproj -- mssql
```
The tool asks for the target database when no argument is provided, then asks for the output path. Press `Enter` to accept the default path derived from the `PeopleDataGenerator` folder.
## Files
- [Program.cs](Program.cs): .NET generator for `06-people.sql` targeting PostgreSQL or Microsoft SQL Server

View File

@@ -0,0 +1,93 @@
# Docker Definitions
This folder contains local Docker-based infrastructure definitions used by the teaching materials in this repository.
## Available Setups
- [postgres-eventsdb](docker-definitions/postgres-eventsdb): the main PostgreSQL database for application runs
- [mssql-eventsdb](docker-definitions/mssql-eventsdb): the same teaching database adapted for Microsoft SQL Server with Full-Text Search
## What The Containers Provide
- initialized PostgreSQL instances
- initialized Microsoft SQL Server instances
- schema creation through startup scripts in each `init` directory
- seed data used by the examples
## Important Files
For each PostgreSQL setup:
- `.env.example`: committed template for local container settings such as ports, usernames, and passwords
- `.env`: local copy created from `.env.example`
- `docker-compose.yml`: service definitions
- `init`: startup scripts copied into `/docker-entrypoint-initdb.d`
- `backup`: optional location for backup files
Before starting a setup for the first time, copy the example file in that folder:
```powershell
Copy-Item docker-definitions\postgres-eventsdb\.env.example docker-definitions\postgres-eventsdb\.env
Copy-Item docker-definitions\mssql-eventsdb\.env.example docker-definitions\mssql-eventsdb\.env
```
On Linux and macOS, make sure the shell scripts in each `init` folder are executable before starting the containers:
```bash
chmod +x docker-definitions/postgres-eventsdb/init/*.sh
```
## Running The Main Database
For normal day-to-day startup, use:
```powershell
docker compose -f docker-definitions\postgres-eventsdb\docker-compose.yml up -d
docker compose -f docker-definitions\mssql-eventsdb\docker-compose.yml up -d
```
For `mssql-eventsdb`, use `--build` when you changed the Docker image setup, for example:
- `Dockerfile`
- files in `scripts`
- initialization logic that depends on the image contents
```powershell
docker compose -f docker-definitions\mssql-eventsdb\docker-compose.yml up -d --build
```
To stop the containers without deleting named volumes:
```powershell
docker compose -f docker-definitions\postgres-eventsdb\docker-compose.yml down
docker compose -f docker-definitions\mssql-eventsdb\docker-compose.yml down
```
To stop the containers and also remove named volumes:
```powershell
docker compose -f docker-definitions\postgres-eventsdb\docker-compose.yml down -v
docker compose -f docker-definitions\mssql-eventsdb\docker-compose.yml down -v
```
`-v` removes the named Docker volumes created by the setup, which also deletes the persisted database files. Use it only when you want a clean re-initialization.
The named database volumes use the `pi_` prefix to reduce the chance of accidental reuse across unrelated Docker setups.
Default services:
- PostgreSQL on port `5432`
- local PostgreSQL backup folder mounted from `docker-definitions\postgres-eventsdb\backup` to `/backup`
- Microsoft SQL Server on port `1433`
- local SQL Server backup folder mounted from `docker-definitions\mssql-eventsdb\backup` to `/var/opt/mssql/backup`
## Configuration Notes
- review the `.env` files before sharing or reusing the setup outside local teaching and demo environments
- application projects connect with the `sport` user created by the initialization scripts
- if you change ports or passwords in `.env`, update the matching `dotnet user-secrets` connection strings as well
- the SQL Server setup uses `Croatian_100_CI_AS_SC_UTF8` collation and has the Full-Text Search package installed, but no runtime full-text checks, catalogs, or indexes are created by default
## Troubleshooting
- If the database is not initialized as expected, remove the Docker volume and recreate the container so the init scripts run again

View File

@@ -0,0 +1,16 @@
# SQL Server admin (sa)
ACCEPT_EULA=Y
MSSQL_PID=Developer
MSSQL_SA_PASSWORD=ChangeMe!123456
MSSQL_DB=Events
MSSQL_PORT=3030
MSSQL_COLLATION=Croatian_100_CI_AS_SC_UTF8
# App user
APP_DB_USER=sport
APP_DB_PASSWORD=ChangeMe!123456
# Local settings for Croatia
TZ=Europe/Zagreb
LANG=hr_HR.UTF-8
LC_ALL=hr_HR.UTF-8

View File

@@ -0,0 +1,33 @@
FROM mcr.microsoft.com/mssql/server:2022-latest
USER root
ENV DEBIAN_FRONTEND=noninteractive
ENV PATH="/opt/mssql-tools18/bin:${PATH}"
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
gnupg \
&& curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | tee /etc/apt/trusted.gpg.d/microsoft.asc >/dev/null \
&& curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/mssql-server-2022.list -o /etc/apt/sources.list.d/mssql-server-2022.list \
&& curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/prod.list -o /etc/apt/sources.list.d/mssql-release.list \
&& apt-get update \
&& ACCEPT_EULA=Y apt-get install -y --no-install-recommends \
locales \
mssql-server-fts \
mssql-tools18 \
tzdata \
&& locale-gen hr_HR.UTF-8 \
&& update-locale LANG=hr_HR.UTF-8 LC_ALL=hr_HR.UTF-8 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh
COPY scripts/configure-db.sh /usr/local/bin/configure-db.sh
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/configure-db.sh
USER mssql
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

View File

@@ -0,0 +1,21 @@
services:
mssql:
build:
context: .
dockerfile: Dockerfile
container_name: mssql_db
restart: unless-stopped
env_file:
- .env
ports:
- "${MSSQL_PORT}:1433"
volumes:
- mssql_data:/var/opt/mssql
- ./backup:/var/opt/mssql/backup
- ./init:/docker-entrypoint-initdb.d
volumes:
mssql_data:

View File

@@ -0,0 +1,55 @@
IF DB_ID(N'$(MSSQL_DB)') IS NULL
BEGIN
DECLARE @createDatabase nvarchar(max) =
N'CREATE DATABASE [' + REPLACE(N'$(MSSQL_DB)', N']', N']]') + N'] COLLATE $(MSSQL_COLLATION);';
EXEC (@createDatabase);
END;
GO
USE [$(MSSQL_DB)];
GO
IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = N'$(APP_DB_USER)')
BEGIN
DECLARE @createLogin nvarchar(max) =
N'CREATE LOGIN [' + REPLACE(N'$(APP_DB_USER)', N']', N']]') + N'] WITH PASSWORD = N''$(APP_DB_PASSWORD)'', CHECK_POLICY = OFF, CHECK_EXPIRATION = OFF;';
EXEC (@createLogin);
END;
GO
IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = N'$(APP_DB_USER)')
BEGIN
DECLARE @createUser nvarchar(max) =
N'CREATE USER [' + REPLACE(N'$(APP_DB_USER)', N']', N']]') + N'] FOR LOGIN [' + REPLACE(N'$(APP_DB_USER)', N']', N']]') + N'];';
EXEC (@createUser);
END;
GO
IF NOT EXISTS (
SELECT 1
FROM sys.database_role_members drm
INNER JOIN sys.database_principals role_principal ON role_principal.principal_id = drm.role_principal_id
INNER JOIN sys.database_principals member_principal ON member_principal.principal_id = drm.member_principal_id
WHERE role_principal.name = N'db_datareader'
AND member_principal.name = N'$(APP_DB_USER)'
)
BEGIN
ALTER ROLE db_datareader ADD MEMBER [$(APP_DB_USER)];
END;
GO
IF NOT EXISTS (
SELECT 1
FROM sys.database_role_members drm
INNER JOIN sys.database_principals role_principal ON role_principal.principal_id = drm.role_principal_id
INNER JOIN sys.database_principals member_principal ON member_principal.principal_id = drm.member_principal_id
WHERE role_principal.name = N'db_datawriter'
AND member_principal.name = N'$(APP_DB_USER)'
)
BEGIN
ALTER ROLE db_datawriter ADD MEMBER [$(APP_DB_USER)];
END;
GO

View File

@@ -0,0 +1,64 @@
USE [$(MSSQL_DB)];
GO
CREATE TABLE dbo.Country (
Code varchar(3) NOT NULL,
Alpha3 char(3) NOT NULL,
Name varchar(100) NOT NULL,
Translations nvarchar(max) NULL,
CONSTRAINT PK_Country PRIMARY KEY CLUSTERED (Code),
CONSTRAINT UQ_Country_Name UNIQUE (Name),
CONSTRAINT CK_Country_Translations_Json CHECK (Translations IS NULL OR ISJSON(Translations) = 1)
);
GO
CREATE TABLE dbo.Person (
Id int IDENTITY(1,1) NOT NULL,
FirstName varchar(100) NOT NULL,
LastName varchar(100) NOT NULL,
FirstNameTranscription varchar(100) NOT NULL,
LastNameTranscription varchar(100) NOT NULL,
AddressLine varchar(200) NOT NULL,
PostalCode varchar(20) NOT NULL,
City varchar(100) NOT NULL,
AddressCountry varchar(100) NOT NULL,
Email varchar(255) NOT NULL,
ContactPhone varchar(50) NOT NULL,
BirthDate date NOT NULL,
DocumentNumber varchar(50) NOT NULL,
CountryCode varchar(3) NOT NULL,
CONSTRAINT PK_Person PRIMARY KEY CLUSTERED (Id),
CONSTRAINT FK_Person_Country FOREIGN KEY (CountryCode) REFERENCES dbo.Country(Code),
CONSTRAINT UQ_Person_DocumentNumber_CountryCode UNIQUE (DocumentNumber, CountryCode)
);
GO
CREATE TABLE dbo.Sport (
Id int IDENTITY(1,1) NOT NULL,
Name varchar(100) NOT NULL,
CONSTRAINT PK_Sport PRIMARY KEY CLUSTERED (Id),
CONSTRAINT UQ_Sport_Name UNIQUE (Name)
);
GO
CREATE TABLE dbo.Event (
Id int IDENTITY(1,1) NOT NULL,
Name varchar(150) NOT NULL,
EventDate date NOT NULL,
CONSTRAINT PK_Event PRIMARY KEY CLUSTERED (Id)
);
GO
CREATE TABLE dbo.Registration (
Id int IDENTITY(1,1) NOT NULL,
PersonId int NOT NULL,
SportId int NOT NULL,
EventId int NOT NULL,
RegisteredAt datetime2 NOT NULL CONSTRAINT DF_Registration_RegisteredAt DEFAULT SYSDATETIME(),
CONSTRAINT PK_Registration PRIMARY KEY CLUSTERED (Id),
CONSTRAINT FK_Registration_Person FOREIGN KEY (PersonId) REFERENCES dbo.Person(Id) ON DELETE CASCADE,
CONSTRAINT FK_Registration_Sport FOREIGN KEY (SportId) REFERENCES dbo.Sport(Id) ON DELETE CASCADE,
CONSTRAINT FK_Registration_Event FOREIGN KEY (EventId) REFERENCES dbo.Event(Id) ON DELETE CASCADE,
CONSTRAINT UQ_Registration_PersonId_SportId_EventId UNIQUE (PersonId, SportId, EventId)
);
GO

View File

@@ -0,0 +1,8 @@
USE [$(MSSQL_DB)];
GO
INSERT INTO dbo.Event (Name, EventDate) VALUES
('Union Games', '2026-03-12'),
('Faculty Meetup', '2026-04-25'),
('City Cup', '2026-06-03'),
('Research Forum', '2026-09-18');

View File

@@ -0,0 +1,254 @@
USE [$(MSSQL_DB)];
GO
INSERT INTO dbo.Country (Code, Alpha3, Name, Translations) VALUES
('AD', 'AND', 'Andorra', '{"hr": "Andora", "mk": "Андора"}'),
('AE', 'ARE', 'United Arab Emirates', '{"hr": "Ujedinjeni Arapski Emirati", "mk": "Обединети Арапски Емирати"}'),
('AF', 'AFG', 'Afghanistan', '{"hr": "Afganistan", "mk": "Авганистан"}'),
('AG', 'ATG', 'Antigua and Barbuda', '{"hr": "Antigva i Barbuda", "mk": "Антига и Барбуда"}'),
('AI', 'AIA', 'Anguilla', '{"hr": "Angvila", "mk": "Ангвила"}'),
('AL', 'ALB', 'Albania', '{"hr": "Albanija", "mk": "Албанија"}'),
('AM', 'ARM', 'Armenia', '{"hr": "Armenija", "mk": "Ерменија"}'),
('AO', 'AGO', 'Angola', '{"hr": "Angola", "mk": "Ангола"}'),
('AQ', 'ATA', 'Antarctica', '{"hr": "Antarktika", "mk": "Антарктик"}'),
('AR', 'ARG', 'Argentina', '{"hr": "Argentina", "mk": "Аргентина"}'),
('AS', 'ASM', 'American Samoa', '{"hr": "Američka Samoa", "mk": "Американска Самоа"}'),
('AT', 'AUT', 'Austria', '{"hr": "Austrija", "mk": "Австрија"}'),
('AU', 'AUS', 'Australia', '{"hr": "Australija", "mk": "Австралија"}'),
('AW', 'ABW', 'Aruba', '{"hr": "Aruba", "mk": "Аруба"}'),
('AX', 'ALA', 'Åland Islands', '{"hr": "Ålandski otoci", "mk": "Оландски Острови"}'),
('AZ', 'AZE', 'Azerbaijan', '{"hr": "Azerbajdžan", "mk": "Азербејџан"}'),
('BA', 'BIH', 'Bosnia and Herzegovina', '{"hr": "Bosna i Hercegovina", "mk": "Босна и Херцеговина"}'),
('BB', 'BRB', 'Barbados', '{"hr": "Barbados", "mk": "Барбадос"}'),
('BD', 'BGD', 'Bangladesh', '{"hr": "Bangladeš", "mk": "Бангладеш"}'),
('BE', 'BEL', 'Belgium', '{"hr": "Belgija", "mk": "Белгија"}'),
('BF', 'BFA', 'Burkina Faso', '{"hr": "Burkina Faso", "mk": "Буркина Фасо"}'),
('BG', 'BGR', 'Bulgaria', '{"hr": "Bugarska", "mk": "Бугарија"}'),
('BH', 'BHR', 'Bahrain', '{"hr": "Bahrein", "mk": "Бахреин"}'),
('BI', 'BDI', 'Burundi', '{"hr": "Burundi", "mk": "Бурунди"}'),
('BJ', 'BEN', 'Benin', '{"hr": "Benin", "mk": "Бенин"}'),
('BL', 'BLM', 'Saint Barthélemy', '{"hr": "Saint Barthélemy", "mk": "Свети Вартоломеј"}'),
('BM', 'BMU', 'Bermuda', '{"hr": "Bermudi", "mk": "Бермуди"}'),
('BN', 'BRN', 'Brunei Darussalam', '{"hr": "Brunej", "mk": "Брунеј"}'),
('BO', 'BOL', 'Bolivia', '{"hr": "Bolivija", "mk": "Боливија"}'),
('BQ', 'BES', 'Bonaire, Sint Eustatius and Saba', '{"hr": "Karipski otoci Nizozemske", "mk": "Карипска Холандија"}'),
('BR', 'BRA', 'Brazil', '{"hr": "Brazil", "mk": "Бразил"}'),
('BS', 'BHS', 'Bahamas', '{"hr": "Bahami", "mk": "Бахами"}'),
('BT', 'BTN', 'Bhutan', '{"hr": "Butan", "mk": "Бутан"}'),
('BV', 'BVT', 'Bouvet Island', '{"hr": "Otok Bouvet", "mk": "Остров Буве"}'),
('BW', 'BWA', 'Botswana', '{"hr": "Bocvana", "mk": "Боцвана"}'),
('BY', 'BLR', 'Belarus', '{"hr": "Bjelorusija", "mk": "Белорусија"}'),
('BZ', 'BLZ', 'Belize', '{"hr": "Belize", "mk": "Белизе"}'),
('CA', 'CAN', 'Canada', '{"hr": "Kanada", "mk": "Канада"}'),
('CC', 'CCK', 'Cocos (Keeling) Islands', '{"hr": "Kokosovi (Keelingovi) Otoci", "mk": "Кокосови (Килиншки) Острови"}'),
('CD', 'COD', 'Congo, The Democratic Republic of the', '{"hr": "Kongo - Kinshasa", "mk": "Конго - Киншаса"}'),
('CF', 'CAF', 'Central African Republic', '{"hr": "Srednjoafrička Republika", "mk": "Централноафриканска Република"}'),
('CG', 'COG', 'Congo', '{"hr": "Kongo - Brazzaville", "mk": "Конго - Бразавил"}'),
('CH', 'CHE', 'Switzerland', '{"hr": "Švicarska", "mk": "Швајцарија"}'),
('CI', 'CIV', 'Côte d''Ivoire', '{"hr": "Obala Bjelokosti", "mk": "Брегот на Слоновата Коска"}'),
('CK', 'COK', 'Cook Islands', '{"hr": "Cookovi Otoci", "mk": "Кукови Острови"}'),
('CL', 'CHL', 'Chile', '{"hr": "Čile", "mk": "Чиле"}'),
('CM', 'CMR', 'Cameroon', '{"hr": "Kamerun", "mk": "Камерун"}'),
('CN', 'CHN', 'China', '{"hr": "Kina", "mk": "Кина"}'),
('CO', 'COL', 'Colombia', '{"hr": "Kolumbija", "mk": "Колумбија"}'),
('CR', 'CRI', 'Costa Rica', '{"hr": "Kostarika", "mk": "Костарика"}'),
('CU', 'CUB', 'Cuba', '{"hr": "Kuba", "mk": "Куба"}'),
('CV', 'CPV', 'Cabo Verde', '{"hr": "Zelenortska Republika", "mk": "Кабо Верде"}'),
('CW', 'CUW', 'Curaçao', '{"hr": "Curaçao", "mk": "Курасао"}'),
('CX', 'CXR', 'Christmas Island', '{"hr": "Božićni Otok", "mk": "Божиќен Остров"}'),
('CY', 'CYP', 'Cyprus', '{"hr": "Cipar", "mk": "Кипар"}'),
('CZ', 'CZE', 'Czechia', '{"hr": "Češka", "mk": "Чешка"}'),
('DE', 'DEU', 'Germany', '{"hr": "Njemačka", "mk": "Германија"}'),
('DJ', 'DJI', 'Djibouti', '{"hr": "Džibuti", "mk": "Џибути"}'),
('DK', 'DNK', 'Denmark', '{"hr": "Danska", "mk": "Данска"}'),
('DM', 'DMA', 'Dominica', '{"hr": "Dominika", "mk": "Доминика"}'),
('DO', 'DOM', 'Dominican Republic', '{"hr": "Dominikanska Republika", "mk": "Доминиканска Република"}'),
('DZ', 'DZA', 'Algeria', '{"hr": "Alžir", "mk": "Алжир"}'),
('EC', 'ECU', 'Ecuador', '{"hr": "Ekvador", "mk": "Еквадор"}'),
('EE', 'EST', 'Estonia', '{"hr": "Estonija", "mk": "Естонија"}'),
('EG', 'EGY', 'Egypt', '{"hr": "Egipat", "mk": "Египет"}'),
('EH', 'ESH', 'Western Sahara', '{"hr": "Zapadna Sahara", "mk": "Западна Сахара"}'),
('ER', 'ERI', 'Eritrea', '{"hr": "Eritreja", "mk": "Еритреја"}'),
('ES', 'ESP', 'Spain', '{"hr": "Španjolska", "mk": "Шпанија"}'),
('ET', 'ETH', 'Ethiopia', '{"hr": "Etiopija", "mk": "Етиопија"}'),
('FI', 'FIN', 'Finland', '{"hr": "Finska", "mk": "Финска"}'),
('FJ', 'FJI', 'Fiji', '{"hr": "Fidži", "mk": "Фиџи"}'),
('FK', 'FLK', 'Falkland Islands (Malvinas)', '{"hr": "Falklandski Otoci", "mk": "Фолкландски Острови"}'),
('FM', 'FSM', 'Micronesia, Federated States of', '{"hr": "Mikronezija", "mk": "Микронезија"}'),
('FO', 'FRO', 'Faroe Islands', '{"hr": "Ovčji Otoci", "mk": "Фарски Острови"}'),
('FR', 'FRA', 'France', '{"hr": "Francuska", "mk": "Франција"}'),
('GA', 'GAB', 'Gabon', '{"hr": "Gabon", "mk": "Габон"}'),
('GB', 'GBR', 'United Kingdom', '{"hr": "Ujedinjeno Kraljevstvo", "mk": "Обединето Кралство"}'),
('GD', 'GRD', 'Grenada', '{"hr": "Grenada", "mk": "Гренада"}'),
('GE', 'GEO', 'Georgia', '{"hr": "Gruzija", "mk": "Грузија"}'),
('GF', 'GUF', 'French Guiana', '{"hr": "Francuska Gijana", "mk": "Француска Гвајана"}'),
('GG', 'GGY', 'Guernsey', '{"hr": "Guernsey", "mk": "Гернзи"}'),
('GH', 'GHA', 'Ghana', '{"hr": "Gana", "mk": "Гана"}'),
('GI', 'GIB', 'Gibraltar', '{"hr": "Gibraltar", "mk": "Гибралтар"}'),
('GL', 'GRL', 'Greenland', '{"hr": "Grenland", "mk": "Гренланд"}'),
('GM', 'GMB', 'Gambia', '{"hr": "Gambija", "mk": "Гамбија"}'),
('GN', 'GIN', 'Guinea', '{"hr": "Gvineja", "mk": "Гвинеја"}'),
('GP', 'GLP', 'Guadeloupe', '{"hr": "Guadalupe", "mk": "Гвадалупе"}'),
('GQ', 'GNQ', 'Equatorial Guinea', '{"hr": "Ekvatorska Gvineja", "mk": "Екваторска Гвинеја"}'),
('GR', 'GRC', 'Greece', '{"hr": "Grčka", "mk": "Грција"}'),
('GS', 'SGS', 'South Georgia and the South Sandwich Islands', '{"hr": "Južna Georgia i Otoci Južni Sandwich", "mk": "Јужна Џорџија и Јужни Сендвички Острови"}'),
('GT', 'GTM', 'Guatemala', '{"hr": "Gvatemala", "mk": "Гватемала"}'),
('GU', 'GUM', 'Guam', '{"hr": "Guam", "mk": "Гуам"}'),
('GW', 'GNB', 'Guinea-Bissau', '{"hr": "Gvineja Bisau", "mk": "Гвинеја Бисао"}'),
('GY', 'GUY', 'Guyana', '{"hr": "Gvajana", "mk": "Гвајана"}'),
('HK', 'HKG', 'Hong Kong', '{"hr": "PUP Hong Kong Kina", "mk": "Хонгконг САР Кина"}'),
('HM', 'HMD', 'Heard Island and McDonald Islands', '{"hr": "Otoci Heard i McDonald", "mk": "Остров Херд и Острови Мекдоналд"}'),
('HN', 'HND', 'Honduras', '{"hr": "Honduras", "mk": "Хондурас"}'),
('HR', 'HRV', 'Croatia', '{"hr": "Hrvatska", "mk": "Хрватска"}'),
('HT', 'HTI', 'Haiti', '{"hr": "Haiti", "mk": "Хаити"}'),
('HU', 'HUN', 'Hungary', '{"hr": "Mađarska", "mk": "Унгарија"}'),
('ID', 'IDN', 'Indonesia', '{"hr": "Indonezija", "mk": "Индонезија"}'),
('IE', 'IRL', 'Ireland', '{"hr": "Irska", "mk": "Ирска"}'),
('IL', 'ISR', 'Israel', '{"hr": "Izrael", "mk": "Израел"}'),
('IM', 'IMN', 'Isle of Man', '{"hr": "Otok Man", "mk": "Остров Ман"}'),
('IN', 'IND', 'India', '{"hr": "Indija", "mk": "Индија"}'),
('IO', 'IOT', 'British Indian Ocean Territory', '{"hr": "Britanski Indijskooceanski Teritorij", "mk": "Британска Индоокеанска Територија"}'),
('IQ', 'IRQ', 'Iraq', '{"hr": "Irak", "mk": "Ирак"}'),
('IR', 'IRN', 'Iran', '{"hr": "Iran", "mk": "Иран"}'),
('IS', 'ISL', 'Iceland', '{"hr": "Island", "mk": "Исланд"}'),
('IT', 'ITA', 'Italy', '{"hr": "Italija", "mk": "Италија"}'),
('JE', 'JEY', 'Jersey', '{"hr": "Jersey", "mk": "Џерси"}'),
('JM', 'JAM', 'Jamaica', '{"hr": "Jamajka", "mk": "Јамајка"}'),
('JO', 'JOR', 'Jordan', '{"hr": "Jordan", "mk": "Јордан"}'),
('JP', 'JPN', 'Japan', '{"hr": "Japan", "mk": "Јапонија"}'),
('KE', 'KEN', 'Kenya', '{"hr": "Kenija", "mk": "Кенија"}'),
('KG', 'KGZ', 'Kyrgyzstan', '{"hr": "Kirgistan", "mk": "Киргистан"}'),
('KH', 'KHM', 'Cambodia', '{"hr": "Kambodža", "mk": "Камбоџа"}'),
('KI', 'KIR', 'Kiribati', '{"hr": "Kiribati", "mk": "Кирибати"}'),
('KM', 'COM', 'Comoros', '{"hr": "Komori", "mk": "Коморски Острови"}'),
('KN', 'KNA', 'Saint Kitts and Nevis', '{"hr": "Sveti Kristofor i Nevis", "mk": "Свети Китс и Невис"}'),
('KP', 'PRK', 'North Korea', '{"hr": "Sjeverna Koreja", "mk": "Северна Кореја"}'),
('KR', 'KOR', 'South Korea', '{"hr": "Južna Koreja", "mk": "Јужна Кореја"}'),
('KW', 'KWT', 'Kuwait', '{"hr": "Kuvajt", "mk": "Кувајт"}'),
('KY', 'CYM', 'Cayman Islands', '{"hr": "Kajmanski Otoci", "mk": "Кајмански Острови"}'),
('KZ', 'KAZ', 'Kazakhstan', '{"hr": "Kazahstan", "mk": "Казахстан"}'),
('LA', 'LAO', 'Laos', '{"hr": "Laos", "mk": "Лаос"}'),
('LB', 'LBN', 'Lebanon', '{"hr": "Libanon", "mk": "Либан"}'),
('LC', 'LCA', 'Saint Lucia', '{"hr": "Sveta Lucija", "mk": "Сент Лусија"}'),
('LI', 'LIE', 'Liechtenstein', '{"hr": "Lihtenštajn", "mk": "Лихтенштајн"}'),
('LK', 'LKA', 'Sri Lanka', '{"hr": "Šri Lanka", "mk": "Шри Ланка"}'),
('LR', 'LBR', 'Liberia', '{"hr": "Liberija", "mk": "Либерија"}'),
('LS', 'LSO', 'Lesotho', '{"hr": "Lesoto", "mk": "Лесото"}'),
('LT', 'LTU', 'Lithuania', '{"hr": "Litva", "mk": "Литванија"}'),
('LU', 'LUX', 'Luxembourg', '{"hr": "Luksemburg", "mk": "Луксембург"}'),
('LV', 'LVA', 'Latvia', '{"hr": "Latvija", "mk": "Латвија"}'),
('LY', 'LBY', 'Libya', '{"hr": "Libija", "mk": "Либија"}'),
('MA', 'MAR', 'Morocco', '{"hr": "Maroko", "mk": "Мароко"}'),
('MC', 'MCO', 'Monaco', '{"hr": "Monako", "mk": "Монако"}'),
('MD', 'MDA', 'Moldova', '{"hr": "Moldavija", "mk": "Молдавија"}'),
('ME', 'MNE', 'Montenegro', '{"hr": "Crna Gora", "mk": "Црна Гора"}'),
('MF', 'MAF', 'Saint Martin (French part)', '{"hr": "Saint Martin", "mk": "Сент Мартин"}'),
('MG', 'MDG', 'Madagascar', '{"hr": "Madagaskar", "mk": "Мадагаскар"}'),
('MH', 'MHL', 'Marshall Islands', '{"hr": "Maršalovi Otoci", "mk": "Маршалски Острови"}'),
('MK', 'MKD', 'North Macedonia', '{"hr": "Sjeverna Makedonija", "mk": "Северна Македонија"}'),
('ML', 'MLI', 'Mali', '{"hr": "Mali", "mk": "Мали"}'),
('MM', 'MMR', 'Myanmar', '{"hr": "Mjanmar (Burma)", "mk": "Мјанмар (Бурма)"}'),
('MN', 'MNG', 'Mongolia', '{"hr": "Mongolija", "mk": "Монголија"}'),
('MO', 'MAC', 'Macao', '{"hr": "PUP Makao Kina", "mk": "Макао САР"}'),
('MP', 'MNP', 'Northern Mariana Islands', '{"hr": "Sjevernomarijanski Otoci", "mk": "Северни Маријански Острови"}'),
('MQ', 'MTQ', 'Martinique', '{"hr": "Martinik", "mk": "Мартиник"}'),
('MR', 'MRT', 'Mauritania', '{"hr": "Mauretanija", "mk": "Мавританија"}'),
('MS', 'MSR', 'Montserrat', '{"hr": "Montserrat", "mk": "Монсерат"}'),
('MT', 'MLT', 'Malta', '{"hr": "Malta", "mk": "Малта"}'),
('MU', 'MUS', 'Mauritius', '{"hr": "Mauricijus", "mk": "Маврициус"}'),
('MV', 'MDV', 'Maldives', '{"hr": "Maldivi", "mk": "Малдиви"}'),
('MW', 'MWI', 'Malawi', '{"hr": "Malavi", "mk": "Малави"}'),
('MX', 'MEX', 'Mexico', '{"hr": "Meksiko", "mk": "Мексико"}'),
('MY', 'MYS', 'Malaysia', '{"hr": "Malezija", "mk": "Малезија"}'),
('MZ', 'MOZ', 'Mozambique', '{"hr": "Mozambik", "mk": "Мозамбик"}'),
('NA', 'NAM', 'Namibia', '{"hr": "Namibija", "mk": "Намибија"}'),
('NC', 'NCL', 'New Caledonia', '{"hr": "Nova Kaledonija", "mk": "Нова Каледонија"}'),
('NE', 'NER', 'Niger', '{"hr": "Niger", "mk": "Нигер"}'),
('NF', 'NFK', 'Norfolk Island', '{"hr": "Otok Norfolk", "mk": "Норфолшки Остров"}'),
('NG', 'NGA', 'Nigeria', '{"hr": "Nigerija", "mk": "Нигерија"}'),
('NI', 'NIC', 'Nicaragua', '{"hr": "Nikaragva", "mk": "Никарагва"}'),
('NL', 'NLD', 'Netherlands', '{"hr": "Nizozemska", "mk": "Холандија"}'),
('NO', 'NOR', 'Norway', '{"hr": "Norveška", "mk": "Норвешка"}'),
('NP', 'NPL', 'Nepal', '{"hr": "Nepal", "mk": "Непал"}'),
('NR', 'NRU', 'Nauru', '{"hr": "Nauru", "mk": "Науру"}'),
('NU', 'NIU', 'Niue', '{"hr": "Niue", "mk": "Ниује"}'),
('NZ', 'NZL', 'New Zealand', '{"hr": "Novi Zeland", "mk": "Нов Зеланд"}'),
('OM', 'OMN', 'Oman', '{"hr": "Oman", "mk": "Оман"}'),
('PA', 'PAN', 'Panama', '{"hr": "Panama", "mk": "Панама"}'),
('PE', 'PER', 'Peru', '{"hr": "Peru", "mk": "Перу"}'),
('PF', 'PYF', 'French Polynesia', '{"hr": "Francuska Polinezija", "mk": "Француска Полинезија"}'),
('PG', 'PNG', 'Papua New Guinea', '{"hr": "Papua Nova Gvineja", "mk": "Папуа Нова Гвинеја"}'),
('PH', 'PHL', 'Philippines', '{"hr": "Filipini", "mk": "Филипини"}'),
('PK', 'PAK', 'Pakistan', '{"hr": "Pakistan", "mk": "Пакистан"}'),
('PL', 'POL', 'Poland', '{"hr": "Poljska", "mk": "Полска"}'),
('PM', 'SPM', 'Saint Pierre and Miquelon', '{"hr": "Sveti Petar i Mikelon", "mk": "Сент Пјер и Микелан"}'),
('PN', 'PCN', 'Pitcairn', '{"hr": "Pitcairnovi Otoci", "mk": "Питкернски Острови"}'),
('PR', 'PRI', 'Puerto Rico', '{"hr": "Portoriko", "mk": "Порторико"}'),
('PS', 'PSE', 'Palestine, State of', '{"hr": "Palestinsko područje", "mk": "Палестински Територии"}'),
('PT', 'PRT', 'Portugal', '{"hr": "Portugal", "mk": "Португалија"}'),
('PW', 'PLW', 'Palau', '{"hr": "Palau", "mk": "Палау"}'),
('PY', 'PRY', 'Paraguay', '{"hr": "Paragvaj", "mk": "Парагвај"}'),
('QA', 'QAT', 'Qatar', '{"hr": "Katar", "mk": "Катар"}'),
('RE', 'REU', 'Réunion', '{"hr": "Réunion", "mk": "Рејунион"}'),
('RO', 'ROU', 'Romania', '{"hr": "Rumunjska", "mk": "Романија"}'),
('RS', 'SRB', 'Serbia', '{"hr": "Srbija", "mk": "Србија"}'),
('RU', 'RUS', 'Russian Federation', '{"hr": "Rusija", "mk": "Русија"}'),
('RW', 'RWA', 'Rwanda', '{"hr": "Ruanda", "mk": "Руанда"}'),
('SA', 'SAU', 'Saudi Arabia', '{"hr": "Saudijska Arabija", "mk": "Саудиска Арабија"}'),
('SB', 'SLB', 'Solomon Islands', '{"hr": "Salomonovi Otoci", "mk": "Соломонски Острови"}'),
('SC', 'SYC', 'Seychelles', '{"hr": "Sejšeli", "mk": "Сејшели"}'),
('SD', 'SDN', 'Sudan', '{"hr": "Sudan", "mk": "Судан"}'),
('SE', 'SWE', 'Sweden', '{"hr": "Švedska", "mk": "Шведска"}'),
('SG', 'SGP', 'Singapore', '{"hr": "Singapur", "mk": "Сингапур"}'),
('SH', 'SHN', 'Saint Helena, Ascension and Tristan da Cunha', '{"hr": "Sveta Helena", "mk": "Света Елена"}'),
('SI', 'SVN', 'Slovenia', '{"hr": "Slovenija", "mk": "Словенија"}'),
('SJ', 'SJM', 'Svalbard and Jan Mayen', '{"hr": "Svalbard i Jan Mayen", "mk": "Свалбард и Јан Мајен"}'),
('SK', 'SVK', 'Slovakia', '{"hr": "Slovačka", "mk": "Словачка"}'),
('SL', 'SLE', 'Sierra Leone', '{"hr": "Sijera Leone", "mk": "Сиера Леоне"}'),
('SM', 'SMR', 'San Marino', '{"hr": "San Marino", "mk": "Сан Марино"}'),
('SN', 'SEN', 'Senegal', '{"hr": "Senegal", "mk": "Сенегал"}'),
('SO', 'SOM', 'Somalia', '{"hr": "Somalija", "mk": "Сомалија"}'),
('SR', 'SUR', 'Suriname', '{"hr": "Surinam", "mk": "Суринам"}'),
('SS', 'SSD', 'South Sudan', '{"hr": "Južni Sudan", "mk": "Јужен Судан"}'),
('ST', 'STP', 'Sao Tome and Principe', '{"hr": "Sveti Toma i Princip", "mk": "Саун Томе и Принсип"}'),
('SV', 'SLV', 'El Salvador', '{"hr": "Salvador", "mk": "Ел Салвадор"}'),
('SX', 'SXM', 'Sint Maarten (Dutch part)', '{"hr": "Sint Maarten", "mk": "Свети Мартин"}'),
('SY', 'SYR', 'Syria', '{"hr": "Sirija", "mk": "Сирија"}'),
('SZ', 'SWZ', 'Eswatini', '{"hr": "Esvatini", "mk": "Свазиленд"}'),
('TC', 'TCA', 'Turks and Caicos Islands', '{"hr": "Otoci Turks i Caicos", "mk": "Острови Туркс и Каикос"}'),
('TD', 'TCD', 'Chad', '{"hr": "Čad", "mk": "Чад"}'),
('TF', 'ATF', 'French Southern Territories', '{"hr": "Francuski Južni Teritoriji", "mk": "Француски Јужни Територии"}'),
('TG', 'TGO', 'Togo', '{"hr": "Togo", "mk": "Того"}'),
('TH', 'THA', 'Thailand', '{"hr": "Tajland", "mk": "Тајланд"}'),
('TJ', 'TJK', 'Tajikistan', '{"hr": "Tadžikistan", "mk": "Таџикистан"}'),
('TK', 'TKL', 'Tokelau', '{"hr": "Tokelau", "mk": "Токелау"}'),
('TL', 'TLS', 'Timor-Leste', '{"hr": "Timor-Leste", "mk": "Тимор Лесте"}'),
('TM', 'TKM', 'Turkmenistan', '{"hr": "Turkmenistan", "mk": "Туркменистан"}'),
('TN', 'TUN', 'Tunisia', '{"hr": "Tunis", "mk": "Тунис"}'),
('TO', 'TON', 'Tonga', '{"hr": "Tonga", "mk": "Тонга"}'),
('TR', 'TUR', 'Türkiye', '{"hr": "Turska", "mk": "Турција"}'),
('TT', 'TTO', 'Trinidad and Tobago', '{"hr": "Trinidad i Tobago", "mk": "Тринидад и Тобаго"}'),
('TV', 'TUV', 'Tuvalu', '{"hr": "Tuvalu", "mk": "Тувалу"}'),
('TW', 'TWN', 'Taiwan', '{"hr": "Tajvan", "mk": "Тајван"}'),
('TZ', 'TZA', 'Tanzania', '{"hr": "Tanzanija", "mk": "Танзанија"}'),
('UA', 'UKR', 'Ukraine', '{"hr": "Ukrajina", "mk": "Украина"}'),
('UG', 'UGA', 'Uganda', '{"hr": "Uganda", "mk": "Уганда"}'),
('UM', 'UMI', 'United States Minor Outlying Islands', '{"hr": "Mali udaljeni otoci SAD-a", "mk": "Американски територии во Пацификот"}'),
('US', 'USA', 'United States', '{"hr": "Sjedinjene Američke Države", "mk": "Соединети Американски Држави"}'),
('UY', 'URY', 'Uruguay', '{"hr": "Urugvaj", "mk": "Уругвај"}'),
('UZ', 'UZB', 'Uzbekistan', '{"hr": "Uzbekistan", "mk": "Узбекистан"}'),
('VA', 'VAT', 'Holy See (Vatican City State)', '{"hr": "Vatikan", "mk": "Ватикан"}'),
('VC', 'VCT', 'Saint Vincent and the Grenadines', '{"hr": "Sveti Vincent i Grenadini", "mk": "Сент Винсент и Гренадини"}'),
('VE', 'VEN', 'Venezuela', '{"hr": "Venezuela", "mk": "Венецуела"}'),
('VG', 'VGB', 'Virgin Islands, British', '{"hr": "Britanski Djevičanski Otoci", "mk": "Британски Девствени Острови"}'),
('VI', 'VIR', 'Virgin Islands, U.S.', '{"hr": "Američki Djevičanski Otoci", "mk": "Американски Девствени Острови"}'),
('VN', 'VNM', 'Vietnam', '{"hr": "Vijetnam", "mk": "Виетнам"}'),
('VU', 'VUT', 'Vanuatu', '{"hr": "Vanuatu", "mk": "Вануату"}'),
('WF', 'WLF', 'Wallis and Futuna', '{"hr": "Wallis i Futuna", "mk": "Валис и Футуна"}'),
('WS', 'WSM', 'Samoa', '{"hr": "Samoa", "mk": "Самоа"}'),
('XK', 'XKX', 'Kosovo', '{"hr": "Kosovo", "mk": "Косово"}'),
('YE', 'YEM', 'Yemen', '{"hr": "Jemen", "mk": "Јемен"}'),
('YT', 'MYT', 'Mayotte', '{"hr": "Mayotte", "mk": "Мајот"}'),
('ZA', 'ZAF', 'South Africa', '{"hr": "Južnoafrička Republika", "mk": "Јужноафриканска Република"}'),
('ZM', 'ZMB', 'Zambia', '{"hr": "Zambija", "mk": "Замбија"}'),
('ZW', 'ZWE', 'Zimbabwe', '{"hr": "Zimbabve", "mk": "Зимбабве"}');

View File

@@ -0,0 +1,13 @@
USE [$(MSSQL_DB)];
GO
INSERT INTO dbo.Sport (Name) VALUES
('Running'),
('Darts'),
('Bowling'),
('Table Tennis'),
('Chess'),
('Pikado'),
('Mini Golf'),
('Swimming'),
('Cycling');

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,52 @@
#!/bin/bash
set -euo pipefail
sqlcmd_bin="/opt/mssql-tools18/bin/sqlcmd"
server_ready=false
db_exists=""
run_sqlcmd_query() {
"$sqlcmd_bin" -S localhost -U sa -P "${MSSQL_SA_PASSWORD}" -C "$@"
}
for _ in $(seq 1 60); do
if run_sqlcmd_query -Q "SELECT 1" >/dev/null 2>&1; then
server_ready=true
break
fi
sleep 2
done
if [ "$server_ready" != "true" ]; then
echo "SQL Server did not become ready in time." >&2
exit 1
fi
for _ in $(seq 1 30); do
if db_exists="$(run_sqlcmd_query -h -1 -W -Q "SET NOCOUNT ON; SELECT CASE WHEN DB_ID(N'${MSSQL_DB}') IS NULL THEN 0 ELSE 1 END;" 2>/dev/null)"; then
db_exists="$(echo "$db_exists" | tr -d '[:space:]')"
break
fi
sleep 2
done
if [ -z "$db_exists" ]; then
echo "Could not determine whether database ${MSSQL_DB} exists." >&2
exit 1
fi
if [ "$db_exists" = "1" ]; then
echo "Database ${MSSQL_DB} already exists. Skipping initialization."
exit 0
fi
for sql_file in /docker-entrypoint-initdb.d/*.sql; do
echo "Running ${sql_file}"
run_sqlcmd_query \
-b \
-f 65001 \
-i "${sql_file}" \
-v MSSQL_DB="${MSSQL_DB}" APP_DB_USER="${APP_DB_USER}" APP_DB_PASSWORD="${APP_DB_PASSWORD}" MSSQL_COLLATION="${MSSQL_COLLATION}"
done

View File

@@ -0,0 +1,8 @@
#!/bin/bash
set -euo pipefail
/opt/mssql/bin/sqlservr &
/usr/local/bin/configure-db.sh
wait

View File

@@ -0,0 +1,9 @@
# PostgreSQL admin (superuser)
POSTGRES_USER=bib
POSTGRES_PASSWORD=change-me
POSTGRES_DB=events
POSTGRES_PORT=5432
# App user
APP_DB_USER=sport
APP_DB_PASSWORD=change-me

View File

@@ -0,0 +1,33 @@
services:
postgres:
image: postgres:18
container_name: postgres_db
restart: unless-stopped
env_file:
- .env
environment:
# custom variables for init scripts (.sh)
APP_DB_USER: ${APP_DB_USER}
APP_DB_PASSWORD: ${APP_DB_PASSWORD}
ports:
- "${POSTGRES_PORT}:5432"
volumes:
- postgres_data:/var/lib/postgresql
- ./backup:/backup
#chmod +x init/01-roles.sh on linux?
- ./init:/docker-entrypoint-initdb.d
# if we need periodic backup
# entrypoint: >
# bash -c "while true; do
# pg_dump -h postgres -U ${POSTGRES_USER} ${POSTGRES_DB} > /backup/backup_$$(date +%Y%m%d_%H%M%S).sql;
# sleep 86400;
# done"
volumes:
postgres_data:

View File

@@ -0,0 +1,7 @@
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE USER ${APP_DB_USER} WITH PASSWORD '${APP_DB_PASSWORD}';
GRANT CONNECT ON DATABASE ${POSTGRES_DB} TO ${APP_DB_USER};
EOSQL

View File

@@ -0,0 +1,61 @@
-- COUNTRIES
CREATE TABLE country (
code VARCHAR(3) PRIMARY KEY,
alpha3 CHAR(3) NOT NULL,
name VARCHAR(100) NOT NULL,
translations JSONB,
UNIQUE (name)
);
-- PERSONS
CREATE TABLE person (
id SERIAL PRIMARY KEY,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
first_name_transcription VARCHAR(100) NOT NULL,
last_name_transcription VARCHAR(100) NOT NULL,
address_line VARCHAR(200) NOT NULL,
postal_code VARCHAR(20) NOT NULL,
city VARCHAR(100) NOT NULL,
address_country VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL,
contact_phone VARCHAR(50) NOT NULL,
birth_date DATE NOT NULL,
document_number VARCHAR(50) NOT NULL,
country_code VARCHAR(3) NOT NULL,
FOREIGN KEY (country_code) REFERENCES country(code),
-- UNIQUE dokument po državi
UNIQUE (document_number, country_code)
);
-- SPORTS
CREATE TABLE sport (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
UNIQUE (name)
);
-- EVENTS
CREATE TABLE event (
id SERIAL PRIMARY KEY,
name VARCHAR(150) NOT NULL,
event_date DATE NOT NULL
);
-- REGISTRATIONS
CREATE TABLE registration (
id SERIAL PRIMARY KEY,
person_id INT NOT NULL,
sport_id INT NOT NULL,
event_id INT NOT NULL,
registered_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (person_id) REFERENCES person(id) ON DELETE CASCADE,
FOREIGN KEY (sport_id) REFERENCES sport(id) ON DELETE CASCADE,
FOREIGN KEY (event_id) REFERENCES event(id) ON DELETE CASCADE,
UNIQUE (person_id, sport_id, event_id)
);

View File

@@ -0,0 +1,15 @@
#!/bin/bash
set -e
# Ova datoteka mora imati LF, a ne CRLF
DB_NAME="${POSTGRES_DB}"
APP_USER="${APP_DB_USER}"
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$DB_NAME" <<-EOSQL
GRANT USAGE ON SCHEMA public TO ${APP_USER};
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO ${APP_USER};
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO ${APP_USER};
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ${APP_USER};
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO ${APP_USER};
EOSQL

View File

@@ -0,0 +1,251 @@
INSERT INTO country (code, alpha3, name, translations) VALUES
('AD', 'AND', 'Andorra', '{"hr": "Andora", "mk": "Андора"}'),
('AE', 'ARE', 'United Arab Emirates', '{"hr": "Ujedinjeni Arapski Emirati", "mk": "Обединети Арапски Емирати"}'),
('AF', 'AFG', 'Afghanistan', '{"hr": "Afganistan", "mk": "Авганистан"}'),
('AG', 'ATG', 'Antigua and Barbuda', '{"hr": "Antigva i Barbuda", "mk": "Антига и Барбуда"}'),
('AI', 'AIA', 'Anguilla', '{"hr": "Angvila", "mk": "Ангвила"}'),
('AL', 'ALB', 'Albania', '{"hr": "Albanija", "mk": "Албанија"}'),
('AM', 'ARM', 'Armenia', '{"hr": "Armenija", "mk": "Ерменија"}'),
('AO', 'AGO', 'Angola', '{"hr": "Angola", "mk": "Ангола"}'),
('AQ', 'ATA', 'Antarctica', '{"hr": "Antarktika", "mk": "Антарктик"}'),
('AR', 'ARG', 'Argentina', '{"hr": "Argentina", "mk": "Аргентина"}'),
('AS', 'ASM', 'American Samoa', '{"hr": "Američka Samoa", "mk": "Американска Самоа"}'),
('AT', 'AUT', 'Austria', '{"hr": "Austrija", "mk": "Австрија"}'),
('AU', 'AUS', 'Australia', '{"hr": "Australija", "mk": "Австралија"}'),
('AW', 'ABW', 'Aruba', '{"hr": "Aruba", "mk": "Аруба"}'),
('AX', 'ALA', 'Åland Islands', '{"hr": "Ålandski otoci", "mk": "Оландски Острови"}'),
('AZ', 'AZE', 'Azerbaijan', '{"hr": "Azerbajdžan", "mk": "Азербејџан"}'),
('BA', 'BIH', 'Bosnia and Herzegovina', '{"hr": "Bosna i Hercegovina", "mk": "Босна и Херцеговина"}'),
('BB', 'BRB', 'Barbados', '{"hr": "Barbados", "mk": "Барбадос"}'),
('BD', 'BGD', 'Bangladesh', '{"hr": "Bangladeš", "mk": "Бангладеш"}'),
('BE', 'BEL', 'Belgium', '{"hr": "Belgija", "mk": "Белгија"}'),
('BF', 'BFA', 'Burkina Faso', '{"hr": "Burkina Faso", "mk": "Буркина Фасо"}'),
('BG', 'BGR', 'Bulgaria', '{"hr": "Bugarska", "mk": "Бугарија"}'),
('BH', 'BHR', 'Bahrain', '{"hr": "Bahrein", "mk": "Бахреин"}'),
('BI', 'BDI', 'Burundi', '{"hr": "Burundi", "mk": "Бурунди"}'),
('BJ', 'BEN', 'Benin', '{"hr": "Benin", "mk": "Бенин"}'),
('BL', 'BLM', 'Saint Barthélemy', '{"hr": "Saint Barthélemy", "mk": "Свети Вартоломеј"}'),
('BM', 'BMU', 'Bermuda', '{"hr": "Bermudi", "mk": "Бермуди"}'),
('BN', 'BRN', 'Brunei Darussalam', '{"hr": "Brunej", "mk": "Брунеј"}'),
('BO', 'BOL', 'Bolivia', '{"hr": "Bolivija", "mk": "Боливија"}'),
('BQ', 'BES', 'Bonaire, Sint Eustatius and Saba', '{"hr": "Karipski otoci Nizozemske", "mk": "Карипска Холандија"}'),
('BR', 'BRA', 'Brazil', '{"hr": "Brazil", "mk": "Бразил"}'),
('BS', 'BHS', 'Bahamas', '{"hr": "Bahami", "mk": "Бахами"}'),
('BT', 'BTN', 'Bhutan', '{"hr": "Butan", "mk": "Бутан"}'),
('BV', 'BVT', 'Bouvet Island', '{"hr": "Otok Bouvet", "mk": "Остров Буве"}'),
('BW', 'BWA', 'Botswana', '{"hr": "Bocvana", "mk": "Боцвана"}'),
('BY', 'BLR', 'Belarus', '{"hr": "Bjelorusija", "mk": "Белорусија"}'),
('BZ', 'BLZ', 'Belize', '{"hr": "Belize", "mk": "Белизе"}'),
('CA', 'CAN', 'Canada', '{"hr": "Kanada", "mk": "Канада"}'),
('CC', 'CCK', 'Cocos (Keeling) Islands', '{"hr": "Kokosovi (Keelingovi) Otoci", "mk": "Кокосови (Килиншки) Острови"}'),
('CD', 'COD', 'Congo, The Democratic Republic of the', '{"hr": "Kongo - Kinshasa", "mk": "Конго - Киншаса"}'),
('CF', 'CAF', 'Central African Republic', '{"hr": "Srednjoafrička Republika", "mk": "Централноафриканска Република"}'),
('CG', 'COG', 'Congo', '{"hr": "Kongo - Brazzaville", "mk": "Конго - Бразавил"}'),
('CH', 'CHE', 'Switzerland', '{"hr": "Švicarska", "mk": "Швајцарија"}'),
('CI', 'CIV', 'Côte d''Ivoire', '{"hr": "Obala Bjelokosti", "mk": "Брегот на Слоновата Коска"}'),
('CK', 'COK', 'Cook Islands', '{"hr": "Cookovi Otoci", "mk": "Кукови Острови"}'),
('CL', 'CHL', 'Chile', '{"hr": "Čile", "mk": "Чиле"}'),
('CM', 'CMR', 'Cameroon', '{"hr": "Kamerun", "mk": "Камерун"}'),
('CN', 'CHN', 'China', '{"hr": "Kina", "mk": "Кина"}'),
('CO', 'COL', 'Colombia', '{"hr": "Kolumbija", "mk": "Колумбија"}'),
('CR', 'CRI', 'Costa Rica', '{"hr": "Kostarika", "mk": "Костарика"}'),
('CU', 'CUB', 'Cuba', '{"hr": "Kuba", "mk": "Куба"}'),
('CV', 'CPV', 'Cabo Verde', '{"hr": "Zelenortska Republika", "mk": "Кабо Верде"}'),
('CW', 'CUW', 'Curaçao', '{"hr": "Curaçao", "mk": "Курасао"}'),
('CX', 'CXR', 'Christmas Island', '{"hr": "Božićni Otok", "mk": "Божиќен Остров"}'),
('CY', 'CYP', 'Cyprus', '{"hr": "Cipar", "mk": "Кипар"}'),
('CZ', 'CZE', 'Czechia', '{"hr": "Češka", "mk": "Чешка"}'),
('DE', 'DEU', 'Germany', '{"hr": "Njemačka", "mk": "Германија"}'),
('DJ', 'DJI', 'Djibouti', '{"hr": "Džibuti", "mk": "Џибути"}'),
('DK', 'DNK', 'Denmark', '{"hr": "Danska", "mk": "Данска"}'),
('DM', 'DMA', 'Dominica', '{"hr": "Dominika", "mk": "Доминика"}'),
('DO', 'DOM', 'Dominican Republic', '{"hr": "Dominikanska Republika", "mk": "Доминиканска Република"}'),
('DZ', 'DZA', 'Algeria', '{"hr": "Alžir", "mk": "Алжир"}'),
('EC', 'ECU', 'Ecuador', '{"hr": "Ekvador", "mk": "Еквадор"}'),
('EE', 'EST', 'Estonia', '{"hr": "Estonija", "mk": "Естонија"}'),
('EG', 'EGY', 'Egypt', '{"hr": "Egipat", "mk": "Египет"}'),
('EH', 'ESH', 'Western Sahara', '{"hr": "Zapadna Sahara", "mk": "Западна Сахара"}'),
('ER', 'ERI', 'Eritrea', '{"hr": "Eritreja", "mk": "Еритреја"}'),
('ES', 'ESP', 'Spain', '{"hr": "Španjolska", "mk": "Шпанија"}'),
('ET', 'ETH', 'Ethiopia', '{"hr": "Etiopija", "mk": "Етиопија"}'),
('FI', 'FIN', 'Finland', '{"hr": "Finska", "mk": "Финска"}'),
('FJ', 'FJI', 'Fiji', '{"hr": "Fidži", "mk": "Фиџи"}'),
('FK', 'FLK', 'Falkland Islands (Malvinas)', '{"hr": "Falklandski Otoci", "mk": "Фолкландски Острови"}'),
('FM', 'FSM', 'Micronesia, Federated States of', '{"hr": "Mikronezija", "mk": "Микронезија"}'),
('FO', 'FRO', 'Faroe Islands', '{"hr": "Ovčji Otoci", "mk": "Фарски Острови"}'),
('FR', 'FRA', 'France', '{"hr": "Francuska", "mk": "Франција"}'),
('GA', 'GAB', 'Gabon', '{"hr": "Gabon", "mk": "Габон"}'),
('GB', 'GBR', 'United Kingdom', '{"hr": "Ujedinjeno Kraljevstvo", "mk": "Обединето Кралство"}'),
('GD', 'GRD', 'Grenada', '{"hr": "Grenada", "mk": "Гренада"}'),
('GE', 'GEO', 'Georgia', '{"hr": "Gruzija", "mk": "Грузија"}'),
('GF', 'GUF', 'French Guiana', '{"hr": "Francuska Gijana", "mk": "Француска Гвајана"}'),
('GG', 'GGY', 'Guernsey', '{"hr": "Guernsey", "mk": "Гернзи"}'),
('GH', 'GHA', 'Ghana', '{"hr": "Gana", "mk": "Гана"}'),
('GI', 'GIB', 'Gibraltar', '{"hr": "Gibraltar", "mk": "Гибралтар"}'),
('GL', 'GRL', 'Greenland', '{"hr": "Grenland", "mk": "Гренланд"}'),
('GM', 'GMB', 'Gambia', '{"hr": "Gambija", "mk": "Гамбија"}'),
('GN', 'GIN', 'Guinea', '{"hr": "Gvineja", "mk": "Гвинеја"}'),
('GP', 'GLP', 'Guadeloupe', '{"hr": "Guadalupe", "mk": "Гвадалупе"}'),
('GQ', 'GNQ', 'Equatorial Guinea', '{"hr": "Ekvatorska Gvineja", "mk": "Екваторска Гвинеја"}'),
('GR', 'GRC', 'Greece', '{"hr": "Grčka", "mk": "Грција"}'),
('GS', 'SGS', 'South Georgia and the South Sandwich Islands', '{"hr": "Južna Georgia i Otoci Južni Sandwich", "mk": "Јужна Џорџија и Јужни Сендвички Острови"}'),
('GT', 'GTM', 'Guatemala', '{"hr": "Gvatemala", "mk": "Гватемала"}'),
('GU', 'GUM', 'Guam', '{"hr": "Guam", "mk": "Гуам"}'),
('GW', 'GNB', 'Guinea-Bissau', '{"hr": "Gvineja Bisau", "mk": "Гвинеја Бисао"}'),
('GY', 'GUY', 'Guyana', '{"hr": "Gvajana", "mk": "Гвајана"}'),
('HK', 'HKG', 'Hong Kong', '{"hr": "PUP Hong Kong Kina", "mk": "Хонгконг САР Кина"}'),
('HM', 'HMD', 'Heard Island and McDonald Islands', '{"hr": "Otoci Heard i McDonald", "mk": "Остров Херд и Острови Мекдоналд"}'),
('HN', 'HND', 'Honduras', '{"hr": "Honduras", "mk": "Хондурас"}'),
('HR', 'HRV', 'Croatia', '{"hr": "Hrvatska", "mk": "Хрватска"}'),
('HT', 'HTI', 'Haiti', '{"hr": "Haiti", "mk": "Хаити"}'),
('HU', 'HUN', 'Hungary', '{"hr": "Mađarska", "mk": "Унгарија"}'),
('ID', 'IDN', 'Indonesia', '{"hr": "Indonezija", "mk": "Индонезија"}'),
('IE', 'IRL', 'Ireland', '{"hr": "Irska", "mk": "Ирска"}'),
('IL', 'ISR', 'Israel', '{"hr": "Izrael", "mk": "Израел"}'),
('IM', 'IMN', 'Isle of Man', '{"hr": "Otok Man", "mk": "Остров Ман"}'),
('IN', 'IND', 'India', '{"hr": "Indija", "mk": "Индија"}'),
('IO', 'IOT', 'British Indian Ocean Territory', '{"hr": "Britanski Indijskooceanski Teritorij", "mk": "Британска Индоокеанска Територија"}'),
('IQ', 'IRQ', 'Iraq', '{"hr": "Irak", "mk": "Ирак"}'),
('IR', 'IRN', 'Iran', '{"hr": "Iran", "mk": "Иран"}'),
('IS', 'ISL', 'Iceland', '{"hr": "Island", "mk": "Исланд"}'),
('IT', 'ITA', 'Italy', '{"hr": "Italija", "mk": "Италија"}'),
('JE', 'JEY', 'Jersey', '{"hr": "Jersey", "mk": "Џерси"}'),
('JM', 'JAM', 'Jamaica', '{"hr": "Jamajka", "mk": "Јамајка"}'),
('JO', 'JOR', 'Jordan', '{"hr": "Jordan", "mk": "Јордан"}'),
('JP', 'JPN', 'Japan', '{"hr": "Japan", "mk": "Јапонија"}'),
('KE', 'KEN', 'Kenya', '{"hr": "Kenija", "mk": "Кенија"}'),
('KG', 'KGZ', 'Kyrgyzstan', '{"hr": "Kirgistan", "mk": "Киргистан"}'),
('KH', 'KHM', 'Cambodia', '{"hr": "Kambodža", "mk": "Камбоџа"}'),
('KI', 'KIR', 'Kiribati', '{"hr": "Kiribati", "mk": "Кирибати"}'),
('KM', 'COM', 'Comoros', '{"hr": "Komori", "mk": "Коморски Острови"}'),
('KN', 'KNA', 'Saint Kitts and Nevis', '{"hr": "Sveti Kristofor i Nevis", "mk": "Свети Китс и Невис"}'),
('KP', 'PRK', 'North Korea', '{"hr": "Sjeverna Koreja", "mk": "Северна Кореја"}'),
('KR', 'KOR', 'South Korea', '{"hr": "Južna Koreja", "mk": "Јужна Кореја"}'),
('KW', 'KWT', 'Kuwait', '{"hr": "Kuvajt", "mk": "Кувајт"}'),
('KY', 'CYM', 'Cayman Islands', '{"hr": "Kajmanski Otoci", "mk": "Кајмански Острови"}'),
('KZ', 'KAZ', 'Kazakhstan', '{"hr": "Kazahstan", "mk": "Казахстан"}'),
('LA', 'LAO', 'Laos', '{"hr": "Laos", "mk": "Лаос"}'),
('LB', 'LBN', 'Lebanon', '{"hr": "Libanon", "mk": "Либан"}'),
('LC', 'LCA', 'Saint Lucia', '{"hr": "Sveta Lucija", "mk": "Сент Лусија"}'),
('LI', 'LIE', 'Liechtenstein', '{"hr": "Lihtenštajn", "mk": "Лихтенштајн"}'),
('LK', 'LKA', 'Sri Lanka', '{"hr": "Šri Lanka", "mk": "Шри Ланка"}'),
('LR', 'LBR', 'Liberia', '{"hr": "Liberija", "mk": "Либерија"}'),
('LS', 'LSO', 'Lesotho', '{"hr": "Lesoto", "mk": "Лесото"}'),
('LT', 'LTU', 'Lithuania', '{"hr": "Litva", "mk": "Литванија"}'),
('LU', 'LUX', 'Luxembourg', '{"hr": "Luksemburg", "mk": "Луксембург"}'),
('LV', 'LVA', 'Latvia', '{"hr": "Latvija", "mk": "Латвија"}'),
('LY', 'LBY', 'Libya', '{"hr": "Libija", "mk": "Либија"}'),
('MA', 'MAR', 'Morocco', '{"hr": "Maroko", "mk": "Мароко"}'),
('MC', 'MCO', 'Monaco', '{"hr": "Monako", "mk": "Монако"}'),
('MD', 'MDA', 'Moldova', '{"hr": "Moldavija", "mk": "Молдавија"}'),
('ME', 'MNE', 'Montenegro', '{"hr": "Crna Gora", "mk": "Црна Гора"}'),
('MF', 'MAF', 'Saint Martin (French part)', '{"hr": "Saint Martin", "mk": "Сент Мартин"}'),
('MG', 'MDG', 'Madagascar', '{"hr": "Madagaskar", "mk": "Мадагаскар"}'),
('MH', 'MHL', 'Marshall Islands', '{"hr": "Maršalovi Otoci", "mk": "Маршалски Острови"}'),
('MK', 'MKD', 'North Macedonia', '{"hr": "Sjeverna Makedonija", "mk": "Северна Македонија"}'),
('ML', 'MLI', 'Mali', '{"hr": "Mali", "mk": "Мали"}'),
('MM', 'MMR', 'Myanmar', '{"hr": "Mjanmar (Burma)", "mk": "Мјанмар (Бурма)"}'),
('MN', 'MNG', 'Mongolia', '{"hr": "Mongolija", "mk": "Монголија"}'),
('MO', 'MAC', 'Macao', '{"hr": "PUP Makao Kina", "mk": "Макао САР"}'),
('MP', 'MNP', 'Northern Mariana Islands', '{"hr": "Sjevernomarijanski Otoci", "mk": "Северни Маријански Острови"}'),
('MQ', 'MTQ', 'Martinique', '{"hr": "Martinik", "mk": "Мартиник"}'),
('MR', 'MRT', 'Mauritania', '{"hr": "Mauretanija", "mk": "Мавританија"}'),
('MS', 'MSR', 'Montserrat', '{"hr": "Montserrat", "mk": "Монсерат"}'),
('MT', 'MLT', 'Malta', '{"hr": "Malta", "mk": "Малта"}'),
('MU', 'MUS', 'Mauritius', '{"hr": "Mauricijus", "mk": "Маврициус"}'),
('MV', 'MDV', 'Maldives', '{"hr": "Maldivi", "mk": "Малдиви"}'),
('MW', 'MWI', 'Malawi', '{"hr": "Malavi", "mk": "Малави"}'),
('MX', 'MEX', 'Mexico', '{"hr": "Meksiko", "mk": "Мексико"}'),
('MY', 'MYS', 'Malaysia', '{"hr": "Malezija", "mk": "Малезија"}'),
('MZ', 'MOZ', 'Mozambique', '{"hr": "Mozambik", "mk": "Мозамбик"}'),
('NA', 'NAM', 'Namibia', '{"hr": "Namibija", "mk": "Намибија"}'),
('NC', 'NCL', 'New Caledonia', '{"hr": "Nova Kaledonija", "mk": "Нова Каледонија"}'),
('NE', 'NER', 'Niger', '{"hr": "Niger", "mk": "Нигер"}'),
('NF', 'NFK', 'Norfolk Island', '{"hr": "Otok Norfolk", "mk": "Норфолшки Остров"}'),
('NG', 'NGA', 'Nigeria', '{"hr": "Nigerija", "mk": "Нигерија"}'),
('NI', 'NIC', 'Nicaragua', '{"hr": "Nikaragva", "mk": "Никарагва"}'),
('NL', 'NLD', 'Netherlands', '{"hr": "Nizozemska", "mk": "Холандија"}'),
('NO', 'NOR', 'Norway', '{"hr": "Norveška", "mk": "Норвешка"}'),
('NP', 'NPL', 'Nepal', '{"hr": "Nepal", "mk": "Непал"}'),
('NR', 'NRU', 'Nauru', '{"hr": "Nauru", "mk": "Науру"}'),
('NU', 'NIU', 'Niue', '{"hr": "Niue", "mk": "Ниује"}'),
('NZ', 'NZL', 'New Zealand', '{"hr": "Novi Zeland", "mk": "Нов Зеланд"}'),
('OM', 'OMN', 'Oman', '{"hr": "Oman", "mk": "Оман"}'),
('PA', 'PAN', 'Panama', '{"hr": "Panama", "mk": "Панама"}'),
('PE', 'PER', 'Peru', '{"hr": "Peru", "mk": "Перу"}'),
('PF', 'PYF', 'French Polynesia', '{"hr": "Francuska Polinezija", "mk": "Француска Полинезија"}'),
('PG', 'PNG', 'Papua New Guinea', '{"hr": "Papua Nova Gvineja", "mk": "Папуа Нова Гвинеја"}'),
('PH', 'PHL', 'Philippines', '{"hr": "Filipini", "mk": "Филипини"}'),
('PK', 'PAK', 'Pakistan', '{"hr": "Pakistan", "mk": "Пакистан"}'),
('PL', 'POL', 'Poland', '{"hr": "Poljska", "mk": "Полска"}'),
('PM', 'SPM', 'Saint Pierre and Miquelon', '{"hr": "Sveti Petar i Mikelon", "mk": "Сент Пјер и Микелан"}'),
('PN', 'PCN', 'Pitcairn', '{"hr": "Pitcairnovi Otoci", "mk": "Питкернски Острови"}'),
('PR', 'PRI', 'Puerto Rico', '{"hr": "Portoriko", "mk": "Порторико"}'),
('PS', 'PSE', 'Palestine, State of', '{"hr": "Palestinsko područje", "mk": "Палестински Територии"}'),
('PT', 'PRT', 'Portugal', '{"hr": "Portugal", "mk": "Португалија"}'),
('PW', 'PLW', 'Palau', '{"hr": "Palau", "mk": "Палау"}'),
('PY', 'PRY', 'Paraguay', '{"hr": "Paragvaj", "mk": "Парагвај"}'),
('QA', 'QAT', 'Qatar', '{"hr": "Katar", "mk": "Катар"}'),
('RE', 'REU', 'Réunion', '{"hr": "Réunion", "mk": "Рејунион"}'),
('RO', 'ROU', 'Romania', '{"hr": "Rumunjska", "mk": "Романија"}'),
('RS', 'SRB', 'Serbia', '{"hr": "Srbija", "mk": "Србија"}'),
('RU', 'RUS', 'Russian Federation', '{"hr": "Rusija", "mk": "Русија"}'),
('RW', 'RWA', 'Rwanda', '{"hr": "Ruanda", "mk": "Руанда"}'),
('SA', 'SAU', 'Saudi Arabia', '{"hr": "Saudijska Arabija", "mk": "Саудиска Арабија"}'),
('SB', 'SLB', 'Solomon Islands', '{"hr": "Salomonovi Otoci", "mk": "Соломонски Острови"}'),
('SC', 'SYC', 'Seychelles', '{"hr": "Sejšeli", "mk": "Сејшели"}'),
('SD', 'SDN', 'Sudan', '{"hr": "Sudan", "mk": "Судан"}'),
('SE', 'SWE', 'Sweden', '{"hr": "Švedska", "mk": "Шведска"}'),
('SG', 'SGP', 'Singapore', '{"hr": "Singapur", "mk": "Сингапур"}'),
('SH', 'SHN', 'Saint Helena, Ascension and Tristan da Cunha', '{"hr": "Sveta Helena", "mk": "Света Елена"}'),
('SI', 'SVN', 'Slovenia', '{"hr": "Slovenija", "mk": "Словенија"}'),
('SJ', 'SJM', 'Svalbard and Jan Mayen', '{"hr": "Svalbard i Jan Mayen", "mk": "Свалбард и Јан Мајен"}'),
('SK', 'SVK', 'Slovakia', '{"hr": "Slovačka", "mk": "Словачка"}'),
('SL', 'SLE', 'Sierra Leone', '{"hr": "Sijera Leone", "mk": "Сиера Леоне"}'),
('SM', 'SMR', 'San Marino', '{"hr": "San Marino", "mk": "Сан Марино"}'),
('SN', 'SEN', 'Senegal', '{"hr": "Senegal", "mk": "Сенегал"}'),
('SO', 'SOM', 'Somalia', '{"hr": "Somalija", "mk": "Сомалија"}'),
('SR', 'SUR', 'Suriname', '{"hr": "Surinam", "mk": "Суринам"}'),
('SS', 'SSD', 'South Sudan', '{"hr": "Južni Sudan", "mk": "Јужен Судан"}'),
('ST', 'STP', 'Sao Tome and Principe', '{"hr": "Sveti Toma i Princip", "mk": "Саун Томе и Принсип"}'),
('SV', 'SLV', 'El Salvador', '{"hr": "Salvador", "mk": "Ел Салвадор"}'),
('SX', 'SXM', 'Sint Maarten (Dutch part)', '{"hr": "Sint Maarten", "mk": "Свети Мартин"}'),
('SY', 'SYR', 'Syria', '{"hr": "Sirija", "mk": "Сирија"}'),
('SZ', 'SWZ', 'Eswatini', '{"hr": "Esvatini", "mk": "Свазиленд"}'),
('TC', 'TCA', 'Turks and Caicos Islands', '{"hr": "Otoci Turks i Caicos", "mk": "Острови Туркс и Каикос"}'),
('TD', 'TCD', 'Chad', '{"hr": "Čad", "mk": "Чад"}'),
('TF', 'ATF', 'French Southern Territories', '{"hr": "Francuski Južni Teritoriji", "mk": "Француски Јужни Територии"}'),
('TG', 'TGO', 'Togo', '{"hr": "Togo", "mk": "Того"}'),
('TH', 'THA', 'Thailand', '{"hr": "Tajland", "mk": "Тајланд"}'),
('TJ', 'TJK', 'Tajikistan', '{"hr": "Tadžikistan", "mk": "Таџикистан"}'),
('TK', 'TKL', 'Tokelau', '{"hr": "Tokelau", "mk": "Токелау"}'),
('TL', 'TLS', 'Timor-Leste', '{"hr": "Timor-Leste", "mk": "Тимор Лесте"}'),
('TM', 'TKM', 'Turkmenistan', '{"hr": "Turkmenistan", "mk": "Туркменистан"}'),
('TN', 'TUN', 'Tunisia', '{"hr": "Tunis", "mk": "Тунис"}'),
('TO', 'TON', 'Tonga', '{"hr": "Tonga", "mk": "Тонга"}'),
('TR', 'TUR', 'Türkiye', '{"hr": "Turska", "mk": "Турција"}'),
('TT', 'TTO', 'Trinidad and Tobago', '{"hr": "Trinidad i Tobago", "mk": "Тринидад и Тобаго"}'),
('TV', 'TUV', 'Tuvalu', '{"hr": "Tuvalu", "mk": "Тувалу"}'),
('TW', 'TWN', 'Taiwan', '{"hr": "Tajvan", "mk": "Тајван"}'),
('TZ', 'TZA', 'Tanzania', '{"hr": "Tanzanija", "mk": "Танзанија"}'),
('UA', 'UKR', 'Ukraine', '{"hr": "Ukrajina", "mk": "Украина"}'),
('UG', 'UGA', 'Uganda', '{"hr": "Uganda", "mk": "Уганда"}'),
('UM', 'UMI', 'United States Minor Outlying Islands', '{"hr": "Mali udaljeni otoci SAD-a", "mk": "Американски територии во Пацификот"}'),
('US', 'USA', 'United States', '{"hr": "Sjedinjene Američke Države", "mk": "Соединети Американски Држави"}'),
('UY', 'URY', 'Uruguay', '{"hr": "Urugvaj", "mk": "Уругвај"}'),
('UZ', 'UZB', 'Uzbekistan', '{"hr": "Uzbekistan", "mk": "Узбекистан"}'),
('VA', 'VAT', 'Holy See (Vatican City State)', '{"hr": "Vatikan", "mk": "Ватикан"}'),
('VC', 'VCT', 'Saint Vincent and the Grenadines', '{"hr": "Sveti Vincent i Grenadini", "mk": "Сент Винсент и Гренадини"}'),
('VE', 'VEN', 'Venezuela', '{"hr": "Venezuela", "mk": "Венецуела"}'),
('VG', 'VGB', 'Virgin Islands, British', '{"hr": "Britanski Djevičanski Otoci", "mk": "Британски Девствени Острови"}'),
('VI', 'VIR', 'Virgin Islands, U.S.', '{"hr": "Američki Djevičanski Otoci", "mk": "Американски Девствени Острови"}'),
('VN', 'VNM', 'Vietnam', '{"hr": "Vijetnam", "mk": "Виетнам"}'),
('VU', 'VUT', 'Vanuatu', '{"hr": "Vanuatu", "mk": "Вануату"}'),
('WF', 'WLF', 'Wallis and Futuna', '{"hr": "Wallis i Futuna", "mk": "Валис и Футуна"}'),
('WS', 'WSM', 'Samoa', '{"hr": "Samoa", "mk": "Самоа"}'),
('XK', 'XKX', 'Kosovo', '{"hr": "Kosovo", "mk": "Косово"}'),
('YE', 'YEM', 'Yemen', '{"hr": "Jemen", "mk": "Јемен"}'),
('YT', 'MYT', 'Mayotte', '{"hr": "Mayotte", "mk": "Мајот"}'),
('ZA', 'ZAF', 'South Africa', '{"hr": "Južnoafrička Republika", "mk": "Јужноафриканска Република"}'),
('ZM', 'ZMB', 'Zambia', '{"hr": "Zambija", "mk": "Замбија"}'),
('ZW', 'ZWE', 'Zimbabwe', '{"hr": "Zimbabve", "mk": "Зимбабве"}');

View File

@@ -0,0 +1,10 @@
INSERT INTO sport (name) VALUES
('Running'),
('Darts'),
('Bowling'),
('Table Tennis'),
('Chess'),
('Pikado'),
('Mini Golf'),
('Swimming'),
('Cycling');

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
INSERT INTO event (name, event_date) VALUES
('Union Games', '2026-03-12'),
('Faculty Meetup', '2026-04-25'),
('City Cup', '2026-06-03'),
('Research Forum', '2026-09-18');

View File

@@ -0,0 +1,6 @@
#!/bin/bash
# Docker init env varovi
USER=${POSTGRES_USER:-postgres}
DB=${POSTGRES_DB:-postgres}
pg_isready -U "$USER" -d "$DB"