Kotlin, Spring Boot a Heroku – Databáza (3/4)

Predošlá časť - Vytvorenie API Nasledujúca časť - Heroku

Niekde v internete bude naša databáza, do ktorej budeme ukladať naše dáta. Alebo ich z nej aj načítavať. Databáza má tabuľky. Kedysi sa ešte reálne písali SQL dotazy na získanie týchto dát, ale dnes už na to používame ORM – jednoducho objekty v spojení s niečim, čo ich vie transformovať na SQL príkazy na pozadí (bez toho, aby sme o tom museli nejak extra veľa vedieť).

Poznámka: Okej, nie vždy sa využíva ORM. Má aj svoje nevýhody. Napr. môže byť trochu pomalšie ako SQL príkazy.

Spring Boot na ORM má Spring Data JPA (Java Persistance API) a Hibernate. Na to, aby sme ich mohli využívať musíme najprv pridať dependency (package, libku, akokoľvek to chceme volať) do Gradlu – upravením súboru

build.gradle
build.gradle. Pod riadok
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-web") vložme:

implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")

Poznámka: Pokiaľ riadok pridáme do dependencies, tak nezáleží, na ktoré miesto ho dáme. Pokiaľ viem, tak na poradí dependencies nezáleží. Daj len pozor, aby si to pridal/a do

dependencies
dependencies bloku.

Zároveň budeme chcieť náš kód spúšťať aj bez toho, aby sme si museli nejaku databázu niekde vytvárať (aspoň zatiaľ). Na to si pridáme ďalšiu dependency:

implementation("com.h2database:h2")
implementation("com.h2database:h2")
implementation("com.h2database:h2")

Týmto pridáme závislosť na H2 databázu, ktorá vie pracovať in-memory alebo z lokálneho súboru. To je užitočné hlavne na testovanie, aby sme sa nemuseli stále pripájať niekam ďaleko (a hlavne, aby sme databázu zatiaľ nemuseli setupovať).

Ak používaš IDEA, tak potrebuješ “syncnúť” Gradle (napravo hore v editore by si mal/a vidieť ikonku sloníka s refresh šípkami – to je ono! Klikni na to!). Ak si v inom editore, tak ti to netreba.

Ako syncnúť Gradle v IDEA
Ako syncnúť Gradle v IDEA

Konfigurácia databázy

Pred tým ako prejdeme na všetko kódenie si ešte musíme nakonfigurovať, ako naša appka bude komunikovať s databázou. Takéto veci sa konfigurujú v súbore

src/main/resources/application.properties
src/main/resources/application.properties (Čo to vlastne sú tie application.properties?). Spring má preddefinované nejaké properties, ktoré na pripojenie k databáze očakáva a pri štarte aplikácie ich načítava. Náš
application.properties
application.properties súbor bude vyzerať takto:

spring.datasource.url = jdbc:h2:file:./data/passwords
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=Password123+
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url = jdbc:h2:file:./data/passwords spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=Password123+ spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=update
spring.datasource.url = jdbc:h2:file:./data/passwords
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=Password123+
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update

Podľa názvu sa dá celkom pekne odvodiť, čo asi robia, ale poďme si niektoré (alebo všetky) aj tak prejsť.

spring.datasource.url
spring.datasource.url Spring-u hovorí, kde našu databázu nájde. Na testovanie chceme využívať lokálnu databázu, ktorá bude uložená v zložke
data/passwords
data/passwords.
spring.datasource.driverClassName
spring.datasource.driverClassName je názov driveru, ktorý chceme pre databázu používať. Driver je proste niečo, pomocou čoho pristupujeme k databáze – netreba to príliš riešiť. Väčšina známych databáz má svoj driver (MySQL driver, PostgreSQL driver, …).

