Sequelize.js: jak korzystać z migracji i synchronizacji


137

Jestem bliski ukończenia mojego projektu do uruchomienia. Mam duże plany po uruchomieniu, a struktura bazy danych ulegnie zmianie - nowe kolumny w istniejących tabelach, nowe tabele, nowe skojarzenia z istniejącymi i nowymi modelami.

Nie poruszyłem jeszcze migracji w Sequelize, ponieważ miałem tylko dane testowe, których nie mam nic przeciwko usuwaniu za każdym razem, gdy zmienia się baza danych.

W tym celu obecnie uruchamiam się, sync force: truegdy moja aplikacja się uruchamia, jeśli zmieniłem definicje modelu. Spowoduje to usunięcie wszystkich tabel i utworzenie ich od podstaw. Mógłbym pominąć forceopcję, aby tworzyć tylko nowe tabele. Ale jeśli istniejące uległy zmianie, nie jest to przydatne.

Więc kiedy dodam migracje, jak to działa? Oczywiście nie chcę, aby istniejące tabele (z danymi) zostały usunięte, więc nie sync force: truema mowy. W innych aplikacjach, które pomogłem opracować (Laravel i inne frameworki) w ramach procedury wdrażania aplikacji, uruchamiamy polecenie migrate, aby uruchomić wszelkie oczekujące migracje. Ale w tych aplikacjach pierwsza migracja ma szkieletową bazę danych, z bazą danych w stanie, w którym była na wczesnym etapie rozwoju - pierwsza wersja alfa lub cokolwiek innego. Dzięki temu nawet instancja aplikacji spóźniona na imprezę może przyspieszyć za jednym zamachem, wykonując wszystkie migracje po kolei.

Jak wygenerować taką „pierwszą migrację” w Sequelize? Jeśli go nie mam, nowe wystąpienie aplikacji w pewnym stopniu nie będzie miało szkieletowej bazy danych do uruchomienia migracji lub uruchomi synchronizację na początku i spowoduje, że baza danych będzie w nowym stanie ze wszystkimi nowe tabele itp., ale wtedy, gdy próbuje uruchomić migracje, nie mają one sensu, ponieważ zostały napisane z myślą o oryginalnej bazie danych i każdej kolejnej iteracji.

Mój proces myślowy: na każdym etapie początkowa baza danych plus każda migracja w kolejności powinny odpowiadać (plus lub minus dane) bazie danych wygenerowanej, gdy sync force: trueprowadzony jest. Dzieje się tak, ponieważ opisy modeli w kodzie opisują strukturę bazy danych. Więc może jeśli nie ma tabeli migracji, po prostu uruchamiamy synchronizację i oznaczamy wszystkie migracje jako wykonane, nawet jeśli nie zostały uruchomione. Czy to jest to, co muszę zrobić (jak?), Czy też Sequelize ma to zrobić samodzielnie, czy też szczekam na niewłaściwe drzewo? A jeśli jestem we właściwym obszarze, z pewnością powinien istnieć dobry sposób na automatyczne generowanie większości migracji, biorąc pod uwagę stare modele (przez skrót zatwierdzenia? Lub nawet czy każda migracja może być powiązana z zatwierdzeniem? w nieprzenośnym uniwersum git-centric) i nowe modele. Może różnicować strukturę i generować polecenia potrzebne do przekształcenia bazy danych ze starej na nową iz powrotem, a następnie programista może wejść i wprowadzić wszelkie niezbędne poprawki (usuwanie / przenoszenie określonych danych itp.).

Kiedy uruchamiam plik binarny sequelize za pomocą --initpolecenia, wyświetla mi się pusty katalog migracji. Kiedy następnie uruchamiam sequelize --migrate, staje się tabelą SequelizeMeta bez żadnych innych tabel. Oczywiście, że nie, ponieważ ten plik binarny nie wie, jak załadować moją aplikację i wczytać modele.

Muszę czegoś przegapić.

TLDR: jak skonfigurować moją aplikację i jej migracje, aby można było zaktualizować różne wystąpienia aplikacji na żywo, a także zupełnie nową aplikację bez starszej początkowej bazy danych?


