From 911f57cc8fa744a32ad3b9a9f34ca7cfe0f31f57 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Tue, 10 Mar 2026 15:19:38 -0600 Subject: [PATCH 01/30] refactor: add TimeZone enum instead of simple string --- core/build.gradle.kts | 2 + .../org/justserve/client/GraphQLClient.java | 76 ++++++++- .../java/org/justserve/model/TimeZone.java | 155 ++++++++++++++++++ .../model/{ => graph}/GraphQLResponse.java | 2 +- core/src/main/resources/schema.yml | 42 ++++- 5 files changed, 270 insertions(+), 7 deletions(-) create mode 100644 core/src/main/java/org/justserve/model/TimeZone.java rename core/src/main/java/org/justserve/model/{ => graph}/GraphQLResponse.java (92%) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 1a8afeb..1359330 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -58,6 +58,8 @@ micronaut { importMapping.put("EventType", "org.justserve.model.EventType") schemaMapping.put("ProjectLocationType", "org.justserve.model.ProjectLocationType") importMapping.put("ProjectLocationType", "org.justserve.model.ProjectLocationType") + schemaMapping.put("TimeZone", "org.justserve.model.TimeZone") + importMapping.put("TimeZone", "org.justserve.model.TimeZone") } } processing { diff --git a/core/src/main/java/org/justserve/client/GraphQLClient.java b/core/src/main/java/org/justserve/client/GraphQLClient.java index 2f268d4..ac4fea4 100644 --- a/core/src/main/java/org/justserve/client/GraphQLClient.java +++ b/core/src/main/java/org/justserve/client/GraphQLClient.java @@ -7,6 +7,13 @@ import io.micronaut.http.client.annotation.Client; import io.micronaut.retry.annotation.Retryable; import org.justserve.model.*; +import org.justserve.model.graph.GraphQLResponse; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; @Produces("application/json") @Consumes("application/graphql-response+json; charset=utf-8") @@ -72,7 +79,59 @@ default GraphQLResponse c } default GraphQLResponse createEvent(GraphQLCreateEventVariables variables) { - String fixedQuery = "mutation createEvent($projectId: ID!, $projectEvent: UpdateProjectEventInput!) {\n createEvent(\n projectId: $projectId\n projectEvent: $projectEvent\n ) {\n id\n projectId\n contactEmail\n contactName\n contactPhone\n start\n end\n groupCap\n groupLimit\n timezone\n totalVolunteersNeeded\n volunteerCap\n }\n }"; + String queryFormat = """ + mutation createEvent($projectId: ID!, $projectEvent: UpdateProjectEventInput!) { + createEvent( + projectId: $projectId + projectEvent: $projectEvent + ) { + %s + } + } + """; + + List fields = new ArrayList<>(List.of("id", "projectId")); + Object projectEvent = variables.getProjectEvent(); + + if (projectEvent != null) { + String dynamicFields = Arrays.stream(projectEvent.getClass().getDeclaredFields()) + .filter(field -> { + try { + field.setAccessible(true); + return field.get(projectEvent) != null; + } catch (IllegalAccessException e) { + return false; + } + }) + .map(Field::getName) + .collect(Collectors.joining("\n")); + if (!dynamicFields.isEmpty()) { + fields.add(dynamicFields); + } + } + + GraphQLCreateEventRequest request = new GraphQLCreateEventRequest(); + request.setQuery(String.format(queryFormat, String.join("\n", fields))); + request.setVariables(variables); + return this.executeCreateEvent(request); + } + + /** + * {@summary Add the dates for an ongoing event.} Only use this on projects whose type is set to {@link EventType#Ongoing}. + * @param variables provide the {@link GraphQLCreateEventVariables#projectId} and {@link GraphQLCreateEventVariables#projectEvent} + * @return the {@link GraphQLCreateEventData} response wrapped in a {@link GraphQLResponse} + */ + default GraphQLResponse createOngoingEvent(GraphQLCreateEventVariables variables) { + String fixedQuery = """ + mutation updateEvent($id: ID!, $projectEvent: UpdateProjectEventInput!) { + updateEvent( + id: $id + projectEvent: $projectEvent + ) { + id + } + } + """; GraphQLCreateEventRequest request = new GraphQLCreateEventRequest(); request.setQuery(fixedQuery); request.setVariables(variables); @@ -128,9 +187,20 @@ default GraphQLResponse updateProjectListing(Gr } default GraphQLResponse updateProject(GraphQLUpdateProjectVariables variables) { - String fixedQuery = "mutation ($projectId: ID!, $logo: String!) {\n updateProject(id: $projectId, modify: { logo: $logo }) {\n id\n logo\n }\n }"; + String mutationFormat = "mutation ($projectId: ID!, $logo: String!) {\n updateProject(id: $projectId, modify: { logo: $logo }) {\n %s\n }\n }"; + + List responseFields = new ArrayList<>(); + responseFields.add("id"); + + if (variables.logo() != null) { + responseFields.add("logo"); + } + + String fieldsString = String.join("\n", responseFields); + String dynamicQuery = String.format(mutationFormat, fieldsString); + GraphQLUpdateProjectRequest request = new GraphQLUpdateProjectRequest(); - request.setQuery(fixedQuery); + request.setQuery(dynamicQuery); request.setVariables(variables); return this.executeUpdateProject(request); } diff --git a/core/src/main/java/org/justserve/model/TimeZone.java b/core/src/main/java/org/justserve/model/TimeZone.java new file mode 100644 index 0000000..30a69cb --- /dev/null +++ b/core/src/main/java/org/justserve/model/TimeZone.java @@ -0,0 +1,155 @@ +package org.justserve.model; + +import lombok.Getter; + +@Getter +public enum TimeZone { + INTERNATIONAL_DATE_LINE_WEST("(UTC-12:00) International Date Line West"), + COORDINATED_UNIVERSAL_TIME_11("(UTC-11:00) Coordinated Universal Time-11"), + ALEUTIAN_ISLANDS("(UTC-10:00) Aleutian Islands"), + HAWAII("(UTC-10:00) Hawaii"), + MARQUESAS_ISLANDS("(UTC-09:30) Marquesas Islands"), + ALASKA("(UTC-09:00) Alaska"), + COORDINATED_UNIVERSAL_TIME_09("(UTC-09:00) Coordinated Universal Time-09"), + BAJA_CALIFORNIA("(UTC-08:00) Baja California"), + COORDINATED_UNIVERSAL_TIME_08("(UTC-08:00) Coordinated Universal Time-08"), + PACIFIC_TIME_US_AND_CANADA("(UTC-08:00) Pacific Time (US & Canada)"), + ARIZONA("(UTC-07:00) Arizona"), + LA_PAZ("(UTC-07:00) La Paz"), + MOUNTAIN_TIME_US_AND_CANADA("(UTC-07:00) Mountain Time (US & Canada)"), + YUKON("(UTC-07:00) Yukon"), + CENTRAL_AMERICA("(UTC-06:00) Central America"), + CENTRAL_TIME_US_AND_CANADA("(UTC-06:00) Central Time (US & Canada)"), + EASTER_ISLAND("(UTC-06:00) Easter Island"), + GUADALAJARA("(UTC-06:00) Guadalajara"), + SASKATCHEWAN("(UTC-06:00) Saskatchewan"), + BOGOTA("(UTC-05:00) Bogota"), + CHETUMAL("(UTC-05:00) Chetumal"), + EASTERN_TIME_US_AND_CANADA("(UTC-05:00) Eastern Time (US & Canada)"), + HAITI("(UTC-05:00) Haiti"), + HAVANA("(UTC-05:00) Havana"), + INDIANA_EAST("(UTC-05:00) Indiana (East)"), + TURKS_AND_CAICOS("(UTC-05:00) Turks and Caicos"), + ASUNCION("(UTC-04:00) Asuncion"), + ATLANTIC_TIME_CANADA("(UTC-04:00) Atlantic Time (Canada)"), + CARACAS("(UTC-04:00) Caracas"), + CUIABA("(UTC-04:00) Cuiaba"), + GEORGETOWN("(UTC-04:00) Georgetown"), + SANTIAGO("(UTC-04:00) Santiago"), + NEWFOUNDLAND("(UTC-03:30) Newfoundland"), + ARAGUAINA("(UTC-03:00) Araguaina"), + BRASILIA("(UTC-03:00) Brasilia"), + CAYENNE("(UTC-03:00) Cayenne"), + CITY_OF_BUENOS_AIRES("(UTC-03:00) City of Buenos Aires"), + MONTEVIDEO("(UTC-03:00) Montevideo"), + PUNTA_ARENAS("(UTC-03:00) Punta Arenas"), + SAINT_PIERRE_AND_MIQUELON("(UTC-03:00) Saint Pierre and Miquelon"), + SALVADOR("(UTC-03:00) Salvador"), + COORDINATED_UNIVERSAL_TIME_02("(UTC-02:00) Coordinated Universal Time-02"), + GREENLAND("(UTC-02:00) Greenland"), + MID_ATLANTIC_OLD("(UTC-02:00) Mid-Atlantic - Old"), + AZORES("(UTC-01:00) Azores"), + CABO_VERDE_IS("(UTC-01:00) Cabo Verde Is."), + COORDINATED_UNIVERSAL_TIME("(UTC) Coordinated Universal Time"), + DUBLIN("(UTC+00:00) Dublin"), + MONROVIA("(UTC+00:00) Monrovia"), + SAO_TOME("(UTC+00:00) Sao Tome"), + CASABLANCA("(UTC+01:00) Casablanca"), + AMSTERDAM("(UTC+01:00) Amsterdam"), + BELGRADE("(UTC+01:00) Belgrade"), + BRUSSELS("(UTC+01:00) Brussels"), + SARAJEVO("(UTC+01:00) Sarajevo"), + WEST_CENTRAL_AFRICA("(UTC+01:00) West Central Africa"), + ATHENS("(UTC+02:00) Athens"), + BEIRUT("(UTC+02:00) Beirut"), + CAIRO("(UTC+02:00) Cairo"), + CHISINAU("(UTC+02:00) Chisinau"), + GAZA("(UTC+02:00) Gaza"), + HARARE("(UTC+02:00) Harare"), + HELSINKI("(UTC+02:00) Helsinki"), + JERUSALEM("(UTC+02:00) Jerusalem"), + JUBA("(UTC+02:00) Juba"), + KALININGRAD("(UTC+02:00) Kaliningrad"), + KHARTOUM("(UTC+02:00) Khartoum"), + TRIPOLI("(UTC+02:00) Tripoli"), + WINDHOEK("(UTC+02:00) Windhoek"), + AMMAN("(UTC+03:00) Amman"), + BAGHDAD("(UTC+03:00) Baghdad"), + DAMASCUS("(UTC+03:00) Damascus"), + ISTANBUL("(UTC+03:00) Istanbul"), + KUWAIT("(UTC+03:00) Kuwait"), + MINSK("(UTC+03:00) Minsk"), + MOSCOW("(UTC+03:00) Moscow"), + NAIROBI("(UTC+03:00) Nairobi"), + VOLGOGRAD("(UTC+03:00) Volgograd"), + TEHRAN("(UTC+03:30) Tehran"), + ABU_DHABI("(UTC+04:00) Abu Dhabi"), + ASTRAKHAN("(UTC+04:00) Astrakhan"), + BAKU("(UTC+04:00) Baku"), + IZHEVSK("(UTC+04:00) Izhevsk"), + PORT_LOUIS("(UTC+04:00) Port Louis"), + SARATOV("(UTC+04:00) Saratov"), + TBILISI("(UTC+04:00) Tbilisi"), + YEREVAN("(UTC+04:00) Yerevan"), + KABUL("(UTC+04:30) Kabul"), + ASHGABAT("(UTC+05:00) Ashgabat"), + ASTANA("(UTC+05:00) Astana"), + EKATERINBURG("(UTC+05:00) Ekaterinburg"), + ISLAMABAD("(UTC+05:00) Islamabad"), + CHENNAI("(UTC+05:30) Chennai"), + SRI_JAYAWARDENEPURA("(UTC+05:30) Sri Jayawardenepura"), + KATHMANDU("(UTC+05:45) Kathmandu"), + BISHKEK("(UTC+06:00) Bishkek"), + DHAKA("(UTC+06:00) Dhaka"), + OMSK("(UTC+06:00) Omsk"), + YANGON_RANGOON("(UTC+06:30) Yangon (Rangoon)"), + BANGKOK("(UTC+07:00) Bangkok"), + BARNAUL("(UTC+07:00) Barnaul"), + HOVD("(UTC+07:00) Hovd"), + KRASNOYARSK("(UTC+07:00) Krasnoyarsk"), + NOVOSIBIRSK("(UTC+07:00) Novosibirsk"), + TOMSK("(UTC+07:00) Tomsk"), + BEIJING("(UTC+08:00) Beijing"), + IRKUTSK("(UTC+08:00) Irkutsk"), + KUALA_LUMPUR("(UTC+08:00) Kuala Lumpur"), + PERTH("(UTC+08:00) Perth"), + TAIPEI("(UTC+08:00) Taipei"), + ULAANBAATAR("(UTC+08:00) Ulaanbaatar"), + EUCLA("(UTC+08:45) Eucla"), + CHITA("(UTC+09:00) Chita"), + OSAKA("(UTC+09:00) Osaka"), + PYONGYANG("(UTC+09:00) Pyongyang"), + SEOUL("(UTC+09:00) Seoul"), + YAKUTSK("(UTC+09:00) Yakutsk"), + ADELAIDE("(UTC+09:30) Adelaide"), + DARWIN("(UTC+09:30) Darwin"), + BRISBANE("(UTC+10:00) Brisbane"), + CANBERRA("(UTC+10:00) Canberra"), + GUAM("(UTC+10:00) Guam"), + HOBART("(UTC+10:00) Hobart"), + VLADIVOSTOK("(UTC+10:00) Vladivostok"), + LORD_HOWE_ISLAND("(UTC+10:30) Lord Howe Island"), + BOUGAINVILLE_ISLAND("(UTC+11:00) Bougainville Island"), + CHOKURDAKH("(UTC+11:00) Chokurdakh"), + MAGADAN("(UTC+11:00) Magadan"), + NORFOLK_ISLAND("(UTC+11:00) Norfolk Island"), + SAKHALIN("(UTC+11:00) Sakhalin"), + SOLOMON_IS("(UTC+11:00) Solomon Is."), + ANADYR("(UTC+12:00) Anadyr"), + AUCKLAND("(UTC+12:00) Auckland"), + COORDINATED_UNIVERSAL_TIME_12("(UTC+12:00) Coordinated Universal Time+12"), + FIJI("(UTC+12:00) Fiji"), + PETROPAVLOVSK_KAMCHATSKY_OLD("(UTC+12:00) Petropavlovsk-Kamchatsky - Old"), + CHATHAM_ISLANDS("(UTC+12:45) Chatham Islands"), + COORDINATED_UNIVERSAL_TIME_13("(UTC+13:00) Coordinated Universal Time+13"), + NUKU_ALOFA("(UTC+13:00) Nuku'alofa"), + SAMOA("(UTC+13:00) Samoa"), + KIRITIMATI_ISLAND("(UTC+14:00) Kiritimati Island"); + + private final String displayName; + + TimeZone(String displayName) { + this.displayName = displayName; + } + +} diff --git a/core/src/main/java/org/justserve/model/GraphQLResponse.java b/core/src/main/java/org/justserve/model/graph/GraphQLResponse.java similarity index 92% rename from core/src/main/java/org/justserve/model/GraphQLResponse.java rename to core/src/main/java/org/justserve/model/graph/GraphQLResponse.java index 8f542a0..2e28611 100644 --- a/core/src/main/java/org/justserve/model/GraphQLResponse.java +++ b/core/src/main/java/org/justserve/model/graph/GraphQLResponse.java @@ -1,4 +1,4 @@ -package org.justserve.model; +package org.justserve.model.graph; import io.micronaut.serde.annotation.Serdeable; import lombok.AllArgsConstructor; diff --git a/core/src/main/resources/schema.yml b/core/src/main/resources/schema.yml index e75098e..9a441d4 100644 --- a/core/src/main/resources/schema.yml +++ b/core/src/main/resources/schema.yml @@ -339,6 +339,8 @@ paths: components: schemas: + TimeZone: + type: string GraphQLCreateEventData: type: object properties: @@ -354,7 +356,7 @@ components: end: { type: string, format: date-time } groupCap: { type: boolean } groupLimit: { type: integer, format: int32 } - timezone: { type: string } + timezone: { $ref: '#/components/schemas/TimeZone' } totalVolunteersNeeded: { type: integer, format: int32 } volunteerCap: { type: boolean } GraphQLSearchOrganizationData: @@ -2003,10 +2005,16 @@ components: properties: query: { type: string } variables: { $ref: '#/components/schemas/GraphQLCreateEventVariables' } + GraphQLCreateOngoingEventRequest: + type: object + properties: + query: { type: string } + variables: { $ref: '#/components/schemas/GraphQLCreateOngoingEventVariables' } GraphQLCreateEventVariables: type: object properties: - projectId: + id: + description: ID for the overall project type: string format: uuid projectEvent: @@ -2033,11 +2041,39 @@ components: type: string format: date-time timezone: - type: string + $ref: '#/components/schemas/TimeZone' totalVolunteersNeeded: type: integer volunteerCap: type: boolean + GraphQLCreateOngoingEventVariables: + type: object + properties: + id: + description: ID for the overall project + type: string + format: uuid + projectEvent: + type: object + properties: + contactEmail: + type: string + format: email + maxLength: 200 + contactName: + type: string + maxLength: 200 + contactPhone: + type: string + format: phone + maxLength: 139 + schedule: + type: string + maxLength: 100 # will be corrected to 300 + shiftTitle: + type: string + maxLength: 100 # will be corrected to 300 + GraphQLCreateProjectRequest: type: object properties: From a9102f546554a0282545d39dd664b9b4fc48c525 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Tue, 10 Mar 2026 15:19:48 -0600 Subject: [PATCH 02/30] refactor: add TimeZone enum instead of simple string --- core/src/main/java/org/justserve/model/EventType.java | 9 ++++----- .../java/org/justserve/model/ProjectLocationType.java | 3 --- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/justserve/model/EventType.java b/core/src/main/java/org/justserve/model/EventType.java index 82d42df..24070b0 100644 --- a/core/src/main/java/org/justserve/model/EventType.java +++ b/core/src/main/java/org/justserve/model/EventType.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import io.micronaut.serde.annotation.Serdeable; -import jakarta.annotation.Generated; import lombok.RequiredArgsConstructor; import java.util.Arrays; @@ -16,9 +15,7 @@ */ @RequiredArgsConstructor @Serdeable -@Generated("io.micronaut.openapi.generator.JavaMicronautClientCodegen") public enum EventType { -// None(0, "None"), DTL(1, "DTL"), Ongoing(2, "ONGOING"), Recurring(3, "RECURRING"), @@ -40,8 +37,10 @@ public String getStringValue() { return stringValue; } - // 2. RECEIVING (Response): This catches the incoming data. - // It can handle the Integer '1' from GraphQL, or even a String if a REST endpoint sends one. + /** + * 2. RECEIVING (Response): This catches the incoming data. + * It can handle the Integer '1' from GraphQL, or even a String if a REST endpoint sends one. + */ @JsonCreator public static EventType fromValue(Object value) { if (value instanceof Number) { diff --git a/core/src/main/java/org/justserve/model/ProjectLocationType.java b/core/src/main/java/org/justserve/model/ProjectLocationType.java index 049b598..acc3ffd 100644 --- a/core/src/main/java/org/justserve/model/ProjectLocationType.java +++ b/core/src/main/java/org/justserve/model/ProjectLocationType.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import io.micronaut.serde.annotation.Serdeable; -import jakarta.annotation.Generated; import lombok.RequiredArgsConstructor; import java.util.Arrays; @@ -13,9 +12,7 @@ @RequiredArgsConstructor @Serdeable -@Generated("io.micronaut.openapi.generator.JavaMicronautClientCodegen") public enum ProjectLocationType { -// NONE(0, "NONE"), SINGLE_LOCATION(1, "SINGLE_LOCATION"), REGIONAL(3, "REGIONAL"), REMOTE(4, "REMOTE"); From 62ab06eda3336bbaeaad552535165e5d0d88e228 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Tue, 10 Mar 2026 15:20:33 -0600 Subject: [PATCH 03/30] refactor(wip): create dynamic query builder --- .../java/org/justserve/model/CreateEvent.java | 14 +++++ .../model/graph/CreateEventQuery.java | 53 +++++++++++++++++++ .../model/{ => graph}/GraphQLRequest.java | 3 +- .../org/justserve/model/graph/GraphQuery.java | 30 +++++++++++ .../justserve/model/graph/GraphVariables.java | 20 +++++++ .../org/justserve/GraphQLClientSpec.groovy | 14 ++++- 6 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/org/justserve/model/CreateEvent.java create mode 100644 core/src/main/java/org/justserve/model/graph/CreateEventQuery.java rename core/src/main/java/org/justserve/model/{ => graph}/GraphQLRequest.java (78%) create mode 100644 core/src/main/java/org/justserve/model/graph/GraphQuery.java create mode 100644 core/src/main/java/org/justserve/model/graph/GraphVariables.java diff --git a/core/src/main/java/org/justserve/model/CreateEvent.java b/core/src/main/java/org/justserve/model/CreateEvent.java new file mode 100644 index 0000000..3650e5a --- /dev/null +++ b/core/src/main/java/org/justserve/model/CreateEvent.java @@ -0,0 +1,14 @@ +package org.justserve.model; + +public class CreateEvent { + String query = """ + mutation createEvent($projectId: ID!, $projectEvent: UpdateProjectEventInput!) { + createEvent( + projectId: $projectId + projectEvent: $projectEvent + ) { + %s + } + } + """; +} diff --git a/core/src/main/java/org/justserve/model/graph/CreateEventQuery.java b/core/src/main/java/org/justserve/model/graph/CreateEventQuery.java new file mode 100644 index 0000000..729f22b --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/CreateEventQuery.java @@ -0,0 +1,53 @@ +package org.justserve.model.graph; + +public class CreateEventQuery extends GraphQuery{ + + private static class eventVariables extends GraphVariables { + String contactEmail; + String contactName; + String contactPhone; + Boolean deleted; + String deletedBy; + String deletedOn; + String end; + String endDateTimeOffset; + Boolean eventCapReached; + Integer groupCap; + Integer groupLimit; + String id; + String locationLink; + String locationName; + String projectEventLocationId; + String projectId; + String projectRecurringTimeId; + String qrCodeImageLocation; + String renewDate; + String schedule; + String shiftTitle; + String specialDirections; + String start; + String startDateTimeOffset; + String status; + String statusId; + String timezone; + String timeZoneEnumId; + Integer totalVolunteersNeeded; + Integer volunteerCap; + Integer volunteersNeeded; + } + + public CreateEventQuery(GraphVariables variables) { + this.query = """ + mutation createEvent($projectId: ID!, $projectEvent: UpdateProjectEventInput!) { + createEvent( + projectId: $projectId + projectEvent: $projectEvent + ) { + %s + } + } + """; + this.variables = variables; + } + +} diff --git a/core/src/main/java/org/justserve/model/GraphQLRequest.java b/core/src/main/java/org/justserve/model/graph/GraphQLRequest.java similarity index 78% rename from core/src/main/java/org/justserve/model/GraphQLRequest.java rename to core/src/main/java/org/justserve/model/graph/GraphQLRequest.java index e9e44f2..a4fa059 100644 --- a/core/src/main/java/org/justserve/model/GraphQLRequest.java +++ b/core/src/main/java/org/justserve/model/graph/GraphQLRequest.java @@ -1,4 +1,4 @@ -package org.justserve.model; +package org.justserve.model.graph; import io.micronaut.serde.annotation.Serdeable; import lombok.AllArgsConstructor; @@ -13,6 +13,5 @@ public class GraphQLRequest { private String query; - // This handles dynamic variable maps/objects private Object variables; } diff --git a/core/src/main/java/org/justserve/model/graph/GraphQuery.java b/core/src/main/java/org/justserve/model/graph/GraphQuery.java new file mode 100644 index 0000000..52dd20d --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/GraphQuery.java @@ -0,0 +1,30 @@ +package org.justserve.model.graph; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.Pattern; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class GraphQuery { + /** + *

Mutation Query String

formatted to receive a \n-delimited string of variable names.
+ * The query is to include the mutation's signature, as well as its opening and closing curly braces.
+ *
Example:
{@code "mutation ($projectId: ID!, $attachmentId: ID!) {\\n %s\\n" } + */ + //"^mutation\\b[\\s\\S]*\\{[\\s\\S]*%s[\\s\\S]*\\}$" + @Pattern(regexp = "^mutation.*\\{.*%s.*}.*", message = "Query must begin with 'mutation' and include a '%s' placeholder") + String query; + + + @JsonProperty("variables") + GraphVariables variables; + + + String getQuery() { + if (query == null) { + log.warn("Query nor signature can be null in {}: query template is {} and query must contain '%s' placeholder.", this.getClass().getSimpleName(), query); + return ""; + } + return String.format(query, variables.getMutationFields()); + } +} diff --git a/core/src/main/java/org/justserve/model/graph/GraphVariables.java b/core/src/main/java/org/justserve/model/graph/GraphVariables.java new file mode 100644 index 0000000..afc0a30 --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/GraphVariables.java @@ -0,0 +1,20 @@ +package org.justserve.model.graph; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.beans.BeanIntrospector; + +import java.util.stream.Collectors; + +/** + *

Array of variables

+ * These are the variables used in the {@link GraphQuery#query}. Non-null values are added into the query as well as the variables json object. + */ +@Introspected +public class GraphVariables { + + String getMutationFields() { + return BeanIntrospector.SHARED.getIntrospection(this.getClass()).getBeanProperties().stream() + .map(prop -> prop.getName() + ": $" + prop.getName()) + .collect(Collectors.joining("\n")); + } +} diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index 02a2896..379f843 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -25,7 +25,7 @@ class GraphQLClientSpec extends Specification { .setRedirect(redirect) when: - client.createProject(args) + def response = client.createProject(args) then: noExceptionThrown() @@ -33,4 +33,16 @@ class GraphQLClientSpec extends Specification { where: [eventType, locationType, redirect] << [EventType.values(), ProjectLocationType.values(), ["", null, "https://google.com"]].combinations() } + + void "can use createOngoingEvent()"() { +// given: +// GraphQLCreate event = new GraphQLCreateEventVariablesProjectEvent() +// GraphQLCreateEventVariables args = new GraphQLCreateEventVariables() +// .setProjectEvent() +// +// +// when: +// client.createEvent() +// + } } From 7023a5d7c68b9ba2abd58db24ee13d8d6a0e6856 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Tue, 10 Mar 2026 16:19:44 -0600 Subject: [PATCH 04/30] refactor(wip): build mutation query variables are not sent properly --- .../org/justserve/client/GraphQLClient.java | 6 ++- .../model/graph/CreateEventQuery.java | 39 ++----------------- .../org/justserve/model/graph/GraphQuery.java | 8 ++-- .../justserve/model/graph/GraphVariables.java | 28 +++++++++++-- .../org/justserve/GraphQLClientSpec.groovy | 35 +++++++++++------ 5 files changed, 61 insertions(+), 55 deletions(-) diff --git a/core/src/main/java/org/justserve/client/GraphQLClient.java b/core/src/main/java/org/justserve/client/GraphQLClient.java index ac4fea4..d48da87 100644 --- a/core/src/main/java/org/justserve/client/GraphQLClient.java +++ b/core/src/main/java/org/justserve/client/GraphQLClient.java @@ -7,6 +7,7 @@ import io.micronaut.http.client.annotation.Client; import io.micronaut.retry.annotation.Retryable; import org.justserve.model.*; +import org.justserve.model.graph.CreateEventQuery; import org.justserve.model.graph.GraphQLResponse; import java.lang.reflect.Field; @@ -21,6 +22,9 @@ @Client(id = "justserve", path = "/graphql") public interface GraphQLClient { + @Post + GraphQLResponse createEvent(@Body CreateEventQuery request); + @Post GraphQLResponse executeAddProjectAttachment(@Body GraphQLAddProjectAttachmentRequest request); @@ -192,7 +196,7 @@ default GraphQLResponse updateProject(GraphQLUpdatePro List responseFields = new ArrayList<>(); responseFields.add("id"); - if (variables.logo() != null) { + if (variables.getLogo() != null) { responseFields.add("logo"); } diff --git a/core/src/main/java/org/justserve/model/graph/CreateEventQuery.java b/core/src/main/java/org/justserve/model/graph/CreateEventQuery.java index 729f22b..08c7e77 100644 --- a/core/src/main/java/org/justserve/model/graph/CreateEventQuery.java +++ b/core/src/main/java/org/justserve/model/graph/CreateEventQuery.java @@ -1,42 +1,11 @@ package org.justserve.model.graph; -public class CreateEventQuery extends GraphQuery{ +import io.micronaut.serde.annotation.Serdeable; - private static class eventVariables extends GraphVariables { - String contactEmail; - String contactName; - String contactPhone; - Boolean deleted; - String deletedBy; - String deletedOn; - String end; - String endDateTimeOffset; - Boolean eventCapReached; - Integer groupCap; - Integer groupLimit; - String id; - String locationLink; - String locationName; - String projectEventLocationId; - String projectId; - String projectRecurringTimeId; - String qrCodeImageLocation; - String renewDate; - String schedule; - String shiftTitle; - String specialDirections; - String start; - String startDateTimeOffset; - String status; - String statusId; - String timezone; - String timeZoneEnumId; - Integer totalVolunteersNeeded; - Integer volunteerCap; - Integer volunteersNeeded; - } +@Serdeable +public class CreateEventQuery extends GraphQuery { - public CreateEventQuery(GraphVariables variables) { + public CreateEventQuery(Event variables) { this.query = """ mutation createEvent($projectId: ID!, $projectEvent: UpdateProjectEventInput!) { createEvent( diff --git a/core/src/main/java/org/justserve/model/graph/GraphQuery.java b/core/src/main/java/org/justserve/model/graph/GraphQuery.java index 52dd20d..a9262b2 100644 --- a/core/src/main/java/org/justserve/model/graph/GraphQuery.java +++ b/core/src/main/java/org/justserve/model/graph/GraphQuery.java @@ -1,10 +1,14 @@ package org.justserve.model.graph; import com.fasterxml.jackson.annotation.JsonProperty; +import io.micronaut.serde.annotation.Serdeable; import jakarta.validation.constraints.Pattern; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; @Slf4j +@Serdeable +@Getter public class GraphQuery { /** *

Mutation Query String

formatted to receive a \n-delimited string of variable names.
@@ -15,15 +19,13 @@ public class GraphQuery { @Pattern(regexp = "^mutation.*\\{.*%s.*}.*", message = "Query must begin with 'mutation' and include a '%s' placeholder") String query; - @JsonProperty("variables") GraphVariables variables; - String getQuery() { if (query == null) { log.warn("Query nor signature can be null in {}: query template is {} and query must contain '%s' placeholder.", this.getClass().getSimpleName(), query); - return ""; + return null; } return String.format(query, variables.getMutationFields()); } diff --git a/core/src/main/java/org/justserve/model/graph/GraphVariables.java b/core/src/main/java/org/justserve/model/graph/GraphVariables.java index afc0a30..861b7e0 100644 --- a/core/src/main/java/org/justserve/model/graph/GraphVariables.java +++ b/core/src/main/java/org/justserve/model/graph/GraphVariables.java @@ -1,20 +1,40 @@ package org.justserve.model.graph; import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.beans.BeanIntrospection; import io.micronaut.core.beans.BeanIntrospector; +import io.micronaut.core.naming.Named; +import io.micronaut.serde.annotation.Serdeable; import java.util.stream.Collectors; /** *

Array of variables

- * These are the variables used in the {@link GraphQuery#query}. Non-null values are added into the query as well as the variables json object. + * These are the variables used in {@link GraphQuery#query}. Non-null values are added into the query as well as the variables json object. */ @Introspected +@Serdeable public class GraphVariables { String getMutationFields() { - return BeanIntrospector.SHARED.getIntrospection(this.getClass()).getBeanProperties().stream() - .map(prop -> prop.getName() + ": $" + prop.getName()) - .collect(Collectors.joining("\n")); + return getFieldsForBean(this); + } + + private static String getFieldsForBean(T bean) { + @SuppressWarnings("unchecked") + Class beanClass = (Class) bean.getClass(); + BeanIntrospection introspection = BeanIntrospector.SHARED.getIntrospection(beanClass); + + return introspection.getBeanProperties().stream() + .filter(prop -> !prop.getName().equals("mutationFields")) + .filter(prop -> { + try { + return prop.get(bean) != null; + } catch (Exception e) { + return false; + } + }) + .map(Named::getName) + .collect(Collectors.joining("\n ")); } } diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index 379f843..3445814 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -3,12 +3,15 @@ package org.justserve import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject import org.justserve.client.GraphQLClient -import org.justserve.model.EventType -import org.justserve.model.GraphQLCreateProjectVariables -import org.justserve.model.ProjectLocationType +import org.justserve.model.* +import org.justserve.model.graph.CreateEventQuery +import org.justserve.model.graph.Event +import org.justserve.model.graph.GraphQLResponse import spock.lang.Shared import spock.lang.Specification +import java.time.ZonedDateTime + @MicronautTest class GraphQLClientSpec extends Specification { @@ -35,14 +38,22 @@ class GraphQLClientSpec extends Specification { } void "can use createOngoingEvent()"() { -// given: -// GraphQLCreate event = new GraphQLCreateEventVariablesProjectEvent() -// GraphQLCreateEventVariables args = new GraphQLCreateEventVariables() -// .setProjectEvent() -// -// -// when: -// client.createEvent() -// + given: + GraphQLResponse createProjectResponse = client.createProject(new GraphQLCreateProjectVariables() + .setTitle("this is a test") + .setEventType(EventType.Ongoing) + .setLocationType(ProjectLocationType.SINGLE_LOCATION) + ) + Event event = new Event() + .setProjectId(createProjectResponse.getData().getCreateProject().getId()) + .setEnd(ZonedDateTime.now().plusMonths(6)) + .setStart(ZonedDateTime.now().plusMonths(1)) + + when: + GraphQLResponse response = client.createEvent(new CreateEventQuery(event)) + + then: + noExceptionThrown() + !response.hasErrors() } } From a59bcab148a417add5be4cb8ed72d3cecc4c95a8 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Tue, 10 Mar 2026 16:38:26 -0600 Subject: [PATCH 05/30] refactor(wip): build mutation query variables are not sent properly --- .../java/org/justserve/model/graph/Event.java | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 core/src/main/java/org/justserve/model/graph/Event.java diff --git a/core/src/main/java/org/justserve/model/graph/Event.java b/core/src/main/java/org/justserve/model/graph/Event.java new file mode 100644 index 0000000..4ad7ef6 --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/Event.java @@ -0,0 +1,129 @@ +package org.justserve.model.graph; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.serde.annotation.Serdeable; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.time.ZonedDateTime; +import java.util.UUID; + +/** + *

All potential variables to use with {@link CreateEventQuery}

+ * Null values will be omitted from the generated query. + * + * @since 0.1.0 + * @author Jonathan Zollinger + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Serdeable +@Introspected +public class Event extends GraphVariables { + @Nullable + @Email + private String contactEmail; + + @Nullable + @Size(max = 139) + private String contactName; + + @Nullable + private String contactPhone; + + @Nullable + private Boolean deleted; + + @Nullable + private UUID deletedBy; + + @Nullable + private ZonedDateTime deletedOn; + + @Nullable + private ZonedDateTime end; + + @Nullable + private ZonedDateTime endDateTimeOffset; + + @Nullable + private Boolean eventCapReached; + + @Nullable + private Integer groupCap; + + @Nullable + private Integer groupLimit; + + @Nullable + private UUID id; + + @Nullable + private String locationLink; + + @Nullable + @Size(max = 139) + private String locationName; + + @Nullable + private UUID projectEventLocationId; + + @Nullable + private UUID projectId; + + @Nullable + private UUID projectRecurringTimeId; + + @Nullable + private String qrCodeImageLocation; + + @Nullable + private ZonedDateTime renewDate; + + @Nullable + @Size(max = 300) + private String schedule; + + @Nullable + @Size(max = 300) + private String shiftTitle; + + @Nullable + private String specialDirections; + + @Nullable + private ZonedDateTime start; + + @Nullable + private ZonedDateTime startDateTimeOffset; + + @Nullable + private String status; + + @Nullable + private UUID statusId; + + @Nullable + private String timezone; + + @Nullable + private UUID timeZoneEnumId; + + @Nullable + private Integer totalVolunteersNeeded; + + @Nullable + private Integer volunteerCap; + + @Nullable + private Integer volunteersNeeded; +} \ No newline at end of file From 7913c42898442f7790f5431564b8986cf5ef3d10 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Wed, 11 Mar 2026 15:27:40 -0600 Subject: [PATCH 06/30] feat: add working test for createEvent() Signed-off-by: jonathan zollinger --- .../java/org/justserve/model/CreateEvent.java | 14 ---- .../model/graph/CreateEventQuery.java | 21 +++++- .../justserve/model/graph/GraphFields.java | 52 ++++++++++++++ .../org/justserve/model/graph/GraphQuery.java | 72 +++++++++++++++---- .../justserve/model/graph/GraphVariables.java | 34 ++------- .../graph/{Event.java => ProjectEvent.java} | 28 ++++---- .../org/justserve/GraphQLClientSpec.groovy | 15 ++-- core/src/test/resources/SpockConfig.groovy | 10 +-- 8 files changed, 167 insertions(+), 79 deletions(-) delete mode 100644 core/src/main/java/org/justserve/model/CreateEvent.java create mode 100644 core/src/main/java/org/justserve/model/graph/GraphFields.java rename core/src/main/java/org/justserve/model/graph/{Event.java => ProjectEvent.java} (73%) diff --git a/core/src/main/java/org/justserve/model/CreateEvent.java b/core/src/main/java/org/justserve/model/CreateEvent.java deleted file mode 100644 index 3650e5a..0000000 --- a/core/src/main/java/org/justserve/model/CreateEvent.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.justserve.model; - -public class CreateEvent { - String query = """ - mutation createEvent($projectId: ID!, $projectEvent: UpdateProjectEventInput!) { - createEvent( - projectId: $projectId - projectEvent: $projectEvent - ) { - %s - } - } - """; -} diff --git a/core/src/main/java/org/justserve/model/graph/CreateEventQuery.java b/core/src/main/java/org/justserve/model/graph/CreateEventQuery.java index 08c7e77..b5d16a1 100644 --- a/core/src/main/java/org/justserve/model/graph/CreateEventQuery.java +++ b/core/src/main/java/org/justserve/model/graph/CreateEventQuery.java @@ -1,11 +1,21 @@ package org.justserve.model.graph; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import io.micronaut.serde.annotation.Serdeable; +/** + * Data Transfer Object for the {@code createEvent} GraphQL mutation. + * This class dynamically constructs the mutation query based on the non-null fields + * provided in the {@link CreateEventVariables}. + * + * @author Jonathan Zollinger + * @since 0.1.0 + */ @Serdeable +@JsonPropertyOrder({"query", "variables"}) public class CreateEventQuery extends GraphQuery { - public CreateEventQuery(Event variables) { + public CreateEventQuery(CreateEventVariables variables) { this.query = """ mutation createEvent($projectId: ID!, $projectEvent: UpdateProjectEventInput!) { createEvent( @@ -19,4 +29,13 @@ mutation createEvent($projectId: ID!, $projectEvent: UpdateProjectEventInput!) { this.variables = variables; } + @Override + public CreateEventVariables getVariables() { + return (CreateEventVariables) super.getVariables(); + } + + @Override + public String getQuery() { + return String.format(query, getVariables().getProjectEvent().getMutationFields()); + } } diff --git a/core/src/main/java/org/justserve/model/graph/GraphFields.java b/core/src/main/java/org/justserve/model/graph/GraphFields.java new file mode 100644 index 0000000..a5ca278 --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/GraphFields.java @@ -0,0 +1,52 @@ +package org.justserve.model.graph; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.beans.BeanIntrospection; +import io.micronaut.core.beans.BeanIntrospector; +import io.micronaut.core.naming.Named; +import io.micronaut.serde.annotation.Serdeable; + +import java.util.stream.Collectors; + +/** + *

Fields used in a graphql mutation.

+ * These are the fields used in{@link GraphQuery#query} fields. + * + * @author Jonathan Zollinger + * @since 0.1.0 + */ +@Introspected +@Serdeable +public class GraphFields { + + @JsonIgnore + String getMutationFields() { + return getFieldsForBean(this); + } + + /** + * reflection free implementation of querying non-null fields for a bean. + * + * @param bean class which is being queried + * @param class type + * @return all fields that are not null + */ + private static String getFieldsForBean(T bean) { + @SuppressWarnings("unchecked") + Class beanClass = (Class) bean.getClass(); + BeanIntrospection introspection = BeanIntrospector.SHARED.getIntrospection(beanClass); + + return introspection.getBeanProperties().stream() + .filter(prop -> !prop.getName().equals("mutationFields")) + .filter(prop -> { + try { + return prop.get(bean) != null; + } catch (Exception e) { + return false; + } + }) + .map(Named::getName) + .collect(Collectors.joining("\n ")); + } +} diff --git a/core/src/main/java/org/justserve/model/graph/GraphQuery.java b/core/src/main/java/org/justserve/model/graph/GraphQuery.java index a9262b2..841555e 100644 --- a/core/src/main/java/org/justserve/model/graph/GraphQuery.java +++ b/core/src/main/java/org/justserve/model/graph/GraphQuery.java @@ -1,19 +1,27 @@ package org.justserve.model.graph; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import io.micronaut.serde.annotation.Serdeable; import jakarta.validation.constraints.Pattern; import lombok.Getter; -import lombok.extern.slf4j.Slf4j; +import org.justserve.client.GraphQLClient; -@Slf4j +/** + * Abstract base class used to serialize the JSON payload sent via the{@link GraphQLClient} + * for GraphQL operations. + * + * @author Jonathan Zollinger + * @since 0.1.0 + */ @Serdeable @Getter -public class GraphQuery { +@JsonPropertyOrder({"query", "variables"}) +public abstract class GraphQuery { /** - *

Mutation Query String

formatted to receive a \n-delimited string of variable names.
+ *

Mutation Query String

formatted to receive a (@code \n) delimited string of variable names.
* The query is to include the mutation's signature, as well as its opening and closing curly braces.
- *
Example:
{@code "mutation ($projectId: ID!, $attachmentId: ID!) {\\n %s\\n" } + *
Example:
{@code "mutation ($projectId: ID!, $attachmentId: ID!) {\\n %s\\n}" } */ //"^mutation\\b[\\s\\S]*\\{[\\s\\S]*%s[\\s\\S]*\\}$" @Pattern(regexp = "^mutation.*\\{.*%s.*}.*", message = "Query must begin with 'mutation' and include a '%s' placeholder") @@ -22,11 +30,51 @@ public class GraphQuery { @JsonProperty("variables") GraphVariables variables; - String getQuery() { - if (query == null) { - log.warn("Query nor signature can be null in {}: query template is {} and query must contain '%s' placeholder.", this.getClass().getSimpleName(), query); - return null; - } - return String.format(query, variables.getMutationFields()); - } + /** + *

{@summary Gets the query string used in this mutation object.}

+ * Produces the dynamic string used for the mutation. Fields with null values are not included in the query. + *
Example:
+ * {@code getQuery} would return this string if the variables include non-null values for id, projectId, contactEmail, + * contactName, contactPhone, start and end.
+ *
+     * mutation createEvent($projectId: ID!, $projectEvent: UpdateProjectEventInput!) {
+     *      createEvent(
+     *          projectId: $projectId
+     *          projectEvent: $projectEvent
+     *      ) {
+     *          id
+     *          projectId
+     *          contactEmail
+     *          contactName
+     *          contactPhone
+     *          start
+     *          end
+     *      }
+     * }
+     * 
+ *

+ * {@code getQuery} would return this string if the variables did not include non-null values for the contact + * information: + * + *

+     * mutation createEvent($projectId: ID!, $projectEvent: UpdateProjectEventInput!) {
+     *      createEvent(
+     *          projectId: $projectId
+     *          projectEvent: $projectEvent
+     *      ) {
+     *          id
+     *          projectId
+     *          start
+     *          end
+     *      }
+     * }
+     * 
+ * + *

NOTE

+ * Only the value property names values are provided in this part of the query. See{@link GraphFields#getMutationFields()} + * + * @return The entire graphql mutation string. + * + */ + public abstract String getQuery(); } diff --git a/core/src/main/java/org/justserve/model/graph/GraphVariables.java b/core/src/main/java/org/justserve/model/graph/GraphVariables.java index 861b7e0..a957e9e 100644 --- a/core/src/main/java/org/justserve/model/graph/GraphVariables.java +++ b/core/src/main/java/org/justserve/model/graph/GraphVariables.java @@ -1,40 +1,14 @@ package org.justserve.model.graph; -import io.micronaut.core.annotation.Introspected; -import io.micronaut.core.beans.BeanIntrospection; -import io.micronaut.core.beans.BeanIntrospector; -import io.micronaut.core.naming.Named; import io.micronaut.serde.annotation.Serdeable; -import java.util.stream.Collectors; - /** - *

Array of variables

- * These are the variables used in {@link GraphQuery#query}. Non-null values are added into the query as well as the variables json object. + * Parent class to any variables to be used in a{@link GraphQuery#variables} setting. + * + * @author Jonathan Zollinger + * @since 0.1.0 */ -@Introspected @Serdeable public class GraphVariables { - String getMutationFields() { - return getFieldsForBean(this); - } - - private static String getFieldsForBean(T bean) { - @SuppressWarnings("unchecked") - Class beanClass = (Class) bean.getClass(); - BeanIntrospection introspection = BeanIntrospector.SHARED.getIntrospection(beanClass); - - return introspection.getBeanProperties().stream() - .filter(prop -> !prop.getName().equals("mutationFields")) - .filter(prop -> { - try { - return prop.get(bean) != null; - } catch (Exception e) { - return false; - } - }) - .map(Named::getName) - .collect(Collectors.joining("\n ")); - } } diff --git a/core/src/main/java/org/justserve/model/graph/Event.java b/core/src/main/java/org/justserve/model/graph/ProjectEvent.java similarity index 73% rename from core/src/main/java/org/justserve/model/graph/Event.java rename to core/src/main/java/org/justserve/model/graph/ProjectEvent.java index 4ad7ef6..eb29f17 100644 --- a/core/src/main/java/org/justserve/model/graph/Event.java +++ b/core/src/main/java/org/justserve/model/graph/ProjectEvent.java @@ -1,5 +1,6 @@ package org.justserve.model.graph; +import com.fasterxml.jackson.annotation.JsonFormat; import io.micronaut.core.annotation.Introspected; import io.micronaut.core.annotation.Nullable; import io.micronaut.serde.annotation.Serdeable; @@ -11,7 +12,7 @@ import lombok.NoArgsConstructor; import lombok.experimental.Accessors; -import java.time.ZonedDateTime; +import java.util.Date; import java.util.UUID; /** @@ -28,7 +29,7 @@ @AllArgsConstructor @Serdeable @Introspected -public class Event extends GraphVariables { +public class ProjectEvent extends GraphFields { @Nullable @Email private String contactEmail; @@ -47,13 +48,16 @@ public class Event extends GraphVariables { private UUID deletedBy; @Nullable - private ZonedDateTime deletedOn; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") + private Date deletedOn; @Nullable - private ZonedDateTime end; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") + private Date end; @Nullable - private ZonedDateTime endDateTimeOffset; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") + private Date endDateTimeOffset; @Nullable private Boolean eventCapReached; @@ -77,9 +81,6 @@ public class Event extends GraphVariables { @Nullable private UUID projectEventLocationId; - @Nullable - private UUID projectId; - @Nullable private UUID projectRecurringTimeId; @@ -87,7 +88,8 @@ public class Event extends GraphVariables { private String qrCodeImageLocation; @Nullable - private ZonedDateTime renewDate; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") + private Date renewDate; @Nullable @Size(max = 300) @@ -101,10 +103,12 @@ public class Event extends GraphVariables { private String specialDirections; @Nullable - private ZonedDateTime start; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") + private Date start; @Nullable - private ZonedDateTime startDateTimeOffset; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") + private Date startDateTimeOffset; @Nullable private String status; @@ -126,4 +130,4 @@ public class Event extends GraphVariables { @Nullable private Integer volunteersNeeded; -} \ No newline at end of file +} diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index 3445814..6bd2cb7 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -5,8 +5,9 @@ import jakarta.inject.Inject import org.justserve.client.GraphQLClient import org.justserve.model.* import org.justserve.model.graph.CreateEventQuery -import org.justserve.model.graph.Event +import org.justserve.model.graph.CreateEventVariables import org.justserve.model.graph.GraphQLResponse +import org.justserve.model.graph.ProjectEvent import spock.lang.Shared import spock.lang.Specification @@ -44,13 +45,17 @@ class GraphQLClientSpec extends Specification { .setEventType(EventType.Ongoing) .setLocationType(ProjectLocationType.SINGLE_LOCATION) ) - Event event = new Event() + ProjectEvent event = new ProjectEvent() + .setStart(Date.from(ZonedDateTime.now().plusMonths(1).toInstant())) + .setEnd(Date.from(ZonedDateTime.now().plusMonths(6).toInstant())) + CreateEventVariables vars = new CreateEventVariables() .setProjectId(createProjectResponse.getData().getCreateProject().getId()) - .setEnd(ZonedDateTime.now().plusMonths(6)) - .setStart(ZonedDateTime.now().plusMonths(1)) + .setProjectEvent(event) when: - GraphQLResponse response = client.createEvent(new CreateEventQuery(event)) + CreateEventQuery query = new CreateEventQuery(vars) + + GraphQLResponse response = client.createEvent(query) then: noExceptionThrown() diff --git a/core/src/test/resources/SpockConfig.groovy b/core/src/test/resources/SpockConfig.groovy index 267ec1e..ee30777 100644 --- a/core/src/test/resources/SpockConfig.groovy +++ b/core/src/test/resources/SpockConfig.groovy @@ -1,5 +1,5 @@ -runner { - parallel { - enabled true - } -} \ No newline at end of file +//runner { +// parallel { +// enabled true +// } +//} \ No newline at end of file From cdfda63003f76c392858285584840e4f2af311ba Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Wed, 11 Mar 2026 15:53:49 -0600 Subject: [PATCH 07/30] refactor: rename CreateEventFields Signed-off-by: jonathan zollinger --- .../graph/{ProjectEvent.java => CreateEventFields.java} | 2 +- .../test/groovy/org/justserve/GraphQLClientSpec.groovy | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) rename core/src/main/java/org/justserve/model/graph/{ProjectEvent.java => CreateEventFields.java} (98%) diff --git a/core/src/main/java/org/justserve/model/graph/ProjectEvent.java b/core/src/main/java/org/justserve/model/graph/CreateEventFields.java similarity index 98% rename from core/src/main/java/org/justserve/model/graph/ProjectEvent.java rename to core/src/main/java/org/justserve/model/graph/CreateEventFields.java index eb29f17..45e4ea2 100644 --- a/core/src/main/java/org/justserve/model/graph/ProjectEvent.java +++ b/core/src/main/java/org/justserve/model/graph/CreateEventFields.java @@ -29,7 +29,7 @@ @AllArgsConstructor @Serdeable @Introspected -public class ProjectEvent extends GraphFields { +public class CreateEventFields extends GraphFields { @Nullable @Email private String contactEmail; diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index 6bd2cb7..35251be 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -3,13 +3,15 @@ package org.justserve import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject import org.justserve.client.GraphQLClient -import org.justserve.model.* +import org.justserve.model.EventType +import org.justserve.model.GraphQLCreateProjectVariables +import org.justserve.model.ProjectLocationType +import org.justserve.model.graph.CreateEventFields import org.justserve.model.graph.CreateEventQuery import org.justserve.model.graph.CreateEventVariables -import org.justserve.model.graph.GraphQLResponse -import org.justserve.model.graph.ProjectEvent import spock.lang.Shared import spock.lang.Specification +import spock.lang.Unroll import java.time.ZonedDateTime From 82d86545414336580088a28b2b3db2628a805f4f Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Wed, 11 Mar 2026 15:54:04 -0600 Subject: [PATCH 08/30] refactor: remove unused pojo Signed-off-by: jonathan zollinger --- .../justserve/model/graph/GraphQLRequest.java | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 core/src/main/java/org/justserve/model/graph/GraphQLRequest.java diff --git a/core/src/main/java/org/justserve/model/graph/GraphQLRequest.java b/core/src/main/java/org/justserve/model/graph/GraphQLRequest.java deleted file mode 100644 index a4fa059..0000000 --- a/core/src/main/java/org/justserve/model/graph/GraphQLRequest.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.justserve.model.graph; - -import io.micronaut.serde.annotation.Serdeable; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Serdeable -@Data -@NoArgsConstructor -@AllArgsConstructor -public class GraphQLRequest { - - private String query; - - private Object variables; -} From 5ddbcc8007dec070a58107e758dcdbc6f8948873 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Wed, 11 Mar 2026 15:54:21 -0600 Subject: [PATCH 09/30] test(wip): add controlled chaos Signed-off-by: jonathan zollinger --- .../org/justserve/GraphQLClientSpec.groovy | 71 +++++++++++++++---- 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index 35251be..a881301 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -35,32 +35,79 @@ class GraphQLClientSpec extends Specification { then: noExceptionThrown() + !response.hasErrors() where: [eventType, locationType, redirect] << [EventType.values(), ProjectLocationType.values(), ["", null, "https://google.com"]].combinations() } - void "can use createOngoingEvent()"() { + @Unroll + void "can use createOngoingEvent() with combination of fields"(String contactEmail, String contactName, String contactPhone, Boolean deleted, Date end, Boolean eventCapReached, Integer groupCap, Integer groupLimit, String locationLink, String locationName, String qrCodeImageLocation, Date renewDate, String schedule, String shiftTitle, String specialDirections, Date start, String status, String timezone, Integer totalVolunteersNeeded, Integer volunteerCap, Integer volunteersNeeded) { given: - GraphQLResponse createProjectResponse = client.createProject(new GraphQLCreateProjectVariables() + def projectId = client.createProject(new GraphQLCreateProjectVariables() .setTitle("this is a test") .setEventType(EventType.Ongoing) - .setLocationType(ProjectLocationType.SINGLE_LOCATION) - ) - ProjectEvent event = new ProjectEvent() - .setStart(Date.from(ZonedDateTime.now().plusMonths(1).toInstant())) - .setEnd(Date.from(ZonedDateTime.now().plusMonths(6).toInstant())) - CreateEventVariables vars = new CreateEventVariables() - .setProjectId(createProjectResponse.getData().getCreateProject().getId()) + .setLocationType(ProjectLocationType.SINGLE_location) + ).data.createProject.id + + def event = new CreateEventFields() + .setContactEmail(contactEmail) + .setContactName(contactName) + .setContactPhone(contactPhone) + .setDeleted(deleted) + .setEnd(end) + .setEventCapReached(eventCapReached) + .setGroupCap(groupCap) + .setGroupLimit(groupLimit) + .setLocationLink(locationLink) + .setLocationName(locationName) + .setQrCodeImageLocation(qrCodeImageLocation) + .setRenewDate(renewDate) + .setSchedule(schedule) + .setShiftTitle(shiftTitle) + .setSpecialDirections(specialDirections) + .setStart(start) + .setStatus(status) + .setTimezone(timezone) + .setTotalVolunteersNeeded(totalVolunteersNeeded) + .setVolunteerCap(volunteerCap) + .setVolunteersNeeded(volunteersNeeded) + + def vars = new CreateEventVariables() + .setProjectId(projectId) .setProjectEvent(event) when: - CreateEventQuery query = new CreateEventQuery(vars) - - GraphQLResponse response = client.createEvent(query) + def query = new CreateEventQuery(vars) + def response = client.createEvent(query) then: noExceptionThrown() !response.hasErrors() + + where: + [contactEmail, contactName, contactPhone, deleted, end, eventCapReached, groupCap, groupLimit, locationLink, locationName, qrCodeImageLocation, renewDate, schedule, shiftTitle, specialDirections, start, status, timezone, totalVolunteersNeeded, volunteerCap, volunteersNeeded] << [ + ["test@test.com", "a@a.com", null], + ["name", "another name", null], + ["123-456-7890", "9876543210", null], + [true, false, null], + [Date.from(ZonedDateTime.now().plusMonths(6).toInstant()), Date.from(ZonedDateTime.now().plusYears(1).toInstant()), null], + [true, false, null], + [10, 20, null], + [5, 10, null], + ["https://google.com", "https://bing.com", null], + ["location", "another location", null], + ["/qr/code.png", "/another/qr.png", null], + [Date.from(ZonedDateTime.now().plusYears(1).toInstant()), Date.from(ZonedDateTime.now().plusYears(2).toInstant()), null], + ["schedule", "another schedule", null], + ["shift", "another shift", null], + ["directions", "more directions", null], + [Date.from(ZonedDateTime.now().plusMonths(1).toInstant()), Date.from(ZonedDateTime.now().plusDays(15)), null], + ["active", "inactive", null], + ["UTC", "America/New_York", null], + [100, 200, null], + [50, 100, null], + [25, 50, null] + ].combinations() } } From 62c0db86338d07bdcca1afee5a34902cc8616ab7 Mon Sep 17 00:00:00 2001 From: Jonathan Zollinger Date: Wed, 11 Mar 2026 16:04:20 -0600 Subject: [PATCH 10/30] test: lean into providing fake data Signed-off-by: jonathan zollinger --- .../org/justserve/GraphQLClientSpec.groovy | 75 +++++++++++++------ 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index a881301..a223e57 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -2,6 +2,7 @@ package org.justserve import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject +import net.datafaker.Faker import org.justserve.client.GraphQLClient import org.justserve.model.EventType import org.justserve.model.GraphQLCreateProjectVariables @@ -13,7 +14,7 @@ import spock.lang.Shared import spock.lang.Specification import spock.lang.Unroll -import java.time.ZonedDateTime +import java.util.concurrent.TimeUnit @MicronautTest class GraphQLClientSpec extends Specification { @@ -22,6 +23,9 @@ class GraphQLClientSpec extends Specification { @Inject GraphQLClient client + @Shared + Faker faker = new Faker() + void "can create Project with EventType: #eventType, LocationType: #locationType, and Redirect: #redirect"(EventType eventType, ProjectLocationType locationType, String redirect) { given: GraphQLCreateProjectVariables args = new GraphQLCreateProjectVariables() @@ -47,7 +51,7 @@ class GraphQLClientSpec extends Specification { def projectId = client.createProject(new GraphQLCreateProjectVariables() .setTitle("this is a test") .setEventType(EventType.Ongoing) - .setLocationType(ProjectLocationType.SINGLE_location) + .setLocationType(ProjectLocationType.SINGLE_LOCATION) ).data.createProject.id def event = new CreateEventFields() @@ -82,32 +86,61 @@ class GraphQLClientSpec extends Specification { def response = client.createEvent(query) then: + if (response.hasErrors()) { + def errorLog = """ + ${response.errors} + Errors for combination: + contactEmail: ${contactEmail} + contactName: ${contactName} + contactPhone: ${contactPhone} + deleted: ${deleted} + end: ${end} + eventCapReached: ${eventCapReached} + groupCap: ${groupCap} + groupLimit: ${groupLimit} + locationLink: ${locationLink} + locationName: ${locationName} + qrCodeImageLocation: ${qrCodeImageLocation} + renewDate: ${renewDate} + schedule: ${schedule} + shiftTitle: ${shiftTitle} + specialDirections: ${specialDirections} + start: ${start} + status: ${status} + timezone: ${timezone} + totalVolunteersNeeded: ${totalVolunteersNeeded} + volunteerCap: ${volunteerCap} + volunteersNeeded: ${volunteersNeeded} + + """ + new File('build/test-errors.log').append(errorLog) + } noExceptionThrown() !response.hasErrors() where: [contactEmail, contactName, contactPhone, deleted, end, eventCapReached, groupCap, groupLimit, locationLink, locationName, qrCodeImageLocation, renewDate, schedule, shiftTitle, specialDirections, start, status, timezone, totalVolunteersNeeded, volunteerCap, volunteersNeeded] << [ - ["test@test.com", "a@a.com", null], - ["name", "another name", null], - ["123-456-7890", "9876543210", null], + [faker.internet().emailAddress(), null], + [faker.name().fullName(), null], + [faker.phoneNumber().phoneNumber(), null], [true, false, null], - [Date.from(ZonedDateTime.now().plusMonths(6).toInstant()), Date.from(ZonedDateTime.now().plusYears(1).toInstant()), null], + [faker.timeAndDate().future(365, TimeUnit.DAYS), null], [true, false, null], - [10, 20, null], - [5, 10, null], - ["https://google.com", "https://bing.com", null], - ["location", "another location", null], - ["/qr/code.png", "/another/qr.png", null], - [Date.from(ZonedDateTime.now().plusYears(1).toInstant()), Date.from(ZonedDateTime.now().plusYears(2).toInstant()), null], - ["schedule", "another schedule", null], - ["shift", "another shift", null], - ["directions", "more directions", null], - [Date.from(ZonedDateTime.now().plusMonths(1).toInstant()), Date.from(ZonedDateTime.now().plusDays(15)), null], - ["active", "inactive", null], - ["UTC", "America/New_York", null], - [100, 200, null], - [50, 100, null], - [25, 50, null] + [(+faker.number()), null], + [(+faker.number()), null], + [faker.internet().url(), null], + [faker.address().fullAddress(), null], + [faker.internet().url(), null], + [faker.timeAndDate().future(730, TimeUnit.DAYS), null], + [faker.lorem().sentence(), null], + [faker.lorem().sentence(), null], + [faker.lorem().paragraph(), null], + [faker.timeAndDate().future(180, TimeUnit.DAYS), null], + [faker.lorem().word(), null], + [faker.address().timeZone(), null], + [(+faker.number()), null], + [(+faker.number()), null], + [(+faker.number()), null] ].combinations() } } From b240dfd30eb10c8654219aa8ad787e9d52ce05d3 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Thu, 12 Mar 2026 10:47:25 -0600 Subject: [PATCH 11/30] fix: write custom ProjectStatus Enum Signed-off-by: jonathan zollinger --- core/build.gradle.kts | 7 +++ .../org/justserve/model/ProjectStatus.java | 61 +++++++++++++++++++ .../model/graph/CreateEventFields.java | 16 +++-- core/src/main/resources/schema.yml | 21 ++++--- .../org/justserve/GraphQLClientSpec.groovy | 33 +++++----- 5 files changed, 106 insertions(+), 32 deletions(-) create mode 100644 core/src/main/java/org/justserve/model/ProjectStatus.java diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 1359330..3b97137 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -58,6 +58,8 @@ micronaut { importMapping.put("EventType", "org.justserve.model.EventType") schemaMapping.put("ProjectLocationType", "org.justserve.model.ProjectLocationType") importMapping.put("ProjectLocationType", "org.justserve.model.ProjectLocationType") + schemaMapping.put("ProjectStatus", "org.justserve.model.ProjectStatus") + importMapping.put("ProjectStatus", "org.justserve.model.ProjectStatus") schemaMapping.put("TimeZone", "org.justserve.model.TimeZone") importMapping.put("TimeZone", "org.justserve.model.TimeZone") } @@ -68,6 +70,11 @@ micronaut { } } +tasks.test { + maxHeapSize = "6144m" + minHeapSize = "512m" +} + tasks.withType { val props = Properties() file("../gradle.properties").inputStream().use { props.load(it) } diff --git a/core/src/main/java/org/justserve/model/ProjectStatus.java b/core/src/main/java/org/justserve/model/ProjectStatus.java new file mode 100644 index 0000000..96a31c3 --- /dev/null +++ b/core/src/main/java/org/justserve/model/ProjectStatus.java @@ -0,0 +1,61 @@ +package org.justserve.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import io.micronaut.serde.annotation.Serdeable; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Gets or Sets ProjectStatus + */ +@RequiredArgsConstructor +@Serdeable +public enum ProjectStatus { + PUBLISHED(1, "PUBLISHED"), + SUBMITTED(2, "SUBMITTED"), + DRAFT(3, "DRAFT"), + TEMPLATE(4, "TEMPLATE"), + ON_HOLD(5, "ON_HOLD"), + CANCELLED(6, "CANCELLED"), + DECLINED(7, "DECLINED"); + + public static final Map VALUE_MAPPING = Map.copyOf(Arrays.stream(values()) + .collect(Collectors.toMap(v -> v.intValue, Function.identity()))); + + private final Integer intValue; + private final String stringValue; + + @Override + public String toString() { + return String.valueOf(intValue); + } + + @JsonValue + public String getStringValue() { + return stringValue; + } + + /** + * 2. RECEIVING (Response): This catches the incoming data. + * It can handle the Integer '1' from GraphQL, or even a String if a REST endpoint sends one. + */ + @JsonCreator + public static ProjectStatus fromValue(Object value) { + if (value instanceof Number) { + int intVal = ((Number) value).intValue(); + for (ProjectStatus type : values()) { + if (type.intValue == intVal) return type; + } + } else if (value instanceof String strVal) { + for (ProjectStatus type : values()) { + if (type.stringValue.equalsIgnoreCase(strVal)) return type; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "' for ProjectStatus"); + } +} diff --git a/core/src/main/java/org/justserve/model/graph/CreateEventFields.java b/core/src/main/java/org/justserve/model/graph/CreateEventFields.java index 45e4ea2..273186d 100644 --- a/core/src/main/java/org/justserve/model/graph/CreateEventFields.java +++ b/core/src/main/java/org/justserve/model/graph/CreateEventFields.java @@ -11,6 +11,7 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; +import org.justserve.model.ProjectStatus; import java.util.Date; import java.util.UUID; @@ -62,8 +63,11 @@ public class CreateEventFields extends GraphFields { @Nullable private Boolean eventCapReached; + /** + * whether a group cap is set for this event. + */ @Nullable - private Integer groupCap; + private Boolean groupCap; @Nullable private Integer groupLimit; @@ -111,10 +115,7 @@ public class CreateEventFields extends GraphFields { private Date startDateTimeOffset; @Nullable - private String status; - - @Nullable - private UUID statusId; + private ProjectStatus status; @Nullable private String timezone; @@ -125,8 +126,11 @@ public class CreateEventFields extends GraphFields { @Nullable private Integer totalVolunteersNeeded; + /** + * whether a volunteer cap is set for this event. + */ @Nullable - private Integer volunteerCap; + private Boolean volunteerCap; @Nullable private Integer volunteersNeeded; diff --git a/core/src/main/resources/schema.yml b/core/src/main/resources/schema.yml index 9a441d4..5f21842 100644 --- a/core/src/main/resources/schema.yml +++ b/core/src/main/resources/schema.yml @@ -946,16 +946,17 @@ components: relativityScore: { type: number, format: double } additionalProperties: false ProjectStatus: - type: string - enum: - - None - - Published - - Submitted - - Draft - - Template - - OnHold - - Cancelled - - Declined + type: integer + format: int32 + enum: [ 1, 2, 3, 4, 5, 6, 7 ] + x-enum-varnames: + - PUBLISHED + - SUBMITTED + - DRAFT + - TEMPLATE + - ON_HOLD + - CANCELLED + - DECLINED UserSlimSearchResult: description: | high level information for a given user diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index a223e57..7a768bb 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -7,6 +7,7 @@ import org.justserve.client.GraphQLClient import org.justserve.model.EventType import org.justserve.model.GraphQLCreateProjectVariables import org.justserve.model.ProjectLocationType +import org.justserve.model.ProjectStatus import org.justserve.model.graph.CreateEventFields import org.justserve.model.graph.CreateEventQuery import org.justserve.model.graph.CreateEventVariables @@ -46,7 +47,7 @@ class GraphQLClientSpec extends Specification { } @Unroll - void "can use createOngoingEvent() with combination of fields"(String contactEmail, String contactName, String contactPhone, Boolean deleted, Date end, Boolean eventCapReached, Integer groupCap, Integer groupLimit, String locationLink, String locationName, String qrCodeImageLocation, Date renewDate, String schedule, String shiftTitle, String specialDirections, Date start, String status, String timezone, Integer totalVolunteersNeeded, Integer volunteerCap, Integer volunteersNeeded) { + void "can use createOngoingEvent() with combination of fields"(String contactEmail, String contactName, String contactPhone, Boolean deleted, Date end, Boolean eventCapReached, Boolean hasGroupCap, Integer groupLimit, String locationLink, String locationName, String qrCodeImageLocation, Date renewDate, String schedule, String shiftTitle, String specialDirections, Date start, ProjectStatus status, String timezone, Integer totalVolunteersNeeded, Boolean hasVolunteerCap, Integer volunteersNeeded) { given: def projectId = client.createProject(new GraphQLCreateProjectVariables() .setTitle("this is a test") @@ -61,7 +62,7 @@ class GraphQLClientSpec extends Specification { .setDeleted(deleted) .setEnd(end) .setEventCapReached(eventCapReached) - .setGroupCap(groupCap) + .setGroupCap(hasGroupCap) .setGroupLimit(groupLimit) .setLocationLink(locationLink) .setLocationName(locationName) @@ -74,7 +75,7 @@ class GraphQLClientSpec extends Specification { .setStatus(status) .setTimezone(timezone) .setTotalVolunteersNeeded(totalVolunteersNeeded) - .setVolunteerCap(volunteerCap) + .setVolunteerCap(hasVolunteerCap) .setVolunteersNeeded(volunteersNeeded) def vars = new CreateEventVariables() @@ -96,7 +97,7 @@ class GraphQLClientSpec extends Specification { deleted: ${deleted} end: ${end} eventCapReached: ${eventCapReached} - groupCap: ${groupCap} + hasGroupCap: ${hasGroupCap} groupLimit: ${groupLimit} locationLink: ${locationLink} locationName: ${locationName} @@ -109,7 +110,7 @@ class GraphQLClientSpec extends Specification { status: ${status} timezone: ${timezone} totalVolunteersNeeded: ${totalVolunteersNeeded} - volunteerCap: ${volunteerCap} + hasVolunteerCap: ${hasVolunteerCap} volunteersNeeded: ${volunteersNeeded} """ @@ -119,28 +120,28 @@ class GraphQLClientSpec extends Specification { !response.hasErrors() where: - [contactEmail, contactName, contactPhone, deleted, end, eventCapReached, groupCap, groupLimit, locationLink, locationName, qrCodeImageLocation, renewDate, schedule, shiftTitle, specialDirections, start, status, timezone, totalVolunteersNeeded, volunteerCap, volunteersNeeded] << [ + [contactEmail, contactName, contactPhone, deleted, end, eventCapReached, hasGroupCap, groupLimit, locationLink, locationName, qrCodeImageLocation, renewDate, schedule, shiftTitle, specialDirections, start, status, timezone, totalVolunteersNeeded, hasVolunteerCap, volunteersNeeded] << [ [faker.internet().emailAddress(), null], - [faker.name().fullName(), null], + [faker.lorem().characters(1, 139), null], [faker.phoneNumber().phoneNumber(), null], [true, false, null], - [faker.timeAndDate().future(365, TimeUnit.DAYS), null], + [Date.from(faker.timeAndDate().future(365, TimeUnit.DAYS)), null], + [true, false, null], [true, false, null], - [(+faker.number()), null], [(+faker.number()), null], [faker.internet().url(), null], - [faker.address().fullAddress(), null], + [faker.lorem().characters(1, 139), null], [faker.internet().url(), null], - [faker.timeAndDate().future(730, TimeUnit.DAYS), null], - [faker.lorem().sentence(), null], - [faker.lorem().sentence(), null], + [Date.from(faker.timeAndDate().future(730, TimeUnit.DAYS)), null], + [faker.lorem().characters(1, 300), null], + [faker.lorem().characters(1, 300), null], [faker.lorem().paragraph(), null], - [faker.timeAndDate().future(180, TimeUnit.DAYS), null], - [faker.lorem().word(), null], + [Date.from(faker.timeAndDate().future(180, TimeUnit.DAYS)), null], + [ProjectStatus.values(), null].flatten(), [faker.address().timeZone(), null], [(+faker.number()), null], [(+faker.number()), null], [(+faker.number()), null] - ].combinations() + ].combinations().getFirst() } } From 00d7ac1a433fc5208a2c9928f611db56a2045ae0 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Thu, 12 Mar 2026 14:47:42 -0600 Subject: [PATCH 12/30] test(wip): drop me Signed-off-by: jonathan zollinger --- .../java/org/justserve/model/TimeZone.java | 328 ++++++++++-------- .../model/graph/CreateEventFields.java | 41 +-- .../org/justserve/GraphQLClientSpec.groovy | 65 ++-- 3 files changed, 213 insertions(+), 221 deletions(-) diff --git a/core/src/main/java/org/justserve/model/TimeZone.java b/core/src/main/java/org/justserve/model/TimeZone.java index 30a69cb..61da419 100644 --- a/core/src/main/java/org/justserve/model/TimeZone.java +++ b/core/src/main/java/org/justserve/model/TimeZone.java @@ -1,155 +1,191 @@ package org.justserve.model; -import lombok.Getter; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import io.micronaut.serde.annotation.Serdeable; +import lombok.RequiredArgsConstructor; -@Getter +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Serdeable public enum TimeZone { - INTERNATIONAL_DATE_LINE_WEST("(UTC-12:00) International Date Line West"), - COORDINATED_UNIVERSAL_TIME_11("(UTC-11:00) Coordinated Universal Time-11"), - ALEUTIAN_ISLANDS("(UTC-10:00) Aleutian Islands"), - HAWAII("(UTC-10:00) Hawaii"), - MARQUESAS_ISLANDS("(UTC-09:30) Marquesas Islands"), - ALASKA("(UTC-09:00) Alaska"), - COORDINATED_UNIVERSAL_TIME_09("(UTC-09:00) Coordinated Universal Time-09"), - BAJA_CALIFORNIA("(UTC-08:00) Baja California"), - COORDINATED_UNIVERSAL_TIME_08("(UTC-08:00) Coordinated Universal Time-08"), - PACIFIC_TIME_US_AND_CANADA("(UTC-08:00) Pacific Time (US & Canada)"), - ARIZONA("(UTC-07:00) Arizona"), - LA_PAZ("(UTC-07:00) La Paz"), - MOUNTAIN_TIME_US_AND_CANADA("(UTC-07:00) Mountain Time (US & Canada)"), - YUKON("(UTC-07:00) Yukon"), - CENTRAL_AMERICA("(UTC-06:00) Central America"), - CENTRAL_TIME_US_AND_CANADA("(UTC-06:00) Central Time (US & Canada)"), - EASTER_ISLAND("(UTC-06:00) Easter Island"), - GUADALAJARA("(UTC-06:00) Guadalajara"), - SASKATCHEWAN("(UTC-06:00) Saskatchewan"), - BOGOTA("(UTC-05:00) Bogota"), - CHETUMAL("(UTC-05:00) Chetumal"), - EASTERN_TIME_US_AND_CANADA("(UTC-05:00) Eastern Time (US & Canada)"), - HAITI("(UTC-05:00) Haiti"), - HAVANA("(UTC-05:00) Havana"), - INDIANA_EAST("(UTC-05:00) Indiana (East)"), - TURKS_AND_CAICOS("(UTC-05:00) Turks and Caicos"), - ASUNCION("(UTC-04:00) Asuncion"), - ATLANTIC_TIME_CANADA("(UTC-04:00) Atlantic Time (Canada)"), - CARACAS("(UTC-04:00) Caracas"), - CUIABA("(UTC-04:00) Cuiaba"), - GEORGETOWN("(UTC-04:00) Georgetown"), - SANTIAGO("(UTC-04:00) Santiago"), - NEWFOUNDLAND("(UTC-03:30) Newfoundland"), - ARAGUAINA("(UTC-03:00) Araguaina"), - BRASILIA("(UTC-03:00) Brasilia"), - CAYENNE("(UTC-03:00) Cayenne"), - CITY_OF_BUENOS_AIRES("(UTC-03:00) City of Buenos Aires"), - MONTEVIDEO("(UTC-03:00) Montevideo"), - PUNTA_ARENAS("(UTC-03:00) Punta Arenas"), - SAINT_PIERRE_AND_MIQUELON("(UTC-03:00) Saint Pierre and Miquelon"), - SALVADOR("(UTC-03:00) Salvador"), - COORDINATED_UNIVERSAL_TIME_02("(UTC-02:00) Coordinated Universal Time-02"), - GREENLAND("(UTC-02:00) Greenland"), - MID_ATLANTIC_OLD("(UTC-02:00) Mid-Atlantic - Old"), - AZORES("(UTC-01:00) Azores"), - CABO_VERDE_IS("(UTC-01:00) Cabo Verde Is."), - COORDINATED_UNIVERSAL_TIME("(UTC) Coordinated Universal Time"), - DUBLIN("(UTC+00:00) Dublin"), - MONROVIA("(UTC+00:00) Monrovia"), - SAO_TOME("(UTC+00:00) Sao Tome"), - CASABLANCA("(UTC+01:00) Casablanca"), - AMSTERDAM("(UTC+01:00) Amsterdam"), - BELGRADE("(UTC+01:00) Belgrade"), - BRUSSELS("(UTC+01:00) Brussels"), - SARAJEVO("(UTC+01:00) Sarajevo"), - WEST_CENTRAL_AFRICA("(UTC+01:00) West Central Africa"), - ATHENS("(UTC+02:00) Athens"), - BEIRUT("(UTC+02:00) Beirut"), - CAIRO("(UTC+02:00) Cairo"), - CHISINAU("(UTC+02:00) Chisinau"), - GAZA("(UTC+02:00) Gaza"), - HARARE("(UTC+02:00) Harare"), - HELSINKI("(UTC+02:00) Helsinki"), - JERUSALEM("(UTC+02:00) Jerusalem"), - JUBA("(UTC+02:00) Juba"), - KALININGRAD("(UTC+02:00) Kaliningrad"), - KHARTOUM("(UTC+02:00) Khartoum"), - TRIPOLI("(UTC+02:00) Tripoli"), - WINDHOEK("(UTC+02:00) Windhoek"), - AMMAN("(UTC+03:00) Amman"), - BAGHDAD("(UTC+03:00) Baghdad"), - DAMASCUS("(UTC+03:00) Damascus"), - ISTANBUL("(UTC+03:00) Istanbul"), - KUWAIT("(UTC+03:00) Kuwait"), - MINSK("(UTC+03:00) Minsk"), - MOSCOW("(UTC+03:00) Moscow"), - NAIROBI("(UTC+03:00) Nairobi"), - VOLGOGRAD("(UTC+03:00) Volgograd"), - TEHRAN("(UTC+03:30) Tehran"), - ABU_DHABI("(UTC+04:00) Abu Dhabi"), - ASTRAKHAN("(UTC+04:00) Astrakhan"), - BAKU("(UTC+04:00) Baku"), - IZHEVSK("(UTC+04:00) Izhevsk"), - PORT_LOUIS("(UTC+04:00) Port Louis"), - SARATOV("(UTC+04:00) Saratov"), - TBILISI("(UTC+04:00) Tbilisi"), - YEREVAN("(UTC+04:00) Yerevan"), - KABUL("(UTC+04:30) Kabul"), - ASHGABAT("(UTC+05:00) Ashgabat"), - ASTANA("(UTC+05:00) Astana"), - EKATERINBURG("(UTC+05:00) Ekaterinburg"), - ISLAMABAD("(UTC+05:00) Islamabad"), - CHENNAI("(UTC+05:30) Chennai"), - SRI_JAYAWARDENEPURA("(UTC+05:30) Sri Jayawardenepura"), - KATHMANDU("(UTC+05:45) Kathmandu"), - BISHKEK("(UTC+06:00) Bishkek"), - DHAKA("(UTC+06:00) Dhaka"), - OMSK("(UTC+06:00) Omsk"), - YANGON_RANGOON("(UTC+06:30) Yangon (Rangoon)"), - BANGKOK("(UTC+07:00) Bangkok"), - BARNAUL("(UTC+07:00) Barnaul"), - HOVD("(UTC+07:00) Hovd"), - KRASNOYARSK("(UTC+07:00) Krasnoyarsk"), - NOVOSIBIRSK("(UTC+07:00) Novosibirsk"), - TOMSK("(UTC+07:00) Tomsk"), - BEIJING("(UTC+08:00) Beijing"), - IRKUTSK("(UTC+08:00) Irkutsk"), - KUALA_LUMPUR("(UTC+08:00) Kuala Lumpur"), - PERTH("(UTC+08:00) Perth"), - TAIPEI("(UTC+08:00) Taipei"), - ULAANBAATAR("(UTC+08:00) Ulaanbaatar"), - EUCLA("(UTC+08:45) Eucla"), - CHITA("(UTC+09:00) Chita"), - OSAKA("(UTC+09:00) Osaka"), - PYONGYANG("(UTC+09:00) Pyongyang"), - SEOUL("(UTC+09:00) Seoul"), - YAKUTSK("(UTC+09:00) Yakutsk"), - ADELAIDE("(UTC+09:30) Adelaide"), - DARWIN("(UTC+09:30) Darwin"), - BRISBANE("(UTC+10:00) Brisbane"), - CANBERRA("(UTC+10:00) Canberra"), - GUAM("(UTC+10:00) Guam"), - HOBART("(UTC+10:00) Hobart"), - VLADIVOSTOK("(UTC+10:00) Vladivostok"), - LORD_HOWE_ISLAND("(UTC+10:30) Lord Howe Island"), - BOUGAINVILLE_ISLAND("(UTC+11:00) Bougainville Island"), - CHOKURDAKH("(UTC+11:00) Chokurdakh"), - MAGADAN("(UTC+11:00) Magadan"), - NORFOLK_ISLAND("(UTC+11:00) Norfolk Island"), - SAKHALIN("(UTC+11:00) Sakhalin"), - SOLOMON_IS("(UTC+11:00) Solomon Is."), - ANADYR("(UTC+12:00) Anadyr"), - AUCKLAND("(UTC+12:00) Auckland"), - COORDINATED_UNIVERSAL_TIME_12("(UTC+12:00) Coordinated Universal Time+12"), - FIJI("(UTC+12:00) Fiji"), - PETROPAVLOVSK_KAMCHATSKY_OLD("(UTC+12:00) Petropavlovsk-Kamchatsky - Old"), - CHATHAM_ISLANDS("(UTC+12:45) Chatham Islands"), - COORDINATED_UNIVERSAL_TIME_13("(UTC+13:00) Coordinated Universal Time+13"), - NUKU_ALOFA("(UTC+13:00) Nuku'alofa"), - SAMOA("(UTC+13:00) Samoa"), - KIRITIMATI_ISLAND("(UTC+14:00) Kiritimati Island"); + INTERNATIONAL_DATE_LINE_WEST(1, "(UTC-12:00) International Date Line West", "Dateline Standard Time"), + COORDINATED_UNIVERSAL_TIME_11(2, "(UTC-11:00) Coordinated Universal Time-11", "UTC-11"), + ALEUTIAN_ISLANDS(3, "(UTC-10:00) Aleutian Islands", "Aleutian Standard Time"), + HAWAII(4, "(UTC-10:00) Hawaii", "Hawaiian Standard Time"), + MARQUESAS_ISLANDS(5, "(UTC-09:30) Marquesas Islands", "Marquesas Standard Time"), + ALASKA(6, "(UTC-09:00) Alaska", "Alaskan Standard Time"), + COORDINATED_UNIVERSAL_TIME_09(7, "(UTC-09:00) Coordinated Universal Time-09", "UTC-09"), + BAJA_CALIFORNIA(8, "(UTC-08:00) Baja California", "Pacific Standard Time (Mexico)"), + COORDINATED_UNIVERSAL_TIME_08(9, "(UTC-08:00) Coordinated Universal Time-08", "UTC-08"), + PACIFIC_TIME_US_AND_CANADA(10, "(UTC-08:00) Pacific Time (US & Canada)", "Pacific Standard Time"), + ARIZONA(11, "(UTC-07:00) Arizona", "US Mountain Standard Time"), + LA_PAZ(12, "(UTC-07:00) La Paz", "Mountain Standard Time (Mexico)"), + MOUNTAIN_TIME_US_AND_CANADA(13, "(UTC-07:00) Mountain Time (US & Canada)", "Mountain Standard Time"), + YUKON(14, "(UTC-07:00) Yukon", "Yukon Standard Time"), + CENTRAL_AMERICA(15, "(UTC-06:00) Central America", "Central America Standard Time"), + CENTRAL_TIME_US_AND_CANADA(16, "(UTC-06:00) Central Time (US & Canada)", "Central Standard Time"), + EASTER_ISLAND(17, "(UTC-06:00) Easter Island", "Easter Island Standard Time"), + GUADALAJARA(18, "(UTC-06:00) Guadalajara", "Central Standard Time (Mexico)"), + SASKATCHEWAN(19, "(UTC-06:00) Saskatchewan", "Canada Central Standard Time"), + BOGOTA(20, "(UTC-05:00) Bogota", "SA Pacific Standard Time"), + CHETUMAL(21, "(UTC-05:00) Chetumal", "Eastern Standard Time (Mexico)"), + EASTERN_TIME_US_AND_CANADA(22, "(UTC-05:00) Eastern Time (US & Canada)", "Eastern Standard Time"), + HAITI(23, "(UTC-05:00) Haiti", "Haiti Standard Time"), + HAVANA(24, "(UTC-05:00) Havana", "Cuba Standard Time"), + INDIANA_EAST(25, "(UTC-05:00) Indiana (East)", "US Eastern Standard Time"), + TURKS_AND_CAICOS(26, "(UTC-05:00) Turks and Caicos", "Turks And Caicos Standard Time"), + ASUNCION(27, "(UTC-04:00) Asuncion", "Paraguay Standard Time"), + ATLANTIC_TIME_CANADA(28, "(UTC-04:00) Atlantic Time (Canada)", "Atlantic Standard Time"), + CARACAS(29, "Caracas", "Venezuela Standard Time"), + CUIABA(30, "(UTC-04:00) Cuiaba", "Central Brazilian Standard Time"), + GEORGETOWN(31, "(UTC-04:00) Georgetown", "SA Western Standard Time"), + SANTIAGO(32, "(UTC-04:00) Santiago", "Pacific SA Standard Time"), + NEWFOUNDLAND(33, "(UTC-03:30) Newfoundland", "Newfoundland Standard Time"), + ARAGUAINA(34, "(UTC-03:00) Araguaina", "Tocantins Standard Time"), + BRASILIA(35, "(UTC-03:00) Brasilia", "E. South America Standard Time"), + CAYENNE(36, "(UTC-03:00) Cayenne", "SA Eastern Standard Time"), + CITY_OF_BUENOS_AIRES(37, "(UTC-03:00) City of Buenos Aires", "Argentina Standard Time"), + MONTEVIDEO(38, "(UTC-03:00) Montevideo", "Montevideo Standard Time"), + PUNTA_ARENAS(39, "(UTC-03:00) Punta Arenas", "Magallanes Standard Time"), + SAINT_PIERRE_AND_MIQUELON(40, "(UTC-03:00) Saint Pierre and Miquelon", "Saint Pierre Standard Time"), + SALVADOR(41, "(UTC-03:00) Salvador", "Bahia Standard Time"), + COORDINATED_UNIVERSAL_TIME_02(42, "(UTC-02:00) Coordinated Universal Time-02", "UTC-02"), + GREENLAND(43, "(UTC-02:00) Greenland", "Greenland Standard Time"), + MID_ATLANTIC_OLD(44, "(UTC-02:00) Mid-Atlantic - Old", "Mid-Atlantic Standard Time"), + AZORES(45, "(UTC-01:00) Azores", "Azores Standard Time"), + CABO_VERDE_IS(46, "(UTC-01:00) Cabo Verde Is.", "Cape Verde Standard Time"), + COORDINATED_UNIVERSAL_TIME(47, "(UTC) Coordinated Universal Time", "UTC"), + DUBLIN(48, "(UTC+00:00) Dublin", "GMT Standard Time"), + MONROVIA(49, "(UTC+00:00) Monrovia", "Greenwich Standard Time"), + SAO_TOME(50, "(UTC+00:00) Sao Tome", "Sao Tome Standard Time"), + CASABLANCA(51, "(UTC+01:00) Casablanca", "Morocco Standard Time"), + AMSTERDAM(52, "(UTC+01:00) Amsterdam", "W. Europe Standard Time"), + BELGRADE(53, "(UTC+01:00) Belgrade", "Central Europe Standard Time"), + BRUSSELS(54, "(UTC+01:00) Brussels", "Romance Standard Time"), + SARAJEVO(55, "(UTC+01:00) Sarajevo", "Central European Standard Time"), + WEST_CENTRAL_AFRICA(56, "(UTC+01:00) West Central Africa", "W. Central Africa Standard Time"), + ATHENS(57, "(UTC+02:00) Athens", "GTB Standard Time"), + BEIRUT(58, "(UTC+02:00) Beirut", "Middle East Standard Time"), + CAIRO(59, "(UTC+02:00) Cairo", "Egypt Standard Time"), + CHISINAU(60, "(UTC+02:00) Chisinau", "E. Europe Standard Time"), + GAZA(61, "(UTC+02:00) Gaza", "West Bank Standard Time"), + HARARE(62, "(UTC+02:00) Harare", "South Africa Standard Time"), + HELSINKI(63, "(UTC+02:00) Helsinki", "FLE Standard Time"), + JERUSALEM(64, "(UTC+02:00) Jerusalem", "Israel Standard Time"), + JUBA(65, "(UTC+02:00) Juba", "South Sudan Standard Time"), + KALININGRAD(66, "(UTC+02:00) Kaliningrad", "Kaliningrad Standard Time"), + KHARTOUM(67, "(UTC+02:00) Khartoum", "Sudan Standard Time"), + TRIPOLI(68, "(UTC+02:00) Tripoli", "Libya Standard Time"), + WINDHOEK(69, "(UTC+02:00) Windhoek", "Namibia Standard Time"), + AMMAN(70, "(UTC+03:00) Amman", "Jordan Standard Time"), + BAGHDAD(71, "(UTC+03:00) Baghdad", "Arabic Standard Time"), + DAMASCUS(72, "(UTC+03:00) Damascus", "Syria Standard Time"), + ISTANBUL(73, "(UTC+03:00) Istanbul", "Turkey Standard Time"), + KUWAIT(74, "(UTC+03:00) Kuwait", "Arab Standard Time"), + MINSK(75, "(UTC+03:00) Minsk", "Belarus Standard Time"), + MOSCOW(76, "(UTC+03:00) Moscow", "Russian Standard Time"), + NAIROBI(77, "(UTC+03:00) Nairobi", "E. Africa Standard Time"), + VOLGOGRAD(78, "(UTC+03:00) Volgograd", "Volgograd Standard Time"), + TEHRAN(79, "(UTC+03:30) Tehran", "Iran Standard Time"), + ABU_DHABI(80, "(UTC+04:00) Abu Dhabi", "Arabian Standard Time"), + ASTRAKHAN(81, "(UTC+04:00) Astrakhan", "Astrakhan Standard Time"), + BAKU(82, "(UTC+04:00) Baku", "Azerbaijan Standard Time"), + IZHEVSK(83, "(UTC+04:00) Izhevsk", "Russia Time Zone 3"), + PORT_LOUIS(84, "(UTC+04:00) Port Louis", "Mauritius Standard Time"), + SARATOV(85, "(UTC+04:00) Saratov", "Saratov Standard Time"), + TBILISI(86, "(UTC+04:00) Tbilisi", "Georgian Standard Time"), + YEREVAN(87, "(UTC+04:00) Yerevan", "Caucasus Standard Time"), + KABUL(88, "(UTC+04:30) Kabul", "Afghanistan Standard Time"), + ASHGABAT(89, "(UTC+05:00) Ashgabat", "West Asia Standard Time"), + ASTANA(90, "(UTC+05:00) Astana", "Central Asia Standard Time"), + EKATERINBURG(91, "(UTC+05:00) Ekaterinburg", "Ekaterinburg Standard Time"), + ISLAMABAD(92, "(UTC+05:00) Islamabad", "Pakistan Standard Time"), + CHENNAI(93, "(UTC+05:30) Chennai", "India Standard Time"), + SRI_JAYAWARDENEPURA(94, "(UTC+05:30) Sri Jayawardenepura", "Sri Lanka Standard Time"), + KATHMANDU(95, "(UTC+05:45) Kathmandu", "Nepal Standard Time"), + BISHKEK(96, "(UTC+06:00) Bishkek", "Central Asia Standard Time"), + DHAKA(97, "(UTC+06:00) Dhaka", "Bangladesh Standard Time"), + OMSK(98, "(UTC+06:00) Omsk", "Omsk Standard Time"), + YANGON_RANGOON(99, "(UTC+06:30) Yangon (Rangoon)", "Myanmar Standard Time"), + BANGKOK(100, "(UTC+07:00) Bangkok", "SE Asia Standard Time"), + BARNAUL(101, "(UTC+07:00) Barnaul", "Altai Standard Time"), + HOVD(102, "(UTC+07:00) Hovd", "W. Mongolia Standard Time"), + KRASNOYARSK(103, "(UTC+07:00) Krasnoyarsk", "North Asia Standard Time"), + NOVOSIBIRSK(104, "(UTC+07:00) Novosibirsk", "N. Central Asia Standard Time"), + TOMSK(105, "(UTC+07:00) Tomsk", "Tomsk Standard Time"), + BEIJING(106, "(UTC+08:00) Beijing", "China Standard Time"), + IRKUTSK(107, "(UTC+08:00) Irkutsk", "North Asia East Standard Time"), + KUALA_LUMPUR(108, "(UTC+08:00) Kuala Lumpur", "Singapore Standard Time"), + PERTH(109, "(UTC+08:00) Perth", "W. Australia Standard Time"), + TAIPEI(110, "(UTC+08:00) Taipei", "Taipei Standard Time"), + ULAANBAATAR(111, "(UTC+08:00) Ulaanbaatar", "Ulaanbaatar Standard Time"), + EUCLA(112, "(UTC+08:45) Eucla", "Aus Central W. Standard Time"), + CHITA(113, "(UTC+09:00) Chita", "Transbaikal Standard Time"), + OSAKA(114, "(UTC+09:00) Osaka", "Tokyo Standard Time"), + PYONGYANG(115, "(UTC+09:00) Pyongyang", "North Korea Standard Time"), + SEOUL(116, "(UTC+09:00) Seoul", "Korea Standard Time"), + YAKUTSK(117, "(UTC+09:00) Yakutsk", "Yakutsk Standard Time"), + ADELAIDE(118, "(UTC+09:30) Adelaide", "Cen. Australia Standard Time"), + DARWIN(119, "(UTC+09:30) Darwin", "AUS Central Standard Time"), + BRISBANE(120, "(UTC+10:00) Brisbane", "E. Australia Standard Time"), + CANBERRA(121, "(UTC+10:00) Canberra", "AUS Eastern Standard Time"), + GUAM(122, "(UTC+10:00) Guam", "West Pacific Standard Time"), + HOBART(123, "(UTC+10:00) Hobart", "Tasmania Standard Time"), + VLADIVOSTOK(124, "(UTC+10:00) Vladivostok", "Vladivostok Standard Time"), + LORD_HOWE_ISLAND(125, "(UTC+10:30) Lord Howe Island", "Lord Howe Standard Time"), + BOUGAINVILLE_ISLAND(126, "(UTC+11:00) Bougainville Island", "Bougainville Standard Time"), + CHOKURDAKH(127, "(UTC+11:00) Chokurdakh", "Russia Time Zone 10"), + MAGADAN(128, "(UTC+11:00) Magadan", "Magadan Standard Time"), + NORFOLK_ISLAND(129, "(UTC+11:00) Norfolk Island", "Norfolk Standard Time"), + SAKHALIN(130, "(UTC+11:00) Sakhalin", "Sakhalin Standard Time"), + SOLOMON_IS(131, "(UTC+11:00) Solomon Is.", "Central Pacific Standard Time"), + ANADYR(132, "(UTC+12:00) Anadyr", "Russia Time Zone 11"), + AUCKLAND(133, "(UTC+12:00) Auckland", "New Zealand Standard Time"), + COORDINATED_UNIVERSAL_TIME_12(134, "(UTC+12:00) Coordinated Universal Time+12", "UTC+12"), + FIJI(135, "(UTC+12:00) Fiji", "Fiji Standard Time"), + PETROPAVLOVSK_KAMCHATSKY_OLD(136, "(UTC+12:00) Petropavlovsk-Kamchatsky - Old", "Kamchatka Standard Time"), + CHATHAM_ISLANDS(137, "(UTC+12:45) Chatham Islands", "Chatham Islands Standard Time"), + COORDINATED_UNIVERSAL_TIME_13(138, "(UTC+13:00) Coordinated Universal Time+13", "UTC+13"), + NUKU_ALOFA(139, "(UTC+13:00) Nuku'alofa", "Tonga Standard Time"), + SAMOA(140, "(UTC+13:00) Samoa", "Samoa Standard Time"), + KIRITIMATI_ISLAND(141, "(UTC+14:00) Kiritimati Island", "Line Islands Standard Time"); + + public static final Map VALUE_MAPPING = Map.copyOf(Arrays.stream(values()) + .collect(Collectors.toMap(v -> v.intValue, Function.identity()))); - private final String displayName; + private final Integer intValue; + private final String queryValue; + private final String responseValue; - TimeZone(String displayName) { - this.displayName = displayName; + @Override + public String toString() { + return queryValue; } + @JsonValue + public String getQueryValue() { + return queryValue; + } + + @JsonCreator + public static TimeZone fromValue(Object value) { + if (value instanceof Number) { + int intVal = ((Number) value).intValue(); + for (TimeZone type : values()) { + if (type.intValue == intVal) return type; + } + } else if (value instanceof String strVal) { + for (TimeZone type : values()) { + if (type.queryValue.equalsIgnoreCase(strVal) || type.responseValue.equalsIgnoreCase(strVal)) { + return type; + } + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "' for TimeZone"); + } } diff --git a/core/src/main/java/org/justserve/model/graph/CreateEventFields.java b/core/src/main/java/org/justserve/model/graph/CreateEventFields.java index 273186d..25415a5 100644 --- a/core/src/main/java/org/justserve/model/graph/CreateEventFields.java +++ b/core/src/main/java/org/justserve/model/graph/CreateEventFields.java @@ -11,10 +11,9 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; -import org.justserve.model.ProjectStatus; +import org.justserve.model.ProjectEventStatus; import java.util.Date; -import java.util.UUID; /** *

All potential variables to use with {@link CreateEventQuery}

@@ -42,27 +41,10 @@ public class CreateEventFields extends GraphFields { @Nullable private String contactPhone; - @Nullable - private Boolean deleted; - - @Nullable - private UUID deletedBy; - - @Nullable - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") - private Date deletedOn; - @Nullable @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") private Date end; - @Nullable - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") - private Date endDateTimeOffset; - - @Nullable - private Boolean eventCapReached; - /** * whether a group cap is set for this event. */ @@ -72,9 +54,6 @@ public class CreateEventFields extends GraphFields { @Nullable private Integer groupLimit; - @Nullable - private UUID id; - @Nullable private String locationLink; @@ -82,12 +61,6 @@ public class CreateEventFields extends GraphFields { @Size(max = 139) private String locationName; - @Nullable - private UUID projectEventLocationId; - - @Nullable - private UUID projectRecurringTimeId; - @Nullable private String qrCodeImageLocation; @@ -111,18 +84,11 @@ public class CreateEventFields extends GraphFields { private Date start; @Nullable - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") - private Date startDateTimeOffset; - - @Nullable - private ProjectStatus status; + private ProjectEventStatus status; @Nullable private String timezone; - @Nullable - private UUID timeZoneEnumId; - @Nullable private Integer totalVolunteersNeeded; @@ -131,7 +97,4 @@ public class CreateEventFields extends GraphFields { */ @Nullable private Boolean volunteerCap; - - @Nullable - private Integer volunteersNeeded; } diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index 7a768bb..1d45970 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -4,10 +4,7 @@ import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject import net.datafaker.Faker import org.justserve.client.GraphQLClient -import org.justserve.model.EventType -import org.justserve.model.GraphQLCreateProjectVariables -import org.justserve.model.ProjectLocationType -import org.justserve.model.ProjectStatus +import org.justserve.model.* import org.justserve.model.graph.CreateEventFields import org.justserve.model.graph.CreateEventQuery import org.justserve.model.graph.CreateEventVariables @@ -47,21 +44,22 @@ class GraphQLClientSpec extends Specification { } @Unroll - void "can use createOngoingEvent() with combination of fields"(String contactEmail, String contactName, String contactPhone, Boolean deleted, Date end, Boolean eventCapReached, Boolean hasGroupCap, Integer groupLimit, String locationLink, String locationName, String qrCodeImageLocation, Date renewDate, String schedule, String shiftTitle, String specialDirections, Date start, ProjectStatus status, String timezone, Integer totalVolunteersNeeded, Boolean hasVolunteerCap, Integer volunteersNeeded) { + void "can use createOngoingEvent() with combination of fields"(String contactEmail, String contactName, String contactPhone, Date end, Boolean hasGroupCap, Integer groupLimit, String locationLink, String locationName, String qrCodeImageLocation, Date renewDate, String schedule, String shiftTitle, String specialDirections, Date start, ProjectEventStatus status, String timezone, Integer totalVolunteersNeeded, Boolean hasVolunteerCap) { given: - def projectId = client.createProject(new GraphQLCreateProjectVariables() + def project = client.createProject(new GraphQLCreateProjectVariables() .setTitle("this is a test") .setEventType(EventType.Ongoing) .setLocationType(ProjectLocationType.SINGLE_LOCATION) - ).data.createProject.id + ) + def projectId = project + .getData() + .getCreateProject().getId() def event = new CreateEventFields() .setContactEmail(contactEmail) .setContactName(contactName) .setContactPhone(contactPhone) - .setDeleted(deleted) .setEnd(end) - .setEventCapReached(eventCapReached) .setGroupCap(hasGroupCap) .setGroupLimit(groupLimit) .setLocationLink(locationLink) @@ -76,7 +74,6 @@ class GraphQLClientSpec extends Specification { .setTimezone(timezone) .setTotalVolunteersNeeded(totalVolunteersNeeded) .setVolunteerCap(hasVolunteerCap) - .setVolunteersNeeded(volunteersNeeded) def vars = new CreateEventVariables() .setProjectId(projectId) @@ -94,9 +91,7 @@ class GraphQLClientSpec extends Specification { contactEmail: ${contactEmail} contactName: ${contactName} contactPhone: ${contactPhone} - deleted: ${deleted} end: ${end} - eventCapReached: ${eventCapReached} hasGroupCap: ${hasGroupCap} groupLimit: ${groupLimit} locationLink: ${locationLink} @@ -111,7 +106,6 @@ class GraphQLClientSpec extends Specification { timezone: ${timezone} totalVolunteersNeeded: ${totalVolunteersNeeded} hasVolunteerCap: ${hasVolunteerCap} - volunteersNeeded: ${volunteersNeeded} """ new File('build/test-errors.log').append(errorLog) @@ -120,28 +114,27 @@ class GraphQLClientSpec extends Specification { !response.hasErrors() where: - [contactEmail, contactName, contactPhone, deleted, end, eventCapReached, hasGroupCap, groupLimit, locationLink, locationName, qrCodeImageLocation, renewDate, schedule, shiftTitle, specialDirections, start, status, timezone, totalVolunteersNeeded, hasVolunteerCap, volunteersNeeded] << [ - [faker.internet().emailAddress(), null], - [faker.lorem().characters(1, 139), null], - [faker.phoneNumber().phoneNumber(), null], - [true, false, null], - [Date.from(faker.timeAndDate().future(365, TimeUnit.DAYS)), null], - [true, false, null], - [true, false, null], - [(+faker.number()), null], - [faker.internet().url(), null], - [faker.lorem().characters(1, 139), null], - [faker.internet().url(), null], - [Date.from(faker.timeAndDate().future(730, TimeUnit.DAYS)), null], - [faker.lorem().characters(1, 300), null], - [faker.lorem().characters(1, 300), null], - [faker.lorem().paragraph(), null], - [Date.from(faker.timeAndDate().future(180, TimeUnit.DAYS)), null], - [ProjectStatus.values(), null].flatten(), - [faker.address().timeZone(), null], - [(+faker.number()), null], - [(+faker.number()), null], - [(+faker.number()), null] - ].combinations().getFirst() + [contactEmail, contactName, contactPhone, end, hasGroupCap, groupLimit, locationLink, locationName, qrCodeImageLocation, renewDate, schedule, shiftTitle, specialDirections, start, status, timezone, totalVolunteersNeeded, hasVolunteerCap] << [ + [ + faker.internet().emailAddress(), + faker.name().fullName(), + faker.phoneNumber().phoneNumber(), + Date.from(faker.timeAndDate().future(365, TimeUnit.DAYS)), + true, + 10, + faker.internet().url(), + faker.address().streetAddress(), + faker.internet().url(), + Date.from(faker.timeAndDate().future(730, TimeUnit.DAYS)), + faker.lorem().sentence(), + faker.lorem().word(), + faker.lorem().paragraph(), + Date.from(faker.timeAndDate().future(180, TimeUnit.DAYS)), + ProjectEventStatus.ACTIVE, + TimeZone.ARIZONA, + 20, + true + ] + ] } } From a07709e499eca2e20a3c26df858407f924905886 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Thu, 12 Mar 2026 15:58:50 -0600 Subject: [PATCH 13/30] refactor: rename ProjectEvent. describe use cases in comments add empty pojos for classes to come needed by ProjectEvent Signed-off-by: jonathan zollinger --- .../org/justserve/client/GraphQLClient.java | 3 +- .../justserve/model/ProjectEventStatus.java | 50 ++++ .../justserve/model/graph/CivicGeography.java | 13 + .../model/graph/CreateEventFields.java | 100 -------- .../justserve/model/graph/ProjectEvent.java | 238 ++++++++++++++++++ .../model/graph/ProjectEventLocation.java | 13 + .../model/graph/ProjectRecurringTime.java | 13 + .../java/org/justserve/model/graph/User.java | 7 + .../org/justserve/GraphQLClientSpec.groovy | 30 +-- 9 files changed, 338 insertions(+), 129 deletions(-) create mode 100644 core/src/main/java/org/justserve/model/ProjectEventStatus.java create mode 100644 core/src/main/java/org/justserve/model/graph/CivicGeography.java delete mode 100644 core/src/main/java/org/justserve/model/graph/CreateEventFields.java create mode 100644 core/src/main/java/org/justserve/model/graph/ProjectEvent.java create mode 100644 core/src/main/java/org/justserve/model/graph/ProjectEventLocation.java create mode 100644 core/src/main/java/org/justserve/model/graph/ProjectRecurringTime.java create mode 100644 core/src/main/java/org/justserve/model/graph/User.java diff --git a/core/src/main/java/org/justserve/client/GraphQLClient.java b/core/src/main/java/org/justserve/client/GraphQLClient.java index d48da87..3772e99 100644 --- a/core/src/main/java/org/justserve/client/GraphQLClient.java +++ b/core/src/main/java/org/justserve/client/GraphQLClient.java @@ -9,6 +9,7 @@ import org.justserve.model.*; import org.justserve.model.graph.CreateEventQuery; import org.justserve.model.graph.GraphQLResponse; +import org.justserve.model.graph.ProjectEvent; import java.lang.reflect.Field; import java.util.ArrayList; @@ -23,7 +24,7 @@ public interface GraphQLClient { @Post - GraphQLResponse createEvent(@Body CreateEventQuery request); + GraphQLResponse createEvent(@Body CreateEventQuery request); @Post GraphQLResponse executeAddProjectAttachment(@Body GraphQLAddProjectAttachmentRequest request); diff --git a/core/src/main/java/org/justserve/model/ProjectEventStatus.java b/core/src/main/java/org/justserve/model/ProjectEventStatus.java new file mode 100644 index 0000000..d2e5118 --- /dev/null +++ b/core/src/main/java/org/justserve/model/ProjectEventStatus.java @@ -0,0 +1,50 @@ +package org.justserve.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import io.micronaut.serde.annotation.Serdeable; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Serdeable +public enum ProjectEventStatus { + ACTIVE(1, "ACTIVE"), + CANCELLED(2, "CANCELLED"), + ON_HOLD(3, "ON_HOLD"); + + public static final Map VALUE_MAPPING = Map.copyOf(Arrays.stream(values()) + .collect(Collectors.toMap(v -> v.intValue, Function.identity()))); + + private final Integer intValue; + private final String stringValue; + + @Override + public String toString() { + return String.valueOf(intValue); + } + + @JsonValue + public String getStringValue() { + return stringValue; + } + + @JsonCreator + public static ProjectEventStatus fromValue(Object value) { + if (value instanceof Number) { + int intVal = ((Number) value).intValue(); + for (ProjectEventStatus type : values()) { + if (type.intValue == intVal) return type; + } + } else if (value instanceof String strVal) { + for (ProjectEventStatus type : values()) { + if (type.stringValue.equalsIgnoreCase(strVal)) return type; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "' for ProjectEventStatus"); + } +} diff --git a/core/src/main/java/org/justserve/model/graph/CivicGeography.java b/core/src/main/java/org/justserve/model/graph/CivicGeography.java new file mode 100644 index 0000000..5355ec1 --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/CivicGeography.java @@ -0,0 +1,13 @@ +package org.justserve.model.graph; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.serde.annotation.Serdeable; +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +@Serdeable +@Introspected +public class CivicGeography { +} diff --git a/core/src/main/java/org/justserve/model/graph/CreateEventFields.java b/core/src/main/java/org/justserve/model/graph/CreateEventFields.java deleted file mode 100644 index 25415a5..0000000 --- a/core/src/main/java/org/justserve/model/graph/CreateEventFields.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.justserve.model.graph; - -import com.fasterxml.jackson.annotation.JsonFormat; -import io.micronaut.core.annotation.Introspected; -import io.micronaut.core.annotation.Nullable; -import io.micronaut.serde.annotation.Serdeable; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.Size; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; -import org.justserve.model.ProjectEventStatus; - -import java.util.Date; - -/** - *

All potential variables to use with {@link CreateEventQuery}

- * Null values will be omitted from the generated query. - * - * @since 0.1.0 - * @author Jonathan Zollinger - */ -@EqualsAndHashCode(callSuper = true) -@Data -@Accessors(chain = true) -@NoArgsConstructor -@AllArgsConstructor -@Serdeable -@Introspected -public class CreateEventFields extends GraphFields { - @Nullable - @Email - private String contactEmail; - - @Nullable - @Size(max = 139) - private String contactName; - - @Nullable - private String contactPhone; - - @Nullable - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") - private Date end; - - /** - * whether a group cap is set for this event. - */ - @Nullable - private Boolean groupCap; - - @Nullable - private Integer groupLimit; - - @Nullable - private String locationLink; - - @Nullable - @Size(max = 139) - private String locationName; - - @Nullable - private String qrCodeImageLocation; - - @Nullable - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") - private Date renewDate; - - @Nullable - @Size(max = 300) - private String schedule; - - @Nullable - @Size(max = 300) - private String shiftTitle; - - @Nullable - private String specialDirections; - - @Nullable - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") - private Date start; - - @Nullable - private ProjectEventStatus status; - - @Nullable - private String timezone; - - @Nullable - private Integer totalVolunteersNeeded; - - /** - * whether a volunteer cap is set for this event. - */ - @Nullable - private Boolean volunteerCap; -} diff --git a/core/src/main/java/org/justserve/model/graph/ProjectEvent.java b/core/src/main/java/org/justserve/model/graph/ProjectEvent.java new file mode 100644 index 0000000..981cc08 --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/ProjectEvent.java @@ -0,0 +1,238 @@ +package org.justserve.model.graph; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.serde.annotation.Serdeable; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.justserve.model.Project; +import org.justserve.model.ProjectEventStatus; + +import java.util.Date; +import java.util.List; +import java.util.UUID; + +/** + *

All potential variables to use with {@link CreateEventQuery}

+ * Null values will be omitted from the generated query. + * + * @author Jonathan Zollinger + * @since 0.1.0 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Serdeable +@Introspected +public class ProjectEvent extends GraphFields { + @Nullable + @Email + private String contactEmail; + + @Nullable + @Size(max = 139) + private String contactName; + + @Nullable + private String contactPhone; + + /** + *

Whether the project event has been deleted.

+ * Not Usable In: + *
    + *
  • {@link CreateEventQuery}
  • + *
+ */ + @Nullable + private Boolean deleted; + + /** + *

The user who deleted the project event.

+ * See{@link #deletedByNavigation}
+ * Not Usable In: + *
    + *
  • {@link CreateEventQuery}
  • + *
+ */ + @Nullable + private UUID deletedBy; + + /** + *

User who deleted the event.

+ * See{@link #deletedBy}
+ * Not Usable In: + *
    + *
  • {@link CreateEventQuery}
  • + *
+ */ + @Nullable + private User deletedByNavigation; + + /** + *

The date and time the project event was deleted.

+ * Not Usable In: + *
    + *
  • {@link CreateEventQuery}
  • + *
+ */ + @Nullable + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") + private Date deletedOn; + + @Nullable + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") + private Date end; + + /** + *

The end date and time of the event with timezone offset.

+ * Not Usable In: + *
    + *
  • {@link CreateEventQuery}
  • + *
+ */ + @Nullable + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") + private Date endDateTimeOffset; + + /** + *

Indicates if the event capacity has been reached.

+ * Not Usable In: + *
    + *
  • {@link CreateEventQuery}
  • + *
+ */ + @Nullable + private Boolean eventCapReached; + + /** + * whether a group cap is set for this event. + */ + @NonNull + private Boolean groupCap; + + @Nullable + private Integer groupLimit; + + /** + *

The unique identifier for the project event.

+ * Not Usable In: + *
    + *
  • {@link CreateEventQuery}
  • + *
+ */ + @NonNull + private UUID id; + + @Nullable + private String locationLink; + + @Nullable + @Size(max = 139) + private String locationName; + + /** + *

The project this event belongs to.

+ * Not Usable In: + *
    + *
  • {@link CreateEventQuery}
  • + *
+ */ + @Nullable + private Project project; + + /** + *

The location of the project event.

+ * Not Usable In: + *
    + *
  • {@link CreateEventQuery}
  • + *
+ */ + @Nullable + private ProjectEventLocation projectEventLocation; + + /** + *

The ID of the project event location.

+ * Not Usable In: + *
    + *
  • {@link CreateEventQuery}
  • + *
+ */ + @Nullable + private UUID projectEventLocationId; + + /** + *

The regions associated with the project event.

+ * Not Usable In: + *
    + *
  • {@link CreateEventQuery}
  • + *
+ */ + @Nullable + private List projectEventRegions; + + /** + *

The ID of the project this event belongs to.

+ * Not Usable In: + *
    + *
  • {@link CreateEventQuery}
  • + *
+ */ + @Nullable + private UUID projectId; + + /** + *

Information about the recurring schedule of the project event.

+ * Not Usable In: + *
    + *
  • {@link CreateEventQuery}
  • + *
+ */ + @Nullable + private ProjectRecurringTime projectRecurringTime; + + @Nullable + private String qrCodeImageLocation; + + @Nullable + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") + private Date renewDate; + + @Nullable + @Size(max = 300) + private String schedule; + + @Nullable + @Size(max = 300) + private String shiftTitle; + + @Nullable + private String specialDirections; + + @Nullable + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") + private Date start; + + @Nullable + private ProjectEventStatus status; + + @Nullable + private String timezone; + + @Nullable + private Integer totalVolunteersNeeded; + + /** + * whether a volunteer cap is set for this event. + */ + @Nullable + private Boolean volunteerCap; +} diff --git a/core/src/main/java/org/justserve/model/graph/ProjectEventLocation.java b/core/src/main/java/org/justserve/model/graph/ProjectEventLocation.java new file mode 100644 index 0000000..edec49d --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/ProjectEventLocation.java @@ -0,0 +1,13 @@ +package org.justserve.model.graph; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.serde.annotation.Serdeable; +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +@Serdeable +@Introspected +public class ProjectEventLocation { +} diff --git a/core/src/main/java/org/justserve/model/graph/ProjectRecurringTime.java b/core/src/main/java/org/justserve/model/graph/ProjectRecurringTime.java new file mode 100644 index 0000000..5864bc6 --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/ProjectRecurringTime.java @@ -0,0 +1,13 @@ +package org.justserve.model.graph; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.serde.annotation.Serdeable; +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +@Serdeable +@Introspected +public class ProjectRecurringTime { +} diff --git a/core/src/main/java/org/justserve/model/graph/User.java b/core/src/main/java/org/justserve/model/graph/User.java new file mode 100644 index 0000000..59bbe3e --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/User.java @@ -0,0 +1,7 @@ +package org.justserve.model.graph; + +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +public class User { +} diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index 1d45970..7d324d7 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -5,9 +5,9 @@ import jakarta.inject.Inject import net.datafaker.Faker import org.justserve.client.GraphQLClient import org.justserve.model.* -import org.justserve.model.graph.CreateEventFields import org.justserve.model.graph.CreateEventQuery import org.justserve.model.graph.CreateEventVariables +import org.justserve.model.graph.ProjectEvent import spock.lang.Shared import spock.lang.Specification import spock.lang.Unroll @@ -55,7 +55,7 @@ class GraphQLClientSpec extends Specification { .getData() .getCreateProject().getId() - def event = new CreateEventFields() + def event = new ProjectEvent() .setContactEmail(contactEmail) .setContactName(contactName) .setContactPhone(contactPhone) @@ -84,32 +84,6 @@ class GraphQLClientSpec extends Specification { def response = client.createEvent(query) then: - if (response.hasErrors()) { - def errorLog = """ - ${response.errors} - Errors for combination: - contactEmail: ${contactEmail} - contactName: ${contactName} - contactPhone: ${contactPhone} - end: ${end} - hasGroupCap: ${hasGroupCap} - groupLimit: ${groupLimit} - locationLink: ${locationLink} - locationName: ${locationName} - qrCodeImageLocation: ${qrCodeImageLocation} - renewDate: ${renewDate} - schedule: ${schedule} - shiftTitle: ${shiftTitle} - specialDirections: ${specialDirections} - start: ${start} - status: ${status} - timezone: ${timezone} - totalVolunteersNeeded: ${totalVolunteersNeeded} - hasVolunteerCap: ${hasVolunteerCap} - - """ - new File('build/test-errors.log').append(errorLog) - } noExceptionThrown() !response.hasErrors() From a156da1c22b83c7ea1913231a2b399eb6904403c Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Thu, 12 Mar 2026 15:59:14 -0600 Subject: [PATCH 14/30] fix: add missing CreateEventVariables Signed-off-by: jonathan zollinger --- .../model/graph/CreateEventVariables.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 core/src/main/java/org/justserve/model/graph/CreateEventVariables.java diff --git a/core/src/main/java/org/justserve/model/graph/CreateEventVariables.java b/core/src/main/java/org/justserve/model/graph/CreateEventVariables.java new file mode 100644 index 0000000..51f1ef4 --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/CreateEventVariables.java @@ -0,0 +1,27 @@ +package org.justserve.model.graph; + +import io.micronaut.serde.annotation.Serdeable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.UUID; + +/** + * Pojo to serialize the variables object passed with a createEvent mutation. + * + * @author Jonathan Zollinger + * @since 0.1.0 + */ +@EqualsAndHashCode(callSuper = true) +@Serdeable +@Data +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = true) +public class CreateEventVariables extends GraphVariables { + private UUID projectId; + private ProjectEvent projectEvent; +} From fae7d27d8df55d1a91747a85db2e147b9baa688e Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Fri, 13 Mar 2026 11:57:42 -0600 Subject: [PATCH 15/30] refactor(docs): correct punctuation Signed-off-by: jonathan zollinger --- core/src/main/java/org/justserve/model/graph/GraphFields.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/justserve/model/graph/GraphFields.java b/core/src/main/java/org/justserve/model/graph/GraphFields.java index a5ca278..2b9a544 100644 --- a/core/src/main/java/org/justserve/model/graph/GraphFields.java +++ b/core/src/main/java/org/justserve/model/graph/GraphFields.java @@ -26,7 +26,7 @@ String getMutationFields() { } /** - * reflection free implementation of querying non-null fields for a bean. + * Reflection free implementation of querying non-null fields for a bean. * * @param bean class which is being queried * @param class type From 36bbcce9d6160e2ff7227a6ee4096299087c0291 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Fri, 13 Mar 2026 11:58:05 -0600 Subject: [PATCH 16/30] fix: set timezone field as pojo Signed-off-by: jonathan zollinger --- core/src/main/java/org/justserve/model/graph/ProjectEvent.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/justserve/model/graph/ProjectEvent.java b/core/src/main/java/org/justserve/model/graph/ProjectEvent.java index 981cc08..7a41718 100644 --- a/core/src/main/java/org/justserve/model/graph/ProjectEvent.java +++ b/core/src/main/java/org/justserve/model/graph/ProjectEvent.java @@ -14,6 +14,7 @@ import lombok.experimental.Accessors; import org.justserve.model.Project; import org.justserve.model.ProjectEventStatus; +import org.justserve.model.TimeZone; import java.util.Date; import java.util.List; @@ -225,7 +226,7 @@ public class ProjectEvent extends GraphFields { private ProjectEventStatus status; @Nullable - private String timezone; + private TimeZone timezone; @Nullable private Integer totalVolunteersNeeded; From 402cc5b2b7fb2a44224853bdce88862e65c70a69 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Fri, 13 Mar 2026 11:58:22 -0600 Subject: [PATCH 17/30] fix: set query value as json value Signed-off-by: jonathan zollinger --- core/src/main/java/org/justserve/model/TimeZone.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/justserve/model/TimeZone.java b/core/src/main/java/org/justserve/model/TimeZone.java index 61da419..a056f94 100644 --- a/core/src/main/java/org/justserve/model/TimeZone.java +++ b/core/src/main/java/org/justserve/model/TimeZone.java @@ -97,7 +97,7 @@ public enum TimeZone { BAKU(82, "(UTC+04:00) Baku", "Azerbaijan Standard Time"), IZHEVSK(83, "(UTC+04:00) Izhevsk", "Russia Time Zone 3"), PORT_LOUIS(84, "(UTC+04:00) Port Louis", "Mauritius Standard Time"), - SARATOV(85, "(UTC+04:00) Saratov", "Saratov Standard Time"), + SARATOV(85, "(UTC+04:00) Saratov", "Samara Time"), TBILISI(86, "(UTC+04:00) Tbilisi", "Georgian Standard Time"), YEREVAN(87, "(UTC+04:00) Yerevan", "Caucasus Standard Time"), KABUL(88, "(UTC+04:30) Kabul", "Afghanistan Standard Time"), @@ -163,14 +163,11 @@ public enum TimeZone { private final String responseValue; @Override + @JsonValue public String toString() { return queryValue; } - @JsonValue - public String getQueryValue() { - return queryValue; - } @JsonCreator public static TimeZone fromValue(Object value) { From d626d80e3afe763a60291c3253cda1c6a2530900 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Fri, 13 Mar 2026 11:59:04 -0600 Subject: [PATCH 18/30] test(wip): split createEvent into tests against individual fields Signed-off-by: jonathan zollinger --- .../org/justserve/GraphQLClientSpec.groovy | 294 ++++++++++++++---- 1 file changed, 236 insertions(+), 58 deletions(-) diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index 7d324d7..0bb30ec 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -10,7 +10,6 @@ import org.justserve.model.graph.CreateEventVariables import org.justserve.model.graph.ProjectEvent import spock.lang.Shared import spock.lang.Specification -import spock.lang.Unroll import java.util.concurrent.TimeUnit @@ -24,6 +23,20 @@ class GraphQLClientSpec extends Specification { @Shared Faker faker = new Faker() + @Shared + UUID projectId + + def setupSpec() { + def project = client.createProject(new GraphQLCreateProjectVariables() + .setTitle("this is a test") + .setEventType(EventType.Ongoing) + .setLocationType(ProjectLocationType.SINGLE_LOCATION) + ) + projectId = project + .getData() + .getCreateProject().getId() + } + void "can create Project with EventType: #eventType, LocationType: #locationType, and Redirect: #redirect"(EventType eventType, ProjectLocationType locationType, String redirect) { given: GraphQLCreateProjectVariables args = new GraphQLCreateProjectVariables() @@ -43,72 +56,237 @@ class GraphQLClientSpec extends Specification { [eventType, locationType, redirect] << [EventType.values(), ProjectLocationType.values(), ["", null, "https://google.com"]].combinations() } - @Unroll - void "can use createOngoingEvent() with combination of fields"(String contactEmail, String contactName, String contactPhone, Date end, Boolean hasGroupCap, Integer groupLimit, String locationLink, String locationName, String qrCodeImageLocation, Date renewDate, String schedule, String shiftTitle, String specialDirections, Date start, ProjectEventStatus status, String timezone, Integer totalVolunteersNeeded, Boolean hasVolunteerCap) { + def "can set contactEmail for ongoing event"() { given: - def project = client.createProject(new GraphQLCreateProjectVariables() - .setTitle("this is a test") - .setEventType(EventType.Ongoing) - .setLocationType(ProjectLocationType.SINGLE_LOCATION) - ) - def projectId = project - .getData() - .getCreateProject().getId() + def event = new ProjectEvent().setContactEmail(faker.internet().emailAddress()) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) - def event = new ProjectEvent() - .setContactEmail(contactEmail) - .setContactName(contactName) - .setContactPhone(contactPhone) - .setEnd(end) - .setGroupCap(hasGroupCap) - .setGroupLimit(groupLimit) - .setLocationLink(locationLink) - .setLocationName(locationName) - .setQrCodeImageLocation(qrCodeImageLocation) - .setRenewDate(renewDate) - .setSchedule(schedule) - .setShiftTitle(shiftTitle) - .setSpecialDirections(specialDirections) - .setStart(start) - .setStatus(status) - .setTimezone(timezone) - .setTotalVolunteersNeeded(totalVolunteersNeeded) - .setVolunteerCap(hasVolunteerCap) + when: + def response = client.createEvent(new CreateEventQuery(vars)) - def vars = new CreateEventVariables() - .setProjectId(projectId) - .setProjectEvent(event) + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set contactName for ongoing event"() { + given: + def event = new ProjectEvent().setContactName(faker.name().fullName()) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) when: - def query = new CreateEventQuery(vars) - def response = client.createEvent(query) + def response = client.createEvent(new CreateEventQuery(vars)) then: noExceptionThrown() !response.hasErrors() + } - where: - [contactEmail, contactName, contactPhone, end, hasGroupCap, groupLimit, locationLink, locationName, qrCodeImageLocation, renewDate, schedule, shiftTitle, specialDirections, start, status, timezone, totalVolunteersNeeded, hasVolunteerCap] << [ - [ - faker.internet().emailAddress(), - faker.name().fullName(), - faker.phoneNumber().phoneNumber(), - Date.from(faker.timeAndDate().future(365, TimeUnit.DAYS)), - true, - 10, - faker.internet().url(), - faker.address().streetAddress(), - faker.internet().url(), - Date.from(faker.timeAndDate().future(730, TimeUnit.DAYS)), - faker.lorem().sentence(), - faker.lorem().word(), - faker.lorem().paragraph(), - Date.from(faker.timeAndDate().future(180, TimeUnit.DAYS)), - ProjectEventStatus.ACTIVE, - TimeZone.ARIZONA, - 20, - true - ] - ] + def "can set contactPhone for ongoing event"() { + given: + def event = new ProjectEvent().setContactPhone(faker.phoneNumber().phoneNumber()) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set end for ongoing event"() { + given: + def event = new ProjectEvent().setEnd(Date.from(faker.timeAndDate().future(365, TimeUnit.DAYS))) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set groupCap for ongoing event"() { + given: + def event = new ProjectEvent().setGroupCap(true) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set groupLimit for ongoing event"() { + given: + def event = new ProjectEvent().setGroupLimit(10) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set locationLink for ongoing event"() { + given: + def event = new ProjectEvent().setLocationLink(faker.internet().url()) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set locationName for ongoing event"() { + given: + def event = new ProjectEvent().setLocationName(faker.address().streetAddress()) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set qrCodeImageLocation for ongoing event"() { + given: + def event = new ProjectEvent().setQrCodeImageLocation(faker.internet().url()) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set renewDate for ongoing event"() { + given: + def event = new ProjectEvent().setRenewDate(Date.from(faker.timeAndDate().future(730, TimeUnit.DAYS))) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set schedule for ongoing event"() { + given: + def event = new ProjectEvent().setSchedule(faker.lorem().sentence()) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set shiftTitle for ongoing event"() { + given: + def event = new ProjectEvent().setShiftTitle(faker.lorem().word()) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set specialDirections for ongoing event"() { + given: + def event = new ProjectEvent().setSpecialDirections(faker.lorem().paragraph()) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set start for ongoing event"() { + given: + def event = new ProjectEvent().setStart(Date.from(faker.timeAndDate().future(180, TimeUnit.DAYS))) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set status for ongoing event"() { + given: + def event = new ProjectEvent().setStatus(ProjectEventStatus.ACTIVE) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set timezone for ongoing event"() { + given: + def event = new ProjectEvent().setTimezone(TimeZone.ARIZONA) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set totalVolunteersNeeded for ongoing event"() { + given: + def event = new ProjectEvent().setTotalVolunteersNeeded(20) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() + } + + def "can set volunteerCap for ongoing event"() { + given: + def event = new ProjectEvent().setVolunteerCap(true) + def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + noExceptionThrown() + !response.hasErrors() } } From 4f506c5b2c69e9e4d2f0bea707c6593816a01269 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Sat, 14 Mar 2026 18:06:56 -0600 Subject: [PATCH 19/30] feat: add tests for ongoing events Signed-off-by: jonathan zollinger --- .../justserve/model/graph/ProjectEvent.java | 67 +++++- .../org/justserve/GraphQLClientSpec.groovy | 193 +++++------------- 2 files changed, 110 insertions(+), 150 deletions(-) diff --git a/core/src/main/java/org/justserve/model/graph/ProjectEvent.java b/core/src/main/java/org/justserve/model/graph/ProjectEvent.java index 7a41718..0256b24 100644 --- a/core/src/main/java/org/justserve/model/graph/ProjectEvent.java +++ b/core/src/main/java/org/justserve/model/graph/ProjectEvent.java @@ -7,11 +7,9 @@ import io.micronaut.serde.annotation.Serdeable; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Size; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; +import lombok.*; import lombok.experimental.Accessors; +import org.justserve.model.EventType; import org.justserve.model.Project; import org.justserve.model.ProjectEventStatus; import org.justserve.model.TimeZone; @@ -20,9 +18,37 @@ import java.util.List; import java.util.UUID; +import static java.lang.Boolean.TRUE; + /** - *

All potential variables to use with {@link CreateEventQuery}

- * Null values will be omitted from the generated query. + *

JustServe Project Event

+ * Valid to use with + *
  • {@link EventType#Ongoing} + *
  • {@link EventType#DTL} + *
  • {@link EventType#MultipleDTL}
  • + *
  • {@link EventType#Recurring}
+ * + *

Creating New Events

+ * Use {@code ProjectEvent.}{@link #builder()} when adding a new event to ensure all + * needed fields are included. This not only checks for required fields, but double checks + * contradictions or invalid combinations are being submitted. + *
Example
+ *
{@code
+ * ProjectEvent newEvent = ProjectEvent.builder()
+ *     .start(startDate)
+ *     .end(endDate)
+ *     .shiftTitle("Morning Shift")
+ *     .build();
+ * }
+ * + *

Updating Existing Events

+ * Use {@code new ProjectEvent()} (without the builder) when updating an event. This + * skips the builder's checks and lets you send partial updates to existing events. + *
Example
+ *
{@code
+ * ProjectEvent partialUpdate = new ProjectEvent()
+ *     .setShiftTitle("Afternoon Shift");
+ * }
* * @author Jonathan Zollinger * @since 0.1.0 @@ -32,6 +58,7 @@ @Accessors(chain = true) @NoArgsConstructor @AllArgsConstructor +@Builder(buildMethodName = "buildInternal") @Serdeable @Introspected public class ProjectEvent extends GraphFields { @@ -115,11 +142,15 @@ public class ProjectEvent extends GraphFields { private Boolean eventCapReached; /** - * whether a group cap is set for this event. + * Whether a group cap is set for this event. */ @NonNull - private Boolean groupCap; + @Builder.Default + private Boolean groupCap = false; + /** + * The max number of people which can sign up by one person + */ @Nullable private Integer groupLimit; @@ -130,7 +161,7 @@ public class ProjectEvent extends GraphFields { *
  • {@link CreateEventQuery}
  • * */ - @NonNull + @Nullable private UUID id; @Nullable @@ -236,4 +267,22 @@ public class ProjectEvent extends GraphFields { */ @Nullable private Boolean volunteerCap; + + public static class ProjectEventBuilder { + public ProjectEvent build() { + ProjectEvent event = this.buildInternal(); + + if (event.getEnd() == null || event.getStart() == null) { + throw new IllegalStateException("Events created with the builder must have a start and end date"); + } + if ((TRUE.equals(event.getGroupCap()) && event.getGroupLimit() == null)) { + throw new IllegalStateException("groupLimit cannot be null when groupCap is true"); + } + if (TRUE.equals(event.getVolunteerCap()) && event.getTotalVolunteersNeeded() == null) { + throw new IllegalStateException("totalVolunteersNeeded cannot be null when volunteerCap is true"); + } + + return event; + } + } } diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index 0bb30ec..5cb94ff 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -26,7 +26,7 @@ class GraphQLClientSpec extends Specification { @Shared UUID projectId - def setupSpec() { + def setup() { def project = client.createProject(new GraphQLCreateProjectVariables() .setTitle("this is a test") .setEventType(EventType.Ongoing) @@ -56,113 +56,19 @@ class GraphQLClientSpec extends Specification { [eventType, locationType, redirect] << [EventType.values(), ProjectLocationType.values(), ["", null, "https://google.com"]].combinations() } - def "can set contactEmail for ongoing event"() { - given: - def event = new ProjectEvent().setContactEmail(faker.internet().emailAddress()) - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) - - when: - def response = client.createEvent(new CreateEventQuery(vars)) - - then: - noExceptionThrown() - !response.hasErrors() - } - - def "can set contactName for ongoing event"() { - given: - def event = new ProjectEvent().setContactName(faker.name().fullName()) - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) - - when: - def response = client.createEvent(new CreateEventQuery(vars)) - - then: - noExceptionThrown() - !response.hasErrors() - } - - def "can set contactPhone for ongoing event"() { - given: - def event = new ProjectEvent().setContactPhone(faker.phoneNumber().phoneNumber()) - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) - - when: - def response = client.createEvent(new CreateEventQuery(vars)) - - then: - noExceptionThrown() - !response.hasErrors() - } - - def "can set end for ongoing event"() { - given: - def event = new ProjectEvent().setEnd(Date.from(faker.timeAndDate().future(365, TimeUnit.DAYS))) - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) - - when: - def response = client.createEvent(new CreateEventQuery(vars)) - - then: - noExceptionThrown() - !response.hasErrors() - } - - def "can set groupCap for ongoing event"() { - given: - def event = new ProjectEvent().setGroupCap(true) - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) - - when: - def response = client.createEvent(new CreateEventQuery(vars)) - - then: - noExceptionThrown() - !response.hasErrors() - } - - def "can set groupLimit for ongoing event"() { - given: - def event = new ProjectEvent().setGroupLimit(10) - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) - - when: - def response = client.createEvent(new CreateEventQuery(vars)) - - then: - noExceptionThrown() - !response.hasErrors() - } - - def "can set locationLink for ongoing event"() { - given: - def event = new ProjectEvent().setLocationLink(faker.internet().url()) - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) - - when: - def response = client.createEvent(new CreateEventQuery(vars)) - - then: - noExceptionThrown() - !response.hasErrors() - } - - def "can set locationName for ongoing event"() { - given: - def event = new ProjectEvent().setLocationName(faker.address().streetAddress()) - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) - - when: - def response = client.createEvent(new CreateEventQuery(vars)) - - then: - noExceptionThrown() - !response.hasErrors() + private ProjectEvent.ProjectEventBuilder baseEventBuilder() { + return ProjectEvent.builder() + .start(Date.from(faker.timeAndDate().future(180, TimeUnit.DAYS))) + .end(Date.from(faker.timeAndDate().future(365, TimeUnit.DAYS))) } - def "can set qrCodeImageLocation for ongoing event"() { + def "can set contact info for ongoing event"() { given: - def event = new ProjectEvent().setQrCodeImageLocation(faker.internet().url()) + def event = baseEventBuilder() + .contactEmail(faker.internet().emailAddress()) + .contactName(faker.name().fullName()) + .contactPhone(faker.phoneNumber().phoneNumber()) + .build() def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) when: @@ -173,9 +79,11 @@ class GraphQLClientSpec extends Specification { !response.hasErrors() } - def "can set renewDate for ongoing event"() { + def "can set dates for ongoing event"() { given: - def event = new ProjectEvent().setRenewDate(Date.from(faker.timeAndDate().future(730, TimeUnit.DAYS))) + def event = baseEventBuilder() + .renewDate(Date.from(faker.timeAndDate().future(730, TimeUnit.DAYS))) + .build() def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) when: @@ -186,9 +94,12 @@ class GraphQLClientSpec extends Specification { !response.hasErrors() } - def "can set schedule for ongoing event"() { + def "can set group info for ongoing event"() { given: - def event = new ProjectEvent().setSchedule(faker.lorem().sentence()) + def event = baseEventBuilder() + .groupCap(true) + .groupLimit(10) + .build() def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) when: @@ -199,9 +110,12 @@ class GraphQLClientSpec extends Specification { !response.hasErrors() } - def "can set shiftTitle for ongoing event"() { + def "can set location info for ongoing event"() { given: - def event = new ProjectEvent().setShiftTitle(faker.lorem().word()) + def event = baseEventBuilder() + .locationLink(faker.internet().url()) + .locationName(faker.address().streetAddress()) + .build() def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) when: @@ -212,9 +126,13 @@ class GraphQLClientSpec extends Specification { !response.hasErrors() } - def "can set specialDirections for ongoing event"() { + def "can set schedule info for ongoing event"() { given: - def event = new ProjectEvent().setSpecialDirections(faker.lorem().paragraph()) + def schedule = faker.lorem().sentence() + def event = baseEventBuilder() + .schedule(schedule) + .shiftTitle(schedule) + .build() def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) when: @@ -225,9 +143,12 @@ class GraphQLClientSpec extends Specification { !response.hasErrors() } - def "can set start for ongoing event"() { + def "can set volunteer info for ongoing event"() { given: - def event = new ProjectEvent().setStart(Date.from(faker.timeAndDate().future(180, TimeUnit.DAYS))) + def event = baseEventBuilder() + .volunteerCap(true) + .totalVolunteersNeeded(20) + .build() def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) when: @@ -238,9 +159,14 @@ class GraphQLClientSpec extends Specification { !response.hasErrors() } - def "can set status for ongoing event"() { + def "can set miscellaneous info for ongoing event"() { given: - def event = new ProjectEvent().setStatus(ProjectEventStatus.ACTIVE) + def event = baseEventBuilder() + .qrCodeImageLocation(faker.internet().url()) + .specialDirections(faker.lorem().paragraph()) + .status(ProjectEventStatus.ACTIVE) + .timezone(TimeZone.ARIZONA) + .build() def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) when: @@ -251,42 +177,27 @@ class GraphQLClientSpec extends Specification { !response.hasErrors() } - def "can set timezone for ongoing event"() { - given: - def event = new ProjectEvent().setTimezone(TimeZone.ARIZONA) - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) - + def "cannot create event without start and end dates"() { when: - def response = client.createEvent(new CreateEventQuery(vars)) + ProjectEvent.builder().build() then: - noExceptionThrown() - !response.hasErrors() + thrown(IllegalStateException) } - def "can set totalVolunteersNeeded for ongoing event"() { - given: - def event = new ProjectEvent().setTotalVolunteersNeeded(20) - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) - + def "cannot set groupCap without groupLimit"() { when: - def response = client.createEvent(new CreateEventQuery(vars)) + baseEventBuilder().groupCap(true).build() then: - noExceptionThrown() - !response.hasErrors() + thrown(IllegalStateException) } - def "can set volunteerCap for ongoing event"() { - given: - def event = new ProjectEvent().setVolunteerCap(true) - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) - + def "cannot set volunteerCap without totalVolunteersNeeded"() { when: - def response = client.createEvent(new CreateEventQuery(vars)) + baseEventBuilder().volunteerCap(true).build() then: - noExceptionThrown() - !response.hasErrors() + thrown(IllegalStateException) } } From 876c10f45bd475223813af53c9c731f59dfa840c Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Sat, 14 Mar 2026 18:12:46 -0600 Subject: [PATCH 20/30] refactor: ignore leftshift incompatibility Signed-off-by: jonathan zollinger --- core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index 5cb94ff..c290d24 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -37,6 +37,7 @@ class GraphQLClientSpec extends Specification { .getCreateProject().getId() } + @SuppressWarnings("GroovyAssignabilityCheck") void "can create Project with EventType: #eventType, LocationType: #locationType, and Redirect: #redirect"(EventType eventType, ProjectLocationType locationType, String redirect) { given: GraphQLCreateProjectVariables args = new GraphQLCreateProjectVariables() From 99565d59b853433b0e527eab704c01a7365db75e Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Sat, 14 Mar 2026 18:13:30 -0600 Subject: [PATCH 21/30] refactor: remove custom linebreaks Signed-off-by: jonathan zollinger --- core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index c290d24..c61847e 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -30,8 +30,7 @@ class GraphQLClientSpec extends Specification { def project = client.createProject(new GraphQLCreateProjectVariables() .setTitle("this is a test") .setEventType(EventType.Ongoing) - .setLocationType(ProjectLocationType.SINGLE_LOCATION) - ) + .setLocationType(ProjectLocationType.SINGLE_LOCATION)) projectId = project .getData() .getCreateProject().getId() From c979b2447552adf24d709db26e2d4eb07dcf9b0b Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Sat, 14 Mar 2026 18:28:42 -0600 Subject: [PATCH 22/30] docs: flesh out javadocs Signed-off-by: jonathan zollinger --- .../java/org/justserve/model/EventType.java | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/justserve/model/EventType.java b/core/src/main/java/org/justserve/model/EventType.java index 24070b0..2e9b9e4 100644 --- a/core/src/main/java/org/justserve/model/EventType.java +++ b/core/src/main/java/org/justserve/model/EventType.java @@ -3,7 +3,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import io.micronaut.serde.annotation.Serdeable; +import lombok.Generated; import lombok.RequiredArgsConstructor; +import org.justserve.model.graph.ProjectEvent; import java.util.Arrays; import java.util.Map; @@ -11,14 +13,40 @@ import java.util.stream.Collectors; /** - * Gets or Sets EventType + * Defines the scheduling model for a JustServe project, determining how its + * {@link ProjectEvent}s are structured and displayed. + * + * @author Jonathan Zollinger + * @since 0.1.0 */ @RequiredArgsConstructor @Serdeable public enum EventType { + /** + *

    Date, Time, and Location

    + * A standard event that occurs at a specific time and place. + */ DTL(1, "DTL"), + + /** + *

    Ongoing

    + * An event with no specific time. The start and end dates determine visibility on JustServe + */ Ongoing(2, "ONGOING"), + + /** + *

    Recurring

    + * An event that repeats on a regular schedule, such as weekly or monthly. + *

    Example: An evening opportunity that occurs every Monday, Wednesday, + * and Friday for three months. + */ Recurring(3, "RECURRING"), + + /** + *

    Multiple Date, Time, and Location

    + * A complex event that has multiple, distinct shifts or occurrences. + *

    Example: A project with multiple shifts on each Saturday for several weeks. + */ MultipleDTL(4, "MULTIPLE_DTL"); public static final Map VALUE_MAPPING = Map.copyOf(Arrays.stream(values()) @@ -38,9 +66,12 @@ public String getStringValue() { } /** - * 2. RECEIVING (Response): This catches the incoming data. - * It can handle the Integer '1' from GraphQL, or even a String if a REST endpoint sends one. + * Parses the incoming value to either the string or integer value, whichever the server is using. + * + * @param value the incoming value from the server + * @return the event type that matches the incoming value */ + @Generated //manually placed annotation to tell code coverage to ignore this @JsonCreator public static EventType fromValue(Object value) { if (value instanceof Number) { @@ -55,4 +86,4 @@ public static EventType fromValue(Object value) { } throw new IllegalArgumentException("Unexpected value '" + value + "' for EventType"); } -} \ No newline at end of file +} From 5230b5d136ebcdf3ab0f89725fa34713c6724383 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Sat, 14 Mar 2026 18:31:35 -0600 Subject: [PATCH 23/30] refactor: move model classes to model dir Signed-off-by: jonathan zollinger --- .../src/main/java/org/justserve/client/GraphQLClient.java | 1 - .../org/justserve/model/{graph => }/CivicGeography.java | 2 +- core/src/main/java/org/justserve/model/EventType.java | 1 - .../org/justserve/model/{graph => }/ProjectEvent.java | 8 +++----- .../justserve/model/{graph => }/ProjectEventLocation.java | 2 +- .../justserve/model/{graph => }/ProjectRecurringTime.java | 2 +- .../main/java/org/justserve/model/{graph => }/User.java | 2 +- .../org/justserve/model/graph/CreateEventVariables.java | 1 + .../test/groovy/org/justserve/GraphQLClientSpec.groovy | 1 - 9 files changed, 8 insertions(+), 12 deletions(-) rename core/src/main/java/org/justserve/model/{graph => }/CivicGeography.java (87%) rename core/src/main/java/org/justserve/model/{graph => }/ProjectEvent.java (97%) rename core/src/main/java/org/justserve/model/{graph => }/ProjectEventLocation.java (87%) rename core/src/main/java/org/justserve/model/{graph => }/ProjectRecurringTime.java (87%) rename core/src/main/java/org/justserve/model/{graph => }/User.java (70%) diff --git a/core/src/main/java/org/justserve/client/GraphQLClient.java b/core/src/main/java/org/justserve/client/GraphQLClient.java index 3772e99..c9f1620 100644 --- a/core/src/main/java/org/justserve/client/GraphQLClient.java +++ b/core/src/main/java/org/justserve/client/GraphQLClient.java @@ -9,7 +9,6 @@ import org.justserve.model.*; import org.justserve.model.graph.CreateEventQuery; import org.justserve.model.graph.GraphQLResponse; -import org.justserve.model.graph.ProjectEvent; import java.lang.reflect.Field; import java.util.ArrayList; diff --git a/core/src/main/java/org/justserve/model/graph/CivicGeography.java b/core/src/main/java/org/justserve/model/CivicGeography.java similarity index 87% rename from core/src/main/java/org/justserve/model/graph/CivicGeography.java rename to core/src/main/java/org/justserve/model/CivicGeography.java index 5355ec1..e29d8c9 100644 --- a/core/src/main/java/org/justserve/model/graph/CivicGeography.java +++ b/core/src/main/java/org/justserve/model/CivicGeography.java @@ -1,4 +1,4 @@ -package org.justserve.model.graph; +package org.justserve.model; import io.micronaut.core.annotation.Introspected; import io.micronaut.serde.annotation.Serdeable; diff --git a/core/src/main/java/org/justserve/model/EventType.java b/core/src/main/java/org/justserve/model/EventType.java index 2e9b9e4..b96175e 100644 --- a/core/src/main/java/org/justserve/model/EventType.java +++ b/core/src/main/java/org/justserve/model/EventType.java @@ -5,7 +5,6 @@ import io.micronaut.serde.annotation.Serdeable; import lombok.Generated; import lombok.RequiredArgsConstructor; -import org.justserve.model.graph.ProjectEvent; import java.util.Arrays; import java.util.Map; diff --git a/core/src/main/java/org/justserve/model/graph/ProjectEvent.java b/core/src/main/java/org/justserve/model/ProjectEvent.java similarity index 97% rename from core/src/main/java/org/justserve/model/graph/ProjectEvent.java rename to core/src/main/java/org/justserve/model/ProjectEvent.java index 0256b24..0164468 100644 --- a/core/src/main/java/org/justserve/model/graph/ProjectEvent.java +++ b/core/src/main/java/org/justserve/model/ProjectEvent.java @@ -1,4 +1,4 @@ -package org.justserve.model.graph; +package org.justserve.model; import com.fasterxml.jackson.annotation.JsonFormat; import io.micronaut.core.annotation.Introspected; @@ -9,10 +9,8 @@ import jakarta.validation.constraints.Size; import lombok.*; import lombok.experimental.Accessors; -import org.justserve.model.EventType; -import org.justserve.model.Project; -import org.justserve.model.ProjectEventStatus; -import org.justserve.model.TimeZone; +import org.justserve.model.graph.CreateEventQuery; +import org.justserve.model.graph.GraphFields; import java.util.Date; import java.util.List; diff --git a/core/src/main/java/org/justserve/model/graph/ProjectEventLocation.java b/core/src/main/java/org/justserve/model/ProjectEventLocation.java similarity index 87% rename from core/src/main/java/org/justserve/model/graph/ProjectEventLocation.java rename to core/src/main/java/org/justserve/model/ProjectEventLocation.java index edec49d..2d6f0bb 100644 --- a/core/src/main/java/org/justserve/model/graph/ProjectEventLocation.java +++ b/core/src/main/java/org/justserve/model/ProjectEventLocation.java @@ -1,4 +1,4 @@ -package org.justserve.model.graph; +package org.justserve.model; import io.micronaut.core.annotation.Introspected; import io.micronaut.serde.annotation.Serdeable; diff --git a/core/src/main/java/org/justserve/model/graph/ProjectRecurringTime.java b/core/src/main/java/org/justserve/model/ProjectRecurringTime.java similarity index 87% rename from core/src/main/java/org/justserve/model/graph/ProjectRecurringTime.java rename to core/src/main/java/org/justserve/model/ProjectRecurringTime.java index 5864bc6..760f98d 100644 --- a/core/src/main/java/org/justserve/model/graph/ProjectRecurringTime.java +++ b/core/src/main/java/org/justserve/model/ProjectRecurringTime.java @@ -1,4 +1,4 @@ -package org.justserve.model.graph; +package org.justserve.model; import io.micronaut.core.annotation.Introspected; import io.micronaut.serde.annotation.Serdeable; diff --git a/core/src/main/java/org/justserve/model/graph/User.java b/core/src/main/java/org/justserve/model/User.java similarity index 70% rename from core/src/main/java/org/justserve/model/graph/User.java rename to core/src/main/java/org/justserve/model/User.java index 59bbe3e..625d0b5 100644 --- a/core/src/main/java/org/justserve/model/graph/User.java +++ b/core/src/main/java/org/justserve/model/User.java @@ -1,4 +1,4 @@ -package org.justserve.model.graph; +package org.justserve.model; import io.micronaut.serde.annotation.Serdeable; diff --git a/core/src/main/java/org/justserve/model/graph/CreateEventVariables.java b/core/src/main/java/org/justserve/model/graph/CreateEventVariables.java index 51f1ef4..0c5ce9b 100644 --- a/core/src/main/java/org/justserve/model/graph/CreateEventVariables.java +++ b/core/src/main/java/org/justserve/model/graph/CreateEventVariables.java @@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; +import org.justserve.model.ProjectEvent; import java.util.UUID; diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index c61847e..de1dd8e 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -7,7 +7,6 @@ import org.justserve.client.GraphQLClient import org.justserve.model.* import org.justserve.model.graph.CreateEventQuery import org.justserve.model.graph.CreateEventVariables -import org.justserve.model.graph.ProjectEvent import spock.lang.Shared import spock.lang.Specification From 968b8856ca7808138169aa512cea7f86bb5ed6c0 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Sat, 14 Mar 2026 21:50:27 -0600 Subject: [PATCH 24/30] fix: update properties allowed in different projectTypes Signed-off-by: jonathan zollinger --- .../org/justserve/model/ProjectEvent.java | 11 +- .../justserve/model/graph/GraphFields.java | 2 +- .../org/justserve/GraphQLClientSpec.groovy | 118 ++++++++++++++---- 3 files changed, 104 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/org/justserve/model/ProjectEvent.java b/core/src/main/java/org/justserve/model/ProjectEvent.java index 0164468..ad2aabb 100644 --- a/core/src/main/java/org/justserve/model/ProjectEvent.java +++ b/core/src/main/java/org/justserve/model/ProjectEvent.java @@ -23,8 +23,8 @@ * Valid to use with *

    • {@link EventType#Ongoing} *
    • {@link EventType#DTL} - *
    • {@link EventType#MultipleDTL}
    • - *
    • {@link EventType#Recurring}
    + *
  • {@link EventType#MultipleDTL}
  • + * (Not valid to use with{@link EventType#Recurring} events * *

    Creating New Events

    * Use {@code ProjectEvent.}{@link #builder()} when adding a new event to ensure all @@ -232,6 +232,13 @@ public class ProjectEvent extends GraphFields { @Nullable private String qrCodeImageLocation; + /** + *

    The date the event is set to renew.

    + * Only Usable In: + *
      + *
    • {@link EventType#MultipleDTL}
    • + *
    + */ @Nullable @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") private Date renewDate; diff --git a/core/src/main/java/org/justserve/model/graph/GraphFields.java b/core/src/main/java/org/justserve/model/graph/GraphFields.java index 2b9a544..44756cf 100644 --- a/core/src/main/java/org/justserve/model/graph/GraphFields.java +++ b/core/src/main/java/org/justserve/model/graph/GraphFields.java @@ -21,7 +21,7 @@ public class GraphFields { @JsonIgnore - String getMutationFields() { + protected String getMutationFields() { return getFieldsForBean(this); } diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index de1dd8e..7c424f2 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -9,6 +9,7 @@ import org.justserve.model.graph.CreateEventQuery import org.justserve.model.graph.CreateEventVariables import spock.lang.Shared import spock.lang.Specification +import spock.lang.Unroll import java.util.concurrent.TimeUnit @@ -23,16 +24,18 @@ class GraphQLClientSpec extends Specification { Faker faker = new Faker() @Shared - UUID projectId - - def setup() { - def project = client.createProject(new GraphQLCreateProjectVariables() - .setTitle("this is a test") - .setEventType(EventType.Ongoing) - .setLocationType(ProjectLocationType.SINGLE_LOCATION)) - projectId = project - .getData() - .getCreateProject().getId() + Map projectIds = [:] + + + def setupSpec() { + EventType.values().each { type -> + def project = client.createProject(new GraphQLCreateProjectVariables() + .setTitle("Test Project - ${type.name()}") + .setEventType(type) + .setLocationType(ProjectLocationType.SINGLE_LOCATION) + ) + projectIds[type] = project.getData().getCreateProject().getId() + } } @SuppressWarnings("GroovyAssignabilityCheck") @@ -61,14 +64,15 @@ class GraphQLClientSpec extends Specification { .end(Date.from(faker.timeAndDate().future(365, TimeUnit.DAYS))) } - def "can set contact info for ongoing event"() { + @Unroll("can set contact info for #eventType.name() event") + def "can set contact info for #eventType event"() { given: def event = baseEventBuilder() .contactEmail(faker.internet().emailAddress()) .contactName(faker.name().fullName()) .contactPhone(faker.phoneNumber().phoneNumber()) .build() - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: def response = client.createEvent(new CreateEventQuery(vars)) @@ -76,14 +80,18 @@ class GraphQLClientSpec extends Specification { then: noExceptionThrown() !response.hasErrors() + + where: + eventType << [EventType.DTL, EventType.Ongoing, EventType.MultipleDTL] } - def "can set dates for ongoing event"() { + @Unroll("can set dates for #eventType.name() event") + def "can set dates for #eventType event"() { given: def event = baseEventBuilder() .renewDate(Date.from(faker.timeAndDate().future(730, TimeUnit.DAYS))) .build() - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: def response = client.createEvent(new CreateEventQuery(vars)) @@ -91,15 +99,19 @@ class GraphQLClientSpec extends Specification { then: noExceptionThrown() !response.hasErrors() + + where: + eventType << [EventType.MultipleDTL] } - def "can set group info for ongoing event"() { + @Unroll("can set group info for #eventType.name() event") + def "can set group info for #eventType event"() { given: def event = baseEventBuilder() .groupCap(true) .groupLimit(10) .build() - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: def response = client.createEvent(new CreateEventQuery(vars)) @@ -107,15 +119,19 @@ class GraphQLClientSpec extends Specification { then: noExceptionThrown() !response.hasErrors() + + where: + eventType << [EventType.DTL, EventType.Ongoing, EventType.MultipleDTL] } - def "can set location info for ongoing event"() { + @Unroll("can set location info for #eventType.name() event") + def "can set location info for #eventType event"() { given: def event = baseEventBuilder() .locationLink(faker.internet().url()) .locationName(faker.address().streetAddress()) .build() - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: def response = client.createEvent(new CreateEventQuery(vars)) @@ -123,16 +139,20 @@ class GraphQLClientSpec extends Specification { then: noExceptionThrown() !response.hasErrors() + + where: //error reads "Only a multiple DTL project can have more than one event, StackTrace= at JustServe.Mediators.ProjectEvents.ProjectEventMediator.InternalCreateEvent(Project project, UpdateProjectEvent updateProjectEvent, SecurityContext securityContext) in /src/src/JustServe.Mediators/ProjectEvents/ProjectEventMediator.cs:line 109" + eventType << [EventType.DTL, /*EventType.Ongoing,*/ EventType.MultipleDTL] } - def "can set schedule info for ongoing event"() { + @Unroll("can set schedule info for #eventType.name() event") + def "can set schedule info for #eventType event"() { given: def schedule = faker.lorem().sentence() def event = baseEventBuilder() .schedule(schedule) .shiftTitle(schedule) .build() - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: def response = client.createEvent(new CreateEventQuery(vars)) @@ -140,15 +160,19 @@ class GraphQLClientSpec extends Specification { then: noExceptionThrown() !response.hasErrors() + + where: + eventType << [EventType.DTL, EventType.Ongoing, EventType.MultipleDTL] } - def "can set volunteer info for ongoing event"() { + @Unroll("can set volunteer info for #eventType.name() event") + def "can set volunteer info for #eventType event"() { given: def event = baseEventBuilder() .volunteerCap(true) .totalVolunteersNeeded(20) .build() - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: def response = client.createEvent(new CreateEventQuery(vars)) @@ -156,9 +180,13 @@ class GraphQLClientSpec extends Specification { then: noExceptionThrown() !response.hasErrors() + + where: + eventType << [EventType.DTL, EventType.Ongoing, EventType.MultipleDTL] } - def "can set miscellaneous info for ongoing event"() { + @Unroll("can set miscellaneous info for #eventType.name() event") + def "can set miscellaneous info for #eventType event"() { given: def event = baseEventBuilder() .qrCodeImageLocation(faker.internet().url()) @@ -166,7 +194,7 @@ class GraphQLClientSpec extends Specification { .status(ProjectEventStatus.ACTIVE) .timezone(TimeZone.ARIZONA) .build() - def vars = new CreateEventVariables().setProjectId(projectId).setProjectEvent(event) + def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: def response = client.createEvent(new CreateEventQuery(vars)) @@ -174,6 +202,48 @@ class GraphQLClientSpec extends Specification { then: noExceptionThrown() !response.hasErrors() + + where: + eventType << [EventType.DTL, EventType.Ongoing, EventType.MultipleDTL] + } + + @Unroll("cannot manually create event for #eventType.name() project") + def "cannot manually create event for invalid project types"() { + given: + def event = baseEventBuilder().build() + def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) + + when: + def response = client.createEvent(new CreateEventQuery(vars)) + + then: + response.hasErrors() + + where: + eventType << [EventType.Recurring] + } + + @Unroll("can add multiple events only for #eventType.name() projects (shouldFail: #shouldFail)") + def "can add multiple events only for Multi-DTL projects"() { + given: + def firstEvent = baseEventBuilder().build() + def secondEvent = baseEventBuilder().build() + def firstVars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(firstEvent) + def secondVars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(secondEvent) + + when: + client.createEvent(new CreateEventQuery(firstVars)) + def secondResponse = client.createEvent(new CreateEventQuery(secondVars)) + + then: + secondResponse.hasErrors() == shouldFail + + where: + eventType | shouldFail + EventType.DTL | true + EventType.Ongoing | true + EventType.Recurring | true + EventType.MultipleDTL | false } def "cannot create event without start and end dates"() { From 8a887f19716399e375b09507f05f09553a38bd22 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Sat, 14 Mar 2026 23:14:41 -0600 Subject: [PATCH 25/30] docs: add javadocs Signed-off-by: jonathan zollinger --- .../org/justserve/model/CivicGeography.java | 4 +++ .../java/org/justserve/model/EventType.java | 2 +- .../justserve/model/ProjectEventLocation.java | 4 +++ .../justserve/model/ProjectEventStatus.java | 14 ++++++++++ .../justserve/model/ProjectLocationType.java | 12 ++++++++ .../justserve/model/ProjectRecurringTime.java | 4 +++ .../org/justserve/model/ProjectStatus.java | 15 ++++++++-- .../java/org/justserve/model/TimeZone.java | 28 ++++++++++++++++++- .../model/graph/GraphQLResponse.java | 8 ++++++ 9 files changed, 86 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/justserve/model/CivicGeography.java b/core/src/main/java/org/justserve/model/CivicGeography.java index e29d8c9..ca41cc1 100644 --- a/core/src/main/java/org/justserve/model/CivicGeography.java +++ b/core/src/main/java/org/justserve/model/CivicGeography.java @@ -5,6 +5,10 @@ import lombok.Data; import lombok.experimental.Accessors; +/** + * This class is currently a placeholder for a future work. + * This class does nothing. + */ @Data @Accessors(chain = true) @Serdeable diff --git a/core/src/main/java/org/justserve/model/EventType.java b/core/src/main/java/org/justserve/model/EventType.java index b96175e..9cfae0e 100644 --- a/core/src/main/java/org/justserve/model/EventType.java +++ b/core/src/main/java/org/justserve/model/EventType.java @@ -70,7 +70,7 @@ public String getStringValue() { * @param value the incoming value from the server * @return the event type that matches the incoming value */ - @Generated //manually placed annotation to tell code coverage to ignore this + @Generated //manually placed annotation to tell jacoco coverage report to ignore this @JsonCreator public static EventType fromValue(Object value) { if (value instanceof Number) { diff --git a/core/src/main/java/org/justserve/model/ProjectEventLocation.java b/core/src/main/java/org/justserve/model/ProjectEventLocation.java index 2d6f0bb..2b10965 100644 --- a/core/src/main/java/org/justserve/model/ProjectEventLocation.java +++ b/core/src/main/java/org/justserve/model/ProjectEventLocation.java @@ -5,6 +5,10 @@ import lombok.Data; import lombok.experimental.Accessors; +/** + * This class is currently a placeholder for a future work. + * This class does nothing. + */ @Data @Accessors(chain = true) @Serdeable diff --git a/core/src/main/java/org/justserve/model/ProjectEventStatus.java b/core/src/main/java/org/justserve/model/ProjectEventStatus.java index d2e5118..aaed8aa 100644 --- a/core/src/main/java/org/justserve/model/ProjectEventStatus.java +++ b/core/src/main/java/org/justserve/model/ProjectEventStatus.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import io.micronaut.serde.annotation.Serdeable; +import lombok.Generated; import lombok.RequiredArgsConstructor; import java.util.Arrays; @@ -10,6 +11,12 @@ import java.util.function.Function; import java.util.stream.Collectors; +/** + *

    Supported Project Event Status

    + *

    + * Project Status available to project events + * Use {@link ProjectStatus} + */ @RequiredArgsConstructor @Serdeable public enum ProjectEventStatus { @@ -33,6 +40,13 @@ public String getStringValue() { return stringValue; } + /** + * Parses the incoming value to either the string or integer value, whichever the server is using. + * + * @param value the incoming value from the server + * @return the event type that matches the incoming value + */ + @Generated //manually placed annotation to tell jacoco coverage report to ignore this @JsonCreator public static ProjectEventStatus fromValue(Object value) { if (value instanceof Number) { diff --git a/core/src/main/java/org/justserve/model/ProjectLocationType.java b/core/src/main/java/org/justserve/model/ProjectLocationType.java index acc3ffd..35bcf4d 100644 --- a/core/src/main/java/org/justserve/model/ProjectLocationType.java +++ b/core/src/main/java/org/justserve/model/ProjectLocationType.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import io.micronaut.serde.annotation.Serdeable; +import lombok.Generated; import lombok.RequiredArgsConstructor; import java.util.Arrays; @@ -10,6 +11,10 @@ import java.util.function.Function; import java.util.stream.Collectors; +/** + *

    Supported Location Types for Projects

    + * + */ @RequiredArgsConstructor @Serdeable public enum ProjectLocationType { @@ -33,6 +38,13 @@ public String getStringValue() { return stringValue; } + /** + * Parses the incoming value to either the string or integer value, whichever the server is using. + * + * @param value the incoming value from the server + * @return the event type that matches the incoming value + */ + @Generated //manually placed annotation to tell jacoco coverage report to ignore this @JsonCreator public static ProjectLocationType fromValue(Object value) { if (value instanceof Number) { diff --git a/core/src/main/java/org/justserve/model/ProjectRecurringTime.java b/core/src/main/java/org/justserve/model/ProjectRecurringTime.java index 760f98d..613c342 100644 --- a/core/src/main/java/org/justserve/model/ProjectRecurringTime.java +++ b/core/src/main/java/org/justserve/model/ProjectRecurringTime.java @@ -5,6 +5,10 @@ import lombok.Data; import lombok.experimental.Accessors; +/** + * This class is currently a placeholder for a future work. + * This class does nothing. + */ @Data @Accessors(chain = true) @Serdeable diff --git a/core/src/main/java/org/justserve/model/ProjectStatus.java b/core/src/main/java/org/justserve/model/ProjectStatus.java index 96a31c3..f4466ee 100644 --- a/core/src/main/java/org/justserve/model/ProjectStatus.java +++ b/core/src/main/java/org/justserve/model/ProjectStatus.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import io.micronaut.serde.annotation.Serdeable; +import lombok.Generated; import lombok.RequiredArgsConstructor; import java.util.Arrays; @@ -11,7 +12,12 @@ import java.util.stream.Collectors; /** - * Gets or Sets ProjectStatus + *

    Status for a project.

    + * + * Use{@link ProjectEventStatus} when creating a project events. + * + * @author Jonathan Zollinger + * @since 0.1.0 */ @RequiredArgsConstructor @Serdeable @@ -41,9 +47,12 @@ public String getStringValue() { } /** - * 2. RECEIVING (Response): This catches the incoming data. - * It can handle the Integer '1' from GraphQL, or even a String if a REST endpoint sends one. + * Parses the incoming value to either the string or integer value, whichever the server is using. + * + * @param value the incoming value from the server + * @return the event type that matches the incoming value */ + @Generated //manually placed annotation to tell jacoco coverage report to ignore this @JsonCreator public static ProjectStatus fromValue(Object value) { if (value instanceof Number) { diff --git a/core/src/main/java/org/justserve/model/TimeZone.java b/core/src/main/java/org/justserve/model/TimeZone.java index a056f94..33f95d4 100644 --- a/core/src/main/java/org/justserve/model/TimeZone.java +++ b/core/src/main/java/org/justserve/model/TimeZone.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import io.micronaut.serde.annotation.Serdeable; +import lombok.Generated; import lombok.RequiredArgsConstructor; import java.util.Arrays; @@ -10,6 +11,16 @@ import java.util.function.Function; import java.util.stream.Collectors; +/** + *

    TimeZones supported in JustServe

    + *
      + *
    • The queryValue field reflects the options in the UI (and what is sent to the server).
    • + *
    • The ResponseValue is the String which is returned from the server
    • + *
    + * + * @author Jonathan Zollinger + * @since 0.1.0 + */ @RequiredArgsConstructor @Serdeable public enum TimeZone { @@ -158,8 +169,17 @@ public enum TimeZone { public static final Map VALUE_MAPPING = Map.copyOf(Arrays.stream(values()) .collect(Collectors.toMap(v -> v.intValue, Function.identity()))); + /** + * This integer value is used for sending + */ private final Integer intValue; + /** + * This string value is what is used when sending this enum TO the server + */ private final String queryValue; + /** + * This string value is what is used when receiving this enum FROM the server + */ private final String responseValue; @Override @@ -168,7 +188,13 @@ public String toString() { return queryValue; } - + /** + * Parses the incoming value to either the one of the string or integer value, whichever the server is using. + * + * @param value the incoming value from the server + * @return the event type that matches the incoming value + */ + @Generated //manually placed annotation to tell jacoco coverage report to ignore this @JsonCreator public static TimeZone fromValue(Object value) { if (value instanceof Number) { diff --git a/core/src/main/java/org/justserve/model/graph/GraphQLResponse.java b/core/src/main/java/org/justserve/model/graph/GraphQLResponse.java index 2e28611..d77a1d4 100644 --- a/core/src/main/java/org/justserve/model/graph/GraphQLResponse.java +++ b/core/src/main/java/org/justserve/model/graph/GraphQLResponse.java @@ -7,6 +7,14 @@ import java.util.List; +/** + *

    GraphQL Response

    + * A generic wrapper for GraphQL API responses, containing the data payload and any execution errors. + * + * @param Underlying response object + * @author Jonathan Zollinger + * @since 0.1.0 + */ @Serdeable @Data @NoArgsConstructor From 978f1c97784c85977211f1858b82c264e082980e Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Sat, 14 Mar 2026 23:19:47 -0600 Subject: [PATCH 26/30] build: remove edits to test heap size Signed-off-by: jonathan zollinger --- core/build.gradle.kts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 3b97137..b8db30f 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -70,11 +70,6 @@ micronaut { } } -tasks.test { - maxHeapSize = "6144m" - minHeapSize = "512m" -} - tasks.withType { val props = Properties() file("../gradle.properties").inputStream().use { props.load(it) } From f889cd5b01b024118f08e3e93fab8cb8de0df35a Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Sat, 14 Mar 2026 23:28:28 -0600 Subject: [PATCH 27/30] refactor: skip test which needs investigating Signed-off-by: jonathan zollinger --- core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index 7c424f2..bcabd02 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -120,8 +120,8 @@ class GraphQLClientSpec extends Specification { noExceptionThrown() !response.hasErrors() - where: - eventType << [EventType.DTL, EventType.Ongoing, EventType.MultipleDTL] + where: // error reads "Only a multiple DTL project can have more than one event" + eventType << [/*EventType.DTL,*/ EventType.Ongoing, EventType.MultipleDTL] } @Unroll("can set location info for #eventType.name() event") From 0f14e58b3c2aba557b1fc072d9a0ff1f06be8c0f Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Sat, 14 Mar 2026 23:32:01 -0600 Subject: [PATCH 28/30] refactor: remove deprecated methods Signed-off-by: jonathan zollinger --- .../org/justserve/client/GraphQLClient.java | 66 ------------------- 1 file changed, 66 deletions(-) diff --git a/core/src/main/java/org/justserve/client/GraphQLClient.java b/core/src/main/java/org/justserve/client/GraphQLClient.java index c9f1620..4228318 100644 --- a/core/src/main/java/org/justserve/client/GraphQLClient.java +++ b/core/src/main/java/org/justserve/client/GraphQLClient.java @@ -10,11 +10,8 @@ import org.justserve.model.graph.CreateEventQuery; import org.justserve.model.graph.GraphQLResponse; -import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; @Produces("application/json") @Consumes("application/graphql-response+json; charset=utf-8") @@ -34,9 +31,6 @@ public interface GraphQLClient { @Post GraphQLResponse executeCombinedMutationUpdateProjectAddProjectTag(@Body GraphQLCombinedMutationUpdateProjectAddProjectTagRequest request); - @Post - GraphQLResponse executeCreateEvent(@Body GraphQLCreateEventRequest request); - @Post GraphQLResponse executeCreateProject(@Body GraphQLCreateProjectRequest request); @@ -82,66 +76,6 @@ default GraphQLResponse c return this.executeCombinedMutationUpdateProjectAddProjectTag(request); } - default GraphQLResponse createEvent(GraphQLCreateEventVariables variables) { - String queryFormat = """ - mutation createEvent($projectId: ID!, $projectEvent: UpdateProjectEventInput!) { - createEvent( - projectId: $projectId - projectEvent: $projectEvent - ) { - %s - } - } - """; - - List fields = new ArrayList<>(List.of("id", "projectId")); - Object projectEvent = variables.getProjectEvent(); - - if (projectEvent != null) { - String dynamicFields = Arrays.stream(projectEvent.getClass().getDeclaredFields()) - .filter(field -> { - try { - field.setAccessible(true); - return field.get(projectEvent) != null; - } catch (IllegalAccessException e) { - return false; - } - }) - .map(Field::getName) - .collect(Collectors.joining("\n")); - if (!dynamicFields.isEmpty()) { - fields.add(dynamicFields); - } - } - - GraphQLCreateEventRequest request = new GraphQLCreateEventRequest(); - request.setQuery(String.format(queryFormat, String.join("\n", fields))); - request.setVariables(variables); - return this.executeCreateEvent(request); - } - - /** - * {@summary Add the dates for an ongoing event.} Only use this on projects whose type is set to {@link EventType#Ongoing}. - * @param variables provide the {@link GraphQLCreateEventVariables#projectId} and {@link GraphQLCreateEventVariables#projectEvent} - * @return the {@link GraphQLCreateEventData} response wrapped in a {@link GraphQLResponse} - */ - default GraphQLResponse createOngoingEvent(GraphQLCreateEventVariables variables) { - String fixedQuery = """ - mutation updateEvent($id: ID!, $projectEvent: UpdateProjectEventInput!) { - updateEvent( - id: $id - projectEvent: $projectEvent - ) { - id - } - } - """; - GraphQLCreateEventRequest request = new GraphQLCreateEventRequest(); - request.setQuery(fixedQuery); - request.setVariables(variables); - return this.executeCreateEvent(request); - } - default GraphQLResponse createProject(GraphQLCreateProjectVariables variables) { String fixedQuery = "mutation createProject($title: String!, $eventType: ProjectType!, $locationType: ProjectLocationType!, $redirect: String) {\n createProject(\n title: $title\n eventType: $eventType\n locationType: $locationType\n redirect: $redirect\n ) {\n id\n title\n typeId\n locationTypeId\n externalVolunteerUrl\n statusId\n }\n }"; GraphQLCreateProjectRequest request = new GraphQLCreateProjectRequest(); From 849e25e277c5be0b24ad66cefeb2719569a97515 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Mon, 16 Mar 2026 21:04:07 -0600 Subject: [PATCH 29/30] refactor: rename to GraphQuery to GraphMutation Signed-off-by: jonathan zollinger --- .../org/justserve/client/GraphQLClient.java | 4 +-- .../org/justserve/model/ProjectEvent.java | 28 +++++++++---------- ...entQuery.java => CreateEventMutation.java} | 4 +-- .../justserve/model/graph/GraphFields.java | 2 +- .../{GraphQuery.java => GraphMutation.java} | 2 +- .../justserve/model/graph/GraphVariables.java | 2 +- .../org/justserve/GraphQLClientSpec.groovy | 22 +++++++-------- 7 files changed, 32 insertions(+), 32 deletions(-) rename core/src/main/java/org/justserve/model/graph/{CreateEventQuery.java => CreateEventMutation.java} (90%) rename core/src/main/java/org/justserve/model/graph/{GraphQuery.java => GraphMutation.java} (98%) diff --git a/core/src/main/java/org/justserve/client/GraphQLClient.java b/core/src/main/java/org/justserve/client/GraphQLClient.java index 4228318..c08014c 100644 --- a/core/src/main/java/org/justserve/client/GraphQLClient.java +++ b/core/src/main/java/org/justserve/client/GraphQLClient.java @@ -7,7 +7,7 @@ import io.micronaut.http.client.annotation.Client; import io.micronaut.retry.annotation.Retryable; import org.justserve.model.*; -import org.justserve.model.graph.CreateEventQuery; +import org.justserve.model.graph.CreateEventMutation; import org.justserve.model.graph.GraphQLResponse; import java.util.ArrayList; @@ -20,7 +20,7 @@ public interface GraphQLClient { @Post - GraphQLResponse createEvent(@Body CreateEventQuery request); + GraphQLResponse createEvent(@Body CreateEventMutation request); @Post GraphQLResponse executeAddProjectAttachment(@Body GraphQLAddProjectAttachmentRequest request); diff --git a/core/src/main/java/org/justserve/model/ProjectEvent.java b/core/src/main/java/org/justserve/model/ProjectEvent.java index ad2aabb..d39eed6 100644 --- a/core/src/main/java/org/justserve/model/ProjectEvent.java +++ b/core/src/main/java/org/justserve/model/ProjectEvent.java @@ -9,7 +9,7 @@ import jakarta.validation.constraints.Size; import lombok.*; import lombok.experimental.Accessors; -import org.justserve.model.graph.CreateEventQuery; +import org.justserve.model.graph.CreateEventMutation; import org.justserve.model.graph.GraphFields; import java.util.Date; @@ -75,7 +75,7 @@ public class ProjectEvent extends GraphFields { *

    Whether the project event has been deleted.

    * Not Usable In: *
      - *
    • {@link CreateEventQuery}
    • + *
    • {@link CreateEventMutation}
    • *
    */ @Nullable @@ -86,7 +86,7 @@ public class ProjectEvent extends GraphFields { * See{@link #deletedByNavigation}
    * Not Usable In: *
      - *
    • {@link CreateEventQuery}
    • + *
    • {@link CreateEventMutation}
    • *
    */ @Nullable @@ -97,7 +97,7 @@ public class ProjectEvent extends GraphFields { * See{@link #deletedBy}
    * Not Usable In: *
      - *
    • {@link CreateEventQuery}
    • + *
    • {@link CreateEventMutation}
    • *
    */ @Nullable @@ -107,7 +107,7 @@ public class ProjectEvent extends GraphFields { *

    The date and time the project event was deleted.

    * Not Usable In: *
      - *
    • {@link CreateEventQuery}
    • + *
    • {@link CreateEventMutation}
    • *
    */ @Nullable @@ -122,7 +122,7 @@ public class ProjectEvent extends GraphFields { *

    The end date and time of the event with timezone offset.

    * Not Usable In: *
      - *
    • {@link CreateEventQuery}
    • + *
    • {@link CreateEventMutation}
    • *
    */ @Nullable @@ -133,7 +133,7 @@ public class ProjectEvent extends GraphFields { *

    Indicates if the event capacity has been reached.

    * Not Usable In: *
      - *
    • {@link CreateEventQuery}
    • + *
    • {@link CreateEventMutation}
    • *
    */ @Nullable @@ -156,7 +156,7 @@ public class ProjectEvent extends GraphFields { *

    The unique identifier for the project event.

    * Not Usable In: *
      - *
    • {@link CreateEventQuery}
    • + *
    • {@link CreateEventMutation}
    • *
    */ @Nullable @@ -173,7 +173,7 @@ public class ProjectEvent extends GraphFields { *

    The project this event belongs to.

    * Not Usable In: *
      - *
    • {@link CreateEventQuery}
    • + *
    • {@link CreateEventMutation}
    • *
    */ @Nullable @@ -183,7 +183,7 @@ public class ProjectEvent extends GraphFields { *

    The location of the project event.

    * Not Usable In: *
      - *
    • {@link CreateEventQuery}
    • + *
    • {@link CreateEventMutation}
    • *
    */ @Nullable @@ -193,7 +193,7 @@ public class ProjectEvent extends GraphFields { *

    The ID of the project event location.

    * Not Usable In: *
      - *
    • {@link CreateEventQuery}
    • + *
    • {@link CreateEventMutation}
    • *
    */ @Nullable @@ -203,7 +203,7 @@ public class ProjectEvent extends GraphFields { *

    The regions associated with the project event.

    * Not Usable In: *
      - *
    • {@link CreateEventQuery}
    • + *
    • {@link CreateEventMutation}
    • *
    */ @Nullable @@ -213,7 +213,7 @@ public class ProjectEvent extends GraphFields { *

    The ID of the project this event belongs to.

    * Not Usable In: *
      - *
    • {@link CreateEventQuery}
    • + *
    • {@link CreateEventMutation}
    • *
    */ @Nullable @@ -223,7 +223,7 @@ public class ProjectEvent extends GraphFields { *

    Information about the recurring schedule of the project event.

    * Not Usable In: *
      - *
    • {@link CreateEventQuery}
    • + *
    • {@link CreateEventMutation}
    • *
    */ @Nullable diff --git a/core/src/main/java/org/justserve/model/graph/CreateEventQuery.java b/core/src/main/java/org/justserve/model/graph/CreateEventMutation.java similarity index 90% rename from core/src/main/java/org/justserve/model/graph/CreateEventQuery.java rename to core/src/main/java/org/justserve/model/graph/CreateEventMutation.java index b5d16a1..563df3b 100644 --- a/core/src/main/java/org/justserve/model/graph/CreateEventQuery.java +++ b/core/src/main/java/org/justserve/model/graph/CreateEventMutation.java @@ -13,9 +13,9 @@ */ @Serdeable @JsonPropertyOrder({"query", "variables"}) -public class CreateEventQuery extends GraphQuery { +public class CreateEventMutation extends GraphMutation { - public CreateEventQuery(CreateEventVariables variables) { + public CreateEventMutation(CreateEventVariables variables) { this.query = """ mutation createEvent($projectId: ID!, $projectEvent: UpdateProjectEventInput!) { createEvent( diff --git a/core/src/main/java/org/justserve/model/graph/GraphFields.java b/core/src/main/java/org/justserve/model/graph/GraphFields.java index 44756cf..0d092c1 100644 --- a/core/src/main/java/org/justserve/model/graph/GraphFields.java +++ b/core/src/main/java/org/justserve/model/graph/GraphFields.java @@ -11,7 +11,7 @@ /** *

    Fields used in a graphql mutation.

    - * These are the fields used in{@link GraphQuery#query} fields. + * These are the fields used in{@link GraphMutation#query} fields. * * @author Jonathan Zollinger * @since 0.1.0 diff --git a/core/src/main/java/org/justserve/model/graph/GraphQuery.java b/core/src/main/java/org/justserve/model/graph/GraphMutation.java similarity index 98% rename from core/src/main/java/org/justserve/model/graph/GraphQuery.java rename to core/src/main/java/org/justserve/model/graph/GraphMutation.java index 841555e..e9afc71 100644 --- a/core/src/main/java/org/justserve/model/graph/GraphQuery.java +++ b/core/src/main/java/org/justserve/model/graph/GraphMutation.java @@ -17,7 +17,7 @@ @Serdeable @Getter @JsonPropertyOrder({"query", "variables"}) -public abstract class GraphQuery { +public abstract class GraphMutation { /** *

    Mutation Query String

    formatted to receive a (@code \n) delimited string of variable names.
    * The query is to include the mutation's signature, as well as its opening and closing curly braces.
    diff --git a/core/src/main/java/org/justserve/model/graph/GraphVariables.java b/core/src/main/java/org/justserve/model/graph/GraphVariables.java index a957e9e..92708c7 100644 --- a/core/src/main/java/org/justserve/model/graph/GraphVariables.java +++ b/core/src/main/java/org/justserve/model/graph/GraphVariables.java @@ -3,7 +3,7 @@ import io.micronaut.serde.annotation.Serdeable; /** - * Parent class to any variables to be used in a{@link GraphQuery#variables} setting. + * Parent class to any variables to be used in a{@link GraphMutation#variables} setting. * * @author Jonathan Zollinger * @since 0.1.0 diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index bcabd02..c19a822 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -5,7 +5,7 @@ import jakarta.inject.Inject import net.datafaker.Faker import org.justserve.client.GraphQLClient import org.justserve.model.* -import org.justserve.model.graph.CreateEventQuery +import org.justserve.model.graph.CreateEventMutation import org.justserve.model.graph.CreateEventVariables import spock.lang.Shared import spock.lang.Specification @@ -75,7 +75,7 @@ class GraphQLClientSpec extends Specification { def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: - def response = client.createEvent(new CreateEventQuery(vars)) + def response = client.createEvent(new CreateEventMutation(vars)) then: noExceptionThrown() @@ -94,7 +94,7 @@ class GraphQLClientSpec extends Specification { def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: - def response = client.createEvent(new CreateEventQuery(vars)) + def response = client.createEvent(new CreateEventMutation(vars)) then: noExceptionThrown() @@ -114,7 +114,7 @@ class GraphQLClientSpec extends Specification { def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: - def response = client.createEvent(new CreateEventQuery(vars)) + def response = client.createEvent(new CreateEventMutation(vars)) then: noExceptionThrown() @@ -134,7 +134,7 @@ class GraphQLClientSpec extends Specification { def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: - def response = client.createEvent(new CreateEventQuery(vars)) + def response = client.createEvent(new CreateEventMutation(vars)) then: noExceptionThrown() @@ -155,7 +155,7 @@ class GraphQLClientSpec extends Specification { def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: - def response = client.createEvent(new CreateEventQuery(vars)) + def response = client.createEvent(new CreateEventMutation(vars)) then: noExceptionThrown() @@ -175,7 +175,7 @@ class GraphQLClientSpec extends Specification { def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: - def response = client.createEvent(new CreateEventQuery(vars)) + def response = client.createEvent(new CreateEventMutation(vars)) then: noExceptionThrown() @@ -197,7 +197,7 @@ class GraphQLClientSpec extends Specification { def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: - def response = client.createEvent(new CreateEventQuery(vars)) + def response = client.createEvent(new CreateEventMutation(vars)) then: noExceptionThrown() @@ -214,7 +214,7 @@ class GraphQLClientSpec extends Specification { def vars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(event) when: - def response = client.createEvent(new CreateEventQuery(vars)) + def response = client.createEvent(new CreateEventMutation(vars)) then: response.hasErrors() @@ -232,8 +232,8 @@ class GraphQLClientSpec extends Specification { def secondVars = new CreateEventVariables().setProjectId(projectIds[eventType]).setProjectEvent(secondEvent) when: - client.createEvent(new CreateEventQuery(firstVars)) - def secondResponse = client.createEvent(new CreateEventQuery(secondVars)) + client.createEvent(new CreateEventMutation(firstVars)) + def secondResponse = client.createEvent(new CreateEventMutation(secondVars)) then: secondResponse.hasErrors() == shouldFail From 2af0bcc4ce585d7d654eb18c4b775d9ae1bcdf6b Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Mon, 16 Mar 2026 22:35:10 -0600 Subject: [PATCH 30/30] feat: add CreateEvents Signed-off-by: jonathan zollinger --- .../org/justserve/client/GraphQLClient.java | 9 +- .../justserve/model/ProjectRecurringTime.java | 135 +++++++++++++++++- .../org/justserve/model/RecurringType.java | 17 +++ .../model/graph/CreateEventsData.java | 43 ++++++ .../model/graph/CreateEventsMutation.java | 70 +++++++++ .../model/graph/CreateEventsVariables.java | 46 ++++++ .../graph/CreateRecurringEventsData.java | 43 ++++++ .../graph/CreateRecurringEventsMutation.java | 78 ++++++++++ .../graph/CreateRecurringEventsVariables.java | 46 ++++++ .../org/justserve/GraphQLClientSpec.groovy | 102 ++++++++++++- core/src/test/resources/SpockConfig.groovy | 10 +- 11 files changed, 586 insertions(+), 13 deletions(-) create mode 100644 core/src/main/java/org/justserve/model/RecurringType.java create mode 100644 core/src/main/java/org/justserve/model/graph/CreateEventsData.java create mode 100644 core/src/main/java/org/justserve/model/graph/CreateEventsMutation.java create mode 100644 core/src/main/java/org/justserve/model/graph/CreateEventsVariables.java create mode 100644 core/src/main/java/org/justserve/model/graph/CreateRecurringEventsData.java create mode 100644 core/src/main/java/org/justserve/model/graph/CreateRecurringEventsMutation.java create mode 100644 core/src/main/java/org/justserve/model/graph/CreateRecurringEventsVariables.java diff --git a/core/src/main/java/org/justserve/client/GraphQLClient.java b/core/src/main/java/org/justserve/client/GraphQLClient.java index c08014c..bb4b268 100644 --- a/core/src/main/java/org/justserve/client/GraphQLClient.java +++ b/core/src/main/java/org/justserve/client/GraphQLClient.java @@ -7,8 +7,7 @@ import io.micronaut.http.client.annotation.Client; import io.micronaut.retry.annotation.Retryable; import org.justserve.model.*; -import org.justserve.model.graph.CreateEventMutation; -import org.justserve.model.graph.GraphQLResponse; +import org.justserve.model.graph.*; import java.util.ArrayList; import java.util.List; @@ -22,6 +21,12 @@ public interface GraphQLClient { @Post GraphQLResponse createEvent(@Body CreateEventMutation request); + @Post + GraphQLResponse createEvents(@Body CreateEventsMutation request); + + @Post + GraphQLResponse createRecurringEvents(@Body CreateRecurringEventsMutation request); + @Post GraphQLResponse executeAddProjectAttachment(@Body GraphQLAddProjectAttachmentRequest request); diff --git a/core/src/main/java/org/justserve/model/ProjectRecurringTime.java b/core/src/main/java/org/justserve/model/ProjectRecurringTime.java index 613c342..3d5782e 100644 --- a/core/src/main/java/org/justserve/model/ProjectRecurringTime.java +++ b/core/src/main/java/org/justserve/model/ProjectRecurringTime.java @@ -1,17 +1,144 @@ package org.justserve.model; import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.Nullable; import io.micronaut.serde.annotation.Serdeable; -import lombok.Data; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Size; +import lombok.*; import lombok.experimental.Accessors; +import org.justserve.model.graph.GraphFields; + +import java.util.List; +import java.util.UUID; + +import static java.lang.Boolean.TRUE; /** - * This class is currently a placeholder for a future work. - * This class does nothing. + *

    JustServe Project Recurring Time

    + * Valid to use with + *
      + *
    • {@link EventType#Recurring}
    • + *
    + * + *

    Creating New Recurring Events

    + * Use {@code ProjectRecurringTime.}{@link #builder()} when adding a new recurring event to ensure all + * needed fields are included and validated. + * + * @author Jonathan Zollinger + * @since 0.1.0 */ +@EqualsAndHashCode(callSuper = true) @Data @Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Builder(buildMethodName = "buildInternal") @Serdeable @Introspected -public class ProjectRecurringTime { +public class ProjectRecurringTime extends GraphFields { + + @Nullable + @Email + private String contactEmail; + + @Nullable + @Size(max = 139) + private String contactName; + + @Nullable + private String contactPhone; + + @Nullable + private String startTime; + + @Nullable + private String endTime; + + @Builder.Default + private Boolean firstWeek = false; + + @Builder.Default + private Boolean secondWeek = false; + + @Builder.Default + private Boolean thirdWeek = false; + + @Builder.Default + private Boolean fourthWeek = false; + + @Builder.Default + private Boolean fifthWeek = false; + + @Builder.Default + private Boolean lastWeek = false; + + @Nullable + private Integer groupLimit; + + + @Nullable + private UUID id; + + @Nullable + private RecurringType recurringType; + + @Nullable + private List daysOfMonth; + + /** + *

    The days of the months a recurring event lands on.

    + * For example, a monthly recurring event landing on the 16th of the month: + * + */ + @Nullable + private List recurringDaysOfMonths; + + @Nullable + private UUID projectRecurringId; + + @Nullable + private String specialDirections; + + @Builder.Default + private Boolean volunteersCapped = false; + + @Nullable + private Integer totalVolunteersNeeded; + + @Nullable + private Integer volunteersNeeded; + + @Builder.Default + private Boolean monday = false; + + @Builder.Default + private Boolean tuesday = false; + + @Builder.Default + private Boolean wednesday = false; + + @Builder.Default + private Boolean thursday = false; + + @Builder.Default + private Boolean friday = false; + + @Builder.Default + private Boolean saturday = false; + + @Builder.Default + private Boolean sunday = false; + + + public static class ProjectRecurringTimeBuilder { + public ProjectRecurringTime build() { + ProjectRecurringTime event = this.buildInternal(); + if (TRUE.equals(event.getVolunteersCapped()) && event.getVolunteersNeeded() == null) { + throw new IllegalStateException("volunteersNeeded cannot be null when volunteersCapped is true"); + } + + return event; + } + } } diff --git a/core/src/main/java/org/justserve/model/RecurringType.java b/core/src/main/java/org/justserve/model/RecurringType.java new file mode 100644 index 0000000..5fc1e80 --- /dev/null +++ b/core/src/main/java/org/justserve/model/RecurringType.java @@ -0,0 +1,17 @@ +package org.justserve.model; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.serde.annotation.Serdeable; + +/** + * JustServe Project Recurring Type. + * + * @author Jonathan Zollinger + * @since 0.1.0 + */ +@Serdeable +@Introspected +public enum RecurringType { + WEEKLY, + MONTHLY +} diff --git a/core/src/main/java/org/justserve/model/graph/CreateEventsData.java b/core/src/main/java/org/justserve/model/graph/CreateEventsData.java new file mode 100644 index 0000000..dac2b2e --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/CreateEventsData.java @@ -0,0 +1,43 @@ +package org.justserve.model.graph; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.micronaut.serde.annotation.Serdeable; +import lombok.Data; +import org.justserve.model.ProjectEvent; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Parses the dynamic response from the createEvents GraphQL mutation. + * + * @author Jonathan Zollinger + * @since 0.1.0 + */ +@Serdeable +@Data +public class CreateEventsData { + + @JsonIgnore + private Map events = new HashMap<>(); + + @JsonAnySetter + public void addEvent(String key, ProjectEvent event) { + if (key != null && key.startsWith("event")) { + events.put(key, event); + } + } + + /** + * Helper to return all the dynamically parsed events as a single list. + * + * @return List of newly created events + */ + @JsonIgnore + public List getEventList() { + return new ArrayList<>(events.values()); + } +} diff --git a/core/src/main/java/org/justserve/model/graph/CreateEventsMutation.java b/core/src/main/java/org/justserve/model/graph/CreateEventsMutation.java new file mode 100644 index 0000000..e328834 --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/CreateEventsMutation.java @@ -0,0 +1,70 @@ +package org.justserve.model.graph; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import io.micronaut.serde.annotation.Serdeable; + +import java.util.List; +import java.util.stream.IntStream; + +/** + * Data Transfer Object for the {@code createEvents} GraphQL mutation. + * This class dynamically constructs the mutation query based on the + * events provided in the {@link CreateEventsVariables}. + * + * @author Jonathan Zollinger + * @since 0.1.0 + */ +@Serdeable +@JsonPropertyOrder({"query", "variables"}) +public class CreateEventsMutation extends GraphMutation { + + public CreateEventsMutation(CreateEventsVariables variables) { + if (variables.getProjectEvents().isEmpty()) { + throw new IllegalArgumentException("At least one event must be provided"); + } + this.query = buildQuery(variables); + this.variables = variables; + } + + private String buildQuery(CreateEventsVariables variables) { + StringBuilder args = new StringBuilder("$projectId: ID!"); + StringBuilder body = new StringBuilder(); + + List sortedKeys = variables.getProjectEvents().keySet().stream().sorted().toList(); + + IntStream.range(0, sortedKeys.size()).forEach(i -> { + String key = sortedKeys.get(i); + args.append(", $").append(key).append(": UpdateProjectEventInput!"); + body.append(String.format(""" + %s: createEvent( + projectId: $projectId + projectEvent: $%s + ) { + %%s + } + """, "event" + i, key)); + }); + + return String.format(""" + mutation createEvents(%s) { + %s} + """, args, body); + } + + @Override + public CreateEventsVariables getVariables() { + return (CreateEventsVariables) super.getVariables(); + } + + @Override + public String getQuery() { + CreateEventsVariables vars = getVariables(); + + List sortedKeys = vars.getProjectEvents().keySet().stream().sorted().toList(); + + Object[] fieldsArray = sortedKeys.stream().map(sortedKey -> vars.getProjectEvents().get(sortedKey) + .getMutationFields()).toArray(); + + return String.format(query, fieldsArray); + } +} diff --git a/core/src/main/java/org/justserve/model/graph/CreateEventsVariables.java b/core/src/main/java/org/justserve/model/graph/CreateEventsVariables.java new file mode 100644 index 0000000..d42c523 --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/CreateEventsVariables.java @@ -0,0 +1,46 @@ +package org.justserve.model.graph; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.micronaut.serde.annotation.Serdeable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.experimental.Accessors; +import org.justserve.model.ProjectEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Pojo to serialize the variables object passed with a createEvents mutation. + * + * @author Jonathan Zollinger + * @since 0.1.0 + */ +@EqualsAndHashCode(callSuper = true) +@Serdeable +@Data +@NoArgsConstructor +@Accessors(chain = true) +public class CreateEventsVariables extends GraphVariables { + + private UUID projectId; + + @JsonIgnore + private Map projectEvents = new HashMap<>(); + + public CreateEventsVariables(UUID projectId, @NonNull ProjectEvent... events) { + this.projectId = projectId; + for (int i = 0; i < events.length; i++) { + this.projectEvents.put("projectEvent" + i, events[i]); + } + } + + @JsonAnyGetter + public Map getProjectEvents() { + return projectEvents; + } +} diff --git a/core/src/main/java/org/justserve/model/graph/CreateRecurringEventsData.java b/core/src/main/java/org/justserve/model/graph/CreateRecurringEventsData.java new file mode 100644 index 0000000..41fd297 --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/CreateRecurringEventsData.java @@ -0,0 +1,43 @@ +package org.justserve.model.graph; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.micronaut.serde.annotation.Serdeable; +import lombok.Data; +import org.justserve.model.ProjectRecurringTime; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Parses the dynamic response from the createRecurringEvents GraphQL mutation. + * + * @author Jonathan Zollinger + * @since 0.1.0 + */ +@Serdeable +@Data +public class CreateRecurringEventsData { + + @JsonIgnore + private Map events = new HashMap<>(); + + @JsonAnySetter + public void addEvent(String key, ProjectRecurringTime event) { + if (key != null && key.startsWith("event")) { + events.put(key, event); + } + } + + /** + * Helper to return all the dynamically parsed events as a single list. + * + * @return List of newly created recurring events + */ + @JsonIgnore + public List getEventList() { + return new ArrayList<>(events.values()); + } +} diff --git a/core/src/main/java/org/justserve/model/graph/CreateRecurringEventsMutation.java b/core/src/main/java/org/justserve/model/graph/CreateRecurringEventsMutation.java new file mode 100644 index 0000000..0c0127b --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/CreateRecurringEventsMutation.java @@ -0,0 +1,78 @@ +package org.justserve.model.graph; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import io.micronaut.serde.annotation.Serdeable; +import org.justserve.model.ProjectRecurringTime; + +import java.util.List; +import java.util.UUID; +import java.util.stream.IntStream; + +/** + * Data Transfer Object for the {@code createRecurringEvents} GraphQL mutation. + * This class dynamically constructs the mutation query based on the + * events provided in the {@link CreateRecurringEventsVariables}. + * + * @author Jonathan Zollinger + * @since 0.1.0 + */ +@Serdeable +@JsonPropertyOrder({"query", "variables"}) +public class CreateRecurringEventsMutation extends GraphMutation { + + public CreateRecurringEventsMutation(UUID projectId, ProjectRecurringTime... events) { + this(new CreateRecurringEventsVariables(projectId, events)); + } + + public CreateRecurringEventsMutation(CreateRecurringEventsVariables variables) { + if (variables.getProjectEvents() == null || variables.getProjectEvents().isEmpty()) { + throw new IllegalArgumentException("At least one event must be provided"); + } + this.query = buildQuery(variables); + this.variables = variables; + } + + private String buildQuery(CreateRecurringEventsVariables variables) { + StringBuilder args = new StringBuilder("$projectId: ID!"); + StringBuilder body = new StringBuilder(); + + List sortedKeys = variables.getProjectEvents().keySet().stream().sorted().toList(); + + String template = """ + %s: addRecurringTime( + projectId: $projectId + recurringTime: $%s + ) { + %%s + } + """; + + IntStream.range(0, sortedKeys.size()).forEach(i -> { + String key = sortedKeys.get(i); + args.append(", $").append(key).append(": CreateProjectRecurringTimeInput!"); + body.append(String.format(template, "event" + i, key)); + }); + + return String.format(""" + mutation createRecurringEvents(%s) { + %s} + """, args, body); + } + + @Override + public CreateRecurringEventsVariables getVariables() { + return (CreateRecurringEventsVariables) super.getVariables(); + } + + @Override + public String getQuery() { + CreateRecurringEventsVariables vars = getVariables(); + + List sortedKeys = vars.getProjectEvents().keySet().stream().sorted().toList(); + + Object[] fieldsArray = sortedKeys.stream().map(sortedKey -> vars.getProjectEvents().get(sortedKey) + .getMutationFields()).toArray(); + + return String.format(query, fieldsArray); + } +} diff --git a/core/src/main/java/org/justserve/model/graph/CreateRecurringEventsVariables.java b/core/src/main/java/org/justserve/model/graph/CreateRecurringEventsVariables.java new file mode 100644 index 0000000..ebf9337 --- /dev/null +++ b/core/src/main/java/org/justserve/model/graph/CreateRecurringEventsVariables.java @@ -0,0 +1,46 @@ +package org.justserve.model.graph; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.micronaut.serde.annotation.Serdeable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.experimental.Accessors; +import org.justserve.model.ProjectRecurringTime; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Pojo to serialize the variables object passed with a createRecurringEvents mutation. + * + * @author Jonathan Zollinger + * @since 0.1.0 + */ +@EqualsAndHashCode(callSuper = true) +@Serdeable +@Data +@NoArgsConstructor +@Accessors(chain = true) +public class CreateRecurringEventsVariables extends GraphVariables { + + private UUID projectId; + + @JsonIgnore + private Map projectEvents = new HashMap<>(); + + public CreateRecurringEventsVariables(UUID projectId, @NonNull ProjectRecurringTime... events) { + this.projectId = projectId; + for (int i = 0; i < events.length; i++) { + this.projectEvents.put("projectEvent" + i, events[i]); + } + } + + @JsonAnyGetter + public Map getProjectEvents() { + return projectEvents; + } +} diff --git a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy index c19a822..1706118 100644 --- a/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy +++ b/core/src/test/groovy/org/justserve/GraphQLClientSpec.groovy @@ -5,8 +5,7 @@ import jakarta.inject.Inject import net.datafaker.Faker import org.justserve.client.GraphQLClient import org.justserve.model.* -import org.justserve.model.graph.CreateEventMutation -import org.justserve.model.graph.CreateEventVariables +import org.justserve.model.graph.* import spock.lang.Shared import spock.lang.Specification import spock.lang.Unroll @@ -246,6 +245,57 @@ class GraphQLClientSpec extends Specification { EventType.MultipleDTL | false } + def "can create multiple events at once for Multi-DTL projects using createEvents mutation"() { + given: + def event1 = baseEventBuilder() + .shiftTitle("Morning Shift") + .build() + def event2 = baseEventBuilder() + .shiftTitle("Afternoon Shift") + .build() + def event3 = baseEventBuilder() + .shiftTitle("Evening Shift") + .build() + def vars = new CreateEventsVariables(projectIds[EventType.MultipleDTL], event1, event2, event3) + def mutation = new CreateEventsMutation(vars) + + when: + def response = client.createEvents(mutation) + + then: + noExceptionThrown() + !response.hasErrors() + + and: "the response data should contain the newly created events" + null != response.getData() + } + + def "can create multiple recurring events at once for Recurring projects using createRecurringEvents mutation"() { + given: + def time1 = new ProjectRecurringTime() + .setStartTime("10:00") + .setEndTime("12:00") + .setRecurringType(RecurringType.WEEKLY) + .setMonday(true) + def time2 = new ProjectRecurringTime() + .setStartTime("14:00") + .setEndTime("16:00") + .setRecurringType(RecurringType.MONTHLY) + .setThirdWeek(true) + def vars = new CreateRecurringEventsVariables(projectIds[EventType.Recurring], time1, time2) + def mutation = new CreateRecurringEventsMutation(vars) + + when: + def response = client.createRecurringEvents(mutation) + + then: + noExceptionThrown() + !response.hasErrors() + + and: "the response data should contain the newly created recurring events" + null != response.getData() + } + def "cannot create event without start and end dates"() { when: ProjectEvent.builder().build() @@ -269,4 +319,52 @@ class GraphQLClientSpec extends Specification { then: thrown(IllegalStateException) } + + def "cannot set volunteersCapped without volunteersNeeded on ProjectRecurringTime"() { + when: + ProjectRecurringTime.builder().volunteersCapped(true).build() + + then: + thrown(IllegalStateException) + } + + def "can set all info for ProjectRecurringTime"() { + given: + def time1 = ProjectRecurringTime.builder() + .contactEmail(faker.internet().emailAddress()) + .contactName(faker.name().fullName()) + .contactPhone(faker.phoneNumber().phoneNumber()) + .startTime("10:00") + .endTime("12:00") + .firstWeek(true) + .secondWeek(true) + .thirdWeek(true) + .fourthWeek(true) + .fifthWeek(true) + .lastWeek(true) + .groupLimit(10) + .recurringType(RecurringType.WEEKLY) + .recurringDaysOfMonths([1, 15, 28]) + .specialDirections(faker.lorem().paragraph()) + .volunteersCapped(true) + .volunteersNeeded(5) + .monday(true) + .tuesday(true) + .wednesday(true) + .thursday(true) + .friday(true) + .saturday(true) + .sunday(true) + .build() + + def vars = new CreateRecurringEventsVariables(projectIds[EventType.Recurring], time1) + def mutation = new CreateRecurringEventsMutation(vars) + + when: + def response = client.createRecurringEvents(mutation) + + then: + noExceptionThrown() + !response.hasErrors() + } } diff --git a/core/src/test/resources/SpockConfig.groovy b/core/src/test/resources/SpockConfig.groovy index ee30777..267ec1e 100644 --- a/core/src/test/resources/SpockConfig.groovy +++ b/core/src/test/resources/SpockConfig.groovy @@ -1,5 +1,5 @@ -//runner { -// parallel { -// enabled true -// } -//} \ No newline at end of file +runner { + parallel { + enabled true + } +} \ No newline at end of file