Tento driver sa nachádza práve v H2 dependency (

com.h2database:h2
com.h2database:h2), ktorú sme pridali vyššie. Podobným spôsobom neskôr pridáme a nakonfigurujeme PostgreSQL driver.
spring.datasource.username
spring.datasource.username a
spring.datasource.password
spring.datasource.passwordsú asi jasné.
spring.jpa.database-platform
spring.jpa.database-platform súvisi opäť s výberom databázy – neskôr tam budeme mať PostgreSQL.
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.ddl-auto=update nám zabezpečí, že Spring nám databázu automaticky vytvorí. What?

Yup, Spring Boot nám celú databázu automaticky vytvorí podľa našich modelov. Hneď sa k tomu dostaneme.

Ako to s tou databázou teda funguje?

Najprv rýchly prehľad a potom prejdeme k reálnemu kódeniu (alebo ku konfigurácii).

V minulosti by to bolo hlavne o kódení. Teraz nám stačí málo kódu a trošku konfigurácie (niekedy rozmýšľam, či som programátor, alebo skôr konfigurátor). Spring Boot nám vie sám vytvoriť celú databázu aj s tabuľkami. Ako? Tak, že mu musíme povedať, ktoré classy má pretransformovať na tabuľky. Tieto classy sa voľajú modely.

Keď už má tie tabuľky a vie, ktoré modely (classy) k ním prislúchajú, tak potrebujeme niečo, čo nám bude vedieť modely ukladať do databázy – tu prichádza na scénu

Repository
Repository.
Repository
Repository je generický interface, ktorý obsahuje metódky na základné CRUD operácie nad nejakou triedou. Je sprostredkovaná Spring-om.

To znamená, že keď si vytvoríme

Repository<PasswordModel>
Repository<PasswordModel>, tak budeme vedieť do databázy vkladať
PasswordModel
PasswordModel-y, budeme ich vedieť získavať a tak ďalej. A to všetko bez toho, aby sme písali nejak veľa kódu. Mega vec.

Takže už máme

Controller
Controller, budeme mať
Model
Modela
Repository
Repository. Ešte nám k tomu chýba jedna vec, ktorá nám prepojí
Controller
Controller s
Repository
Repository. Prečo nemôžeme prepojiť
Controller
Controller priamo s
Repository
Repository? Mohli by sme, ale chceme sa naučiť písať udržiavateľný kód a tak chceme mať veci pekne oddelené. Toto prepojenie medzi
Controller
Controller-om a
Repository
Repository si nazveme
Service
Service.

Mimochodom, všetky názvy, ktoré používam nie sú vymyslené mnou, ale sú zaužívané v Spring Boot-e, takže sa s nimi stretneš skoro v každom projekte. Podobné (ak nie rovnaké) názvy sú zaužívané aj v ASP.NET v C# a všeobecne v MVC API frameworkoch.

Poďme si všetky tieto classy teda povytvárať.

PasswordModel

Model všeobecne označuje triedy, ktoré nám definujú nejaké dáta. Náš model bude vyzerať takto:

package sk.streetofcode.passwords
class PasswordModel(var username: String, var password: String, var url: String)
package sk.streetofcode.passwords class PasswordModel(var username: String, var password: String, var url: String)
package sk.streetofcode.passwords

class PasswordModel(var username: String, var password: String, var url: String)

Krásne. Žiadne Javovské gettery a settery. Kotlin je top. Máme náš model. Spring Boot ešte ale nevie, že z neho má robiť tabuľky. Ako mu to povieme? Anotácie FTW. Konkrétne použijeme

@Entity
@Entity anotáciu:

package sk.streetofcode.passwords
import javax.persistence.Entity
@Entity
class PasswordModel(var username: String, var password: String, var url: String)
package sk.streetofcode.passwords import javax.persistence.Entity @Entity class PasswordModel(var username: String, var password: String, var url: String)
package sk.streetofcode.passwords

import javax.persistence.Entity

@Entity
class PasswordModel(var username: String, var password: String, var url: String)

Názov tabuľky, ktorú ma Spring vytvoriť zadefinujeme pomocou ďalšej anotácie –

@Table
@Table:

package sk.streetofcode.passwords
import javax.persistence.Entity
import javax.persistence.Table
@Entity
@Table(name = "passwords")
class PasswordModel(var username: String, var password: String, var url: String)
package sk.streetofcode.passwords import javax.persistence.Entity import javax.persistence.Table @Entity @Table(name = "passwords") class PasswordModel(var username: String, var password: String, var url: String)
package sk.streetofcode.passwords

import javax.persistence.Entity
import javax.persistence.Table

@Entity
@Table(name = "passwords")
class PasswordModel(var username: String, var password: String, var url: String)

Keďže už z toho budeme mať databázovú tabuľku, tak by sme mali pre naše heslá mať aj nejaké ID. Upravme našu triedu na:

package sk.streetofcode.passwords
import javax.persistence.*
@Entity
@Table(name = "passwords")
class PasswordModel(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
var id: Long? = null,
var username: String,
var password: String,
var url: String
) {
constructor() : this(null, "", "", "")
}
package sk.streetofcode.passwords import javax.persistence.* @Entity @Table(name = "passwords") class PasswordModel( @Id @GeneratedValue(strategy = GenerationType.AUTO) var id: Long? = null, var username: String, var password: String, var url: String ) { constructor() : this(null, "", "", "") }
package sk.streetofcode.passwords

import javax.persistence.*

@Entity
@Table(name = "passwords")
class PasswordModel(
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  var id: Long? = null,

  var username: String,
  var password: String,
  var url: String
) {
  constructor() : this(null, "", "", "")
}

Trochu som upravil aj formátovanie, nech sa na to lepšie pozerá.

@Id
@Id anotácia springu povie, že tento atribút je ID a
@GeneratedValue
@GeneratedValue anotácia mu povie, akým spôsobom sa táto hodnota bude generovať. AUTO znamená, že sa prispôsobí použitej databáze. V našom prípade bude generovať IDčka od 1 postupne vyššie.
id
id je
null
null-ovateľné, aby sme ho nemuseli nastavovať pri vytvárani objektov tejto triedy –
id
id sa nastaví pri vkladaní do databázy. PasswordModel musí mať aj defaultný konštruktor (bez parametrov), aby Spring vedel objekt vytvoriť (teoreticky sa dá použiť aj tento gradle plugin, ale prišlo mi jednoduchšie tam proste dať ten konštruktor).

To je všetko. Máme model.

PasswordRepository

Ako som už povedal,

Repository
Repository potrebujeme na to, aby sme vedeli model jednoducho ukladať do/získavať z databázy. Nejdem to naťahovať a rovno ukážem celú našu repozitory:

package sk.streetofcode.passwords
import org.springframework.data.repository.CrudRepository
import org.springframework.stereotype.Repository
@Repository
interface PasswordRepository: CrudRepository<PasswordModel, Long> {
}
package sk.streetofcode.passwords import org.springframework.data.repository.CrudRepository import org.springframework.stereotype.Repository @Repository interface PasswordRepository: CrudRepository<PasswordModel, Long> { }
package sk.streetofcode.passwords

import org.springframework.data.repository.CrudRepository
import org.springframework.stereotype.Repository

@Repository
interface PasswordRepository: CrudRepository<PasswordModel, Long> {
}

@Repository
@Repository anotácia povie Spring-u, že táto classa je repository. Dedíme od
CrudRepository
CrudRepository classy, ktorá obsahuje metódy na všetky CRUD operácie. Prvý generický typ definuje model, ktorý budeme upravovať (náš
PasswordModel
PasswordModel) a druhý generický typ (
Long
Long) definuje typ ID-čka daného modelu. Simple enough. Už to skoro máme.

PasswordService

Už iba to prepojenie medzi

Controller
Controller-om a
Repository
Repository. Táto trieda bude mať anotáciu
@Service
@Service. V skratke to znamená, že daná trieda sa bude dať injectnúť do
Controller
Controller-a (a vôbec hocikam). Detaily teraz neriešme. Okrem toho, že táto trieda sa bude dať injectnút, budeme aj DO tejto triedy injectovať. A to konkrétne našu
PasswordRepository
PasswordRepository. Na začiatku bude teda náš
Service
Service vyzerať takto:

@Service
class PasswordService(var passwordRepository: PasswordRepository)
@Service class PasswordService(var passwordRepository: PasswordRepository)
@Service
class PasswordService(var passwordRepository: PasswordRepository)

V našom kóde

PasswordService
PasswordService nikdy nebudeme vytvárať (volať konštruktor). Budeme ho iba injectovať do nášho Controller-u. Spring je taký inteligentný, že pri injectovaní si všimne, že
PasswordService
PasswordService zas potrebuje
PasswordRepository
PasswordRepository a tak mu tam aj tú injectne. Magic.

Poznámka: Dependency injection je koncept, ktorý využiješ nie len pri API projektoch, tak sa oplatí si o tom niečo naštudovať. Väčšina webových frameworkov má nejakým spôsobom DI implementovanú. Prípadne ešte odporúčam prečítať si niečo o IoC – inversion of control.

Okej, náš

Service
Service ale zatiaľ nič nerobí. Pridajme mu metódy na získanie všetkých
PasswordModel
PasswordModel-ov a na pridanie nového
PasswordModel
PasswordModel-u:

package sk.streetofcode.passwords
import org.springframework.stereotype.Service
@Service
class PasswordService(var passwordRepository: PasswordRepository) {
fun getAll(): List<PasswordModel> {
return passwordRepository.findAll().toList()
}
fun add(username: String, password: String, url: String): PasswordModel {
val passwordModel = PasswordModel(null, username, password, url)
return passwordRepository.save(passwordModel)
}
}
package sk.streetofcode.passwords import org.springframework.stereotype.Service @Service class PasswordService(var passwordRepository: PasswordRepository) { fun getAll(): List<PasswordModel> { return passwordRepository.findAll().toList() } fun add(username: String, password: String, url: String): PasswordModel { val passwordModel = PasswordModel(null, username, password, url) return passwordRepository.save(passwordModel) } }
package sk.streetofcode.passwords

import org.springframework.stereotype.Service

@Service
class PasswordService(var passwordRepository: PasswordRepository) {
  fun getAll(): List<PasswordModel> {
    return passwordRepository.findAll().toList()
  }

  fun add(username: String, password: String, url: String): PasswordModel {
    val passwordModel = PasswordModel(null, username, password, url)
    return passwordRepository.save(passwordModel)
  }
}

Trochu si našu triedu skotlinujme:

package sk.streetofcode.passwords
import org.springframework.stereotype.Service
@Service
class PasswordService(var passwordRepository: PasswordRepository) {
fun getAll() = passwordRepository.findAll().toList()
fun add(username: String, password: String, url: String) =
passwordRepository.save(PasswordModel(null, username, password, url))
}
package sk.streetofcode.passwords import org.springframework.stereotype.Service @Service class PasswordService(var passwordRepository: PasswordRepository) { fun getAll() = passwordRepository.findAll().toList() fun add(username: String, password: String, url: String) = passwordRepository.save(PasswordModel(null, username, password, url)) }
package sk.streetofcode.passwords

import org.springframework.stereotype.Service

@Service
class PasswordService(var passwordRepository: PasswordRepository) {
  fun getAll() = passwordRepository.findAll().toList()

  fun add(username: String, password: String, url: String) =
    passwordRepository.save(PasswordModel(null, username, password, url))
}

Cool. Funkcia

getAll
getAll vracia
List
List všetkých
PasswordModel
PasswordModel-ov, ktoré máme v databáze. Funkcia
add
add nám vráti model, ktorý vytvorila a uložila do databázy. Taká je konvencia.
add
add funkciu by som za normálnych okolností napísal asi normálne so zátvorkami a
return
return-om, ale tak prečo neukázať, čo Kotlin dokáže? (aj tak ju neskôr budeme musieť rozpísať)

PasswordController

Teraz máme konečne všetko pripravené na to, aby sme iba upravili náš