2
Odpowiedziałem na temat twojego przepływu pracy, ale idealnie wszystkie tabele powinny być skonfigurowane przy użyciu migracji. Nawet jeśli używasz syncteraz, idea jest taka, że ​​migracje „generują” całą bazę danych, więc poleganie na szkielecie jest samo w sobie problemem. Na przykład obieg pracy w Ruby on Rails wykorzystuje migracje do wszystkiego i jest całkiem niesamowity, gdy się do tego przyzwyczaisz. Edycja: I tak, zauważyłem, że to pytanie jest dość stare, ale ponieważ nigdy nie było satysfakcjonującej odpowiedzi i ludzie mogą tu przychodzić w poszukiwaniu wskazówek, pomyślałem, że powinienem wnieść swój wkład.
Fernando Cordeiro

Odpowiedzi:


88

Generowanie „pierwszej migracji”

W twoim przypadku najbardziej niezawodnym sposobem jest zrobienie tego prawie ręcznie. Sugerowałbym użycie narzędzia sequelize-cli . Składnia jest raczej prosta:

sequelize init
...
sequelize model:create --name User --attributes first_name:string,last_name:string,bio:text

Spowoduje to utworzenie modelu ORAZ migracji. Następnie ręcznie połącz istniejące modele z wygenerowanymi za pomocą sequelize-cli i zrób to samo z migracjami. Po wykonaniu tej czynności wyczyść bazę danych (jeśli to możliwe) i uruchom

sequelize db:migrate

Spowoduje to utworzenie schematu migracji. Powinieneś to zrobić tylko raz, aby przejść do właściwego procesu tworzenia schematu (bez sync: force, ale z autorytatywnymi migracjami).

Później, gdy trzeba zmienić schemat:

  1. Utwórz migrację: sequelize migration:create
  2. Zapisz i zapisz funkcje w pliku migracji
  3. Zgodnie ze zmianami w pliku migracji, zmień model ręcznie
  4. Biegać sequelize db:migrate

Uruchamianie migracji na produkcji

Oczywiście nie możesz ssh na serwer produkcyjny i przeprowadzać migracji ręcznie. Użyj umzug , agnostycznego narzędzia migracji dla Node.JS, aby wykonać oczekujące migracje przed uruchomieniem aplikacji.

Możesz uzyskać listę oczekujących / jeszcze niewykonanych migracji, na przykład:

umzug.pending().then(function (migrations) {
  // "migrations" will be an Array with the names of
  // pending migrations.
}); 

Następnie wykonaj migracje ( wewnątrz wywołania zwrotnego ). Metoda execute to funkcja ogólnego przeznaczenia, która dla każdej określonej migracji uruchamia odpowiednią funkcję:

umzug.execute({
  migrations: ['some-id', 'some-other-id'],
  method: 'up'
}).then(function (migrations) {
  // "migrations" will be an Array of all executed/reverted migrations.
});

Moja sugestia jest taka, aby zrobić to przed uruchomieniem aplikacji i za każdym razem próbuje obsługiwać trasy. Coś takiego:

umzug.pending().then(function(migrations) {
    // "migrations" will be an Array with the names of
    // pending migrations.
    umzug.execute({
        migrations: migrations,
        method: 'up'
    }).then(function(migrations) {
        // "migrations" will be an Array of all executed/reverted migrations.
        // start the server
        app.listen(3000);
        // do your stuff
    });
});

Nie mogę tego teraz wypróbować, ale na pierwszy rzut oka powinno działać.

UPD kwiecień 2016 r

Po roku nadal przydatny, więc dzielę się moimi aktualnymi wskazówkami. Na razie instaluję sequelize-clipakiet zgodnie z wymaganiami zależności na żywo , a następnie modyfikuję skrypty startowe NPM w package.jsonnastępujący sposób:

...
"scripts": {
  "dev": "grunt && sequelize db:migrate && sequelize db:seed:all && node bin/www",
  "start": "sequelize db:migrate && sequelize db:seed:all && node bin/www"
},
...

Jedyne co muszę zrobić na serwerze produkcyjnym to npm start. To polecenie uruchomi wszystkie migracje, zastosuje wszystkie seedery i uruchomi serwer aplikacji. Nie ma potrzeby ręcznego dzwonienia do umzug.


