From 2bb954979fc091d3946c810c64421a522467fcea Mon Sep 17 00:00:00 2001 From: Dave Earley Date: Sat, 15 Jun 2024 21:02:19 -0700 Subject: [PATCH 1/6] Update pull request template and add CLA bot action --- .github/pull_request_template.md | 13 +++++++++++++ .github/workflows/cla.yml | 30 ++++++++++++++++++++++++++++++ CLA.md | 23 +++++++++++++++++++++++ CONTRIBUTING.md | 4 ++++ 4 files changed, 70 insertions(+) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/cla.yml create mode 100644 CLA.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..8c16b2d6 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +Thank you for creating a PR! We appreciate your contribution to Hi.Events. + +To make the process as smooth as possible, please ensure you've read the [contributing guidelines](https://github.com/HiEventsDev/hi.events/blob/develop/CONTRIBUTING.md) before proceeding. + +Please include a summary of the changes and the issue being fixed or the feature being added. The more detail, the better! + +## Checklist + +- [ ] I have read the contributing guidelines. +- [ ] My code is of good quality and follows the coding standards of the project. +- [ ] I have tested my changes, and they work as expected. + +Thank you for your contribution! 🎉 diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml new file mode 100644 index 00000000..6f23c1e2 --- /dev/null +++ b/.github/workflows/cla.yml @@ -0,0 +1,30 @@ +name: "CLA Assistant" +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened,closed,synchronize] + +permissions: + actions: write + contents: read + pull-requests: write + statuses: write + +jobs: + CLAAssistant: + runs-on: ubuntu-latest + steps: + - name: "CLA Assistant" + if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' + uses: contributor-assistant/github-action@v2.4.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + with: + path-to-signatures: 'signatures.json' + path-to-document: 'https://github.com/HiEventsDev/hi.events/blob/develop/CLA.md' + branch: 'main' + allowlist: daveearley,bot* + remote-organization-name: HiEventsDev + remote-repository-name: cla-signatures diff --git a/CLA.md b/CLA.md new file mode 100644 index 00000000..55fff298 --- /dev/null +++ b/CLA.md @@ -0,0 +1,23 @@ +# Hi.Events Contributor License Agreement + +Thank you for your interest in contributing to Hi.Events! + +Like many open source projects, we ask you to please sign this Contributor License Agreement (CLA) to confirm that you have the rights to your contributions and grant us the necessary permissions to use them. This helps us manage the project effectively and offer both open-source and commercial licenses. + +**Contributor License Agreement** + +This Contributor License Agreement ("Agreement") documents the rights granted by contributors to Hi.Event Ltd. ("Company"). To make this a binding legal agreement, please review the following terms and sign below. + +You hereby grant to Hi.Event Ltd. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license to use, modify, reproduce, distribute, and sublicense your contributions under the terms of both the GNU Affero General Public License version 3 (AGPL-3.0) and a commercial license, without any additional compensation to you. + +You confirm that: + +1. Each contribution is an original creation of yours and that you have the right to grant the licenses stated above. +2. Your contributions do not infringe any third-party copyrights, patents, trademarks, or other rights. +3. You will notify Hi.Event Ltd. of any facts or circumstances of which you become aware that would make your representations in this Agreement inaccurate in any respect. + +For more details on how to contribute, please read our [Contribution Guidelines](https://github.com/HiEventsDev/hi.events/blob/develop/CONTRIBUTING.md). + +By signing below, you accept and agree to the terms of this Agreement. + +Thank you for your contributions and support! diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f7c9ab99..ef9f6062 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,6 +53,10 @@ Please ensure that your pull request includes: - Tests for new functionality or bug fixes, if applicable. - A demo or screenshots, if the changes are visual. +Once you create a pull request, a CLA bot will automatically check if you have signed the Contributor License Agreement (CLA). +Signing is as simple as leaving a comment on the pull request with the message `I have read the CLA Document and I hereby sign the CLA`. +We require all contributors to sign the CLA to ensure that we have the necessary permissions to use and distribute your contributions. + ## Development Setup To set up the development environment for Hi.Events, follow the detailed instructions in our [Getting Started with Local Development guide](https://hi.events/docs/getting-started/local-development). From be0f89929f9d48247762b99e71a78a62247923ec Mon Sep 17 00:00:00 2001 From: Dave Earley Date: Sat, 15 Jun 2024 21:29:10 -0700 Subject: [PATCH 2/6] Update CLA --- CLA.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CLA.md b/CLA.md index 55fff298..0dcaff67 100644 --- a/CLA.md +++ b/CLA.md @@ -8,7 +8,7 @@ Like many open source projects, we ask you to please sign this Contributor Licen This Contributor License Agreement ("Agreement") documents the rights granted by contributors to Hi.Event Ltd. ("Company"). To make this a binding legal agreement, please review the following terms and sign below. -You hereby grant to Hi.Event Ltd. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license to use, modify, reproduce, distribute, and sublicense your contributions under the terms of both the GNU Affero General Public License version 3 (AGPL-3.0) and a commercial license, without any additional compensation to you. +You hereby grant to Hi.Event Ltd. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license to use, modify, reproduce, distribute, and sublicense your contributions under the terms of both the GNU Affero General Public License version 3 (AGPL-3.0) and a commercial license, without any compensation to you. You confirm that: @@ -18,6 +18,6 @@ You confirm that: For more details on how to contribute, please read our [Contribution Guidelines](https://github.com/HiEventsDev/hi.events/blob/develop/CONTRIBUTING.md). -By signing below, you accept and agree to the terms of this Agreement. +Before contributing to Hi.Events, you must accept and agree to the terms of this Agreement. Thank you for your contributions and support! From a6893f599c5a22507f625988788f6cb50fb4a006 Mon Sep 17 00:00:00 2001 From: Dave Earley Date: Sat, 15 Jun 2024 23:13:38 -0700 Subject: [PATCH 3/6] Attach ICS calendar event to attendee ticket emails --- .../EventSettingDomainObject.php | 23 +++ .../app/Mail/Attendee/AttendeeTicketMail.php | 41 ++++++ .../Attendee/SendAttendeeTicketService.php | 3 + .../Domain/Mail/SendOrderDetailsService.php | 8 +- .../Attendee/ResendAttendeeTicketHandler.php | 10 +- backend/composer.json | 1 + backend/composer.lock | 138 +++++++++++++++++- 7 files changed, 221 insertions(+), 3 deletions(-) diff --git a/backend/app/DomainObjects/EventSettingDomainObject.php b/backend/app/DomainObjects/EventSettingDomainObject.php index cf6c350a..46dc2fef 100644 --- a/backend/app/DomainObjects/EventSettingDomainObject.php +++ b/backend/app/DomainObjects/EventSettingDomainObject.php @@ -20,4 +20,27 @@ public function getGetEmailFooterHtml(): string HTML; } + + public function getAddressString(): string + { + $locationDetails = $this->getLocationDetails(); + + if (is_null($locationDetails)) { + return ''; + } + + $addressParts = [ + $locationDetails['venue_name'] ?? null, + $locationDetails['address_line_1'] ?? null, + $locationDetails['address_line_2'] ?? null, + $locationDetails['city'] ?? null, + $locationDetails['state_or_region'] ?? null, + $locationDetails['zip_or_postal_code'] ?? null, + $locationDetails['country'] ?? null + ]; + + $filteredAddressParts = array_filter($addressParts, static fn($part) => !is_null($part) && $part !== ''); + + return implode(', ', $filteredAddressParts); + } } diff --git a/backend/app/Mail/Attendee/AttendeeTicketMail.php b/backend/app/Mail/Attendee/AttendeeTicketMail.php index 9e64885b..03e35756 100644 --- a/backend/app/Mail/Attendee/AttendeeTicketMail.php +++ b/backend/app/Mail/Attendee/AttendeeTicketMail.php @@ -2,14 +2,20 @@ namespace HiEvents\Mail\Attendee; +use Carbon\Carbon; use HiEvents\DomainObjects\AttendeeDomainObject; use HiEvents\DomainObjects\EventDomainObject; use HiEvents\DomainObjects\EventSettingDomainObject; +use HiEvents\DomainObjects\OrganizerDomainObject; +use HiEvents\Helper\StringHelper; use HiEvents\Helper\Url; use HiEvents\Mail\BaseMail; +use Illuminate\Mail\Mailables\Attachment; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Support\Str; +use Spatie\IcalendarGenerator\Components\Calendar; +use Spatie\IcalendarGenerator\Components\Event; /** * @uses /backend/resources/views/emails/orders/attendee-ticket.blade.php @@ -20,6 +26,7 @@ public function __construct( private readonly AttendeeDomainObject $attendee, private readonly EventDomainObject $event, private readonly EventSettingDomainObject $eventSettings, + private readonly OrganizerDomainObject $organizer, ) { parent::__construct(); @@ -51,4 +58,38 @@ public function content(): Content ] ); } + + public function attachments(): array + { + $startDateTime = Carbon::parse($this->event->getStartDate(), $this->event->getTimezone()); + $endDateTime = $this->event->getEndDate() ? Carbon::parse($this->event->getEndDate(), $this->event->getTimezone()) : null; + + $event = Event::create() + ->name($this->event->getTitle()) + ->uniqueIdentifier('event-' . $this->attendee->getId()) + ->startsAt($startDateTime) + ->url($this->event->getEventUrl()) + ->organizer($this->organizer->getEmail(), $this->organizer->getName()); + + if ($this->event->getDescription()) { + $event->description(StringHelper::previewFromHtml($this->event->getDescription())); + } + + if ($this->eventSettings->getLocationDetails()) { + $event->address($this->eventSettings->getAddressString()); + } + + if ($endDateTime) { + $event->endsAt($endDateTime); + } + + $calendar = Calendar::create() + ->event($event) + ->get(); + + return [ + Attachment::fromData(static fn() => $calendar, 'event.ics') + ->withMime('text/calendar') + ]; + } } diff --git a/backend/app/Services/Domain/Attendee/SendAttendeeTicketService.php b/backend/app/Services/Domain/Attendee/SendAttendeeTicketService.php index f5075de3..3fc48ca4 100644 --- a/backend/app/Services/Domain/Attendee/SendAttendeeTicketService.php +++ b/backend/app/Services/Domain/Attendee/SendAttendeeTicketService.php @@ -5,6 +5,7 @@ use HiEvents\DomainObjects\AttendeeDomainObject; use HiEvents\DomainObjects\EventDomainObject; use HiEvents\DomainObjects\EventSettingDomainObject; +use HiEvents\DomainObjects\OrganizerDomainObject; use HiEvents\Mail\Attendee\AttendeeTicketMail; use Illuminate\Contracts\Mail\Mailer; @@ -20,12 +21,14 @@ public function send( AttendeeDomainObject $attendee, EventDomainObject $event, EventSettingDomainObject $eventSettings, + OrganizerDomainObject $organizer, ): void { $this->mailer->to($attendee->getEmail())->send(new AttendeeTicketMail( attendee: $attendee, event: $event, eventSettings: $eventSettings, + organizer: $organizer, )); } } diff --git a/backend/app/Services/Domain/Mail/SendOrderDetailsService.php b/backend/app/Services/Domain/Mail/SendOrderDetailsService.php index 398e3d20..19a38fb7 100644 --- a/backend/app/Services/Domain/Mail/SendOrderDetailsService.php +++ b/backend/app/Services/Domain/Mail/SendOrderDetailsService.php @@ -62,7 +62,13 @@ private function sendAttendeeTicketEmails(OrderDomainObject $order, EventDomainO continue; } - $this->sendAttendeeTicketService->send($attendee, $event, $event->getEventSettings()); + $this->sendAttendeeTicketService->send( + attendee: $attendee, + event: $event, + eventSettings: $event->getEventSettings(), + organizer: $event->getOrganizer(), + ); + $sentEmails[] = $attendee->getEmail(); } } diff --git a/backend/app/Services/Handlers/Attendee/ResendAttendeeTicketHandler.php b/backend/app/Services/Handlers/Attendee/ResendAttendeeTicketHandler.php index df5eb0b9..1424a162 100644 --- a/backend/app/Services/Handlers/Attendee/ResendAttendeeTicketHandler.php +++ b/backend/app/Services/Handlers/Attendee/ResendAttendeeTicketHandler.php @@ -3,8 +3,10 @@ namespace HiEvents\Services\Handlers\Attendee; use HiEvents\DomainObjects\EventSettingDomainObject; +use HiEvents\DomainObjects\OrganizerDomainObject; use HiEvents\DomainObjects\Status\AttendeeStatus; use HiEvents\Exceptions\ResourceConflictException; +use HiEvents\Repository\Eloquent\Value\Relationship; use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface; use HiEvents\Repository\Interfaces\EventRepositoryInterface; use HiEvents\Services\Domain\Attendee\SendAttendeeTicketService; @@ -42,10 +44,16 @@ public function handle(ResendAttendeeTicketDTO $resendAttendeeTicketDTO): void } $event = $this->eventRepository + ->loadRelation(new Relationship(OrganizerDomainObject::class, name: 'organizer')) ->loadRelation(EventSettingDomainObject::class) ->findById($resendAttendeeTicketDTO->eventId); - $this->sendAttendeeTicketService->send($attendee, $event, $event->getEventSettings()); + $this->sendAttendeeTicketService->send( + attendee: $attendee, + event: $event, + eventSettings: $event->getEventSettings(), + organizer: $event->getOrganizer(), + ); $this->logger->info('Attendee ticket resent', [ 'attendeeId' => $resendAttendeeTicketDTO->attendeeId, diff --git a/backend/composer.json b/backend/composer.json index 4dec29c5..4b410491 100644 --- a/backend/composer.json +++ b/backend/composer.json @@ -19,6 +19,7 @@ "maatwebsite/excel": "^3.1", "nette/php-generator": "^4.0", "php-open-source-saver/jwt-auth": "^2.1", + "spatie/icalendar-generator": "^2.8", "stripe/stripe-php": "^10.15" }, "require-dev": { diff --git a/backend/composer.lock b/backend/composer.lock index 9f0d35ef..06dfbfb9 100644 --- a/backend/composer.lock +++ b/backend/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d8d83799f1f66406f0c41b2b51c35b10", + "content-hash": "5f55a1a7adc1f649d32d094a084840d1", "packages": [ { "name": "aws/aws-crt-php", @@ -4795,6 +4795,142 @@ ], "time": "2023-11-08T05:53:05+00:00" }, + { + "name": "spatie/enum", + "version": "3.13.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/enum.git", + "reference": "f1a0f464ba909491a53e60a955ce84ad7cd93a2c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/enum/zipball/f1a0f464ba909491a53e60a955ce84ad7cd93a2c", + "reference": "f1a0f464ba909491a53e60a955ce84ad7cd93a2c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.0" + }, + "require-dev": { + "fakerphp/faker": "^1.9.1", + "larapack/dd": "^1.1", + "phpunit/phpunit": "^9.0", + "vimeo/psalm": "^4.3" + }, + "suggest": { + "fakerphp/faker": "To use the enum faker provider", + "phpunit/phpunit": "To use the enum assertions" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Enum\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Roose", + "email": "brent@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Tom Witkowski", + "email": "dev@gummibeer.de", + "homepage": "https://gummibeer.de", + "role": "Developer" + } + ], + "description": "PHP Enums", + "homepage": "https://github.com/spatie/enum", + "keywords": [ + "enum", + "enumerable", + "spatie" + ], + "support": { + "docs": "https://docs.spatie.be/enum", + "issues": "https://github.com/spatie/enum/issues", + "source": "https://github.com/spatie/enum" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-04-22T08:51:55+00:00" + }, + { + "name": "spatie/icalendar-generator", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/icalendar-generator.git", + "reference": "00097f69a35a4b0b98b71f4700f828ffa15a4c2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/icalendar-generator/zipball/00097f69a35a4b0b98b71f4700f828ffa15a4c2d", + "reference": "00097f69a35a4b0b98b71f4700f828ffa15a4c2d", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.4|^8.0", + "spatie/enum": "^3.11" + }, + "require-dev": { + "ext-json": "*", + "larapack/dd": "^1.1", + "nesbot/carbon": "^2.63|^3.0", + "pestphp/pest": "^1.22", + "spatie/pest-plugin-snapshots": "^1.1", + "vimeo/psalm": "^4.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\IcalendarGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ruben Van Assche", + "email": "ruben@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Build calendars in the iCalendar format", + "homepage": "https://github.com/spatie/icalendar-generator", + "keywords": [ + "calendar", + "iCalendar", + "ical", + "ics", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/icalendar-generator/issues", + "source": "https://github.com/spatie/icalendar-generator/tree/2.8.0" + }, + "time": "2024-05-16T15:11:32+00:00" + }, { "name": "stripe/stripe-php", "version": "v10.21.0", From 3c725c648ced77a75b59edc4012d2c70d317200a Mon Sep 17 00:00:00 2001 From: Dave Earley Date: Sat, 15 Jun 2024 23:23:44 -0700 Subject: [PATCH 4/6] Fix event address not being cleared when switching to an online event --- .../EventSettings/PartialUpdateEventSettingsHandler.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/app/Services/Handlers/EventSettings/PartialUpdateEventSettingsHandler.php b/backend/app/Services/Handlers/EventSettings/PartialUpdateEventSettingsHandler.php index e9204815..4a586f57 100644 --- a/backend/app/Services/Handlers/EventSettings/PartialUpdateEventSettingsHandler.php +++ b/backend/app/Services/Handlers/EventSettings/PartialUpdateEventSettingsHandler.php @@ -31,6 +31,13 @@ public function handle(PartialUpdateEventSettingsDTO $eventSettingsDTO): EventSe throw new RefundNotPossibleException('Event settings not found'); } + $locationDetails = $eventSettingsDTO->settings['location_details'] ?? $existingSettings->getLocationDetails(); + $isOnlineEvent = $eventSettingsDTO->settings['is_online_event'] ?? $existingSettings->getIsOnlineEvent(); + + if ($isOnlineEvent) { + $locationDetails = null; + } + return $this->eventSettingsHandler->handle( UpdateEventSettingsDTO::fromArray([ 'event_id' => $eventSettingsDTO->event_id, @@ -59,7 +66,7 @@ public function handle(PartialUpdateEventSettingsDTO $eventSettingsDTO): EventSe 'order_timeout_in_minutes' => $eventSettingsDTO->settings['order_timeout_in_minutes'] ?? $existingSettings->getOrderTimeoutInMinutes(), 'website_url' => $eventSettingsDTO->settings['website_url'] ?? $existingSettings->getWebsiteUrl(), 'maps_url' => $eventSettingsDTO->settings['maps_url'] ?? $existingSettings->getMapsUrl(), - 'location_details' => $eventSettingsDTO->settings['location_details'] ?? $existingSettings->getLocationDetails(), + 'location_details' => $locationDetails, 'is_online_event' => $eventSettingsDTO->settings['is_online_event'] ?? $existingSettings->getIsOnlineEvent(), 'online_event_connection_details' => array_key_exists('online_event_connection_details', $eventSettingsDTO->settings) ? $eventSettingsDTO->settings['online_event_connection_details'] From 29ecceb80203b43dad5457a85861268b69d2a28c Mon Sep 17 00:00:00 2001 From: Dave Earley Date: Sun, 16 Jun 2024 00:13:11 -0700 Subject: [PATCH 5/6] Update copy for donation tickets --- frontend/src/components/forms/TicketForm/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/forms/TicketForm/index.tsx b/frontend/src/components/forms/TicketForm/index.tsx index 29cc7671..045b817b 100644 --- a/frontend/src/components/forms/TicketForm/index.tsx +++ b/frontend/src/components/forms/TicketForm/index.tsx @@ -136,9 +136,9 @@ export const TicketForm = ({form, ticket}: TicketFormProps) => { }, { icon: , - label: t`Donation Ticket`, + label: t`Donation / Pay what you'd like ticket`, value: 'DONATION', - description: t`Set a minimum price and let users donate more`, + description: t`Set a minimum price and let users pay more if they choose`, }, { icon: , From a3e97ef8c91baff8145d5b93c87ab80d60b4464e Mon Sep 17 00:00:00 2001 From: Dave Earley Date: Sun, 16 Jun 2024 00:14:41 -0700 Subject: [PATCH 6/6] Update default mail from name and address --- backend/config/mail.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/config/mail.php b/backend/config/mail.php index 542d98c3..3b6cca04 100644 --- a/backend/config/mail.php +++ b/backend/config/mail.php @@ -98,8 +98,8 @@ */ 'from' => [ - 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), - 'name' => env('MAIL_FROM_NAME', 'Example'), + 'address' => env('MAIL_FROM_ADDRESS', 'hello@hi.events'), + 'name' => env('MAIL_FROM_NAME', 'Hi.Events'), ], /*