Commit Graph

87 Commits

Author SHA1 Message Date
HolgerHatGarKeineNode 76787a1bee Render the handoff page directly instead of 302-redirecting to /app/auth
Chrome follows a server 302 internally and never dispatches the /app/auth
App Link, so the handoff page stayed in the browser and the token never
reached the app. The signed callback (and complete/confirm) now render
the handoff page directly with the einundzwanzig:// deep-link button — the
signer opens the callback in the browser, the user lands on the handoff
page and taps once to return to the app, which stores the token.
2026-06-12 00:29:44 +02:00
HolgerHatGarKeineNode 7e491326a9 Build the Nostr signer URI in the browser, not server-side
Server-side percent-encoding (rawurlencode/http_build_query) produced a
nostrsigner: URI that Amber rejected as malformed. The launcher view now
assembles it in JS with encodeURIComponent(JSON.stringify(event)) — the
exact encoding Amber accepts (verified working earlier in the session).
The controller only passes k1 and the callback URL.
2026-06-11 22:40:57 +02:00
HolgerHatGarKeineNode 58c7e410b0 Add headless Nostr launcher page for the mobile app
A direct ACTION_VIEW intent to nostrsigner: (Browser::open from the app)
lacks category.BROWSABLE, so Amber routes it into its app-to-app path
and rejects it as malformed. The app instead opens /auth/mobile/nostr in
an in-app browser; that page fires the signer via window.location, so
the intent carries BROWSABLE and Amber uses its web-signing flow. No
visible login UI, local signing, token returned via the App Link.
2026-06-11 22:08:17 +02:00
HolgerHatGarKeineNode 7531f28f22 Add verified App Link handoff and mobile token exchange endpoint
Replaces the custom-scheme auto-redirect (which triggers Chrome's
confirmation prompt) with a verified Android App Link handoff:

- public/.well-known/assetlinks.json for space.einundzwanzig.mobile
  (debug cert fingerprint; add the release cert before store builds)
- GET /app/auth handoff: opens the app directly when the App Link is
  verified; renders a button-based fallback page otherwise
- POST /api/mobile/token: trades a NIP-55-signed login event for a
  Sanctum token — used when Amber's callback opens the app directly
- complete/confirm/signedCallback now redirect to the handoff URL
2026-06-11 19:51:14 +02:00
HolgerHatGarKeineNode 4aba1514e9 Make the NIP-55 signer callback robust against Amber URL rewriting
Amber drops the query string when it rebuilds the callback URL and
appends the signed event directly to the path. The mobile login page now
hands out path-based callback URLs (/auth/mobile/signed/{k1}/) so the
event arrives as the remainder of the path.

The new callback runs in the web middleware group: the signer opens it
in the system browser, which shares cookies with the in-app browser
session, so the flow completes immediately — a bridge page issues the
token and fires the einundzwanzig:// deep link. The LoginKey row is
still written as a fallback for the polling login page.
2026-06-11 18:43:59 +02:00
HolgerHatGarKeineNode 07169dfee6 Add mobile app auth flow with Sanctum token handoff via deep link
The Einundzwanzig mobile app opens /auth/mobile in an in-app browser.
After a Lightning (LNURL) or Nostr login the flow issues a personal
access token and hands it back via the einundzwanzig://auth deep link.

- New auth.mobile-login Livewire view: Lightning QR (shared k1) plus
  Nostr signing via NIP-55 Android signers (Amber) with server callback,
  and a confirmation screen for already authenticated sessions
- MobileAuthController: NIP-55 callback verification, completion route
  issuing the token (replacing same-device tokens), redirect whitelist
- Nostr login event verification and npub user resolution extracted to
  App\Support\NostrLogin, now shared with the interactive login
