Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion infrastructure/local/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ services:
- frontend
- full
ports:
- "5173:80"
- "5173:8080"
volumes:
- ../../skyroster-frontend:/app
- /app/node_modules
Expand Down
6 changes: 5 additions & 1 deletion skyroster-backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ RUN ./mvnw package -DskipTests -B
FROM eclipse-temurin:25-jre
WORKDIR /app

COPY --from=build /app/target/*.jar app.jar
RUN groupadd --system app && useradd --system --gid app --home /app app

COPY --from=build --chown=app:app /app/target/*.jar app.jar

USER app

EXPOSE 8001

Expand Down
4 changes: 2 additions & 2 deletions skyroster-frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ ENV VITE_API_URL=$VITE_API_URL
RUN npm run build

# Stage 2: Runtime
FROM nginx:alpine
FROM nginxinc/nginx-unprivileged:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
EXPOSE 8080

CMD ["nginx", "-g", "daemon off;"]
2 changes: 1 addition & 1 deletion skyroster-frontend/nginx.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
server {
listen 80;
listen 8080;
server_name _;
root /usr/share/nginx/html;
index index.html;
Expand Down
104 changes: 78 additions & 26 deletions skyroster-frontend/src/stores/aircraft.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,46 @@ async function getAircraftList() {
return res.data;
}

function mapAircraftResponse(a) {
return {
id: a.id,
rejestracja: a.registrationNumber,
typ: a.aircraftType.icaoCode,
baza: a.operationalBase.icaoCode
};
}

function translateAircraftSaveError(error, registrationNumber) {
const status = error.response?.status;
const backendMessage = error.response?.data?.message ?? '';

if (status === 409) {
return `Samolot o rejestracji "${registrationNumber}" już istnieje`;
}
if (status === 404) {
// 404 can mean the operational base code is invalid OR (on PUT) the aircraft id no longer exists
if (backendMessage.startsWith('Aircraft not found')) {
// AircraftNotFoundException — concurrent delete on PUT
return 'Samolot nie istnieje (mógł zostać usunięty)';
}
// OperationalBaseNotFoundException: "Operational base with code '...' not found"
return 'Nieprawidłowa baza operacyjna';
}
if (status === 400) {
// Prefixes pinned to backend exception messages — if those change server-side, frontend silently falls through to the generic fallback below
if (backendMessage.startsWith('Aircraft type')) {
// AircraftTypeNotFoundException: "Aircraft type with code '...' not found"
return 'Nieprawidłowy typ samolotu';
}
if (backendMessage.startsWith('Registration number')) {
// IllegalArgumentException from Aircraft.validateRegistrationNumber: "Registration number must not be blank"
return 'Rejestracja nie może być pusta';
}
return 'Nieprawidłowe dane samolotu';
}
return 'Nie udało się zapisać samolotu';
}

export const useAircraftStore = defineStore('aircraft', () => {
const aircraft = ref([]);
const aircraftTypeOptions = AIRCRAFT_TYPES
Expand All @@ -30,41 +70,53 @@ export const useAircraftStore = defineStore('aircraft', () => {
try {
const response = await getAircraftList();

aircraft.value = response.map(aircraft => ({
id: aircraft.id,
rejestracja: aircraft.registrationNumber,
typ: aircraft.aircraftType.icaoCode,
baza: aircraft.operationalBase.icaoCode
}));
aircraft.value = response.map(mapAircraftResponse);
} catch (error) {
console.error(error);
}
}

function generateId() {
const maxId = aircraft.value.reduce((max, a) => {
const num = parseInt(a.id.replace('A', ''))
return num > max ? num : max
}, 0)
return `A${String(maxId + 1).padStart(3, '0')}`
}

function addAircraft(aircraftData) {
const newAircraft = {
...aircraftData,
id: generateId()
async function addAircraft(aircraftData) {
try {
const { data } = await apiClient.post('/aircraft', {
registrationNumber: aircraftData.rejestracja,
aircraftTypeCode: aircraftData.typ,
operationalBaseCode: aircraftData.baza
});
const mapped = mapAircraftResponse(data);
aircraft.value.push(mapped);
return { success: true, aircraft: mapped };
} catch (error) {
console.error('addAircraft failed', error);
return {
success: false,
message: translateAircraftSaveError(error, aircraftData.rejestracja)
};
}
aircraft.value.push(newAircraft)
return newAircraft
}

function updateAircraft(id, aircraftData) {
const index = aircraft.value.findIndex(a => a.id === id)
if (index !== -1) {
aircraft.value[index] = { ...aircraft.value[index], ...aircraftData }
return aircraft.value[index]
async function updateAircraft(id, aircraftData) {
try {
const { data } = await apiClient.put(`/aircraft/${id}`, {
registrationNumber: aircraftData.rejestracja,
aircraftTypeCode: aircraftData.typ,
operationalBaseCode: aircraftData.baza
});
const mapped = mapAircraftResponse(data);
const index = aircraft.value.findIndex(a => a.id === id);
if (index !== -1) {
aircraft.value[index] = mapped;
} else {
console.warn(`updateAircraft: aircraft with id ${id} not in local store, skipping local update`);
}
return { success: true, aircraft: mapped };
} catch (error) {
console.error('updateAircraft failed', error);
return {
success: false,
message: translateAircraftSaveError(error, aircraftData.rejestracja)
};
}
return null
}

async function deleteAircraft(id) {
Expand Down
24 changes: 18 additions & 6 deletions skyroster-frontend/src/views/AircraftView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,25 @@ function openEditDialog(aircraft) {
dialogVisible.value = true
}

function handleSave(aircraftData) {
if (isEditMode.value && selectedAircraft.value) {
aircraftStore.updateAircraft(selectedAircraft.value.id, aircraftData)
toast.add({ severity: 'success', summary: 'Sukces', detail: 'Samolot został zaktualizowany', life: 3000 })
async function handleSave(aircraftData) {
const result = isEditMode.value && selectedAircraft.value
? await aircraftStore.updateAircraft(selectedAircraft.value.id, aircraftData)
: await aircraftStore.addAircraft(aircraftData)

if (result.success) {
toast.add({
severity: 'success',
summary: 'Sukces',
detail: isEditMode.value ? 'Samolot został zaktualizowany' : 'Samolot został dodany',
life: 3000
})
} else {
aircraftStore.addAircraft(aircraftData)
toast.add({ severity: 'success', summary: 'Sukces', detail: 'Samolot został dodany', life: 3000 })
toast.add({
severity: 'error',
summary: 'Błąd',
detail: result.message,
life: 5000
})
}
}

Expand Down
Loading