- 🗃️ Updated `meetup_events` table to allow `recurrence_interval` as nullable (default: 1).
- ✅ Added test to verify single event creation with explicit null recurrence fields.
- 🔥 Removed deprecated `placeholder-pattern` component.
- 🧹 Simplified and cleaned up Blade views by removing unused comments and sections.
- 🗂️ Extracted `SetsCreatedBy` concern for DRY and reused it across models.
- 🔧 Consolidated configuration for Horizon `authorized_nostr_keys`.
- 🧪 Migrated media conversion to use new Spatie enums for clarity.
- ♻️ Replaced repetitive link rendering with dynamic rendering in meetups and services views.
- ➕ Introduced reusable `<x-service-disclaimer>` component for better clarity and consistency on service pages.
- 🔗 Added `njump` links for Nostr profile validation in service listings and landing pages.
- 🧪 Included feature tests to verify disclaimer visibility and correct `njump` link rendering or omission for anonymous services.
- 🔒 Ensure leader users are required for Meetup event tests to simulate accurate permissions.
- ➕ Add `editableBy` scope to `MeetupEvent` model for consistent editable event handling.
- 🛠️ Refactor `mine` API endpoint and MCP tool to leverage `editableBy` scope.
- 🧪 Update tests to verify leader-based accessibility for Meetup events.
- 🔧 Refactor `CountryController` to safely process non-numeric values in `selected` query parameter using `array_filter`.
- 🧪 Add feature test to ensure API does not crash when `selected` includes non-numeric codes.
- ➕ Extend `withDetails` flag in Lecturer API to include `next_event` (date of the next course event or null).
- 🧪 Update feature tests to assert presence and validity of `next_event` in responses.
- ➕ Introduced `leaders`, `promoteLeader`, and `demoteLeader` methods in `Meetup` model for consistent handling of meetup leadership.
- ⚙️ Refactored `MeetupLeaderController` to use new leadership methods, improving reusability and maintainability.
- 👮♂️ Added `ValidNpub` validation rule for npub input standardization.
- 🧪 Added feature tests for leadership delegation and permissions.
- 🖼️ Implemented leader management UI in meetup edit page with flash messaging for actions.
- 🔒 Restrict event creation, editing, and deletion to Meetup leaders (`is_leader`) and creators for consistency across APIs, frontend, and MCP.
- ➕ Add new APIs for leader delegation: assign/remove Meetup leaders via `meetup_user.is_leader`.
- 🛠️ Replace loose member checks with specific leadership checks in policies, controllers, and views.
- 🧪 Add exhaustive tests to ensure only eligible leaders execute critical actions (e.g., event creation/edit, Meetup updates).
- 🔄 Refactor pivot relationships and models (`leadByMe`, `isLeader`) for explicit leadership handling.
- ✨ Introduce artisan command `meetups:promote-existing-leaders` to transition legacy data.
- 🧹 Add `places:cleanup` console command for dry-run and forced deletion of venues (without course/bitcoin events) and cities (without venues/meetups).
- 🧪 Add feature tests for `places:cleanup`, covering dry-run, forced deletion, and scenarios ensuring retention of dependent records.
- ➕ Add `bitcoinEvents` relationship to `Venue` model to support cleanup logic.
- 🧹 Introduce `lecturers:cleanup` command to delete lecturers without associated courses or events, merging their items into "Einundzwanzig."
- ⚙️ Add `update` method to `UserController` for handling profile updates, allowing name changes while restricting role modifications.
- 🌐 Register `PATCH /api/user` route for profile updates and update related API tests.
- 🧪 Add feature and console tests for `lecturers:cleanup`, covering dry-run, forced deletion, and edge cases.
- ➕ Introduce `attendeesCount` and `mightAttendeesCount` methods in `MeetupEvent` model for cleaner attendee calculations.
- 🛠️ Refactor API responses to use attendee count helpers in `Meetup` and `MeetupEventController`.
- 🧪 Update tests to validate JSON structure with attendee-related fields (`id`, `attendees`, `might_attendees`).
- 🏷️ Introduce `RsvpStatus` enum for managing attendance states (`attending`, `maybe`, `none`).
- ✏️ Add `MeetupEventController` methods for RSVP actions (`rsvpStatus`, `rsvp`) and payload handling.
- ✨ Implement RSVP helpers in `MeetupEvent` model for user-specific attendance management.
- 🌐 Register RSVP routes for showing and updating attendance in the API.
- 🧪 Add feature tests for RSVP actions, covering validation, idempotency, and correct list handling.
- 🔒 Introduce `removeFromMine` policy for authenticated users to remove meetups.
- ✏️ Add `removeFromMine` method in `MeetupController` with idempotent handling.
- ✨ Add `removeMember` utility in `Meetup` model for managing pivot relationships.
- 🧪 Add feature tests for `removeFromMine`, covering idempotency, permissions, and unknown slugs.
- 🌐 Register `removeFromMine` route in API and link it to `MeetupController`.
- **A1**: Added image uploads (Meetup logo, Lecturer avatar, Course logo) via `HandlesImageUpload` with reusable `<x-image-picker>` component and Saloon multipart requests.
- **A2**: Introduced Recurrence UI in Event Editor with support for `weekly`, `monthly`, and `custom` recurrence types, aligning with portal capabilities.
- Fixed `myCourseEvents` API response handling (`data` wrapper) for consistency.
- ✨ Added `StoreCourseRequest` and `UpdateCourseRequest` for structured validation.
- ✨ Introduced `StoreCourseEventRequest` and `UpdateCourseEventRequest` for consistent request validation.
- 🖼️ Created `CourseResource` and `CourseEventResource` for API responses.
- 🔄 Refactored `CourseController` and `CourseEventController` to use Policies and FormRequests.
- ✨ Added dedicated `uploadLogo` and `uploadAvatar` API endpoints with shared media validation.
- 🚀 Improved API by aligning Course and CourseEvent behavior with other entities.
- 🔒 Introduce `addToMine` policy for authenticated users to add existing meetups.
- ✏️ Add `addToMine` method in `MeetupController` with idempotent handling.
- ✨ Include `addMember` utility in `Meetup` model for managing pivot relationships.
- 🛠️ Refactor `AddMeetupToMineTool` to use `addMember` for consistency.
- 🧪 Add feature tests for `addToMine`, covering idempotency, permissions, and unknown slugs.
- 🌐 Register `addToMine` route in API and link it to `MeetupController`.
- 🔄 Replace `ilike`/`like` conditions with `whereLike` in API controllers and search tools for consistency.
- 🚀 Enhance query usability by ensuring cross-database compatibility (PostgreSQL and SQLite).
- ✏️ Updated `MeetupController` to include `with('media')` for meetups query.
- 🖼️ Added `logo` to `MeetupResource` via `getFirstMediaUrl`.
- 🧪 Extended feature tests to validate `logo` presence and type in API responses.
- ✏️ Adjust `mine` method to fetch meetups based on dashboard selections (`meetup_user` pivot).
- ✏️ Add `viewMine` policy to control access to individual meetups for pivot members.
- 🧪 Update feature tests to reflect pivot-based logic for "My Meetups."
- ✏️ Added feature tests for cities and venues, including pagination limits and `withDetails` parameter handling.
- ✏️ Updated `CityController` to support `withDetails`, returning country code and flag URL while lifting pagination limits.
- ✏️ Updated `VenueController` to support `withDetails`, lifting pagination limits and enriching venue responses with city details.
- 🚀 Introduced feature tests for courses and lecturers, covering pagination limits, detailed data retrieval, and 404 responses.
- ✏️ Updated `CourseController` to support `withDetails` for courses, including lecturer and next event data.
- ✏️ Updated `LecturerController` to support `withDetails` for lecturers, including future events count.
- ⚙️ Expanded routes to include `show` endpoints for courses and lecturers.
A signer-owned Custom Tab never reliably displayed the browser handoff
page, so the token never returned. The Nostr launcher now uses the app's
custom scheme as the callback (einundzwanzig://signed/{k1}/): Amber opens
it directly after signing and the app exchanges the event for a token via
/api/mobile/token — no browser handoff in the loop.
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.
Amber v6.2.0 rejects a plain nostrsigner: navigation as malformed: it
reads the signer parameters from intent extras, which a window.location
navigation cannot set. An intent:// URL lets the launcher pass the event
as the data URI plus type/returnType/appName/callbackUrl as S.* extras,
so Amber accepts the request and shows its sign dialog. The query is also
kept on the data URI for the EXTRA_APPLICATION_ID web flow.
Note: on the emulator with Amber v6.2.0 the post-approval callback did
not always fire (Amber returns via setResult when callingPackage != null);
needs verification on a real device.
Amber v6.2.0 routes nostrsigner: intents by EXTRA_APPLICATION_ID: present
(web flow, reads the event from the URI) vs absent (app-to-app flow,
reads type/event from intent extras → rejects our URI as malformed).
Browsers only attach that extra when the external-app launch comes from a
user gesture, so the auto-redirect on page load always failed. The
launcher now waits for the user to tap "Mit Amber signieren".
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.
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.
The Nostr login is now driven entirely by the app (it launches the
NIP-55 signer via an ACTION_VIEW intent and posts the signed event to
/auth/mobile/signed), so the portal page no longer needs window.nostr or
an Amber button — it only renders the Lightning QR. The path-based
signer callback and token exchange endpoints remain server-side.
Replaces the fragile NIP-55 intent/callback round-trip with the same
mechanism the desktop login uses: openNostrLogin signs the session
challenge via window.nostr — provided by an extension or by
window.nostr.js over a persistent NIP-46 connection (Amber pairing with
permissions). The listener stores a LoginKey for the page's k1 and
navigates to the completion route, which issues the token and redirects
into the app via the verified App Link handoff.