- GET /api/user (auth:sanctum) returns the token owner's profile
2026-06-11 18:01:50 +02:00
HolgerHatGarKeineNode f5cf85b438 Add restore_point functionality to Meetups
- 💾 Introduced `restore_point` JSON column in `meetups` table for saving and restoring master data.
- 🛠️ Added methods `captureRestorePoint` and `restoreFromRestorePoint` to `Meetup` model for managing restore points.
- 🔒 Implemented authorization for updating meetups via `updateViaPortal` policy to include pivot members.
- 🔗 Created Artisan commands `meetups:snapshot` and `meetups:restore` for managing restore points from CLI.
- 🚦 Added rate limiter to restrict excessive update attempts in Livewire meetup editing.
-  Developed exhaustive feature tests for snapshot and restore actions, portal editing rules, and rate limiting.
2026-06-10 10:56:38 +02:00
HolgerHatGarKeineNode 8c68b19138 Add Super-Admin tools for managing any model
- 🛠️ Introduced generic Super-Admin MCP tools, including `list-models`, `describe-model`, `list-records`, `show-record`, `create-record`, and `update-record`.
- 🛡️ Restricted modification of critical fields (e.g., passwords, roles, tokens) to enhance security.
-  Added extensive feature tests for Super-Admin functionality and access control.
- 📜 Increased pagination length to accommodate new tools on a single page.
- 🔗 Registered Super-Admin tools in `EinundzwanzigServer`.
2026-06-08 13:39:04 +02:00
HolgerHatGarKeineNode 3a507cced2 Enhance meetup association and permissions management
- 🔍 Added `resolveInScope` method to `ResolvesEntities` for scoped entity resolution with stricter control.
- 👥 Introduced `AddMeetupToMineTool` MCP tool for adding external meetups to "My Meetups."
- 🛠️ Updated `ListMyMeetupsTool` and `ShowMyMeetupTool` to include both created and joined meetups.
- 📚 Updated `Meetup` model with `associatedWith` scope for querying user-related meetups.
-  Expanded feature tests for meetup membership, creator permissions, and scoped tool usage.
- 🛡️ Unified access checks across Livewire and APIs to restrict editing meetup details to creators or super-admins.
- 🔗 Registered `AddMeetupToMineTool` in `EinundzwanzigServer`.
2026-06-08 11:59:02 +02:00
HolgerHatGarKeineNode dc2b828777 Add SearchMeetupsTool for duplication prevention
- 🔍 Introduced `SearchMeetupsTool` to find existing meetups by name or city before creating new ones.
- ☑️ Updated `CreateMeetupTool` description and logic to enforce pre-checks for existing meetups.
- 🛠️ Adjusted `EinundzwanzigServer` to include `SearchMeetupsTool` in tools list.
-  Added feature tests to verify meetup search functionality and ensure duplication avoidance.
2026-06-08 11:10:29 +02:00
HolgerHatGarKeineNode ab8b91a0af Ensure all tools fit on a single tools/list page
- 📜 Increased `defaultPaginationLength` to 100 to accommodate all tools on a single page.
-  Added feature test to confirm pagination settings align with client requirements.
2026-06-08 10:54:18 +02:00
HolgerHatGarKeineNode b6f05bca41 Add ResolvesEntities concern for name-based ID resolution
- 🤖 Introduced `ResolvesEntities` trait to simplify entity resolution by name or ID across MCP tools.
- 📚 Updated tools (Meetups, Cities, Venues, Courses, Lecturers) to use the concern for resolving related entities (e.g., courses, venues, lecturers).
- 🎯 Enhanced tool descriptions and schemas for better name-based parameter handling with fallback support for IDs.
-  Added dedicated feature tests for name resolution logic, partial matches, and error handling scenarios.
2026-06-08 10:35:16 +02:00
HolgerHatGarKeineNode d0544bfac9 Add OAuth functionality, MCP tools, and feature tests
- 🔒 Added migrations for `oauth_access_tokens`, `oauth_refresh_tokens`, `oauth_auth_codes`, `oauth_clients`, and `oauth_device_codes`.
- 🤖 Created MCP tools (Meetups, Cities, Venues, Courses, Lecturers) for managing entities with authentication and validation.
- 🛠️ Implemented Passport-backed OAuth API guard configuration and validation endpoints.
-  Added comprehensive feature tests for MCP tools and OAuth functionality (access control, validation, and token-based authentication).
2026-06-08 09:37:00 +02:00
HolgerHatGarKeineNode 3cad5f5636 **Enhance input validation and error handling across APIs**
- 🛠️ Refactored controllers to utilize `FiltersNumericIds` concern, ensuring secure numeric ID filtering and avoiding type-sensitive errors in queries.
-  Added feature tests to validate robust input hardening for non-numeric or malformed query parameters (`user_id`, `selected[]`).
- 🔒 Introduced `PublicPropertyNotFoundException` handling in Livewire, returning 400 for invalid property probes and suppressing unnecessary log entries.
-  Updated `MeetupEventController` to handle invalid date formats gracefully, aborting with a 400 response instead of 500.
-  Expanded exception handling pipeline for enhanced resilience against malformed input, bot noise, and exploitable probes.
2026-06-08 02:53:44 +02:00
HolgerHatGarKeineNode 3b93e22e95 **Add authenticated API endpoints for managing Meetups, Cities, Venues, and Lecturers**
-  Introduced `store`, `update`, `mine`, and `mineShow` endpoints for `Meetups`, `Cities`, `Venues`, and `Lecturers` with validation and authorization.
- 🔒 Added `Policies` for `Meetups`, `Cities`, `Venues`, and `Lecturers` leveraging `ChecksCreatorOwnership` for ownership checks.
- 🌐 Created `Resources` for structured API responses: `MeetupResource`, `CityResource`, `VenueResource`, and `LecturerResource`.
-  Added dedicated `Request` classes for input validation: `Store` and `Update` variants for all models.
- 🛠️ Updated controllers to support new functionalities with localized error messages and proper HTTP responses.
2026-06-08 01:58:37 +02:00
HolgerHatGarKeineNode 7510946f38 **Add public API documentation access and navigation links**
- 📚 Added "API Dokumentation" link to the sidebar and header navigation.
- 🔓 Defined `viewApiDocs` gate for public access to API documentation.
-  Added feature tests for API documentation route accessibility and OpenAPI document serving.
2026-06-08 01:34:00 +02:00
HolgerHatGarKeineNode 3875e127e4 🔥 **Remove Highscore and Bindle features**
- 🗑️ Deleted `Highscore` feature (Model, Controller, Factory, Tests, Routes, Migrations) and associated logic.
- 🗑️ Removed `BindleController` and its related test.
- 🧹 Cleaned up unused routes, database seeders, and localization references.
- 🚫 Deprecated inactive book rental guide component and associated views.
2026-06-08 01:08:07 +02:00
HolgerHatGarKeineNode 351dd87fa9 **Enhance API functionality and localizations**
- 🌐 Added API documentation annotations for multiple controllers (Meetups, Cities, Countries, Courses, Highscores, Venues), improving public and developer-facing endpoint clarity.
-  Integrated and configured the `dedoc/scramble` package for automated OpenAPI documentation generation.
- 🔒 Excluded internal routes and actions from API documentation using `ExcludeRouteFromDocs` attributes.
- 🌍 Added new localization keys for API Token features across multiple languages (`lv`, `es`, etc.).
- 🛠️ Introduced `Group`, `Response`, and `QueryParameter` attributes for better request descriptions and structured documentation.
- 🚀 Enhanced functionality for listing operations in controllers with filters and query parameters like `search` and `selected`.
2026-06-08 00:09:59 +02:00
HolgerHatGarKeineNode 4c81e20529 feat(settings): API token management UI for users
Adds a "API Tokens" settings page so an authenticated user can create and
revoke Sanctum personal access tokens for the new authenticated write
endpoints — using the official Sanctum API ($user->createToken() / tokens()).

