Witaj w końcowej fazie naszej przygody w Parku Jurajskim! W tym ćwiczeniu połączymy wszystkie poznane dotychczas techniki, aby stworzyć kompletną aplikację do monitorowania i zarządzania ekosystemem Parku Jurajskiego. To nasz "wielki finał" przed przejściem do strukturyzacji większych projektów.
Stworzymy aplikację "JurassicTracker" - system do monitorowania, śledzenia i zarządzania dinozaurami w parku. Nasza aplikacja będzie wykorzystywać:
Zanim przejdziemy do implementacji, zaplanujmy strukturę naszego projektu:
1jurassic-tracker/
2├── index.js # Główny plik aplikacji
3├── modules/
4│ ├── api.js # Moduł komunikacji z API
5│ ├── dinosaurs.js # Moduł zarządzania dinozaurami
6│ ├── enclosures.js # Moduł zarządzania wybiegami
7│ ├── security.js # Moduł systemów bezpieczeństwa
8│ ├── monitoring.js # Moduł monitorowania parku
9│ ├── utils.js # Narzędzia pomocnicze
10│ └── errors/ # Katalog z klasami błędów
11│ ├── index.js # Eksportuje wszystkie błędy
12│ ├── APIError.js # Błędy związane z API
13│ ├── DinoError.js # Błędy związane z dinozaurami
14│ └── SecurityError.js # Błędy związane z bezpieczeństwem
15└── data/
16 └── config.js # Konfiguracja aplikacjiZacznijmy od zaimplementowania każdego z modułów. Pamiętaj, że w praktyce te pliki znalazłyby się w oddzielnych plikach zgodnie z powyższą strukturą.
Najpierw zdefiniujmy nasze niestandardowe klasy błędów:
1// errors/APIError.js
2export class APIError extends Error {
3 constructor(message, statusCode, endpoint) {
4 super(message);
5 this.name = 'APIError';
6 this.statusCode = statusCode;
7 this.endpoint = endpoint;
8 this.timestamp = new Date();
9 }
10
11 isServerError() {
12 return this.statusCode >= 500;
13 }
14
15 isClientError() {
16 return this.statusCode >= 400 && this.statusCode < 500;
17 }
18}
19
20// errors/DinoError.js
21export class DinoError extends Error {
22 constructor(message, dinoId, species) {
23 super(message);
24 this.name = 'DinoError';
25 this.dinoId = dinoId;
26 this.species = species;
27 this.timestamp = new Date();
28 }
29}
30
31export class DinoNotFoundError extends DinoError {
32 constructor(dinoId) {
33 super(`Dinozaur o ID ${dinoId} nie został znaleziony`, dinoId, null);
34 this.name = 'DinoNotFoundError';
35 }
36}
37
38export class DinoBehaviorError extends DinoError {
39 constructor(message, dinoId, species, behaviorType, dangerLevel) {
40 super(message, dinoId, species);
41 this.name = 'DinoBehaviorError';
42 this.behaviorType = behaviorType;
43 this.dangerLevel = dangerLevel; // 1-10
44 }
45
46 requiresIntervention() {
47 return this.dangerLevel >= 7;
48 }
49}
50
51// errors/SecurityError.js
52export class SecurityError extends Error {
53 constructor(message, sector, systemType) {
54 super(message);
55 this.name = 'SecurityError';
56 this.sector = sector;
57 this.systemType = systemType;
58 this.timestamp = new Date();
59 }
60}
61
62export class EnclosureBreachError extends SecurityError {
63 constructor(sector, dinoSpecies, count) {
64 super(`Naruszenie wybiegu w sektorze ${sector}: ${count} okazów ${dinoSpecies}`, sector, 'enclosure');
65 this.name = 'EnclosureBreachError';
66 this.dinoSpecies = dinoSpecies;
67 this.count = count;
68 }
69}
70
71// errors/index.js
72export * from './APIError.js';
73export * from './DinoError.js';
74export * from './SecurityError.js';Nasz moduł API będzie odpowiedzialny za komunikację z (hipotetycznym) serwerem Parku Jurajskiego:
1// modules/api.js
2import { APIError } from '../errors/index.js';
3
4// Symulacja bazy danych (w rzeczywistości byłoby to API)
5const API_DELAY = 500; // ms - symulacja opóźnienia sieciowego
6const API_ERROR_RATE = 0.1; // 10% szans na błąd API dla realizmu
7
8// Symulowane dane dinozaurów
9let dinosaursData = [
10 { id: 'trx1', name: 'Rex', species: 'Tyrannosaurus', enclosureId: 'enc1', health: 98, lastFed: new Date(Date.now() - 3600000), behavior: 'calm' },
11 { id: 'rap1', name: 'Blue', species: 'Velociraptor', enclosureId: 'enc2', health: 95, lastFed: new Date(Date.now() - 7200000), behavior: 'agitated' },
12 { id: 'rap2', name: 'Delta', species: 'Velociraptor', enclosureId: 'enc2', health: 92, lastFed: new Date(Date.now() - 7200000), behavior: 'hunting' },
13 { id: 'tri1', name: 'Sarah', species: 'Triceratops', enclosureId: 'enc3', health: 88, lastFed: new Date(Date.now() - 1800000), behavior: 'sleeping' },
14 { id: 'bro1', name: 'Bronty', species: 'Brontosaurus', enclosureId: 'enc4', health: 100, lastFed: new Date(Date.now() - 900000), behavior: 'grazing' },
15];
16
17// Symulowane dane wybiegów
18let enclosuresData = [
19 { id: 'enc1', name: 'T-Rex Paddock', sector: 'A1', powerStatus: 'active', securityLevel: 10, lastInspection: new Date(Date.now() - 86400000) },
20 { id: 'enc2', name: 'Raptor Containment', sector: 'B2', powerStatus: 'active', securityLevel: 9, lastInspection: new Date(Date.now() - 129600000) },
21 { id: 'enc3', name: 'Triceratops Territory', sector: 'C3', powerStatus: 'active', securityLevel: 6, lastInspection: new Date(Date.now() - 172800000) },
22 { id: 'enc4', name: 'Herbivore Valley', sector: 'D4', powerStatus: 'active', securityLevel: 4, lastInspection: new Date(Date.now() - 259200000) },
23];
24
25// Funkcja pomocnicza symulująca opóźnienie sieciowe
26const simulateNetworkDelay = () => {
27 return new Promise(resolve => setTimeout(resolve, API_DELAY * (0.5 + Math.random())));
28};
29
30// Funkcja pomocnicza symulująca błędy API
31const simulateApiError = (endpoint) => {
32 if (Math.random() < API_ERROR_RATE) {
33 const errorTypes = [
34 { statusCode: 500, message: 'Internal Server Error' },
35 { statusCode: 503, message: 'Service Unavailable' },
36 { statusCode: 429, message: 'Too Many Requests' },
37 { statusCode: 403, message: 'Forbidden - Insufficient permissions' }
38 ];
39 const errorType = errorTypes[Math.floor(Math.random() * errorTypes.length)];
40 throw new APIError(`API Error: ${errorType.message}`, errorType.statusCode, endpoint);
41 }
42};
43
44// API do pobierania danych o dinozaurach
45export async function getDinosaurs() {
46 const endpoint = '/api/dinosaurs';
47 try {
48 await simulateNetworkDelay();
49 simulateApiError(endpoint);
50 return [...dinosaursData]; // Zwracamy kopię, aby uniknąć mutacji
51 } catch (error) {
52 if (error instanceof APIError) {
53 throw error;
54 }
55 throw new APIError('Błąd podczas pobierania danych o dinozaurach', 500, endpoint);
56 }
57}
58
59export async function getDinosaurById(id) {
60 const endpoint = `/api/dinosaurs/${id}`;
61 try {
62 await simulateNetworkDelay();
63 simulateApiError(endpoint);
64 const dino = dinosaursData.find(d => d.id === id);
65 if (!dino) {
66 throw new APIError(`Dinozaur o ID ${id} nie został znaleziony`, 404, endpoint);
67 }
68 return { ...dino }; // Zwracamy kopię
69 } catch (error) {
70 if (error instanceof APIError) {
71 throw error;
72 }
73 throw new APIError(`Błąd podczas pobierania dinozaura ${id}`, 500, endpoint);
74 }
75}
76
77export async function updateDinosaur(id, updates) {
78 const endpoint = `/api/dinosaurs/${id}`;
79 try {
80 await simulateNetworkDelay();
81 simulateApiError(endpoint);
82 const dinoIndex = dinosaursData.findIndex(d => d.id === id);
83 if (dinoIndex === -1) {
84 throw new APIError(`Dinozaur o ID ${id} nie został znaleziony`, 404, endpoint);
85 }
86
87 // Aktualizacja danych
88 dinosaursData[dinoIndex] = {
89 ...dinosaursData[dinoIndex],
90 ...updates,
91 id // Upewniamy się, że ID się nie zmienia
92 };
93
94 return { ...dinosaursData[dinoIndex] };
95 } catch (error) {
96 if (error instanceof APIError) {
97 throw error;
98 }
99 throw new APIError(`Błąd podczas aktualizacji dinozaura ${id}`, 500, endpoint);
100 }
101}
102
103// API do pobierania danych o wybiegach
104export async function getEnclosures() {
105 const endpoint = '/api/enclosures';
106 try {
107 await simulateNetworkDelay();
108 simulateApiError(endpoint);
109 return [...enclosuresData];
110 } catch (error) {
111 if (error instanceof APIError) {
112 throw error;
113 }
114 throw new APIError('Błąd podczas pobierania danych o wybiegach', 500, endpoint);
115 }
116}
117
118export async function getEnclosureById(id) {
119 const endpoint = `/api/enclosures/${id}`;
120 try {
121 await simulateNetworkDelay();
122 simulateApiError(endpoint);
123 const enclosure = enclosuresData.find(e => e.id === id);
124 if (!enclosure) {
125 throw new APIError(`Wybieg o ID ${id} nie został znaleziony`, 404, endpoint);
126 }
127 return { ...enclosure };
128 } catch (error) {
129 if (error instanceof APIError) {
130 throw error;
131 }
132 throw new APIError(`Błąd podczas pobierania wybiegu ${id}`, 500, endpoint);
133 }
134}
135
136export async function updateEnclosure(id, updates) {
137 const endpoint = `/api/enclosures/${id}`;
138 try {
139 await simulateNetworkDelay();
140 simulateApiError(endpoint);
141 const enclosureIndex = enclosuresData.findIndex(e => e.id === id);
142 if (enclosureIndex === -1) {
143 throw new APIError(`Wybieg o ID ${id} nie został znaleziony`, 404, endpoint);
144 }
145
146 // Aktualizacja danych
147 enclosuresData[enclosureIndex] = {
148 ...enclosuresData[enclosureIndex],
149 ...updates,
150 id
151 };
152
153 return { ...enclosuresData[enclosureIndex] };
154 } catch (error) {
155 if (error instanceof APIError) {
156 throw error;
157 }
158 throw new APIError(`Błąd podczas aktualizacji wybiegu ${id}`, 500, endpoint);
159 }
160}
161
162// API do logowania zdarzeń
163export async function logEvent(eventType, details) {
164 const endpoint = '/api/events';
165 try {
166 await simulateNetworkDelay();
167 simulateApiError(endpoint);
168
169 // W rzeczywistości wysłalibyśmy zdarzenie do serwera
170 console.log(`[EVENT] ${eventType}:`, details);
171
172 return { success: true, timestamp: new Date() };
173 } catch (error) {
174 if (error instanceof APIError) {
175 throw error;
176 }
177 throw new APIError('Błąd podczas logowania zdarzenia', 500, endpoint);
178 }
179}Ten moduł będzie odpowiedzialny za zarządzanie danymi o dinozaurach i ich zachowaniami:
1// modules/dinosaurs.js
2import { getDinosaurs, getDinosaurById, updateDinosaur, logEvent } from './api.js';
3import { DinoNotFoundError, DinoBehaviorError } from '../errors/index.js';
4
5// Ocena ryzyka zachowania dinozaura
6function assessBehaviorRisk(dino) {
7 const dangerousSpecies = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
8 const riskBehaviors = {
9 'calm': 1,
10 'grazing': 1,
11 'sleeping': 2,
12 'exploring': 4,
13 'agitated': 6,
14 'hunting': 7,
15 'aggressive': 9
16 };
17
18 // Bazowy poziom ryzyka zależy od gatunku
19 let baseRisk = dangerousSpecies.includes(dino.species) ? 5 : 2;
20
21 // Modyfikacja ryzyka w zależności od zachowania
22 const behaviorRisk = riskBehaviors[dino.behavior] || 3;
23
24 // Modyfikacja ryzyka w zależności od czasu od ostatniego karmienia
25 const hoursSinceLastFed = (Date.now() - new Date(dino.lastFed).getTime()) / 3600000;
26 const hungerRisk = Math.min(5, Math.floor(hoursSinceLastFed / 3));
27
28 // Całkowity poziom ryzyka (1-10)
29 const totalRisk = Math.min(10, baseRisk + behaviorRisk + hungerRisk);
30
31 return {
32 risk: totalRisk,
33 factors: {
34 species: baseRisk,
35 behavior: behaviorRisk,
36 hunger: hungerRisk
37 }
38 };
39}
40
41// Pobieranie wszystkich dinozaurów
42export async function getAllDinosaurs() {
43 try {
44 const dinosaurs = await getDinosaurs();
45
46 // Wzbogacamy dane o ocenę ryzyka
47 return dinosaurs.map(dino => ({
48 ...dino,
49 riskAssessment: assessBehaviorRisk(dino)
50 }));
51 } catch (error) {
52 console.error('Błąd podczas pobierania listy dinozaurów:', error);
53 throw error;
54 }
55}
56
57// Pobieranie konkretnego dinozaura po ID
58export async function getDinosaur(id) {
59 try {
60 const dino = await getDinosaurById(id);
61 return {
62 ...dino,
63 riskAssessment: assessBehaviorRisk(dino)
64 };
65 } catch (error) {
66 if (error.statusCode === 404) {
67 throw new DinoNotFoundError(id);
68 }
69 console.error(`Błąd podczas pobierania dinozaura ${id}:`, error);
70 throw error;
71 }
72}
73
74// Karmienie dinozaura
75export async function feedDinosaur(id) {
76 try {
77 const dino = await getDinosaur(id);
78
79 // Aktualizacja stanu dinozaura
80 const updatedDino = await updateDinosaur(id, {
81 lastFed: new Date(),
82 health: Math.min(100, dino.health + 5),
83 behavior: 'calm' // Dinozaury są spokojniejsze po karmieniu
84 });
85
86 // Logowanie zdarzenia
87 await logEvent('FEEDING', {
88 dinoId: id,
89 species: dino.species,
90 name: dino.name,
91 previousFeedingTime: dino.lastFed
92 });
93
94 return {
95 ...updatedDino,
96 riskAssessment: assessBehaviorRisk(updatedDino)
97 };
98 } catch (error) {
99 console.error(`Błąd podczas karmienia dinozaura ${id}:`, error);
100 throw error;
101 }
102}
103
104// Monitorowanie zachowania dinozaura
105export async function monitorBehavior(id) {
106 try {
107 const dino = await getDinosaur(id);
108 const { risk, factors } = assessBehaviorRisk(dino);
109
110 // Sprawdzamy, czy zachowanie wymaga interwencji
111 if (risk >= 8) {
112 const error = new DinoBehaviorError(
113 `Niebezpieczne zachowanie dinozaura ${dino.name}`,
114 id,
115 dino.species,
116 dino.behavior,
117 risk
118 );
119
120 // Logowanie zdarzenia wysokiego ryzyka
121 await logEvent('HIGH_RISK_BEHAVIOR', {
122 dinoId: id,
123 species: dino.species,
124 name: dino.name,
125 behavior: dino.behavior,
126 risk,
127 factors
128 });
129
130 throw error;
131 }
132
133 // Dla średniego ryzyka tylko logujemy
134 if (risk >= 5) {
135 await logEvent('MEDIUM_RISK_BEHAVIOR', {
136 dinoId: id,
137 species: dino.species,
138 name: dino.name,
139 behavior: dino.behavior,
140 risk,
141 factors
142 });
143 }
144
145 return { dino, risk, factors };
146 } catch (error) {
147 if (error instanceof DinoBehaviorError) {
148 throw error; // Przekazujemy dalej nasz własny błąd
149 }
150 console.error(`Błąd podczas monitorowania dinozaura ${id}:`, error);
151 throw error;
152 }
153}
154
155// Symulacja zmiany zachowania (używana przez moduł monitorowania)
156export async function simulateBehaviorChange() {
157 try {
158 const allDinos = await getAllDinosaurs();
159 const randomIndex = Math.floor(Math.random() * allDinos.length);
160 const targetDino = allDinos[randomIndex];
161
162 // Możliwe zachowania zależne od gatunku i ostatniego karmienia
163 const possibleBehaviors = ['calm', 'grazing', 'sleeping', 'exploring', 'agitated', 'hunting', 'aggressive'];
164 const carnivores = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
165
166 let behaviorPool;
167 if (carnivores.includes(targetDino.species)) {
168 // Drapieżniki mają większe szanse na agresywne zachowania
169 behaviorPool = ['calm', 'sleeping', 'exploring', 'agitated', 'hunting', 'aggressive'];
170 } else {
171 // Roślinożercy są generalnie spokojniejsi
172 behaviorPool = ['calm', 'grazing', 'sleeping', 'exploring', 'agitated'];
173 }
174
175 // Głodne dinozaury są bardziej agresywne
176 const hoursSinceLastFed = (Date.now() - new Date(targetDino.lastFed).getTime()) / 3600000;
177 if (hoursSinceLastFed > 8) {
178 if (carnivores.includes(targetDino.species)) {
179 behaviorPool = ['agitated', 'hunting', 'aggressive'];
180 } else {
181 behaviorPool = ['agitated', 'exploring'];
182 }
183 }
184
185 // Losowy wybór nowego zachowania
186 const newBehavior = behaviorPool[Math.floor(Math.random() * behaviorPool.length)];
187
188 // Aktualizacja stanu dinozaura
189 const updatedDino = await updateDinosaur(targetDino.id, {
190 behavior: newBehavior
191 });
192
193 await logEvent('BEHAVIOR_CHANGE', {
194 dinoId: targetDino.id,
195 species: targetDino.species,
196 name: targetDino.name,
197 previousBehavior: targetDino.behavior,
198 newBehavior
199 });
200
201 return {
202 ...updatedDino,
203 riskAssessment: assessBehaviorRisk(updatedDino)
204 };
205 } catch (error) {
206 console.error('Błąd podczas symulacji zmiany zachowania:', error);
207 throw error;
208 }
209}Ten moduł będzie zarządzał wybiegami i ich stanem bezpieczeństwa:
1// modules/enclosures.js
2import { getEnclosures, getEnclosureById, updateEnclosure, logEvent } from './api.js';
3import { SecurityError, EnclosureBreachError } from '../errors/index.js';
4import { getAllDinosaurs } from './dinosaurs.js';
5
6// Ocena stanu bezpieczeństwa wybiegu
7function assessSecurityStatus(enclosure, dinosaurs) {
8 // Sprawdzamy, ile dinozaurów jest w wybiegu
9 const enclosureDinosaurs = dinosaurs.filter(dino => dino.enclosureId === enclosure.id);
10
11 // Sprawdzamy, czy są drapieżniki
12 const carnivores = enclosureDinosaurs.filter(dino =>
13 ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'].includes(dino.species)
14 );
15
16 // Sprawdzamy łączne ryzyko
17 const totalRisk = enclosureDinosaurs.reduce((sum, dino) =>
18 sum + (dino.riskAssessment ? dino.riskAssessment.risk : 3), 0);
19
20 // Stan zasilania
21 const powerStatus = enclosure.powerStatus === 'active' ? 10 :
22 enclosure.powerStatus === 'backup' ? 5 : 0;
23
24 // Czas od ostatniej inspekcji (w dniach)
25 const daysSinceInspection = (Date.now() - new Date(enclosure.lastInspection).getTime()) / (24 * 3600000);
26 const inspectionScore = daysSinceInspection < 1 ? 10 :
27 daysSinceInspection < 3 ? 7 :
28 daysSinceInspection < 7 ? 4 : 1;
29
30 // Obliczenie ogólnego wskaźnika bezpieczeństwa (0-100)
31 const securityScore = Math.max(0, Math.min(100,
32 enclosure.securityLevel * 10 - totalRisk + powerStatus + inspectionScore
33 ));
34
35 return {
36 id: enclosure.id,
37 name: enclosure.name,
38 sector: enclosure.sector,
39 dinosaurCount: enclosureDinosaurs.length,
40 carnivoreCount: carnivores.length,
41 securityLevel: enclosure.securityLevel,
42 powerStatus: enclosure.powerStatus,
43 securityScore,
44 status: securityScore >= 80 ? 'secure' :
45 securityScore >= 60 ? 'normal' :
46 securityScore >= 40 ? 'warning' : 'danger',
47 dinosaurs: enclosureDinosaurs.map(d => ({
48 id: d.id,
49 name: d.name,
50 species: d.species,
51 risk: d.riskAssessment ? d.riskAssessment.risk : 3
52 }))
53 };
54}
55
56// Pobieranie wszystkich wybiegów z oceną bezpieczeństwa
57export async function getAllEnclosures() {
58 try {
59 const [enclosures, dinosaurs] = await Promise.all([
60 getEnclosures(),
61 getAllDinosaurs()
62 ]);
63
64 // Wzbogacamy dane o ocenę bezpieczeństwa
65 return enclosures.map(enclosure =>
66 assessSecurityStatus(enclosure, dinosaurs)
67 );
68 } catch (error) {
69 console.error('Błąd podczas pobierania listy wybiegów:', error);
70 throw error;
71 }
72}
73
74// Pobieranie konkretnego wybiegu po ID
75export async function getEnclosure(id) {
76 try {
77 const [enclosure, dinosaurs] = await Promise.all([
78 getEnclosureById(id),
79 getAllDinosaurs()
80 ]);
81
82 return assessSecurityStatus(enclosure, dinosaurs);
83 } catch (error) {
84 console.error(`Błąd podczas pobierania wybiegu ${id}:`, error);
85 throw error;
86 }
87}
88
89// Przeprowadzenie inspekcji wybiegu
90export async function inspectEnclosure(id) {
91 try {
92 const enclosure = await getEnclosureById(id);
93
94 // Aktualizacja daty ostatniej inspekcji
95 const updatedEnclosure = await updateEnclosure(id, {
96 lastInspection: new Date()
97 });
98
99 // Logowanie zdarzenia
100 await logEvent('ENCLOSURE_INSPECTION', {
101 enclosureId: id,
102 name: enclosure.name,
103 sector: enclosure.sector,
104 previousInspection: enclosure.lastInspection
105 });
106
107 // Pobieramy dinozaury, aby zaktualizować ocenę bezpieczeństwa
108 const dinosaurs = await getAllDinosaurs();
109
110 return assessSecurityStatus(updatedEnclosure, dinosaurs);
111 } catch (error) {
112 console.error(`Błąd podczas inspekcji wybiegu ${id}:`, error);
113 throw error;
114 }
115}
116
117// Symulacja awarii systemu zasilania
118export async function simulatePowerFailure() {
119 try {
120 const enclosures = await getEnclosures();
121
122 // Wybieramy losowy wybieg
123 const randomIndex = Math.floor(Math.random() * enclosures.length);
124 const targetEnclosure = enclosures[randomIndex];
125
126 if (targetEnclosure.powerStatus !== 'active') {
127 // Wybieg już ma problem z zasilaniem
128 return;
129 }
130
131 // 70% szans na przejście na zasilanie awaryjne, 30% na całkowitą awarię
132 const newPowerStatus = Math.random() < 0.7 ? 'backup' : 'failure';
133
134 // Aktualizacja stanu wybiegu
135 const updatedEnclosure = await updateEnclosure(targetEnclosure.id, {
136 powerStatus: newPowerStatus
137 });
138
139 // Logowanie zdarzenia
140 await logEvent('POWER_FAILURE', {
141 enclosureId: targetEnclosure.id,
142 name: targetEnclosure.name,
143 sector: targetEnclosure.sector,
144 powerStatus: newPowerStatus
145 });
146
147 // Sprawdzenie, czy awaria może prowadzić do naruszenia wybiegu
148 if (newPowerStatus === 'failure') {
149 const dinosaurs = await getAllDinosaurs();
150 const enclosureDinos = dinosaurs.filter(dino => dino.enclosureId === targetEnclosure.id);
151
152 // Ocena ryzyka naruszenia (zależy od gatunków dinozaurów)
153 const dangerousSpecies = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
154 const hasDangerousDinos = enclosureDinos.some(dino =>
155 dangerousSpecies.includes(dino.species)
156 );
157
158 if (hasDangerousDinos && targetEnclosure.securityLevel >= 8) {
159 // Wysokie ryzyko naruszenia wybiegu
160 const breachingSpecies = enclosureDinos.find(dino =>
161 dangerousSpecies.includes(dino.species)
162 ).species;
163
164 const breachingCount = enclosureDinos.filter(dino =>
165 dino.species === breachingSpecies
166 ).length;
167
168 throw new EnclosureBreachError(
169 targetEnclosure.sector,
170 breachingSpecies,
171 breachingCount
172 );
173 }
174 }
175
176 const dinosaurs = await getAllDinosaurs();
177 return assessSecurityStatus(updatedEnclosure, dinosaurs);
178 } catch (error) {
179 if (error instanceof EnclosureBreachError) {
180 // Logujemy zdarzenie naruszenia wybiegu
181 await logEvent('ENCLOSURE_BREACH', {
182 sector: error.sector,
183 species: error.dinoSpecies,
184 count: error.count,
185 timestamp: error.timestamp
186 });
187 throw error; // Przekazujemy błąd dalej
188 }
189
190 console.error('Błąd podczas symulacji awarii zasilania:', error);
191 throw error;
192 }
193}
194
195// Naprawa systemu zasilania
196export async function repairPowerSystem(id) {
197 try {
198 const enclosure = await getEnclosureById(id);
199
200 if (enclosure.powerStatus === 'active') {
201 throw new SecurityError(
202 'System zasilania jest już aktywny',
203 enclosure.sector,
204 'power'
205 );
206 }
207
208 // Aktualizacja stanu zasilania
209 const updatedEnclosure = await updateEnclosure(id, {
210 powerStatus: 'active'
211 });
212
213 // Logowanie zdarzenia
214 await logEvent('POWER_RESTORED', {
215 enclosureId: id,
216 name: enclosure.name,
217 sector: enclosure.sector,
218 previousStatus: enclosure.powerStatus
219 });
220
221 // Pobieramy dinozaury, aby zaktualizować ocenę bezpieczeństwa
222 const dinosaurs = await getAllDinosaurs();
223
224 return assessSecurityStatus(updatedEnclosure, dinosaurs);
225 } catch (error) {
226 console.error(`Błąd podczas naprawy systemu zasilania wybiegu ${id}:`, error);
227 throw error;
228 }
229}Ten moduł będzie odpowiedzialny za ciągłe monitorowanie parku w tle:
1// modules/monitoring.js
2import { getAllDinosaurs, monitorBehavior, simulateBehaviorChange } from './dinosaurs.js';
3import { getAllEnclosures, simulatePowerFailure } from './enclosures.js';
4import { logEvent } from './api.js';
5import { DinoBehaviorError, EnclosureBreachError } from '../errors/index.js';
6
7// Przechowujemy identyfikatory interwałów, aby móc je później zatrzymać
8const intervals = new Map();
9
10// Aktywne alarmy
11const activeAlerts = new Map();
12
13// Rejestratory dla alertów
14const alertListeners = [];
15
16// Status systemu monitorowania
17let monitoringStatus = 'idle'; // 'idle', 'running', 'error'
18
19// Dodawanie listenera dla alertów
20export function addAlertListener(callback) {
21 alertListeners.push(callback);
22 return function removeListener() {
23 const index = alertListeners.indexOf(callback);
24 if (index !== -1) {
25 alertListeners.splice(index, 1);
26 return true;
27 }
28 return false;
29 };
30}
31
32// Generowanie alertu
33function triggerAlert(alertType, data) {
34 const alertId = `${alertType}-${Date.now()}`;
35 const alert = {
36 id: alertId,
37 type: alertType,
38 timestamp: new Date(),
39 data,
40 resolved: false
41 };
42
43 // Dodajemy do aktywnych alertów
44 activeAlerts.set(alertId, alert);
45
46 // Powiadamiamy wszystkich słuchaczy
47 alertListeners.forEach(listener => {
48 try {
49 listener(alert);
50 } catch (error) {
51 console.error('Błąd w listenerze alertów:', error);
52 }
53 });
54
55 return alertId;
56}
57
58// Oznaczanie alertu jako rozwiązanego
59export function resolveAlert(alertId) {
60 if (!activeAlerts.has(alertId)) {
61 throw new Error(`Alert o ID ${alertId} nie istnieje`);
62 }
63
64 const alert = activeAlerts.get(alertId);
65 alert.resolved = true;
66 alert.resolutionTime = new Date();
67
68 // Powiadamiamy wszystkich słuchaczy
69 alertListeners.forEach(listener => {
70 try {
71 listener({ ...alert, status: 'resolved' });
72 } catch (error) {
73 console.error('Błąd w listenerze alertów:', error);
74 }
75 });
76
77 // Po 5 minutach usuwamy rozwiązany alert z pamięci
78 setTimeout(() => {
79 activeAlerts.delete(alertId);
80 }, 5 * 60 * 1000);
81
82 return alert;
83}
84
85// Pobieranie wszystkich aktywnych alertów
86export function getActiveAlerts() {
87 return Array.from(activeAlerts.values())
88 .filter(alert => !alert.resolved)
89 .sort((a, b) => b.timestamp - a.timestamp);
90}
91
92// Uruchomienie monitorowania zachowań dinozaurów
93function startDinoBehaviorMonitoring(interval = 60000) {
94 if (intervals.has('dinoBehavior')) {
95 clearInterval(intervals.get('dinoBehavior'));
96 }
97
98 // Funkcja monitorująca wszystkie dinozaury
99 async function monitorAllDinosaurs() {
100 try {
101 const allDinos = await getAllDinosaurs();
102
103 // Monitorujemy każdego dinozaura
104 const monitorPromises = allDinos.map(dino =>
105 monitorBehavior(dino.id).catch(error => {
106 if (error instanceof DinoBehaviorError && error.requiresIntervention()) {
107 // Generujemy alert dla niebezpiecznego zachowania
108 triggerAlert('DANGEROUS_BEHAVIOR', {
109 dinoId: error.dinoId,
110 species: error.species,
111 behavior: error.behaviorType,
112 dangerLevel: error.dangerLevel,
113 message: error.message
114 });
115 }
116 return { error, dino };
117 })
118 );
119
120 await Promise.all(monitorPromises);
121
122 // Symulujemy losową zmianę zachowania (dla jednego dinozaura)
123 if (Math.random() < 0.3) { // 30% szans na zmianę w każdym cyklu
124 await simulateBehaviorChange();
125 }
126
127 } catch (error) {
128 console.error('Błąd podczas monitorowania dinozaurów:', error);
129 monitoringStatus = 'error';
130
131 // Automatyczne ponowne uruchomienie po błędzie
132 setTimeout(() => {
133 if (monitoringStatus === 'error') {
134 monitoringStatus = 'running';
135 }
136 }, 5000);
137 }
138 }
139
140 // Pierwsze uruchomienie natychmiast
141 monitorAllDinosaurs();
142
143 // Następnie uruchamiamy cyklicznie
144 const intervalId = setInterval(monitorAllDinosaurs, interval);
145 intervals.set('dinoBehavior', intervalId);
146
147 return intervalId;
148}
149
150// Uruchomienie monitorowania zabezpieczeń wybiegów
151function startEnclosureMonitoring(interval = 90000) {
152 if (intervals.has('enclosureSecurity')) {
153 clearInterval(intervals.get('enclosureSecurity'));
154 }
155
156 // Funkcja monitorująca stan wybiegów
157 async function monitorAllEnclosures() {
158 try {
159 const allEnclosures = await getAllEnclosures();
160
161 // Sprawdzamy stan bezpieczeństwa każdego wybiegu
162 for (const enclosure of allEnclosures) {
163 if (enclosure.status === 'danger') {
164 // Generujemy alert dla niebezpiecznego stanu wybiegu
165 triggerAlert('ENCLOSURE_SECURITY', {
166 enclosureId: enclosure.id,
167 name: enclosure.name,
168 sector: enclosure.sector,
169 securityScore: enclosure.securityScore,
170 powerStatus: enclosure.powerStatus,
171 dinosaurCount: enclosure.dinosaurCount
172 });
173 }
174 }
175
176 // Symulujemy losowe awarie zasilania
177 if (Math.random() < 0.2) { // 20% szans na awarię w każdym cyklu
178 try {
179 await simulatePowerFailure();
180 } catch (error) {
181 if (error instanceof EnclosureBreachError) {
182 // Generujemy alert dla naruszenia wybiegu
183 triggerAlert('ENCLOSURE_BREACH', {
184 sector: error.sector,
185 species: error.dinoSpecies,
186 count: error.count,
187 timestamp: error.timestamp,
188 message: error.message
189 });
190 }
191 throw error;
192 }
193 }
194
195 } catch (error) {
196 if (!(error instanceof EnclosureBreachError)) {
197 console.error('Błąd podczas monitorowania wybiegów:', error);
198 monitoringStatus = 'error';
199
200 // Automatyczne ponowne uruchomienie po błędzie
201 setTimeout(() => {
202 if (monitoringStatus === 'error') {
203 monitoringStatus = 'running';
204 }
205 }, 5000);
206 }
207 }
208 }
209
210 // Pierwsze uruchomienie po 10 sekundach (aby nie nakładało się z monitorowaniem dinozaurów)
211 setTimeout(() => {
212 monitorAllEnclosures();
213 }, 10000);
214
215 // Następnie uruchamiamy cyklicznie
216 const intervalId = setInterval(monitorAllEnclosures, interval);
217 intervals.set('enclosureSecurity', intervalId);
218
219 return intervalId;
220}
221
222// Uruchomienie systemu monitorowania
223export function startMonitoring(options = {}) {
224 if (monitoringStatus === 'running') {
225 console.log('System monitorowania jest już uruchomiony.');
226 return;
227 }
228
229 monitoringStatus = 'running';
230
231 // Ustawienia domyślne
232 const settings = {
233 dinoBehaviorInterval: 60000, // 1 minuta
234 enclosureSecurityInterval: 90000, // 1.5 minuty
235 ...options
236 };
237
238 // Uruchamiamy monitorowanie dinozaurów
239 startDinoBehaviorMonitoring(settings.dinoBehaviorInterval);
240
241 // Uruchamiamy monitorowanie wybiegów
242 startEnclosureMonitoring(settings.enclosureSecurityInterval);
243
244 // Logujemy uruchomienie
245 logEvent('MONITORING_STARTED', {
246 timestamp: new Date(),
247 settings
248 }).catch(error => console.error('Błąd podczas logowania zdarzenia:', error));
249
250 return monitoringStatus;
251}
252
253// Zatrzymanie systemu monitorowania
254export function stopMonitoring() {
255 if (monitoringStatus !== 'running') {
256 console.log('System monitorowania nie jest uruchomiony.');
257 return;
258 }
259
260 // Zatrzymujemy wszystkie interwały
261 for (const [name, intervalId] of intervals.entries()) {
262 clearInterval(intervalId);
263 intervals.delete(name);
264 }
265
266 monitoringStatus = 'idle';
267
268 // Logujemy zatrzymanie
269 logEvent('MONITORING_STOPPED', {
270 timestamp: new Date()
271 }).catch(error => console.error('Błąd podczas logowania zdarzenia:', error));
272
273 return monitoringStatus;
274}
275
276// Zwracanie statusu systemu monitorowania
277export function getMonitoringStatus() {
278 return {
279 status: monitoringStatus,
280 activeMonitors: Array.from(intervals.keys()),
281 alertsCount: getActiveAlerts().length
282 };
283}Ten moduł będzie zawierał przydatne funkcje pomocnicze:
1// modules/utils.js
2
3// Formatowanie daty
4export function formatDate(date) {
5 if (!date) return 'N/A';
6
7 const d = new Date(date);
8 if (isNaN(d.getTime())) return 'Invalid Date';
9
10 return d.toLocaleString('pl-PL', {
11 year: 'numeric',
12 month: '2-digit',
13 day: '2-digit',
14 hour: '2-digit',
15 minute: '2-digit',
16 second: '2-digit'
17 });
18}
19
20// Formatowanie czasu, który upłynął
21export function formatTimeAgo(date) {
22 if (!date) return 'N/A';
23
24 const d = new Date(date);
25 if (isNaN(d.getTime())) return 'Invalid Date';
26
27 const now = new Date();
28 const diffMs = now - d;
29
30 if (diffMs < 0) return 'w przyszłości';
31
32 const diffSec = Math.floor(diffMs / 1000);
33 if (diffSec < 60) return `${diffSec} sek. temu`;
34
35 const diffMin = Math.floor(diffSec / 60);
36 if (diffMin < 60) return `${diffMin} min. temu`;
37
38 const diffHour = Math.floor(diffMin / 60);
39 if (diffHour < 24) return `${diffHour} godz. temu`;
40
41 const diffDay = Math.floor(diffHour / 24);
42 if (diffDay < 30) return `${diffDay} dni temu`;
43
44 const diffMonth = Math.floor(diffDay / 30);
45 if (diffMonth < 12) return `${diffMonth} mies. temu`;
46
47 const diffYear = Math.floor(diffMonth / 12);
48 return `${diffYear} lat temu`;
49}
50
51// Kolorowanie poziomu zagrożenia
52export function getDangerLevelColor(level) {
53 if (level <= 3) return 'green';
54 if (level <= 5) return 'yellow';
55 if (level <= 7) return 'orange';
56 return 'red';
57}
58
59// Formatowanie poziomu zagrożenia jako tekst
60export function formatDangerLevel(level) {
61 if (level <= 2) return 'Niski';
62 if (level <= 5) return 'Umiarkowany';
63 if (level <= 7) return 'Podwyższony';
64 if (level <= 9) return 'Wysoki';
65 return 'Krytyczny';
66}
67
68// Retry utility - ponowne próby wykonania funkcji w przypadku awarii
69export async function retry(fn, options = {}) {
70 const {
71 maxRetries = 3,
72 delay = 1000,
73 backoffFactor = 2,
74 retryCondition = error => true
75 } = options;
76
77 let lastError;
78
79 for (let attempt = 1; attempt <= maxRetries; attempt++) {
80 try {
81 return await fn();
82 } catch (error) {
83 // Sprawdzamy, czy ten błąd kwalifikuje się do ponownej próby
84 if (!retryCondition(error)) {
85 throw error;
86 }
87
88 lastError = error;
89 console.log(`Próba ${attempt}/${maxRetries} nie powiodła się. Powód: ${error.message}`);
90
91 // Jeśli to była ostatnia próba, nie czekamy
92 if (attempt === maxRetries) break;
93
94 // Czekamy z opóźnieniem eksponencjalnym
95 const waitTime = delay * Math.pow(backoffFactor, attempt - 1);
96 await new Promise(resolve => setTimeout(resolve, waitTime));
97 }
98 }
99
100 throw lastError;
101}
102
103// Memoizacja funkcji (buforowanie wyników)
104export function memoize(fn, { maxSize = 100, ttl = 60000 } = {}) {
105 const cache = new Map();
106 const timestamps = new Map();
107
108 return async function(...args) {
109 // Tworzymy klucz z argumentów
110 const key = JSON.stringify(args);
111
112 // Sprawdzamy, czy wynik jest w pamięci podręcznej i czy nie wygasł
113 const now = Date.now();
114 if (cache.has(key)) {
115 const timestamp = timestamps.get(key);
116 if (now - timestamp < ttl) {
117 return cache.get(key);
118 }
119 }
120
121 // Jeśli pamięć podręczna jest za duża, usuwamy najstarszy element
122 if (cache.size >= maxSize) {
123 let oldestKey = null;
124 let oldestTime = now;
125
126 for (const [key, time] of timestamps.entries()) {
127 if (time < oldestTime) {
128 oldestKey = key;
129 oldestTime = time;
130 }
131 }
132
133 if (oldestKey) {
134 cache.delete(oldestKey);
135 timestamps.delete(oldestKey);
136 }
137 }
138
139 // Pobieramy wynik, dodajemy do pamięci podręcznej i zwracamy
140 const result = await fn(...args);
141 cache.set(key, result);
142 timestamps.set(key, now);
143
144 return result;
145 };
146}Plik konfiguracyjny dla naszej aplikacji:
1// data/config.js
2
3export const appConfig = {
4 // Ogólne ustawienia aplikacji
5 appName: 'Jurassic Tracker',
6 version: '1.0.0',
7
8 // Ustawienia monitorowania
9 monitoring: {
10 dinoBehaviorInterval: 60000, // Interwał monitorowania zachowań dinozaurów (ms)
11 enclosureSecurityInterval: 90000, // Interwał monitorowania bezpieczeństwa wybiegów (ms)
12 autoStart: true // Automatyczne uruchamianie monitorowania przy starcie
13 },
14
15 // Ustawienia API
16 api: {
17 baseUrl: 'https://jurassic-park-api.example.com', // W rzeczywistości używalibyśmy prawdziwego URL
18 timeout: 10000, // Timeout dla żądań API (ms)
19 retryCount: 3 // Liczba prób ponownego wykonania nieudanego żądania
20 },
21
22 // Ocena ryzyka dinozaurów
23 dinorisk: {
24 dangerousSpecies: ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus', 'Allosaurus', 'Dilophosaurus'],
25 behaviorRiskMatrix: {
26 'calm': { base: 1, factor: 0.8 },
27 'grazing': { base: 1, factor: 0.9 },
28 'sleeping': { base: 2, factor: 0.7 },
29 'exploring': { base: 4, factor: 1.0 },
30 'agitated': { base: 6, factor: 1.2 },
31 'hunting': { base: 7, factor: 1.5 },
32 'aggressive': { base: 9, factor: 2.0 }
33 },
34 hungerRiskHours: 3, // Liczba godzin, po których głód zaczyna wpływać na ryzyko
35 hungerRiskFactor: 0.5 // Współczynnik wpływu głodu na ryzyko
36 },
37
38 // Poziomy bezpieczeństwa dla powiadomień
39 security: {
40 alertThresholds: {
41 low: 3,
42 medium: 5,
43 high: 7,
44 critical: 9
45 },
46 autoResolveAlerts: false // Czy automatycznie oznaczać alerty jako rozwiązane
47 }
48};
49
50export default appConfig;Na koniec, zapiszmy główny plik naszej aplikacji, który łączy wszystkie moduły:
1// index.js
2import { appConfig } from './data/config.js';
3import * as DinosaurService from './modules/dinosaurs.js';
4import * as EnclosureService from './modules/enclosures.js';
5import * as MonitoringService from './modules/monitoring.js';
6import { formatDate, formatTimeAgo, formatDangerLevel, getDangerLevelColor } from './modules/utils.js';
7import { APIError, DinoError, SecurityError, EnclosureBreachError } from './modules/errors/index.js';
8
9console.log(`==========================================`);
10console.log(`Witaj w ${appConfig.appName} v${appConfig.version}!`);
11console.log(`Inicjalizacja systemów Parku Jurajskiego...`);
12console.log(`==========================================`);
13
14// Obsługa błędów na poziomie globalnym
15process.on('unhandledRejection', (reason, promise) => {
16 console.error('Nieobsłużone odrzucenie obietnicy:', reason);
17});
18
19process.on('uncaughtException', (error) => {
20 console.error('Nieobsłużony wyjątek:', error);
21
22 // Analiza typów błędów
23 if (error instanceof EnclosureBreachError) {
24 console.error(`KRYTYCZNY: Naruszenie wybiegu w sektorze ${error.sector}!`);
25 console.error(`${error.count} ${error.dinoSpecies} poza wybiegiem!`);
26 console.error('Uruchamianie protokołu awaryjnego...');
27 } else if (error instanceof SecurityError) {
28 console.error(`BEZPIECZEŃSTWO: Problem w sektorze ${error.sector}`);
29 } else if (error instanceof DinoError) {
30 console.error(`DINOZAUR: Problem z ${error.species} (ID: ${error.dinoId})`);
31 } else if (error instanceof APIError) {
32 console.error(`API: Błąd ${error.statusCode} podczas komunikacji z ${error.endpoint}`);
33 }
34});
35
36// Główna funkcja startowa
37async function initializeApp() {
38 try {
39 // Rejestrowanie listenera dla alertów
40 const removeAlertListener = MonitoringService.addAlertListener(alert => {
41 const { type, data, timestamp } = alert;
42
43 console.log(`[ALERT] ${formatDate(timestamp)} - ${type}`);
44
45 switch (type) {
46 case 'DANGEROUS_BEHAVIOR':
47 console.log(`Dinozaur ${data.species} (ID: ${data.dinoId}) wykazuje niebezpieczne zachowanie: ${data.behavior}`);
48 console.log(`Poziom zagrożenia: ${formatDangerLevel(data.dangerLevel)}`);
49 break;
50
51 case 'ENCLOSURE_SECURITY':
52 console.log(`Problem z bezpieczeństwem wybiegu ${data.name} (Sektor ${data.sector})`);
53 console.log(`Wskaźnik bezpieczeństwa: ${data.securityScore}/100`);
54 console.log(`Status zasilania: ${data.powerStatus}`);
55 break;
56
57 case 'ENCLOSURE_BREACH':
58 console.log(`!!! NARUSZENIE WYBIEGU !!!`);
59 console.log(`Sektor: ${data.sector}, Gatunek: ${data.species}, Liczba: ${data.count}`);
60 console.log('UWAGA: Wymagana natychmiastowa interwencja!');
61 break;
62
63 default:
64 console.log('Otrzymano alert:', data);
65 }
66 });
67
68 // Pobieranie danych początkowych
69 console.log('Pobieranie danych o dinozaurach...');
70 const dinosaurs = await DinosaurService.getAllDinosaurs();
71 console.log(`Załadowano dane ${dinosaurs.length} dinozaurów.`);
72
73 console.log('Pobieranie danych o wybiegach...');
74 const enclosures = await EnclosureService.getAllEnclosures();
75 console.log(`Załadowano dane ${enclosures.length} wybiegów.`);
76
77 // Uruchomienie monitorowania, jeśli jest włączone w konfiguracji
78 if (appConfig.monitoring.autoStart) {
79 console.log('Uruchamianie systemu monitorowania...');
80 MonitoringService.startMonitoring({
81 dinoBehaviorInterval: appConfig.monitoring.dinoBehaviorInterval,
82 enclosureSecurityInterval: appConfig.monitoring.enclosureSecurityInterval
83 });
84
85 const status = MonitoringService.getMonitoringStatus();
86 console.log(`Status monitorowania: ${status.status}`);
87 console.log(`Aktywne monitory: ${status.activeMonitors.join(', ')}`);
88 }
89
90 console.log('\nSystemy Parku Jurajskiego zostały zainicjalizowane pomyślnie.');
91 console.log('Rozpoczynam monitorowanie...');
92
93 // Przykładowe wywołania API, które mogą zostać użyte przez użytkownika
94 setTimeout(async () => {
95 try {
96 // Karmienie dinozaura
97 const fedDino = await DinosaurService.feedDinosaur('trx1');
98 console.log(`Nakarmiono ${fedDino.name} (${fedDino.species})`);
99
100 // Inspekcja wybiegu
101 const inspectedEnclosure = await EnclosureService.inspectEnclosure('enc2');
102 console.log(`Przeprowadzono inspekcję wybiegu: ${inspectedEnclosure.name} (Sektor ${inspectedEnclosure.sector})`);
103
104 // Naprawa systemu zasilania (jeśli jakiś wybieg ma problem)
105 const enclosureWithPowerIssue = enclosures.find(e => e.powerStatus !== 'active');
106 if (enclosureWithPowerIssue) {
107 const repairedEnclosure = await EnclosureService.repairPowerSystem(enclosureWithPowerIssue.id);
108 console.log(`Naprawiono system zasilania w: ${repairedEnclosure.name}`);
109 }
110
111 } catch (error) {
112 console.error('Błąd podczas wykonywania operacji demonstracyjnych:', error);
113 }
114 }, 5000);
115
116 } catch (error) {
117 console.error('Błąd podczas inicjalizacji aplikacji:', error);
118
119 if (error instanceof APIError) {
120 console.error(`Nie można połączyć się z API: ${error.statusCode} ${error.message}`);
121 } else {
122 console.error('Nieoczekiwany błąd:', error.message);
123 }
124
125 console.error('Próba ponownej inicjalizacji za 10 sekund...');
126 setTimeout(initializeApp, 10000);
127 }
128}
129
130// Uruchomienie aplikacji
131initializeApp();
132
133// Funkcja do zatrzymania aplikacji przed zamknięciem
134function cleanup() {
135 console.log('\nZamykanie aplikacji...');
136
137 // Zatrzymanie systemu monitorowania
138 MonitoringService.stopMonitoring();
139
140 console.log('Wszystkie systemy monitorowania zostały zatrzymane.');
141 console.log('Do widzenia!');
142 process.exit(0);
143}
144
145// Obsługa sygnałów zakończenia
146process.on('SIGINT', cleanup);
147process.on('SIGTERM', cleanup);W rzeczywistym środowisku moglibyśmy uruchomić tę aplikację za pomocą:
1node index.jsPo uruchomieniu, aplikacja:
W naszej aplikacji JurassicTracker wykorzystaliśmy wszystkie najważniejsze techniki poznane w tym module:
Ta aplikacja pokazuje, jak można połączyć wszystkie poznane techniki w funkcjonalny, modularny i odporny na błędy system.
W rzeczywistej aplikacji webowej moglibyśmy dodać warstwę UI (React, Vue, Angular) oraz prawdziwe API backendowe, ale podstawowa struktura i organizacja kodu pozostałyby podobne.
Budowa złożonej aplikacji JavaScript wymaga dobrego zrozumienia:
Nasz system JurassicTracker, choć jest przykładem dydaktycznym, pokazuje, jak te wszystkie elementy współpracują ze sobą, tworząc solidną aplikację, która mogłaby - z odpowiednimi rozszerzeniami - zostać wykorzystana w prawdziwym Parku Jurajskim. Miejmy jednak nadzieję, że jej systemy bezpieczeństwa działałyby lepiej niż w filmie!
W następnym ćwiczeniu zagłębimy się w strukturyzację kodu w jeszcze większych projektach JavaScript, rozszerzając poznane techniki i wzorce.