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

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"