- New Volt component settings/api-tokens (create token, one-time plain-text
  reveal with copy-to-clipboard, list + revoke own tokens).
- Registered route settings.api-tokens (country-prefixed, auth group) and
  added a nav entry in the settings layout.
- SEO definition for the new page.
- Pest feature tests (create/reveal-once, validation, revoke, ownership
  scoping) and a Pest browser screenshot test.
2026-06-07 22:56:28 +02:00
HolgerHatGarKeineNode a3062f6c4e feat(api): authenticated course & course-event write endpoints
Implements Sanctum-authenticated write endpoints so a lecturer can create
and update their own courses and dated course events programmatically
(e.g. to keep the portal's course events in sync with an external system).

- CourseController@store / @update implemented (validation mirrors the
  Livewire course create form; create requires is_lecturer, update is
  restricted to the owner or a super-admin).
- New CourseEventController with index/store/update. index returns only the
  authenticated user's own events (optional ?course_id= filter) for
  idempotent syncing; validation mirrors the Livewire course event form.
- Public `courses` API resource narrowed to index/show; all writes moved
  behind an `auth:sanctum` route group (the previous store/update/destroy
  actions were empty no-ops).
- Pest feature test covering auth (401), authorization (403/is_lecturer/
  ownership), creation (201), validation (422) and ownership-scoped listing.

Ported from Einundzwanzig-Podcast/einundzwanzig-portal#25, adapted to this
repo's conventions (inline authorization instead of policies, Pest tests,
validation mirroring the current Livewire forms) while keeping the same
endpoint outputs.

