Compare commits
116 Commits
mb-npm-cho
...
2b26ac23be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b26ac23be | ||
| 5c1d8876be | |||
| e89c5c7439 | |||
| b605f82af7 | |||
| a59d8c0331 | |||
| 567f31dd3d | |||
| 71760a500f | |||
| b48b689aeb | |||
| 9f57cbaa71 | |||
| a1b18d6f92 | |||
|
|
284a853344 | ||
| 465a42acac | |||
|
|
ebce600356 | ||
| 6e418b6f2f | |||
| 328a8e3e35 | |||
| bfb95610f6 | |||
| 68674dd1c5 | |||
| 9a16ce0c21 | |||
| 16689318eb | |||
| b12ea81bbf | |||
| 49a638d595 | |||
| 452d257c7a | |||
| 599eec0e43 | |||
| 433c914c4a | |||
| 0338351eef | |||
| e1803aea3e | |||
| 6f491e20e5 | |||
| 7f26710a40 | |||
| 9203c61541 | |||
| 3a57a1334d | |||
| 72c19d7a75 | |||
| 8b25076599 | |||
| a44f8b445c | |||
| 5ec457fea7 | |||
| 3ce95ecb49 | |||
| 4fcd34cfa9 | |||
| d64f6f61ba | |||
| 5934bbe666 | |||
| f08764c3d1 | |||
| b7cc01ff1c | |||
| e9a78db048 | |||
| b52e3160d5 | |||
| 0996a81d52 | |||
| 25df7a935c | |||
| 6f7077adf4 | |||
|
|
55c0647b55 | ||
| 4b2107d0f6 | |||
| de544b9c98 | |||
| 302ff3c8a3 | |||
| 657b378169 | |||
| cb65f24f67 | |||
| 2bb2942a0f | |||
| 6b78f31aa4 | |||
| c7f1702663 | |||
| ccff9a3752 | |||
| aac99c86fa | |||
| 2e13acc0b0 | |||
| 6a59634de3 | |||
| 63a32f02bf | |||
| 429f0c1ddc | |||
| 0354e4e190 | |||
| 7935d1837f | |||
| f769af279b | |||
| de62585b64 | |||
| ac24be6c5e | |||
| 13976b02d8 | |||
| 3aef4fa971 | |||
| 29e9911653 | |||
| eca711e572 | |||
| 09aa0fc136 | |||
| cc9505ca1e | |||
| 22f70f533a | |||
| 6df029b4a7 | |||
| 1d4d59842b | |||
| a63d29a42a | |||
| 1f4ebc31ed | |||
| 50cd3c2d75 | |||
| 0edd796f73 | |||
| e883c0e6e2 | |||
| d2390ca5c2 | |||
| 4906b757b8 | |||
| 0b62f59d19 | |||
| 924683511c | |||
| d7d6eb2b43 | |||
| 4859890389 | |||
| 4f34cc180c | |||
| 3c26381901 | |||
| e01f9806bd | |||
| 71087af0df | |||
| 6efcaaccf9 | |||
| 60578dfaba | |||
| addf0f437b | |||
| 51df7f2d1e | |||
| 78faf1b0db | |||
| e3fc756b3f | |||
| 7083d27644 | |||
| 8277ef6af8 | |||
| 67d5df9c18 | |||
| 3ffc44a5a2 | |||
| bd2686fa9c | |||
| 495ee666cd | |||
| 5296b6a6c1 | |||
| 49e657ab54 | |||
| 25bbaca0d3 | |||
| 26038eabe4 | |||
| 57acd92e7c | |||
| c136c60e62 | |||
| a5e90ea014 | |||
| f0f3909239 | |||
| 1438bbe3a8 | |||
| a910cd745d | |||
| 6265440288 | |||
| 3baed66ebc | |||
| 499ce06438 | |||
| 67e5277c62 | |||
| ce154bf060 |
@@ -17,6 +17,9 @@ jobs:
|
|||||||
- name: Run Test DB Script
|
- name: Run Test DB Script
|
||||||
run: ./test_db.sh
|
run: ./test_db.sh
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: npm --version
|
||||||
|
|
||||||
- name: Cache Cargo dependencies
|
- name: Cache Cargo dependencies
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
@@ -25,15 +28,15 @@ jobs:
|
|||||||
cargo build
|
cargo build
|
||||||
cd frontend && npm install && npm run build
|
cd frontend && npm install && npm run build
|
||||||
- name: Frontend tests
|
- name: Frontend tests
|
||||||
run: cd frontend && npx playwright install && npx playwright test --workers 1 --reporter line
|
run: cd frontend && npx playwright install && npx playwright test --workers 1 --reporter html,line
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: frontend/playwright-report/
|
||||||
|
retention-days: 30
|
||||||
- name: Backend tests
|
- name: Backend tests
|
||||||
run: cargo test --verbose
|
run: cargo test --verbose
|
||||||
#- uses: actions/upload-artifact@v3
|
|
||||||
# if: always()
|
|
||||||
# with:
|
|
||||||
# name: playwright-report
|
|
||||||
# path: frontend/playwright-report/
|
|
||||||
# retention-days: 30
|
|
||||||
|
|
||||||
deploy-staging:
|
deploy-staging:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
1345
Cargo.lock
generated
1345
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -23,11 +23,11 @@ tera = { version = "1.20", features = ["date-locale"], optional = true}
|
|||||||
ics = "0.5"
|
ics = "0.5"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
lettre = "0.11"
|
lettre = "0.11"
|
||||||
csv = "1.3"
|
csv = "1.4"
|
||||||
itertools = "0.14"
|
itertools = "0.14"
|
||||||
job_scheduler_ng = "2.2"
|
job_scheduler_ng = "2.4"
|
||||||
ureq = { version = "3.0", features = ["json"] }
|
ureq = { version = "3.1", features = ["json"] }
|
||||||
regex = "1.11"
|
regex = "1.12"
|
||||||
urlencoding = "2.1"
|
urlencoding = "2.1"
|
||||||
|
|
||||||
[target.'cfg(not(windows))'.dependencies]
|
[target.'cfg(not(windows))'.dependencies]
|
||||||
|
|||||||
@@ -413,6 +413,7 @@ function initNewChoice(select: HTMLInputElement) {
|
|||||||
steering_person.setAttribute("required", "required");
|
steering_person.setAttribute("required", "required");
|
||||||
}
|
}
|
||||||
const choice = new Choices(select, {
|
const choice = new Choices(select, {
|
||||||
|
searchResultLimit: 100,
|
||||||
searchFields: ["label", "value", "customProperties.searchableText"],
|
searchFields: ["label", "value", "customProperties.searchableText"],
|
||||||
removeItemButton: true,
|
removeItemButton: true,
|
||||||
loadingText: "Wird geladen...",
|
loadingText: "Wird geladen...",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"sass": "^1.60.0",
|
"sass": "^1.60.0",
|
||||||
"tailwindcss": "^3.3.1",
|
"tailwindcss": "^3.3.1",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^4.2.0",
|
"vite": "^4.2.0",
|
||||||
"vite-plugin-static-copy": "^0.13.1"
|
"vite-plugin-static-copy": "^0.13.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { test, expect } from "@playwright/test";
|
import { test, expect, Page } from "@playwright/test";
|
||||||
|
import { resetDatabase, login } from "./helpers";
|
||||||
|
|
||||||
|
test.beforeEach(async () => {
|
||||||
|
await resetDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
test("cox can create and delete trip", async ({ page }) => {
|
test("cox can create and delete trip", async ({ page }) => {
|
||||||
await page.goto("/auth");
|
await page.goto("/auth");
|
||||||
@@ -16,22 +21,13 @@ test("cox can create and delete trip", async ({ page }) => {
|
|||||||
await page.getByRole("spinbutton").fill("5");
|
await page.getByRole("spinbutton").fill("5");
|
||||||
await page.getByRole("button", { name: "Erstellen", exact: true }).click();
|
await page.getByRole("button", { name: "Erstellen", exact: true }).click();
|
||||||
await expect(page.locator("body")).toContainText("18:00 Uhr (cox) Details");
|
await expect(page.locator("body")).toContainText("18:00 Uhr (cox) Details");
|
||||||
|
|
||||||
await page.goto("/planned");
|
|
||||||
await page.getByRole('link', { name: 'Details' }).nth(1).click();
|
|
||||||
await page.getByRole("link", { name: "Termin löschen" }).click();
|
|
||||||
await expect(page.locator("body")).toContainText("Erfolgreich gelöscht!");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: group -> cox can create trips
|
// TODO: group -> cox can create trips
|
||||||
// TODO: cox can help/register at trips/events
|
// TODO: cox can help/register at trips/events
|
||||||
|
|
||||||
test.describe("cox can edit trips", () => {
|
test.describe("cox can edit trips", () => {
|
||||||
let sharedPage: Page;
|
async function createTrip(page: Page) {
|
||||||
|
|
||||||
test.beforeAll(async ({ browser }) => {
|
|
||||||
const page = await browser.newPage();
|
|
||||||
|
|
||||||
await page.goto("/auth");
|
await page.goto("/auth");
|
||||||
await page.getByPlaceholder("Name").click();
|
await page.getByPlaceholder("Name").click();
|
||||||
await page.getByPlaceholder("Name").fill("cox");
|
await page.getByPlaceholder("Name").fill("cox");
|
||||||
@@ -46,151 +42,101 @@ test.describe("cox can edit trips", () => {
|
|||||||
await page.locator("#sidebar #planned_starting_time").press("Tab");
|
await page.locator("#sidebar #planned_starting_time").press("Tab");
|
||||||
await page.getByRole("spinbutton").fill("5");
|
await page.getByRole("spinbutton").fill("5");
|
||||||
await page.getByRole("button", { name: "Erstellen", exact: true }).click();
|
await page.getByRole("button", { name: "Erstellen", exact: true }).click();
|
||||||
|
}
|
||||||
|
|
||||||
sharedPage = page;
|
test("edit remarks", async ({ page }) => {
|
||||||
});
|
await createTrip(page);
|
||||||
|
|
||||||
test("edit remarks", async () => {
|
await page.goto("/planned");
|
||||||
await sharedPage.goto("/planned");
|
await page.getByRole('link', { name: 'Details' }).nth(1).click();
|
||||||
await sharedPage.getByRole('link', { name: 'Details' }).nth(1).click();
|
await page.locator("#sidebar #notes").click();
|
||||||
await sharedPage.locator("#sidebar #notes").click();
|
await page.locator("#sidebar #notes").fill("Meine Anmerkung");
|
||||||
await sharedPage.locator("#sidebar #notes").fill("Meine Anmerkung");
|
await page.getByRole("button", { name: "Speichern" }).click();
|
||||||
await sharedPage.getByRole("button", { name: "Speichern" }).click();
|
await page.getByRole("link", { name: "Details" }).nth(1).click();
|
||||||
await sharedPage.getByRole("link", { name: "Details" }).nth(1).click();
|
await expect(page.locator("#sidebar")).toContainText(
|
||||||
await expect(sharedPage.locator("#sidebar")).toContainText(
|
|
||||||
"Meine Anmerkung",
|
"Meine Anmerkung",
|
||||||
);
|
);
|
||||||
|
|
||||||
await sharedPage
|
|
||||||
.getByRole("button", { name: "Ausfahrt erstellen schließen" })
|
|
||||||
.click();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("add and remove guest", async () => {
|
test("add and remove guest", async ({ page }) => {
|
||||||
await sharedPage.goto("/planned");
|
await createTrip(page);
|
||||||
await sharedPage.getByRole("link", { name: "Details" }).nth(1).click();
|
|
||||||
await sharedPage.locator("#sidebar #user_note").click();
|
await page.goto("/planned");
|
||||||
await sharedPage.locator("#sidebar #user_note").fill("Mein Gast");
|
await page.getByRole("link", { name: "Details" }).nth(1).click();
|
||||||
await sharedPage.getByRole("button", { name: "Gast hinzufügen" }).click();
|
await page.locator("#sidebar #user_note").click();
|
||||||
await expect(sharedPage.locator("body")).toContainText(
|
await page.locator("#sidebar #user_note").fill("Mein Gast");
|
||||||
|
await page.getByRole("button", { name: "Gast hinzufügen" }).click();
|
||||||
|
await expect(page.locator("body")).toContainText(
|
||||||
"Erfolgreich angemeldet!",
|
"Erfolgreich angemeldet!",
|
||||||
);
|
);
|
||||||
await sharedPage.getByRole("link", { name: "Details" }).nth(1).click();
|
await page.getByRole("link", { name: "Details" }).nth(1).click();
|
||||||
await expect(sharedPage.locator("#sidebar")).toContainText(
|
await expect(page.locator("#sidebar")).toContainText(
|
||||||
"Freie Plätze: 4",
|
"Freie Plätze: 4",
|
||||||
);
|
);
|
||||||
await expect(sharedPage.locator("#sidebar")).toContainText(
|
await expect(page.locator("#sidebar")).toContainText(
|
||||||
"Mein Gast (Gast) Abmelden",
|
"Mein Gast (Gast) Abmelden",
|
||||||
);
|
);
|
||||||
await expect(
|
await expect(
|
||||||
sharedPage.getByRole("link", { name: "Termin löschen" }),
|
page.getByRole("link", { name: "Termin löschen" }),
|
||||||
).not.toBeVisible();
|
).not.toBeVisible();
|
||||||
|
|
||||||
await sharedPage.getByRole("link", { name: "Abmelden" }).click();
|
await page.getByRole("link", { name: "Abmelden" }).click();
|
||||||
await expect(sharedPage.locator("body")).toContainText(
|
await expect(page.locator("body")).toContainText(
|
||||||
"Erfolgreich abgemeldet!",
|
"Erfolgreich abgemeldet!",
|
||||||
);
|
);
|
||||||
await sharedPage.getByRole("link", { name: "Details" }).nth(1).click();
|
await page.getByRole("link", { name: "Details" }).nth(1).click();
|
||||||
await expect(sharedPage.locator("#sidebar")).toContainText(
|
await expect(page.locator("#sidebar")).toContainText(
|
||||||
"Freie Plätze: 5",
|
"Freie Plätze: 5",
|
||||||
);
|
);
|
||||||
await expect(sharedPage.locator("#sidebar")).toContainText(
|
await expect(page.locator("#sidebar")).toContainText(
|
||||||
"Keine Ruderer angemeldet",
|
"Keine Ruderer angemeldet",
|
||||||
);
|
);
|
||||||
await expect(
|
await expect(
|
||||||
sharedPage.getByRole("link", { name: "Termin löschen" }),
|
page.getByRole("link", { name: "Termin löschen" }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
await sharedPage
|
|
||||||
.getByRole("button", { name: "Ausfahrt erstellen schließen" })
|
|
||||||
.click();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("change amount rower", async () => {
|
test("change amount rower", async ({ page }) => {
|
||||||
await sharedPage.goto("/planned");
|
await createTrip(page);
|
||||||
await sharedPage.getByRole("link", { name: "Details" }).nth(1).click();
|
|
||||||
await expect(sharedPage.locator("#sidebar")).toContainText(
|
await page.goto("/planned");
|
||||||
|
await page.getByRole("link", { name: "Details" }).nth(1).click();
|
||||||
|
await expect(page.locator("#sidebar")).toContainText(
|
||||||
"Freie Plätze: 5",
|
"Freie Plätze: 5",
|
||||||
);
|
);
|
||||||
await sharedPage.getByRole("spinbutton").click();
|
await page.getByRole("spinbutton").click();
|
||||||
await sharedPage.getByRole("spinbutton").fill("3");
|
await page.getByRole("spinbutton").fill("3");
|
||||||
await sharedPage.getByRole("button", { name: "Speichern" }).click();
|
await page.getByRole("button", { name: "Speichern" }).click();
|
||||||
await expect(sharedPage.locator("body")).toContainText(
|
await expect(page.locator("body")).toContainText(
|
||||||
"Ausfahrt erfolgreich aktualisiert.",
|
"Ausfahrt erfolgreich aktualisiert.",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("call off trip", async () => {
|
test("call off trip", async ({ page }) => {
|
||||||
|
await createTrip(page);
|
||||||
|
|
||||||
// Someone registers...
|
// Someone registers...
|
||||||
await sharedPage.goto("/auth/logout");
|
await page.goto("/auth/logout");
|
||||||
await sharedPage.goto("/auth");
|
await page.waitForURL("/auth");
|
||||||
await sharedPage.getByPlaceholder("Name").click();
|
await login(page, "rower", "rower");
|
||||||
await sharedPage.getByPlaceholder("Name").fill("rower");
|
|
||||||
await sharedPage.getByPlaceholder("Name").press("Tab");
|
|
||||||
await sharedPage.getByPlaceholder("Passwort").fill("rower");
|
|
||||||
await sharedPage.getByPlaceholder("Passwort").press("Enter");
|
|
||||||
|
|
||||||
await sharedPage.goto("/planned");
|
|
||||||
await sharedPage.getByRole('link', { name: 'Mitrudern' }).nth(1).click();
|
|
||||||
|
|
||||||
|
await page.goto("/planned");
|
||||||
|
await page.getByRole('link', { name: 'Mitrudern' }).nth(1).click();
|
||||||
|
|
||||||
// Login as cox again
|
// Login as cox again
|
||||||
await sharedPage.goto("/auth/logout");
|
await page.goto("/auth/logout");
|
||||||
await sharedPage.goto("/auth");
|
await page.waitForURL("/auth");
|
||||||
await sharedPage.getByPlaceholder("Name").click();
|
await login(page, "cox", "cox");
|
||||||
await sharedPage.getByPlaceholder("Name").fill("cox");
|
|
||||||
await sharedPage.getByPlaceholder("Name").press("Tab");
|
|
||||||
await sharedPage.getByPlaceholder("Passwort").fill("cox");
|
|
||||||
await sharedPage.getByPlaceholder("Passwort").press("Enter");
|
|
||||||
|
|
||||||
await sharedPage.goto("/planned");
|
await page.goto("/planned");
|
||||||
|
|
||||||
|
// Now cancel the trip
|
||||||
// ... now I can cancel trip
|
await page.getByRole("link", { name: "Details" }).nth(1).click();
|
||||||
await sharedPage.getByRole("link", { name: "Details" }).nth(1).click();
|
await page.getByRole("button", { name: "Ausfahrt absagen" }).click();
|
||||||
await sharedPage.getByRole("button", { name: "Ausfahrt absagen" }).click();
|
await expect(page.locator("body")).toContainText(
|
||||||
await expect(sharedPage.locator("body")).toContainText(
|
|
||||||
"Ausfahrt erfolgreich aktualisiert.",
|
"Ausfahrt erfolgreich aktualisiert.",
|
||||||
);
|
);
|
||||||
await expect(sharedPage.locator("body")).toContainText("(Absage cox)");
|
await expect(page.locator("body")).toContainText("(Absage cox)");
|
||||||
|
|
||||||
|
|
||||||
// Done with the test -> cancel the cancellation of the trip, otherwise the afterAll function below fails
|
|
||||||
await sharedPage.getByRole("link", { name: "Details" }).nth(1).click();
|
|
||||||
await sharedPage.getByRole("spinbutton").click();
|
|
||||||
await sharedPage.getByRole("spinbutton").fill("3");
|
|
||||||
await sharedPage.getByRole("button", { name: "Speichern" }).click();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// deregistering
|
|
||||||
await sharedPage.goto("/auth/logout");
|
|
||||||
await sharedPage.goto("/auth");
|
|
||||||
await sharedPage.getByPlaceholder("Name").click();
|
|
||||||
await sharedPage.getByPlaceholder("Name").fill("rower");
|
|
||||||
await sharedPage.getByPlaceholder("Name").press("Tab");
|
|
||||||
await sharedPage.getByPlaceholder("Passwort").fill("rower");
|
|
||||||
await sharedPage.getByPlaceholder("Passwort").press("Enter");
|
|
||||||
|
|
||||||
await sharedPage.goto("/planned");
|
|
||||||
await sharedPage.getByRole('link', { name: 'Abmelden' }).click();
|
|
||||||
|
|
||||||
|
|
||||||
// now cox can delete trip again in afterAll
|
|
||||||
await sharedPage.goto("/auth/logout");
|
|
||||||
await sharedPage.goto("/auth");
|
|
||||||
await sharedPage.getByPlaceholder("Name").click();
|
|
||||||
await sharedPage.getByPlaceholder("Name").fill("cox");
|
|
||||||
await sharedPage.getByPlaceholder("Name").press("Tab");
|
|
||||||
await sharedPage.getByPlaceholder("Passwort").fill("cox");
|
|
||||||
await sharedPage.getByPlaceholder("Passwort").press("Enter");
|
|
||||||
});
|
|
||||||
|
|
||||||
test.afterAll(async () => {
|
|
||||||
await sharedPage.goto("/planned");
|
|
||||||
await sharedPage.getByRole('link', { name: 'Details' }).nth(1).click();
|
|
||||||
await sharedPage.getByRole("link", { name: "Termin löschen" }).click();
|
|
||||||
await sharedPage.close();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: 'Immer anzeigen' (also verify the functionality), 'Gesperrt' + type
|
// TODO: 'Immer anzeigen' (also verify the functionality), 'Gesperrt' + type
|
||||||
|
|||||||
29
frontend/tests/helpers.ts
Normal file
29
frontend/tests/helpers.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { exec } from 'child_process';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
import { Page } from '@playwright/test';
|
||||||
|
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
export async function resetDatabase(): Promise<void> {
|
||||||
|
await execAsync('cd .. && ./reset_test_data.sh');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function login(page: Page, username: string, password: string): Promise<void> {
|
||||||
|
// Clear cookies to ensure clean state
|
||||||
|
await page.context().clearCookies();
|
||||||
|
|
||||||
|
// Navigate to auth page and wait for it to fully load
|
||||||
|
await page.goto("/auth", { waitUntil: 'load' });
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
await page.getByPlaceholder("Name").click();
|
||||||
|
await page.getByPlaceholder("Name").fill(username);
|
||||||
|
await page.getByPlaceholder("Passwort").click();
|
||||||
|
await page.getByPlaceholder("Passwort").fill(password);
|
||||||
|
|
||||||
|
// Wait for navigation after form submission
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForURL(/\/(planned|log|$)/, { timeout: 10000 }),
|
||||||
|
page.getByPlaceholder("Passwort").press("Enter")
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
import { test, expect } from "@playwright/test";
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { resetDatabase } from "./helpers";
|
||||||
|
|
||||||
|
test.beforeEach(async () => {
|
||||||
|
await resetDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
test("Cox can start and cancel trip", async ({ page }, testInfo) => {
|
test("Cox can start and cancel trip", async ({ page }, testInfo) => {
|
||||||
await page.goto("/auth");
|
await page.goto("/auth");
|
||||||
@@ -34,12 +39,6 @@ test("Cox can start and cancel trip", async ({ page }, testInfo) => {
|
|||||||
"Ausfahrt erfolgreich hinzugefügt",
|
"Ausfahrt erfolgreich hinzugefügt",
|
||||||
);
|
);
|
||||||
await expect(page.locator("body")).toContainText("Joe");
|
await expect(page.locator("body")).toContainText("Joe");
|
||||||
|
|
||||||
await page.getByRole("link", { name: "Joe" }).click();
|
|
||||||
page.once("dialog", (dialog) => {
|
|
||||||
dialog.accept().catch(() => {});
|
|
||||||
});
|
|
||||||
await page.getByRole("link", { name: "Löschen" }).click();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Cox can start and finish trip", async ({ page }, testInfo) => {
|
test("Cox can start and finish trip", async ({ page }, testInfo) => {
|
||||||
@@ -102,28 +101,6 @@ test("Cox can start and finish trip", async ({ page }, testInfo) => {
|
|||||||
await expect(page.locator('body')).toContainText('(cox2)');
|
await expect(page.locator('body')).toContainText('(cox2)');
|
||||||
await expect(page.locator('body')).toContainText('Ottensheim (25 km)');
|
await expect(page.locator('body')).toContainText('Ottensheim (25 km)');
|
||||||
await expect(page.locator('body')).toContainText('Ruderer: cox2, rower2');
|
await expect(page.locator('body')).toContainText('Ruderer: cox2, rower2');
|
||||||
|
|
||||||
|
|
||||||
//Ausloggen...
|
|
||||||
await page.getByRole('banner').getByRole('link', { name: 'Logbuch' }).click();
|
|
||||||
await page.getByRole('link', { name: 'Ausloggen' }).click();
|
|
||||||
// Login as admin
|
|
||||||
await page.getByPlaceholder("Name").click();
|
|
||||||
await page.getByPlaceholder("Name").fill("main");
|
|
||||||
await page.getByPlaceholder("Name").press("Tab");
|
|
||||||
await page.getByPlaceholder("Passwort").fill("admin");
|
|
||||||
await page.getByPlaceholder("Passwort").press("Enter");
|
|
||||||
|
|
||||||
await page.goto("/log/show");
|
|
||||||
await page.getByRole('link', { name: 'Joe' }).nth(1).click();
|
|
||||||
page.once("dialog", (dialog) => {
|
|
||||||
dialog.accept().catch(() => {});
|
|
||||||
});
|
|
||||||
await page.getByRole('link', { name: 'Löschen' }).click();
|
|
||||||
|
|
||||||
//Ausloggen...
|
|
||||||
await page.getByRole('banner').getByRole('link', { name: 'Logbuch' }).click();
|
|
||||||
await page.getByRole('link', { name: 'Ausloggen' }).click();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Kiosk can start and cancel trip", async ({ page }, testInfo) => {
|
test("Kiosk can start and cancel trip", async ({ page }, testInfo) => {
|
||||||
@@ -151,12 +128,6 @@ test("Kiosk can start and cancel trip", async ({ page }, testInfo) => {
|
|||||||
"Ausfahrt erfolgreich hinzugefügt",
|
"Ausfahrt erfolgreich hinzugefügt",
|
||||||
);
|
);
|
||||||
await expect(page.locator("body")).toContainText("Joe");
|
await expect(page.locator("body")).toContainText("Joe");
|
||||||
|
|
||||||
await page.getByRole("link", { name: "Joe" }).click();
|
|
||||||
page.once("dialog", (dialog) => {
|
|
||||||
dialog.accept().catch(() => {});
|
|
||||||
});
|
|
||||||
await page.getByRole("link", { name: "Löschen" }).click();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Kiosk can start and finish trip", async ({ page }, testInfo) => {
|
test("Kiosk can start and finish trip", async ({ page }, testInfo) => {
|
||||||
@@ -210,29 +181,6 @@ test("Kiosk can start and finish trip", async ({ page }, testInfo) => {
|
|||||||
await expect(page.locator('body')).toContainText('Joe');
|
await expect(page.locator('body')).toContainText('Joe');
|
||||||
await expect(page.locator('body')).toContainText('Ottensheim (25 km)');
|
await expect(page.locator('body')).toContainText('Ottensheim (25 km)');
|
||||||
await expect(page.locator('body')).toContainText('Ruderer: cox2, rower2');
|
await expect(page.locator('body')).toContainText('Ruderer: cox2, rower2');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Ausloggen...
|
|
||||||
await page.context().clearCookies();
|
|
||||||
await page.goto("/auth");
|
|
||||||
// Login as admin
|
|
||||||
await page.getByPlaceholder("Name").click();
|
|
||||||
await page.getByPlaceholder("Name").fill("main");
|
|
||||||
await page.getByPlaceholder("Name").press("Tab");
|
|
||||||
await page.getByPlaceholder("Passwort").fill("admin");
|
|
||||||
await page.getByPlaceholder("Passwort").press("Enter");
|
|
||||||
|
|
||||||
await page.goto("/log/show");
|
|
||||||
await page.getByRole('link', { name: 'Joe' }).nth(1).click();
|
|
||||||
page.once("dialog", (dialog) => {
|
|
||||||
dialog.accept().catch(() => {});
|
|
||||||
});
|
|
||||||
await page.getByRole('link', { name: 'Löschen' }).click();
|
|
||||||
|
|
||||||
//Ausloggen...
|
|
||||||
await page.getByRole('banner').getByRole('link', { name: 'Logbuch' }).click();
|
|
||||||
await page.getByRole('link', { name: 'Ausloggen' }).click();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Cox can start and finish trip with cox steering only", async ({ page }, testInfo) => {
|
test("Cox can start and finish trip with cox steering only", async ({ page }, testInfo) => {
|
||||||
@@ -286,29 +234,6 @@ test("Cox can start and finish trip with cox steering only", async ({ page }, te
|
|||||||
await page.goto('/log/show');
|
await page.goto('/log/show');
|
||||||
await expect(page.locator('body')).toContainText('cox_only_steering_boat');
|
await expect(page.locator('body')).toContainText('cox_only_steering_boat');
|
||||||
await expect(page.locator('body')).toContainText('Ottensheim (25 km)');
|
await expect(page.locator('body')).toContainText('Ottensheim (25 km)');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Ausloggen...
|
|
||||||
await page.getByRole('banner').getByRole('link', { name: 'Logbuch' }).click();
|
|
||||||
await page.getByRole('link', { name: 'Ausloggen' }).click();
|
|
||||||
// Login as admin
|
|
||||||
await page.getByPlaceholder("Name").click();
|
|
||||||
await page.getByPlaceholder("Name").fill("main");
|
|
||||||
await page.getByPlaceholder("Name").press("Tab");
|
|
||||||
await page.getByPlaceholder("Passwort").fill("admin");
|
|
||||||
await page.getByPlaceholder("Passwort").press("Enter");
|
|
||||||
|
|
||||||
await page.goto("/log/show");
|
|
||||||
await page.getByRole("link", { name: "cox_only_steering_boat" }).click();
|
|
||||||
page.once("dialog", (dialog) => {
|
|
||||||
dialog.accept().catch(() => {});
|
|
||||||
});
|
|
||||||
await page.getByRole('link', { name: 'Löschen' }).click();
|
|
||||||
|
|
||||||
//Ausloggen...
|
|
||||||
await page.getByRole('banner').getByRole('link', { name: 'Logbuch' }).click();
|
|
||||||
await page.getByRole('link', { name: 'Ausloggen' }).click();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Kiosk can start and finish trip in one stop", async ({ page }, testInfo) => {
|
test("Kiosk can start and finish trip in one stop", async ({ page }, testInfo) => {
|
||||||
@@ -355,27 +280,4 @@ test("Kiosk can start and finish trip in one stop", async ({ page }, testInfo) =
|
|||||||
await expect(page.locator('body')).toContainText('(cox2)');
|
await expect(page.locator('body')).toContainText('(cox2)');
|
||||||
await expect(page.locator('body')).toContainText('a (1 km)');
|
await expect(page.locator('body')).toContainText('a (1 km)');
|
||||||
await expect(page.locator('body')).toContainText('Ruderer: cox2, rower2');
|
await expect(page.locator('body')).toContainText('Ruderer: cox2, rower2');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Ausloggen...
|
|
||||||
await page.context().clearCookies();
|
|
||||||
await page.goto("/auth");
|
|
||||||
// Login as admin
|
|
||||||
await page.getByPlaceholder("Name").click();
|
|
||||||
await page.getByPlaceholder("Name").fill("main");
|
|
||||||
await page.getByPlaceholder("Name").press("Tab");
|
|
||||||
await page.getByPlaceholder("Passwort").fill("admin");
|
|
||||||
await page.getByPlaceholder("Passwort").press("Enter");
|
|
||||||
|
|
||||||
await page.goto("/log/show");
|
|
||||||
await page.getByRole('link', { name: 'Joe' }).nth(1).click();
|
|
||||||
page.once("dialog", (dialog) => {
|
|
||||||
dialog.accept().catch(() => {});
|
|
||||||
});
|
|
||||||
await page.getByRole('link', { name: 'Löschen' }).click();
|
|
||||||
|
|
||||||
//Ausloggen...
|
|
||||||
await page.getByRole('banner').getByRole('link', { name: 'Logbuch' }).click();
|
|
||||||
await page.getByRole('link', { name: 'Ausloggen' }).click();
|
|
||||||
});
|
});
|
||||||
|
|||||||
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "rowt",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
19
reset_test_data.sh
Executable file
19
reset_test_data.sh
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DB_FILE="db.sqlite"
|
||||||
|
|
||||||
|
# Clear all data and reseed
|
||||||
|
sqlite3 "$DB_FILE" << 'EOF'
|
||||||
|
PRAGMA writable_schema = 1;
|
||||||
|
DELETE FROM sqlite_sequence;
|
||||||
|
PRAGMA writable_schema = 0;
|
||||||
|
PRAGMA foreign_keys = OFF;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Get all tables and delete from them
|
||||||
|
sqlite3 "$DB_FILE" "SELECT 'DELETE FROM ' || name || ';' FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';" | sqlite3 "$DB_FILE"
|
||||||
|
|
||||||
|
# Re-enable foreign keys and reseed
|
||||||
|
sqlite3 "$DB_FILE" "PRAGMA foreign_keys = ON;"
|
||||||
|
sqlite3 "$DB_FILE" < seeds.sql
|
||||||
@@ -207,7 +207,7 @@ dein Vereinsbeitrag für das aktuelle Jahr beträgt {}€",
|
|||||||
fees.name
|
fees.name
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
content.push_str("\nBitte überweise diesen auf folgendes Konto: IBAN: AT58 2032 0321 0072 9256. Auf https://app.rudernlinz.at/planned findest du einen QR Code, den du mit deiner Bankapp scannen kannst um alle Eingaben bereits ausgefüllt zu haben.\n\n\
|
content.push_str("\nBitte überweise diesen auf folgendes Konto: IBAN: AT58 2032 0321 0072 9256 (Name: ASKÖ Ruderverein Donau Linz). Auf https://app.rudernlinz.at/planned findest du einen QR Code, den du mit deiner Bankapp scannen kannst um alle Eingaben bereits ausgefüllt zu haben.\n\n\
|
||||||
Falls die Berechnung nicht stimmt (korrekte Preise findest du unter https://rudernlinz.at/unser-verein/gebuhren/) melde dich bitte bei kassier@rudernlinz.at. @Studenten: Bitte die aktuelle Studienbestätigung an kassier@rudernlinz.at schicken.\n\n\
|
Falls die Berechnung nicht stimmt (korrekte Preise findest du unter https://rudernlinz.at/unser-verein/gebuhren/) melde dich bitte bei kassier@rudernlinz.at. @Studenten: Bitte die aktuelle Studienbestätigung an kassier@rudernlinz.at schicken.\n\n\
|
||||||
Wenn du die Vereinsgebühren schon bezahlt hast, kannst du diese Mail einfach ignorieren.\n\n
|
Wenn du die Vereinsgebühren schon bezahlt hast, kannst du diese Mail einfach ignorieren.\n\n
|
||||||
Beste Grüße\n\
|
Beste Grüße\n\
|
||||||
@@ -333,7 +333,7 @@ Dein Vereinsbeitrag für das aktuelle Jahr beträgt {}€",
|
|||||||
Gemäß § 7 Abs. 3 lit. c unseres Status behalten wir uns vor, bei ausbleibender Zahlung die Mitgliedschaft zu beenden. Dies möchten wir vermeiden und hoffen auf deine Unterstützung.\n\n\
|
Gemäß § 7 Abs. 3 lit. c unseres Status behalten wir uns vor, bei ausbleibender Zahlung die Mitgliedschaft zu beenden. Dies möchten wir vermeiden und hoffen auf deine Unterstützung.\n\n\
|
||||||
Bei Fragen oder Problemen stehen wir gerne zur Verfügung.
|
Bei Fragen oder Problemen stehen wir gerne zur Verfügung.
|
||||||
|
|
||||||
Bankverbindung: IBAN: AT58 2032 0321 0072 9256 (Unter https://app.rudernlinz.at/planned findest du einen QR Code, den du mit deiner Bankapp scannen kannst um alle Eingaben bereits ausgefüllt zu haben.)
|
Bankverbindung: IBAN: AT58 2032 0321 0072 9256 (Name: ASKÖ Ruderverein Donau Linz; unter https://app.rudernlinz.at/planned findest du einen QR Code, den du mit deiner Bankapp scannen kannst um alle Eingaben bereits ausgefüllt zu haben.)
|
||||||
|
|
||||||
Mit freundlichen Grüßen,\n\
|
Mit freundlichen Grüßen,\n\
|
||||||
Der Vorstand");
|
Der Vorstand");
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::model::{
|
|||||||
notification::Notification,
|
notification::Notification,
|
||||||
role::Role,
|
role::Role,
|
||||||
};
|
};
|
||||||
use chrono::NaiveDate;
|
use chrono::{Datelike, Local, NaiveDate};
|
||||||
use rocket::{fs::TempFile, tokio::io::AsyncReadExt};
|
use rocket::{fs::TempFile, tokio::io::AsyncReadExt};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
@@ -578,4 +578,32 @@ impl User {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn has_to_pay_einschreibgebuehr_this_year(&self, db: &SqlitePool) -> bool {
|
||||||
|
if !self.has_role(db, "schnupperant").await {
|
||||||
|
if let Some(member_since_date) = &self.member_since_date {
|
||||||
|
if let Ok(member_since_date) =
|
||||||
|
NaiveDate::parse_from_str(member_since_date, "%Y-%m-%d")
|
||||||
|
{
|
||||||
|
if member_since_date.year() == Local::now().year()
|
||||||
|
&& !self.has_role(db, "no-einschreibgebuehr").await
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
pub(crate) fn has_to_pay_only_half(&self) -> bool {
|
||||||
|
if let Some(member_since_date) = &self.member_since_date {
|
||||||
|
if let Ok(member_since_date) = NaiveDate::parse_from_str(member_since_date, "%Y-%m-%d")
|
||||||
|
{
|
||||||
|
let halfprice_startdate =
|
||||||
|
NaiveDate::from_ymd_opt(Local::now().year(), 7, 1).unwrap();
|
||||||
|
return member_since_date >= halfprice_startdate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
use super::User;
|
use super::User;
|
||||||
use crate::{
|
use crate::{
|
||||||
BOAT_STORAGE, DUAL_MEMBERSHIP, EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE, FAMILY_TWO, FOERDERND,
|
model::family::Family, BOAT_STORAGE, DUAL_MEMBERSHIP, EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE,
|
||||||
REGULAR, RENNRUDERBEITRAG, SCHECKBUCH, STUDENT_OR_PUPIL, TRIAL_ROWING, TRIAL_ROWING_REDUCED,
|
FAMILY_TWO, FOERDERND, REGULAR, RENNRUDERBEITRAG, SCHECKBUCH, STUDENT_OR_PUPIL, TRIAL_ROWING,
|
||||||
UNTERSTUETZEND, model::family::Family,
|
TRIAL_ROWING_REDUCED, UNTERSTUETZEND,
|
||||||
};
|
};
|
||||||
use chrono::{Datelike, Local, NaiveDate};
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
@@ -81,30 +80,52 @@ impl User {
|
|||||||
let mut fee = Fee::new();
|
let mut fee = Fee::new();
|
||||||
|
|
||||||
if let Some(family) = Family::find_by_opt_id(db, self.family_id).await {
|
if let Some(family) = Family::find_by_opt_id(db, self.family_id).await {
|
||||||
|
let mut einschreibgebuehr = false;
|
||||||
|
let mut half_price = true;
|
||||||
for member in family.members(db).await {
|
for member in family.members(db).await {
|
||||||
fee.add_person(&member);
|
fee.add_person(&member);
|
||||||
if member.has_role(db, "paid").await {
|
if member.has_role(db, "paid").await {
|
||||||
fee.paid();
|
fee.paid();
|
||||||
}
|
}
|
||||||
fee.merge(member.fee_without_families(db).await);
|
fee.merge(member.fee_without_families(db, true).await);
|
||||||
|
if member.has_to_pay_einschreibgebuehr_this_year(db).await {
|
||||||
|
einschreibgebuehr = true;
|
||||||
|
}
|
||||||
|
if !member.has_to_pay_only_half() {
|
||||||
|
half_price = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if family.amount_family_members(db).await > 2 {
|
if family.amount_family_members(db).await > 2 {
|
||||||
|
if half_price {
|
||||||
|
fee.add(
|
||||||
|
"Familie 3+ Personen (Halbpreis)".into(),
|
||||||
|
FAMILY_THREE_OR_MORE / 2,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
fee.add("Familie 3+ Personen".into(), FAMILY_THREE_OR_MORE);
|
fee.add("Familie 3+ Personen".into(), FAMILY_THREE_OR_MORE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if half_price {
|
||||||
|
fee.add("Familie 2 Personen (Halbpreis)".into(), FAMILY_TWO / 2);
|
||||||
} else {
|
} else {
|
||||||
fee.add("Familie 2 Personen".into(), FAMILY_TWO);
|
fee.add("Familie 2 Personen".into(), FAMILY_TWO);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if einschreibgebuehr {
|
||||||
|
fee.add("Einschreibgebühr (Familie)".into(), EINSCHREIBGEBUEHR);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fee.add_person(self);
|
fee.add_person(self);
|
||||||
if self.has_role(db, "paid").await {
|
if self.has_role(db, "paid").await {
|
||||||
fee.paid();
|
fee.paid();
|
||||||
}
|
}
|
||||||
fee.merge(self.fee_without_families(db).await);
|
fee.merge(self.fee_without_families(db, false).await);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(fee)
|
Some(fee)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fee_without_families(&self, db: &SqlitePool) -> Fee {
|
async fn fee_without_families(&self, db: &SqlitePool, entry_fee_paid_with_family: bool) -> Fee {
|
||||||
let mut fee = Fee::new();
|
let mut fee = Fee::new();
|
||||||
|
|
||||||
if !self.has_role(db, "Donau Linz").await
|
if !self.has_role(db, "Donau Linz").await
|
||||||
@@ -125,38 +146,24 @@ impl User {
|
|||||||
|
|
||||||
let amount_boats = self.amount_boats(db).await;
|
let amount_boats = self.amount_boats(db).await;
|
||||||
if amount_boats > 0 {
|
if amount_boats > 0 {
|
||||||
|
if self.has_to_pay_only_half() {
|
||||||
|
fee.add(
|
||||||
|
format!("{}x Bootsplatz (Halbpreis)", amount_boats),
|
||||||
|
amount_boats * BOAT_STORAGE / 2,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
fee.add(
|
fee.add(
|
||||||
format!("{}x Bootsplatz", amount_boats),
|
format!("{}x Bootsplatz", amount_boats),
|
||||||
amount_boats * BOAT_STORAGE,
|
amount_boats * BOAT_STORAGE,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !self.has_role(db, "schnupperant").await {
|
if self.has_to_pay_einschreibgebuehr_this_year(db).await && !entry_fee_paid_with_family {
|
||||||
if let Some(member_since_date) = &self.member_since_date {
|
|
||||||
if let Ok(member_since_date) =
|
|
||||||
NaiveDate::parse_from_str(member_since_date, "%Y-%m-%d")
|
|
||||||
{
|
|
||||||
if member_since_date.year() == Local::now().year()
|
|
||||||
&& !self.has_role(db, "no-einschreibgebuehr").await
|
|
||||||
{
|
|
||||||
fee.add("Einschreibgebühr".into(), EINSCHREIBGEBUEHR);
|
fee.add("Einschreibgebühr".into(), EINSCHREIBGEBUEHR);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let halfprice = if let Some(member_since_date) = &self.member_since_date {
|
let halfprice = self.has_to_pay_only_half();
|
||||||
match NaiveDate::parse_from_str(member_since_date, "%Y-%m-%d") {
|
|
||||||
Ok(member_since_date) => {
|
|
||||||
let halfprice_startdate =
|
|
||||||
NaiveDate::from_ymd_opt(Local::now().year(), 7, 1).unwrap();
|
|
||||||
member_since_date >= halfprice_startdate
|
|
||||||
}
|
|
||||||
Err(_) => false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.has_role(db, "schnupperant").await {
|
if self.has_role(db, "schnupperant").await {
|
||||||
if self.has_role(db, "Student").await || self.has_role(db, "Schüler").await {
|
if self.has_role(db, "Student").await || self.has_role(db, "Schüler").await {
|
||||||
|
|||||||
@@ -795,6 +795,7 @@ macro_rules! special_user {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl $name {
|
impl $name {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn into_inner(self) -> User {
|
pub fn into_inner(self) -> User {
|
||||||
self.user
|
self.user
|
||||||
}
|
}
|
||||||
@@ -859,6 +860,7 @@ special_user!(AllowedForPlannedTripsUser, +"Donau Linz", +"scheckbuch", +"Förde
|
|||||||
special_user!(DonauLinzUser, +"Donau Linz", +"Förderndes Mitglied", -"Unterstützend"); // TODO:
|
special_user!(DonauLinzUser, +"Donau Linz", +"Förderndes Mitglied", -"Unterstützend"); // TODO:
|
||||||
// remove ->
|
// remove ->
|
||||||
// RegularUser
|
// RegularUser
|
||||||
|
special_user!(ErgoAdminUser, +"ergo-admin", +"admin");
|
||||||
special_user!(SchnupperBetreuerUser, +"schnupper-betreuer");
|
special_user!(SchnupperBetreuerUser, +"schnupper-betreuer");
|
||||||
special_user!(VorstandUser, +"admin", +"Vorstand");
|
special_user!(VorstandUser, +"admin", +"Vorstand");
|
||||||
special_user!(EventUser, +"manage_events");
|
special_user!(EventUser, +"manage_events");
|
||||||
|
|||||||
115
src/tera/ergo.rs
115
src/tera/ergo.rs
@@ -1,8 +1,7 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::{Datelike, Utc};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
FromForm, Route, State,
|
|
||||||
form::Form,
|
form::Form,
|
||||||
fs::TempFile,
|
fs::TempFile,
|
||||||
get,
|
get,
|
||||||
@@ -10,18 +9,19 @@ use rocket::{
|
|||||||
post,
|
post,
|
||||||
request::FlashMessage,
|
request::FlashMessage,
|
||||||
response::{Flash, Redirect},
|
response::{Flash, Redirect},
|
||||||
routes,
|
routes, FromForm, Route, State,
|
||||||
};
|
};
|
||||||
use rocket_dyn_templates::{Template, context};
|
use rocket_dyn_templates::{context, Template};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use tera::Context;
|
use tera::Context;
|
||||||
|
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
|
activity::ActivityBuilder,
|
||||||
log::Log,
|
log::Log,
|
||||||
notification::Notification,
|
notification::Notification,
|
||||||
role::Role,
|
role::Role,
|
||||||
user::{AdminUser, User, UserWithDetails},
|
user::{AdminUser, ErgoAdminUser, User, UserWithDetails},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@@ -59,7 +59,7 @@ async fn send(db: &State<SqlitePool>, _user: AdminUser) -> Template {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/reset")]
|
#[get("/reset")]
|
||||||
async fn reset(db: &State<SqlitePool>, _user: AdminUser) -> Flash<Redirect> {
|
async fn reset(db: &State<SqlitePool>, _user: ErgoAdminUser) -> Flash<Redirect> {
|
||||||
sqlx::query!("UPDATE user SET dirty_thirty = NULL, dirty_dozen = NULL;")
|
sqlx::query!("UPDATE user SET dirty_thirty = NULL, dirty_dozen = NULL;")
|
||||||
.execute(db.inner())
|
.execute(db.inner())
|
||||||
.await
|
.await
|
||||||
@@ -74,7 +74,7 @@ async fn reset(db: &State<SqlitePool>, _user: AdminUser) -> Flash<Redirect> {
|
|||||||
#[get("/<challenge>/user/<user_id>/new?<new>")]
|
#[get("/<challenge>/user/<user_id>/new?<new>")]
|
||||||
async fn update(
|
async fn update(
|
||||||
db: &State<SqlitePool>,
|
db: &State<SqlitePool>,
|
||||||
_admin: AdminUser,
|
_admin: ErgoAdminUser,
|
||||||
challenge: &str,
|
challenge: &str,
|
||||||
user_id: i64,
|
user_id: i64,
|
||||||
new: &str,
|
new: &str,
|
||||||
@@ -146,47 +146,61 @@ pub struct UserAdd {
|
|||||||
sex: String,
|
sex: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
//#[post("/set-data", data = "<data>")]
|
#[post("/set-data", data = "<data>")]
|
||||||
//async fn new_user(db: &State<SqlitePool>, data: Form<UserAdd>, user: User) -> Flash<Redirect> {
|
async fn new_user(db: &State<SqlitePool>, data: Form<UserAdd>, user: User) -> Flash<Redirect> {
|
||||||
// if user.has_role(db, "ergo").await {
|
if user.has_role(db, "ergo").await {
|
||||||
// return Flash::error(Redirect::to("/ergo"), "Du hast deine Daten schon eingegeben. Wenn du sie updaten willst, melde dich bitte bei it@rudernlinz.at");
|
return Flash::error(Redirect::to("/ergo"), "Du hast deine Daten schon eingegeben. Wenn du sie updaten willst, melde dich bitte bei info@rudernlinz.at");
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// // check data
|
// check data
|
||||||
// if data.birthyear < 1900 || data.birthyear > chrono::Utc::now().year() - 5 {
|
if data.birthyear < 1900 || data.birthyear > chrono::Utc::now().year() - 5 {
|
||||||
// return Flash::error(Redirect::to("/ergo"), "Bitte überprüfe dein Geburtsjahr...");
|
return Flash::error(Redirect::to("/ergo"), "Bitte überprüfe dein Geburtsjahr...");
|
||||||
// }
|
}
|
||||||
// if data.weight < 20 || data.weight > 200 {
|
if data.weight < 20 || data.weight > 200 {
|
||||||
// return Flash::error(Redirect::to("/ergo"), "Bitte überprüfe dein Gewicht...");
|
return Flash::error(Redirect::to("/ergo"), "Bitte überprüfe dein Gewicht...");
|
||||||
// }
|
}
|
||||||
// if &data.sex != "f" && &data.sex != "m" {
|
if &data.sex != "f" && &data.sex != "m" {
|
||||||
// return Flash::error(Redirect::to("/ergo"), "Bitte überprüfe dein Geschlecht...");
|
return Flash::error(Redirect::to("/ergo"), "Bitte überprüfe dein Geschlecht...");
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// // set data
|
// set data
|
||||||
// user.update_ergo(db, data.birthyear, data.weight, &data.sex)
|
user.update_ergo(db, data.birthyear, data.weight, &data.sex)
|
||||||
// .await;
|
.await;
|
||||||
//
|
|
||||||
// // inform all other `ergo` users
|
// inform all other `ergo` users
|
||||||
// let ergo = Role::find_by_name(db, "ergo").await.unwrap();
|
let ergo = Role::find_by_name(db, "ergo").await.unwrap();
|
||||||
// Notification::create_for_role(
|
Notification::create_for_role(
|
||||||
// db,
|
db,
|
||||||
// &ergo,
|
&ergo,
|
||||||
// &format!("{} nimmt heuer an der Ergochallenge teil 💪", user.name),
|
&format!("{} nimmt heuer an der Ergochallenge teil 💪", user.name),
|
||||||
// "Ergo Challenge",
|
"Ergo Challenge",
|
||||||
// None,
|
None,
|
||||||
// None,
|
None,
|
||||||
// )
|
)
|
||||||
// .await;
|
.await;
|
||||||
//
|
|
||||||
// // add to `ergo` group
|
// add to `ergo` group
|
||||||
// user.add_role(db, &ergo).await.unwrap();
|
sqlx::query!(
|
||||||
//
|
"INSERT INTO user_role(user_id, role_id) VALUES (?, ?)",
|
||||||
// Flash::success(
|
user.id,
|
||||||
// Redirect::to("/ergo"),
|
ergo.id
|
||||||
// "Du hast deine Daten erfolgreich eingegeben. Viel Spaß beim Schwitzen :-)",
|
)
|
||||||
// )
|
.execute(db.inner())
|
||||||
//}
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
ActivityBuilder::new(&format!(
|
||||||
|
"{user} nimmt an der Ergo-Challenge teil und hat gerade die Daten eingegeben."
|
||||||
|
))
|
||||||
|
.user(&user)
|
||||||
|
.save(db)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Flash::success(
|
||||||
|
Redirect::to("/ergo"),
|
||||||
|
"Du hast deine Daten erfolgreich eingegeben. Viel Spaß beim Schwitzen :-)",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(FromForm, Debug)]
|
#[derive(FromForm, Debug)]
|
||||||
pub struct ErgoToAdd<'a> {
|
pub struct ErgoToAdd<'a> {
|
||||||
@@ -359,10 +373,7 @@ async fn new_dozen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
routes![
|
routes![index, new_thirty, new_dozen, send, reset, update, new_user]
|
||||||
index, new_thirty, new_dozen, send, reset, update,
|
|
||||||
// new_user
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::{fs::OpenOptions, io::Write};
|
|||||||
|
|
||||||
use chrono::{Datelike, Local};
|
use chrono::{Datelike, Local};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
Build, Data, FromForm, Request, Rocket, State, catch, catchers,
|
catch, catchers,
|
||||||
fairing::{AdHoc, Fairing, Info, Kind},
|
fairing::{AdHoc, Fairing, Info, Kind},
|
||||||
form::Form,
|
form::Form,
|
||||||
fs::FileServer,
|
fs::FileServer,
|
||||||
@@ -13,6 +13,7 @@ use rocket::{
|
|||||||
response::{Flash, Redirect},
|
response::{Flash, Redirect},
|
||||||
routes,
|
routes,
|
||||||
time::{Duration, OffsetDateTime},
|
time::{Duration, OffsetDateTime},
|
||||||
|
Build, Data, FromForm, Request, Rocket, State,
|
||||||
};
|
};
|
||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@@ -20,7 +21,6 @@ use sqlx::SqlitePool;
|
|||||||
use tera::Context;
|
use tera::Context;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
SCHECKBUCH,
|
|
||||||
model::{
|
model::{
|
||||||
logbook::Logbook,
|
logbook::Logbook,
|
||||||
notification::Notification,
|
notification::Notification,
|
||||||
@@ -28,6 +28,7 @@ use crate::{
|
|||||||
role::Role,
|
role::Role,
|
||||||
user::{User, UserWithDetails},
|
user::{User, UserWithDetails},
|
||||||
},
|
},
|
||||||
|
SCHECKBUCH,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) mod admin;
|
pub(crate) mod admin;
|
||||||
@@ -330,13 +331,11 @@ mod test {
|
|||||||
|
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
|
|
||||||
assert!(
|
assert!(response
|
||||||
response
|
|
||||||
.into_string()
|
.into_string()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.contains("Ruderassistent")
|
.contains("Ruderassistent"));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[sqlx::test]
|
#[sqlx::test]
|
||||||
|
|||||||
@@ -15,10 +15,7 @@
|
|||||||
class="link-primary">Überblick der Challenges</a>
|
class="link-primary">Überblick der Challenges</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="py-1">
|
<li class="py-1">
|
||||||
Eintragung ist jederzeit möglich, alle Daten die bis Sonntag 23:59 hier hochgeladen wurden, werden gesammelt an die Ister Ergo Challenge geschickt
|
Eintragung ist jederzeit möglich, wenn du sie auch an die offizielle Liste schicken willst, kannst du das <a href="https://data.ergochallenge.at/" target="_blank" style="text-decoration: underline">hier</a> machen
|
||||||
<li class="py-1">
|
|
||||||
Montag → gemeinsames Training; bitte um <a href="/planned" class="link-primary">Anmeldung</a>, damit jeder einen Ergo hat
|
|
||||||
</li>
|
|
||||||
<li class="py-1">
|
<li class="py-1">
|
||||||
<a href="https://data.ergochallenge.at"
|
<a href="https://data.ergochallenge.at"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -194,7 +191,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
{% if "admin" in loggedin_user.roles %}
|
{% if "admin" in loggedin_user.roles or "ergo-admin" in loggedin_user.roles %}
|
||||||
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow grid gap-3">
|
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow grid gap-3">
|
||||||
<h2 class="h2">Update</h2>
|
<h2 class="h2">Update</h2>
|
||||||
<details class="p-2">
|
<details class="p-2">
|
||||||
@@ -233,6 +230,14 @@
|
|||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
<div class="mt-3 text-right">
|
||||||
|
<a href="/ergo/reset"
|
||||||
|
class="w-28 btn btn-alert"
|
||||||
|
onclick="return confirm('Willst du wirklich alle Ergo-Eingaben löschen?');">
|
||||||
|
{% include "includes/delete-icon" %}
|
||||||
|
Einträge löschen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user