Controller
Controller a aby sme mohli získať veci z našej databázy. Do nášho
Controller
Controller-a musíme injectnúť
PasswordService
PasswordService – na to nám stačí ho pridať ako property tejto triedy – Spring si domyslí. A už iba upravíme našu get metódku, aby vrátila dáta z
PasswordService
PasswordService-u (opäť použijeme kotlinovskú skrátenú syntax):

package sk.streetofcode.passwords
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/passwords")
class PasswordController(
var passwordService: PasswordService,
private val passwords: MutableList<String> = mutableListOf()
) {
@GetMapping("")
fun get() = passwordService.getAll()
@PostMapping("")
fun post(@RequestBody password: String) {
passwords.add(password)
}
}
package sk.streetofcode.passwords import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/passwords") class PasswordController( var passwordService: PasswordService, private val passwords: MutableList<String> = mutableListOf() ) { @GetMapping("") fun get() = passwordService.getAll() @PostMapping("") fun post(@RequestBody password: String) { passwords.add(password) } }
package sk.streetofcode.passwords

import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/passwords")
class PasswordController(
  var passwordService: PasswordService,
  private val passwords: MutableList<String> = mutableListOf()
) {
  @GetMapping("")
  fun get() = passwordService.getAll()

  @PostMapping("")
  fun post(@RequestBody password: String) {
    passwords.add(password)
  }
}

Zároveň upravme aj našu

post
post metódu na uloženie nového hesla:

package sk.streetofcode.passwords
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/passwords")
class PasswordController(
var passwordService: PasswordService,
private val passwords: MutableList<String> = mutableListOf()
) {
@GetMapping("")
fun get() = passwordService.getAll()
@PostMapping("")
fun post(username: String, password: String, url: String) =
passwordService.add(username, password, url)
}
package sk.streetofcode.passwords import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/passwords") class PasswordController( var passwordService: PasswordService, private val passwords: MutableList<String> = mutableListOf() ) { @GetMapping("") fun get() = passwordService.getAll() @PostMapping("") fun post(username: String, password: String, url: String) = passwordService.add(username, password, url) }
package sk.streetofcode.passwords

import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/passwords")
class PasswordController(
  var passwordService: PasswordService,
  private val passwords: MutableList<String> = mutableListOf()
) {
  @GetMapping("")
  fun get() = passwordService.getAll()

  @PostMapping("")
  fun post(username: String, password: String, url: String) = 
    passwordService.add(username, password, url)
}

Hm. Spring ale nevie, odkiaľ má zobrať parametre našej

add
add funkcie. Mohli by sme napríklad pred každý parameter pridať
@RequestParam
@RequestParam anotáciu a vtedy by sme request volali nasledovne:

POST http://localhost:8080/password/add?username=habla&password=bubla&url=hablabubla
POST http://localhost:8080/password/add?username=habla&password=bubla&url=hablabubla
POST http://localhost:8080/password/add?username=habla&password=bubla&url=hablabubla

Ale keďže robíme POST request, tak je zaužívaná konvencia, že naše parametre pošleme ako JSON v tele (body) requestu. What? Neriešme detaily, pozrime sa radšej, ako to v Spring-u spraviť.

Najprv si vytvoríme objekt, ktorý sa bude v tele requestu posielať (ja som si ho dal do samostatného súboru):

package sk.streetofcode.passwords
data class AddPasswordRequest(
val username: String,
val password: String,
val url: String
)
package sk.streetofcode.passwords data class AddPasswordRequest( val username: String, val password: String, val url: String )
package sk.streetofcode.passwords

data class AddPasswordRequest(
  val username: String,
  val password: String,
  val url: String
)

A teraz môžeme dokončiť náš

PasswordController
PasswordController:

package sk.streetofcode.passwords
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/passwords")
class PasswordController(
var passwordService: PasswordService,
private val passwords: MutableList<String> = mutableListOf()
) {
@GetMapping("")
fun get() = passwordService.getAll()
@PostMapping("")
fun add(@RequestBody request: AddPasswordRequest) =
passwordService.add(request)
}
package sk.streetofcode.passwords import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/passwords") class PasswordController( var passwordService: PasswordService, private val passwords: MutableList<String> = mutableListOf() ) { @GetMapping("") fun get() = passwordService.getAll() @PostMapping("") fun add(@RequestBody request: AddPasswordRequest) = passwordService.add(request) }
package sk.streetofcode.passwords

import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/passwords")
class PasswordController(
  var passwordService: PasswordService,
  private val passwords: MutableList<String> = mutableListOf()
) {
  @GetMapping("")
  fun get() = passwordService.getAll()

  @PostMapping("")
  fun add(@RequestBody request: AddPasswordRequest) =
    passwordService.add(request)
}

Žial, musíme upraviť aj našu

PasswordService.add
PasswordService.add metódu, aby brala
AddPasswordRequest
AddPasswordRequest ako parameter:

package sk.streetofcode.passwords
import org.springframework.stereotype.Service
@Service
class PasswordService(var passwordRepository: PasswordRepository) {
fun getAll() = passwordRepository.findAll().toList()
fun add(request: AddPasswordRequest): PasswordModel {
val (username, password, url) = request
return passwordRepository.save(PasswordModel(null, username, password, url))
}
}
package sk.streetofcode.passwords import org.springframework.stereotype.Service @Service class PasswordService(var passwordRepository: PasswordRepository) { fun getAll() = passwordRepository.findAll().toList() fun add(request: AddPasswordRequest): PasswordModel { val (username, password, url) = request return passwordRepository.save(PasswordModel(null, username, password, url)) } }
package sk.streetofcode.passwords

import org.springframework.stereotype.Service

@Service
class PasswordService(var passwordRepository: PasswordRepository) {
    fun getAll() = passwordRepository.findAll().toList()

    fun add(request: AddPasswordRequest): PasswordModel {
        val (username, password, url) = request
        return passwordRepository.save(PasswordModel(null, username, password, url))
    }
}

Spring teraz vie, že keď mu príde POST request na

/passwords
/passwords, tak má očakávať v tele do JSON-u zoserializovaný objekt
AddPasswordRequest
AddPasswordRequest. Keď tento request príde, tak sa ho Spring pokúsi deserializovať, aby sme my už dostali objekt.

Poznámka: JSON je predvolený formát, takže nemusíme nič nastavovať.

Príde ti divný tento riadok –

val (username, password, url) = request
val (username, password, url) = request? Nebudem zbytočne opisovať čo to znamená, keď už je to pekne opísané v dokumentácii.

Ouk. Aplikáciu si môžme spustiť a otestovať. Keď sa nám aplikácia spustila, navigujme sa v browseri (alebo zavolajme request z Postman-a) na: http://localhost:8080/password

Mali by sme dostať prázdny JSON (prázdne pole). To preto, že sme opäť zatiaľ nepridali žiadne heslo. Skúsme ho teda pridať cez Postman-a pomocou nášho

POST /passwords
POST /passwords endpointu:

POST na /passwords
POST na /passwords

 

Keď opäť skúsime pozrieť na http://localhost:8080/password, tak si všimeneme, že už tam nejaké to heslo máme. Magic.

Screenshot výsledku GET requestu
GET /passwords

Tým, že H2 databázu máme nakonfigurovanú do súboru, tak by sa nám mali naše heslá uchovávať aj cez reštarty aplikácie. Keby používame in-memory variantu H2 databázy, tak sa databáza pri vypnutí aplikácie proste stratí, pretože sa vymaže všetka pamäť (RAM) nášho procesu.

Oukej, tým by sme mali vytvorenú našu základnú appku, ktorú sa následne pokúsime deploynúť do Heroku spolu s databázou.

Kód z tejto časti tutoriálu nájdeš na našom GitHub-e.

Predošlá časť - Vytvorenie API Nasledujúca časť - Heroku


Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *

Prihlás sa na odber nášho newslettra