Co-authored-by: schnuartz-ai <schnuartz@gmail.com>
2026-06-07 22:14:29 +02:00
HolgerHatGarKeineNode 3a8775fa52 🛡️ **Add robust Livewire payload validation and throttling**
-  Implemented handling for `CorruptComponentPayloadException` to prevent logging noise and improve exception management.
- 🛠️ Added IP-based throttling (120 requests/min) for the `/livewire/update` endpoint with middleware integration for better traffic control.
-  Introduced unit tests to validate throttle settings and middleware application.
- 🧪 Enhanced tests for ensuring silent handling of corrupt payload scenarios and reduced log noise.
2026-06-04 11:45:02 +02:00
HolgerHatGarKeineNode 35ddad365c **Add Latvian translations and enhance localization support**
-  Added Latvian (`lv`) JSON translations and validation/password localization files.
- 🛠️ Updated `lang-country` config to include `lv-LV` and centralized language definitions.
- 🌐 Extended sidebar and navigation with new translatable strings.
-  Introduced 72px width customization for the sidebar layout.
-  Improved timezone mapping in Nostr publishing commands (added `Europe/Riga`).
- 🛠️ Refactored language selector to dynamically source available languages from the config.
2026-05-20 11:45:50 +02:00
HolgerHatGarKeineNode 308cd8a611 🚀 **Automate Meetup Activity Recalculation**
- Introduced `recalculateActivity` method in `Meetup` model to centralize activity and event timestamp updates.
- Added `MeetupEventObserver` to trigger activity recalculation on event save/delete.
- Updated `/meetups:update-activity` command to leverage the new model method for cleanup.
- Enhanced tests to cover various `MeetupEvent` scenarios affecting activity states.
2026-05-17 18:13:37 +02:00
HolgerHatGarKeineNode 71a4898303 🎉 **Introduce meetup activity management**
- Added `is_active` and `last_event_at` fields to meetups with migration.
- Enhanced UI: Display `Aktiv`/`Inaktiv` badges and last event dates across dashboard, tables, and maps.
- Introduced `/meetups:update-activity` command to manage activity flags and timestamps.
- Validated latitude/longitude to prevent `0,0` inputs in city creation and updates.
- Updated factories and tests to include meetup activity states (`active`, `inactive`).
2026-05-17 17:57:16 +02:00
HolgerHatGarKeineNode 52276ee682 🗑️ **Remove redundant Laravel Boost guidelines**
Deleted unused Laravel Boost guideline files (`laravel-boost.md` and `laravel-boost.mdc`) to clean up the repository.
2026-05-17 15:08:01 +02:00
Claude d46c0161fe security: medium-severity fixes (proxies, ssrf, uploads, lnurl, github_data)
- Trust the Forge reverse proxy and force https URLs in production so
  generated absolute URLs match the actual TLS termination.
- Reject Nostr profile photo URLs that aren't http(s) or that resolve to
  loopback / private (RFC1918) addresses to close an SSRF vector in
  FetchNostrProfileJob.
- Tighten image upload validation across meetup, course, and lecturer
  create/edit components: explicit mimes whitelist (jpeg, png, webp),
  max 5 MiB, and dimension cap of 4000x4000.
- Replace the silent "skip if exists" branch in LnurlAuthController with
  updateOrCreate so concurrent callers cannot race on the k1 record.
- Validate github_data on Meetup edit, decoding the JSON, and keep only
  the whitelisted keys (top, left, state) with strict type coercion to
  prevent storing arbitrary attacker-controlled JSON.
2026-05-03 12:57:57 +00:00
Claude 9b81f6cd92 security: high-severity fixes (api throttle, fillable, idor, path, rel)
- Add 60 req/min throttle to the public API group and a stricter 10 req/min
  throttle to POST /highscores.
- Replace mass-assigned $guarded=[] with explicit $fillable on User, Meetup,
  Course, Lecturer, and SelfHostedService. created_by stays out of the
  whitelist; the existing creating() hooks continue to populate it.
- Require authenticated user on Api/MeetupController::index instead of
  trusting the user_id query parameter (IDOR).