3
To brzmi jak to, czego szukam. Nie wydaje się tak magiczne i automatyczne, jak „powinno”, ale może jest to najlepsze, na co można mieć nadzieję. Jednak obecnie nie pracuję z Sequelize i w najbliższym czasie nie będę mógł tego przetestować. Ale jeśli ktoś zgodzi się, że to rozwiązanie jest dobre, przyjmuję tę odpowiedź. Nadal uważam za trochę smutne, że wydaje mi się, że nie ma sposobu, aby automatycznie dokonać tych migracji z różnic między wersjami modeli.
tremby

4
@tremby jedynym frameworkiem, którego użyłem, który naprawdę rozumie modele, był Django. Analizuje modele i pyta „Cóż, wygląda na to, że nazwa pola została zmieniona na imię_nazwa w modelu Użytkownik. Czy chcesz utworzyć dla niego migrację?” W Django działa to niemal magicznie, inne narzędzia, z których korzystałem, zakładają to samo podejście do migracji, o którym wspomniałem powyżej: jesteś odpowiedzialny za samodzielne pisanie migracji, dogłębnie rozumiejąc, jakie pole jakiego typu dodać, aby było aktualne w bieżących stanach modelu
f1nn

2
Możesz się go pozbyć, pendinga potem executepo prostu zrobić umzug.up().then(function (migrations) { app.listen(3000); }). Zgodnie z dokumentacją umzug, spowoduje to wykonanie wszystkich oczekujących migracji.
Vinay

Czy po zakończeniu migracji często dodaje się pola do schematu w oryginalnym pliku modelu?
theptrk

@ f1nn Mam pytanie dotyczące Twojej konfiguracji, jak radzisz sobie z grupowaniem aplikacji i dostępnością? Zintegruję pm2 w moim przepływie pracy i może nie działa to bezpośrednio ze skryptami npm.
diosney

17

Sam się tego uczyłem, ale myślę, że poleciłbym teraz korzystanie z migracji, abyś się do nich przyzwyczaił. Odkryłem, że najlepszą rzeczą do ustalenia, co dzieje się podczas migracji, jest przyjrzenie się sql w tabelach utworzonych przez, sequelize.sync()a następnie zbudowanie z niego migracji.

migrations -c [migration name] 

Utworzy plik migracji szablonu w katalogu migracji. Następnie możesz wypełnić go polami, które chcesz utworzyć. Ten plik będzie musiał zawierać createdAt/ updatedAt, pola potrzebne do skojarzeń itp.

Do początkowego tworzenia tabeli dół powinien mieć:

migration.dropTable('MyTable');

Ale kolejne aktualizacje struktury tabeli mogą to pominąć i po prostu użyć alter table.

./node_modules/.bin/sequelize --migrate

Przykładowa kreacja wyglądałaby następująco:

module.exports = {
  up: function(migration, DataTypes, done) {
    migration.createTable(
        'MyTable',
        {
          id: {
            type: DataTypes.INTEGER,
            primaryKey: true,
            autoIncrement: true
          },
          bigString: {type: DataTypes.TEXT, allowNull: false},
          MyOtherTableId: DataTypes.INTEGER,
          createdAt: {
            type: DataTypes.DATE
          },
          updatedAt: {
            type: DataTypes.DATE
          }
        });
    done();
  },
  down: function(migration, DataTypes, done) {
    migration.dropTable('MyTable');
    done();
  }

Aby powtórzyć od początku:

./node_modules/.bin/sequelize --migrate --undo
./node_modules/.bin/sequelize --migrate

Używam kawy do uruchomienia pliku zarodkowego do zapełnienia tabel po:

coffee server/seed.coffee

To po prostu zawiera funkcję tworzenia, która wygląda mniej więcej tak:

user = db.User.create
  username: 'bob'
  password: 'suruncle'
  email: 'bob@bob.com'
.success (user) ->
  console.log 'added user'
  user_id = user.id
  myTable = [
    field1: 'womp'
    field2: 'rat'

    subModel: [
      field1: 'womp'
     ,
      field1: 'rat'
    ]
  ]

Pamiętaj, aby sync()usunąć indeks w swoich modelach lub nadpisze to, co robią migracje i seed.

Oczywiście dokumenty są dostępne pod adresem http://sequelize.readthedocs.org/en/latest/docs/migrations/ . Ale podstawowa odpowiedź brzmi: musisz dodać wszystko w sobie, aby określić potrzebne pola. Nie robi tego za Ciebie.


5
Nie pytałem, jak tworzyć i uruchamiać migracje - jak zauważyłeś, wszystko to jest dostępne w dokumentacji. Pytałem o to, jak ich używać w kontekście aplikacji wielokrotnego użytku, w której istniejące instancje muszą zostać zaktualizowane do nowszej wersji bazy danych, a nowe instancje potrzebują tej bazy danych od podstaw. A może odpowiadając, że mówiąc, że nie powinno być za pomocą sync () w ogóle, a co wstępną bazę danych i wszystkie zmiany do niej w migracji. Czy to właśnie mówisz?
tremby

1
@tremby Myślę, że to właśnie mówi. Możesz użyć synchronizacji i zająć się wynikami lub możesz utworzyć migracje ręcznie. Nasze frameworki, w stylu Railsów, generują pliki migracyjne w oparciu o różnice w schemacie, KOCHAŁbym, gdyby Sequelize zrobiłby to za mnie. Zbyt duży ból, aby wykonywać migracje ręcznie ...
uruchomiono

Szkoda, że ​​nie możesz sequelize.sync()mieć wygenerowanego skryptu, który tworzy wszystkie podstawowe tabele i indeksy podczas pierwszej migracji (podobnie jak w przypadku railsów schema.rb). Po przeczytaniu tego wydaje się, że najlepszym rozwiązaniem może być wyeksportowanie początkowego schematu jako sql, a następnie umieść go w dużej execinstrukcji podczas pierwszej migracji. Następnie wykonujesz przyrostowe zmiany względem znanego punktu początkowego „wersji 1.0”.
thom_nic

11

Na potrzeby programowania istnieje teraz opcja synchronizacji bieżących tabel poprzez zmianę ich struktury. Korzystając z najnowszej wersji z repozytorium sequelize github , możesz teraz uruchomić synchronizację z alterparametrem.

Table.sync({alter: true})

Ostrzeżenie z dokumentacji:

Zmienia tabele, aby pasowały do ​​modeli. Nie zalecane do użytku produkcyjnego. Usuwa dane z kolumn, które zostały usunięte lub których typ został zmieniony w modelu.


3

Teraz z nową sekwencją migracja jest bardzo prosta.

To jest przykład tego, co możesz zrobić.

    'use strict';

    var Promise = require('bluebird'),
        fs = require('fs');

    module.exports = {
        up: function (queryInterface, Sequelize) {

            return Promise
                .resolve()
                .then(function() {
                    return fs.readFileSync(__dirname + '/../initial-db.sql', 'utf-8');
                })
                .then(function (initialSchema) {
                    return queryInterface.sequelize.query(initialSchema);
                })
        },

        down: function (queryInterface, Sequelize) {
            return Promise
                .resolve()
                .then(function() {
                    return fs.readFileSync(__dirname + '/../drop-initial-db.sql', 'utf-8');
                })
                .then(function (dropSql) {
                    return queryInterface.sequelize.query(dropSql);
                });
        }
    };

Pamiętaj, że musisz ustawić:

"dialectOptions": { "multipleStatements": true }

w konfiguracji bazy danych.


Czy to nie po prostu upuszcza i nie tworzy ponownie bazy danych?
TWilly

Myślę, że użycie początkowego dużego pliku sql nie jest zalecanym sposobem, aby to zrobić, ponieważ spowoduje to związanie adaptera i bazy danych, które w przeciwnym razie będą agnostykami bazy danych, ponieważ można użyć do programowania sqlite i do produkcji mariadb lub innych.
diosney

2

Użyj wersji. Wersja aplikacji zależy od wersji bazy danych. Jeśli nowa wersja wymaga aktualizacji bazy danych, utwórz dla niej migrację.

aktualizacja: zdecydowałem się porzucić migrację ( KISS ) i uruchomić skrypt update_db (sync forse: false), gdy jest to potrzebne.


Podobnie jak moja odpowiedź na odpowiedź użytkownika 1916988, czy mówisz, że nie powinienem w ogóle używać sync()i że muszę ręcznie zapisać migracje ze schematu modeli starszej wersji do modeli nowszej wersji?
tremby

Dałem +1 z powodu Twojej aktualizacji. Właściwie myślę o zrobieniu tego samego. Ręczne pisanie wszystkich migracji, gdy aplikacja może to zrobić, jest trochę głupie, więc zrobię po prostu ręczny skrypt, który uruchomi aplikację raz i uruchomi funkcję synchronizacji.
Sallar

2

Trochę późno i po przeczytaniu dokumentacji nie musisz mieć tej pierwszej migracji, o której mówisz. Wystarczy zadzwonić sync, aby stworzyć stoliki.

sequelize.sync()

Możesz również uruchomić prostą synchronizację modelu, wykonując coś takiego:

Project.sync()ale myślę, że sequelize.sync()jest to bardziej użyteczny przypadek ogólny dla twojego projektu (o ile importujesz dobre modele w momencie rozpoczęcia).

(pobrane z http://sequelizejs.com/docs/latest/models#database-synchronization )

Spowoduje to utworzenie wszystkich początkowych struktur. Później będziesz musiał tylko tworzyć migracje, aby rozwijać swoje schematy.

mam nadzieję, że to pomoże.


7
Nie sądzę, żebyś przeczytał oryginalny post zbyt dokładnie, a może nie byłam wystarczająco jasna. Jestem bardziej niż świadomy tego, sequelize.sync()co on robi.
drżenie

2

Sequelize może asynchronicznie uruchamiać dowolny kod SQL .

Co bym zrobił to:

  • Wygeneruj migrację (do użycia jako pierwsza migracja);
  • Zrzuć bazę danych, na przykład: mysql_dump -uUSER -pPASS DBNAME > FILE.SQL
  • Wklej pełny zrzut jako tekst (niebezpieczny) lub załaduj plik z pełnym zrzutem w Node:
    • var baseSQL = "LOTS OF SQL and it's EVIL because you gotta put \ backslashes before line breakes and \"quotes\" and/or sum" + " one string for each line, or everything will break";
    • var baseSQL = fs.readFileSync('../seed/baseDump.sql');
  • Uruchom ten zrzut w migracji Sequelize:
module.exports = {
  up: function (migration, DataTypes) {
    var baseSQL = "whatever" // I recommend loading a file
    migration.migrator.sequelize.query(baseSQL);
  }
}

To powinno zająć się konfiguracją bazy danych, chociaż asynchronizacja może stać się problemem. Gdyby tak się stało, upszukałbym sposobu na odroczenie zwracania funkcji sequelize do momentu zakończenia queryfunkcji async .

Więcej o mysql_dump: http://dev.mysql.com/doc/refman/5.1/en/mysqldump.html
Więcej o Sequelize Migrations: http://sequelize.readthedocs.org/en/latest/docs/migrations/
Więcej informacji Uruchamianie SQL z poziomu migracji Sequelize: https://github.com/sequelize/sequelize/issues/313


1

Oto mój obecny przepływ pracy. Jestem otwarty na sugestie.

  1. Ustaw sequelize, aby stworzyć tabele, które nie istnieją
  2. Ustaw sekwencjonowanie, aby usunąć i ponownie utworzyć wszystkie tabele w pustej bazie danych o nazwie _blank
  3. Użyj narzędzia mysql, aby porównać _blank i zsynchronizować zmiany za pomocą tego narzędzia. Wciąż szukam niedrogiego narzędzia, które może to zrobić na komputerze Mac. Środowisko pracy MySql wygląda na to, że możesz zaimportować model z istniejącego schematu, a następnie zsynchronizować schemat. Próbuję dowiedzieć się, jak to zrobić za pomocą wiersza poleceń, aby było to łatwe.

W ten sposób nie musisz ręcznie aktualizować tabeli migracji i martwić się o grube palce, ale nadal masz ORM.


1

Znajomy miałem to samo pytanie i udało mi się zrozumieć, jak z nich korzystać.

Zacząłem bez sequelize ORM, więc miałem już model danych.
Musiałem wygenerować modele automatycznie za pomocą sequelize-auto i wygenerować ich migracje za pomocą tego pliku, który tworzysz https://gist.github.com/ahelord/a7a7d293695b71aadf04157f0f7dee64 i zsynchronizować ( {Force: false})
To jest w dev. model i migracje i wykonuję je za każdym razem, gdy ściągam kod.

W wersji produkcyjnej serwer znajduje się tylko na piętrze, więc musisz tylko uruchamiać migracje i zarządzać każdym zatwierdzeniem, ponieważ będziesz wersjonować model bez zatrzymywania zaplecza


1

Przejrzałem ten post i podobne pytania, ale tak naprawdę nie odpowiedziałem. Migracje są przydatne do uruchamiania lokalnych baz danych i aktualizowania danych w środowisku produkcyjnym

Zadałem tutaj pytanie i również na nie odpowiedziałem: Przepływ pracy dla obsługi migracji sequelize i inicjalizacji?

Wersja TL-DR dla projektu greenfield

  1. Zaprojektuj schemat bazy danych w taki sam sposób, jak tradycyjnie przy użyciu czystych skryptów SQL lub jeśli zamiast tego używasz narzędzia GUI
  2. Kiedy sfinalizujesz wszystkie 95% schematu db i jesteś z niego zadowolony, przejdź dalej i przenieś go do sekwencjonowania, przenosząc cały .sqlplik
  3. Wykonaj swoją pierwszą migrację. Uruchom sequelize init:migratew dowolnym folderze, w którym się modelsznajdujesz
  4. Utwórz swój pierwszy plik migracji. Biegaćsequelize migration:generate --name [name_of_your_migration]
  5. W tym pliku migracji umieść tam ten kod
("use strict");
/**
 * DROP SCHEMA public CASCADE; CREATE SCHEMA public
 * ^ there's a schema file with all the tables in there. it drops all of that, recreates
 */
const fs = require("fs");
const initialSqlScript = fs.readFileSync("./migrations/sql/Production001.sql", {
  encoding: "utf-8",
});
const db = require("../models");
module.exports = {
  up: () => db.sequelize.query(initialSqlScript),
  down: () =>
    db.sequelize.query(`DROP SCHEMA public CASCADE; CREATE SCHEMA public;
`),
};

wprowadź opis obrazu tutaj

z tą ogólną strukturą folderów

wprowadź opis obrazu tutaj

  1. Teraz Twoja konfiguracja sequelize jest zsynchronizowana z początkowym schematem bazy danych
  2. jeśli chcesz edytować schemat bazy danych, uruchom to ponownie sequelize migration:generate --name [name_of_your_migration]
  3. Śmiało i wprowadź modyfikacje tutaj na ścieżkach migracji upi down. To są twoje instrukcje ALTER do zmiany nazw kolumn, DELETE, ADD kolumny itp
  4. Biegać sequelize db:migrate
  5. Chcesz, aby modele były synchronizowane ze zmianami w zdalnej bazie danych, więc możesz teraz zrobić npm install sequelize-auto.
  6. Spowoduje to odczytanie bieżącego schematu bazy danych w bazie danych i automatyczne wygenerowanie plików modelu. Użyj polecenia podobnego do tego, sequelize-auto -o "./models" -d sequelize_auto_test -h localhost -u my_username -p 5432 -x my_password -e postgresktóre można znaleźć pod adresem https://github.com/sequelize/sequelize-auto

Możesz użyć git, aby zobaczyć różnice w swoim modelu, powinny być tylko zmiany odzwierciedlające zmiany w modelu bazy danych. Na marginesie, nigdy nie modyfikuj modelsbezpośrednio, jeśli używasz sequelize auto, ponieważ wygeneruje to je dla Ciebie. Podobnie, nie powinieneś już modyfikować schematu bazy danych bezpośrednio za pomocą plików SQL, zakładając, że jest to opcja, ponieważ możesz je importować.sql pliki

Teraz schemat Twojej bazy danych jest aktualny i oficjalnie przeniosłeś się na sekwencjonowanie tylko migracji bazy danych.

Wszystko podlega kontroli wersji. Jest to idealny przepływ pracy dla programistów baz danych i zaplecza


0

Jest jeszcze prostszy sposób (unikanie Sequalize). Co wygląda tak:

  1. Wpisujesz polecenie wewnątrz projektu: npm run migrate: new

  2. Tworzy to 3 pliki. Plik js i dwa pliki sql o nazwach w górę iw dół

  3. W tych plikach umieszczasz instrukcję SQL, która jest czystym sql
  4. Następnie wpisz: npm run migrate: up lub npm run migrate: down

Aby to zadziałało, spójrz na moduł db-migrate .

Po skonfigurowaniu (co nie jest trudne), zmiana bazy danych jest naprawdę łatwa i oszczędza dużo czasu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.