- Constrain the /img and /img-public route paths to a safe character set
  and reject any path containing ".." in ImageController.
- Add rel="noopener noreferrer" to every target="_blank" link on the meetup
  and course landing pages.
2026-05-03 12:55:09 +00:00
Claude 90835f8b1f security: critical fixes (test route, edit authz, nostr signature, calendar IDOR)
- Remove unauthenticated /test route that dispatched FetchNostrProfileJob
  for a hardcoded user (routes/web.php).
- Enforce created_by ownership check in meetup and lecturer Livewire edit
  components; mirror the existing services/edit pattern.
- Replace blind-trust nostrLoggedIn handler with NIP-42-style signed event
  verification: server-issued challenge stored in session, client signs a
  kind:22242 event, server verifies signature via swentel/nostr-php and
  derives npub. Challenge is single-use with 5-minute TTL.
- Validate the ?my[] parameter on the calendar download endpoint as an
  array of integers and intersect with the authenticated user's meetups.
2026-05-03 12:51:10 +00:00
BT 04e3e30fcf 🔥 **Cleanup:** Removed BookCase and OrangePill models, factories, migrations, and related references. Added tests for new service and meetup creation flows. Updated PHPUnit settings and browser-specific configurations. 2026-05-02 22:00:26 +01:00
BT 1f0bfba0d3 🎨 **Style:** Fixed indentation inconsistencies in meetup_user migration file.
🛠️ **Factory:** Created factories for `TwitterAccount`, `EmailCampaign`, `EmailTexts`, and `BookCase`.
 **Helper:** Added `NostrHelper` with methods for generating fake/mocked Nostr data.
⬆️ **Dependencies:** Updated multiple Composer dependencies including `laravel/framework`, `astrotomic/laravel-translatable`, and others to their latest versions.
2026-05-02 19:17:02 +01:00
HolgerHatGarKeineNode 2f5ca24da0 🔥 Remove Laravel Sail, Docker, and related setup, migrate to simplified local development environment
- **Removed:** `docker-compose.yml`, Sail-specific Dockerfiles, and related scripts for PHP 8.3 setup.
- **Updated:** Documentation to reflect a shift from Docker to a direct PHP-based local development workflow.
- **Removed:** `laravel/sail` dependency from `composer.lock`.
- **Implemented:** `#[Locked]` Livewire attribute across components for read-only properties.
- **Added:** Feature tests to ensure locked properties cannot be tampered with.
2026-02-11 21:10:09 +01:00
HolgerHatGarKeineNode bb84117fd1 🛠️ Remove deprecated Forge deployment workflow, add logic for fetching missing highscore names via Nostr, and update API routes with refined highscore endpoints. 2026-02-02 12:36:00 +01:00
HolgerHatGarKeineNode 6dd04dee30 🏆 Add highscore feature with API endpoints, validations, and tests
- **Added:** Endpoints for submitting highscores (`highscores.store`) and retrieving the leaderboard (`highscores.index`).
- **Implemented:** Validation rules via `StoreHighscoreRequest` to ensure highscore integrity.
- **Included:** `Highscore` model, migration, and factory for data handling and seeding.
- **Enhanced:** Comprehensive feature tests covering submission, updating, retrieval, and payload validation.
2026-02-02 12:27:01 +01:00
HolgerHatGarKeineNode 32e327cd9c 🔥 Remove redundant PRD.md file, update media models with stricter MIME type validation, and refine media conversion settings. 2026-01-25 23:54:44 +01:00
HolgerHatGarKeineNode da1324adda 🗓️ Add MeetupEventFactory, implement rate limiting for calendar downloads, and enhance test coverage
- **Added:** `MeetupEventFactory` for generating test data.
- **Implemented:** Rate limiting (`throttle:calendar`) for `stream-calendar` routes to prevent abuse.
- **Enhanced:** `DownloadMeetupCalendar` controller with validation and cleaner query structure.
- **Added:** Feature tests for calendar downloading, invalid input handling, and rate limiting.
2026-01-17 21:18:55 +01:00
HolgerHatGarKeineNode 7f92e77684 🔥 Remove unused tests, update factories, and introduce recurrence features
- **Removed:** Unused feature and component tests to clean up the codebase.
- **Added:** `RecurrenceType` enum for handling event recurrence modes.
- **Introduced:** City, Country, and Meetup factories for test data generation.
- **Implemented:** Migration to support recurring event fields in `meetup_events` table.
- **Enhanced:** Livewire meetup events creation with recurrence validation and preview logic.
- **Updated:** PHPUnit test suite configuration and composer dependencies for `pestphp/pest@v4.3`.
- **Refined:** SEO configuration (`favicon`) to standardize icon format.
2026-01-17 21:00:46 +01:00
HolgerHatGarKeineNode 74263a4581 🔒 Add manual hex validation for LNURL-Auth parameters k1 and key with improved test coverage 2026-01-17 17:25:21 +01:00
HolgerHatGarKeineNode e5ea65fa77 🔑 Implement LNURL-Auth support with error handling, frontend polling, and test coverage
- Added `LnurlAuthController` to handle LNURL authentication flow with signature verification, user creation, and session expiry checks.
- Integrated authentication error polling in `nostrLogin.js`.
- Added `LoginKeyFactory` for testing and database seed purposes.
- Created feature tests (`LnurlAuthTest`) to validate LNURL callback, error responses, and session handling.
- Extended `login.blade.php` with dynamic error handling and reset logic for expired sessions.
2026-01-17 15:23:38 +01:00
HolgerHatGarKeineNode 233402dace 🔧 Add .gitignore for static assets and update comment language in DomainMiddleware 2025-12-14 20:53:09 +01:00
HolgerHatGarKeineNode 1ccbd0a9ed 🔄 Refactor domain image helper to return attributes array
🖼️ Update SEO data to use domain attributes instead of single image
🌐 Adjust meetup model and blade component to use new domain attributes function
2025-12-11 16:35:46 +01:00
HolgerHatGarKeineNode deeb1f066d 🌍 Update timezone fallback for Nostr publishing command to use Europe/Berlin 2025-12-11 02:13:17 +01:00
HolgerHatGarKeineNode a8f1257948 🌍 Add Austria and Switzerland to German locale mapping in Nostr publishing command
- Extend `TZ_MAP` and `DOMAIN_MAP` with `'at'` and `'ch'` entries pointing to `Europe/Berlin` and `portal.einundzwanzig.space` respectively.
- Adjust locale setting logic to use `'de'` for Austria and Switzerland, ensuring consistent language handling in Nostr publishing flows.
2025-12-10 03:46:14 +01:00
HolgerHatGarKeineNode 52c05e6d47 ⚙️ Preserve user language preference in DomainMiddleware and adjust locale‑setting logic to avoid overwriting existing session values while still configuring domain‑specific app settings. 2025-12-09 23:41:44 +01:00
HolgerHatGarKeineNode c6c524a876 ⚙️ Update PublishUnpublishedItems to run hourly and only publish MeetupEvents starting within the next 7 days. 2025-12-09 23:02:43 +01:00
HolgerHatGarKeineNode d263eaf92d 🛠️ Add German portal domain and refactor URL handling for Nostr publishing
- Introduce `portal.einundzwanzig.space` with locale settings in `DomainMiddleware`.
- Replace verbose if‑chain in `NostrTrait::getUrl()` with a concise match expression.
- Add `DOMAIN_MAP` constant to `PublishUnpublishedItems` and force URL generation to use the correct domain per country.
- Update command flow: set domain before configuring timezone/locale.
2025-12-09 22:45:26 +01:00
HolgerHatGarKeineNode 2a70537fcb 🛠️ Add Nostr publishing support with i18n templates and country‑specific configuration for domain handling and CLI command 2025-12-09 22:35:54 +01:00
HolgerHatGarKeineNode c4cea2ae7a 🛠️ Add anon column to self-hosted services: Update views, models, forms, and migrations to support anonymous service creation and display 2025-12-07 06:16:47 +01:00
HolgerHatGarKeineNode 980b833e80 🌐 Enhance service management: Add IP address field to forms, views, and database schema 2025-12-07 06:04:52 +01:00
HolgerHatGarKeineNode 4e150518a4 🛠️ Remove deprecated LNbits enum and related references from SelfHostedServiceType 2025-12-07 05:18:54 +01:00
HolgerHatGarKeineNode 14f717a2b9 🛠️ Refactor service components: Add dynamic type filters, restructure landing page UI, and introduce ServiceForm for improved form handling and validations 2025-12-07 01:06:20 +01:00