diff --git a/cmd/mailing-list-api/committee.go b/cmd/mailing-list-api/committee.go index 83052b1..7e0b722 100644 --- a/cmd/mailing-list-api/committee.go +++ b/cmd/mailing-list-api/committee.go @@ -1,107 +1,6 @@ // Copyright The Linux Foundation and each contributor to LFX. // SPDX-License-Identifier: MIT +// Package main provides the mailing list API service. +// Committee sync has been removed as part of the ITX proxy refactoring. package main - -import ( - "context" - "fmt" - "log/slog" - "sync" - "time" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/cmd/mailing-list-api/service" - internalService "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/service" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/nats-io/nats.go" -) - -// handleCommitteeSync sets up and starts committee event subscriptions -// Pattern: mirrors handleHTTPServer - does both setup and start in one function -func handleCommitteeSync(ctx context.Context, wg *sync.WaitGroup) error { - slog.InfoContext(ctx, "starting committee sync") - - // Get dependencies (all inside function - mirrors handleHTTPServer) - mailingListReader := service.GrpsIOReader(ctx) - memberWriter := service.GrpsIOWriterOrchestrator(ctx) // Use orchestrator for message publishing - memberReader := service.GrpsIOReader(ctx) - entityReader := service.EntityAttributeRetriever(ctx) - natsClient := service.GetNATSClient(ctx) - - // Create committee sync service - syncService := internalService.NewCommitteeSyncService( - mailingListReader, - memberWriter, - memberReader, - entityReader, - ) - - // Subscribe to all committee event subjects - subjects := []string{ - constants.CommitteeMemberCreatedSubject, - constants.CommitteeMemberDeletedSubject, - constants.CommitteeMemberUpdatedSubject, - } - - for _, subject := range subjects { - // Capture loop variable for closure - subject := subject - _, subErr := natsClient.QueueSubscribe( - subject, - constants.MailingListAPIQueue, - func(msg *nats.Msg) { - // Check if service is shutting down - select { - case <-ctx.Done(): - slog.InfoContext(ctx, "rejecting message - service shutting down", - "subject", msg.Subject) - if nakErr := msg.Nak(); nakErr != nil { - slog.ErrorContext(ctx, "failed to nak message during shutdown", "error", nakErr) - } - return - default: - // Continue processing - } - - // Create fresh context with timeout for this message - // Not derived from shutdown context to avoid cancellation issues - msgCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - // Process message with proper error handling and acknowledgment - if handleErr := syncService.HandleMessage(msgCtx, msg); handleErr != nil { - slog.ErrorContext(msgCtx, "failed to process committee event, will retry", - "error", handleErr, - "subject", msg.Subject) - if nakErr := msg.Nak(); nakErr != nil { - slog.ErrorContext(msgCtx, "failed to nak message", "error", nakErr) - } - } else { - // Success - acknowledge message - if ackErr := msg.Ack(); ackErr != nil { - slog.ErrorContext(msgCtx, "failed to ack message", "error", ackErr) - } - } - }, - ) - if subErr != nil { - return fmt.Errorf("failed to subscribe to %s: %w", subject, subErr) - } - slog.InfoContext(ctx, "subscribed to committee event", - "subject", subject, - "queue", constants.MailingListAPIQueue) - } - - slog.InfoContext(ctx, "committee sync started successfully") - - // Graceful shutdown (mirrors handleHTTPServer) - wg.Add(1) - go func() { - defer wg.Done() - <-ctx.Done() - slog.InfoContext(ctx, "shutting down committee sync") - // NATS client cleanup handled by existing Close() in main shutdown - }() - - return nil -} diff --git a/cmd/mailing-list-api/design/mailing_list.go b/cmd/mailing-list-api/design/mailing_list.go index ff8a110..27d0bb1 100644 --- a/cmd/mailing-list-api/design/mailing_list.go +++ b/cmd/mailing-list-api/design/mailing_list.go @@ -11,7 +11,7 @@ import ( // API describes the global properties of the API server. var _ = dsl.API("mailing-list", func() { dsl.Title("Mailing List Service") - dsl.Description("Service for managing mailing lists in LFX") + dsl.Description("Service for proxying GroupsIO operations to the ITX API") }) // JWTAuth defines the JWT security scheme for authenticated endpoints @@ -21,7 +21,7 @@ var JWTAuth = dsl.JWTSecurity("jwt", func() { // MailingListService defines the mailing list service. var _ = dsl.Service("mailing-list", func() { - dsl.Description("The mailing list service manages mailing lists and services") + dsl.Description("The mailing list service proxies GroupsIO operations to the ITX API") // Health check endpoints dsl.Method("livez", func() { @@ -52,432 +52,402 @@ var _ = dsl.Service("mailing-list", func() { }) }) - // Service Management endpoints - dsl.Method("create-grpsio-service", func() { - dsl.Description("Create GroupsIO service with type-specific validation rules") + // ---- GroupsIO Service endpoints ---- + + dsl.Method("list-groupsio-services", func() { + dsl.Description("List GroupsIO services, optionally filtered by project UID") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - - GrpsIOServiceBaseAttributes() - - WritersAttribute() - AuditorsAttribute() + dsl.Attribute("project_uid", dsl.String, "LFX v2 project UID filter", func() { + dsl.Format(dsl.FormatUUID) + }) + dsl.Token("bearer_token", dsl.String) + }) + dsl.Result(GroupsioServiceListType) + dsl.Error("BadRequest", BadRequestError, "Bad request") + dsl.Error("InternalServerError", InternalServerError, "Internal server error") + dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") + dsl.HTTP(func() { + dsl.GET("/groupsio/services") + dsl.Param("project_uid") + dsl.Header("bearer_token:Authorization") + dsl.Response(dsl.StatusOK) + dsl.Response("BadRequest", dsl.StatusBadRequest) + dsl.Response("InternalServerError", dsl.StatusInternalServerError) + dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) + }) + }) - // Only common required fields - type-specific validation handled in service layer - dsl.Required("type", "project_uid", "version") + dsl.Method("create-groupsio-service", func() { + dsl.Description("Create a GroupsIO service") + dsl.Security(JWTAuth) + dsl.Payload(func() { + BearerTokenAttribute() + dsl.Extend(GroupsioServiceRequestType) + dsl.Token("bearer_token", dsl.String) }) - dsl.Result(GrpsIOServiceFull) - dsl.Error("BadRequest", BadRequestError, "Bad request - Invalid type, missing required fields, or validation failures") - dsl.Error("NotFound", NotFoundError, "Resource not found") + dsl.Result(GroupsioServiceType) + dsl.Error("BadRequest", BadRequestError, "Bad request") dsl.Error("Conflict", ConflictError, "Conflict") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { dsl.POST("/groupsio/services") - dsl.Param("version:v") dsl.Header("bearer_token:Authorization") dsl.Response(dsl.StatusCreated) dsl.Response("BadRequest", dsl.StatusBadRequest) - dsl.Response("NotFound", dsl.StatusNotFound) dsl.Response("Conflict", dsl.StatusConflict) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - dsl.Method("get-grpsio-service", func() { - dsl.Description("Get groupsIO service details by ID") + dsl.Method("get-groupsio-service", func() { + dsl.Description("Get a GroupsIO service by ID") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - GrpsIOServiceUIDAttribute() - }) - dsl.Result(func() { - dsl.Attribute("service", GrpsIOServiceWithReadonlyAttributes) - ETagAttribute() - VersionAttribute() - dsl.Required("service", "version") + dsl.Attribute("service_id", dsl.String, "Service ID") + dsl.Required("service_id") + dsl.Token("bearer_token", dsl.String) }) - dsl.Error("BadRequest", BadRequestError, "Bad request") - dsl.Error("NotFound", NotFoundError, "Resource not found") + dsl.Result(GroupsioServiceType) + dsl.Error("NotFound", NotFoundError, "Service not found") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.GET("/groupsio/services/{uid}") - dsl.Param("version:v") - dsl.Param("uid") + dsl.GET("/groupsio/services/{service_id}") + dsl.Param("service_id") dsl.Header("bearer_token:Authorization") - dsl.Response(dsl.StatusOK, func() { - dsl.Body("service") - dsl.Header("etag:ETag") - }) - dsl.Response("BadRequest", dsl.StatusBadRequest) + dsl.Response(dsl.StatusOK) dsl.Response("NotFound", dsl.StatusNotFound) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - dsl.Method("update-grpsio-service", func() { - dsl.Description("Update GroupsIO service") + dsl.Method("update-groupsio-service", func() { + dsl.Description("Update a GroupsIO service") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - IfMatchAttribute() - - GrpsIOServiceUIDAttribute() - GrpsIOServiceBaseAttributes() - - dsl.Required("type", "project_uid", "version") + dsl.Attribute("service_id", dsl.String, "Service ID") + dsl.Extend(GroupsioServiceRequestType) + dsl.Required("service_id") + dsl.Token("bearer_token", dsl.String) }) - dsl.Result(GrpsIOServiceWithReadonlyAttributes) + dsl.Result(GroupsioServiceType) dsl.Error("BadRequest", BadRequestError, "Bad request") - dsl.Error("NotFound", NotFoundError, "Resource not found") - dsl.Error("Conflict", ConflictError, "Conflict") + dsl.Error("NotFound", NotFoundError, "Service not found") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.PUT("/groupsio/services/{uid}") - dsl.Param("version:v") - dsl.Param("uid") + dsl.PUT("/groupsio/services/{service_id}") + dsl.Param("service_id") dsl.Header("bearer_token:Authorization") - dsl.Header("if_match:If-Match") dsl.Response(dsl.StatusOK) dsl.Response("BadRequest", dsl.StatusBadRequest) dsl.Response("NotFound", dsl.StatusNotFound) - dsl.Response("Conflict", dsl.StatusConflict) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - dsl.Method("delete-grpsio-service", func() { - dsl.Description("Delete GroupsIO service") + dsl.Method("delete-groupsio-service", func() { + dsl.Description("Delete a GroupsIO service") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - IfMatchAttribute() - GrpsIOServiceUIDAttribute() + dsl.Attribute("service_id", dsl.String, "Service ID") + dsl.Required("service_id") + dsl.Token("bearer_token", dsl.String) }) - dsl.Error("BadRequest", BadRequestError, "Bad request") - dsl.Error("NotFound", NotFoundError, "Resource not found") - dsl.Error("Conflict", ConflictError, "Conflict") + dsl.Error("NotFound", NotFoundError, "Service not found") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.DELETE("/groupsio/services/{uid}") - dsl.Param("version:v") - dsl.Param("uid") + dsl.DELETE("/groupsio/services/{service_id}") + dsl.Param("service_id") dsl.Header("bearer_token:Authorization") - dsl.Header("if_match:If-Match") dsl.Response(dsl.StatusNoContent) - dsl.Response("BadRequest", dsl.StatusBadRequest) dsl.Response("NotFound", dsl.StatusNotFound) - dsl.Response("Conflict", dsl.StatusConflict) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - dsl.Method("get-grpsio-service-settings", func() { - dsl.Description("Get GroupsIO service settings (writers and auditors)") + dsl.Method("get-groupsio-service-projects", func() { + dsl.Description("Get projects that have GroupsIO services") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - GrpsIOServiceUIDAttribute() + dsl.Token("bearer_token", dsl.String) + }) + dsl.Result(GroupsioProjectsResponseType) + dsl.Error("InternalServerError", InternalServerError, "Internal server error") + dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") + dsl.HTTP(func() { + dsl.GET("/groupsio/services/_projects") + dsl.Header("bearer_token:Authorization") + dsl.Response(dsl.StatusOK) + dsl.Response("InternalServerError", dsl.StatusInternalServerError) + dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) - dsl.Result(func() { - dsl.Attribute("service_settings", GrpsIOServiceSettings) - ETagAttribute() - dsl.Required("service_settings") + }) + + dsl.Method("find-parent-groupsio-service", func() { + dsl.Description("Find the parent GroupsIO service for a project") + dsl.Security(JWTAuth) + dsl.Payload(func() { + BearerTokenAttribute() + dsl.Attribute("project_uid", dsl.String, "LFX v2 project UID", func() { + dsl.Format(dsl.FormatUUID) + }) + dsl.Required("project_uid") + dsl.Token("bearer_token", dsl.String) }) + dsl.Result(GroupsioServiceType) + dsl.Error("NotFound", NotFoundError, "Parent service not found") dsl.Error("BadRequest", BadRequestError, "Bad request") - dsl.Error("NotFound", NotFoundError, "Service settings not found") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.GET("/groupsio/services/{uid}/settings") - dsl.Param("version:v") - dsl.Param("uid") + dsl.GET("/groupsio/services/find_parent") + dsl.Param("project_uid") dsl.Header("bearer_token:Authorization") - dsl.Response(dsl.StatusOK, func() { - dsl.Body("service_settings") - dsl.Header("etag:ETag") - }) - dsl.Response("BadRequest", dsl.StatusBadRequest) + dsl.Response(dsl.StatusOK) dsl.Response("NotFound", dsl.StatusNotFound) + dsl.Response("BadRequest", dsl.StatusBadRequest) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - dsl.Method("update-grpsio-service-settings", func() { - dsl.Description("Update GroupsIO service settings (writers and auditors)") + // ---- GroupsIO Subgroup endpoints ---- + + dsl.Method("list-groupsio-subgroups", func() { + dsl.Description("List GroupsIO subgroups, optionally filtered by project UID and/or committee UID") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - IfMatchAttribute() - GrpsIOServiceUIDAttribute() - WritersAttribute() - AuditorsAttribute() - dsl.Required("version", "uid") - }) - dsl.Result(GrpsIOServiceSettings) + dsl.Attribute("project_uid", dsl.String, "LFX v2 project UID filter", func() { + dsl.Format(dsl.FormatUUID) + }) + dsl.Attribute("committee_uid", dsl.String, "LFX v2 committee UID filter", func() { + dsl.Format(dsl.FormatUUID) + }) + dsl.Token("bearer_token", dsl.String) + }) + dsl.Result(GroupsioSubgroupListType) dsl.Error("BadRequest", BadRequestError, "Bad request") - dsl.Error("NotFound", NotFoundError, "Service settings not found") - dsl.Error("Conflict", ConflictError, "Conflict - ETag mismatch") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.PUT("/groupsio/services/{uid}/settings") - dsl.Param("version:v") - dsl.Param("uid") + dsl.GET("/groupsio/subgroups") + dsl.Param("project_uid") + dsl.Param("committee_uid") dsl.Header("bearer_token:Authorization") - dsl.Header("if_match:If-Match") dsl.Response(dsl.StatusOK) dsl.Response("BadRequest", dsl.StatusBadRequest) - dsl.Response("NotFound", dsl.StatusNotFound) - dsl.Response("Conflict", dsl.StatusConflict) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - // Mailing List Management endpoints - dsl.Method("create-grpsio-mailing-list", func() { - dsl.Description("Create GroupsIO mailing list/subgroup with comprehensive validation") + dsl.Method("create-groupsio-subgroup", func() { + dsl.Description("Create a GroupsIO subgroup") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - - GrpsIOMailingListBaseAttributes() - - // Settings fields (writers and auditors) for initial mailing list setup - WritersAttribute() - AuditorsAttribute() - - // Required fields for mailing list creation - dsl.Required("group_name", "public", "type", "description", "title", "service_uid", "version") + dsl.Extend(GroupsioSubgroupRequestType) + dsl.Token("bearer_token", dsl.String) }) - dsl.Result(GrpsIOMailingListFull) - dsl.Error("BadRequest", BadRequestError, "Bad request - Invalid data, missing required fields, or validation failures") - dsl.Error("NotFound", NotFoundError, "Parent service not found or committee not found") - dsl.Error("Conflict", ConflictError, "Mailing list with same name already exists") + dsl.Result(GroupsioSubgroupType) + dsl.Error("BadRequest", BadRequestError, "Bad request") + dsl.Error("Conflict", ConflictError, "Conflict") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.POST("/groupsio/mailing-lists") - dsl.Param("version:v") + dsl.POST("/groupsio/subgroups") dsl.Header("bearer_token:Authorization") dsl.Response(dsl.StatusCreated) dsl.Response("BadRequest", dsl.StatusBadRequest) - dsl.Response("NotFound", dsl.StatusNotFound) dsl.Response("Conflict", dsl.StatusConflict) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - dsl.Method("get-grpsio-mailing-list", func() { - dsl.Description("Get GroupsIO mailing list details by UID") + dsl.Method("get-groupsio-subgroup", func() { + dsl.Description("Get a GroupsIO subgroup by ID") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - GrpsIOMailingListUIDAttribute() - dsl.Required("bearer_token", "version") - }) - dsl.Result(func() { - dsl.Attribute("mailing_list", GrpsIOMailingListWithReadonlyAttributes) - ETagAttribute() - dsl.Required("mailing_list") + dsl.Attribute("subgroup_id", dsl.String, "Subgroup ID") + dsl.Required("subgroup_id") + dsl.Token("bearer_token", dsl.String) }) - dsl.Error("BadRequest", BadRequestError, "Bad request") - dsl.Error("NotFound", NotFoundError, "Mailing list not found") + dsl.Result(GroupsioSubgroupType) + dsl.Error("NotFound", NotFoundError, "Subgroup not found") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.GET("/groupsio/mailing-lists/{uid}") - dsl.Param("version:v") - dsl.Param("uid") + dsl.GET("/groupsio/subgroups/{subgroup_id}") + dsl.Param("subgroup_id") dsl.Header("bearer_token:Authorization") - dsl.Response(dsl.StatusOK, func() { - dsl.Body("mailing_list") - dsl.Header("etag:ETag") - }) - dsl.Response("BadRequest", dsl.StatusBadRequest) + dsl.Response(dsl.StatusOK) dsl.Response("NotFound", dsl.StatusNotFound) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - dsl.Method("update-grpsio-mailing-list", func() { - dsl.Description("Update GroupsIO mailing list") + dsl.Method("update-groupsio-subgroup", func() { + dsl.Description("Update a GroupsIO subgroup") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - IfMatchAttribute() - - GrpsIOMailingListUIDAttribute() - GrpsIOMailingListBaseAttributes() - - dsl.Required("group_name", "public", "type", "description", "title", "service_uid", "version") + dsl.Attribute("subgroup_id", dsl.String, "Subgroup ID") + dsl.Extend(GroupsioSubgroupRequestType) + dsl.Required("subgroup_id") + dsl.Token("bearer_token", dsl.String) }) - dsl.Result(GrpsIOMailingListWithReadonlyAttributes) + dsl.Result(GroupsioSubgroupType) dsl.Error("BadRequest", BadRequestError, "Bad request") - dsl.Error("NotFound", NotFoundError, "Mailing list not found") - dsl.Error("Conflict", ConflictError, "Conflict - ETag mismatch or validation failure") + dsl.Error("NotFound", NotFoundError, "Subgroup not found") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.PUT("/groupsio/mailing-lists/{uid}") - dsl.Param("version:v") - dsl.Param("uid") + dsl.PUT("/groupsio/subgroups/{subgroup_id}") + dsl.Param("subgroup_id") dsl.Header("bearer_token:Authorization") - dsl.Header("if_match:If-Match") dsl.Response(dsl.StatusOK) dsl.Response("BadRequest", dsl.StatusBadRequest) dsl.Response("NotFound", dsl.StatusNotFound) - dsl.Response("Conflict", dsl.StatusConflict) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - dsl.Method("delete-grpsio-mailing-list", func() { - dsl.Description("Delete GroupsIO mailing list") + dsl.Method("delete-groupsio-subgroup", func() { + dsl.Description("Delete a GroupsIO subgroup") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - IfMatchAttribute() - GrpsIOMailingListUIDAttribute() + dsl.Attribute("subgroup_id", dsl.String, "Subgroup ID") + dsl.Required("subgroup_id") + dsl.Token("bearer_token", dsl.String) }) - dsl.Error("BadRequest", BadRequestError, "Bad request") - dsl.Error("NotFound", NotFoundError, "Mailing list not found") - dsl.Error("Conflict", ConflictError, "Conflict - ETag mismatch or deletion not allowed") + dsl.Error("NotFound", NotFoundError, "Subgroup not found") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.DELETE("/groupsio/mailing-lists/{uid}") - dsl.Param("version:v") - dsl.Param("uid") + dsl.DELETE("/groupsio/subgroups/{subgroup_id}") + dsl.Param("subgroup_id") dsl.Header("bearer_token:Authorization") - dsl.Header("if_match:If-Match") dsl.Response(dsl.StatusNoContent) - dsl.Response("BadRequest", dsl.StatusBadRequest) dsl.Response("NotFound", dsl.StatusNotFound) - dsl.Response("Conflict", dsl.StatusConflict) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - dsl.Method("get-grpsio-mailing-list-settings", func() { - dsl.Description("Get GroupsIO mailing list settings (writers and auditors)") + dsl.Method("get-groupsio-subgroup-count", func() { + dsl.Description("Get count of GroupsIO subgroups for a project") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - GrpsIOMailingListUIDAttribute() - }) - dsl.Result(func() { - dsl.Attribute("mailing_list_settings", GrpsIOMailingListSettings) - ETagAttribute() - dsl.Required("mailing_list_settings") + dsl.Attribute("project_uid", dsl.String, "LFX v2 project UID", func() { + dsl.Format(dsl.FormatUUID) + }) + dsl.Required("project_uid") + dsl.Token("bearer_token", dsl.String) }) + dsl.Result(GroupsioCountType) dsl.Error("BadRequest", BadRequestError, "Bad request") - dsl.Error("NotFound", NotFoundError, "Mailing list settings not found") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.GET("/groupsio/mailing-lists/{uid}/settings") - dsl.Param("version:v") - dsl.Param("uid") + dsl.GET("/groupsio/subgroups/count") + dsl.Param("project_uid") dsl.Header("bearer_token:Authorization") - dsl.Response(dsl.StatusOK, func() { - dsl.Body("mailing_list_settings") - dsl.Header("etag:ETag") - }) + dsl.Response(dsl.StatusOK) dsl.Response("BadRequest", dsl.StatusBadRequest) - dsl.Response("NotFound", dsl.StatusNotFound) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - dsl.Method("update-grpsio-mailing-list-settings", func() { - dsl.Description("Update GroupsIO mailing list settings (writers and auditors)") + dsl.Method("get-groupsio-subgroup-member-count", func() { + dsl.Description("Get count of members in a GroupsIO subgroup") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - IfMatchAttribute() - GrpsIOMailingListUIDAttribute() - WritersAttribute() - AuditorsAttribute() - dsl.Required("version", "uid") - }) - dsl.Result(GrpsIOMailingListSettings) - dsl.Error("BadRequest", BadRequestError, "Bad request") - dsl.Error("NotFound", NotFoundError, "Mailing list settings not found") - dsl.Error("Conflict", ConflictError, "Conflict - ETag mismatch") + dsl.Attribute("subgroup_id", dsl.String, "Subgroup ID") + dsl.Required("subgroup_id") + dsl.Token("bearer_token", dsl.String) + }) + dsl.Result(GroupsioCountType) + dsl.Error("NotFound", NotFoundError, "Subgroup not found") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.PUT("/groupsio/mailing-lists/{uid}/settings") - dsl.Param("version:v") - dsl.Param("uid") + dsl.GET("/groupsio/subgroups/{subgroup_id}/member_count") + dsl.Param("subgroup_id") dsl.Header("bearer_token:Authorization") - dsl.Header("if_match:If-Match") dsl.Response(dsl.StatusOK) - dsl.Response("BadRequest", dsl.StatusBadRequest) dsl.Response("NotFound", dsl.StatusNotFound) - dsl.Response("Conflict", dsl.StatusConflict) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - // Member management endpoints - dsl.Method("create-grpsio-mailing-list-member", func() { - dsl.Description("Create a new member for a GroupsIO mailing list") - dsl.Security(JWTAuth) + // ---- GroupsIO Member endpoints ---- + dsl.Method("list-groupsio-members", func() { + dsl.Description("List members of a GroupsIO subgroup") + dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - dsl.Attribute("uid", dsl.String, "Mailing list UID", func() { - dsl.Example("f47ac10b-58cc-4372-a567-0e02b2c3d479") - }) - - GrpsIOMemberBaseAttributes() - - dsl.Required("version", "uid", "email") + dsl.Attribute("subgroup_id", dsl.String, "Subgroup ID") + dsl.Required("subgroup_id") + dsl.Token("bearer_token", dsl.String) }) + dsl.Result(GroupsioMemberListType) + dsl.Error("NotFound", NotFoundError, "Subgroup not found") + dsl.Error("InternalServerError", InternalServerError, "Internal server error") + dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") + dsl.HTTP(func() { + dsl.GET("/groupsio/subgroups/{subgroup_id}/members") + dsl.Param("subgroup_id") + dsl.Header("bearer_token:Authorization") + dsl.Response(dsl.StatusOK) + dsl.Response("NotFound", dsl.StatusNotFound) + dsl.Response("InternalServerError", dsl.StatusInternalServerError) + dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) + }) + }) - dsl.Result(GrpsIOMemberFull) - + dsl.Method("add-groupsio-member", func() { + dsl.Description("Add a member to a GroupsIO subgroup") + dsl.Security(JWTAuth) + dsl.Payload(func() { + BearerTokenAttribute() + dsl.Attribute("subgroup_id", dsl.String, "Subgroup ID") + dsl.Extend(GroupsioMemberRequestType) + dsl.Required("subgroup_id") + dsl.Token("bearer_token", dsl.String) + }) + dsl.Result(GroupsioMemberType) dsl.Error("BadRequest", BadRequestError, "Bad request") - dsl.Error("NotFound", NotFoundError, "Mailing list not found") + dsl.Error("NotFound", NotFoundError, "Subgroup not found") dsl.Error("Conflict", ConflictError, "Member already exists") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") - dsl.HTTP(func() { - dsl.POST("/groupsio/mailing-lists/{uid}/members") - dsl.Param("version:v") - dsl.Param("uid") + dsl.POST("/groupsio/subgroups/{subgroup_id}/members") + dsl.Param("subgroup_id") dsl.Header("bearer_token:Authorization") dsl.Response(dsl.StatusCreated) dsl.Response("BadRequest", dsl.StatusBadRequest) @@ -488,130 +458,134 @@ var _ = dsl.Service("mailing-list", func() { }) }) - // Member management endpoints - dsl.Method("get-grpsio-mailing-list-member", func() { - dsl.Description("Get a member of a GroupsIO mailing list by UID") + dsl.Method("get-groupsio-member", func() { + dsl.Description("Get a member of a GroupsIO subgroup by ID") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - GrpsIOMailingListUIDAttribute() - GrpsIOMemberUIDAttribute() - dsl.Required("bearer_token", "version", "uid", "member_uid") - }) - dsl.Result(func() { - dsl.Attribute("member", GrpsIOMemberWithReadonlyAttributes) - ETagAttribute() - dsl.Required("member") + dsl.Attribute("subgroup_id", dsl.String, "Subgroup ID") + dsl.Attribute("member_id", dsl.String, "Member ID") + dsl.Required("subgroup_id", "member_id") + dsl.Token("bearer_token", dsl.String) }) - dsl.Error("BadRequest", BadRequestError, "Bad request") + dsl.Result(GroupsioMemberType) dsl.Error("NotFound", NotFoundError, "Member not found") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.GET("/groupsio/mailing-lists/{uid}/members/{member_uid}") - dsl.Param("version:v") - dsl.Param("uid") - dsl.Param("member_uid") + dsl.GET("/groupsio/subgroups/{subgroup_id}/members/{member_id}") + dsl.Param("subgroup_id") + dsl.Param("member_id") dsl.Header("bearer_token:Authorization") - dsl.Response(dsl.StatusOK, func() { - dsl.Body("member") - dsl.Header("etag:ETag") - }) - dsl.Response("BadRequest", dsl.StatusBadRequest) + dsl.Response(dsl.StatusOK) dsl.Response("NotFound", dsl.StatusNotFound) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - dsl.Method("update-grpsio-mailing-list-member", func() { - dsl.Description("Update a member of a GroupsIO mailing list") + dsl.Method("update-groupsio-member", func() { + dsl.Description("Update a member of a GroupsIO subgroup") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - IfMatchAttribute() - GrpsIOMailingListUIDAttribute() - GrpsIOMemberUIDAttribute() - - GrpsIOMemberUpdateAttributes() - - dsl.Required("bearer_token", "version", "uid", "member_uid", "if_match") + dsl.Attribute("subgroup_id", dsl.String, "Subgroup ID") + dsl.Attribute("member_id", dsl.String, "Member ID") + dsl.Extend(GroupsioMemberRequestType) + dsl.Required("subgroup_id", "member_id") + dsl.Token("bearer_token", dsl.String) }) - dsl.Result(GrpsIOMemberWithReadonlyAttributes) - dsl.Error("BadRequest", BadRequestError, "Bad request - Invalid data or immutable field modification") + dsl.Result(GroupsioMemberType) + dsl.Error("BadRequest", BadRequestError, "Bad request") dsl.Error("NotFound", NotFoundError, "Member not found") - dsl.Error("Conflict", ConflictError, "Conflict - ETag mismatch or validation failure") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.PUT("/groupsio/mailing-lists/{uid}/members/{member_uid}") - dsl.Param("version:v") - dsl.Param("uid") - dsl.Param("member_uid") + dsl.PUT("/groupsio/subgroups/{subgroup_id}/members/{member_id}") + dsl.Param("subgroup_id") + dsl.Param("member_id") dsl.Header("bearer_token:Authorization") - dsl.Header("if_match:If-Match") dsl.Response(dsl.StatusOK) dsl.Response("BadRequest", dsl.StatusBadRequest) dsl.Response("NotFound", dsl.StatusNotFound) - dsl.Response("Conflict", dsl.StatusConflict) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - dsl.Method("delete-grpsio-mailing-list-member", func() { - dsl.Description("Delete a member from a GroupsIO mailing list") + dsl.Method("delete-groupsio-member", func() { + dsl.Description("Delete a member from a GroupsIO subgroup") dsl.Security(JWTAuth) dsl.Payload(func() { BearerTokenAttribute() - VersionAttribute() - IfMatchAttribute() - GrpsIOMailingListUIDAttribute() - GrpsIOMemberUIDAttribute() - dsl.Required("bearer_token", "version", "uid", "member_uid", "if_match") + dsl.Attribute("subgroup_id", dsl.String, "Subgroup ID") + dsl.Attribute("member_id", dsl.String, "Member ID") + dsl.Required("subgroup_id", "member_id") + dsl.Token("bearer_token", dsl.String) }) - dsl.Error("BadRequest", BadRequestError, "Bad request - Cannot remove sole owner") dsl.Error("NotFound", NotFoundError, "Member not found") - dsl.Error("Conflict", ConflictError, "Conflict - ETag mismatch") dsl.Error("InternalServerError", InternalServerError, "Internal server error") dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.DELETE("/groupsio/mailing-lists/{uid}/members/{member_uid}") - dsl.Param("version:v") - dsl.Param("uid") - dsl.Param("member_uid") + dsl.DELETE("/groupsio/subgroups/{subgroup_id}/members/{member_id}") + dsl.Param("subgroup_id") + dsl.Param("member_id") dsl.Header("bearer_token:Authorization") - dsl.Header("if_match:If-Match") dsl.Response(dsl.StatusNoContent) - dsl.Response("BadRequest", dsl.StatusBadRequest) dsl.Response("NotFound", dsl.StatusNotFound) - dsl.Response("Conflict", dsl.StatusConflict) dsl.Response("InternalServerError", dsl.StatusInternalServerError) dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) - // Webhook endpoint for GroupsIO events - dsl.Method("groupsio-webhook", func() { - dsl.Description("Handle GroupsIO webhook events for subgroup and member changes") - - dsl.NoSecurity() // No JWT auth - validated via HMAC signature - - dsl.Payload(GroupsIOWebhookPayload) - - dsl.Result(dsl.Empty) // 204 No Content has no response body + dsl.Method("invite-groupsio-members", func() { + dsl.Description("Invite members to a GroupsIO subgroup by email") + dsl.Security(JWTAuth) + dsl.Payload(func() { + BearerTokenAttribute() + dsl.Attribute("subgroup_id", dsl.String, "Subgroup ID") + dsl.Extend(GroupsioInviteMembersRequestType) + dsl.Required("subgroup_id", "emails") + dsl.Token("bearer_token", dsl.String) + }) + dsl.Error("BadRequest", BadRequestError, "Bad request") + dsl.Error("NotFound", NotFoundError, "Subgroup not found") + dsl.Error("InternalServerError", InternalServerError, "Internal server error") + dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") + dsl.HTTP(func() { + dsl.POST("/groupsio/subgroups/{subgroup_id}/invitemembers") + dsl.Param("subgroup_id") + dsl.Header("bearer_token:Authorization") + dsl.Response(dsl.StatusNoContent) + dsl.Response("BadRequest", dsl.StatusBadRequest) + dsl.Response("NotFound", dsl.StatusNotFound) + dsl.Response("InternalServerError", dsl.StatusInternalServerError) + dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) + }) + }) - dsl.Error("BadRequest", BadRequestError, "Invalid webhook payload or signature") - dsl.Error("Unauthorized", UnauthorizedError, "Invalid webhook signature") + // ---- Other endpoints ---- + dsl.Method("check-groupsio-subscriber", func() { + dsl.Description("Check if an email address is subscribed to a GroupsIO subgroup") + dsl.Security(JWTAuth) + dsl.Payload(func() { + BearerTokenAttribute() + dsl.Extend(GroupsioCheckSubscriberRequestType) + dsl.Required("email", "subgroup_id") + dsl.Token("bearer_token", dsl.String) + }) + dsl.Result(GroupsioCheckSubscriberResponseType) + dsl.Error("BadRequest", BadRequestError, "Bad request") + dsl.Error("InternalServerError", InternalServerError, "Internal server error") + dsl.Error("ServiceUnavailable", ServiceUnavailableError, "Service unavailable") dsl.HTTP(func() { - dsl.POST("/webhooks/groupsio") // Plural webhooks, following meeting service pattern - dsl.Header("signature:x-groupsio-signature") - dsl.Response(dsl.StatusNoContent) // 204 - GroupsIO expects this + dsl.POST("/groupsio/checksubscriber") + dsl.Header("bearer_token:Authorization") + dsl.Response(dsl.StatusOK) dsl.Response("BadRequest", dsl.StatusBadRequest) - dsl.Response("Unauthorized", dsl.StatusUnauthorized) + dsl.Response("InternalServerError", dsl.StatusInternalServerError) + dsl.Response("ServiceUnavailable", dsl.StatusServiceUnavailable) }) }) diff --git a/cmd/mailing-list-api/design/type.go b/cmd/mailing-list-api/design/type.go index be3aa45..55656ec 100644 --- a/cmd/mailing-list-api/design/type.go +++ b/cmd/mailing-list-api/design/type.go @@ -7,125 +7,6 @@ import ( "goa.design/goa/v3/dsl" ) -// UserInfo represents user information including profile details. -var UserInfo = dsl.Type("UserInfo", func() { - dsl.Description("User information including profile details.") - dsl.Attribute("name", dsl.String, "The full name of the user") - dsl.Attribute("email", dsl.String, "The email address of the user", func() { - dsl.Format(dsl.FormatEmail) - }) - dsl.Attribute("username", dsl.String, "The username/LFID of the user") - dsl.Attribute("avatar", dsl.String, "The avatar URL of the user", func() { - dsl.Format(dsl.FormatURI) - }) -}) - -// GrpsIOServiceBaseAttributes is the DSL attributes for a GroupsIO service base. -func GrpsIOServiceBaseAttributes() { - dsl.Attribute("type", dsl.String, "Service type", func() { - dsl.Enum("primary", "formation", "shared") - dsl.Example("primary") - }) - dsl.Attribute("domain", dsl.String, "Service domain", func() { - dsl.Example("lists.project.org") - }) - dsl.Attribute("group_id", dsl.Int64, "GroupsIO group ID", func() { - dsl.Example(12345) - }) - dsl.Attribute("status", dsl.String, "Service status", func() { - dsl.Example("created") - }) - dsl.Attribute("global_owners", dsl.ArrayOf(dsl.String), "List of global owner email addresses (required for primary, forbidden for shared)", func() { - dsl.Elem(func() { - dsl.Format(dsl.FormatEmail) - }) - dsl.Example([]string{"admin@example.com"}) - }) - dsl.Attribute("prefix", dsl.String, "Email prefix (required for formation and shared, forbidden for primary)", func() { - dsl.Example("formation") - }) - dsl.Attribute("parent_service_uid", dsl.String, "Parent primary service UID (automatically set for shared type services)", func() { - dsl.Format(dsl.FormatUUID) - dsl.Example("7cad5a8d-19d0-41a4-81a6-043453daf9ee") - }) - dsl.Attribute("project_slug", dsl.String, "Project slug identifier", func() { - dsl.Format(dsl.FormatRegexp) - dsl.Pattern(`^[a-z][a-z0-9_\-]*[a-z0-9]$`) - dsl.Example("cncf") - }) - dsl.Attribute("project_uid", dsl.String, "LFXv2 Project UID", func() { - dsl.Format(dsl.FormatUUID) - dsl.Example("7cad5a8d-19d0-41a4-81a6-043453daf9ee") - }) - dsl.Attribute("url", dsl.String, "Service URL", func() { - dsl.Format(dsl.FormatURI) - dsl.Example("https://lists.project.org") - }) - dsl.Attribute("group_name", dsl.String, "GroupsIO group name", func() { - dsl.Example("project-name") - }) - dsl.Attribute("public", dsl.Boolean, "Whether the service is publicly accessible", func() { - dsl.Default(false) - dsl.Example(true) - }) - - // Base required fields for all service types - dsl.Required("type", "project_uid") -} - -// GrpsIOServiceSettingsAttributes defines attributes for service settings (user management). -func GrpsIOServiceSettingsAttributes() { - GrpsIOServiceUIDAttribute() - WritersAttribute() - AuditorsAttribute() - LastReviewedAtAttribute() - LastReviewedByAttribute() - LastAuditedByAttribute() - LastAuditedTimeAttribute() - CreatedAtAttribute() - UpdatedAtAttribute() -} - -// GrpsIOServiceSettings is the DSL type for service settings. -var GrpsIOServiceSettings = dsl.Type("grps-io-service-settings", func() { - dsl.Description("A representation of GroupsIO service settings for user management.") - GrpsIOServiceSettingsAttributes() -}) - -// GrpsIOServiceWithReadonlyAttributes is the DSL type for a GroupsIO service with readonly attributes. -var GrpsIOServiceWithReadonlyAttributes = dsl.Type("grps-io-service-with-readonly-attributes", func() { - dsl.Description("A representation of GroupsIO services with readonly attributes.") - - GrpsIOServiceUIDAttribute() - GrpsIOServiceBaseAttributes() - ProjectNameAttribute() - CreatedAtAttribute() - UpdatedAtAttribute() - WritersAttribute() - AuditorsAttribute() -}) - -// GrpsIOServiceUIDAttribute is the DSL attribute for service UID. -func GrpsIOServiceUIDAttribute() { - dsl.Attribute("uid", dsl.String, "Service UID -- unique identifier for the service", func() { - dsl.Example("7cad5a8d-19d0-41a4-81a6-043453daf9ee") - dsl.Format(dsl.FormatUUID) - }) -} - -// GrpsIOServiceFull is the DSL type for a complete service representation with all attributes. -var GrpsIOServiceFull = dsl.Type("grps-io-service-full", func() { - dsl.Description("A complete representation of GroupsIO services with all attributes including access control and audit trail.") - - GrpsIOServiceUIDAttribute() - GrpsIOServiceBaseAttributes() - ProjectNameAttribute() - CreatedAtAttribute() - UpdatedAtAttribute() - WritersAttribute() - AuditorsAttribute() -}) - // BearerTokenAttribute is the DSL attribute for bearer token. func BearerTokenAttribute() { dsl.Token("bearer_token", dsl.String, func() { @@ -134,100 +15,6 @@ func BearerTokenAttribute() { }) } -// LastReviewedAtAttribute is the DSL attribute for last review timestamp. -func LastReviewedAtAttribute() { - dsl.Attribute("last_reviewed_at", dsl.String, "The timestamp when the service was last reviewed in RFC3339 format", func() { - dsl.Format(dsl.FormatDateTime) - dsl.Example("2025-08-04T09:00:00Z") - }) -} - -// LastReviewedByAttribute is the DSL attribute for last review user. -func LastReviewedByAttribute() { - dsl.Attribute("last_reviewed_by", dsl.String, "The user ID who last reviewed this service", func() { - dsl.Example("user_id_12345") - }) -} - -// ProjectNameAttribute is the DSL attribute for project name (read-only). -func ProjectNameAttribute() { - dsl.Attribute("project_name", dsl.String, "Project name (read-only)", func() { - dsl.Example("Cloud Native Computing Foundation") - }) -} - -// ProjectSlugAttribute is the DSL attribute for project slug (read-only). -func ProjectSlugAttribute() { - dsl.Attribute("project_slug", dsl.String, "Project slug identifier (read-only)", func() { - dsl.Format(dsl.FormatRegexp) - dsl.Pattern(`^[a-z][a-z0-9_\-]*[a-z0-9]$`) - dsl.Example("cncf") - }) -} - -// WritersAttribute is the DSL attribute for writers (UserInfo array). -func WritersAttribute() { - dsl.Attribute("writers", dsl.ArrayOf(UserInfo), "Manager users who can edit/modify this resource") -} - -// AuditorsAttribute is the DSL attribute for auditors (UserInfo array). -func AuditorsAttribute() { - dsl.Attribute("auditors", dsl.ArrayOf(UserInfo), "Auditor users who can audit this resource") -} - -// LastAuditedByAttribute is the DSL attribute for last audited by user. -func LastAuditedByAttribute() { - dsl.Attribute("last_audited_by", dsl.String, "The user ID who last audited the service", func() { - dsl.Example("user_id_12345") - }) -} - -// LastAuditedTimeAttribute is the DSL attribute for last audit timestamp. -func LastAuditedTimeAttribute() { - dsl.Attribute("last_audited_time", dsl.String, "The timestamp when the service was last audited", func() { - dsl.Format(dsl.FormatDateTime) - dsl.Example("2023-05-10T09:15:00Z") - }) -} - -// IfMatchAttribute is the DSL attribute for If-Match header (for conditional requests). -func IfMatchAttribute() { - dsl.Attribute("if_match", dsl.String, "If-Match header value for conditional requests", func() { - dsl.Example("123") - }) -} - -// VersionAttribute is the DSL attribute for API version. -func VersionAttribute() { - dsl.Attribute("version", dsl.String, "Version of the API", func() { - dsl.Example("1") - dsl.Enum("1") - }) -} - -// ETagAttribute is the DSL attribute for ETag header. -func ETagAttribute() { - dsl.Attribute("etag", dsl.String, "ETag header value", func() { - dsl.Example("123") - }) -} - -// CreatedAtAttribute is the DSL attribute for creation timestamp. -func CreatedAtAttribute() { - dsl.Attribute("created_at", dsl.String, "The timestamp when the service was created (read-only)", func() { - dsl.Format(dsl.FormatDateTime) - dsl.Example("2023-01-15T10:30:00Z") - }) -} - -// UpdatedAtAttribute is the DSL attribute for update timestamp. -func UpdatedAtAttribute() { - dsl.Attribute("updated_at", dsl.String, "The timestamp when the service was last updated (read-only)", func() { - dsl.Format(dsl.FormatDateTime) - dsl.Example("2023-06-20T14:45:30Z") - }) -} - // BadRequestError is the DSL type for a bad request error. var BadRequestError = dsl.Type("bad-request-error", func() { dsl.Attribute("message", dsl.String, "Error message", func() { @@ -276,358 +63,168 @@ var ServiceUnavailableError = dsl.Type("service-unavailable-error", func() { dsl.Required("message") }) -// Committee represents a committee associated with a mailing list. -// Multiple committees can be associated with a single mailing list, -// with OR logic for access control (any committee grants access). -var Committee = dsl.Type("Committee", func() { - dsl.Description("Committee associated with a mailing list") - dsl.Attribute("uid", dsl.String, "Committee UUID", func() { +// GroupsioServiceType represents an ITX GroupsIO service. +var GroupsioServiceType = dsl.Type("groupsio-service", func() { + dsl.Description("A GroupsIO service managed via ITX") + dsl.Attribute("id", dsl.String, "Service ID") + dsl.Attribute("project_uid", dsl.String, "LFX v2 project UID", func() { dsl.Format(dsl.FormatUUID) dsl.Example("7cad5a8d-19d0-41a4-81a6-043453daf9ee") }) - dsl.Attribute("name", dsl.String, "Committee name (read-only, populated by server)") - dsl.Attribute("allowed_voting_statuses", dsl.ArrayOf(dsl.String), "Committee member voting statuses that determine which members are synced", func() { - dsl.Elem(func() { - dsl.Enum("Voting Rep", "Alternate Voting Rep", "Observer", "Emeritus", "None") - }) - dsl.Example([]string{"Voting Rep", "Alternate Voting Rep"}) + dsl.Attribute("type", dsl.String, "Service type", func() { + dsl.Example("primary") }) - dsl.Required("uid") // Only uid is required on input; name is server-populated + dsl.Attribute("group_id", dsl.Int64, "GroupsIO group ID") + dsl.Attribute("domain", dsl.String, "Service domain") + dsl.Attribute("prefix", dsl.String, "Email prefix") + dsl.Attribute("status", dsl.String, "Service status") + dsl.Attribute("created_at", dsl.String, "Creation timestamp") + dsl.Attribute("updated_at", dsl.String, "Last update timestamp") }) -// GrpsIOMailingListSettingsAttributes defines attributes for mailing list settings (user management). -func GrpsIOMailingListSettingsAttributes() { - GrpsIOMailingListUIDAttribute() - WritersAttribute() - AuditorsAttribute() - LastReviewedAtAttribute() - LastReviewedByAttribute() - LastAuditedByAttribute() - LastAuditedTimeAttribute() - CreatedAtAttribute() - UpdatedAtAttribute() -} - -// GrpsIOMailingListSettings is the DSL type for mailing list settings. -var GrpsIOMailingListSettings = dsl.Type("grps-io-mailing-list-settings", func() { - dsl.Description("A representation of GroupsIO mailing list settings for user management.") - GrpsIOMailingListSettingsAttributes() -}) - -// GrpsIOMailingListBaseAttributes defines attributes for mailing list requests (CREATE/UPDATE) - excludes project_uid. -func GrpsIOMailingListBaseAttributes() { - dsl.Attribute("group_name", dsl.String, "Mailing list group name", func() { - dsl.Example("technical-steering-committee") - dsl.Pattern(`^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$`) - dsl.MinLength(3) - dsl.MaxLength(34) - }) - dsl.Attribute("group_id", dsl.Int64, "Mailing list group ID", func() { - dsl.Example(12345) - dsl.Minimum(0) - }) - dsl.Attribute("public", dsl.Boolean, "Whether the mailing list is publicly accessible", func() { - dsl.Default(false) - dsl.Example(false) - }) - dsl.Attribute("type", dsl.String, "Mailing list type", func() { - // TODO: Future PR - Verify if Groups.io supports "custom" type and update enum accordingly - // If supported: Add "custom" to enum below and update validation in grpsio_mailing_list.go line 103 - // If not supported: Remove TypeCustom from grpsio_mailing_list.go and ValidMailingListTypes() - dsl.Enum("announcement", "discussion_moderated", "discussion_open") - dsl.Example("discussion_moderated") - }) - dsl.Attribute("audience_access", dsl.String, "Controls who can discover and join this mailing list", func() { - dsl.Enum("public", "approval_required", "invite_only") - dsl.Default("public") - dsl.Example("public") - dsl.Description("public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.") - }) - dsl.Attribute("committees", dsl.ArrayOf(Committee), "Committees associated with this mailing list (OR logic for access control)") - dsl.Attribute("description", dsl.String, "Mailing list description (11-500 characters)", func() { - dsl.MinLength(11) - dsl.MaxLength(500) - dsl.Example("Technical steering committee discussions") - }) - dsl.Attribute("title", dsl.String, "Mailing list title", func() { - dsl.Example("Technical Steering Committee") - dsl.MinLength(5) - dsl.MaxLength(100) - }) - dsl.Attribute("subject_tag", dsl.String, "Subject tag prefix", func() { - dsl.Example("[TSC]") - dsl.MaxLength(50) - }) - dsl.Attribute("service_uid", dsl.String, "Service UUID", func() { +// GroupsioServiceRequestType represents a create/update request for a GroupsIO service. +var GroupsioServiceRequestType = dsl.Type("groupsio-service-request", func() { + dsl.Description("Request body for creating or updating a GroupsIO service") + dsl.Attribute("project_uid", dsl.String, "LFX v2 project UID", func() { dsl.Format(dsl.FormatUUID) dsl.Example("7cad5a8d-19d0-41a4-81a6-043453daf9ee") }) - dsl.Attribute("subscriber_count", dsl.Int, "Number of subscribers in this mailing list (read-only, maintained by service)", func() { - dsl.Example(42) - dsl.Minimum(0) - }) - -} - -// GrpsIOMailingListUIDAttribute is the DSL attribute for mailing list UID. -func GrpsIOMailingListUIDAttribute() { - dsl.Attribute("uid", dsl.String, "Mailing list UID -- unique identifier for the mailing list", func() { - dsl.Example("7cad5a8d-19d0-41a4-81a6-043453daf9ee") - dsl.Format(dsl.FormatUUID) - }) -} - -// GrpsIOMailingListFull is the DSL type for a complete mailing list representation with all attributes. -var GrpsIOMailingListFull = dsl.Type("grps-io-mailing-list-full", func() { - dsl.Description("A complete representation of GroupsIO mailing lists with all attributes including access control and audit trail.") - - GrpsIOMailingListUIDAttribute() - GrpsIOMailingListBaseAttributes() - - // project_uid only appears in responses (inherited from parent service) - dsl.Attribute("project_uid", dsl.String, "LFXv2 Project UID (inherited from parent service)", func() { - dsl.Format(dsl.FormatUUID) - dsl.Example("7cad5a8d-19d0-41a4-81a6-043453daf9ee") + dsl.Attribute("type", dsl.String, "Service type", func() { + dsl.Enum("primary", "formation", "shared") + dsl.Example("primary") }) - - // Settings fields (writers and auditors) - WritersAttribute() - AuditorsAttribute() - - ProjectNameAttribute() - ProjectSlugAttribute() - CreatedAtAttribute() - UpdatedAtAttribute() + dsl.Attribute("group_id", dsl.Int64, "GroupsIO group ID") + dsl.Attribute("domain", dsl.String, "Service domain") + dsl.Attribute("prefix", dsl.String, "Email prefix") + dsl.Attribute("status", dsl.String, "Service status") }) -// GrpsIOMailingListWithReadonlyAttributes is the DSL type for a mailing list with readonly attributes. -var GrpsIOMailingListWithReadonlyAttributes = dsl.Type("grps-io-mailing-list-with-readonly-attributes", func() { - dsl.Description("A representation of GroupsIO mailing lists with readonly attributes.") - - GrpsIOMailingListUIDAttribute() - GrpsIOMailingListBaseAttributes() - - // project_uid only appears in responses (inherited from parent service) - dsl.Attribute("project_uid", dsl.String, "LFXv2 Project UID (inherited from parent service)", func() { - dsl.Format(dsl.FormatUUID) - dsl.Example("7cad5a8d-19d0-41a4-81a6-043453daf9ee") - }) - - ProjectNameAttribute() - ProjectSlugAttribute() - CreatedAtAttribute() - UpdatedAtAttribute() +// GroupsioServiceListType represents a list of GroupsIO services. +var GroupsioServiceListType = dsl.Type("groupsio-service-list", func() { + dsl.Description("List of GroupsIO services") + dsl.Attribute("items", dsl.ArrayOf(GroupsioServiceType), "List of services") + dsl.Attribute("total", dsl.Int, "Total count") }) -// GrpsIOMemberBaseAttributes defines common attributes for member requests and responses. -func GrpsIOMemberBaseAttributes() { - dsl.Attribute("username", dsl.String, "Member username", func() { - dsl.MaxLength(255) - dsl.Example("jdoe") - }) - - dsl.Attribute("first_name", dsl.String, "Member first name", func() { - dsl.MinLength(1) - dsl.MaxLength(255) - dsl.Example("John") - }) - - dsl.Attribute("last_name", dsl.String, "Member last name", func() { - dsl.MinLength(1) - dsl.MaxLength(255) - dsl.Example("Doe") - }) - - dsl.Attribute("email", dsl.String, "Member email address", func() { - dsl.Format(dsl.FormatEmail) - dsl.Example("john.doe@example.com") - }) - - dsl.Attribute("organization", dsl.String, "Member organization", func() { - dsl.MaxLength(255) - dsl.Example("Example Corp") - }) - - dsl.Attribute("job_title", dsl.String, "Member job title", func() { - dsl.MaxLength(255) - dsl.Example("Software Engineer") - }) - - dsl.Attribute("member_type", dsl.String, "Member type", func() { - dsl.Enum("committee", "direct") - dsl.Default("direct") - }) - - dsl.Attribute("delivery_mode", dsl.String, "Email delivery mode", func() { - dsl.Enum("normal", "digest", "none") - dsl.Default("normal") - }) - - dsl.Attribute("mod_status", dsl.String, "Moderation status", func() { - dsl.Enum("none", "moderator", "owner") - dsl.Default("none") - }) - - dsl.Attribute("last_reviewed_at", dsl.String, "Last reviewed timestamp", func() { - dsl.Format(dsl.FormatDateTime) - dsl.Example("2023-01-15T14:30:00Z") - }) - - dsl.Attribute("last_reviewed_by", dsl.String, "Last reviewed by user ID", func() { - dsl.Example("admin@example.com") - }) -} - -// GrpsIOMemberUIDAttribute is the DSL attribute for member UID. -func GrpsIOMemberUIDAttribute() { - dsl.Attribute("member_uid", dsl.String, "Member UID -- unique identifier for the member", func() { - dsl.Example("f47ac10b-58cc-4372-a567-0e02b2c3d479") +// GroupsioSubgroupType represents an ITX GroupsIO subgroup (mailing list). +var GroupsioSubgroupType = dsl.Type("groupsio-subgroup", func() { + dsl.Description("A GroupsIO subgroup (mailing list) managed via ITX") + dsl.Attribute("id", dsl.String, "Subgroup ID") + dsl.Attribute("project_uid", dsl.String, "LFX v2 project UID", func() { dsl.Format(dsl.FormatUUID) + dsl.Example("7cad5a8d-19d0-41a4-81a6-043453daf9ee") }) -} - -// GrpsIOMemberWithReadonlyAttributes is the DSL type for a member with readonly attributes. -var GrpsIOMemberWithReadonlyAttributes = dsl.Type("grps-io-member-with-readonly-attributes", func() { - dsl.Description("A representation of GroupsIO mailing list members with readonly attributes.") - - dsl.Attribute("uid", dsl.String, "Member UID", func() { - dsl.Format(dsl.FormatUUID) - dsl.Example("f47ac10b-58cc-4372-a567-0e02b2c3d479") - }) - - dsl.Attribute("mailing_list_uid", dsl.String, "Mailing list UID", func() { + dsl.Attribute("committee_uid", dsl.String, "LFX v2 committee UID", func() { dsl.Format(dsl.FormatUUID) dsl.Example("7cad5a8d-19d0-41a4-81a6-043453daf9ee") }) - - GrpsIOMemberBaseAttributes() - - dsl.Attribute("status", dsl.String, "Member status", func() { - dsl.Example("pending") - }) - - dsl.Attribute("member_id", dsl.Int64, "Groups.io member ID", func() { - dsl.Example(12345) - }) - - dsl.Attribute("group_id", dsl.Int64, "Groups.io group ID", func() { - dsl.Example(67890) - }) - - CreatedAtAttribute() - UpdatedAtAttribute() - WritersAttribute() - AuditorsAttribute() + dsl.Attribute("group_id", dsl.Int64, "GroupsIO group ID") + dsl.Attribute("name", dsl.String, "Subgroup name") + dsl.Attribute("description", dsl.String, "Subgroup description") + dsl.Attribute("type", dsl.String, "Subgroup type") + dsl.Attribute("audience_access", dsl.String, "Audience access setting") + dsl.Attribute("created_at", dsl.String, "Creation timestamp") + dsl.Attribute("updated_at", dsl.String, "Last update timestamp") }) -// GrpsIOMemberFull is the DSL type for a complete member response. -var GrpsIOMemberFull = dsl.Type("grps-io-member-full", func() { - dsl.Description("A complete representation of a GroupsIO mailing list member with all attributes.") - - dsl.Attribute("uid", dsl.String, "Member UID", func() { +// GroupsioSubgroupRequestType represents a create/update request for a GroupsIO subgroup. +var GroupsioSubgroupRequestType = dsl.Type("groupsio-subgroup-request", func() { + dsl.Description("Request body for creating or updating a GroupsIO subgroup") + dsl.Attribute("project_uid", dsl.String, "LFX v2 project UID", func() { dsl.Format(dsl.FormatUUID) - dsl.Example("f47ac10b-58cc-4372-a567-0e02b2c3d479") + dsl.Example("7cad5a8d-19d0-41a4-81a6-043453daf9ee") }) - - dsl.Attribute("mailing_list_uid", dsl.String, "Mailing list UID", func() { + dsl.Attribute("committee_uid", dsl.String, "LFX v2 committee UID", func() { dsl.Format(dsl.FormatUUID) dsl.Example("7cad5a8d-19d0-41a4-81a6-043453daf9ee") }) - - GrpsIOMemberBaseAttributes() - - dsl.Attribute("status", dsl.String, "Member status", func() { - dsl.Example("pending") - }) - - dsl.Attribute("member_id", dsl.Int64, "Groups.io member ID", func() { - dsl.Example(12345) - }) - - dsl.Attribute("group_id", dsl.Int64, "Groups.io group ID", func() { - dsl.Example(67890) - }) - - CreatedAtAttribute() - UpdatedAtAttribute() - WritersAttribute() - AuditorsAttribute() - - dsl.Required( - "uid", "mailing_list_uid", "first_name", "last_name", "email", - "member_type", "delivery_mode", "mod_status", "status", - "created_at", "updated_at", - ) + dsl.Attribute("group_id", dsl.Int64, "GroupsIO group ID") + dsl.Attribute("name", dsl.String, "Subgroup name") + dsl.Attribute("description", dsl.String, "Subgroup description") + dsl.Attribute("type", dsl.String, "Subgroup type") + dsl.Attribute("audience_access", dsl.String, "Audience access setting") }) -// GrpsIOMemberUpdateAttributes defines mutable attributes for member updates (excludes immutable fields like email) -func GrpsIOMemberUpdateAttributes() { - dsl.Attribute("username", dsl.String, "Member username", func() { - dsl.MaxLength(255) - dsl.Example("jdoe") - }) +// GroupsioSubgroupListType represents a list of GroupsIO subgroups. +var GroupsioSubgroupListType = dsl.Type("groupsio-subgroup-list", func() { + dsl.Description("List of GroupsIO subgroups") + dsl.Attribute("items", dsl.ArrayOf(GroupsioSubgroupType), "List of subgroups") + dsl.Attribute("total", dsl.Int, "Total count") +}) - dsl.Attribute("first_name", dsl.String, "Member first name", func() { - dsl.MinLength(1) - dsl.MaxLength(255) - dsl.Example("John") - }) +// GroupsioCountType represents a count response. +var GroupsioCountType = dsl.Type("groupsio-count", func() { + dsl.Description("Count response") + dsl.Attribute("count", dsl.Int, "Count value") + dsl.Required("count") +}) - dsl.Attribute("last_name", dsl.String, "Member last name", func() { - dsl.MinLength(1) - dsl.MaxLength(255) - dsl.Example("Doe") +// GroupsioMemberType represents an ITX GroupsIO member. +var GroupsioMemberType = dsl.Type("groupsio-member", func() { + dsl.Description("A member of a GroupsIO subgroup") + dsl.Attribute("id", dsl.String, "Member ID") + dsl.Attribute("subgroup_id", dsl.String, "Subgroup ID") + dsl.Attribute("email", dsl.String, "Member email address", func() { + dsl.Format(dsl.FormatEmail) }) + dsl.Attribute("name", dsl.String, "Member display name") + dsl.Attribute("first_name", dsl.String, "Member first name") + dsl.Attribute("last_name", dsl.String, "Member last name") + dsl.Attribute("mod_status", dsl.String, "Moderation status") + dsl.Attribute("delivery_mode", dsl.String, "Email delivery mode") + dsl.Attribute("status", dsl.String, "Member status") + dsl.Attribute("created_at", dsl.String, "Creation timestamp") + dsl.Attribute("updated_at", dsl.String, "Last update timestamp") +}) - dsl.Attribute("organization", dsl.String, "Member organization", func() { - dsl.MaxLength(255) - dsl.Example("Example Corp") +// GroupsioMemberRequestType represents a create/update request for a GroupsIO member. +var GroupsioMemberRequestType = dsl.Type("groupsio-member-request", func() { + dsl.Description("Request body for adding or updating a GroupsIO member") + dsl.Attribute("email", dsl.String, "Member email address", func() { + dsl.Format(dsl.FormatEmail) }) - - dsl.Attribute("job_title", dsl.String, "Member job title", func() { - dsl.MaxLength(255) - dsl.Example("Software Engineer") + dsl.Attribute("name", dsl.String, "Member display name") + dsl.Attribute("mod_status", dsl.String, "Moderation status", func() { + dsl.Enum("none", "moderator", "owner") }) - dsl.Attribute("delivery_mode", dsl.String, "Email delivery mode", func() { dsl.Enum("normal", "digest", "none") - dsl.Default("normal") - }) - - dsl.Attribute("mod_status", dsl.String, "Moderation status", func() { - dsl.Enum("none", "moderator", "owner") - dsl.Default("none") }) -} +}) -// GroupsIOWebhookPayload represents the webhook event payload from Groups.io -var GroupsIOWebhookPayload = dsl.Type("groupsio-webhook-payload", func() { - dsl.Description("Webhook event payload from Groups.io") +// GroupsioMemberListType represents a list of GroupsIO members. +var GroupsioMemberListType = dsl.Type("groupsio-member-list", func() { + dsl.Description("List of GroupsIO members") + dsl.Attribute("items", dsl.ArrayOf(GroupsioMemberType), "List of members") + dsl.Attribute("total", dsl.Int, "Total count") +}) - // Event type - matches production event names - dsl.Attribute("action", dsl.String, "The type of webhook event", func() { - dsl.Example("created_subgroup") - dsl.Enum( - "created_subgroup", - "deleted_subgroup", - "added_member", - "removed_member", - "ban_members", - ) - }) +// GroupsioInviteMembersRequestType represents an invite members request. +var GroupsioInviteMembersRequestType = dsl.Type("groupsio-invite-members-request", func() { + dsl.Description("Request body for inviting members to a GroupsIO subgroup") + dsl.Attribute("emails", dsl.ArrayOf(dsl.String), "Email addresses to invite") + dsl.Required("emails") +}) - // Event-specific data - matches production payload structure - dsl.Attribute("group", dsl.Any, "Group information for subgroup events", func() { - dsl.Description("Contains subgroup data from Groups.io") - }) - dsl.Attribute("member_info", dsl.Any, "Member information for member events", func() { - dsl.Description("Contains member data from Groups.io") +// GroupsioCheckSubscriberRequestType represents a check subscriber request. +var GroupsioCheckSubscriberRequestType = dsl.Type("groupsio-check-subscriber-request", func() { + dsl.Description("Request body for checking if an email is subscribed") + dsl.Attribute("email", dsl.String, "Email address to check", func() { + dsl.Format(dsl.FormatEmail) }) - dsl.Attribute("extra", dsl.String, "Extra data field (subgroup suffix)") - dsl.Attribute("extra_id", dsl.Int, "Extra ID field (subgroup ID for deletion)") + dsl.Attribute("subgroup_id", dsl.String, "Subgroup ID") + dsl.Required("email", "subgroup_id") +}) - // Signature from header - dsl.Attribute("signature", dsl.String, "HMAC-SHA1 base64 signature for verification") +// GroupsioCheckSubscriberResponseType represents a check subscriber response. +var GroupsioCheckSubscriberResponseType = dsl.Type("groupsio-check-subscriber-response", func() { + dsl.Description("Response for check subscriber request") + dsl.Attribute("subscribed", dsl.Boolean, "Whether the email is subscribed") + dsl.Required("subscribed") +}) - dsl.Required("action", "signature") +// GroupsioProjectsResponseType represents a list of projects with services. +var GroupsioProjectsResponseType = dsl.Type("groupsio-projects-response", func() { + dsl.Description("Projects that have GroupsIO services") + dsl.Attribute("projects", dsl.ArrayOf(dsl.String), "List of project identifiers") }) diff --git a/cmd/mailing-list-api/http.go b/cmd/mailing-list-api/http.go index f850148..bb500f6 100644 --- a/cmd/mailing-list-api/http.go +++ b/cmd/mailing-list-api/http.go @@ -64,8 +64,6 @@ func handleHTTPServer(ctx context.Context, host string, mailingListServiceEndpoi var handler http.Handler = mux // Add RequestID middleware first handler = middleware.RequestIDMiddleware()(handler) - // Add GroupsIO webhook body capture middleware - handler = middleware.GrpsIOWebhookBodyCaptureMiddleware()(handler) // Add Authorization middleware handler = middleware.AuthorizationMiddleware()(handler) if dbg { diff --git a/cmd/mailing-list-api/mailing_list_sync.go b/cmd/mailing-list-api/mailing_list_sync.go index 6bbe9db..05b557e 100644 --- a/cmd/mailing-list-api/mailing_list_sync.go +++ b/cmd/mailing-list-api/mailing_list_sync.go @@ -1,115 +1,6 @@ // Copyright The Linux Foundation and each contributor to LFX. // SPDX-License-Identifier: MIT +// Package main provides the mailing list API service. +// Mailing list sync has been removed as part of the ITX proxy refactoring. package main - -import ( - "context" - "fmt" - "log/slog" - "sync" - "time" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/cmd/mailing-list-api/service" - internalService "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/service" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/nats-io/nats.go" -) - -// handleMailingListSync sets up and starts mailing list event subscriptions -// Pattern: mirrors handleCommitteeSync - does both setup and start in one function -func handleMailingListSync(ctx context.Context, wg *sync.WaitGroup) error { - slog.InfoContext(ctx, "starting mailing list sync") - - // Get dependencies - mailingListReader := service.GrpsIOReader(ctx) - memberWriter := service.GrpsIOWriterOrchestrator(ctx) // Use orchestrator for message publishing - memberReader := service.GrpsIOReader(ctx) - entityReader := service.EntityAttributeRetriever(ctx) - natsClient := service.GetNATSClient(ctx) - - // Create committee sync service (used by mailing list sync) - committeeSyncService := internalService.NewCommitteeSyncService( - mailingListReader, - memberWriter, - memberReader, - entityReader, - ) - - // Create mailing list sync service - syncService := internalService.NewMailingListSyncService( - committeeSyncService, - ) - - // Subscribe to mailing list event subjects - subjects := []string{ - constants.MailingListCreatedSubject, - constants.MailingListUpdatedSubject, - } - - for _, subject := range subjects { - // Capture loop variable for closure - subject := subject - _, subErr := natsClient.QueueSubscribe( - subject, - constants.MailingListAPIQueue, - func(msg *nats.Msg) { - // Check if service is shutting down - select { - case <-ctx.Done(): - slog.InfoContext(ctx, "rejecting message - service shutting down", - "subject", msg.Subject) - if msg.Reply != "" { - if nakErr := msg.Nak(); nakErr != nil { - slog.ErrorContext(ctx, "failed to nak message during shutdown", "error", nakErr) - } - } - return - default: - // Continue processing - } - - // Create fresh context with timeout for this message - // Not derived from shutdown context to avoid cancellation issues - msgCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - // Process message with proper error handling and acknowledgment - if handleErr := syncService.HandleMessage(msgCtx, msg); handleErr != nil { - slog.ErrorContext(msgCtx, "failed to process mailing list event, will retry", - "error", handleErr, - "subject", msg.Subject) - if msg.Reply != "" { - if nakErr := msg.Nak(); nakErr != nil { - slog.ErrorContext(msgCtx, "failed to nak message", "error", nakErr) - } - } - } else if msg.Reply != "" { - // Success - acknowledge message - if ackErr := msg.Ack(); ackErr != nil { - slog.ErrorContext(msgCtx, "failed to ack message", "error", ackErr) - } - } - }, - ) - if subErr != nil { - return fmt.Errorf("failed to subscribe to %s: %w", subject, subErr) - } - slog.InfoContext(ctx, "subscribed to mailing list event", - "subject", subject, - "queue", constants.MailingListAPIQueue) - } - - slog.InfoContext(ctx, "mailing list sync started successfully") - - // Graceful shutdown (mirrors handleCommitteeSync) - wg.Add(1) - go func() { - defer wg.Done() - <-ctx.Done() - slog.InfoContext(ctx, "shutting down mailing list sync") - // NATS client cleanup handled by existing Close() in main shutdown - }() - - return nil -} diff --git a/cmd/mailing-list-api/main.go b/cmd/mailing-list-api/main.go index 1f3f986..dbf0c8d 100644 --- a/cmd/mailing-list-api/main.go +++ b/cmd/mailing-list-api/main.go @@ -1,6 +1,8 @@ // Copyright The Linux Foundation and each contributor to LFX. // SPDX-License-Identifier: MIT +// Package main is the ITX mailing list proxy service that provides a lightweight proxy +// layer to the ITX GroupsIO API. package main import ( @@ -17,34 +19,21 @@ import ( "github.com/linuxfoundation/lfx-v2-mailing-list-service/cmd/mailing-list-api/service" mailinglistservice "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" logging "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/log" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/utils" + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/proxy" "goa.design/clue/debug" ) -// Build-time variables set via ldflags -var ( - Version = "dev" - BuildTime = "unknown" - GitCommit = "unknown" -) - const ( - defaultPort = "8080" - // gracefulShutdownSeconds should be higher than NATS client - // request timeout, and lower than the pod or liveness probe's - // terminationGracePeriodSeconds. + defaultPort = "8080" gracefulShutdownSeconds = 25 ) func init() { - // slog is the standard library logger, we use it to log errors and logging.InitStructureLogConfig() } func main() { - // Define command line flags, add any other flag required to configure the - // service. var ( dbgF = flag.Bool("d", false, "enable debug logging") port = flag.String("p", defaultPort, "listen port") @@ -57,73 +46,43 @@ func main() { flag.Parse() ctx := context.Background() - - // Set up OpenTelemetry SDK. - // Command-line/environment OTEL_SERVICE_VERSION takes precedence over - // the build-time Version variable. - otelConfig := utils.OTelConfigFromEnv() - if otelConfig.ServiceVersion == "" { - otelConfig.ServiceVersion = Version - } - otelShutdown, err := utils.SetupOTelSDKWithConfig(ctx, otelConfig) - if err != nil { - slog.ErrorContext(ctx, "error setting up OpenTelemetry SDK", "error", err) - os.Exit(1) - } - // Handle shutdown properly so nothing leaks. - defer func() { - shutdownCtx, cancel := context.WithTimeout(context.Background(), gracefulShutdownSeconds*time.Second) - defer cancel() - if shutdownErr := otelShutdown(shutdownCtx); shutdownErr != nil { - slog.ErrorContext(ctx, "error shutting down OpenTelemetry SDK", "error", shutdownErr) - } - }() - - slog.InfoContext(ctx, "Starting mailing list service", + slog.InfoContext(ctx, "Starting ITX mailing list proxy service", "bind", *bind, "http-port", *port, "graceful-shutdown-seconds", gracefulShutdownSeconds, - "version", Version, - "build-time", BuildTime, - "git-commit", GitCommit, ) - // Validate provider configuration before initializing dependencies - service.ValidateProviderConfiguration(ctx) - - // Initialize dependencies using provider pattern - storage := service.GrpsIOReaderWriter(ctx) + // Initialize authentication service authService := service.AuthService(ctx) - // Create orchestrators using provider functions - readGrpsIOService := service.GrpsIOReaderOrchestrator(ctx) - writeGrpsIOService := service.GrpsIOWriterOrchestrator(ctx) + // Initialize ID mapper for v1/v2 ID conversions + idMapper := service.IDMapper(ctx) + + // Initialize ITX proxy client + itxConfig := service.ITXProxyConfig() + itxClient := proxy.NewClient(itxConfig) - // Initialize GroupsIO webhook dependencies - grpsioWebhookValidator := service.GrpsIOWebhookValidator(ctx) - grpsioWebhookProcessor := service.GrpsIOWebhookProcessor(ctx) + // Initialize ITX GroupsIO services + svcService := service.GroupsioServiceService(ctx, itxClient, idMapper) + subgroupService := service.GroupsioSubgroupService(ctx, itxClient, idMapper) + memberService := service.GroupsioMemberService(ctx, itxClient) - // Initialize the mailing list service with service management endpoints - mailingListServiceSvc := service.NewMailingList( + slog.InfoContext(ctx, "ITX proxy client initialized") + + // Create the mailing list API service + mailingListSvc := service.NewMailingListAPI( authService, - readGrpsIOService, - writeGrpsIOService, - storage, - grpsioWebhookValidator, - grpsioWebhookProcessor, + svcService, + subgroupService, + memberService, ) - // Wrap the services in endpoints that can be invoked from other services - // potentially running in different processes. - mailingListServiceEndpoints := mailinglistservice.NewEndpoints(mailingListServiceSvc) + // Wrap the services in endpoints + mailingListServiceEndpoints := mailinglistservice.NewEndpoints(mailingListSvc) mailingListServiceEndpoints.Use(debug.LogPayloads()) - // Create channel used by both the signal handler and server goroutines - // to notify the main goroutine when to stop the server. errc := make(chan error) - // Setup interrupt handler. This optional step configures the process so - // that SIGINT and SIGTERM signals cause the services to stop gracefully. go func() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) @@ -133,7 +92,6 @@ func main() { var wg sync.WaitGroup ctx, cancel := context.WithCancel(ctx) - // Start the servers and send errors (if any) to the error channel. addr := ":" + *port if *bind != "*" { addr = *bind + ":" + *port @@ -141,31 +99,15 @@ func main() { handleHTTPServer(ctx, addr, mailingListServiceEndpoints, &wg, errc, *dbgF) - // Start committee sync - critical for data consistency - if err := handleCommitteeSync(ctx, &wg); err != nil { - slog.ErrorContext(ctx, "FATAL: failed to start committee sync - service cannot maintain data consistency", "error", err) - os.Exit(1) - } - - // Start mailing list sync - critical for data consistency - if err := handleMailingListSync(ctx, &wg); err != nil { - slog.ErrorContext(ctx, "FATAL: failed to start mailing list sync - service cannot maintain data consistency", "error", err) - os.Exit(1) - } - - // Wait for signal. slog.InfoContext(ctx, "received shutdown signal, stopping servers", "signal", <-errc, ) - // Send cancellation signal to the goroutines. cancel() - // Create a timeout context for graceful shutdown shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), gracefulShutdownSeconds*time.Second) defer shutdownCancel() - // Wait for all goroutines to finish with timeout done := make(chan struct{}) go func() { wg.Wait() diff --git a/cmd/mailing-list-api/service/error.go b/cmd/mailing-list-api/service/error.go deleted file mode 100644 index 75fd3b6..0000000 --- a/cmd/mailing-list-api/service/error.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "log/slog" - - mailinglistservice "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" - lfxerrors "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" -) - -func wrapError(ctx context.Context, err error) error { - if err == nil { - return nil - } - - f := func(err error) error { - switch e := err.(type) { - case lfxerrors.Validation: - return &mailinglistservice.BadRequestError{Message: e.Error()} - case lfxerrors.NotFound: - return &mailinglistservice.NotFoundError{Message: e.Error()} - case lfxerrors.Conflict: - return &mailinglistservice.ConflictError{Message: e.Error()} - case lfxerrors.ServiceUnavailable: - return &mailinglistservice.ServiceUnavailableError{Message: e.Error()} - default: - return &mailinglistservice.InternalServerError{Message: "An internal server error occurred"} - } - } - - slog.ErrorContext(ctx, "request failed", - "error", err, - ) - return f(err) -} diff --git a/cmd/mailing-list-api/service/grpsio_webhook_test.go b/cmd/mailing-list-api/service/grpsio_webhook_test.go deleted file mode 100644 index b65364f..0000000 --- a/cmd/mailing-list-api/service/grpsio_webhook_test.go +++ /dev/null @@ -1,508 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "crypto/hmac" - "crypto/sha1" - "encoding/base64" - "encoding/json" - "fmt" - "testing" - - mailinglistservice "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/groupsio" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/mock" - svcinternal "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/service" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const testWebhookSecret = "test-secret-123" - -// Helper function to generate HMAC-SHA1 signature with base64 encoding (matches production) -func generateSignature(body []byte, secret string) string { - mac := hmac.New(sha1.New, []byte(secret)) - mac.Write(body) - return base64.StdEncoding.EncodeToString(mac.Sum(nil)) -} - -// createProductionWebhookPayload simulates production GroupsIO payload structure after GOA decoding -func createProductionWebhookPayload(t *testing.T, bodyBytes []byte, signature string) *mailinglistservice.GroupsioWebhookPayload { - t.Helper() - var eventJSON map[string]interface{} - err := json.Unmarshal(bodyBytes, &eventJSON) - require.NoError(t, err, "failed to unmarshal webhook payload in test helper") - - payload := &mailinglistservice.GroupsioWebhookPayload{ - Signature: signature, - } - - // Extract action (required field) - if action, ok := eventJSON["action"].(string); ok { - payload.Action = action - } - - // Populate fields based on event type (simulating GOA decoder) - if group, ok := eventJSON["group"]; ok { - payload.Group = group - } - if memberInfo, ok := eventJSON["member_info"]; ok { - payload.MemberInfo = memberInfo - } - if extra, ok := eventJSON["extra"].(string); ok { - payload.Extra = &extra - } - if extraID, ok := eventJSON["extra_id"].(float64); ok { - id := int(extraID) - payload.ExtraID = &id - } - - return payload -} - -// TestWebhook_ValidSignature tests webhook with valid HMAC-SHA1 signature -func TestWebhook_ValidSignature(t *testing.T) { - // Create webhook service with mock dependencies - mockRepo := mock.NewMockRepository() - grpsioWebhookValidator := groupsio.NewGrpsIOWebhookValidator(testWebhookSecret) - grpsioWebhookProcessor := svcinternal.NewGrpsIOWebhookProcessor( - svcinternal.WithServiceReader(mockRepo), - svcinternal.WithMailingListReader(mockRepo), - svcinternal.WithMailingListWriter(mock.NewMockGrpsIOMailingListWriter(mockRepo)), - ) - - svc := NewMailingList( - mock.NewMockAuthService(), - nil, // grpsIOReaderOrchestrator - nil, // grpsIOWriterOrchestrator - nil, // storage - grpsioWebhookValidator, - grpsioWebhookProcessor, - ) - - // Create production-like webhook event payload (use float64 for numbers as JSON unmarshaling does) - event := map[string]interface{}{ - "action": "created_subgroup", - "group": map[string]interface{}{ - "id": float64(142630), - "name": "lfx-test-1759227480", - "parent_group_id": float64(141234), - "title": "LFX Test Project", - "type": "sub_group", - "privacy": "private", - }, - "extra": "developers", - } - bodyBytes, err := json.Marshal(event) - require.NoError(t, err) - - // Generate valid base64 signature - signature := generateSignature(bodyBytes, testWebhookSecret) - - // Create context with body - ctx := context.WithValue(context.Background(), constants.GrpsIOWebhookBodyContextKey, bodyBytes) - - // Create payload with GOA-style populated fields (not Body field) - payload := createProductionWebhookPayload(t, bodyBytes, signature) - - // Call webhook handler and verify success (204 No Content) - err = svc.(*mailingListService).GroupsioWebhook(ctx, payload) - require.NoError(t, err, "webhook handler should succeed") - - // Note: In this test, the subgroup won't actually be created in the mock repository - // because there's no parent service configured. The webhook succeeds but logs a warning. - // For full end-to-end testing with state verification, see other tests that set up - // the complete service hierarchy. -} - -// TestWebhook_InvalidSignature tests webhook with invalid signature -func TestWebhook_InvalidSignature(t *testing.T) { - mockRepo := mock.NewMockRepository() - grpsioWebhookValidator := groupsio.NewGrpsIOWebhookValidator(testWebhookSecret) - grpsioWebhookProcessor := svcinternal.NewGrpsIOWebhookProcessor( - svcinternal.WithServiceReader(mockRepo), - svcinternal.WithMailingListReader(mockRepo), - svcinternal.WithMailingListWriter(mock.NewMockGrpsIOMailingListWriter(mockRepo)), - ) - - svc := NewMailingList( - mock.NewMockAuthService(), - nil, - nil, - nil, - grpsioWebhookValidator, - grpsioWebhookProcessor, - ) - - // Production-like payload with float64 for numbers - event := map[string]interface{}{ - "action": "created_subgroup", - "group": map[string]interface{}{ - "id": float64(142630), - "name": "lfx-test-1759227480", - "parent_group_id": float64(141234), - }, - "extra": "developers", - } - bodyBytes, err := json.Marshal(event) - require.NoError(t, err) - - // Invalid signature - invalidSignature := "invalid-signature-12345" - - ctx := context.WithValue(context.Background(), constants.GrpsIOWebhookBodyContextKey, bodyBytes) - - // Create payload with populated fields - payload := createProductionWebhookPayload(t, bodyBytes, invalidSignature) - - err = svc.(*mailingListService).GroupsioWebhook(ctx, payload) - - // Verify 401 Unauthorized - require.Error(t, err) - unauthorizedErr, ok := err.(*mailinglistservice.UnauthorizedError) - assert.True(t, ok, "Expected UnauthorizedError") - assert.Equal(t, "invalid webhook signature", unauthorizedErr.Message) -} - -// TestWebhook_MissingBody tests webhook without body in context -func TestWebhook_MissingBody(t *testing.T) { - grpsioWebhookValidator := mock.NewMockGrpsIOWebhookValidator() - grpsioWebhookProcessor := svcinternal.NewGrpsIOWebhookProcessor() - - svc := NewMailingList( - mock.NewMockAuthService(), - nil, - nil, - nil, - grpsioWebhookValidator, - grpsioWebhookProcessor, - ) - - // Context without body - ctx := context.Background() - - // Create minimal payload - event := map[string]interface{}{ - "action": "created_subgroup", - } - bodyBytes, _ := json.Marshal(event) - - payload := createProductionWebhookPayload(t, bodyBytes, "some-signature") - - err := svc.(*mailingListService).GroupsioWebhook(ctx, payload) - - // Verify 400 Bad Request - require.Error(t, err) - badRequestErr, ok := err.(*mailinglistservice.BadRequestError) - assert.True(t, ok, "Expected BadRequestError") - assert.Equal(t, "missing webhook body", badRequestErr.Message) -} - -// TestWebhook_MalformedPayload tests webhook with malformed event (missing action field) -func TestWebhook_MalformedPayload(t *testing.T) { - grpsioWebhookValidator := mock.NewMockGrpsIOWebhookValidator() - grpsioWebhookProcessor := svcinternal.NewGrpsIOWebhookProcessor() - - svc := NewMailingList( - mock.NewMockAuthService(), - nil, - nil, - nil, - grpsioWebhookValidator, - grpsioWebhookProcessor, - ) - - // Malformed event: missing required 'action' field - event := map[string]interface{}{ - "group": map[string]interface{}{ - "id": float64(123), - "name": "test-group", - "parent_group_id": float64(456), - }, - } - bodyBytes, _ := json.Marshal(event) - - ctx := context.WithValue(context.Background(), constants.GrpsIOWebhookBodyContextKey, bodyBytes) - - // Create payload without action field - payload := &mailinglistservice.GroupsioWebhookPayload{ - Signature: "some-signature", - Group: event["group"], - } - - err := svc.(*mailingListService).GroupsioWebhook(ctx, payload) - - // Should fail validation or return error - // Handler returns nil (204) to prevent retries, but logs error internally - assert.NoError(t, err) -} - -// TestWebhook_UnsupportedEventType tests webhook with unsupported event type -func TestWebhook_UnsupportedEventType(t *testing.T) { - grpsioWebhookValidator := groupsio.NewGrpsIOWebhookValidator(testWebhookSecret) - grpsioWebhookProcessor := svcinternal.NewGrpsIOWebhookProcessor() - - svc := NewMailingList( - mock.NewMockAuthService(), - nil, - nil, - nil, - grpsioWebhookValidator, - grpsioWebhookProcessor, - ) - - event := map[string]interface{}{ - "action": "unsupported_event", - } - bodyBytes, err := json.Marshal(event) - require.NoError(t, err) - - signature := generateSignature(bodyBytes, testWebhookSecret) - - ctx := context.WithValue(context.Background(), constants.GrpsIOWebhookBodyContextKey, bodyBytes) - - payload := createProductionWebhookPayload(t, bodyBytes, signature) - - err = svc.(*mailingListService).GroupsioWebhook(ctx, payload) - - // Verify 400 Bad Request - require.Error(t, err) - badRequestErr, ok := err.(*mailinglistservice.BadRequestError) - assert.True(t, ok, "Expected BadRequestError") - assert.Contains(t, badRequestErr.Message, "unsupported event type") -} - -// TestWebhook_MockMode tests webhook in mock mode (always valid) -func TestWebhook_MockMode(t *testing.T) { - mockRepo := mock.NewMockRepository() - grpsioWebhookValidator := mock.NewMockGrpsIOWebhookValidator() - grpsioWebhookProcessor := svcinternal.NewGrpsIOWebhookProcessor( - svcinternal.WithServiceReader(mockRepo), - svcinternal.WithMailingListReader(mockRepo), - svcinternal.WithMailingListWriter(mock.NewMockGrpsIOMailingListWriter(mockRepo)), - ) - - svc := NewMailingList( - mock.NewMockAuthService(), - nil, - nil, - nil, - grpsioWebhookValidator, - grpsioWebhookProcessor, - ) - - // Production-like payload with float64 for numbers - event := map[string]interface{}{ - "action": "created_subgroup", - "group": map[string]interface{}{ - "id": float64(142630), - "name": "lfx-test-1759227480", - "parent_group_id": float64(141234), - }, - "extra": "developers", - } - bodyBytes, err := json.Marshal(event) - require.NoError(t, err) - - // No signature needed in mock mode - ctx := context.WithValue(context.Background(), constants.GrpsIOWebhookBodyContextKey, bodyBytes) - - payload := createProductionWebhookPayload(t, bodyBytes, "any-signature-works-in-mock") - - err = svc.(*mailingListService).GroupsioWebhook(ctx, payload) - - // Verify success (204 No Content) - assert.NoError(t, err) -} - -// TestWebhook_AllEventTypes tests all 5 supported event types -func TestWebhook_AllEventTypes(t *testing.T) { - eventTypes := []string{ - "created_subgroup", - "deleted_subgroup", - "added_member", - "removed_member", - "ban_members", - } - - mockRepo := mock.NewMockRepository() - grpsioWebhookValidator := mock.NewMockGrpsIOWebhookValidator() - grpsioWebhookProcessor := svcinternal.NewGrpsIOWebhookProcessor( - svcinternal.WithServiceReader(mockRepo), - svcinternal.WithMailingListReader(mockRepo), - svcinternal.WithMailingListWriter(mock.NewMockGrpsIOMailingListWriter(mockRepo)), - svcinternal.WithMemberReader(mockRepo), - svcinternal.WithMemberWriter(mock.NewMockGrpsIOMemberWriter(mockRepo)), - ) - - svc := NewMailingList( - mock.NewMockAuthService(), - nil, - nil, - nil, - grpsioWebhookValidator, - grpsioWebhookProcessor, - ) - - for _, eventType := range eventTypes { - t.Run(eventType, func(t *testing.T) { - event := map[string]interface{}{ - "action": eventType, - } - - // Add required fields based on event type (use float64 for numbers) - switch eventType { - case "created_subgroup": - event["group"] = map[string]interface{}{ - "id": float64(142630), - "name": "lfx-test-1759227480", - "parent_group_id": float64(141234), - "title": "LFX Test Project", - } - event["extra"] = "developers" - case "deleted_subgroup": - event["extra_id"] = float64(789) - case "added_member", "removed_member", "ban_members": - event["member_info"] = map[string]interface{}{ - "id": float64(12345), - "user_id": float64(67890), - "group_id": float64(142630), - "group_name": "lfx-test-1759227480+developers", - "email": "user@example.com", - "status": "approved", - "object": "member", - } - } - - bodyBytes, err := json.Marshal(event) - require.NoError(t, err) - - ctx := context.WithValue(context.Background(), constants.GrpsIOWebhookBodyContextKey, bodyBytes) - - payload := createProductionWebhookPayload(t, bodyBytes, "mock-signature") - - err = svc.(*mailingListService).GroupsioWebhook(ctx, payload) - - // All should succeed (204 No Content) - assert.NoError(t, err, "Event type %s should succeed", eventType) - }) - } -} - -// TestWebhook_CreatedSubgroupMissingGroupInfo tests created_subgroup with missing group info -func TestWebhook_CreatedSubgroupMissingGroupInfo(t *testing.T) { - grpsioWebhookValidator := mock.NewMockGrpsIOWebhookValidator() - grpsioWebhookProcessor := svcinternal.NewGrpsIOWebhookProcessor() - - svc := NewMailingList( - mock.NewMockAuthService(), - nil, - nil, - nil, - grpsioWebhookValidator, - grpsioWebhookProcessor, - ) - - event := map[string]interface{}{ - "action": "created_subgroup", - // Missing group field - "extra": "developers", - } - bodyBytes, err := json.Marshal(event) - require.NoError(t, err) - - ctx := context.WithValue(context.Background(), constants.GrpsIOWebhookBodyContextKey, bodyBytes) - - payload := createProductionWebhookPayload(t, bodyBytes, "mock-signature") - - err = svc.(*mailingListService).GroupsioWebhook(ctx, payload) - - // Should still return 204 (logged error, but always returns nil to prevent retries) - assert.NoError(t, err) -} - -// TestWebhook_MemberEventMissingMemberInfo tests member events with missing member info -func TestWebhook_MemberEventMissingMemberInfo(t *testing.T) { - mockRepo := mock.NewMockRepository() - grpsioWebhookValidator := mock.NewMockGrpsIOWebhookValidator() - grpsioWebhookProcessor := svcinternal.NewGrpsIOWebhookProcessor( - svcinternal.WithServiceReader(mockRepo), - svcinternal.WithMailingListReader(mockRepo), - svcinternal.WithMailingListWriter(mock.NewMockGrpsIOMailingListWriter(mockRepo)), - svcinternal.WithMemberReader(mockRepo), - svcinternal.WithMemberWriter(mock.NewMockGrpsIOMemberWriter(mockRepo)), - ) - - svc := NewMailingList( - mock.NewMockAuthService(), - nil, - nil, - nil, - grpsioWebhookValidator, - grpsioWebhookProcessor, - ) - - event := map[string]interface{}{ - "action": "added_member", - // Missing member_info field - } - bodyBytes, err := json.Marshal(event) - require.NoError(t, err) - - ctx := context.WithValue(context.Background(), constants.GrpsIOWebhookBodyContextKey, bodyBytes) - - payload := createProductionWebhookPayload(t, bodyBytes, "mock-signature") - - err = svc.(*mailingListService).GroupsioWebhook(ctx, payload) - - // Should still return 204 (logged error, but always returns nil to prevent retries) - assert.NoError(t, err) -} - -// TestWebhook_TransientErrorRetriesExhausted tests that transient errors (non-validation) -// result in 500 InternalServerError after retries are exhausted -func TestWebhook_TransientErrorRetriesExhausted(t *testing.T) { - grpsioWebhookValidator := mock.NewMockGrpsIOWebhookValidator() - - // Create a processor that always returns a transient (non-validation) error - grpsioWebhookProcessor := mock.NewMockGrpsIOWebhookProcessorWithError( - fmt.Errorf("database connection timeout"), // Transient error - ) - - svc := NewMailingList( - mock.NewMockAuthService(), - nil, - nil, - nil, - grpsioWebhookValidator, - grpsioWebhookProcessor, - ) - - event := map[string]interface{}{ - "action": "created_subgroup", - "group": map[string]interface{}{ - "id": float64(142630), - "name": "lfx-test-1759227480", - "parent_group_id": float64(141234), - }, - "extra": "developers", - } - bodyBytes, err := json.Marshal(event) - require.NoError(t, err) - - ctx := context.WithValue(context.Background(), constants.GrpsIOWebhookBodyContextKey, bodyBytes) - - payload := createProductionWebhookPayload(t, bodyBytes, "mock-signature") - - err = svc.(*mailingListService).GroupsioWebhook(ctx, payload) - - // Should return 500 InternalServerError after retries exhausted - require.Error(t, err) - internalServerErr, ok := err.(*mailinglistservice.InternalServerError) - assert.True(t, ok, "Expected InternalServerError, got %T", err) - assert.Contains(t, internalServerErr.Message, "webhook processing failed") -} diff --git a/cmd/mailing-list-api/service/mailing_list_api.go b/cmd/mailing-list-api/service/mailing_list_api.go new file mode 100644 index 0000000..247e866 --- /dev/null +++ b/cmd/mailing-list-api/service/mailing_list_api.go @@ -0,0 +1,390 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +// Package service implements the mailing list API service, proxying to the ITX GroupsIO API. +package service + +import ( + "context" + "errors" + "log/slog" + + mailinglist "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain" + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/models" + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" + itxsvc "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/service/itx" + "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" + + "goa.design/goa/v3/security" +) + +// mailingListAPI implements the generated mailinglist.Service interface by proxying to ITX. +type mailingListAPI struct { + auth port.Authenticator + serviceService *itxsvc.GroupsioServiceService + subgroupService *itxsvc.GroupsioSubgroupService + memberService *itxsvc.GroupsioMemberService +} + +// NewMailingListAPI returns the mailing list API service implementation. +func NewMailingListAPI( + auth port.Authenticator, + serviceService *itxsvc.GroupsioServiceService, + subgroupService *itxsvc.GroupsioSubgroupService, + memberService *itxsvc.GroupsioMemberService, +) mailinglist.Service { + return &mailingListAPI{ + auth: auth, + serviceService: serviceService, + subgroupService: subgroupService, + memberService: memberService, + } +} + +// JWTAuth implements the authorization logic for the JWT security scheme. +func (s *mailingListAPI) JWTAuth(ctx context.Context, token string, _ *security.JWTScheme) (context.Context, error) { + principal, err := s.auth.ParsePrincipal(ctx, token, slog.Default()) + if err != nil { + return ctx, err + } + return context.WithValue(ctx, constants.PrincipalContextID, principal), nil +} + +// Livez implements the liveness probe endpoint. +func (s *mailingListAPI) Livez(_ context.Context) ([]byte, error) { + return []byte("OK"), nil +} + +// Readyz implements the readiness probe endpoint. +func (s *mailingListAPI) Readyz(_ context.Context) ([]byte, error) { + return []byte("OK"), nil +} + +// ---- GroupsIO Service endpoints ---- + +func (s *mailingListAPI) ListGroupsioServices(ctx context.Context, p *mailinglist.ListGroupsioServicesPayload) (*mailinglist.GroupsioServiceList, error) { + projectUID := "" + if p.ProjectUID != nil { + projectUID = *p.ProjectUID + } + resp, err := s.serviceService.ListServices(ctx, projectUID) + if err != nil { + return nil, mapDomainError(err) + } + items := make([]*mailinglist.GroupsioService, len(resp.Items)) + for i, svc := range resp.Items { + items[i] = convertService(svc) + } + total := resp.Total + return &mailinglist.GroupsioServiceList{Items: items, Total: &total}, nil +} + +func (s *mailingListAPI) CreateGroupsioService(ctx context.Context, p *mailinglist.CreateGroupsioServicePayload) (*mailinglist.GroupsioService, error) { + req := &models.GroupsioServiceRequest{ + ProjectID: strVal(p.ProjectUID), + Type: strVal(p.Type), + GroupID: int64Val(p.GroupID), + Domain: strVal(p.Domain), + Prefix: strVal(p.Prefix), + Status: strVal(p.Status), + } + resp, err := s.serviceService.CreateService(ctx, req) + if err != nil { + return nil, mapDomainError(err) + } + return convertService(resp), nil +} + +func (s *mailingListAPI) GetGroupsioService(ctx context.Context, p *mailinglist.GetGroupsioServicePayload) (*mailinglist.GroupsioService, error) { + resp, err := s.serviceService.GetService(ctx, p.ServiceID) + if err != nil { + return nil, mapDomainError(err) + } + return convertService(resp), nil +} + +func (s *mailingListAPI) UpdateGroupsioService(ctx context.Context, p *mailinglist.UpdateGroupsioServicePayload) (*mailinglist.GroupsioService, error) { + req := &models.GroupsioServiceRequest{ + ProjectID: strVal(p.ProjectUID), + Type: strVal(p.Type), + GroupID: int64Val(p.GroupID), + Domain: strVal(p.Domain), + Prefix: strVal(p.Prefix), + Status: strVal(p.Status), + } + resp, err := s.serviceService.UpdateService(ctx, p.ServiceID, req) + if err != nil { + return nil, mapDomainError(err) + } + return convertService(resp), nil +} + +func (s *mailingListAPI) DeleteGroupsioService(ctx context.Context, p *mailinglist.DeleteGroupsioServicePayload) error { + return mapDomainError(s.serviceService.DeleteService(ctx, p.ServiceID)) +} + +func (s *mailingListAPI) GetGroupsioServiceProjects(ctx context.Context, _ *mailinglist.GetGroupsioServiceProjectsPayload) (*mailinglist.GroupsioProjectsResponse, error) { + resp, err := s.serviceService.GetProjects(ctx) + if err != nil { + return nil, mapDomainError(err) + } + return &mailinglist.GroupsioProjectsResponse{Projects: resp.Projects}, nil +} + +func (s *mailingListAPI) FindParentGroupsioService(ctx context.Context, p *mailinglist.FindParentGroupsioServicePayload) (*mailinglist.GroupsioService, error) { + resp, err := s.serviceService.FindParentService(ctx, p.ProjectUID) + if err != nil { + return nil, mapDomainError(err) + } + return convertService(resp), nil +} + +// ---- GroupsIO Subgroup endpoints ---- + +func (s *mailingListAPI) ListGroupsioSubgroups(ctx context.Context, p *mailinglist.ListGroupsioSubgroupsPayload) (*mailinglist.GroupsioSubgroupList, error) { + projectUID := "" + if p.ProjectUID != nil { + projectUID = *p.ProjectUID + } + committeeUID := "" + if p.CommitteeUID != nil { + committeeUID = *p.CommitteeUID + } + resp, err := s.subgroupService.ListSubgroups(ctx, projectUID, committeeUID) + if err != nil { + return nil, mapDomainError(err) + } + items := make([]*mailinglist.GroupsioSubgroup, len(resp.Items)) + for i, sg := range resp.Items { + items[i] = convertSubgroup(sg) + } + total := resp.Total + return &mailinglist.GroupsioSubgroupList{Items: items, Total: &total}, nil +} + +func (s *mailingListAPI) CreateGroupsioSubgroup(ctx context.Context, p *mailinglist.CreateGroupsioSubgroupPayload) (*mailinglist.GroupsioSubgroup, error) { + req := &models.GroupsioSubgroupRequest{ + ProjectID: strVal(p.ProjectUID), + CommitteeID: strVal(p.CommitteeUID), + GroupID: int64Val(p.GroupID), + Name: strVal(p.Name), + Description: strVal(p.Description), + Type: strVal(p.Type), + AudienceAccess: strVal(p.AudienceAccess), + } + resp, err := s.subgroupService.CreateSubgroup(ctx, req) + if err != nil { + return nil, mapDomainError(err) + } + return convertSubgroup(resp), nil +} + +func (s *mailingListAPI) GetGroupsioSubgroup(ctx context.Context, p *mailinglist.GetGroupsioSubgroupPayload) (*mailinglist.GroupsioSubgroup, error) { + resp, err := s.subgroupService.GetSubgroup(ctx, p.SubgroupID) + if err != nil { + return nil, mapDomainError(err) + } + return convertSubgroup(resp), nil +} + +func (s *mailingListAPI) UpdateGroupsioSubgroup(ctx context.Context, p *mailinglist.UpdateGroupsioSubgroupPayload) (*mailinglist.GroupsioSubgroup, error) { + req := &models.GroupsioSubgroupRequest{ + ProjectID: strVal(p.ProjectUID), + CommitteeID: strVal(p.CommitteeUID), + GroupID: int64Val(p.GroupID), + Name: strVal(p.Name), + Description: strVal(p.Description), + Type: strVal(p.Type), + AudienceAccess: strVal(p.AudienceAccess), + } + resp, err := s.subgroupService.UpdateSubgroup(ctx, p.SubgroupID, req) + if err != nil { + return nil, mapDomainError(err) + } + return convertSubgroup(resp), nil +} + +func (s *mailingListAPI) DeleteGroupsioSubgroup(ctx context.Context, p *mailinglist.DeleteGroupsioSubgroupPayload) error { + return mapDomainError(s.subgroupService.DeleteSubgroup(ctx, p.SubgroupID)) +} + +func (s *mailingListAPI) GetGroupsioSubgroupCount(ctx context.Context, p *mailinglist.GetGroupsioSubgroupCountPayload) (*mailinglist.GroupsioCount, error) { + resp, err := s.subgroupService.GetSubgroupCount(ctx, p.ProjectUID) + if err != nil { + return nil, mapDomainError(err) + } + return &mailinglist.GroupsioCount{Count: resp.Count}, nil +} + +func (s *mailingListAPI) GetGroupsioSubgroupMemberCount(ctx context.Context, p *mailinglist.GetGroupsioSubgroupMemberCountPayload) (*mailinglist.GroupsioCount, error) { + resp, err := s.subgroupService.GetMemberCount(ctx, p.SubgroupID) + if err != nil { + return nil, mapDomainError(err) + } + return &mailinglist.GroupsioCount{Count: resp.Count}, nil +} + +// ---- GroupsIO Member endpoints ---- + +func (s *mailingListAPI) ListGroupsioMembers(ctx context.Context, p *mailinglist.ListGroupsioMembersPayload) (*mailinglist.GroupsioMemberList, error) { + resp, err := s.memberService.ListMembers(ctx, p.SubgroupID) + if err != nil { + return nil, mapDomainError(err) + } + items := make([]*mailinglist.GroupsioMember, len(resp.Items)) + for i, m := range resp.Items { + items[i] = convertMember(m) + } + total := resp.Total + return &mailinglist.GroupsioMemberList{Items: items, Total: &total}, nil +} + +func (s *mailingListAPI) AddGroupsioMember(ctx context.Context, p *mailinglist.AddGroupsioMemberPayload) (*mailinglist.GroupsioMember, error) { + req := &models.GroupsioMemberRequest{ + Email: strVal(p.Email), + Name: strVal(p.Name), + ModStatus: strVal(p.ModStatus), + DeliveryMode: strVal(p.DeliveryMode), + } + resp, err := s.memberService.AddMember(ctx, p.SubgroupID, req) + if err != nil { + return nil, mapDomainError(err) + } + return convertMember(resp), nil +} + +func (s *mailingListAPI) GetGroupsioMember(ctx context.Context, p *mailinglist.GetGroupsioMemberPayload) (*mailinglist.GroupsioMember, error) { + resp, err := s.memberService.GetMember(ctx, p.SubgroupID, p.MemberID) + if err != nil { + return nil, mapDomainError(err) + } + return convertMember(resp), nil +} + +func (s *mailingListAPI) UpdateGroupsioMember(ctx context.Context, p *mailinglist.UpdateGroupsioMemberPayload) (*mailinglist.GroupsioMember, error) { + req := &models.GroupsioMemberRequest{ + Email: strVal(p.Email), + Name: strVal(p.Name), + ModStatus: strVal(p.ModStatus), + DeliveryMode: strVal(p.DeliveryMode), + } + resp, err := s.memberService.UpdateMember(ctx, p.SubgroupID, p.MemberID, req) + if err != nil { + return nil, mapDomainError(err) + } + return convertMember(resp), nil +} + +func (s *mailingListAPI) DeleteGroupsioMember(ctx context.Context, p *mailinglist.DeleteGroupsioMemberPayload) error { + return mapDomainError(s.memberService.DeleteMember(ctx, p.SubgroupID, p.MemberID)) +} + +func (s *mailingListAPI) InviteGroupsioMembers(ctx context.Context, p *mailinglist.InviteGroupsioMembersPayload) error { + req := &models.GroupsioInviteMembersRequest{Emails: p.Emails} + return mapDomainError(s.memberService.InviteMembers(ctx, p.SubgroupID, req)) +} + +func (s *mailingListAPI) CheckGroupsioSubscriber(ctx context.Context, p *mailinglist.CheckGroupsioSubscriberPayload) (*mailinglist.GroupsioCheckSubscriberResponse, error) { + req := &models.GroupsioCheckSubscriberRequest{ + Email: p.Email, + SubgroupID: p.SubgroupID, + } + resp, err := s.memberService.CheckSubscriber(ctx, req) + if err != nil { + return nil, mapDomainError(err) + } + return &mailinglist.GroupsioCheckSubscriberResponse{Subscribed: resp.Subscribed}, nil +} + +// ---- Helpers ---- + +func mapDomainError(err error) error { + if err == nil { + return nil + } + var domErr *domain.DomainError + if !errors.As(err, &domErr) { + return &mailinglist.InternalServerError{Message: err.Error()} + } + switch domErr.Type { + case domain.ErrorTypeNotFound: + return &mailinglist.NotFoundError{Message: domErr.Message} + case domain.ErrorTypeValidation: + return &mailinglist.BadRequestError{Message: domErr.Message} + case domain.ErrorTypeConflict: + return &mailinglist.ConflictError{Message: domErr.Message} + case domain.ErrorTypeUnavailable: + return &mailinglist.ServiceUnavailableError{Message: domErr.Message} + default: + return &mailinglist.InternalServerError{Message: domErr.Message} + } +} + +func strVal(s *string) string { + if s == nil { + return "" + } + return *s +} + +func int64Val(v *int64) int64 { + if v == nil { + return 0 + } + return *v +} + +func convertService(svc *models.GroupsioService) *mailinglist.GroupsioService { + if svc == nil { + return nil + } + return &mailinglist.GroupsioService{ + ID: &svc.ID, + ProjectUID: &svc.ProjectID, + Type: &svc.Type, + GroupID: &svc.GroupID, + Domain: &svc.Domain, + Prefix: &svc.Prefix, + Status: &svc.Status, + CreatedAt: &svc.CreatedAt, + UpdatedAt: &svc.UpdatedAt, + } +} + +func convertSubgroup(sg *models.GroupsioSubgroup) *mailinglist.GroupsioSubgroup { + if sg == nil { + return nil + } + return &mailinglist.GroupsioSubgroup{ + ID: &sg.ID, + ProjectUID: &sg.ProjectID, + CommitteeUID: &sg.CommitteeID, + GroupID: &sg.GroupID, + Name: &sg.Name, + Description: &sg.Description, + Type: &sg.Type, + AudienceAccess: &sg.AudienceAccess, + CreatedAt: &sg.CreatedAt, + UpdatedAt: &sg.UpdatedAt, + } +} + +func convertMember(m *models.GroupsioMember) *mailinglist.GroupsioMember { + if m == nil { + return nil + } + return &mailinglist.GroupsioMember{ + ID: &m.ID, + SubgroupID: &m.SubgroupID, + Email: &m.Email, + Name: &m.Name, + FirstName: &m.FirstName, + LastName: &m.LastName, + ModStatus: &m.ModStatus, + DeliveryMode: &m.DeliveryMode, + Status: &m.Status, + CreatedAt: &m.CreatedAt, + UpdatedAt: &m.UpdatedAt, + } +} diff --git a/cmd/mailing-list-api/service/mailing_list_service.go b/cmd/mailing-list-api/service/mailing_list_service.go deleted file mode 100644 index 1fbf57e..0000000 --- a/cmd/mailing-list-api/service/mailing_list_service.go +++ /dev/null @@ -1,829 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -// Package service implements the mailing list service business logic and endpoints. -package service - -import ( - "context" - stderrors "errors" - "fmt" - "log/slog" - "time" - - mailinglistservice "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/service" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/redaction" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/utils" - - "github.com/google/uuid" - "goa.design/goa/v3/security" -) - -// mailingListService is the implementation of the mailing list service. -type mailingListService struct { - auth port.Authenticator - grpsIOReaderOrchestrator service.GrpsIOReader - grpsIOWriterOrchestrator service.GrpsIOWriter - storage port.GrpsIOReaderWriter - - // GroupsIO Webhook dependencies - grpsioWebhookValidator port.GrpsIOWebhookValidator - grpsioWebhookProcessor port.GrpsIOWebhookProcessor -} - -// NewMailingList returns the mailing list service implementation. -func NewMailingList( - auth port.Authenticator, - grpsIOReaderOrchestrator service.GrpsIOReader, - grpsIOWriterOrchestrator service.GrpsIOWriter, - storage port.GrpsIOReaderWriter, - grpsioWebhookValidator port.GrpsIOWebhookValidator, - grpsioWebhookProcessor port.GrpsIOWebhookProcessor, -) mailinglistservice.Service { - return &mailingListService{ - auth: auth, - grpsIOReaderOrchestrator: grpsIOReaderOrchestrator, - grpsIOWriterOrchestrator: grpsIOWriterOrchestrator, - storage: storage, - grpsioWebhookValidator: grpsioWebhookValidator, - grpsioWebhookProcessor: grpsioWebhookProcessor, - } -} - -// JWTAuth implements the authorization logic for service "mailing-list" -// for the "jwt" security scheme. -func (s *mailingListService) JWTAuth(ctx context.Context, token string, _ *security.JWTScheme) (context.Context, error) { - // Parse the Heimdall-authorized principal from the token - principal, err := s.auth.ParsePrincipal(ctx, token, slog.Default()) - if err != nil { - return ctx, err - } - - // Return a new context containing the principal as a value - return context.WithValue(ctx, constants.PrincipalContextID, principal), nil -} - -// Livez implements the livez endpoint for liveness probes. -func (s *mailingListService) Livez(ctx context.Context) ([]byte, error) { - slog.DebugContext(ctx, "liveness check completed successfully") - return []byte("OK"), nil -} - -// Readyz implements the readyz endpoint for readiness probes. -func (s *mailingListService) Readyz(ctx context.Context) ([]byte, error) { - // Check NATS readiness - if err := s.storage.IsReady(ctx); err != nil { - slog.ErrorContext(ctx, "service not ready", "error", err) - return nil, err // This will automatically return ServiceUnavailable - } - - return []byte("OK\n"), nil -} - -// GetGrpsioService retrieves a single service by ID -func (s *mailingListService) GetGrpsioService(ctx context.Context, payload *mailinglistservice.GetGrpsioServicePayload) (result *mailinglistservice.GetGrpsioServiceResult, err error) { - slog.DebugContext(ctx, "mailingListService.get-grpsio-service", "service_uid", payload.UID) - - // Execute use case - service, revision, err := s.grpsIOReaderOrchestrator.GetGrpsIOService(ctx, *payload.UID) - if err != nil { - slog.ErrorContext(ctx, "failed to get service", "error", err, "service_uid", payload.UID) - return nil, wrapError(ctx, err) - } - - // Convert domain model to GOA response - goaService := s.convertGrpsIOServiceDomainToStandardResponse(service) - - // Create result with ETag (using revision from NATS) - revisionStr := fmt.Sprintf("%d", revision) - result = &mailinglistservice.GetGrpsioServiceResult{ - Service: goaService, - Etag: &revisionStr, - } - - slog.InfoContext(ctx, "successfully retrieved service", "service_uid", payload.UID, "etag", revisionStr) - return result, nil -} - -// CreateGrpsioService creates a new GroupsIO service with type-specific validation -func (s *mailingListService) CreateGrpsioService(ctx context.Context, payload *mailinglistservice.CreateGrpsioServicePayload) (result *mailinglistservice.GrpsIoServiceFull, err error) { - slog.DebugContext(ctx, "mailingListService.create-grpsio-service", "service_type", payload.Type) - - // Validate type-specific requirements - if err := validateServiceCreationRules(payload); err != nil { - slog.WarnContext(ctx, "service creation validation failed", "error", err, "service_type", payload.Type) - return nil, wrapError(ctx, err) - } - - // Generate new UID for the service - serviceUID := uuid.New().String() - - // Convert GOA payload to domain model - domainService := s.convertGrpsIOServiceCreatePayloadToDomain(payload) - domainService.UID = serviceUID - - // Extract settings from payload (writers/auditors) - domainSettings := s.convertGrpsIOServiceCreatePayloadToSettings(payload, serviceUID) - - // Execute use case - createdServiceFull, revision, err := s.grpsIOWriterOrchestrator.CreateGrpsIOService(ctx, domainService, domainSettings) - if err != nil { - slog.ErrorContext(ctx, "failed to create service", "error", err, "service_type", payload.Type) - return nil, wrapError(ctx, err) - } - - // Convert domain model to GOA response, including settings - result = s.convertGrpsIOServiceFullDomainToResponse(createdServiceFull) - - slog.InfoContext(ctx, "successfully created service", "service_uid", createdServiceFull.Base.UID, "revision", revision) - return result, nil -} - -// UpdateGrpsioService updates an existing GroupsIO service -func (s *mailingListService) UpdateGrpsioService(ctx context.Context, payload *mailinglistservice.UpdateGrpsioServicePayload) (result *mailinglistservice.GrpsIoServiceWithReadonlyAttributes, err error) { - slog.DebugContext(ctx, "mailingListService.update-grpsio-service", "service_uid", payload.UID) - - // Parse expected revision from ETag - expectedRevision, err := etagValidator(payload.IfMatch) - if err != nil { - slog.ErrorContext(ctx, "invalid if-match", "error", err, "if_match", payload.IfMatch) - return nil, wrapError(ctx, err) - } - - // Retrieve existing service for immutability validation - existingService, _, err := s.grpsIOReaderOrchestrator.GetGrpsIOService(ctx, *payload.UID) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve existing service for update validation", "error", err, "service_uid", payload.UID) - return nil, wrapError(ctx, err) - } - - // Validate immutability constraints - if err := validateUpdateImmutabilityConstraints(existingService, payload); err != nil { - slog.WarnContext(ctx, "update validation failed due to immutability constraints", "error", err, "service_uid", payload.UID) - return nil, wrapError(ctx, err) - } - - // Convert GOA payload to domain model - domainService := s.convertGrpsIOServiceUpdatePayloadToDomain(existingService, payload) - - // Enhanced business rule validation (POST-PUT conversion) - // This prevents PUT semantics from violating mandatory business constraints - if err := validateServiceBusinessRules(domainService); err != nil { - slog.WarnContext(ctx, "business rule validation failed after payload conversion", - "error", err, - "service_uid", payload.UID, - "service_type", domainService.Type) - return nil, wrapError(ctx, err) - } - - // Execute use case - updatedService, revision, err := s.grpsIOWriterOrchestrator.UpdateGrpsIOService(ctx, *payload.UID, domainService, expectedRevision) - if err != nil { - slog.ErrorContext(ctx, "failed to update service", "error", err, "service_uid", payload.UID) - return nil, wrapError(ctx, err) - } - - // Convert domain model to GOA response - result = s.convertGrpsIOServiceDomainToStandardResponse(updatedService) - - slog.InfoContext(ctx, "successfully updated service", "service_uid", payload.UID, "revision", revision) - return result, nil -} - -// GetGrpsioServiceSettings retrieves service settings (writers and auditors) -func (s *mailingListService) GetGrpsioServiceSettings(ctx context.Context, payload *mailinglistservice.GetGrpsioServiceSettingsPayload) (result *mailinglistservice.GetGrpsioServiceSettingsResult, err error) { - slog.DebugContext(ctx, "mailingListService.get-grpsio-service-settings", "service_uid", payload.UID) - - // Execute use case - settings, revision, err := s.grpsIOReaderOrchestrator.GetGrpsIOServiceSettings(ctx, *payload.UID) - if err != nil { - slog.ErrorContext(ctx, "failed to get service settings", "error", err, "service_uid", payload.UID) - return nil, wrapError(ctx, err) - } - - // Convert domain model to GOA response - goaSettings := s.convertGrpsIOServiceSettingsDomainToResponse(settings) - - // Create result with ETag (using revision from NATS) - revisionStr := fmt.Sprintf("%d", revision) - result = &mailinglistservice.GetGrpsioServiceSettingsResult{ - ServiceSettings: goaSettings, - Etag: &revisionStr, - } - - slog.InfoContext(ctx, "successfully retrieved service settings", "service_uid", payload.UID, "etag", revisionStr) - return result, nil -} - -// UpdateGrpsioServiceSettings updates service settings (writers and auditors) -func (s *mailingListService) UpdateGrpsioServiceSettings(ctx context.Context, payload *mailinglistservice.UpdateGrpsioServiceSettingsPayload) (result *mailinglistservice.GrpsIoServiceSettings, err error) { - slog.DebugContext(ctx, "mailingListService.update-grpsio-service-settings", "service_uid", payload.UID) - - // Parse expected revision from ETag - expectedRevision, err := etagValidator(payload.IfMatch) - if err != nil { - slog.ErrorContext(ctx, "invalid if-match", "error", err, "if_match", payload.IfMatch) - return nil, wrapError(ctx, err) - } - - // Convert GOA payload to domain model - domainSettings := s.convertGrpsIOServiceSettingsPayloadToDomain(payload) - - // Execute use case - updatedSettings, revision, err := s.grpsIOWriterOrchestrator.UpdateGrpsIOServiceSettings(ctx, domainSettings, expectedRevision) - if err != nil { - slog.ErrorContext(ctx, "failed to update service settings", "error", err, "service_uid", payload.UID) - return nil, wrapError(ctx, err) - } - - // Convert domain model to GOA response - result = s.convertGrpsIOServiceSettingsDomainToResponse(updatedSettings) - - slog.InfoContext(ctx, "successfully updated service settings", "service_uid", payload.UID, "revision", revision) - return result, nil -} - -// DeleteGrpsioService deletes a GroupsIO service -func (s *mailingListService) DeleteGrpsioService(ctx context.Context, payload *mailinglistservice.DeleteGrpsioServicePayload) (err error) { - slog.DebugContext(ctx, "mailingListService.delete-grpsio-service", "service_uid", payload.UID) - - // Validate ETag - expectedRevision, err := etagValidator(payload.IfMatch) - if err != nil { - slog.ErrorContext(ctx, "invalid if-match", "error", err, "if_match", payload.IfMatch) - return wrapError(ctx, err) - } - - // Retrieve existing service for deletion protection validation - existingService, _, err := s.grpsIOReaderOrchestrator.GetGrpsIOService(ctx, *payload.UID) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve existing service for delete validation", "error", err, "service_uid", payload.UID) - return wrapError(ctx, err) - } - - // Validate deletion protection rules - if err := validateDeleteProtectionRules(existingService); err != nil { - slog.WarnContext(ctx, "delete validation failed due to protection rules", "error", err, "service_uid", payload.UID, "service_type", existingService.Type) - return wrapError(ctx, err) - } - - // Execute use case - err = s.grpsIOWriterOrchestrator.DeleteGrpsIOService(ctx, *payload.UID, expectedRevision, existingService) - if err != nil { - slog.ErrorContext(ctx, "failed to delete service", "error", err, "service_uid", payload.UID) - return wrapError(ctx, err) - } - - slog.InfoContext(ctx, "successfully deleted service", "service_uid", payload.UID, "service_type", existingService.Type) - return nil -} - -// CreateGrpsioMailingList creates a new GroupsIO mailing list with comprehensive validation -func (s *mailingListService) CreateGrpsioMailingList(ctx context.Context, payload *mailinglistservice.CreateGrpsioMailingListPayload) (result *mailinglistservice.GrpsIoMailingListFull, err error) { - slog.DebugContext(ctx, "mailingListService.create-grpsio-mailing-list", "group_name", payload.GroupName, "service_uid", payload.ServiceUID) - - // Validate mailing list creation requirements - if err := validateMailingListCreation(payload); err != nil { - slog.WarnContext(ctx, "mailing list creation validation failed", "error", err, "group_name", payload.GroupName) - return nil, wrapError(ctx, err) - } - - // Generate new UID for the mailing list - mailingListUID := uuid.New().String() - - // Convert GOA payload to domain model - domainMailingList := s.convertGrpsIOMailingListPayloadToDomain(payload) - domainMailingList.UID = mailingListUID - - // Extract writers and auditors from payload and create settings - domainSettings := &model.GrpsIOMailingListSettings{ - Writers: convertUserInfoPayloadToDomain(payload.Writers), - Auditors: convertUserInfoPayloadToDomain(payload.Auditors), - } - - // Execute use case - createdMailingList, revision, err := s.grpsIOWriterOrchestrator.CreateGrpsIOMailingList(ctx, domainMailingList, domainSettings) - if err != nil { - slog.ErrorContext(ctx, "failed to create mailing list", "error", err, "group_name", payload.GroupName) - return nil, wrapError(ctx, err) - } - - // Convert domain model to GOA response with settings - result = s.convertGrpsIOMailingListDomainToResponse(createdMailingList, domainSettings) - - slog.InfoContext(ctx, "successfully created mailing list", "mailing_list_uid", createdMailingList.UID, "group_name", createdMailingList.GroupName, "project_uid", createdMailingList.ProjectUID, "revision", revision) - return result, nil -} - -// GetGrpsioMailingList retrieves a single mailing list by UID -func (s *mailingListService) GetGrpsioMailingList(ctx context.Context, payload *mailinglistservice.GetGrpsioMailingListPayload) (result *mailinglistservice.GetGrpsioMailingListResult, err error) { - slog.DebugContext(ctx, "mailingListService.get-grpsio-mailing-list", "mailing_list_uid", payload.UID) - - // Execute use case - mailingList, revision, err := s.grpsIOReaderOrchestrator.GetGrpsIOMailingList(ctx, *payload.UID) - if err != nil { - slog.ErrorContext(ctx, "failed to get mailing list", "error", err, "mailing_list_uid", payload.UID) - return nil, wrapError(ctx, err) - } - - // Convert domain model to GOA response - goaMailingList := s.convertGrpsIOMailingListDomainToStandardResponse(mailingList) - - // Create result with ETag (using revision from NATS) - revisionStr := fmt.Sprintf("%d", revision) - result = &mailinglistservice.GetGrpsioMailingListResult{ - MailingList: goaMailingList, - Etag: &revisionStr, - } - - slog.InfoContext(ctx, "successfully retrieved mailing list", "mailing_list_uid", payload.UID, "etag", revisionStr) - return result, nil -} - -// UpdateGrpsioMailingList updates an existing GroupsIO mailing list -func (s *mailingListService) UpdateGrpsioMailingList(ctx context.Context, payload *mailinglistservice.UpdateGrpsioMailingListPayload) (result *mailinglistservice.GrpsIoMailingListWithReadonlyAttributes, err error) { - slog.DebugContext(ctx, "mailingListService.update-grpsio-mailing-list", "mailing_list_uid", payload.UID) - - // Parse expected revision from ETag - expectedRevision, err := etagValidator(payload.IfMatch) - if err != nil { - slog.ErrorContext(ctx, "invalid if-match", "error", err, "if_match", payload.IfMatch) - return nil, wrapError(ctx, err) - } - - // Retrieve existing mailing list for validation - existingMailingList, _, err := s.grpsIOReaderOrchestrator.GetGrpsIOMailingList(ctx, *payload.UID) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve existing mailing list for update validation", "error", err, "mailing_list_uid", payload.UID) - return nil, wrapError(ctx, err) - } - - // Retrieve parent service for validation (needed for main group checks) - parentService, _, err := s.grpsIOReaderOrchestrator.GetGrpsIOService(ctx, existingMailingList.ServiceUID) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve parent service for update validation", "error", err, "service_uid", existingMailingList.ServiceUID) - return nil, wrapError(ctx, err) - } - - // Validate update constraints - if err := validateMailingListUpdate(ctx, existingMailingList, parentService, payload, s.grpsIOReaderOrchestrator); err != nil { - slog.WarnContext(ctx, "update validation failed", - "error", err, - "mailing_list_uid", payload.UID) - return nil, wrapError(ctx, err) - } - - // Convert GOA payload to domain model - domainMailingList := s.convertGrpsIOMailingListUpdatePayloadToDomain(existingMailingList, payload) - // Ensure persisted JSON UID matches the key - if payload.UID != nil { - domainMailingList.UID = *payload.UID - } - - // Execute use case - updatedMailingList, revision, err := s.grpsIOWriterOrchestrator.UpdateGrpsIOMailingList(ctx, *payload.UID, domainMailingList, expectedRevision) - if err != nil { - slog.ErrorContext(ctx, "failed to update mailing list", "error", err, "mailing_list_uid", payload.UID) - return nil, wrapError(ctx, err) - } - - // Convert domain model to GOA response - result = s.convertGrpsIOMailingListDomainToStandardResponse(updatedMailingList) - - slog.InfoContext(ctx, "successfully updated mailing list", "mailing_list_uid", payload.UID, "revision", revision) - return result, nil -} - -// DeleteGrpsioMailingList deletes a GroupsIO mailing list -func (s *mailingListService) DeleteGrpsioMailingList(ctx context.Context, payload *mailinglistservice.DeleteGrpsioMailingListPayload) (err error) { - slog.DebugContext(ctx, "mailingListService.delete-grpsio-mailing-list", "mailing_list_uid", payload.UID) - - // Validate ETag - expectedRevision, err := etagValidator(payload.IfMatch) - if err != nil { - slog.ErrorContext(ctx, "invalid if-match", "error", err, "if_match", payload.IfMatch) - return wrapError(ctx, err) - } - - // Retrieve existing mailing list for deletion protection validation - existingMailingList, _, err := s.grpsIOReaderOrchestrator.GetGrpsIOMailingList(ctx, *payload.UID) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve existing mailing list for delete validation", "error", err, "mailing_list_uid", payload.UID) - return wrapError(ctx, err) - } - - // Retrieve parent service for deletion protection validation - parentService, _, err := s.grpsIOReaderOrchestrator.GetGrpsIOService(ctx, existingMailingList.ServiceUID) - if err != nil { - slog.WarnContext(ctx, "failed to retrieve parent service for delete validation", "error", err, "service_uid", existingMailingList.ServiceUID) - // Continue with deletion if service not found (service might be deleted) - parentService = nil - } - - // Validate deletion protection rules - if err := validateMailingListDeleteProtection(existingMailingList, parentService); err != nil { - slog.WarnContext(ctx, "delete validation failed due to protection rules", - "error", err, - "mailing_list_uid", payload.UID, - "group_name", existingMailingList.GroupName) - return wrapError(ctx, err) - } - - // Execute use case - err = s.grpsIOWriterOrchestrator.DeleteGrpsIOMailingList(ctx, *payload.UID, expectedRevision, existingMailingList) - if err != nil { - slog.ErrorContext(ctx, "failed to delete mailing list", "error", err, "mailing_list_uid", payload.UID) - return wrapError(ctx, err) - } - - slog.InfoContext(ctx, "successfully deleted mailing list", "mailing_list_uid", payload.UID, "group_name", existingMailingList.GroupName) - return nil -} - -// GetGrpsioMailingListSettings retrieves mailing list settings (writers and auditors) -func (s *mailingListService) GetGrpsioMailingListSettings(ctx context.Context, payload *mailinglistservice.GetGrpsioMailingListSettingsPayload) (result *mailinglistservice.GetGrpsioMailingListSettingsResult, err error) { - slog.DebugContext(ctx, "mailingListService.get-grpsio-mailing-list-settings", "mailing_list_uid", payload.UID) - - // Execute use case - settings, revision, err := s.grpsIOReaderOrchestrator.GetGrpsIOMailingListSettings(ctx, *payload.UID) - if err != nil { - slog.ErrorContext(ctx, "failed to get mailing list settings", "error", err, "mailing_list_uid", payload.UID) - return nil, wrapError(ctx, err) - } - - // Convert domain model to GOA response - goaSettings := s.convertGrpsIOMailingListSettingsDomainToResponse(settings) - - // Create result with ETag (using revision from NATS) - revisionStr := fmt.Sprintf("%d", revision) - result = &mailinglistservice.GetGrpsioMailingListSettingsResult{ - MailingListSettings: goaSettings, - Etag: &revisionStr, - } - - slog.InfoContext(ctx, "successfully retrieved mailing list settings", "mailing_list_uid", payload.UID, "etag", revisionStr) - return result, nil -} - -// UpdateGrpsioMailingListSettings updates mailing list settings (writers and auditors) -func (s *mailingListService) UpdateGrpsioMailingListSettings(ctx context.Context, payload *mailinglistservice.UpdateGrpsioMailingListSettingsPayload) (result *mailinglistservice.GrpsIoMailingListSettings, err error) { - slog.DebugContext(ctx, "mailingListService.update-grpsio-mailing-list-settings", "mailing_list_uid", payload.UID) - - // Parse expected revision from ETag - expectedRevision, err := etagValidator(payload.IfMatch) - if err != nil { - slog.ErrorContext(ctx, "invalid if-match", "error", err, "if_match", payload.IfMatch) - return nil, wrapError(ctx, err) - } - - // Convert GOA payload to domain model - domainSettings := s.convertGrpsIOMailingListSettingsPayloadToDomain(payload) - - // Execute use case - updatedSettings, revision, err := s.grpsIOWriterOrchestrator.UpdateGrpsIOMailingListSettings(ctx, domainSettings, expectedRevision) - if err != nil { - slog.ErrorContext(ctx, "failed to update mailing list settings", "error", err, "mailing_list_uid", payload.UID) - return nil, wrapError(ctx, err) - } - - // Convert domain model to GOA response - result = s.convertGrpsIOMailingListSettingsDomainToResponse(updatedSettings) - - slog.InfoContext(ctx, "successfully updated mailing list settings", "mailing_list_uid", payload.UID, "revision", revision) - return result, nil -} - -// CreateGrpsioMailingListMember creates a new member for a GroupsIO mailing list -func (s *mailingListService) CreateGrpsioMailingListMember(ctx context.Context, payload *mailinglistservice.CreateGrpsioMailingListMemberPayload) (result *mailinglistservice.GrpsIoMemberFull, err error) { - slog.DebugContext(ctx, "mailingListService.create-grpsio-mailing-list-member", - "mailing_list_uid", payload.UID, - "email", redaction.RedactEmail(payload.Email), - ) - - // Validate member creation requirements - if err := validateMemberCreation(ctx, payload, s.grpsIOReaderOrchestrator); err != nil { - slog.WarnContext(ctx, "member creation validation failed", "error", err, "email", redaction.RedactEmail(payload.Email)) - return nil, wrapError(ctx, err) - } - - // Generate new UID for the member - memberUID := uuid.New().String() - - // Convert GOA payload to domain model - domainMember := s.convertGrpsIOMemberPayloadToDomain(payload) - domainMember.UID = memberUID - - // Execute use case - createdMember, revision, err := s.grpsIOWriterOrchestrator.CreateGrpsIOMember(ctx, domainMember) - if err != nil { - slog.ErrorContext(ctx, "failed to create member", - "error", err, - "mailing_list_uid", payload.UID, - "email", redaction.RedactEmail(payload.Email), - ) - return nil, wrapError(ctx, err) - } - - // TODO: Future PR - Add Groups.io API integration - // When Groups.io API integration is complete, add member to Groups.io - // Handle Groups.io error "user already exists" to detect member adoption scenarios - - // TODO: LFXV2-478 - Add committee sync functionality - // - Sync committee members when committee_uid is provided - // - Handle committee member role changes (owner/moderator/member) - // - Auto-update member status based on committee membership changes - - // Convert domain model to GOA response - result = s.convertGrpsIOMemberDomainToResponse(createdMember) - - slog.InfoContext(ctx, "successfully created member", - "member_uid", createdMember.UID, - "mailing_list_uid", createdMember.MailingListUID, - "email", redaction.RedactEmail(createdMember.Email), - "revision", revision, - ) - return result, nil -} - -// GetGrpsioMailingListMember retrieves a member from a GroupsIO mailing list -func (s *mailingListService) GetGrpsioMailingListMember(ctx context.Context, payload *mailinglistservice.GetGrpsioMailingListMemberPayload) (*mailinglistservice.GetGrpsioMailingListMemberResult, error) { - slog.DebugContext(ctx, "getting GroupsIO mailing list member", - "mailing_list_uid", payload.UID, - "member_uid", payload.MemberUID) - - // Get member using reader orchestrator - member, revision, err := s.grpsIOReaderOrchestrator.GetGrpsIOMember(ctx, payload.MemberUID) - if err != nil { - slog.ErrorContext(ctx, "failed to get member", - "error", err, - "member_uid", payload.MemberUID) - return nil, wrapError(ctx, err) - } - - // Verify member belongs to the requested mailing list - if member.MailingListUID != payload.UID { - slog.WarnContext(ctx, "member does not belong to requested mailing list", - "member_uid", payload.MemberUID, - "requested_mailing_list_uid", payload.UID, - "actual_mailing_list_uid", member.MailingListUID) - return nil, wrapError(ctx, errors.NewNotFound("member not found in mailing list")) - } - - // Convert to response format - memberResponse := s.convertGrpsIOMemberToResponse(member) - etag := fmt.Sprintf("%d", revision) - - slog.InfoContext(ctx, "successfully retrieved member", - "member_uid", payload.MemberUID, - "mailing_list_uid", payload.UID, - "etag", etag) - - return &mailinglistservice.GetGrpsioMailingListMemberResult{ - Member: memberResponse, - Etag: &etag, - }, nil -} - -// UpdateGrpsioMailingListMember updates a member in a GroupsIO mailing list -func (s *mailingListService) UpdateGrpsioMailingListMember(ctx context.Context, payload *mailinglistservice.UpdateGrpsioMailingListMemberPayload) (*mailinglistservice.GrpsIoMemberWithReadonlyAttributes, error) { - slog.DebugContext(ctx, "updating GroupsIO mailing list member", - "mailing_list_uid", payload.UID, - "member_uid", payload.MemberUID) - - // Parse ETag for revision checking - expectedRevision, err := etagValidator(&payload.IfMatch) - if err != nil { - slog.ErrorContext(ctx, "invalid ETag format", "error", err, "etag", payload.IfMatch) - return nil, wrapError(ctx, errors.NewValidation("invalid ETag format", err)) - } - - // Get existing member for validation - existingMember, _, err := s.grpsIOReaderOrchestrator.GetGrpsIOMember(ctx, payload.MemberUID) - if err != nil { - slog.ErrorContext(ctx, "failed to get existing member", - "error", err, - "member_uid", payload.MemberUID) - return nil, wrapError(ctx, err) - } - - // Verify member belongs to the requested mailing list - if existingMember.MailingListUID != payload.UID { - slog.WarnContext(ctx, "member does not belong to requested mailing list", - "member_uid", payload.MemberUID, - "requested_mailing_list_uid", payload.UID, - "actual_mailing_list_uid", existingMember.MailingListUID) - return nil, wrapError(ctx, errors.NewNotFound("member not found in mailing list")) - } - - // Build updated member model from payload - updatedMember := s.convertGrpsIOMemberUpdatePayloadToDomain(payload, existingMember) - - // Validate immutable fields - if err := validateMemberUpdate(existingMember, updatedMember); err != nil { - slog.ErrorContext(ctx, "member update validation failed", - "error", err, - "member_uid", payload.MemberUID) - return nil, wrapError(ctx, err) - } - - // Update member via writer orchestrator with revision check - updated, revision, err := s.grpsIOWriterOrchestrator.UpdateGrpsIOMember(ctx, payload.MemberUID, updatedMember, expectedRevision) - if err != nil { - slog.ErrorContext(ctx, "failed to update member", - "error", err, - "member_uid", payload.MemberUID) - return nil, wrapError(ctx, err) - } - - // TODO: LFXV2-353 - Add Groups.io API sync for member updates - // When Groups.io API integration is complete, sync member changes to Groups.io - // Handle modStatus changes (owner/moderator/member role updates) - - // TODO: LFXV2-481 - Add member notification for profile changes - // Notify member when profile information is updated by moderators - // Send email notification for role changes (promotion to owner/moderator) - - // Convert to response format - memberResponse := s.convertGrpsIOMemberToResponse(updated) - - slog.InfoContext(ctx, "successfully updated member", - "member_uid", payload.MemberUID, - "mailing_list_uid", payload.UID, - "revision", revision) - - return memberResponse, nil -} - -// DeleteGrpsioMailingListMember deletes a member from a GroupsIO mailing list -func (s *mailingListService) DeleteGrpsioMailingListMember(ctx context.Context, payload *mailinglistservice.DeleteGrpsioMailingListMemberPayload) error { - slog.DebugContext(ctx, "deleting GroupsIO mailing list member", - "mailing_list_uid", payload.UID, - "member_uid", payload.MemberUID) - - // Parse ETag for revision checking - expectedRevision, err := etagValidator(&payload.IfMatch) - if err != nil { - slog.ErrorContext(ctx, "invalid ETag format", "error", err, "etag", payload.IfMatch) - return wrapError(ctx, errors.NewValidation("invalid ETag format")) - } - - // Get existing member for validation - existingMember, _, err := s.grpsIOReaderOrchestrator.GetGrpsIOMember(ctx, payload.MemberUID) - if err != nil { - slog.ErrorContext(ctx, "failed to get existing member", - "error", err, - "member_uid", payload.MemberUID) - return wrapError(ctx, err) - } - - // Verify member belongs to the requested mailing list - if existingMember.MailingListUID != payload.UID { - slog.WarnContext(ctx, "member does not belong to requested mailing list", - "member_uid", payload.MemberUID, - "requested_mailing_list_uid", payload.UID, - "actual_mailing_list_uid", existingMember.MailingListUID) - return wrapError(ctx, errors.NewNotFound("member not found in mailing list")) - } - - // Validate member deletion protection rules - if err := validateMemberDeleteProtection(existingMember); err != nil { - slog.WarnContext(ctx, "member deletion protection failed", "error", err, "member_uid", payload.MemberUID) - return wrapError(ctx, err) - } - - // TODO: Future PR - Check sole owner protection via Groups.io API - // Prevent deletion if this is the only owner/moderator of the mailing list - - // Delete member via writer orchestrator with revision check - err = s.grpsIOWriterOrchestrator.DeleteGrpsIOMember(ctx, payload.MemberUID, expectedRevision, existingMember) - if err != nil { - slog.ErrorContext(ctx, "failed to delete member", - "error", err, - "member_uid", payload.MemberUID) - return wrapError(ctx, err) - } - - slog.InfoContext(ctx, "successfully deleted member", - "member_uid", payload.MemberUID, - "mailing_list_uid", payload.UID) - - return nil -} - -// GroupsioWebhook handles GroupsIO webhook events -func (s *mailingListService) GroupsioWebhook(ctx context.Context, p *mailinglistservice.GroupsioWebhookPayload) error { - // Get raw body from context - bodyBytes, ok := ctx.Value(constants.GrpsIOWebhookBodyContextKey).([]byte) - if !ok { - slog.ErrorContext(ctx, "failed to get raw body from context") - return &mailinglistservice.BadRequestError{Message: "missing webhook body"} - } - - // Validate signature - if err := s.grpsioWebhookValidator.ValidateSignature(bodyBytes, p.Signature); err != nil { - slog.ErrorContext(ctx, "webhook signature validation failed", "error", err) - return &mailinglistservice.UnauthorizedError{Message: "invalid webhook signature"} - } - - // GOA already parsed the action field - use it directly - slog.InfoContext(ctx, "processing groupsio webhook event", "event_type", p.Action) - - // Validate event type - if !s.grpsioWebhookValidator.IsValidEvent(p.Action) { - slog.WarnContext(ctx, "unsupported event type", "event_type", p.Action) - return &mailinglistservice.BadRequestError{Message: fmt.Sprintf("unsupported event type: %s", p.Action)} - } - - // Convert GOA payload to domain model - event := &model.GrpsIOWebhookEvent{ - Action: p.Action, - ReceivedAt: time.Now(), - } - - // Convert Group field if present (for subgroup events) - if p.Group != nil { - if groupMap, ok := p.Group.(map[string]any); ok { - convertedGroup, err := s.convertWebhookGroupInfo(groupMap) - if err != nil { - slog.ErrorContext(ctx, "invalid group data in webhook", "error", err) - return &mailinglistservice.BadRequestError{Message: fmt.Sprintf("invalid group data: %v", err)} - } - event.Group = convertedGroup - } - } - - // Convert MemberInfo field if present (for member events) - if p.MemberInfo != nil { - if memberMap, ok := p.MemberInfo.(map[string]any); ok { - convertedMember, err := s.convertWebhookMemberInfo(memberMap) - if err != nil { - slog.ErrorContext(ctx, "invalid member data in webhook", "error", err) - return &mailinglistservice.BadRequestError{Message: fmt.Sprintf("invalid member data: %v", err)} - } - event.MemberInfo = convertedMember - } - } - - // Map extra fields - if p.Extra != nil { - event.Extra = *p.Extra - } - if p.ExtraID != nil { - event.ExtraID = *p.ExtraID - } - - // Process event synchronously with exponential backoff retries - retryConfig := utils.NewRetryConfig( - constants.WebhookMaxRetries, - constants.WebhookRetryBaseDelay*time.Millisecond, - constants.WebhookRetryMaxDelay*time.Millisecond, - ) - err := utils.RetryWithExponentialBackoff(ctx, retryConfig, func() error { - return s.grpsioWebhookProcessor.ProcessEvent(ctx, event) - }) - - if err != nil { - // Check if this is a validation error (malformed data) - var validationErr errors.Validation - if stderrors.As(err, &validationErr) { - // Validation errors should not trigger retries - log and return success - slog.ErrorContext(ctx, "webhook validation failed - returning success to prevent retries", - "error", err, - "action", p.Action) - return nil // Return 204 to prevent GroupsIO from retrying - } - - // For other errors (transient failures), return error to trigger GroupsIO retry - slog.ErrorContext(ctx, "webhook processing failed after retries", - "error", err, - "action", p.Action, - "retries", constants.WebhookMaxRetries) - return &mailinglistservice.InternalServerError{Message: "webhook processing failed"} - } - - // Success - return 204 No Content - return nil -} - -// Helper functions - -// payloadStringValue safely extracts string value from payload pointer -func payloadStringValue(val *string) string { - if val == nil { - return "" - } - return *val -} - -// payloadInt64Ptr safely converts int64 pointer from payload to nullable pointer for domain model -func payloadInt64Ptr(val *int64) *int64 { - if val == nil { - return nil - } - return val -} diff --git a/cmd/mailing-list-api/service/mailing_list_validators_test.go b/cmd/mailing-list-api/service/mailing_list_validators_test.go deleted file mode 100644 index 45a6055..0000000 --- a/cmd/mailing-list-api/service/mailing_list_validators_test.go +++ /dev/null @@ -1,544 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "testing" - - mailinglistservice "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" -) - -// TestValidateDescriptionLength was removed - description length validation -// is now handled by GOA design layer (MinLength/MaxLength attributes) - -func TestValidateMailingListUpdate(t *testing.T) { - tests := []struct { - name string - existing *model.GrpsIOMailingList - parentService *model.GrpsIOService - payload *mailinglistservice.UpdateGrpsioMailingListPayload - wantErr bool - errMsg string - }{ - { - name: "valid update - same visibility", - existing: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "test-group", - Public: true, - ServiceUID: "svc-123", - }, - parentService: &model.GrpsIOService{ - UID: "svc-123", - GroupName: "different-group", - }, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - UID: stringPtr("ml-123"), - GroupName: "test-group", - Public: true, - ServiceUID: "svc-123", - Description: "Valid description with enough characters", - }, - wantErr: false, - }, - { - name: "valid update - public to public", - existing: &model.GrpsIOMailingList{ - UID: "ml-123", - Public: true, - ServiceUID: "svc-123", - }, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - UID: stringPtr("ml-123"), - Public: true, - ServiceUID: "svc-123", - Description: "Valid description with enough characters", - }, - wantErr: false, - }, - { - name: "invalid update - private to public", - existing: &model.GrpsIOMailingList{ - UID: "ml-123", - Public: false, - ServiceUID: "svc-123", - }, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - UID: stringPtr("ml-123"), - Public: true, - ServiceUID: "svc-123", - Description: "Valid description with enough characters", - }, - wantErr: true, - errMsg: "cannot change visibility from private to public", - }, - { - name: "invalid update - change service uid", - existing: &model.GrpsIOMailingList{ - UID: "ml-123", - Public: true, - ServiceUID: "svc-123", - }, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - UID: stringPtr("ml-123"), - Public: true, - ServiceUID: "svc-456", - Description: "Valid description with enough characters", - }, - wantErr: true, - errMsg: "cannot change parent service", - }, - // Description length validation test removed - now handled by GOA design layer - { - name: "valid update - valid subject tag", - existing: &model.GrpsIOMailingList{ - UID: "ml-123", - Public: true, - ServiceUID: "svc-123", - }, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - UID: stringPtr("ml-123"), - Public: true, - ServiceUID: "svc-123", - Description: "Valid description with enough characters", - SubjectTag: stringPtr("VALID-TAG"), - }, - wantErr: false, - }, - { - name: "invalid update - invalid subject tag", - existing: &model.GrpsIOMailingList{ - UID: "ml-123", - Public: true, - ServiceUID: "svc-123", - }, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - UID: stringPtr("ml-123"), - Public: true, - ServiceUID: "svc-123", - Description: "Valid description with enough characters", - SubjectTag: stringPtr("[INVALID]"), - }, - wantErr: true, - errMsg: "invalid subject tag format", - }, - // Committee filter validation test removed - now handled by GOA design layer - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Provide nil parent service for tests that don't need it - // Pass nil service reader since these tests don't test parent service changes - err := validateMailingListUpdate(context.Background(), tt.existing, tt.parentService, tt.payload, nil) - if tt.wantErr { - if err == nil { - t.Errorf("validateMailingListUpdate() error = nil, wantErr %v", tt.wantErr) - return - } - if tt.errMsg != "" && err.Error() != tt.errMsg { - t.Errorf("validateMailingListUpdate() error = %v, want %v", err.Error(), tt.errMsg) - } - } else { - if err != nil { - t.Errorf("validateMailingListUpdate() error = %v, wantErr %v", err, tt.wantErr) - } - } - }) - } -} - -// TestValidateMailingListUpdateNewRules tests the new validation rules added for Groups.io compatibility -func TestValidateMailingListUpdateNewRules(t *testing.T) { - tests := []struct { - name string - existing *model.GrpsIOMailingList - parentService *model.GrpsIOService - payload *mailinglistservice.UpdateGrpsioMailingListPayload - wantErr bool - errMsg string - }{ - { - name: "invalid update - group name change (immutability rule)", - existing: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "original-group", - Type: "announcement", - Public: true, - ServiceUID: "svc-123", - Description: "Valid description", - }, - parentService: &model.GrpsIOService{ - UID: "svc-123", - GroupName: "different-group", - Type: "formation", - }, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - UID: stringPtr("ml-123"), - GroupName: "changed-group", - Type: "announcement", - Public: true, - ServiceUID: "svc-123", - Description: "Valid description with enough characters", - }, - wantErr: true, - errMsg: "field 'group_name' is immutable", - }, - { - name: "invalid update - main group type change", - existing: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "main-group", - Type: "announcement", - Public: true, - ServiceUID: "svc-123", - Description: "Valid description", - }, - parentService: &model.GrpsIOService{ - UID: "svc-123", - GroupName: "main-group", // Same as mailing list - makes it a main group - Type: "primary", - }, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - UID: stringPtr("ml-123"), - GroupName: "main-group", - Type: "discussion_open", - Public: true, - ServiceUID: "svc-123", - Description: "Valid description with enough characters", - }, - wantErr: true, - errMsg: "main group must be an announcement list", - }, - { - name: "invalid update - main group visibility change", - existing: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "main-group", - Type: "announcement", - Public: true, - ServiceUID: "svc-123", - Description: "Valid description", - }, - parentService: &model.GrpsIOService{ - UID: "svc-123", - GroupName: "main-group", // Same as mailing list - makes it a main group - Type: "primary", - }, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - UID: stringPtr("ml-123"), - GroupName: "main-group", - Type: "announcement", - Public: false, - ServiceUID: "svc-123", - Description: "Valid description with enough characters", - }, - wantErr: true, - errMsg: "main group must remain public", - }, - { - name: "invalid update - set type to custom when not already custom", - existing: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "test-group", - Type: "announcement", - Public: true, - ServiceUID: "svc-123", - Description: "Valid description", - }, - parentService: &model.GrpsIOService{ - UID: "svc-123", - GroupName: "different-group", - Type: "formation", - }, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - UID: stringPtr("ml-123"), - GroupName: "test-group", - Type: "custom", - Public: true, - ServiceUID: "svc-123", - Description: "Valid description with enough characters", - }, - wantErr: true, - errMsg: "cannot set type to \"custom\"", - }, - { - name: "valid update - main group keeps valid values", - existing: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "main-group", - Type: "announcement", - Public: true, - ServiceUID: "svc-123", - Description: "Valid description", - }, - parentService: &model.GrpsIOService{ - UID: "svc-123", - GroupName: "main-group", // Same as mailing list - makes it a main group - Type: "primary", - }, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - UID: stringPtr("ml-123"), - GroupName: "main-group", - Type: "announcement", - Public: true, - ServiceUID: "svc-123", - Description: "Updated description with enough characters", - }, - wantErr: false, - }, - { - name: "valid update - non-main group can change type and visibility", - existing: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "sub-group", - Type: "announcement", - Public: false, - ServiceUID: "svc-123", - Description: "Valid description", - }, - parentService: &model.GrpsIOService{ - UID: "svc-123", - GroupName: "main-group", // Different from mailing list - not a main group - Type: "primary", - }, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - UID: stringPtr("ml-123"), - GroupName: "sub-group", - Type: "discussion_moderated", - Public: false, - ServiceUID: "svc-123", - Description: "Updated description with enough characters", - }, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateMailingListUpdate(context.Background(), tt.existing, tt.parentService, tt.payload, nil) - if tt.wantErr { - if err == nil { - t.Errorf("validateMailingListUpdate() error = nil, wantErr %v", tt.wantErr) - return - } - if tt.errMsg != "" && err.Error() != tt.errMsg { - t.Errorf("validateMailingListUpdate() error = %v, want %v", err.Error(), tt.errMsg) - } - } else { - if err != nil { - t.Errorf("validateMailingListUpdate() error = %v, wantErr %v", err, tt.wantErr) - } - } - }) - } -} - -func TestValidateMailingListDeleteProtection(t *testing.T) { - tests := []struct { - name string - mailingList *model.GrpsIOMailingList - parentService *model.GrpsIOService - wantErr bool - errMsg string - }{ - { - name: "valid delete - non-primary service", - mailingList: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "test-group", - }, - parentService: &model.GrpsIOService{ - Type: "formation", - GroupName: "parent-group", - }, - wantErr: false, - }, - { - name: "valid delete - primary service but different group name", - mailingList: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "subgroup", - }, - parentService: &model.GrpsIOService{ - Type: "primary", - GroupName: "main-group", - }, - wantErr: false, - }, - { - name: "invalid delete - main group of primary service", - mailingList: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "main-group", - }, - parentService: &model.GrpsIOService{ - Type: "primary", - GroupName: "main-group", - }, - wantErr: true, - errMsg: "cannot delete the main group of a primary service", - }, - { - name: "valid delete - no parent service", - mailingList: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "test-group", - }, - parentService: nil, - wantErr: false, - }, - { - name: "valid delete - committee-based list (with debug log)", - mailingList: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "committee-group", - Committees: []model.Committee{ - {UID: "committee-456"}, - }, - }, - parentService: &model.GrpsIOService{ - Type: "formation", - GroupName: "parent-group", - }, - wantErr: false, - }, - { - name: "invalid delete - main group of formation service", - mailingList: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "form-prefix", - }, - parentService: &model.GrpsIOService{ - Type: "formation", - Prefix: "form-prefix", - }, - wantErr: true, - errMsg: "cannot delete the main group of a formation service", - }, - { - name: "invalid delete - main group of shared service", - mailingList: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "shared-prefix", - }, - parentService: &model.GrpsIOService{ - Type: "shared", - Prefix: "shared-prefix", - }, - wantErr: true, - errMsg: "cannot delete the main group of a shared service", - }, - { - name: "invalid delete - announcement list", - mailingList: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "announcements", - Type: "announcement", - }, - parentService: &model.GrpsIOService{ - Type: "primary", - GroupName: "main-group", - }, - wantErr: true, - errMsg: "announcement lists require special handling for deletion", - }, - { - name: "valid delete - formation service different prefix", - mailingList: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "sub-list", - }, - parentService: &model.GrpsIOService{ - Type: "formation", - Prefix: "form-prefix", - }, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateMailingListDeleteProtection(tt.mailingList, tt.parentService) - if tt.wantErr { - if err == nil { - t.Errorf("validateMailingListDeleteProtection() error = nil, wantErr %v", tt.wantErr) - return - } - if tt.errMsg != "" && err.Error() != tt.errMsg { - t.Errorf("validateMailingListDeleteProtection() error = %v, want %v", err.Error(), tt.errMsg) - } - } else { - if err != nil { - t.Errorf("validateMailingListDeleteProtection() error = %v, wantErr %v", err, tt.wantErr) - } - } - }) - } -} - -func TestIsValidSubjectTag(t *testing.T) { - tests := []struct { - name string - tag string - want bool - }{ - { - name: "valid tag", - tag: "VALID-TAG", - want: true, - }, - { - name: "empty tag", - tag: "", - want: false, - }, - { - name: "whitespace only", - tag: " ", - want: false, - }, - // Length validation removed - now handled by GOA MaxLength validation - { - name: "tag with newline", - tag: "BAD\nTAG", - want: false, - }, - { - name: "tag with carriage return", - tag: "BAD\rTAG", - want: false, - }, - { - name: "tag with tab", - tag: "BAD\tTAG", - want: false, - }, - { - name: "tag with square brackets", - tag: "[INVALID]", - want: false, - }, - { - name: "valid tag with spaces", - tag: " VALID TAG ", - want: true, - }, - { - name: "maximum length tag", - tag: "THIS-IS-EXACTLY-FIFTY-CHARS-1234567890123456789", - want: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := isValidSubjectTag(tt.tag) - if got != tt.want { - t.Errorf("isValidSubjectTag() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/cmd/mailing-list-api/service/providers.go b/cmd/mailing-list-api/service/providers.go index f3f79dc..13dc9ee 100644 --- a/cmd/mailing-list-api/service/providers.go +++ b/cmd/mailing-list-api/service/providers.go @@ -1,6 +1,7 @@ // Copyright The Linux Foundation and each contributor to LFX. // SPDX-License-Identifier: MIT +// Package service provides provider functions for initializing service dependencies. package service import ( @@ -8,167 +9,21 @@ import ( "log" "log/slog" "os" - "strconv" - "sync" "time" + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain" "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/auth" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/groupsio" infrastructure "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/mock" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/nats" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/service" + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/idmapper" + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/proxy" + itxsvc "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/service/itx" ) -var ( - natsStorageClient port.GrpsIOReaderWriter - natsMessagingClient port.EntityAttributeReader - natsPublisherClient port.MessagePublisher - groupsIOClient groupsio.ClientInterface - grpsioWebhookValidator port.GrpsIOWebhookValidator - mockRepositoryClient *infrastructure.MockRepository - - natsDoOnce sync.Once - groupsIOClientOnce sync.Once - grpsioWebhookValidatorOnce sync.Once - mockRepositoryOnce sync.Once -) - -// ValidateProviderConfiguration checks for configuration mismatches that could cause issues -// Call this during application startup to fail fast on misconfiguration -func ValidateProviderConfiguration(ctx context.Context) { - groupsioSource := os.Getenv("GROUPSIO_SOURCE") - if groupsioSource == "" { - groupsioSource = "groupsio" - } - - repoSource := os.Getenv("REPOSITORY_SOURCE") - if repoSource == "" { - repoSource = "nats" - } - - authSource := os.Getenv("AUTH_SOURCE") - if authSource == "" { - authSource = "jwt" - } - - slog.InfoContext(ctx, "provider configuration validation", - "auth_source", authSource, - "repository_source", repoSource, - "groupsio_source", groupsioSource, - ) - - // Warn about potentially dangerous mismatches - if groupsioSource == "mock" && repoSource != "mock" { - slog.WarnContext(ctx, - "CONFIGURATION MISMATCH: mock GroupsIO with real repository - mock validator will accept all webhooks into production storage!", - "groupsio_source", groupsioSource, - "repository_source", repoSource, - ) - } - - if authSource == "mock" && repoSource != "mock" { - slog.WarnContext(ctx, - "CONFIGURATION MISMATCH: mock auth with real repository - authentication is bypassed but data is stored in production!", - "auth_source", authSource, - "repository_source", repoSource, - ) - } - - // Validate webhook secret in production mode - if groupsioSource != "mock" { - webhookSecret := os.Getenv("GROUPSIO_WEBHOOK_SECRET") - if webhookSecret == "" { - slog.WarnContext(ctx, "GROUPSIO_WEBHOOK_SECRET not set - webhook validation will fail in production!") - } - } - - slog.InfoContext(ctx, "provider configuration validation completed") -} - -func natsInit(ctx context.Context) { - natsDoOnce.Do(func() { - natsURL := os.Getenv("NATS_URL") - if natsURL == "" { - natsURL = "nats://localhost:4222" - } - - natsTimeout := os.Getenv("NATS_TIMEOUT") - if natsTimeout == "" { - natsTimeout = "10s" - } - natsTimeoutDuration, err := time.ParseDuration(natsTimeout) - if err != nil { - log.Fatalf("invalid NATS timeout duration: %v", err) - } - - natsMaxReconnect := os.Getenv("NATS_MAX_RECONNECT") - if natsMaxReconnect == "" { - natsMaxReconnect = "3" - } - natsMaxReconnectInt, err := strconv.Atoi(natsMaxReconnect) - if err != nil { - log.Fatalf("invalid NATS max reconnect value %s: %v", natsMaxReconnect, err) - } - - natsReconnectWait := os.Getenv("NATS_RECONNECT_WAIT") - if natsReconnectWait == "" { - natsReconnectWait = "2s" - } - natsReconnectWaitDuration, err := time.ParseDuration(natsReconnectWait) - if err != nil { - log.Fatalf("invalid NATS reconnect wait duration %s : %v", natsReconnectWait, err) - } - - config := nats.Config{ - URL: natsURL, - Timeout: natsTimeoutDuration, - MaxReconnect: natsMaxReconnectInt, - ReconnectWait: natsReconnectWaitDuration, - } - - client, errNewClient := nats.NewClient(ctx, config) - if errNewClient != nil { - log.Fatalf("failed to create NATS client: %v", errNewClient) - } - natsStorageClient = nats.NewStorage(client) - natsMessagingClient = nats.NewEntityAttributeReader(client) - natsPublisherClient = nats.NewMessagePublisher(client) - }) -} - -func natsStorage(ctx context.Context) port.GrpsIOReaderWriter { - natsInit(ctx) - return natsStorageClient -} - -func natsMessaging(ctx context.Context) port.EntityAttributeReader { - natsInit(ctx) - return natsMessagingClient -} - -func natsPublisher(ctx context.Context) port.MessagePublisher { - natsInit(ctx) - return natsPublisherClient -} - -func mockRepositoryInit(ctx context.Context) { - mockRepositoryOnce.Do(func() { - slog.InfoContext(ctx, "initializing shared mock repository") - mockRepositoryClient = infrastructure.NewMockRepository() - }) -} - -func mockRepository(ctx context.Context) *infrastructure.MockRepository { - mockRepositoryInit(ctx) - return mockRepositoryClient -} - // AuthService initializes the authentication service implementation func AuthService(ctx context.Context) port.Authenticator { var authService port.Authenticator - // Repository implementation configuration authSource := os.Getenv("AUTH_SOURCE") if authSource == "" { authSource = "jwt" @@ -196,305 +51,58 @@ func AuthService(ctx context.Context) port.Authenticator { return authService } -// EntityAttributeRetriever initializes the entity attribute retriever implementation based on the repository source -func EntityAttributeRetriever(ctx context.Context) port.EntityAttributeReader { - var entityReader port.EntityAttributeReader - - // Repository implementation configuration - repoSource := os.Getenv("REPOSITORY_SOURCE") - if repoSource == "" { - repoSource = "nats" +// ITXProxyConfig reads ITX proxy configuration from environment variables. +func ITXProxyConfig() proxy.Config { + return proxy.Config{ + BaseURL: os.Getenv("ITX_BASE_URL"), + ClientID: os.Getenv("ITX_CLIENT_ID"), + PrivateKey: os.Getenv("ITX_CLIENT_PRIVATE_KEY"), + Auth0Domain: os.Getenv("ITX_AUTH0_DOMAIN"), + Audience: os.Getenv("ITX_AUDIENCE"), + Timeout: 30 * time.Second, } - - switch repoSource { - case "mock": - slog.InfoContext(ctx, "initializing mock entity attribute retriever") - entityReader = infrastructure.NewMockEntityAttributeReader(mockRepository(ctx)) - - case "nats": - slog.InfoContext(ctx, "initializing NATS entity attribute retriever") - natsClient := natsMessaging(ctx) - if natsClient == nil { - log.Fatalf("failed to initialize NATS client") - } - entityReader = natsClient - - default: - log.Fatalf("unsupported entity attribute reader implementation: %s", repoSource) - } - - return entityReader } -// GrpsIOReader initializes the service reader implementation -func GrpsIOReader(ctx context.Context) port.GrpsIOReader { - var grpsIOReader port.GrpsIOReader - - // Repository implementation configuration - repoSource := os.Getenv("REPOSITORY_SOURCE") - if repoSource == "" { - repoSource = "nats" +// IDMapper initializes the ID mapper based on configuration. +func IDMapper(ctx context.Context) domain.IDMapper { + if os.Getenv("ID_MAPPING_DISABLED") == "true" { + slog.WarnContext(ctx, "ID mapping is DISABLED - using no-op mapper (IDs will pass through unchanged)") + return idmapper.NewNoOpMapper() } - switch repoSource { - case "mock": - slog.InfoContext(ctx, "initializing mock grpsio service reader") - grpsIOReader = infrastructure.NewMockGrpsIOReader(mockRepository(ctx)) - - case "nats": - slog.InfoContext(ctx, "initializing NATS service") - natsClient := natsStorage(ctx) - if natsClient == nil { - log.Fatalf("failed to initialize NATS client") - } - grpsIOReader = natsClient - - default: - log.Fatalf("unsupported service reader implementation: %s", repoSource) - } - - return grpsIOReader -} - -// GrpsIOReaderWriter initializes the service reader writer implementation -func GrpsIOReaderWriter(ctx context.Context) port.GrpsIOReaderWriter { - var storage port.GrpsIOReaderWriter - // Repository implementation configuration - repoSource := os.Getenv("REPOSITORY_SOURCE") - if repoSource == "" { - repoSource = "nats" + natsURL := os.Getenv("NATS_URL") + if natsURL == "" { + slog.WarnContext(ctx, "NATS_URL not set, using no-op ID mapper") + return idmapper.NewNoOpMapper() } - switch repoSource { - case "mock": - slog.InfoContext(ctx, "initializing mock grpsio storage reader writer") - storage = infrastructure.NewMockGrpsIOReaderWriter(mockRepository(ctx)) - - case "nats": - slog.InfoContext(ctx, "initializing NATS service") - natsClient := natsStorage(ctx) - if natsClient == nil { - log.Fatalf("failed to initialize NATS client") - } - storage = natsClient - - default: - log.Fatalf("unsupported service reader implementation: %s", repoSource) - } - - return storage -} - -// GrpsIOWriter initializes the service writer implementation -func GrpsIOWriter(ctx context.Context) port.GrpsIOWriter { - var grpsIOWriter port.GrpsIOWriter - - // Repository implementation configuration - repoSource := os.Getenv("REPOSITORY_SOURCE") - if repoSource == "" { - repoSource = "nats" - } - - switch repoSource { - case "mock": - slog.InfoContext(ctx, "initializing mock grpsio service writer") - grpsIOWriter = infrastructure.NewMockGrpsIOWriter(mockRepository(ctx)) - - case "nats": - slog.InfoContext(ctx, "initializing NATS service writer") - natsClient := natsStorage(ctx) - if natsClient == nil { - log.Fatalf("failed to initialize NATS client") - } - grpsIOWriter = natsClient - - default: - log.Fatalf("unsupported service writer implementation: %s", repoSource) - } - - return grpsIOWriter -} - -// GrpsIOMemberRepository initializes the member repository implementation -func GrpsIOMemberRepository(ctx context.Context) port.GrpsIOMemberRepository { - var memberRepository port.GrpsIOMemberRepository - - // Repository implementation configuration - repoSource := os.Getenv("REPOSITORY_SOURCE") - if repoSource == "" { - repoSource = "nats" - } - - switch repoSource { - case "mock": - slog.InfoContext(ctx, "initializing mock grpsio member repository") - memberRepository = infrastructure.NewMockGrpsIOMemberRepository(mockRepository(ctx)) - - case "nats": - slog.InfoContext(ctx, "initializing NATS member repository") - natsClient := natsStorage(ctx) - if natsClient == nil { - log.Fatalf("failed to initialize NATS client") - } - memberRepository = natsClient - - default: - log.Fatalf("unsupported member repository implementation: %s", repoSource) - } - - return memberRepository -} - -// MessagePublisher initializes the service publisher implementation -func MessagePublisher(ctx context.Context) port.MessagePublisher { - var publisher port.MessagePublisher - - // Repository implementation configuration - repoSource := os.Getenv("REPOSITORY_SOURCE") - if repoSource == "" { - repoSource = "nats" - } - - switch repoSource { - case "mock": - slog.InfoContext(ctx, "initializing mock service publisher") - publisher = infrastructure.NewMockMessagePublisher() - - case "nats": - slog.InfoContext(ctx, "initializing NATS service publisher") - natsPublisher := natsPublisher(ctx) - if natsPublisher == nil { - log.Fatalf("failed to initialize NATS publisher") - } - publisher = natsPublisher - - default: - log.Fatalf("unsupported service publisher implementation: %s", repoSource) - } - - return publisher -} - -// GroupsIOClient initializes the GroupsIO client with singleton pattern -func GroupsIOClient(ctx context.Context) groupsio.ClientInterface { - groupsIOClientOnce.Do(func() { - var client groupsio.ClientInterface - - // Repository implementation configuration - source := os.Getenv("GROUPSIO_SOURCE") - if source == "" { - source = "groupsio" // Default to production GroupsIO client - } - - switch source { - case "mock": - slog.InfoContext(ctx, "initializing mock groupsio client") - client = infrastructure.NewMockGroupsIOClient() - - case "groupsio": - slog.InfoContext(ctx, "initializing groupsio client") - config := groupsio.NewConfigFromEnv() - - var err error - client, err = groupsio.NewClient(config) - if err != nil { - log.Fatalf("failed to initialize GroupsIO client - missing required configuration: %v", err) - } - slog.InfoContext(ctx, "groupsio client initialized successfully") - - default: - log.Fatalf("unsupported groupsio client implementation: %s", source) - } - - groupsIOClient = client + natsMapper, err := idmapper.NewNATSMapper(idmapper.Config{ + URL: natsURL, + Timeout: 5 * time.Second, }) + if err != nil { + slog.With("error", err).WarnContext(ctx, "Failed to initialize NATS ID mapper, falling back to no-op mapper") + return idmapper.NewNoOpMapper() + } - return groupsIOClient -} - -// GrpsIOReaderOrchestrator initializes the service reader orchestrator -func GrpsIOReaderOrchestrator(ctx context.Context) service.GrpsIOReader { - grpsIOReader := GrpsIOReader(ctx) - - return service.NewGrpsIOReaderOrchestrator( - service.WithGrpsIOReader(grpsIOReader), - ) + slog.InfoContext(ctx, "ID mapping enabled - using NATS mapper for v1/v2 ID conversions") + return natsMapper } -// GrpsIOWriterOrchestrator initializes the service writer orchestrator -func GrpsIOWriterOrchestrator(ctx context.Context) service.GrpsIOWriter { - grpsIOWriter := GrpsIOWriter(ctx) - memberRepository := GrpsIOMemberRepository(ctx) - grpsIOReader := GrpsIOReader(ctx) - entityReader := EntityAttributeRetriever(ctx) - publisher := MessagePublisher(ctx) - groupsClient := GroupsIOClient(ctx) // May be nil for mock/disabled - - return service.NewGrpsIOWriterOrchestrator( - service.WithGrpsIOWriter(grpsIOWriter), - service.WithMemberRepository(memberRepository), - service.WithGrpsIOWriterReader(grpsIOReader), - service.WithEntityAttributeReader(entityReader), - service.WithPublisher(publisher), - service.WithGroupsIOClient(groupsClient), - ) +// GroupsioServiceService initializes the GroupsIO service handler. +func GroupsioServiceService(ctx context.Context, client domain.ITXGroupsioClient, mapper domain.IDMapper) *itxsvc.GroupsioServiceService { + slog.InfoContext(ctx, "initializing GroupsIO service service") + return itxsvc.NewGroupsioServiceService(client, mapper) } -// GrpsIOWebhookValidator initializes the GroupsIO webhook validator with mock support and singleton pattern -func GrpsIOWebhookValidator(ctx context.Context) port.GrpsIOWebhookValidator { - grpsioWebhookValidatorOnce.Do(func() { - var validator port.GrpsIOWebhookValidator - - // Repository implementation configuration - source := os.Getenv("GROUPSIO_SOURCE") - if source == "" { - source = "groupsio" // Default to production GroupsIO webhook validation - } - - switch source { - case "mock": - slog.InfoContext(ctx, "initializing mock groupsio webhook validator") - validator = infrastructure.NewMockGrpsIOWebhookValidator() - - case "groupsio": - slog.InfoContext(ctx, "initializing groupsio webhook validator") - secret := os.Getenv("GROUPSIO_WEBHOOK_SECRET") - if secret == "" { - log.Fatalf("GROUPSIO_WEBHOOK_SECRET is required for groupsio webhook validation") - } - validator = groupsio.NewGrpsIOWebhookValidator(secret) - - default: - log.Fatalf("unsupported groupsio webhook validator implementation: %s", source) - } - - grpsioWebhookValidator = validator - }) - - return grpsioWebhookValidator +// GroupsioSubgroupService initializes the GroupsIO subgroup handler. +func GroupsioSubgroupService(ctx context.Context, client domain.ITXGroupsioClient, mapper domain.IDMapper) *itxsvc.GroupsioSubgroupService { + slog.InfoContext(ctx, "initializing GroupsIO subgroup service") + return itxsvc.NewGroupsioSubgroupService(client, mapper) } -// GrpsIOWebhookProcessor creates GroupsIO webhook processor with explicit dependency injection -func GrpsIOWebhookProcessor(ctx context.Context) port.GrpsIOWebhookProcessor { - slog.InfoContext(ctx, "initializing groupsio webhook processor with dependency injection") - - return service.NewGrpsIOWebhookProcessor( - service.WithServiceReader(GrpsIOReader(ctx)), - service.WithMailingListReader(GrpsIOReader(ctx)), - service.WithMailingListWriter(GrpsIOWriter(ctx)), - service.WithMemberReader(GrpsIOReader(ctx)), - service.WithMemberWriter(GrpsIOWriter(ctx)), - ) -} - -// GetNATSClient returns the initialized NATS client for subscriptions -func GetNATSClient(ctx context.Context) *nats.NATSClient { - natsInit(ctx) - // Access the client through storage adapter - storageImpl, ok := natsStorageClient.(interface{ Client() *nats.NATSClient }) - if !ok { - slog.ErrorContext(ctx, "NATS storage does not implement Client() method") - panic("NATS storage implementation error") - } - return storageImpl.Client() +// GroupsioMemberService initializes the GroupsIO member handler. +func GroupsioMemberService(ctx context.Context, client domain.ITXGroupsioClient) *itxsvc.GroupsioMemberService { + slog.InfoContext(ctx, "initializing GroupsIO member service") + return itxsvc.NewGroupsioMemberService(client) } diff --git a/cmd/mailing-list-api/service/service_payload_converters.go b/cmd/mailing-list-api/service/service_payload_converters.go deleted file mode 100644 index cca280e..0000000 --- a/cmd/mailing-list-api/service/service_payload_converters.go +++ /dev/null @@ -1,365 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "time" - - mailinglistservice "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" -) - -// convertGrpsIOServiceCreatePayloadToDomain converts GOA payload to domain model -// convertPayloadToDomain -func (s *mailingListService) convertGrpsIOServiceCreatePayloadToDomain(p *mailinglistservice.CreateGrpsioServicePayload) *model.GrpsIOService { - // Check for nil payload to avoid panic - if p == nil { - return &model.GrpsIOService{} - } - - now := time.Now() - service := &model.GrpsIOService{ - Type: p.Type, - Domain: payloadStringValue(p.Domain), - GroupID: payloadInt64Ptr(p.GroupID), - Status: payloadStringValue(p.Status), - GlobalOwners: p.GlobalOwners, - Prefix: payloadStringValue(p.Prefix), - ParentServiceUID: payloadStringValue(p.ParentServiceUID), - ProjectSlug: payloadStringValue(p.ProjectSlug), - ProjectUID: p.ProjectUID, - URL: payloadStringValue(p.URL), - GroupName: payloadStringValue(p.GroupName), - Public: p.Public, - Source: constants.SourceAPI, // API operations always use api source - CreatedAt: now, - UpdatedAt: now, - } - - return service -} - -// convertGrpsIOServiceCreatePayloadToSettings extracts writers/auditors from create payload to settings model -func (s *mailingListService) convertGrpsIOServiceCreatePayloadToSettings(p *mailinglistservice.CreateGrpsioServicePayload, serviceUID string) *model.GrpsIOServiceSettings { - if p == nil { - return nil - } - - now := time.Now() - settings := &model.GrpsIOServiceSettings{ - UID: serviceUID, - Writers: convertUserInfoPayloadToDomain(p.Writers), - Auditors: convertUserInfoPayloadToDomain(p.Auditors), - CreatedAt: now, - UpdatedAt: now, - } - - return settings -} - -// convertGrpsIOMailingListPayloadToDomain converts GOA mailing list payload to domain model -func (s *mailingListService) convertGrpsIOMailingListPayloadToDomain(p *mailinglistservice.CreateGrpsioMailingListPayload) *model.GrpsIOMailingList { - // Check for nil payload to avoid panic - if p == nil { - return &model.GrpsIOMailingList{} - } - - now := time.Now() - - mailingList := &model.GrpsIOMailingList{ - GroupName: p.GroupName, - Public: p.Public, - Type: p.Type, - AudienceAccess: p.AudienceAccess, - Committees: convertCommitteesToDomain(p.Committees), - Description: p.Description, - Title: p.Title, - SubjectTag: payloadStringValue(p.SubjectTag), - ServiceUID: p.ServiceUID, - // project_uid is intentionally NOT set here - it will be inherited from parent in orchestrator - Source: constants.SourceAPI, // API operations always use api source - CreatedAt: now, - UpdatedAt: now, - } - - return mailingList -} - -// convertCommitteesToDomain converts GOA Committee array to domain model Committee array -func convertCommitteesToDomain(committees []*mailinglistservice.Committee) []model.Committee { - if committees == nil { - return nil - } - - result := make([]model.Committee, 0, len(committees)) - for _, c := range committees { - if c == nil { - continue - } - result = append(result, model.Committee{ - UID: c.UID, - Name: payloadStringValue(c.Name), // Name is read-only, but may be passed through - AllowedVotingStatuses: c.AllowedVotingStatuses, - }) - } - return result -} - -// convertGrpsIOServiceUpdatePayloadToDomain converts GOA update payload to domain model -func (s *mailingListService) convertGrpsIOServiceUpdatePayloadToDomain(existing *model.GrpsIOService, p *mailinglistservice.UpdateGrpsioServicePayload) *model.GrpsIOService { - // Check for nil payload or existing to avoid panic - if p == nil || p.UID == nil || existing == nil { - return &model.GrpsIOService{} - } - - now := time.Now() - return &model.GrpsIOService{ - // Preserve ALL immutable fields from existing service - UID: *p.UID, - Type: existing.Type, // Fixed: preserve from existing, not payload - Domain: existing.Domain, - GroupID: existing.GroupID, - Prefix: existing.Prefix, - ProjectSlug: existing.ProjectSlug, - ProjectName: existing.ProjectName, - ParentServiceUID: existing.ParentServiceUID, - URL: existing.URL, // Fixed: add missing field preservation - GroupName: existing.GroupName, // Fixed: add missing field preservation - CreatedAt: existing.CreatedAt, - - // Update mutable fields (PUT semantics - complete replacement) - Status: payloadStringValue(p.Status), // nil → "" - ProjectUID: existing.ProjectUID, // IMMUTABLE (keep as is) - Public: p.Public, // Direct assignment - GlobalOwners: p.GlobalOwners, // nil → nil - UpdatedAt: now, - } -} - -// convertGrpsIOMailingListUpdatePayloadToDomain converts an update payload to domain model -func (s *mailingListService) convertGrpsIOMailingListUpdatePayloadToDomain(existing *model.GrpsIOMailingList, payload *mailinglistservice.UpdateGrpsioMailingListPayload) *model.GrpsIOMailingList { - // Create updated mailing list from payload data (PUT semantics) - return &model.GrpsIOMailingList{ - // Preserve immutable/readonly fields - UID: existing.UID, - GroupID: existing.GroupID, // Preserve Groups.io group ID - GroupName: existing.GroupName, // Fixed: GroupName is immutable, preserve from existing - ProjectUID: existing.ProjectUID, - ProjectName: existing.ProjectName, - ProjectSlug: existing.ProjectSlug, - Source: existing.Source, // Preserve source tracking - CreatedAt: existing.CreatedAt, - - // Update all mutable fields (PUT semantics - complete replacement) - Public: payload.Public, // Direct assignment - AudienceAccess: payload.AudienceAccess, // Direct assignment - Type: payload.Type, // Direct assignment - Description: payload.Description, // Direct assignment - Title: payload.Title, // Direct assignment - ServiceUID: payload.ServiceUID, // Direct assignment - Committees: convertCommitteesToDomain(payload.Committees), // nil → nil - SubjectTag: payloadStringValue(payload.SubjectTag), // nil → "" - UpdatedAt: time.Now().UTC(), - } -} - -// convertGrpsIOMemberPayloadToDomain converts GOA member payload to domain model -func (s *mailingListService) convertGrpsIOMemberPayloadToDomain(payload *mailinglistservice.CreateGrpsioMailingListMemberPayload) *model.GrpsIOMember { - now := time.Now().UTC() - member := &model.GrpsIOMember{ - MailingListUID: payload.UID, - Email: payload.Email, - MemberType: payload.MemberType, - DeliveryMode: payload.DeliveryMode, - ModStatus: payload.ModStatus, - Status: "normal", - Source: constants.SourceAPI, // API operations always use api source - CreatedAt: now, - UpdatedAt: now, - } - - // Handle required fields that might be pointers - if payload.FirstName != nil { - member.FirstName = *payload.FirstName - } - if payload.LastName != nil { - member.LastName = *payload.LastName - } - - // Handle optional fields - if payload.Username != nil { - member.Username = *payload.Username - } - if payload.Organization != nil { - member.Organization = *payload.Organization - } - if payload.JobTitle != nil { - member.JobTitle = *payload.JobTitle - } - if payload.LastReviewedAt != nil { - member.LastReviewedAt = payload.LastReviewedAt - } - if payload.LastReviewedBy != nil { - member.LastReviewedBy = payload.LastReviewedBy - } - - return member -} - -// convertGrpsIOMemberUpdatePayloadToDomain converts update payload to domain member model -func (s *mailingListService) convertGrpsIOMemberUpdatePayloadToDomain(payload *mailinglistservice.UpdateGrpsioMailingListMemberPayload, existing *model.GrpsIOMember) *model.GrpsIOMember { - // Create updated member from payload data (PUT semantics) - return &model.GrpsIOMember{ - // Preserve immutable fields - UID: existing.UID, - MailingListUID: existing.MailingListUID, - Email: existing.Email, // Immutable - MemberType: existing.MemberType, // Immutable for now - MemberID: existing.MemberID, - GroupID: existing.GroupID, - CreatedAt: existing.CreatedAt, - Status: existing.Status, - LastReviewedAt: existing.LastReviewedAt, - LastReviewedBy: existing.LastReviewedBy, - - // Update all mutable fields (PUT semantics - complete replacement) - Username: payloadStringValue(payload.Username), // nil → "" - FirstName: payloadStringValue(payload.FirstName), // nil → "" - LastName: payloadStringValue(payload.LastName), // nil → "" - Organization: payloadStringValue(payload.Organization), // nil → "" - JobTitle: payloadStringValue(payload.JobTitle), // nil → "" - DeliveryMode: payload.DeliveryMode, // Direct (always has value) - ModStatus: payload.ModStatus, // Direct (always has value) - UpdatedAt: time.Now().UTC(), - } -} - -// convertWebhookGroupInfo converts webhook group data to domain model -func (s *mailingListService) convertWebhookGroupInfo(m map[string]any) (*model.GroupInfo, error) { - if m == nil { - return nil, errors.NewValidation("group info is nil") - } - - group := &model.GroupInfo{} - - // Required field: ID - if id, ok := m["id"].(float64); ok { - group.ID = int(id) - } else { - return nil, errors.NewValidation("group id is missing or invalid") - } - - // Required field: Name - if name, ok := m["name"].(string); ok { - group.Name = name - } else { - return nil, errors.NewValidation("group name is missing or invalid") - } - - // Required field: ParentGroupID - if parentGroupID, ok := m["parent_group_id"].(float64); ok { - group.ParentGroupID = int(parentGroupID) - } else { - return nil, errors.NewValidation("parent_group_id is missing or invalid") - } - - return group, nil -} - -// convertWebhookMemberInfo converts webhook member data to domain model -func (s *mailingListService) convertWebhookMemberInfo(m map[string]any) (*model.MemberInfo, error) { - if m == nil { - return nil, errors.NewValidation("member info is nil") - } - - member := &model.MemberInfo{} - - // Required field: ID - if id, ok := m["id"].(float64); ok { - member.ID = int(id) - } else { - return nil, errors.NewValidation("member id is missing or invalid") - } - - // Required field: GroupID - if groupID, ok := m["group_id"].(float64); ok { - member.GroupID = uint64(groupID) - } else { - return nil, errors.NewValidation("group_id is missing or invalid") - } - - // Required field: Email - if email, ok := m["email"].(string); ok { - member.Email = email - } else { - return nil, errors.NewValidation("email is missing or invalid") - } - - // Required field: Status - if status, ok := m["status"].(string); ok { - member.Status = status - } else { - return nil, errors.NewValidation("status is missing or invalid") - } - - // Optional fields: UserID, GroupName - if userID, ok := m["user_id"].(float64); ok { - member.UserID = int(userID) - } - if groupName, ok := m["group_name"].(string); ok { - member.GroupName = groupName - } - - return member, nil -} - -// convertGrpsIOServiceSettingsPayloadToDomain converts GOA settings payload to domain model -func (s *mailingListService) convertGrpsIOServiceSettingsPayloadToDomain(payload *mailinglistservice.UpdateGrpsioServiceSettingsPayload) *model.GrpsIOServiceSettings { - settings := &model.GrpsIOServiceSettings{ - UID: payload.UID, - Writers: convertUserInfoPayloadToDomain(payload.Writers), - Auditors: convertUserInfoPayloadToDomain(payload.Auditors), - UpdatedAt: time.Now().UTC(), - } - - return settings -} - -// convertUserInfoPayloadToDomain converts GOA UserInfo array to domain UserInfo array -func convertUserInfoPayloadToDomain(goaUsers []*mailinglistservice.UserInfo) []model.UserInfo { - if goaUsers == nil { - return []model.UserInfo{} - } - - users := make([]model.UserInfo, 0, len(goaUsers)) - for _, u := range goaUsers { - // Skip nil entries to avoid panic - if u == nil { - continue - } - - user := model.UserInfo{ - Name: u.Name, - Email: u.Email, - Username: u.Username, - Avatar: u.Avatar, - } - users = append(users, user) - } - return users -} - -// convertGrpsIOMailingListSettingsPayloadToDomain converts GOA mailing list settings payload to domain model -func (s *mailingListService) convertGrpsIOMailingListSettingsPayloadToDomain(payload *mailinglistservice.UpdateGrpsioMailingListSettingsPayload) *model.GrpsIOMailingListSettings { - settings := &model.GrpsIOMailingListSettings{ - UID: payload.UID, - Writers: convertUserInfoPayloadToDomain(payload.Writers), - Auditors: convertUserInfoPayloadToDomain(payload.Auditors), - UpdatedAt: time.Now().UTC(), - } - - return settings -} diff --git a/cmd/mailing-list-api/service/service_payload_converters_test.go b/cmd/mailing-list-api/service/service_payload_converters_test.go deleted file mode 100644 index 1b48cc1..0000000 --- a/cmd/mailing-list-api/service/service_payload_converters_test.go +++ /dev/null @@ -1,1403 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "testing" - "time" - - mailinglistservice "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/stretchr/testify/assert" -) - -func TestConvertCreatePayloadToDomain(t *testing.T) { - tests := []struct { - name string - payload *mailinglistservice.CreateGrpsioServicePayload - expected *model.GrpsIOService - }{ - { - name: "complete payload conversion", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "primary", - Domain: stringPtr("example.groups.io"), - GroupID: int64Ptr(12345), - Status: stringPtr("active"), - GlobalOwners: []string{"owner1@example.com", "owner2@example.com"}, - Prefix: stringPtr("test-prefix"), - ProjectSlug: stringPtr("test-project"), - ProjectUID: "project-123", - URL: stringPtr("https://example.groups.io/g/test-group"), - GroupName: stringPtr("test-group"), - Public: true, - Writers: []*mailinglistservice.UserInfo{{Name: stringPtr("Writer One")}, {Name: stringPtr("Writer Two")}}, - Auditors: []*mailinglistservice.UserInfo{{Name: stringPtr("Auditor One")}, {Name: stringPtr("Auditor Two")}}, - }, - expected: &model.GrpsIOService{ - Type: "primary", - Domain: "example.groups.io", - GroupID: int64Ptr(12345), - Status: "active", - GlobalOwners: []string{"owner1@example.com", "owner2@example.com"}, - Prefix: "test-prefix", - ProjectSlug: "test-project", - ProjectUID: "project-123", - URL: "https://example.groups.io/g/test-group", - GroupName: "test-group", - Public: true, - }, - }, - { - name: "minimal payload conversion", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "formation", - ProjectUID: "project-456", - Public: false, - }, - expected: &model.GrpsIOService{ - Type: "formation", - Domain: "", - GroupID: nil, - Status: "", - GlobalOwners: nil, - Prefix: "", - ProjectSlug: "", - ProjectUID: "project-456", - URL: "", - GroupName: "", - Public: false, - }, - }, - { - name: "nil payload", - payload: nil, - expected: &model.GrpsIOService{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &mailingListService{} - result := svc.convertGrpsIOServiceCreatePayloadToDomain(tt.payload) - - // Check all fields except timestamps - assert.Equal(t, tt.expected.Type, result.Type) - assert.Equal(t, tt.expected.Domain, result.Domain) - assert.Equal(t, tt.expected.GroupID, result.GroupID) - assert.Equal(t, tt.expected.Status, result.Status) - assert.Equal(t, tt.expected.GlobalOwners, result.GlobalOwners) - assert.Equal(t, tt.expected.Prefix, result.Prefix) - assert.Equal(t, tt.expected.ProjectSlug, result.ProjectSlug) - assert.Equal(t, tt.expected.ProjectUID, result.ProjectUID) - assert.Equal(t, tt.expected.URL, result.URL) - assert.Equal(t, tt.expected.GroupName, result.GroupName) - assert.Equal(t, tt.expected.Public, result.Public) - - // Verify timestamps are set for non-nil payloads - if tt.payload != nil { - assert.False(t, result.CreatedAt.IsZero()) - assert.False(t, result.UpdatedAt.IsZero()) - } - }) - } -} - -func TestConvertMailingListPayloadToDomain(t *testing.T) { - tests := []struct { - name string - payload *mailinglistservice.CreateGrpsioMailingListPayload - expected *model.GrpsIOMailingList - }{ - { - name: "complete mailing list payload conversion", - payload: &mailinglistservice.CreateGrpsioMailingListPayload{ - GroupName: "test-mailing-list", - Public: true, - Type: "discussion_open", - Committees: []*mailinglistservice.Committee{ - {UID: "committee-123", AllowedVotingStatuses: []string{"Voting Rep", "Observer"}}, - }, - Description: "This is a test mailing list description", - Title: "Test Mailing List", - SubjectTag: stringPtr("[TEST]"), - ServiceUID: "parent-service-456", - }, - expected: &model.GrpsIOMailingList{ - GroupName: "test-mailing-list", - Public: true, - Type: "discussion_open", - Committees: []model.Committee{ - {UID: "committee-123", AllowedVotingStatuses: []string{"Voting Rep", "Observer"}}, - }, - Description: "This is a test mailing list description", - Title: "Test Mailing List", - SubjectTag: "[TEST]", - ServiceUID: "parent-service-456", - }, - }, - { - name: "minimal mailing list payload conversion", - payload: &mailinglistservice.CreateGrpsioMailingListPayload{ - GroupName: "minimal-list", - Public: false, - Type: "announcement", - Description: "Minimal description for testing", - Title: "Minimal List", - ServiceUID: "parent-789", - }, - expected: &model.GrpsIOMailingList{ - GroupName: "minimal-list", - Public: false, - Type: "announcement", - Committees: nil, - Description: "Minimal description for testing", - Title: "Minimal List", - SubjectTag: "", - ServiceUID: "parent-789", - }, - }, - { - name: "nil mailing list payload", - payload: nil, - expected: &model.GrpsIOMailingList{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &mailingListService{} - result := svc.convertGrpsIOMailingListPayloadToDomain(tt.payload) - - // Check all fields except timestamps - assert.Equal(t, tt.expected.GroupName, result.GroupName) - assert.Equal(t, tt.expected.Public, result.Public) - assert.Equal(t, tt.expected.Type, result.Type) - assert.Equal(t, len(tt.expected.Committees), len(result.Committees)) - for i := range tt.expected.Committees { - assert.Equal(t, tt.expected.Committees[i].UID, result.Committees[i].UID) - assert.Equal(t, tt.expected.Committees[i].AllowedVotingStatuses, result.Committees[i].AllowedVotingStatuses) - } - assert.Equal(t, tt.expected.Description, result.Description) - assert.Equal(t, tt.expected.Title, result.Title) - assert.Equal(t, tt.expected.SubjectTag, result.SubjectTag) - assert.Equal(t, tt.expected.ServiceUID, result.ServiceUID) - - // Verify timestamps are set for non-nil payloads - if tt.payload != nil { - assert.False(t, result.CreatedAt.IsZero()) - assert.False(t, result.UpdatedAt.IsZero()) - } - }) - } -} - -func TestConvertUpdatePayloadToDomain(t *testing.T) { - baseTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) - - tests := []struct { - name string - existing *model.GrpsIOService - payload *mailinglistservice.UpdateGrpsioServicePayload - expected *model.GrpsIOService - }{ - { - name: "complete update payload conversion", - existing: &model.GrpsIOService{ - Type: "primary", - UID: "service-123", - Domain: "example.groups.io", - GroupID: int64Ptr(12345), - Prefix: "", - ProjectSlug: "test-project", - ProjectName: "Test Project", - ProjectUID: "project-123", - URL: "https://example.groups.io/g/test", - GroupName: "test-group", - CreatedAt: baseTime, - }, - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - UID: stringPtr("service-123"), - Status: stringPtr("inactive"), - GlobalOwners: []string{"newowner@example.com"}, - Public: true, - }, - expected: &model.GrpsIOService{ - Type: "primary", - UID: "service-123", - Domain: "example.groups.io", - GroupID: int64Ptr(12345), - Status: "inactive", - GlobalOwners: []string{"newowner@example.com"}, - Prefix: "", - ProjectSlug: "test-project", - ProjectName: "Test Project", - ProjectUID: "project-123", - URL: "https://example.groups.io/g/test", - GroupName: "test-group", - Public: true, - CreatedAt: baseTime, - }, - }, - { - name: "minimal update payload conversion", - existing: &model.GrpsIOService{ - Type: "formation", - UID: "service-456", - Domain: "test.groups.io", - GroupID: int64Ptr(67890), - ProjectUID: "project-456", - ProjectSlug: "test-formation", - CreatedAt: baseTime, - }, - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - UID: stringPtr("service-456"), - Public: false, - }, - expected: &model.GrpsIOService{ - Type: "formation", - UID: "service-456", - Domain: "test.groups.io", - GroupID: int64Ptr(67890), - Status: "", - ProjectUID: "project-456", - ProjectSlug: "test-formation", - Public: false, - CreatedAt: baseTime, - }, - }, - { - name: "nil payload", - existing: nil, - payload: nil, - expected: &model.GrpsIOService{}, - }, - { - name: "nil UID in payload", - existing: &model.GrpsIOService{UID: "test-123"}, - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - UID: nil, - }, - expected: &model.GrpsIOService{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &mailingListService{} - result := svc.convertGrpsIOServiceUpdatePayloadToDomain(tt.existing, tt.payload) - - // Check all fields except UpdatedAt timestamp - assert.Equal(t, tt.expected.Type, result.Type) - assert.Equal(t, tt.expected.UID, result.UID) - assert.Equal(t, tt.expected.Domain, result.Domain) - assert.Equal(t, tt.expected.GroupID, result.GroupID) - assert.Equal(t, tt.expected.Status, result.Status) - assert.Equal(t, tt.expected.GlobalOwners, result.GlobalOwners) - assert.Equal(t, tt.expected.Prefix, result.Prefix) - assert.Equal(t, tt.expected.ProjectSlug, result.ProjectSlug) - assert.Equal(t, tt.expected.ProjectName, result.ProjectName) - assert.Equal(t, tt.expected.ProjectUID, result.ProjectUID) - assert.Equal(t, tt.expected.URL, result.URL) - assert.Equal(t, tt.expected.GroupName, result.GroupName) - assert.Equal(t, tt.expected.Public, result.Public) - assert.Equal(t, tt.expected.CreatedAt, result.CreatedAt) - - // Verify UpdatedAt is set for valid payloads - if tt.payload != nil && tt.payload.UID != nil && tt.existing != nil { - assert.False(t, result.UpdatedAt.IsZero()) - } - }) - } -} - -func TestConvertMemberUpdatePayloadToDomain(t *testing.T) { - now := time.Now().UTC() - existingMember := &model.GrpsIOMember{ - UID: "member-123", - MailingListUID: "ml-456", - Email: "existing@example.com", - Username: "existinguser", - FirstName: "Existing", - LastName: "User", - Organization: "Existing Corp", - JobTitle: "Existing Engineer", - DeliveryMode: "digest", // Existing preference - ModStatus: "moderator", // Existing permission - MemberType: "direct", - Status: "active", - CreatedAt: now.Add(-24 * time.Hour), - UpdatedAt: now.Add(-1 * time.Hour), - } - - tests := []struct { - name string - existing *model.GrpsIOMember - payload *mailinglistservice.UpdateGrpsioMailingListMemberPayload - expected *model.GrpsIOMember - }{ - { - name: "partial update - only name fields, clear other mutable fields (PUT semantics)", - existing: existingMember, - payload: &mailinglistservice.UpdateGrpsioMailingListMemberPayload{ - FirstName: stringPtr("Updated"), - LastName: stringPtr("Name"), - // DeliveryMode and ModStatus are nil - PUT semantics clears them to "" - }, - expected: &model.GrpsIOMember{ - UID: "member-123", - MailingListUID: "ml-456", - Email: "existing@example.com", - Username: "", // CLEARED (PUT semantics) - FirstName: "Updated", // Updated - LastName: "Name", // Updated - Organization: "", // CLEARED (PUT semantics) - JobTitle: "", // CLEARED (PUT semantics) - DeliveryMode: "", // CLEARED (PUT semantics) - ModStatus: "", // CLEARED (PUT semantics) - MemberType: "direct", // IMMUTABLE - Status: "active", // IMMUTABLE - CreatedAt: existingMember.CreatedAt, - }, - }, - { - name: "partial update - only delivery mode, clear other mutable fields (PUT semantics)", - existing: existingMember, - payload: &mailinglistservice.UpdateGrpsioMailingListMemberPayload{ - DeliveryMode: "normal", - // All other fields nil - PUT semantics clears them to "" - }, - expected: &model.GrpsIOMember{ - UID: "member-123", - MailingListUID: "ml-456", - Email: "existing@example.com", - Username: "", // CLEARED (PUT semantics) - FirstName: "", // CLEARED (PUT semantics) - LastName: "", // CLEARED (PUT semantics) - Organization: "", // CLEARED (PUT semantics) - JobTitle: "", // CLEARED (PUT semantics) - DeliveryMode: "normal", // Updated - ModStatus: "", // CLEARED (PUT semantics) - MemberType: "direct", // IMMUTABLE - Status: "active", // IMMUTABLE - CreatedAt: existingMember.CreatedAt, - }, - }, - { - name: "complete update - all fields provided", - existing: existingMember, - payload: &mailinglistservice.UpdateGrpsioMailingListMemberPayload{ - Username: stringPtr("newuser"), - FirstName: stringPtr("New"), - LastName: stringPtr("Person"), - Organization: stringPtr("New Corp"), - JobTitle: stringPtr("New Role"), - DeliveryMode: "none", - ModStatus: "owner", - }, - expected: &model.GrpsIOMember{ - UID: "member-123", - MailingListUID: "ml-456", - Email: "existing@example.com", // Immutable - Username: "newuser", // Updated - FirstName: "New", // Updated - LastName: "Person", // Updated - Organization: "New Corp", // Updated - JobTitle: "New Role", // Updated - DeliveryMode: "none", // Updated - ModStatus: "owner", // Updated - MemberType: "direct", // Immutable - Status: "active", // Immutable - CreatedAt: existingMember.CreatedAt, // Immutable - }, - }, - { - name: "empty update - no fields provided, all mutable fields cleared (PUT semantics)", - existing: existingMember, - payload: &mailinglistservice.UpdateGrpsioMailingListMemberPayload{ - // All fields nil - PUT semantics clears all mutable fields - }, - expected: &model.GrpsIOMember{ - UID: "member-123", - MailingListUID: "ml-456", - Email: "existing@example.com", - Username: "", // CLEARED (PUT semantics) - FirstName: "", // CLEARED (PUT semantics) - LastName: "", // CLEARED (PUT semantics) - Organization: "", // CLEARED (PUT semantics) - JobTitle: "", // CLEARED (PUT semantics) - DeliveryMode: "", // CLEARED (PUT semantics) - ModStatus: "", // CLEARED (PUT semantics) - MemberType: "direct", // IMMUTABLE - Status: "active", // IMMUTABLE - CreatedAt: existingMember.CreatedAt, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &mailingListService{} - result := svc.convertGrpsIOMemberUpdatePayloadToDomain(tt.payload, tt.existing) - - // Check all fields except UpdatedAt timestamp - assert.Equal(t, tt.expected.UID, result.UID) - assert.Equal(t, tt.expected.MailingListUID, result.MailingListUID) - assert.Equal(t, tt.expected.Email, result.Email) - assert.Equal(t, tt.expected.Username, result.Username) - assert.Equal(t, tt.expected.FirstName, result.FirstName) - assert.Equal(t, tt.expected.LastName, result.LastName) - assert.Equal(t, tt.expected.Organization, result.Organization) - assert.Equal(t, tt.expected.JobTitle, result.JobTitle) - assert.Equal(t, tt.expected.DeliveryMode, result.DeliveryMode, "DeliveryMode should follow PUT semantics (nil clears to empty)") - assert.Equal(t, tt.expected.ModStatus, result.ModStatus, "ModStatus should follow PUT semantics (nil clears to empty)") - assert.Equal(t, tt.expected.MemberType, result.MemberType) - assert.Equal(t, tt.expected.Status, result.Status) - assert.Equal(t, tt.expected.CreatedAt, result.CreatedAt) - - // Verify UpdatedAt is set to current time - assert.False(t, result.UpdatedAt.IsZero(), "UpdatedAt should be set") - assert.True(t, result.UpdatedAt.After(tt.existing.UpdatedAt), "UpdatedAt should be newer than existing") - }) - } -} - -func TestConvertServiceUpdatePayloadToDomain(t *testing.T) { - now := time.Now().UTC() - existingService := &model.GrpsIOService{ - UID: "service-123", - Type: "primary", - Domain: "existing.domain.com", - GroupID: int64Ptr(12345), - Status: "active", - Public: true, // Existing setting - GlobalOwners: []string{"existing@example.com"}, - ProjectUID: "project-123", - CreatedAt: now.Add(-24 * time.Hour), - UpdatedAt: now.Add(-1 * time.Hour), - } - - tests := []struct { - name string - existing *model.GrpsIOService - payload *mailinglistservice.UpdateGrpsioServicePayload - expected *model.GrpsIOService - }{ - { - name: "partial update - only status, clear other mutable fields (PUT semantics)", - existing: existingService, - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - UID: stringPtr("service-123"), - Status: stringPtr("updated"), - // Public is nil - PUT semantics clears it to false - }, - expected: &model.GrpsIOService{ - UID: "service-123", - Type: "primary", // IMMUTABLE - Domain: "existing.domain.com", // IMMUTABLE - GroupID: int64Ptr(12345), // IMMUTABLE - Status: "updated", // Updated - Public: false, // CLEARED (PUT semantics) - GlobalOwners: nil, // CLEARED (PUT semantics) - ProjectUID: "project-123", // IMMUTABLE - CreatedAt: existingService.CreatedAt, // IMMUTABLE - }, - }, - { - name: "partial update - only public field, clear other mutable fields (PUT semantics)", - existing: existingService, - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - UID: stringPtr("service-123"), - Public: false, - // All other fields nil - PUT semantics clears mutable fields - }, - expected: &model.GrpsIOService{ - UID: "service-123", - Type: "primary", // IMMUTABLE - Domain: "existing.domain.com", // IMMUTABLE - GroupID: int64Ptr(12345), // IMMUTABLE - Status: "", // CLEARED (PUT semantics) - Public: false, // Updated - GlobalOwners: nil, // CLEARED (PUT semantics) - ProjectUID: "project-123", // IMMUTABLE - CreatedAt: existingService.CreatedAt, // IMMUTABLE - }, - }, - { - name: "complete update - all mutable fields provided", - existing: existingService, - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - UID: stringPtr("service-123"), - Type: "formation", - Status: stringPtr("disabled"), - Public: false, - GlobalOwners: []string{"new1@example.com", "new2@example.com"}, - ProjectUID: "new-project-456", - }, - expected: &model.GrpsIOService{ - UID: "service-123", - Type: "primary", // IMMUTABLE (can't change service type) - Domain: "existing.domain.com", // IMMUTABLE - GroupID: int64Ptr(12345), // IMMUTABLE - Status: "disabled", // Updated - Public: false, // Updated - GlobalOwners: []string{"new1@example.com", "new2@example.com"}, // Updated - ProjectUID: "project-123", // IMMUTABLE (can't change project) - CreatedAt: existingService.CreatedAt, // IMMUTABLE - }, - }, - { - name: "empty update - no fields provided, all mutable fields cleared (PUT semantics)", - existing: existingService, - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - UID: stringPtr("service-123"), - // All fields nil - PUT semantics clears all mutable fields - }, - expected: &model.GrpsIOService{ - UID: "service-123", - Type: "primary", // IMMUTABLE - Domain: "existing.domain.com", // IMMUTABLE - GroupID: int64Ptr(12345), // IMMUTABLE - Status: "", // CLEARED (PUT semantics) - Public: false, // CLEARED to default false (PUT semantics) - GlobalOwners: nil, // CLEARED (PUT semantics) - ProjectUID: "project-123", // IMMUTABLE - CreatedAt: existingService.CreatedAt, // IMMUTABLE - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &mailingListService{} - result := svc.convertGrpsIOServiceUpdatePayloadToDomain(tt.existing, tt.payload) - - // Check all fields except UpdatedAt timestamp - assert.Equal(t, tt.expected.UID, result.UID) - assert.Equal(t, tt.expected.Type, result.Type) - assert.Equal(t, tt.expected.Domain, result.Domain) - assert.Equal(t, tt.expected.GroupID, result.GroupID) - assert.Equal(t, tt.expected.Status, result.Status) - assert.Equal(t, tt.expected.Public, result.Public, "Public field should follow PUT semantics (nil clears to false)") - assert.Equal(t, tt.expected.GlobalOwners, result.GlobalOwners) - assert.Equal(t, tt.expected.ProjectUID, result.ProjectUID) - assert.Equal(t, tt.expected.CreatedAt, result.CreatedAt) - - // Verify UpdatedAt is set to current time - assert.False(t, result.UpdatedAt.IsZero(), "UpdatedAt should be set") - assert.True(t, result.UpdatedAt.After(tt.existing.UpdatedAt), "UpdatedAt should be newer than existing") - }) - } -} - -func TestConvertMailingListUpdatePayloadToDomain(t *testing.T) { - now := time.Now().UTC() - existingMailingList := &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "existing-group", - Public: false, // Existing setting - Type: "discussion_moderated", - Description: "Existing description for the group", - Title: "Existing Title", - ServiceUID: "service-123", - ProjectUID: "project-123", - CreatedAt: now.Add(-24 * time.Hour), - UpdatedAt: now.Add(-1 * time.Hour), - } - - tests := []struct { - name string - existing *model.GrpsIOMailingList - payload *mailinglistservice.UpdateGrpsioMailingListPayload - expected *model.GrpsIOMailingList - }{ - { - name: "partial update - only title, clear other mutable fields (PUT semantics)", - existing: existingMailingList, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - Title: "Updated Title", - // All other fields nil - PUT semantics clears mutable fields - }, - expected: &model.GrpsIOMailingList{ - UID: "ml-123", // IMMUTABLE - GroupName: "existing-group", // IMMUTABLE - Public: false, // CLEARED to default false - Type: "", // CLEARED (PUT semantics) - Description: "", // CLEARED (PUT semantics) - Title: "Updated Title", // Updated - ServiceUID: "", // CLEARED (PUT semantics) - ProjectUID: "project-123", // IMMUTABLE - CreatedAt: existingMailingList.CreatedAt, // IMMUTABLE - }, - }, - { - name: "partial update - only public field, clear other mutable fields (PUT semantics)", - existing: existingMailingList, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - Public: true, - // All other fields nil - PUT semantics clears mutable fields - }, - expected: &model.GrpsIOMailingList{ - UID: "ml-123", // IMMUTABLE - GroupName: "existing-group", // IMMUTABLE - Public: true, // Updated - Type: "", // CLEARED (PUT semantics) - Description: "", // CLEARED (PUT semantics) - Title: "", // CLEARED (PUT semantics) - ServiceUID: "", // CLEARED (PUT semantics) - ProjectUID: "project-123", // IMMUTABLE - CreatedAt: existingMailingList.CreatedAt, // IMMUTABLE - }, - }, - { - name: "complete update - all fields provided", - existing: existingMailingList, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - GroupName: "new-group", - Public: true, - Type: "discussion_open", - Description: "New description that is long enough", - Title: "New Title", - ServiceUID: "new-service-456", - }, - expected: &model.GrpsIOMailingList{ - UID: "ml-123", // IMMUTABLE - GroupName: "existing-group", // IMMUTABLE (can't change group name) - Public: true, // Updated - Type: "discussion_open", // Updated - Description: "New description that is long enough", // Updated - Title: "New Title", // Updated - ServiceUID: "new-service-456", // Updated - ProjectUID: "project-123", // IMMUTABLE - CreatedAt: existingMailingList.CreatedAt, // PRESERVED (immutable) - }, - }, - { - name: "empty update - no fields provided, all mutable fields cleared (PUT semantics)", - existing: existingMailingList, - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - // All fields nil - PUT semantics clears all mutable fields - }, - expected: &model.GrpsIOMailingList{ - UID: "ml-123", // IMMUTABLE - GroupName: "existing-group", // IMMUTABLE - Public: false, // CLEARED to default false - Type: "", // CLEARED (PUT semantics) - Description: "", // CLEARED (PUT semantics) - Title: "", // CLEARED (PUT semantics) - ServiceUID: "", // CLEARED (PUT semantics) - ProjectUID: "project-123", // IMMUTABLE - CreatedAt: existingMailingList.CreatedAt, // IMMUTABLE - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &mailingListService{} - result := svc.convertGrpsIOMailingListUpdatePayloadToDomain(tt.existing, tt.payload) - - // Check all fields except UpdatedAt timestamp - assert.Equal(t, tt.expected.UID, result.UID) - assert.Equal(t, tt.expected.GroupName, result.GroupName) - assert.Equal(t, tt.expected.Public, result.Public, "Public field should follow PUT semantics (nil clears to false)") - assert.Equal(t, tt.expected.Type, result.Type) - assert.Equal(t, tt.expected.Description, result.Description) - assert.Equal(t, tt.expected.Title, result.Title) - assert.Equal(t, tt.expected.ServiceUID, result.ServiceUID) - assert.Equal(t, tt.expected.ProjectUID, result.ProjectUID) - assert.Equal(t, tt.expected.CreatedAt, result.CreatedAt) - - // Verify UpdatedAt is set to current time - assert.False(t, result.UpdatedAt.IsZero(), "UpdatedAt should be set") - assert.True(t, result.UpdatedAt.After(tt.existing.UpdatedAt), "UpdatedAt should be newer than existing") - }) - } -} - -// Helper functions for creating pointers to primitives -func stringPtr(s string) *string { - return &s -} - -func int64Ptr(i int64) *int64 { - return &i -} - -func boolPtr(b bool) *bool { - return &b -} - -// TestMailingListService_convertWebhookGroupInfo tests the webhook group info converter -func TestMailingListService_convertWebhookGroupInfo(t *testing.T) { - s := &mailingListService{} - - tests := []struct { - name string - input map[string]any - expected *model.GroupInfo - expectError bool - }{ - { - name: "valid group info with all fields", - input: map[string]any{ - "id": float64(12345), - "name": "test-group", - "parent_group_id": float64(67890), - }, - expected: &model.GroupInfo{ - ID: 12345, - Name: "test-group", - ParentGroupID: 67890, - }, - expectError: false, - }, - { - name: "nil input", - input: nil, - expected: nil, - expectError: true, - }, - { - name: "missing id field", - input: map[string]any{ - "name": "test-group", - "parent_group_id": float64(67890), - }, - expected: nil, - expectError: true, - }, - { - name: "invalid id type", - input: map[string]any{ - "id": "not-a-number", - "name": "test-group", - "parent_group_id": float64(67890), - }, - expected: nil, - expectError: true, - }, - { - name: "missing name field", - input: map[string]any{ - "id": float64(12345), - "parent_group_id": float64(67890), - }, - expected: nil, - expectError: true, - }, - { - name: "invalid name type", - input: map[string]any{ - "id": float64(12345), - "name": 12345, - "parent_group_id": float64(67890), - }, - expected: nil, - expectError: true, - }, - { - name: "missing parent_group_id field", - input: map[string]any{ - "id": float64(12345), - "name": "test-group", - }, - expected: nil, - expectError: true, - }, - { - name: "invalid parent_group_id type", - input: map[string]any{ - "id": float64(12345), - "name": "test-group", - "parent_group_id": "not-a-number", - }, - expected: nil, - expectError: true, - }, - { - name: "zero values are valid", - input: map[string]any{ - "id": float64(0), - "name": "", - "parent_group_id": float64(0), - }, - expected: &model.GroupInfo{ - ID: 0, - Name: "", - ParentGroupID: 0, - }, - expectError: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := s.convertWebhookGroupInfo(tt.input) - - if tt.expectError { - assert.Error(t, err) - assert.Nil(t, result) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.expected, result) - } - }) - } -} - -// TestConvertGrpsIOServiceCreatePayloadToSettings tests settings extraction from service create payload -func TestConvertGrpsIOServiceCreatePayloadToSettings(t *testing.T) { - tests := []struct { - name string - payload *mailinglistservice.CreateGrpsioServicePayload - serviceUID string - expected *model.GrpsIOServiceSettings - }{ - { - name: "complete payload with writers and auditors", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "primary", - ProjectUID: "project-123", - Public: true, - Writers: []*mailinglistservice.UserInfo{ - { - Name: stringPtr("Writer One"), - Email: stringPtr("writer1@example.com"), - Username: stringPtr("writer1"), - Avatar: stringPtr("https://example.com/avatar1.png"), - }, - { - Name: stringPtr("Writer Two"), - Email: stringPtr("writer2@example.com"), - Username: stringPtr("writer2"), - Avatar: stringPtr("https://example.com/avatar2.png"), - }, - }, - Auditors: []*mailinglistservice.UserInfo{ - { - Name: stringPtr("Auditor One"), - Email: stringPtr("auditor1@example.com"), - Username: stringPtr("auditor1"), - Avatar: stringPtr("https://example.com/avatar3.png"), - }, - }, - }, - serviceUID: "service-uid-123", - expected: &model.GrpsIOServiceSettings{ - UID: "service-uid-123", - Writers: []model.UserInfo{ - { - Name: stringPtr("Writer One"), - Email: stringPtr("writer1@example.com"), - Username: stringPtr("writer1"), - Avatar: stringPtr("https://example.com/avatar1.png"), - }, - { - Name: stringPtr("Writer Two"), - Email: stringPtr("writer2@example.com"), - Username: stringPtr("writer2"), - Avatar: stringPtr("https://example.com/avatar2.png"), - }, - }, - Auditors: []model.UserInfo{ - { - Name: stringPtr("Auditor One"), - Email: stringPtr("auditor1@example.com"), - Username: stringPtr("auditor1"), - Avatar: stringPtr("https://example.com/avatar3.png"), - }, - }, - }, - }, - { - name: "payload with only writers", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "primary", - ProjectUID: "project-456", - Public: false, - Writers: []*mailinglistservice.UserInfo{ - { - Name: stringPtr("Solo Writer"), - Email: stringPtr("solo@example.com"), - Username: stringPtr("solo"), - }, - }, - Auditors: nil, - }, - serviceUID: "service-uid-456", - expected: &model.GrpsIOServiceSettings{ - UID: "service-uid-456", - Writers: []model.UserInfo{ - { - Name: stringPtr("Solo Writer"), - Email: stringPtr("solo@example.com"), - Username: stringPtr("solo"), - }, - }, - Auditors: []model.UserInfo{}, - }, - }, - { - name: "payload with empty writers and auditors", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "formation", - ProjectUID: "project-789", - Public: true, - Writers: []*mailinglistservice.UserInfo{}, - Auditors: []*mailinglistservice.UserInfo{}, - }, - serviceUID: "service-uid-789", - expected: &model.GrpsIOServiceSettings{ - UID: "service-uid-789", - Writers: []model.UserInfo{}, - Auditors: []model.UserInfo{}, - }, - }, - { - name: "nil payload", - payload: nil, - serviceUID: "service-uid-nil", - expected: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &mailingListService{} - result := svc.convertGrpsIOServiceCreatePayloadToSettings(tt.payload, tt.serviceUID) - - if tt.expected == nil { - assert.Nil(t, result) - return - } - - assert.NotNil(t, result) - assert.Equal(t, tt.expected.UID, result.UID, "UID should match") - assert.Equal(t, len(tt.expected.Writers), len(result.Writers), "Writers count should match") - assert.Equal(t, len(tt.expected.Auditors), len(result.Auditors), "Auditors count should match") - - // Verify writers field values - for i, expectedWriter := range tt.expected.Writers { - assert.Equal(t, expectedWriter.Name, result.Writers[i].Name, "Writer Name should match") - assert.Equal(t, expectedWriter.Email, result.Writers[i].Email, "Writer Email should match") - assert.Equal(t, expectedWriter.Username, result.Writers[i].Username, "Writer Username should match") - assert.Equal(t, expectedWriter.Avatar, result.Writers[i].Avatar, "Writer Avatar should match") - } - - // Verify auditors field values - for i, expectedAuditor := range tt.expected.Auditors { - assert.Equal(t, expectedAuditor.Name, result.Auditors[i].Name, "Auditor Name should match") - assert.Equal(t, expectedAuditor.Email, result.Auditors[i].Email, "Auditor Email should match") - assert.Equal(t, expectedAuditor.Username, result.Auditors[i].Username, "Auditor Username should match") - assert.Equal(t, expectedAuditor.Avatar, result.Auditors[i].Avatar, "Auditor Avatar should match") - } - - // Verify timestamps are set for non-nil payloads - if tt.payload != nil { - assert.False(t, result.CreatedAt.IsZero(), "CreatedAt should be set") - assert.False(t, result.UpdatedAt.IsZero(), "UpdatedAt should be set") - } - }) - } -} - -// TestConvertGrpsIOMailingListCreatePayloadToSettings tests settings extraction from mailing list create payload -func TestConvertGrpsIOMailingListCreatePayloadToSettings(t *testing.T) { - tests := []struct { - name string - payload *mailinglistservice.CreateGrpsioMailingListPayload - mailingListUID string - expected *model.GrpsIOMailingListSettings - }{ - { - name: "complete payload with writers and auditors", - payload: &mailinglistservice.CreateGrpsioMailingListPayload{ - GroupName: "test-list", - Public: true, - Type: "discussion_open", - Description: "Test mailing list", - Title: "Test List", - ServiceUID: "service-123", - Writers: []*mailinglistservice.UserInfo{ - { - Name: stringPtr("ML Writer One"), - Email: stringPtr("mlwriter1@example.com"), - Username: stringPtr("mlwriter1"), - Avatar: stringPtr("https://example.com/avatar1.png"), - }, - { - Name: stringPtr("ML Writer Two"), - Email: stringPtr("mlwriter2@example.com"), - Username: stringPtr("mlwriter2"), - Avatar: stringPtr("https://example.com/avatar2.png"), - }, - }, - Auditors: []*mailinglistservice.UserInfo{ - { - Name: stringPtr("ML Auditor One"), - Email: stringPtr("mlauditor1@example.com"), - Username: stringPtr("mlauditor1"), - Avatar: stringPtr("https://example.com/avatar3.png"), - }, - }, - }, - mailingListUID: "ml-uid-123", - expected: &model.GrpsIOMailingListSettings{ - UID: "ml-uid-123", - Writers: []model.UserInfo{ - { - Name: stringPtr("ML Writer One"), - Email: stringPtr("mlwriter1@example.com"), - Username: stringPtr("mlwriter1"), - Avatar: stringPtr("https://example.com/avatar1.png"), - }, - { - Name: stringPtr("ML Writer Two"), - Email: stringPtr("mlwriter2@example.com"), - Username: stringPtr("mlwriter2"), - Avatar: stringPtr("https://example.com/avatar2.png"), - }, - }, - Auditors: []model.UserInfo{ - { - Name: stringPtr("ML Auditor One"), - Email: stringPtr("mlauditor1@example.com"), - Username: stringPtr("mlauditor1"), - Avatar: stringPtr("https://example.com/avatar3.png"), - }, - }, - }, - }, - { - name: "payload with only auditors", - payload: &mailinglistservice.CreateGrpsioMailingListPayload{ - GroupName: "audit-list", - Public: false, - Type: "discussion_moderated", - Description: "Audit mailing list", - Title: "Audit List", - ServiceUID: "service-456", - Writers: nil, - Auditors: []*mailinglistservice.UserInfo{ - { - Name: stringPtr("Solo Auditor"), - Email: stringPtr("soloauditor@example.com"), - Username: stringPtr("soloauditor"), - }, - }, - }, - mailingListUID: "ml-uid-456", - expected: &model.GrpsIOMailingListSettings{ - UID: "ml-uid-456", - Writers: []model.UserInfo{}, - Auditors: []model.UserInfo{ - { - Name: stringPtr("Solo Auditor"), - Email: stringPtr("soloauditor@example.com"), - Username: stringPtr("soloauditor"), - }, - }, - }, - }, - { - name: "payload with empty writers and auditors", - payload: &mailinglistservice.CreateGrpsioMailingListPayload{ - GroupName: "empty-list", - Public: true, - Type: "announcement", - Description: "Empty mailing list", - Title: "Empty List", - ServiceUID: "service-789", - Writers: []*mailinglistservice.UserInfo{}, - Auditors: []*mailinglistservice.UserInfo{}, - }, - mailingListUID: "ml-uid-789", - expected: &model.GrpsIOMailingListSettings{ - UID: "ml-uid-789", - Writers: []model.UserInfo{}, - Auditors: []model.UserInfo{}, - }, - }, - { - name: "payload with nil entries in writers array (should be skipped)", - payload: &mailinglistservice.CreateGrpsioMailingListPayload{ - GroupName: "nil-entries-list", - Public: true, - Type: "discussion_open", - Description: "List with nil entries", - Title: "Nil Entries List", - ServiceUID: "service-nil", - Writers: []*mailinglistservice.UserInfo{ - { - Name: stringPtr("Valid Writer"), - Email: stringPtr("valid@example.com"), - Username: stringPtr("valid"), - }, - nil, // Should be skipped - { - Name: stringPtr("Another Writer"), - Email: stringPtr("another@example.com"), - Username: stringPtr("another"), - }, - }, - Auditors: nil, - }, - mailingListUID: "ml-uid-nil", - expected: &model.GrpsIOMailingListSettings{ - UID: "ml-uid-nil", - Writers: []model.UserInfo{ - { - Name: stringPtr("Valid Writer"), - Email: stringPtr("valid@example.com"), - Username: stringPtr("valid"), - }, - { - Name: stringPtr("Another Writer"), - Email: stringPtr("another@example.com"), - Username: stringPtr("another"), - }, - }, - Auditors: []model.UserInfo{}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Simulate the settings extraction logic from mailing_list_service.go:304-307 - domainSettings := &model.GrpsIOMailingListSettings{ - UID: tt.mailingListUID, - Writers: convertUserInfoPayloadToDomain(tt.payload.Writers), - Auditors: convertUserInfoPayloadToDomain(tt.payload.Auditors), - } - - assert.NotNil(t, domainSettings) - assert.Equal(t, tt.expected.UID, domainSettings.UID, "UID should match") - assert.Equal(t, len(tt.expected.Writers), len(domainSettings.Writers), "Writers count should match") - assert.Equal(t, len(tt.expected.Auditors), len(domainSettings.Auditors), "Auditors count should match") - - // Verify writers field values - for i, expectedWriter := range tt.expected.Writers { - assert.Equal(t, expectedWriter.Name, domainSettings.Writers[i].Name, "Writer Name should match") - assert.Equal(t, expectedWriter.Email, domainSettings.Writers[i].Email, "Writer Email should match") - assert.Equal(t, expectedWriter.Username, domainSettings.Writers[i].Username, "Writer Username should match") - assert.Equal(t, expectedWriter.Avatar, domainSettings.Writers[i].Avatar, "Writer Avatar should match") - } - - // Verify auditors field values - for i, expectedAuditor := range tt.expected.Auditors { - assert.Equal(t, expectedAuditor.Name, domainSettings.Auditors[i].Name, "Auditor Name should match") - assert.Equal(t, expectedAuditor.Email, domainSettings.Auditors[i].Email, "Auditor Email should match") - assert.Equal(t, expectedAuditor.Username, domainSettings.Auditors[i].Username, "Auditor Username should match") - assert.Equal(t, expectedAuditor.Avatar, domainSettings.Auditors[i].Avatar, "Auditor Avatar should match") - } - }) - } -} - -// TestMailingListService_convertWebhookMemberInfo tests the webhook member info converter -func TestMailingListService_convertWebhookMemberInfo(t *testing.T) { - s := &mailingListService{} - - tests := []struct { - name string - input map[string]any - expected *model.MemberInfo - expectError bool - }{ - { - name: "valid member info with all required fields", - input: map[string]any{ - "id": float64(123), - "group_id": float64(456), - "email": "test@example.com", - "status": "active", - }, - expected: &model.MemberInfo{ - ID: 123, - GroupID: 456, - Email: "test@example.com", - Status: "active", - }, - expectError: false, - }, - { - name: "valid member info with optional fields", - input: map[string]any{ - "id": float64(123), - "user_id": float64(789), - "group_id": float64(456), - "group_name": "test-group", - "email": "test@example.com", - "status": "pending", - }, - expected: &model.MemberInfo{ - ID: 123, - UserID: 789, - GroupID: 456, - GroupName: "test-group", - Email: "test@example.com", - Status: "pending", - }, - expectError: false, - }, - { - name: "nil input", - input: nil, - expected: nil, - expectError: true, - }, - { - name: "missing id field", - input: map[string]any{ - "group_id": float64(456), - "email": "test@example.com", - "status": "active", - }, - expected: nil, - expectError: true, - }, - { - name: "invalid id type", - input: map[string]any{ - "id": "not-a-number", - "group_id": float64(456), - "email": "test@example.com", - "status": "active", - }, - expected: nil, - expectError: true, - }, - { - name: "missing group_id field", - input: map[string]any{ - "id": float64(123), - "email": "test@example.com", - "status": "active", - }, - expected: nil, - expectError: true, - }, - { - name: "invalid group_id type", - input: map[string]any{ - "id": float64(123), - "group_id": "not-a-number", - "email": "test@example.com", - "status": "active", - }, - expected: nil, - expectError: true, - }, - { - name: "missing email field", - input: map[string]any{ - "id": float64(123), - "group_id": float64(456), - "status": "active", - }, - expected: nil, - expectError: true, - }, - { - name: "invalid email type", - input: map[string]any{ - "id": float64(123), - "group_id": float64(456), - "email": 12345, - "status": "active", - }, - expected: nil, - expectError: true, - }, - { - name: "missing status field", - input: map[string]any{ - "id": float64(123), - "group_id": float64(456), - "email": "test@example.com", - }, - expected: nil, - expectError: true, - }, - { - name: "invalid status type", - input: map[string]any{ - "id": float64(123), - "group_id": float64(456), - "email": "test@example.com", - "status": 12345, - }, - expected: nil, - expectError: true, - }, - { - name: "optional user_id with wrong type is ignored", - input: map[string]any{ - "id": float64(123), - "user_id": "not-a-number", - "group_id": float64(456), - "email": "test@example.com", - "status": "active", - }, - expected: &model.MemberInfo{ - ID: 123, - UserID: 0, // Optional field, wrong type means zero value - GroupID: 456, - Email: "test@example.com", - Status: "active", - }, - expectError: false, - }, - { - name: "optional group_name with wrong type is ignored", - input: map[string]any{ - "id": float64(123), - "group_id": float64(456), - "group_name": 12345, - "email": "test@example.com", - "status": "active", - }, - expected: &model.MemberInfo{ - ID: 123, - GroupID: 456, - GroupName: "", // Optional field, wrong type means zero value - Email: "test@example.com", - Status: "active", - }, - expectError: false, - }, - { - name: "zero values are valid for required fields", - input: map[string]any{ - "id": float64(0), - "group_id": float64(0), - "email": "", - "status": "", - }, - expected: &model.MemberInfo{ - ID: 0, - GroupID: 0, - Email: "", - Status: "", - }, - expectError: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := s.convertWebhookMemberInfo(tt.input) - - if tt.expectError { - assert.Error(t, err) - assert.Nil(t, result) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.expected, result) - } - }) - } -} diff --git a/cmd/mailing-list-api/service/service_response_converters.go b/cmd/mailing-list-api/service/service_response_converters.go deleted file mode 100644 index f27c818..0000000 --- a/cmd/mailing-list-api/service/service_response_converters.go +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "time" - - mailinglistservice "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" -) - -// convertGrpsIOServiceDomainToFullResponse converts domain model to full response (for CREATE operations) -// Following convertGrpsIOServiceDomainToFullResponse -func (s *mailingListService) convertGrpsIOServiceFullDomainToResponse(service *model.GrpsIOServiceFull) *mailinglistservice.GrpsIoServiceFull { - if service == nil { - return &mailinglistservice.GrpsIoServiceFull{} - } - - if service.Base == nil { - service.Base = &model.GrpsIOService{} - } - if service.Settings == nil { - service.Settings = &model.GrpsIOServiceSettings{} - } - - result := &mailinglistservice.GrpsIoServiceFull{ - UID: &service.Base.UID, - Type: service.Base.Type, - Domain: &service.Base.Domain, - GroupID: service.Base.GroupID, - Status: &service.Base.Status, - GlobalOwners: service.Base.GlobalOwners, - Prefix: &service.Base.Prefix, - ProjectSlug: &service.Base.ProjectSlug, - ProjectName: &service.Base.ProjectName, - ProjectUID: service.Base.ProjectUID, - URL: &service.Base.URL, - GroupName: &service.Base.GroupName, - Public: service.Base.Public, - Writers: convertUserInfoDomainToResponse(service.Settings.Writers), - Auditors: convertUserInfoDomainToResponse(service.Settings.Auditors), - } - - // Only set ParentServiceUID if it's non-empty - if service.Base.ParentServiceUID != "" { - result.ParentServiceUID = &service.Base.ParentServiceUID - } - - // Handle timestamps - if !service.Base.CreatedAt.IsZero() { - createdAt := service.Base.CreatedAt.Format(time.RFC3339) - result.CreatedAt = &createdAt - } - - if !service.Base.UpdatedAt.IsZero() { - updatedAt := service.Base.UpdatedAt.Format(time.RFC3339) - result.UpdatedAt = &updatedAt - } - - return result -} - -// convertGrpsIOServiceDomainToStandardResponse converts domain model to standard response (for GET/UPDATE operations) -// convertBaseToResponse -func (s *mailingListService) convertGrpsIOServiceDomainToStandardResponse(service *model.GrpsIOService) *mailinglistservice.GrpsIoServiceWithReadonlyAttributes { - if service == nil { - return &mailinglistservice.GrpsIoServiceWithReadonlyAttributes{} - } - - result := &mailinglistservice.GrpsIoServiceWithReadonlyAttributes{ - UID: &service.UID, - Type: service.Type, - Domain: &service.Domain, - GroupID: service.GroupID, - Status: &service.Status, - GlobalOwners: service.GlobalOwners, - Prefix: &service.Prefix, - ProjectSlug: &service.ProjectSlug, - ProjectName: &service.ProjectName, - ProjectUID: service.ProjectUID, - URL: &service.URL, - GroupName: &service.GroupName, - Public: service.Public, - } - - // Only set ParentServiceUID if it's non-empty - if service.ParentServiceUID != "" { - result.ParentServiceUID = &service.ParentServiceUID - } - - // Handle timestamps - if !service.CreatedAt.IsZero() { - createdAt := service.CreatedAt.Format(time.RFC3339) - result.CreatedAt = &createdAt - } - - if !service.UpdatedAt.IsZero() { - updatedAt := service.UpdatedAt.Format(time.RFC3339) - result.UpdatedAt = &updatedAt - } - - return result -} - -// convertGrpsIOMailingListDomainToResponse converts domain mailing list to full response (for CREATE operations) -func (s *mailingListService) convertGrpsIOMailingListDomainToResponse(ml *model.GrpsIOMailingList, settings *model.GrpsIOMailingListSettings) *mailinglistservice.GrpsIoMailingListFull { - if ml == nil { - return &mailinglistservice.GrpsIoMailingListFull{} - } - - result := &mailinglistservice.GrpsIoMailingListFull{ - UID: &ml.UID, - GroupID: ml.GroupID, - GroupName: &ml.GroupName, - Public: ml.Public, - AudienceAccess: ml.AudienceAccess, - Type: &ml.Type, - SubscriberCount: &ml.SubscriberCount, - Committees: convertCommitteesToResponse(ml.Committees), - Description: &ml.Description, - Title: &ml.Title, - SubjectTag: stringToPointer(ml.SubjectTag), - ServiceUID: &ml.ServiceUID, - ProjectUID: &ml.ProjectUID, // This is inherited from parent in orchestrator - ProjectName: &ml.ProjectName, // Inherited from parent service - ProjectSlug: &ml.ProjectSlug, // Inherited from parent service - } - - // Add writers and auditors from settings - if settings != nil { - result.Writers = convertUserInfoDomainToResponse(settings.Writers) - result.Auditors = convertUserInfoDomainToResponse(settings.Auditors) - } - - // Handle timestamps - if !ml.CreatedAt.IsZero() { - createdAt := ml.CreatedAt.Format(time.RFC3339) - result.CreatedAt = &createdAt - } - - if !ml.UpdatedAt.IsZero() { - updatedAt := ml.UpdatedAt.Format(time.RFC3339) - result.UpdatedAt = &updatedAt - } - - return result -} - -// convertCommitteesToResponse converts domain Committee array to GOA Committee array -func convertCommitteesToResponse(committees []model.Committee) []*mailinglistservice.Committee { - if committees == nil { - return nil - } - - result := make([]*mailinglistservice.Committee, 0, len(committees)) - for _, c := range committees { - result = append(result, &mailinglistservice.Committee{ - UID: c.UID, - Name: stringToPointer(c.Name), - AllowedVotingStatuses: c.AllowedVotingStatuses, - }) - } - return result -} - -// convertGrpsIOMailingListDomainToStandardResponse converts a domain mailing list to GOA standard response type -func (s *mailingListService) convertGrpsIOMailingListDomainToStandardResponse(mailingList *model.GrpsIOMailingList) *mailinglistservice.GrpsIoMailingListWithReadonlyAttributes { - if mailingList == nil { - return &mailinglistservice.GrpsIoMailingListWithReadonlyAttributes{} - } - - response := &mailinglistservice.GrpsIoMailingListWithReadonlyAttributes{ - UID: &mailingList.UID, - GroupID: mailingList.GroupID, - GroupName: &mailingList.GroupName, - Public: mailingList.Public, - AudienceAccess: mailingList.AudienceAccess, - Type: &mailingList.Type, - SubscriberCount: &mailingList.SubscriberCount, - Committees: convertCommitteesToResponse(mailingList.Committees), - Description: &mailingList.Description, - Title: &mailingList.Title, - SubjectTag: stringToPointer(mailingList.SubjectTag), - ServiceUID: &mailingList.ServiceUID, - ProjectUID: stringToPointer(mailingList.ProjectUID), - ProjectName: stringToPointer(mailingList.ProjectName), - ProjectSlug: stringToPointer(mailingList.ProjectSlug), - } - - // Convert timestamps - if !mailingList.CreatedAt.IsZero() { - createdAt := mailingList.CreatedAt.Format(time.RFC3339) - response.CreatedAt = &createdAt - } - if !mailingList.UpdatedAt.IsZero() { - updatedAt := mailingList.UpdatedAt.Format(time.RFC3339) - response.UpdatedAt = &updatedAt - } - - // Note: LastReviewedAt/By fields are not in MailingListWithReadonlyAttributes - // They might be in a different response type or future enhancement - - return response -} - -// convertGrpsIOMemberToResponse converts domain member model to API response -func (s *mailingListService) convertGrpsIOMemberToResponse(member *model.GrpsIOMember) *mailinglistservice.GrpsIoMemberWithReadonlyAttributes { - if member == nil { - return &mailinglistservice.GrpsIoMemberWithReadonlyAttributes{} - } - - result := &mailinglistservice.GrpsIoMemberWithReadonlyAttributes{ - UID: &member.UID, - MailingListUID: &member.MailingListUID, - Username: &member.Username, - FirstName: &member.FirstName, - LastName: &member.LastName, - Email: &member.Email, - Organization: &member.Organization, - JobTitle: &member.JobTitle, - MemberType: member.MemberType, - DeliveryMode: member.DeliveryMode, - ModStatus: member.ModStatus, - Status: &member.Status, - } - - // Handle optional GroupsIO fields - if member.MemberID != nil && *member.MemberID > 0 { - result.MemberID = member.MemberID - } - if member.GroupID != nil && *member.GroupID > 0 { - result.GroupID = member.GroupID - } - - // Handle timestamps - if !member.CreatedAt.IsZero() { - createdAt := member.CreatedAt.Format(time.RFC3339) - result.CreatedAt = &createdAt - } - if !member.UpdatedAt.IsZero() { - updatedAt := member.UpdatedAt.Format(time.RFC3339) - result.UpdatedAt = &updatedAt - } - - // Handle optional string fields (nullable in domain model) - if member.LastReviewedAt != nil && *member.LastReviewedAt != "" { - result.LastReviewedAt = member.LastReviewedAt - } - if member.LastReviewedBy != nil && *member.LastReviewedBy != "" { - result.LastReviewedBy = member.LastReviewedBy - } - - return result -} - -// convertGrpsIOMemberDomainToResponse converts domain member to GOA response -func (s *mailingListService) convertGrpsIOMemberDomainToResponse(member *model.GrpsIOMember) *mailinglistservice.GrpsIoMemberFull { - response := &mailinglistservice.GrpsIoMemberFull{ - UID: member.UID, - MailingListUID: member.MailingListUID, - FirstName: member.FirstName, - LastName: member.LastName, - Email: member.Email, - MemberType: member.MemberType, - DeliveryMode: member.DeliveryMode, - ModStatus: member.ModStatus, - Status: member.Status, - } - - // Handle optional fields - if member.Username != "" { - response.Username = &member.Username - } - if member.Organization != "" { - response.Organization = &member.Organization - } - if member.JobTitle != "" { - response.JobTitle = &member.JobTitle - } - if member.MemberID != nil && *member.MemberID != 0 { - response.MemberID = member.MemberID - } - if member.GroupID != nil && *member.GroupID != 0 { - response.GroupID = member.GroupID - } - if member.LastReviewedAt != nil { - response.LastReviewedAt = member.LastReviewedAt - } - if member.LastReviewedBy != nil { - response.LastReviewedBy = member.LastReviewedBy - } - // Note: Access control is managed at the mailing list level - - // Convert timestamps - if !member.CreatedAt.IsZero() { - response.CreatedAt = member.CreatedAt.Format(time.RFC3339) - } - if !member.UpdatedAt.IsZero() { - response.UpdatedAt = member.UpdatedAt.Format(time.RFC3339) - } - - return response -} - -// stringToPointer converts empty string to nil pointer, non-empty string to pointer -func stringToPointer(s string) *string { - if s == "" { - return nil - } - return &s -} - -// convertGrpsIOServiceSettingsDomainToResponse converts domain settings to GOA response -func (s *mailingListService) convertGrpsIOServiceSettingsDomainToResponse(settings *model.GrpsIOServiceSettings) *mailinglistservice.GrpsIoServiceSettings { - if settings == nil { - return &mailinglistservice.GrpsIoServiceSettings{} - } - - createdAt := settings.CreatedAt.Format(time.RFC3339) - updatedAt := settings.UpdatedAt.Format(time.RFC3339) - - response := &mailinglistservice.GrpsIoServiceSettings{ - UID: &settings.UID, - Writers: convertUserInfoDomainToResponse(settings.Writers), - Auditors: convertUserInfoDomainToResponse(settings.Auditors), - CreatedAt: &createdAt, - UpdatedAt: &updatedAt, - } - - if settings.LastReviewedAt != nil { - response.LastReviewedAt = settings.LastReviewedAt - } - if settings.LastReviewedBy != nil { - response.LastReviewedBy = settings.LastReviewedBy - } - if settings.LastAuditedTime != nil { - response.LastAuditedTime = settings.LastAuditedTime - } - if settings.LastAuditedBy != nil { - response.LastAuditedBy = settings.LastAuditedBy - } - - return response -} - -// convertUserInfoDomainToResponse converts domain UserInfo array to GOA UserInfo array -func convertUserInfoDomainToResponse(domainUsers []model.UserInfo) []*mailinglistservice.UserInfo { - if domainUsers == nil { - return []*mailinglistservice.UserInfo{} - } - - users := make([]*mailinglistservice.UserInfo, len(domainUsers)) - for i, u := range domainUsers { - users[i] = &mailinglistservice.UserInfo{ - Name: u.Name, - Email: u.Email, - Username: u.Username, - Avatar: u.Avatar, - } - } - return users -} - -// convertGrpsIOMailingListSettingsDomainToResponse converts domain mailing list settings to GOA response -func (s *mailingListService) convertGrpsIOMailingListSettingsDomainToResponse(settings *model.GrpsIOMailingListSettings) *mailinglistservice.GrpsIoMailingListSettings { - if settings == nil { - return &mailinglistservice.GrpsIoMailingListSettings{} - } - - createdAt := settings.CreatedAt.Format(time.RFC3339) - updatedAt := settings.UpdatedAt.Format(time.RFC3339) - - response := &mailinglistservice.GrpsIoMailingListSettings{ - UID: &settings.UID, - Writers: convertUserInfoDomainToResponse(settings.Writers), - Auditors: convertUserInfoDomainToResponse(settings.Auditors), - CreatedAt: &createdAt, - UpdatedAt: &updatedAt, - } - - if settings.LastReviewedAt != nil { - response.LastReviewedAt = settings.LastReviewedAt - } - if settings.LastReviewedBy != nil { - response.LastReviewedBy = settings.LastReviewedBy - } - if settings.LastAuditedTime != nil { - response.LastAuditedTime = settings.LastAuditedTime - } - if settings.LastAuditedBy != nil { - response.LastAuditedBy = settings.LastAuditedBy - } - - return response -} diff --git a/cmd/mailing-list-api/service/service_response_converters_test.go b/cmd/mailing-list-api/service/service_response_converters_test.go deleted file mode 100644 index cf47b36..0000000 --- a/cmd/mailing-list-api/service/service_response_converters_test.go +++ /dev/null @@ -1,345 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "testing" - "time" - - mailinglistservice "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/stretchr/testify/assert" -) - -func TestConvertDomainToFullResponse(t *testing.T) { - createdAt := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) - updatedAt := time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC) - - tests := []struct { - name string - domain *model.GrpsIOServiceFull - expected *mailinglistservice.GrpsIoServiceFull - }{ - { - name: "complete domain to full response conversion", - domain: &model.GrpsIOServiceFull{ - Base: &model.GrpsIOService{ - UID: "service-123", - Type: "primary", - Domain: "example.groups.io", - GroupID: int64Ptr(12345), - Status: "active", - GlobalOwners: []string{"owner1@example.com", "owner2@example.com"}, - Prefix: "", - ProjectSlug: "test-project", - ProjectName: "Test Project", - ProjectUID: "project-123", - URL: "https://example.groups.io/g/test", - GroupName: "test-group", - Public: true, - CreatedAt: createdAt, - UpdatedAt: updatedAt, - }, - Settings: &model.GrpsIOServiceSettings{ - UID: "service-123", - Writers: []model.UserInfo{}, - Auditors: []model.UserInfo{}, - }, - }, - expected: &mailinglistservice.GrpsIoServiceFull{ - UID: stringPtr("service-123"), - Type: "primary", - Domain: stringPtr("example.groups.io"), - GroupID: int64Ptr(12345), - Status: stringPtr("active"), - GlobalOwners: []string{"owner1@example.com", "owner2@example.com"}, - Prefix: stringPtr(""), - ProjectSlug: stringPtr("test-project"), - ProjectName: stringPtr("Test Project"), - ProjectUID: "project-123", - URL: stringPtr("https://example.groups.io/g/test"), - GroupName: stringPtr("test-group"), - Public: true, - CreatedAt: stringPtr("2023-01-01T12:00:00Z"), - UpdatedAt: stringPtr("2023-01-02T12:00:00Z"), - Writers: []*mailinglistservice.UserInfo{}, - Auditors: []*mailinglistservice.UserInfo{}, - }, - }, - { - name: "minimal domain to full response conversion", - domain: &model.GrpsIOServiceFull{ - Base: &model.GrpsIOService{ - Type: "formation", - ProjectUID: "project-456", - Public: false, - CreatedAt: time.Time{}, // Zero timestamp - UpdatedAt: time.Time{}, // Zero timestamp - }, - Settings: nil, - }, - expected: &mailinglistservice.GrpsIoServiceFull{ - UID: stringPtr(""), - Type: "formation", - Domain: stringPtr(""), - GroupID: nil, - Status: stringPtr(""), - GlobalOwners: nil, - Prefix: stringPtr(""), - ProjectSlug: stringPtr(""), - ProjectName: stringPtr(""), - ProjectUID: "project-456", - URL: stringPtr(""), - GroupName: stringPtr(""), - Public: false, - // CreatedAt and UpdatedAt should be nil when timestamps are zero - CreatedAt: nil, - UpdatedAt: nil, - Writers: []*mailinglistservice.UserInfo{}, - Auditors: []*mailinglistservice.UserInfo{}, - }, - }, - { - name: "nil domain", - domain: nil, - expected: &mailinglistservice.GrpsIoServiceFull{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &mailingListService{} - result := svc.convertGrpsIOServiceFullDomainToResponse(tt.domain) - - assert.Equal(t, tt.expected, result) - }) - } -} - -func TestConvertDomainToStandardResponse(t *testing.T) { - createdAt := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) - updatedAt := time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC) - - tests := []struct { - name string - domain *model.GrpsIOService - expected *mailinglistservice.GrpsIoServiceWithReadonlyAttributes - }{ - { - name: "complete domain to standard response conversion", - domain: &model.GrpsIOService{ - UID: "service-123", - Type: "shared", - Domain: "shared.groups.io", - GroupID: int64Ptr(67890), - Status: "inactive", - GlobalOwners: []string{"shared@example.com"}, - Prefix: "shared-prefix", - ProjectSlug: "shared-project", - ProjectName: "Shared Project", - ProjectUID: "project-789", - URL: "https://shared.groups.io/g/shared", - GroupName: "shared-group", - Public: false, - CreatedAt: createdAt, - UpdatedAt: updatedAt, - }, - expected: &mailinglistservice.GrpsIoServiceWithReadonlyAttributes{ - UID: stringPtr("service-123"), - Type: "shared", - Domain: stringPtr("shared.groups.io"), - GroupID: int64Ptr(67890), - Status: stringPtr("inactive"), - GlobalOwners: []string{"shared@example.com"}, - Prefix: stringPtr("shared-prefix"), - ProjectSlug: stringPtr("shared-project"), - ProjectName: stringPtr("Shared Project"), - ProjectUID: "project-789", - URL: stringPtr("https://shared.groups.io/g/shared"), - GroupName: stringPtr("shared-group"), - Public: false, - CreatedAt: stringPtr("2023-01-01T12:00:00Z"), - UpdatedAt: stringPtr("2023-01-02T12:00:00Z"), - }, - }, - { - name: "domain with zero timestamps", - domain: &model.GrpsIOService{ - UID: "service-456", - Type: "formation", - ProjectUID: "project-456", - Public: true, - CreatedAt: time.Time{}, // Zero timestamp - UpdatedAt: time.Time{}, // Zero timestamp - }, - expected: &mailinglistservice.GrpsIoServiceWithReadonlyAttributes{ - UID: stringPtr("service-456"), - Type: "formation", - Domain: stringPtr(""), - GroupID: nil, - Status: stringPtr(""), - GlobalOwners: nil, - Prefix: stringPtr(""), - ProjectSlug: stringPtr(""), - ProjectName: stringPtr(""), - ProjectUID: "project-456", - URL: stringPtr(""), - GroupName: stringPtr(""), - Public: true, - // CreatedAt and UpdatedAt should be nil when timestamps are zero - CreatedAt: nil, - UpdatedAt: nil, - }, - }, - { - name: "nil domain", - domain: nil, - expected: &mailinglistservice.GrpsIoServiceWithReadonlyAttributes{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &mailingListService{} - result := svc.convertGrpsIOServiceDomainToStandardResponse(tt.domain) - - assert.Equal(t, tt.expected, result) - }) - } -} - -func TestConvertMailingListDomainToResponse(t *testing.T) { - createdAt := time.Date(2023, 2, 1, 10, 0, 0, 0, time.UTC) - updatedAt := time.Date(2023, 2, 2, 10, 0, 0, 0, time.UTC) - - tests := []struct { - name string - domain *model.GrpsIOMailingList - expected *mailinglistservice.GrpsIoMailingListFull - }{ - { - name: "complete mailing list domain to response conversion", - domain: &model.GrpsIOMailingList{ - UID: "ml-123", - GroupName: "test-mailing-list", - Public: true, - Type: "discussion_open", - Committees: []model.Committee{ - {UID: "committee-123", Name: "Test Committee", AllowedVotingStatuses: []string{"Voting Rep", "Observer"}}, - }, - Description: "This is a comprehensive test mailing list", - Title: "Test Mailing List", - SubjectTag: "[TEST]", - ServiceUID: "parent-service-456", - ProjectUID: "project-789", - ProjectName: "Test Project", - ProjectSlug: "test-project", - CreatedAt: createdAt, - UpdatedAt: updatedAt, - }, - expected: &mailinglistservice.GrpsIoMailingListFull{ - UID: stringPtr("ml-123"), - GroupName: stringPtr("test-mailing-list"), - Public: true, - Type: stringPtr("discussion_open"), - SubscriberCount: intPtr(0), - Committees: []*mailinglistservice.Committee{ - {UID: "committee-123", Name: stringPtr("Test Committee"), AllowedVotingStatuses: []string{"Voting Rep", "Observer"}}, - }, - Description: stringPtr("This is a comprehensive test mailing list"), - Title: stringPtr("Test Mailing List"), - SubjectTag: stringPtr("[TEST]"), - ServiceUID: stringPtr("parent-service-456"), - ProjectUID: stringPtr("project-789"), - ProjectName: stringPtr("Test Project"), - ProjectSlug: stringPtr("test-project"), - CreatedAt: stringPtr("2023-02-01T10:00:00Z"), - UpdatedAt: stringPtr("2023-02-02T10:00:00Z"), - }, - }, - { - name: "minimal mailing list domain to response conversion", - domain: &model.GrpsIOMailingList{ - UID: "ml-456", - GroupName: "minimal-list", - Public: false, - Type: "announcement", - Description: "Minimal mailing list", - Title: "Minimal List", - ServiceUID: "parent-789", - CreatedAt: time.Time{}, // Zero timestamp - UpdatedAt: time.Time{}, // Zero timestamp - }, - expected: &mailinglistservice.GrpsIoMailingListFull{ - UID: stringPtr("ml-456"), - GroupName: stringPtr("minimal-list"), - Public: false, - Type: stringPtr("announcement"), - SubscriberCount: intPtr(0), - Committees: nil, // No committees - Description: stringPtr("Minimal mailing list"), - Title: stringPtr("Minimal List"), - SubjectTag: nil, // Empty string converts to nil - ServiceUID: stringPtr("parent-789"), - ProjectUID: stringPtr(""), - ProjectName: stringPtr(""), - ProjectSlug: stringPtr(""), - // CreatedAt and UpdatedAt should be nil when timestamps are zero - CreatedAt: nil, - UpdatedAt: nil, - }, - }, - { - name: "nil mailing list domain", - domain: nil, - expected: &mailinglistservice.GrpsIoMailingListFull{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &mailingListService{} - result := svc.convertGrpsIOMailingListDomainToResponse(tt.domain, nil) - - assert.Equal(t, tt.expected, result) - }) - } -} - -func TestStringToPointer(t *testing.T) { - tests := []struct { - name string - input string - expected *string - }{ - { - name: "non-empty string", - input: "test-string", - expected: stringPtr("test-string"), - }, - { - name: "empty string", - input: "", - expected: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := stringToPointer(tt.input) - - if tt.expected == nil { - assert.Nil(t, result) - } else { - assert.NotNil(t, result) - assert.Equal(t, *tt.expected, *result) - } - }) - } -} - -// Helper function for creating int pointer in test data -func intPtr(i int) *int { - return &i -} diff --git a/cmd/mailing-list-api/service/service_validators.go b/cmd/mailing-list-api/service/service_validators.go deleted file mode 100644 index 6cba044..0000000 --- a/cmd/mailing-list-api/service/service_validators.go +++ /dev/null @@ -1,614 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "fmt" - "log/slog" - "net/mail" - "strconv" - "strings" - - mailinglistservice "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/redaction" -) - -// etagValidator validates ETag format and converts to uint64 for optimistic locking -// Supports standard HTTP ETag formats: "123", W/"123", and plain numeric "123" -func etagValidator(etag *string) (uint64, error) { - // Parse ETag to get revision for optimistic locking - if etag == nil || *etag == "" { - return 0, errors.NewValidation("ETag is required for update operations") - } - - raw := strings.TrimSpace(*etag) - - // Handle weak ETags: W/"123" -> "123" - if strings.HasPrefix(raw, "W/") || strings.HasPrefix(raw, "w/") { - raw = strings.TrimSpace(raw[2:]) - } - - // Strip surrounding quotes if present: "123" -> 123 - raw = strings.Trim(raw, `"`) - - parsedRevision, errParse := strconv.ParseUint(raw, 10, 64) - if errParse != nil { - return 0, errors.NewValidation("invalid ETag format", errParse) - } - - return parsedRevision, nil -} - -// Reserved words that cannot be used for group names -var reservedWords = []string{ - "admin", "api", "www", "mail", "support", "noreply", "postmaster", - "no-reply", "webmaster", "root", "administrator", "moderator", -} - -// Group name pattern validation is now handled by GOA design layer - -// validateGroupName validates business rules for group names (reserved words only) -// Format and length validations are now handled by GOA design layer -func validateGroupName(groupName, fieldName string) error { - // Reserved words validation (business logic that cannot be in GOA) - lowerName := strings.ToLower(groupName) - for _, reserved := range reservedWords { - if lowerName == reserved { - return errors.NewValidation(fmt.Sprintf("%s cannot use reserved word '%s'", fieldName, reserved)) - } - } - - return nil -} - -// validateServiceCreationRules validates type-specific business rules for service creation -// TODO: Future PR - Service limits per project when NATS List/Watch available: -// - Max 1 primary per project (unique constraint already enforced) -// - Max 5 formation per project -// - Max 10 shared per project -func validateServiceCreationRules(payload *mailinglistservice.CreateGrpsioServicePayload) error { - serviceType := payload.Type - - switch serviceType { - case constants.ServiceTypePrimary: - return validatePrimaryRules(payload) - case constants.ServiceTypeFormation: - return validateFormationRules(payload) - case constants.ServiceTypeShared: - return validateSharedRules(payload) - default: - return errors.NewValidation(fmt.Sprintf("invalid service type: %s. Must be one of: %s, %s, %s", - serviceType, constants.ServiceTypePrimary, constants.ServiceTypeFormation, constants.ServiceTypeShared)) - } -} - -// validatePrimaryRules validates rules for primary service type -func validatePrimaryRules(payload *mailinglistservice.CreateGrpsioServicePayload) error { - // primary rules: - // - prefix must NOT be provided (will return 400 error) - // - global_owners must be provided and contain at least one valid email - // - Unique constraint validation handled by storage layer (UniqueProjectType) - - if payload.Prefix != nil && *payload.Prefix != "" { - return errors.NewValidation("prefix must not be provided for primary service type") - } - - // global_owners is required for primary services and must be 1-10 emails - if len(payload.GlobalOwners) == 0 { - return errors.NewValidation("global_owners is required and must contain at least one email address for primary service type") - } - if len(payload.GlobalOwners) > 10 { - return errors.NewValidation("global_owners must not exceed 10 email addresses for primary service type") - } - - // Validate global_owners email addresses - if err := validateEmailAddresses(payload.GlobalOwners, "global_owners"); err != nil { - return err - } - - return nil -} - -// validateFormationRules validates rules for formation service type -func validateFormationRules(payload *mailinglistservice.CreateGrpsioServicePayload) error { - // formation rules: - // - prefix must be non-empty string - - if payload.Prefix == nil || strings.TrimSpace(*payload.Prefix) == "" { - return errors.NewValidation("prefix is required and must be non-empty for formation service type") - } - - // Validate global_owners email addresses if provided - if err := validateEmailAddresses(payload.GlobalOwners, "global_owners"); err != nil { - return err - } - - return nil -} - -// validateSharedRules validates rules for shared service type -func validateSharedRules(payload *mailinglistservice.CreateGrpsioServicePayload) error { - // shared rules: - // - prefix must be non-empty string - // - global_owners must NOT be provided (will return 400 error) - - if payload.Prefix == nil || strings.TrimSpace(*payload.Prefix) == "" { - return errors.NewValidation("prefix is required and must be non-empty for shared service type") - } - - if len(payload.GlobalOwners) > 0 { - return errors.NewValidation("global_owners must not be provided for shared service type") - } - - return nil -} - -// validateUpdateImmutabilityConstraints validates that only mutable fields are being modified -func validateUpdateImmutabilityConstraints(existing *model.GrpsIOService, payload *mailinglistservice.UpdateGrpsioServicePayload) error { - // Immutable Fields: type, project_uid, prefix, domain, group_id, url, group_name - // Mutable Fields: global_owners, status, public only - - if payload.Type != existing.Type { - return errors.NewValidation(fmt.Sprintf("field 'type' is immutable. Cannot change from '%s' to '%s'", existing.Type, payload.Type)) - } - - if payload.ProjectUID != existing.ProjectUID { - return errors.NewValidation(fmt.Sprintf("field 'project_uid' is immutable. Cannot change from '%s' to '%s'", existing.ProjectUID, payload.ProjectUID)) - } - - // Check prefix immutability - only validate if explicitly provided - if payload.Prefix != nil && *payload.Prefix != existing.Prefix { - return errors.NewValidation(fmt.Sprintf("field 'prefix' is immutable. Cannot change from '%s' to '%s'", existing.Prefix, *payload.Prefix)) - } - - // Check domain immutability - only validate if explicitly provided - if payload.Domain != nil && *payload.Domain != existing.Domain { - return errors.NewValidation(fmt.Sprintf("field 'domain' is immutable. Cannot change from '%s' to '%s'", existing.Domain, *payload.Domain)) - } - - // Check group_id immutability - only validate if explicitly provided - if payload.GroupID != nil { - // Compare nullable pointers properly - if existing.GroupID == nil || *payload.GroupID != *existing.GroupID { - existingVal := "null" - if existing.GroupID != nil { - existingVal = fmt.Sprintf("%d", *existing.GroupID) - } - return errors.NewValidation(fmt.Sprintf("field 'group_id' is immutable. Cannot change from '%s' to '%d'", existingVal, *payload.GroupID)) - } - } - - // Check url immutability - only validate if explicitly provided - if payload.URL != nil && *payload.URL != existing.URL { - return errors.NewValidation(fmt.Sprintf("field 'url' is immutable. Cannot change from '%s' to '%s'", existing.URL, *payload.URL)) - } - - // Check group_name immutability - only validate if explicitly provided - if payload.GroupName != nil && *payload.GroupName != existing.GroupName { - return errors.NewValidation(fmt.Sprintf("field 'group_name' is immutable. Cannot change from '%s' to '%s'", existing.GroupName, *payload.GroupName)) - } - - // Validate global_owners email addresses if being updated - // Primary services MUST always have at least one owner - critical business rule - if existing.Type == constants.ServiceTypePrimary { - if len(payload.GlobalOwners) == 0 { - return errors.NewValidation("global_owners must contain at least one email address for primary service type") - } - } - if len(payload.GlobalOwners) > 10 { - return errors.NewValidation("global_owners must not exceed 10 email addresses") - } - if err := validateEmailAddresses(payload.GlobalOwners, "global_owners"); err != nil { - return err - } - - return nil -} - -// validateDeleteProtectionRules validates deletion protection rules based on service type -func validateDeleteProtectionRules(service *model.GrpsIOService) error { - // Delete Protection Rules: - // - primary services: Cannot be deleted (critical infrastructure protection) - // - formation/shared services: Can be deleted by owner only (TODO: implement owner check) - - switch service.Type { - case constants.ServiceTypePrimary: - return errors.NewValidation("Primary services cannot be deleted as they are critical infrastructure components") - case constants.ServiceTypeFormation: - // TODO: Add owner permission check when OpenFGA integration is complete - // For now, allow deletion of formation services - slog.Debug("Allowing deletion of formation service", "service_id", service.UID, "type", service.Type) - return nil - case constants.ServiceTypeShared: - // TODO: Add owner permission check when OpenFGA integration is complete - // For now, allow deletion of shared services - slog.Debug("Allowing deletion of shared service", "service_id", service.UID, "type", service.Type) - return nil - default: - return errors.NewValidation(fmt.Sprintf("Unknown service type '%s' - deletion not permitted", service.Type)) - } -} - -// validateEmailAddresses validates a slice of email addresses -func validateEmailAddresses(emails []string, fieldName string) error { - if emails == nil { - return nil - } - for _, email := range emails { - if _, err := mail.ParseAddress(email); err != nil { - return errors.NewValidation(fmt.Sprintf("invalid email address in %s: %s", fieldName, redaction.RedactEmail(email))) - } - } - return nil -} - -// validateMailingListCreation validates mailing list creation payload -func validateMailingListCreation(payload *mailinglistservice.CreateGrpsioMailingListPayload) error { - if payload == nil { - return errors.NewValidation("payload is required") - } - - // Group name validation: length, pattern, and reserved words - if err := validateGroupName(payload.GroupName, "group_name"); err != nil { - return err - } - - // Title, description, and committee filter format validations now handled by GOA - - // Committees validation - each committee must have a UID - for i, committee := range payload.Committees { - if committee == nil { - return errors.NewValidation(fmt.Sprintf("committees[%d] cannot be nil", i)) - } - if committee.UID == "" { - return errors.NewValidation(fmt.Sprintf("committees[%d].uid is required", i)) - } - } - - return nil -} - -// Description length validation is now handled by GOA design layer - -// validateMailingListUpdate validates update constraints for mailing lists -func validateMailingListUpdate(ctx context.Context, existing *model.GrpsIOMailingList, parentService *model.GrpsIOService, payload *mailinglistservice.UpdateGrpsioMailingListPayload, serviceReader port.GrpsIOServiceReader) error { - // Validate group_name immutability (critical business rule) - if payload.GroupName != existing.GroupName { - return errors.NewValidation("field 'group_name' is immutable") - } - - // Validate main group restrictions (critical business rule from Groups.io) - if parentService != nil && isMainGroupForService(existing, parentService) { - // Main groups must remain public announcement lists - if payload.Type != "announcement" { - return errors.NewValidation("main group must be an announcement list") - } - if !payload.Public { - return errors.NewValidation("main group must remain public") - } - } - - // Cannot set type to "custom" unless already "custom" (Groups.io business rule) - if payload.Type == "custom" && existing.Type != "custom" { - return errors.NewValidation("cannot set type to \"custom\"") - } - - // Cannot change visibility from private to public - // TODO: LFXV2-479 - Migrate from boolean 'public' field to string 'visibility' field - // for full Groups.io API compatibility (supporting "public", "private", "custom" values) - if !existing.Public && payload.Public { - return errors.NewValidation("cannot change visibility from private to public") - } - - // Parent service change validation (allow within same project only) - if payload.ServiceUID != existing.ServiceUID { - // Check if service reader is available for validation - if serviceReader == nil { - // Fallback to old restrictive behavior if no service reader provided - slog.WarnContext(ctx, "service reader not available for parent service validation - blocking change", - "mailing_list_uid", existing.UID, - "old_service_uid", existing.ServiceUID, - "new_service_uid", payload.ServiceUID) - return errors.NewValidation("cannot change parent service") - } - - // Fetch the new parent service to validate the project ownership - newParentService, _, err := serviceReader.GetGrpsIOService(ctx, payload.ServiceUID) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve new parent service for validation", - "error", err, - "new_service_uid", payload.ServiceUID, - "mailing_list_uid", existing.UID) - return errors.NewValidation("new parent service not found") - } - - // Allow parent service changes only within the same project - if newParentService.ProjectUID != existing.ProjectUID { - slog.WarnContext(ctx, "blocked cross-project parent service change", - "mailing_list_uid", existing.UID, - "current_project_uid", existing.ProjectUID, - "new_project_uid", newParentService.ProjectUID, - "current_service_uid", existing.ServiceUID, - "new_service_uid", payload.ServiceUID) - return errors.NewValidation("cannot move mailing list to service in different project") - } - - slog.InfoContext(ctx, "allowing parent service change within same project", - "mailing_list_uid", existing.UID, - "project_uid", existing.ProjectUID, - "old_service_uid", existing.ServiceUID, - "new_service_uid", payload.ServiceUID) - } - - // Detect committee changes for member sync - if committeesChanged(existing.Committees, payload.Committees) { - // TODO: LFXV2-478 - Trigger committee member sync - slog.Debug("committees change detected - member sync required", "mailing_list_uid", existing.UID) - } - - // Description and title length validations now handled by GOA - - // Validate subject tag format if provided - subjectTagValue := payloadStringValue(payload.SubjectTag) - if subjectTagValue != "" { - if !isValidSubjectTag(subjectTagValue) { - return errors.NewValidation("invalid subject tag format") - } - } - - // Committee filter enum validations now handled by GOA - - return nil -} - -// validateMailingListDeleteProtection validates deletion protection rules -func validateMailingListDeleteProtection(mailingList *model.GrpsIOMailingList, parentService *model.GrpsIOService) error { - // Check if it's a main group (any service type) - if parentService != nil && isMainGroupForService(mailingList, parentService) { - return errors.NewValidation(fmt.Sprintf("cannot delete the main group of a %s service", parentService.Type)) - } - - // Protect announcement lists (typically used for critical communications) - if mailingList.Type == "announcement" { - return errors.NewValidation("announcement lists require special handling for deletion") - } - - // Check for active committee associations - if len(mailingList.Committees) > 0 { - // TODO: LFXV2-478 - When committee sync is implemented, validate: - // - Check if committee sync is active - // - Verify no pending sync operations - // - Ensure committee members are notified - committeeUIDs := make([]string, 0, len(mailingList.Committees)) - for _, c := range mailingList.Committees { - committeeUIDs = append(committeeUIDs, c.UID) - } - slog.Debug("committee-based list deletion - cleanup may be required", - "mailing_list_uid", mailingList.UID, - "committee_uids", committeeUIDs) - } - - // TODO: LFXV2-353 - Groups.io API integration for: - // - Actual group/subgroup creation and validation - // - Validate subscriber count (block if >50 active subscribers) - // - Check for recent activity (block if activity within 7 days) - // - Verify no pending messages in moderation queue - // - DNS delegation checks for primary services - - // TODO: LFXV2-478 - Committee service integration for: - // - Member synchronization when committee changes - // - Committee association event handling - // - Automatic member updates based on committee filters - - return nil -} - -// isValidSubjectTag validates subject tag format (business logic only) -// Length validation is now handled by GOA design layer -func isValidSubjectTag(tag string) bool { - trimmed := strings.TrimSpace(tag) - if len(trimmed) == 0 { - return false - } - - // Check for characters that would break email subject formatting - invalidChars := []string{"\n", "\r", "\t", "[", "]"} - for _, char := range invalidChars { - if strings.Contains(trimmed, char) { - return false - } - } - - return true -} - -// contains checks if a string slice contains a specific string -func contains(slice []string, item string) bool { - for _, s := range slice { - if s == item { - return true - } - } - return false -} - -// isMainGroupForService determines if a list is the "main" group for its parent. -func isMainGroupForService(ml *model.GrpsIOMailingList, svc *model.GrpsIOService) bool { - switch svc.Type { - case constants.ServiceTypePrimary: - return ml.GroupName == svc.GroupName - case constants.ServiceTypeFormation, constants.ServiceTypeShared: - return ml.GroupName == svc.Prefix - default: - return false - } -} - -// committeesChanged detects if committees have changed between existing domain model and payload -// This includes changes to UIDs and AllowedVotingStatuses to ensure proper validation -func committeesChanged(existing []model.Committee, payload []*mailinglistservice.Committee) bool { - // Different number of committees means changed - if len(existing) != len(payload) { - return true - } - - // Build a map of existing committees by UID for detailed comparison - existingMap := make(map[string]model.Committee) - for _, c := range existing { - existingMap[c.UID] = c - } - - // Check if any payload committee has changed UID or AllowedVotingStatuses - for _, payloadCommittee := range payload { - if payloadCommittee == nil { - continue - } - - existingCommittee, exists := existingMap[payloadCommittee.UID] - if !exists { - // Committee UID not found in existing - return true - } - - // Compare allowed voting statuses - both length and content - if len(existingCommittee.AllowedVotingStatuses) != len(payloadCommittee.AllowedVotingStatuses) { - return true - } - - // Build status set for comparison - existingStatuses := make(map[string]bool) - for _, s := range existingCommittee.AllowedVotingStatuses { - existingStatuses[s] = true - } - - // Check if any payload status is missing from existing - for _, s := range payloadCommittee.AllowedVotingStatuses { - if !existingStatuses[s] { - return true - } - } - } - - return false -} - -// validateMemberUpdate validates that immutable fields are not changed during updates -func validateMemberUpdate(existing, updated *model.GrpsIOMember) error { - if existing == nil || updated == nil { - return errors.NewValidation("invalid member data for validation") - } - - // Check immutable fields - if existing.Email != updated.Email { - return errors.NewValidation("email cannot be changed") - } - - if existing.UID != updated.UID { - return errors.NewValidation("member UID cannot be changed") - } - - if existing.MailingListUID != updated.MailingListUID { - return errors.NewValidation("mailing list UID cannot be changed") - } - - // TODO: LFXV2-353 - Add validation for Groups.io sync requirements - // if existing.GroupsIOMemberID != updated.GroupsIOMemberID { - // return errors.NewBadRequest("Groups.io member ID cannot be changed") - // } - - return nil -} - -// validateMemberCreation validates business logic for member creation beyond GOA's basic validations -func validateMemberCreation(ctx context.Context, payload *mailinglistservice.CreateGrpsioMailingListMemberPayload, reader port.GrpsIOServiceReader) error { - slog.DebugContext(ctx, "validating member creation payload") - if payload == nil { - return errors.NewValidation("payload is required") - } - - // Validate mailing list exists - this will be checked by the orchestrator as well, - // but we validate early to provide better error messages - if payload.UID == "" { - return errors.NewValidation("mailing list UID is required") - } - - // Check for valid email format - GOA already validates this, but we can add additional business rules here - if payload.Email == "" { - return errors.NewValidation("email is required") - } - - // TODO: LFXV2-480 - Add business logic validations: - // - Validate mailing list capacity limits - // - Check member permissions based on who's adding them - - // TODO: LFXV2-353 - Groups.io API integration: - // - Validate against Groups.io API constraints - // - Auto-adopt members from Groups.io if they exist there but not in our database - - return nil -} - -// validateMemberDeleteProtection validates that a member can be safely deleted -func validateMemberDeleteProtection(member *model.GrpsIOMember) error { - if member == nil { - return errors.NewValidation("member is required for deletion validation") - } - - // Basic validation - member must be in a valid state for deletion - if member.UID == "" { - return errors.NewValidation("member UID is required") - } - - // Check if member is an owner or moderator - log warning for now - if member.ModStatus == "owner" { - slog.Warn("Deleting an owner - ensure this is not the sole owner", - "member_uid", member.UID, - "email", redaction.RedactEmail(member.Email), - "mailing_list_uid", member.MailingListUID) - } - - if member.ModStatus == "moderator" { - slog.Info("Deleting a moderator", - "member_uid", member.UID, - "email", redaction.RedactEmail(member.Email), - "mailing_list_uid", member.MailingListUID) - } - - // TODO: LFXV2-353 - Add sole owner/moderator protection via Groups.io API - // This is already noted in the delete endpoint with a TODO comment - // When Groups.io API integration is added, we will: - // - Check if this member is the only owner/moderator of the mailing list - // - Prevent deletion if it would orphan the mailing list (return error if sole owner) - // - Validate member status allows deletion - // - Check cascading impacts of member deletion - // - Handle Groups.io API error "sole group owner" as seen in old implementation - - return nil -} - -// ================================================================================== -// Enhanced Business Rule Validation (POST-PUT Conversion) -// ================================================================================== -// These functions validate business rules AFTER PUT payload conversion to prevent -// violations of critical constraints while maintaining pure PUT semantics. - -// validateServiceBusinessRules validates business rules after PUT conversion -// This prevents PUT semantics from violating mandatory business constraints -func validateServiceBusinessRules(service *model.GrpsIOService) error { - // Primary services MUST have GlobalOwners (critical business rule) - // This prevents clearing GlobalOwners via PUT from making primary services invalid - if service.Type == constants.ServiceTypePrimary && len(service.GlobalOwners) == 0 { - return errors.NewValidation("primary services must have at least one global owner") - } - - // Additional service business rules can be added here - // Example: specific service types requiring certain fields - - return nil -} diff --git a/cmd/mailing-list-api/service/service_validators_test.go b/cmd/mailing-list-api/service/service_validators_test.go deleted file mode 100644 index b07117e..0000000 --- a/cmd/mailing-list-api/service/service_validators_test.go +++ /dev/null @@ -1,987 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "testing" - - mailinglistservice "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestEtagValidator(t *testing.T) { - tests := []struct { - name string - etag *string - expected uint64 - expectErr bool - }{ - { - name: "valid numeric etag", - etag: stringPtr("123"), - expected: 123, - expectErr: false, - }, - { - name: "valid quoted etag", - etag: stringPtr(`"456"`), - expected: 456, - expectErr: false, - }, - { - name: "valid weak etag", - etag: stringPtr(`W/"789"`), - expected: 789, - expectErr: false, - }, - { - name: "valid weak etag lowercase", - etag: stringPtr(`w/"101"`), - expected: 101, - expectErr: false, - }, - { - name: "nil etag", - etag: nil, - expected: 0, - expectErr: true, - }, - { - name: "empty etag", - etag: stringPtr(""), - expected: 0, - expectErr: true, - }, - { - name: "invalid etag format", - etag: stringPtr("invalid"), - expected: 0, - expectErr: true, - }, - { - name: "etag with spaces", - etag: stringPtr(" 123 "), - expected: 123, - expectErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := etagValidator(tt.etag) - - if tt.expectErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.expected, result) - } - }) - } -} - -func TestValidateServiceCreationRules(t *testing.T) { - tests := []struct { - name string - payload *mailinglistservice.CreateGrpsioServicePayload - expectErr bool - }{ - { - name: "valid primary service", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "primary", - GlobalOwners: []string{"owner@example.com"}, - }, - expectErr: false, - }, - { - name: "valid formation service", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "formation", - Prefix: stringPtr("test-prefix"), - }, - expectErr: false, - }, - { - name: "valid shared service", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "shared", - Prefix: stringPtr("shared-prefix"), - GroupID: int64Ptr(12345), - }, - expectErr: false, - }, - { - name: "invalid service type", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "invalid-type", - }, - expectErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateServiceCreationRules(tt.payload) - - if tt.expectErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestValidatePrimaryRules(t *testing.T) { - tests := []struct { - name string - payload *mailinglistservice.CreateGrpsioServicePayload - expectErr bool - }{ - { - name: "valid primary service", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "primary", - GlobalOwners: []string{"owner@example.com"}, - }, - expectErr: false, - }, - { - name: "primary service with prefix should fail", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "primary", - Prefix: stringPtr("test-prefix"), - GlobalOwners: []string{"owner@example.com"}, - }, - expectErr: true, - }, - { - name: "primary service without global owners should fail", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "primary", - }, - expectErr: true, - }, - { - name: "primary service with invalid email should fail", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "primary", - GlobalOwners: []string{"invalid-email"}, - }, - expectErr: true, - }, - { - name: "primary service with too many emails should fail", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "primary", - GlobalOwners: []string{"owner1@example.com", "owner2@example.com", "owner3@example.com", "owner4@example.com", "owner5@example.com", "owner6@example.com", "owner7@example.com", "owner8@example.com", "owner9@example.com", "owner10@example.com", "owner11@example.com"}, - }, - expectErr: true, - }, - { - name: "primary service with empty prefix string is valid", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "primary", - Prefix: stringPtr(""), - GlobalOwners: []string{"owner@example.com"}, - }, - expectErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validatePrimaryRules(tt.payload) - - if tt.expectErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestValidateFormationRules(t *testing.T) { - tests := []struct { - name string - payload *mailinglistservice.CreateGrpsioServicePayload - expectErr bool - }{ - { - name: "valid formation service", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "formation", - Prefix: stringPtr("test-prefix"), - }, - expectErr: false, - }, - { - name: "formation service with global owners", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "formation", - Prefix: stringPtr("test-prefix"), - GlobalOwners: []string{"owner@example.com"}, - }, - expectErr: false, - }, - { - name: "formation service without prefix should fail", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "formation", - }, - expectErr: true, - }, - { - name: "formation service with empty prefix should fail", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "formation", - Prefix: stringPtr(""), - }, - expectErr: true, - }, - { - name: "formation service with whitespace prefix should fail", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "formation", - Prefix: stringPtr(" "), - }, - expectErr: true, - }, - { - name: "formation service with invalid email should fail", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "formation", - Prefix: stringPtr("test-prefix"), - GlobalOwners: []string{"invalid-email"}, - }, - expectErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateFormationRules(tt.payload) - - if tt.expectErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestValidateSharedRules(t *testing.T) { - tests := []struct { - name string - payload *mailinglistservice.CreateGrpsioServicePayload - expectErr bool - }{ - { - name: "valid shared service", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "shared", - Prefix: stringPtr("shared-prefix"), - GroupID: int64Ptr(12345), - }, - expectErr: false, - }, - { - name: "shared service without prefix should fail", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "shared", - GroupID: int64Ptr(12345), - }, - expectErr: true, - }, - { - name: "shared service with empty prefix should fail", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "shared", - Prefix: stringPtr(""), - GroupID: int64Ptr(12345), - }, - expectErr: true, - }, - { - name: "shared service without group_id should pass (group_id inherited from parent)", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "shared", - Prefix: stringPtr("shared-prefix"), - }, - expectErr: false, - }, - { - name: "shared service with invalid group_id should pass (group_id inherited from parent)", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "shared", - Prefix: stringPtr("shared-prefix"), - GroupID: int64Ptr(0), - }, - expectErr: false, - }, - { - name: "shared service with negative group_id should pass (group_id inherited from parent)", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "shared", - Prefix: stringPtr("shared-prefix"), - GroupID: int64Ptr(-1), - }, - expectErr: false, - }, - { - name: "shared service with global owners should fail", - payload: &mailinglistservice.CreateGrpsioServicePayload{ - Type: "shared", - Prefix: stringPtr("shared-prefix"), - GroupID: int64Ptr(12345), - GlobalOwners: []string{"owner@example.com"}, - }, - expectErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateSharedRules(tt.payload) - - if tt.expectErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestValidateUpdateImmutabilityConstraints(t *testing.T) { - existing := &model.GrpsIOService{ - Type: "primary", - ProjectUID: "project-123", - Prefix: "", - Domain: "example.groups.io", - GroupID: int64Ptr(12345), - URL: "https://example.groups.io/g/test", - GroupName: "test-group", - } - - tests := []struct { - name string - payload *mailinglistservice.UpdateGrpsioServicePayload - expectErr bool - }{ - { - name: "valid update with mutable fields only", - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - Type: "primary", - ProjectUID: "project-123", - Status: stringPtr("active"), - GlobalOwners: []string{"newowner@example.com"}, - Public: true, - }, - expectErr: false, - }, - { - name: "attempt to change type should fail", - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - Type: "formation", - ProjectUID: "project-123", - }, - expectErr: true, - }, - { - name: "attempt to change project_uid should fail", - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - Type: "primary", - ProjectUID: "different-project", - }, - expectErr: true, - }, - { - name: "attempt to change prefix should fail", - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - Type: "primary", - ProjectUID: "project-123", - Prefix: stringPtr("new-prefix"), - }, - expectErr: true, - }, - { - name: "attempt to change domain should fail", - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - Type: "primary", - ProjectUID: "project-123", - Domain: stringPtr("different.groups.io"), - }, - expectErr: true, - }, - { - name: "attempt to change group_id should fail", - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - Type: "primary", - ProjectUID: "project-123", - GroupID: int64Ptr(99999), - }, - expectErr: true, - }, - { - name: "attempt to change url should fail", - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - Type: "primary", - ProjectUID: "project-123", - URL: stringPtr("https://different.groups.io/g/test"), - }, - expectErr: true, - }, - { - name: "attempt to change group_name should fail", - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - Type: "primary", - ProjectUID: "project-123", - GroupName: stringPtr("different-group"), - }, - expectErr: true, - }, - { - name: "update with invalid email should fail", - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - Type: "primary", - ProjectUID: "project-123", - GlobalOwners: []string{"invalid-email"}, - }, - expectErr: true, - }, - { - name: "update with too many global owners should fail", - payload: &mailinglistservice.UpdateGrpsioServicePayload{ - Type: "primary", - ProjectUID: "project-123", - GlobalOwners: []string{"owner1@example.com", "owner2@example.com", "owner3@example.com", "owner4@example.com", "owner5@example.com", "owner6@example.com", "owner7@example.com", "owner8@example.com", "owner9@example.com", "owner10@example.com", "owner11@example.com"}, - }, - expectErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateUpdateImmutabilityConstraints(existing, tt.payload) - - if tt.expectErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestValidateDeleteProtectionRules(t *testing.T) { - tests := []struct { - name string - service *model.GrpsIOService - expectErr bool - }{ - { - name: "primary service deletion should fail", - service: &model.GrpsIOService{ - UID: "service-123", - Type: "primary", - }, - expectErr: true, - }, - { - name: "formation service deletion should succeed", - service: &model.GrpsIOService{ - UID: "service-456", - Type: "formation", - }, - expectErr: false, - }, - { - name: "shared service deletion should succeed", - service: &model.GrpsIOService{ - UID: "service-789", - Type: "shared", - }, - expectErr: false, - }, - { - name: "unknown service type deletion should fail", - service: &model.GrpsIOService{ - UID: "service-unknown", - Type: "unknown", - }, - expectErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateDeleteProtectionRules(tt.service) - - if tt.expectErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestValidateEmailAddresses(t *testing.T) { - tests := []struct { - name string - emails []string - fieldName string - expectErr bool - }{ - { - name: "valid email addresses", - emails: []string{"test@example.com", "user@domain.org"}, - fieldName: "global_owners", - expectErr: false, - }, - { - name: "single valid email", - emails: []string{"valid@email.com"}, - fieldName: "global_owners", - expectErr: false, - }, - { - name: "invalid email address", - emails: []string{"invalid-email"}, - fieldName: "global_owners", - expectErr: true, - }, - { - name: "mixed valid and invalid emails", - emails: []string{"valid@email.com", "invalid-email"}, - fieldName: "global_owners", - expectErr: true, - }, - { - name: "nil email slice", - emails: nil, - fieldName: "global_owners", - expectErr: false, - }, - { - name: "empty email slice", - emails: []string{}, - fieldName: "global_owners", - expectErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateEmailAddresses(tt.emails, tt.fieldName) - - if tt.expectErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestValidateMailingListCreation(t *testing.T) { - tests := []struct { - name string - payload *mailinglistservice.CreateGrpsioMailingListPayload - expectErr bool - }{ - { - name: "valid mailing list payload", - payload: &mailinglistservice.CreateGrpsioMailingListPayload{ - GroupName: "test-list", - Type: "discussion_open", - Description: "This is a test mailing list description", - Title: "Test List", - ServiceUID: "parent-123", - }, - expectErr: false, - }, - { - name: "valid mailing list with committee", - payload: &mailinglistservice.CreateGrpsioMailingListPayload{ - GroupName: "committee-list", - Type: "discussion_moderated", - Committees: []*mailinglistservice.Committee{ - {UID: "committee-123", AllowedVotingStatuses: []string{"Voting Rep", "Observer"}}, - }, - Description: "Committee-based mailing list", - Title: "Committee List", - ServiceUID: "parent-456", - }, - expectErr: false, - }, - { - name: "nil payload should fail", - payload: nil, - expectErr: true, - }, - // Group name length test removed - now handled by GOA MaxLength validation - { - name: "committee filters without committee UID should fail", - payload: &mailinglistservice.CreateGrpsioMailingListPayload{ - GroupName: "invalid-list", - Type: "discussion_open", - Committees: []*mailinglistservice.Committee{ - {UID: "", AllowedVotingStatuses: []string{"Voting Rep"}}, // Empty UID with filters - }, - Description: "Invalid committee setup", - Title: "Invalid List", - ServiceUID: "parent-123", - }, - expectErr: true, - }, - // Committee filter enum test removed - now handled by GOA enum validation - // Group name length test removed - now handled by GOA MinLength validation - // Group name pattern test removed - now handled by GOA Pattern validation - { - name: "reserved word group name should fail", - payload: &mailinglistservice.CreateGrpsioMailingListPayload{ - GroupName: "admin", - Type: "announcement", - Description: "Test description that meets minimum length", - Title: "Test Title", - ServiceUID: "parent-123", - }, - expectErr: true, - }, - // Title length test removed - now handled by GOA MinLength validation - // Title length test removed - now handled by GOA MaxLength validation - // Description length test removed - now handled by GOA MaxLength validation - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateMailingListCreation(tt.payload) - - if tt.expectErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestContains(t *testing.T) { - tests := []struct { - name string - slice []string - item string - expected bool - }{ - { - name: "item exists in slice", - slice: []string{"a", "b", "c"}, - item: "b", - expected: true, - }, - { - name: "item does not exist in slice", - slice: []string{"a", "b", "c"}, - item: "d", - expected: false, - }, - { - name: "empty slice", - slice: []string{}, - item: "a", - expected: false, - }, - { - name: "nil slice", - slice: nil, - item: "a", - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := contains(tt.slice, tt.item) - assert.Equal(t, tt.expected, result) - }) - } -} - -// MockServiceReader is a mock implementation of the GrpsIOServiceReader interface -type MockServiceReader struct { - mock.Mock -} - -func (m *MockServiceReader) GetGrpsIOService(ctx context.Context, uid string) (*model.GrpsIOService, uint64, error) { - args := m.Called(ctx, uid) - if args.Get(0) == nil { - return nil, 0, args.Error(2) - } - return args.Get(0).(*model.GrpsIOService), args.Get(1).(uint64), args.Error(2) -} - -func (m *MockServiceReader) GetRevision(ctx context.Context, uid string) (uint64, error) { - args := m.Called(ctx, uid) - return args.Get(0).(uint64), args.Error(1) -} - -func (m *MockServiceReader) GetServicesByGroupID(ctx context.Context, groupID uint64) ([]*model.GrpsIOService, error) { - args := m.Called(ctx, groupID) - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).([]*model.GrpsIOService), args.Error(1) -} - -func (m *MockServiceReader) GetServicesByProjectUID(ctx context.Context, projectUID string) ([]*model.GrpsIOService, error) { - args := m.Called(ctx, projectUID) - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).([]*model.GrpsIOService), args.Error(1) -} - -func (m *MockServiceReader) GetGrpsIOServiceSettings(ctx context.Context, uid string) (*model.GrpsIOServiceSettings, uint64, error) { - args := m.Called(ctx, uid) - if args.Get(0) == nil { - return nil, 0, args.Error(2) - } - return args.Get(0).(*model.GrpsIOServiceSettings), args.Get(1).(uint64), args.Error(2) -} - -func (m *MockServiceReader) GetSettingsRevision(ctx context.Context, uid string) (uint64, error) { - args := m.Called(ctx, uid) - return args.Get(0).(uint64), args.Error(1) -} - -func TestValidateMailingListUpdateParentServiceChange(t *testing.T) { - ctx := context.Background() - - existingMailingList := &model.GrpsIOMailingList{ - UID: "mailing-list-123", - GroupName: "sub-list", // Different from parent service to avoid main group rules - Public: false, - Type: "discussion_open", - ServiceUID: "service-123", - ProjectUID: "project-123", - ProjectName: "Test Project", - Description: "Test description for mailing list", - } - - parentService := &model.GrpsIOService{ - UID: "service-123", - Type: "primary", - ProjectUID: "project-123", - GroupName: "main-group", // Different from mailing list to avoid main group rules - } - - tests := []struct { - name string - payload *mailinglistservice.UpdateGrpsioMailingListPayload - setupMock func(*MockServiceReader) - expectErr bool - expectedErrMsg string - }{ - { - name: "valid update without parent service change", - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - GroupName: "sub-list", // unchanged - ServiceUID: "service-123", // unchanged - Description: "Updated description that is long enough", - Type: "discussion_moderated", - Public: false, - }, - setupMock: func(m *MockServiceReader) { - // No mock calls expected since ServiceUID hasn't changed - }, - expectErr: false, - }, - { - name: "valid parent service change within same project", - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - GroupName: "sub-list", - ServiceUID: "service-456", // different service - Description: "Updated description that is long enough", - Type: "discussion_open", - Public: false, - }, - setupMock: func(m *MockServiceReader) { - newService := &model.GrpsIOService{ - UID: "service-456", - Type: "formation", - ProjectUID: "project-123", // same project - Prefix: "formation", - } - m.On("GetGrpsIOService", ctx, "service-456").Return(newService, uint64(1), nil) - }, - expectErr: false, - }, - { - name: "blocked cross-project parent service change", - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - GroupName: "sub-list", - ServiceUID: "service-different-project", // different project - Description: "Updated description that is long enough", - Type: "discussion_open", - Public: false, - }, - setupMock: func(m *MockServiceReader) { - differentProjectService := &model.GrpsIOService{ - UID: "service-different-project", - Type: "primary", - ProjectUID: "different-project-456", // different project - } - m.On("GetGrpsIOService", ctx, "service-different-project").Return(differentProjectService, uint64(1), nil) - }, - expectErr: true, - expectedErrMsg: "cannot move mailing list to service in different project", - }, - { - name: "parent service change with non-existent service", - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - GroupName: "sub-list", - ServiceUID: "non-existent-service", - Description: "Updated description that is long enough", - Type: "discussion_open", - Public: false, - }, - setupMock: func(m *MockServiceReader) { - m.On("GetGrpsIOService", ctx, "non-existent-service").Return(nil, uint64(0), errors.NewServiceUnavailable("service not found")) - }, - expectErr: true, - expectedErrMsg: "new parent service not found", - }, - { - name: "immutable group_name change should fail", - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - GroupName: "different-list-name", // changed - ServiceUID: "service-123", - Description: "Updated description that is long enough", - Type: "discussion_open", - Public: false, - }, - setupMock: func(m *MockServiceReader) { - // No mock calls expected since validation should fail before that - }, - expectErr: true, - expectedErrMsg: "field 'group_name' is immutable", - }, - { - name: "private to public visibility change should fail", - payload: &mailinglistservice.UpdateGrpsioMailingListPayload{ - GroupName: "sub-list", - ServiceUID: "service-123", - Description: "Updated description that is long enough", - Type: "discussion_open", - Public: true, // changing from private to public - }, - setupMock: func(m *MockServiceReader) { - // No mock calls expected since validation should fail before that - }, - expectErr: true, - expectedErrMsg: "cannot change visibility from private to public", - }, - // Description length test removed - now handled by GOA MinLength validation - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockReader := new(MockServiceReader) - tt.setupMock(mockReader) - - err := validateMailingListUpdate(ctx, existingMailingList, parentService, tt.payload, mockReader) - - if tt.expectErr { - assert.Error(t, err) - if tt.expectedErrMsg != "" { - assert.Contains(t, err.Error(), tt.expectedErrMsg) - } - } else { - assert.NoError(t, err) - } - - mockReader.AssertExpectations(t) - }) - } -} - -// ================================================================================== -// Enhanced Business Rule Validation Tests (POST-PUT Conversion) -// ================================================================================== - -func TestValidateServiceBusinessRules(t *testing.T) { - tests := []struct { - name string - service *model.GrpsIOService - expectErr bool - errMsg string - }{ - { - name: "primary service with GlobalOwners should pass", - service: &model.GrpsIOService{ - Type: "primary", - GlobalOwners: []string{"owner@example.com"}, - }, - expectErr: false, - }, - { - name: "primary service without GlobalOwners should fail", - service: &model.GrpsIOService{ - Type: "primary", - GlobalOwners: []string{}, // Empty - should fail - }, - expectErr: true, - errMsg: "primary services must have at least one global owner", - }, - { - name: "primary service with nil GlobalOwners should fail", - service: &model.GrpsIOService{ - Type: "primary", - GlobalOwners: nil, // Nil - should fail - }, - expectErr: true, - errMsg: "primary services must have at least one global owner", - }, - { - name: "formation service without GlobalOwners should pass", - service: &model.GrpsIOService{ - Type: "formation", - GlobalOwners: []string{}, // Empty is OK for formation - }, - expectErr: false, - }, - { - name: "shared service without GlobalOwners should pass", - service: &model.GrpsIOService{ - Type: "shared", - GlobalOwners: nil, // Nil is OK for shared - }, - expectErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateServiceBusinessRules(tt.service) - - if tt.expectErr { - assert.Error(t, err) - if tt.errMsg != "" { - assert.Contains(t, err.Error(), tt.errMsg) - } - } else { - assert.NoError(t, err) - } - }) - } -} diff --git a/gen/http/cli/mailing_list/cli.go b/gen/http/cli/mailing_list/cli.go index 22fe514..93ef33d 100644 --- a/gen/http/cli/mailing_list/cli.go +++ b/gen/http/cli/mailing_list/cli.go @@ -23,7 +23,7 @@ import ( // // command (subcommand1|subcommand2|...) func UsageCommands() string { - return `mailing-list (livez|readyz|create-grpsio-service|get-grpsio-service|update-grpsio-service|delete-grpsio-service|get-grpsio-service-settings|update-grpsio-service-settings|create-grpsio-mailing-list|get-grpsio-mailing-list|update-grpsio-mailing-list|delete-grpsio-mailing-list|get-grpsio-mailing-list-settings|update-grpsio-mailing-list-settings|create-grpsio-mailing-list-member|get-grpsio-mailing-list-member|update-grpsio-mailing-list-member|delete-grpsio-mailing-list-member|groupsio-webhook) + return `mailing-list (livez|readyz|list-groupsio-services|create-groupsio-service|get-groupsio-service|update-groupsio-service|delete-groupsio-service|get-groupsio-service-projects|find-parent-groupsio-service|list-groupsio-subgroups|create-groupsio-subgroup|get-groupsio-subgroup|update-groupsio-subgroup|delete-groupsio-subgroup|get-groupsio-subgroup-count|get-groupsio-subgroup-member-count|list-groupsio-members|add-groupsio-member|get-groupsio-member|update-groupsio-member|delete-groupsio-member|invite-groupsio-members|check-groupsio-subscriber) ` } @@ -49,127 +49,122 @@ func ParseEndpoint( mailingListReadyzFlags = flag.NewFlagSet("readyz", flag.ExitOnError) - mailingListCreateGrpsioServiceFlags = flag.NewFlagSet("create-grpsio-service", flag.ExitOnError) - mailingListCreateGrpsioServiceBodyFlag = mailingListCreateGrpsioServiceFlags.String("body", "REQUIRED", "") - mailingListCreateGrpsioServiceVersionFlag = mailingListCreateGrpsioServiceFlags.String("version", "REQUIRED", "") - mailingListCreateGrpsioServiceBearerTokenFlag = mailingListCreateGrpsioServiceFlags.String("bearer-token", "", "") - - mailingListGetGrpsioServiceFlags = flag.NewFlagSet("get-grpsio-service", flag.ExitOnError) - mailingListGetGrpsioServiceUIDFlag = mailingListGetGrpsioServiceFlags.String("uid", "REQUIRED", "Service UID -- unique identifier for the service") - mailingListGetGrpsioServiceVersionFlag = mailingListGetGrpsioServiceFlags.String("version", "", "") - mailingListGetGrpsioServiceBearerTokenFlag = mailingListGetGrpsioServiceFlags.String("bearer-token", "", "") - - mailingListUpdateGrpsioServiceFlags = flag.NewFlagSet("update-grpsio-service", flag.ExitOnError) - mailingListUpdateGrpsioServiceBodyFlag = mailingListUpdateGrpsioServiceFlags.String("body", "REQUIRED", "") - mailingListUpdateGrpsioServiceUIDFlag = mailingListUpdateGrpsioServiceFlags.String("uid", "REQUIRED", "Service UID -- unique identifier for the service") - mailingListUpdateGrpsioServiceVersionFlag = mailingListUpdateGrpsioServiceFlags.String("version", "REQUIRED", "") - mailingListUpdateGrpsioServiceBearerTokenFlag = mailingListUpdateGrpsioServiceFlags.String("bearer-token", "", "") - mailingListUpdateGrpsioServiceIfMatchFlag = mailingListUpdateGrpsioServiceFlags.String("if-match", "", "") - - mailingListDeleteGrpsioServiceFlags = flag.NewFlagSet("delete-grpsio-service", flag.ExitOnError) - mailingListDeleteGrpsioServiceUIDFlag = mailingListDeleteGrpsioServiceFlags.String("uid", "REQUIRED", "Service UID -- unique identifier for the service") - mailingListDeleteGrpsioServiceVersionFlag = mailingListDeleteGrpsioServiceFlags.String("version", "", "") - mailingListDeleteGrpsioServiceBearerTokenFlag = mailingListDeleteGrpsioServiceFlags.String("bearer-token", "", "") - mailingListDeleteGrpsioServiceIfMatchFlag = mailingListDeleteGrpsioServiceFlags.String("if-match", "", "") - - mailingListGetGrpsioServiceSettingsFlags = flag.NewFlagSet("get-grpsio-service-settings", flag.ExitOnError) - mailingListGetGrpsioServiceSettingsUIDFlag = mailingListGetGrpsioServiceSettingsFlags.String("uid", "REQUIRED", "Service UID -- unique identifier for the service") - mailingListGetGrpsioServiceSettingsVersionFlag = mailingListGetGrpsioServiceSettingsFlags.String("version", "", "") - mailingListGetGrpsioServiceSettingsBearerTokenFlag = mailingListGetGrpsioServiceSettingsFlags.String("bearer-token", "", "") - - mailingListUpdateGrpsioServiceSettingsFlags = flag.NewFlagSet("update-grpsio-service-settings", flag.ExitOnError) - mailingListUpdateGrpsioServiceSettingsBodyFlag = mailingListUpdateGrpsioServiceSettingsFlags.String("body", "REQUIRED", "") - mailingListUpdateGrpsioServiceSettingsUIDFlag = mailingListUpdateGrpsioServiceSettingsFlags.String("uid", "REQUIRED", "Service UID -- unique identifier for the service") - mailingListUpdateGrpsioServiceSettingsVersionFlag = mailingListUpdateGrpsioServiceSettingsFlags.String("version", "REQUIRED", "") - mailingListUpdateGrpsioServiceSettingsBearerTokenFlag = mailingListUpdateGrpsioServiceSettingsFlags.String("bearer-token", "", "") - mailingListUpdateGrpsioServiceSettingsIfMatchFlag = mailingListUpdateGrpsioServiceSettingsFlags.String("if-match", "", "") - - mailingListCreateGrpsioMailingListFlags = flag.NewFlagSet("create-grpsio-mailing-list", flag.ExitOnError) - mailingListCreateGrpsioMailingListBodyFlag = mailingListCreateGrpsioMailingListFlags.String("body", "REQUIRED", "") - mailingListCreateGrpsioMailingListVersionFlag = mailingListCreateGrpsioMailingListFlags.String("version", "REQUIRED", "") - mailingListCreateGrpsioMailingListBearerTokenFlag = mailingListCreateGrpsioMailingListFlags.String("bearer-token", "", "") - - mailingListGetGrpsioMailingListFlags = flag.NewFlagSet("get-grpsio-mailing-list", flag.ExitOnError) - mailingListGetGrpsioMailingListUIDFlag = mailingListGetGrpsioMailingListFlags.String("uid", "REQUIRED", "Mailing list UID -- unique identifier for the mailing list") - mailingListGetGrpsioMailingListVersionFlag = mailingListGetGrpsioMailingListFlags.String("version", "REQUIRED", "") - mailingListGetGrpsioMailingListBearerTokenFlag = mailingListGetGrpsioMailingListFlags.String("bearer-token", "REQUIRED", "") - - mailingListUpdateGrpsioMailingListFlags = flag.NewFlagSet("update-grpsio-mailing-list", flag.ExitOnError) - mailingListUpdateGrpsioMailingListBodyFlag = mailingListUpdateGrpsioMailingListFlags.String("body", "REQUIRED", "") - mailingListUpdateGrpsioMailingListUIDFlag = mailingListUpdateGrpsioMailingListFlags.String("uid", "REQUIRED", "Mailing list UID -- unique identifier for the mailing list") - mailingListUpdateGrpsioMailingListVersionFlag = mailingListUpdateGrpsioMailingListFlags.String("version", "REQUIRED", "") - mailingListUpdateGrpsioMailingListBearerTokenFlag = mailingListUpdateGrpsioMailingListFlags.String("bearer-token", "", "") - mailingListUpdateGrpsioMailingListIfMatchFlag = mailingListUpdateGrpsioMailingListFlags.String("if-match", "", "") - - mailingListDeleteGrpsioMailingListFlags = flag.NewFlagSet("delete-grpsio-mailing-list", flag.ExitOnError) - mailingListDeleteGrpsioMailingListUIDFlag = mailingListDeleteGrpsioMailingListFlags.String("uid", "REQUIRED", "Mailing list UID -- unique identifier for the mailing list") - mailingListDeleteGrpsioMailingListVersionFlag = mailingListDeleteGrpsioMailingListFlags.String("version", "", "") - mailingListDeleteGrpsioMailingListBearerTokenFlag = mailingListDeleteGrpsioMailingListFlags.String("bearer-token", "", "") - mailingListDeleteGrpsioMailingListIfMatchFlag = mailingListDeleteGrpsioMailingListFlags.String("if-match", "", "") - - mailingListGetGrpsioMailingListSettingsFlags = flag.NewFlagSet("get-grpsio-mailing-list-settings", flag.ExitOnError) - mailingListGetGrpsioMailingListSettingsUIDFlag = mailingListGetGrpsioMailingListSettingsFlags.String("uid", "REQUIRED", "Mailing list UID -- unique identifier for the mailing list") - mailingListGetGrpsioMailingListSettingsVersionFlag = mailingListGetGrpsioMailingListSettingsFlags.String("version", "", "") - mailingListGetGrpsioMailingListSettingsBearerTokenFlag = mailingListGetGrpsioMailingListSettingsFlags.String("bearer-token", "", "") - - mailingListUpdateGrpsioMailingListSettingsFlags = flag.NewFlagSet("update-grpsio-mailing-list-settings", flag.ExitOnError) - mailingListUpdateGrpsioMailingListSettingsBodyFlag = mailingListUpdateGrpsioMailingListSettingsFlags.String("body", "REQUIRED", "") - mailingListUpdateGrpsioMailingListSettingsUIDFlag = mailingListUpdateGrpsioMailingListSettingsFlags.String("uid", "REQUIRED", "Mailing list UID -- unique identifier for the mailing list") - mailingListUpdateGrpsioMailingListSettingsVersionFlag = mailingListUpdateGrpsioMailingListSettingsFlags.String("version", "REQUIRED", "") - mailingListUpdateGrpsioMailingListSettingsBearerTokenFlag = mailingListUpdateGrpsioMailingListSettingsFlags.String("bearer-token", "", "") - mailingListUpdateGrpsioMailingListSettingsIfMatchFlag = mailingListUpdateGrpsioMailingListSettingsFlags.String("if-match", "", "") - - mailingListCreateGrpsioMailingListMemberFlags = flag.NewFlagSet("create-grpsio-mailing-list-member", flag.ExitOnError) - mailingListCreateGrpsioMailingListMemberBodyFlag = mailingListCreateGrpsioMailingListMemberFlags.String("body", "REQUIRED", "") - mailingListCreateGrpsioMailingListMemberUIDFlag = mailingListCreateGrpsioMailingListMemberFlags.String("uid", "REQUIRED", "Mailing list UID") - mailingListCreateGrpsioMailingListMemberVersionFlag = mailingListCreateGrpsioMailingListMemberFlags.String("version", "REQUIRED", "") - mailingListCreateGrpsioMailingListMemberBearerTokenFlag = mailingListCreateGrpsioMailingListMemberFlags.String("bearer-token", "", "") - - mailingListGetGrpsioMailingListMemberFlags = flag.NewFlagSet("get-grpsio-mailing-list-member", flag.ExitOnError) - mailingListGetGrpsioMailingListMemberUIDFlag = mailingListGetGrpsioMailingListMemberFlags.String("uid", "REQUIRED", "Mailing list UID -- unique identifier for the mailing list") - mailingListGetGrpsioMailingListMemberMemberUIDFlag = mailingListGetGrpsioMailingListMemberFlags.String("member-uid", "REQUIRED", "Member UID -- unique identifier for the member") - mailingListGetGrpsioMailingListMemberVersionFlag = mailingListGetGrpsioMailingListMemberFlags.String("version", "REQUIRED", "") - mailingListGetGrpsioMailingListMemberBearerTokenFlag = mailingListGetGrpsioMailingListMemberFlags.String("bearer-token", "REQUIRED", "") - - mailingListUpdateGrpsioMailingListMemberFlags = flag.NewFlagSet("update-grpsio-mailing-list-member", flag.ExitOnError) - mailingListUpdateGrpsioMailingListMemberBodyFlag = mailingListUpdateGrpsioMailingListMemberFlags.String("body", "REQUIRED", "") - mailingListUpdateGrpsioMailingListMemberUIDFlag = mailingListUpdateGrpsioMailingListMemberFlags.String("uid", "REQUIRED", "Mailing list UID -- unique identifier for the mailing list") - mailingListUpdateGrpsioMailingListMemberMemberUIDFlag = mailingListUpdateGrpsioMailingListMemberFlags.String("member-uid", "REQUIRED", "Member UID -- unique identifier for the member") - mailingListUpdateGrpsioMailingListMemberVersionFlag = mailingListUpdateGrpsioMailingListMemberFlags.String("version", "REQUIRED", "") - mailingListUpdateGrpsioMailingListMemberBearerTokenFlag = mailingListUpdateGrpsioMailingListMemberFlags.String("bearer-token", "REQUIRED", "") - mailingListUpdateGrpsioMailingListMemberIfMatchFlag = mailingListUpdateGrpsioMailingListMemberFlags.String("if-match", "REQUIRED", "") - - mailingListDeleteGrpsioMailingListMemberFlags = flag.NewFlagSet("delete-grpsio-mailing-list-member", flag.ExitOnError) - mailingListDeleteGrpsioMailingListMemberUIDFlag = mailingListDeleteGrpsioMailingListMemberFlags.String("uid", "REQUIRED", "Mailing list UID -- unique identifier for the mailing list") - mailingListDeleteGrpsioMailingListMemberMemberUIDFlag = mailingListDeleteGrpsioMailingListMemberFlags.String("member-uid", "REQUIRED", "Member UID -- unique identifier for the member") - mailingListDeleteGrpsioMailingListMemberVersionFlag = mailingListDeleteGrpsioMailingListMemberFlags.String("version", "REQUIRED", "") - mailingListDeleteGrpsioMailingListMemberBearerTokenFlag = mailingListDeleteGrpsioMailingListMemberFlags.String("bearer-token", "REQUIRED", "") - mailingListDeleteGrpsioMailingListMemberIfMatchFlag = mailingListDeleteGrpsioMailingListMemberFlags.String("if-match", "REQUIRED", "") - - mailingListGroupsioWebhookFlags = flag.NewFlagSet("groupsio-webhook", flag.ExitOnError) - mailingListGroupsioWebhookBodyFlag = mailingListGroupsioWebhookFlags.String("body", "REQUIRED", "") - mailingListGroupsioWebhookSignatureFlag = mailingListGroupsioWebhookFlags.String("signature", "REQUIRED", "") + mailingListListGroupsioServicesFlags = flag.NewFlagSet("list-groupsio-services", flag.ExitOnError) + mailingListListGroupsioServicesProjectUIDFlag = mailingListListGroupsioServicesFlags.String("project-uid", "", "") + mailingListListGroupsioServicesBearerTokenFlag = mailingListListGroupsioServicesFlags.String("bearer-token", "", "") + + mailingListCreateGroupsioServiceFlags = flag.NewFlagSet("create-groupsio-service", flag.ExitOnError) + mailingListCreateGroupsioServiceBodyFlag = mailingListCreateGroupsioServiceFlags.String("body", "REQUIRED", "") + mailingListCreateGroupsioServiceBearerTokenFlag = mailingListCreateGroupsioServiceFlags.String("bearer-token", "", "") + + mailingListGetGroupsioServiceFlags = flag.NewFlagSet("get-groupsio-service", flag.ExitOnError) + mailingListGetGroupsioServiceServiceIDFlag = mailingListGetGroupsioServiceFlags.String("service-id", "REQUIRED", "Service ID") + mailingListGetGroupsioServiceBearerTokenFlag = mailingListGetGroupsioServiceFlags.String("bearer-token", "", "") + + mailingListUpdateGroupsioServiceFlags = flag.NewFlagSet("update-groupsio-service", flag.ExitOnError) + mailingListUpdateGroupsioServiceBodyFlag = mailingListUpdateGroupsioServiceFlags.String("body", "REQUIRED", "") + mailingListUpdateGroupsioServiceServiceIDFlag = mailingListUpdateGroupsioServiceFlags.String("service-id", "REQUIRED", "Service ID") + mailingListUpdateGroupsioServiceBearerTokenFlag = mailingListUpdateGroupsioServiceFlags.String("bearer-token", "", "") + + mailingListDeleteGroupsioServiceFlags = flag.NewFlagSet("delete-groupsio-service", flag.ExitOnError) + mailingListDeleteGroupsioServiceServiceIDFlag = mailingListDeleteGroupsioServiceFlags.String("service-id", "REQUIRED", "Service ID") + mailingListDeleteGroupsioServiceBearerTokenFlag = mailingListDeleteGroupsioServiceFlags.String("bearer-token", "", "") + + mailingListGetGroupsioServiceProjectsFlags = flag.NewFlagSet("get-groupsio-service-projects", flag.ExitOnError) + mailingListGetGroupsioServiceProjectsBearerTokenFlag = mailingListGetGroupsioServiceProjectsFlags.String("bearer-token", "", "") + + mailingListFindParentGroupsioServiceFlags = flag.NewFlagSet("find-parent-groupsio-service", flag.ExitOnError) + mailingListFindParentGroupsioServiceProjectUIDFlag = mailingListFindParentGroupsioServiceFlags.String("project-uid", "REQUIRED", "") + mailingListFindParentGroupsioServiceBearerTokenFlag = mailingListFindParentGroupsioServiceFlags.String("bearer-token", "", "") + + mailingListListGroupsioSubgroupsFlags = flag.NewFlagSet("list-groupsio-subgroups", flag.ExitOnError) + mailingListListGroupsioSubgroupsProjectUIDFlag = mailingListListGroupsioSubgroupsFlags.String("project-uid", "", "") + mailingListListGroupsioSubgroupsCommitteeUIDFlag = mailingListListGroupsioSubgroupsFlags.String("committee-uid", "", "") + mailingListListGroupsioSubgroupsBearerTokenFlag = mailingListListGroupsioSubgroupsFlags.String("bearer-token", "", "") + + mailingListCreateGroupsioSubgroupFlags = flag.NewFlagSet("create-groupsio-subgroup", flag.ExitOnError) + mailingListCreateGroupsioSubgroupBodyFlag = mailingListCreateGroupsioSubgroupFlags.String("body", "REQUIRED", "") + mailingListCreateGroupsioSubgroupBearerTokenFlag = mailingListCreateGroupsioSubgroupFlags.String("bearer-token", "", "") + + mailingListGetGroupsioSubgroupFlags = flag.NewFlagSet("get-groupsio-subgroup", flag.ExitOnError) + mailingListGetGroupsioSubgroupSubgroupIDFlag = mailingListGetGroupsioSubgroupFlags.String("subgroup-id", "REQUIRED", "Subgroup ID") + mailingListGetGroupsioSubgroupBearerTokenFlag = mailingListGetGroupsioSubgroupFlags.String("bearer-token", "", "") + + mailingListUpdateGroupsioSubgroupFlags = flag.NewFlagSet("update-groupsio-subgroup", flag.ExitOnError) + mailingListUpdateGroupsioSubgroupBodyFlag = mailingListUpdateGroupsioSubgroupFlags.String("body", "REQUIRED", "") + mailingListUpdateGroupsioSubgroupSubgroupIDFlag = mailingListUpdateGroupsioSubgroupFlags.String("subgroup-id", "REQUIRED", "Subgroup ID") + mailingListUpdateGroupsioSubgroupBearerTokenFlag = mailingListUpdateGroupsioSubgroupFlags.String("bearer-token", "", "") + + mailingListDeleteGroupsioSubgroupFlags = flag.NewFlagSet("delete-groupsio-subgroup", flag.ExitOnError) + mailingListDeleteGroupsioSubgroupSubgroupIDFlag = mailingListDeleteGroupsioSubgroupFlags.String("subgroup-id", "REQUIRED", "Subgroup ID") + mailingListDeleteGroupsioSubgroupBearerTokenFlag = mailingListDeleteGroupsioSubgroupFlags.String("bearer-token", "", "") + + mailingListGetGroupsioSubgroupCountFlags = flag.NewFlagSet("get-groupsio-subgroup-count", flag.ExitOnError) + mailingListGetGroupsioSubgroupCountProjectUIDFlag = mailingListGetGroupsioSubgroupCountFlags.String("project-uid", "REQUIRED", "") + mailingListGetGroupsioSubgroupCountBearerTokenFlag = mailingListGetGroupsioSubgroupCountFlags.String("bearer-token", "", "") + + mailingListGetGroupsioSubgroupMemberCountFlags = flag.NewFlagSet("get-groupsio-subgroup-member-count", flag.ExitOnError) + mailingListGetGroupsioSubgroupMemberCountSubgroupIDFlag = mailingListGetGroupsioSubgroupMemberCountFlags.String("subgroup-id", "REQUIRED", "Subgroup ID") + mailingListGetGroupsioSubgroupMemberCountBearerTokenFlag = mailingListGetGroupsioSubgroupMemberCountFlags.String("bearer-token", "", "") + + mailingListListGroupsioMembersFlags = flag.NewFlagSet("list-groupsio-members", flag.ExitOnError) + mailingListListGroupsioMembersSubgroupIDFlag = mailingListListGroupsioMembersFlags.String("subgroup-id", "REQUIRED", "Subgroup ID") + mailingListListGroupsioMembersBearerTokenFlag = mailingListListGroupsioMembersFlags.String("bearer-token", "", "") + + mailingListAddGroupsioMemberFlags = flag.NewFlagSet("add-groupsio-member", flag.ExitOnError) + mailingListAddGroupsioMemberBodyFlag = mailingListAddGroupsioMemberFlags.String("body", "REQUIRED", "") + mailingListAddGroupsioMemberSubgroupIDFlag = mailingListAddGroupsioMemberFlags.String("subgroup-id", "REQUIRED", "Subgroup ID") + mailingListAddGroupsioMemberBearerTokenFlag = mailingListAddGroupsioMemberFlags.String("bearer-token", "", "") + + mailingListGetGroupsioMemberFlags = flag.NewFlagSet("get-groupsio-member", flag.ExitOnError) + mailingListGetGroupsioMemberSubgroupIDFlag = mailingListGetGroupsioMemberFlags.String("subgroup-id", "REQUIRED", "Subgroup ID") + mailingListGetGroupsioMemberMemberIDFlag = mailingListGetGroupsioMemberFlags.String("member-id", "REQUIRED", "Member ID") + mailingListGetGroupsioMemberBearerTokenFlag = mailingListGetGroupsioMemberFlags.String("bearer-token", "", "") + + mailingListUpdateGroupsioMemberFlags = flag.NewFlagSet("update-groupsio-member", flag.ExitOnError) + mailingListUpdateGroupsioMemberBodyFlag = mailingListUpdateGroupsioMemberFlags.String("body", "REQUIRED", "") + mailingListUpdateGroupsioMemberSubgroupIDFlag = mailingListUpdateGroupsioMemberFlags.String("subgroup-id", "REQUIRED", "Subgroup ID") + mailingListUpdateGroupsioMemberMemberIDFlag = mailingListUpdateGroupsioMemberFlags.String("member-id", "REQUIRED", "Member ID") + mailingListUpdateGroupsioMemberBearerTokenFlag = mailingListUpdateGroupsioMemberFlags.String("bearer-token", "", "") + + mailingListDeleteGroupsioMemberFlags = flag.NewFlagSet("delete-groupsio-member", flag.ExitOnError) + mailingListDeleteGroupsioMemberSubgroupIDFlag = mailingListDeleteGroupsioMemberFlags.String("subgroup-id", "REQUIRED", "Subgroup ID") + mailingListDeleteGroupsioMemberMemberIDFlag = mailingListDeleteGroupsioMemberFlags.String("member-id", "REQUIRED", "Member ID") + mailingListDeleteGroupsioMemberBearerTokenFlag = mailingListDeleteGroupsioMemberFlags.String("bearer-token", "", "") + + mailingListInviteGroupsioMembersFlags = flag.NewFlagSet("invite-groupsio-members", flag.ExitOnError) + mailingListInviteGroupsioMembersBodyFlag = mailingListInviteGroupsioMembersFlags.String("body", "REQUIRED", "") + mailingListInviteGroupsioMembersSubgroupIDFlag = mailingListInviteGroupsioMembersFlags.String("subgroup-id", "REQUIRED", "Subgroup ID") + mailingListInviteGroupsioMembersBearerTokenFlag = mailingListInviteGroupsioMembersFlags.String("bearer-token", "", "") + + mailingListCheckGroupsioSubscriberFlags = flag.NewFlagSet("check-groupsio-subscriber", flag.ExitOnError) + mailingListCheckGroupsioSubscriberBodyFlag = mailingListCheckGroupsioSubscriberFlags.String("body", "REQUIRED", "") + mailingListCheckGroupsioSubscriberBearerTokenFlag = mailingListCheckGroupsioSubscriberFlags.String("bearer-token", "", "") ) mailingListFlags.Usage = mailingListUsage mailingListLivezFlags.Usage = mailingListLivezUsage mailingListReadyzFlags.Usage = mailingListReadyzUsage - mailingListCreateGrpsioServiceFlags.Usage = mailingListCreateGrpsioServiceUsage - mailingListGetGrpsioServiceFlags.Usage = mailingListGetGrpsioServiceUsage - mailingListUpdateGrpsioServiceFlags.Usage = mailingListUpdateGrpsioServiceUsage - mailingListDeleteGrpsioServiceFlags.Usage = mailingListDeleteGrpsioServiceUsage - mailingListGetGrpsioServiceSettingsFlags.Usage = mailingListGetGrpsioServiceSettingsUsage - mailingListUpdateGrpsioServiceSettingsFlags.Usage = mailingListUpdateGrpsioServiceSettingsUsage - mailingListCreateGrpsioMailingListFlags.Usage = mailingListCreateGrpsioMailingListUsage - mailingListGetGrpsioMailingListFlags.Usage = mailingListGetGrpsioMailingListUsage - mailingListUpdateGrpsioMailingListFlags.Usage = mailingListUpdateGrpsioMailingListUsage - mailingListDeleteGrpsioMailingListFlags.Usage = mailingListDeleteGrpsioMailingListUsage - mailingListGetGrpsioMailingListSettingsFlags.Usage = mailingListGetGrpsioMailingListSettingsUsage - mailingListUpdateGrpsioMailingListSettingsFlags.Usage = mailingListUpdateGrpsioMailingListSettingsUsage - mailingListCreateGrpsioMailingListMemberFlags.Usage = mailingListCreateGrpsioMailingListMemberUsage - mailingListGetGrpsioMailingListMemberFlags.Usage = mailingListGetGrpsioMailingListMemberUsage - mailingListUpdateGrpsioMailingListMemberFlags.Usage = mailingListUpdateGrpsioMailingListMemberUsage - mailingListDeleteGrpsioMailingListMemberFlags.Usage = mailingListDeleteGrpsioMailingListMemberUsage - mailingListGroupsioWebhookFlags.Usage = mailingListGroupsioWebhookUsage + mailingListListGroupsioServicesFlags.Usage = mailingListListGroupsioServicesUsage + mailingListCreateGroupsioServiceFlags.Usage = mailingListCreateGroupsioServiceUsage + mailingListGetGroupsioServiceFlags.Usage = mailingListGetGroupsioServiceUsage + mailingListUpdateGroupsioServiceFlags.Usage = mailingListUpdateGroupsioServiceUsage + mailingListDeleteGroupsioServiceFlags.Usage = mailingListDeleteGroupsioServiceUsage + mailingListGetGroupsioServiceProjectsFlags.Usage = mailingListGetGroupsioServiceProjectsUsage + mailingListFindParentGroupsioServiceFlags.Usage = mailingListFindParentGroupsioServiceUsage + mailingListListGroupsioSubgroupsFlags.Usage = mailingListListGroupsioSubgroupsUsage + mailingListCreateGroupsioSubgroupFlags.Usage = mailingListCreateGroupsioSubgroupUsage + mailingListGetGroupsioSubgroupFlags.Usage = mailingListGetGroupsioSubgroupUsage + mailingListUpdateGroupsioSubgroupFlags.Usage = mailingListUpdateGroupsioSubgroupUsage + mailingListDeleteGroupsioSubgroupFlags.Usage = mailingListDeleteGroupsioSubgroupUsage + mailingListGetGroupsioSubgroupCountFlags.Usage = mailingListGetGroupsioSubgroupCountUsage + mailingListGetGroupsioSubgroupMemberCountFlags.Usage = mailingListGetGroupsioSubgroupMemberCountUsage + mailingListListGroupsioMembersFlags.Usage = mailingListListGroupsioMembersUsage + mailingListAddGroupsioMemberFlags.Usage = mailingListAddGroupsioMemberUsage + mailingListGetGroupsioMemberFlags.Usage = mailingListGetGroupsioMemberUsage + mailingListUpdateGroupsioMemberFlags.Usage = mailingListUpdateGroupsioMemberUsage + mailingListDeleteGroupsioMemberFlags.Usage = mailingListDeleteGroupsioMemberUsage + mailingListInviteGroupsioMembersFlags.Usage = mailingListInviteGroupsioMembersUsage + mailingListCheckGroupsioSubscriberFlags.Usage = mailingListCheckGroupsioSubscriberUsage if err := flag.CommandLine.Parse(os.Args[1:]); err != nil { return nil, nil, err @@ -211,56 +206,68 @@ func ParseEndpoint( case "readyz": epf = mailingListReadyzFlags - case "create-grpsio-service": - epf = mailingListCreateGrpsioServiceFlags + case "list-groupsio-services": + epf = mailingListListGroupsioServicesFlags - case "get-grpsio-service": - epf = mailingListGetGrpsioServiceFlags + case "create-groupsio-service": + epf = mailingListCreateGroupsioServiceFlags - case "update-grpsio-service": - epf = mailingListUpdateGrpsioServiceFlags + case "get-groupsio-service": + epf = mailingListGetGroupsioServiceFlags - case "delete-grpsio-service": - epf = mailingListDeleteGrpsioServiceFlags + case "update-groupsio-service": + epf = mailingListUpdateGroupsioServiceFlags - case "get-grpsio-service-settings": - epf = mailingListGetGrpsioServiceSettingsFlags + case "delete-groupsio-service": + epf = mailingListDeleteGroupsioServiceFlags - case "update-grpsio-service-settings": - epf = mailingListUpdateGrpsioServiceSettingsFlags + case "get-groupsio-service-projects": + epf = mailingListGetGroupsioServiceProjectsFlags - case "create-grpsio-mailing-list": - epf = mailingListCreateGrpsioMailingListFlags + case "find-parent-groupsio-service": + epf = mailingListFindParentGroupsioServiceFlags - case "get-grpsio-mailing-list": - epf = mailingListGetGrpsioMailingListFlags + case "list-groupsio-subgroups": + epf = mailingListListGroupsioSubgroupsFlags - case "update-grpsio-mailing-list": - epf = mailingListUpdateGrpsioMailingListFlags + case "create-groupsio-subgroup": + epf = mailingListCreateGroupsioSubgroupFlags - case "delete-grpsio-mailing-list": - epf = mailingListDeleteGrpsioMailingListFlags + case "get-groupsio-subgroup": + epf = mailingListGetGroupsioSubgroupFlags - case "get-grpsio-mailing-list-settings": - epf = mailingListGetGrpsioMailingListSettingsFlags + case "update-groupsio-subgroup": + epf = mailingListUpdateGroupsioSubgroupFlags - case "update-grpsio-mailing-list-settings": - epf = mailingListUpdateGrpsioMailingListSettingsFlags + case "delete-groupsio-subgroup": + epf = mailingListDeleteGroupsioSubgroupFlags - case "create-grpsio-mailing-list-member": - epf = mailingListCreateGrpsioMailingListMemberFlags + case "get-groupsio-subgroup-count": + epf = mailingListGetGroupsioSubgroupCountFlags - case "get-grpsio-mailing-list-member": - epf = mailingListGetGrpsioMailingListMemberFlags + case "get-groupsio-subgroup-member-count": + epf = mailingListGetGroupsioSubgroupMemberCountFlags - case "update-grpsio-mailing-list-member": - epf = mailingListUpdateGrpsioMailingListMemberFlags + case "list-groupsio-members": + epf = mailingListListGroupsioMembersFlags - case "delete-grpsio-mailing-list-member": - epf = mailingListDeleteGrpsioMailingListMemberFlags + case "add-groupsio-member": + epf = mailingListAddGroupsioMemberFlags - case "groupsio-webhook": - epf = mailingListGroupsioWebhookFlags + case "get-groupsio-member": + epf = mailingListGetGroupsioMemberFlags + + case "update-groupsio-member": + epf = mailingListUpdateGroupsioMemberFlags + + case "delete-groupsio-member": + epf = mailingListDeleteGroupsioMemberFlags + + case "invite-groupsio-members": + epf = mailingListInviteGroupsioMembersFlags + + case "check-groupsio-subscriber": + epf = mailingListCheckGroupsioSubscriberFlags } @@ -291,57 +298,69 @@ func ParseEndpoint( endpoint = c.Livez() case "readyz": endpoint = c.Readyz() - case "create-grpsio-service": - endpoint = c.CreateGrpsioService() - data, err = mailinglistc.BuildCreateGrpsioServicePayload(*mailingListCreateGrpsioServiceBodyFlag, *mailingListCreateGrpsioServiceVersionFlag, *mailingListCreateGrpsioServiceBearerTokenFlag) - case "get-grpsio-service": - endpoint = c.GetGrpsioService() - data, err = mailinglistc.BuildGetGrpsioServicePayload(*mailingListGetGrpsioServiceUIDFlag, *mailingListGetGrpsioServiceVersionFlag, *mailingListGetGrpsioServiceBearerTokenFlag) - case "update-grpsio-service": - endpoint = c.UpdateGrpsioService() - data, err = mailinglistc.BuildUpdateGrpsioServicePayload(*mailingListUpdateGrpsioServiceBodyFlag, *mailingListUpdateGrpsioServiceUIDFlag, *mailingListUpdateGrpsioServiceVersionFlag, *mailingListUpdateGrpsioServiceBearerTokenFlag, *mailingListUpdateGrpsioServiceIfMatchFlag) - case "delete-grpsio-service": - endpoint = c.DeleteGrpsioService() - data, err = mailinglistc.BuildDeleteGrpsioServicePayload(*mailingListDeleteGrpsioServiceUIDFlag, *mailingListDeleteGrpsioServiceVersionFlag, *mailingListDeleteGrpsioServiceBearerTokenFlag, *mailingListDeleteGrpsioServiceIfMatchFlag) - case "get-grpsio-service-settings": - endpoint = c.GetGrpsioServiceSettings() - data, err = mailinglistc.BuildGetGrpsioServiceSettingsPayload(*mailingListGetGrpsioServiceSettingsUIDFlag, *mailingListGetGrpsioServiceSettingsVersionFlag, *mailingListGetGrpsioServiceSettingsBearerTokenFlag) - case "update-grpsio-service-settings": - endpoint = c.UpdateGrpsioServiceSettings() - data, err = mailinglistc.BuildUpdateGrpsioServiceSettingsPayload(*mailingListUpdateGrpsioServiceSettingsBodyFlag, *mailingListUpdateGrpsioServiceSettingsUIDFlag, *mailingListUpdateGrpsioServiceSettingsVersionFlag, *mailingListUpdateGrpsioServiceSettingsBearerTokenFlag, *mailingListUpdateGrpsioServiceSettingsIfMatchFlag) - case "create-grpsio-mailing-list": - endpoint = c.CreateGrpsioMailingList() - data, err = mailinglistc.BuildCreateGrpsioMailingListPayload(*mailingListCreateGrpsioMailingListBodyFlag, *mailingListCreateGrpsioMailingListVersionFlag, *mailingListCreateGrpsioMailingListBearerTokenFlag) - case "get-grpsio-mailing-list": - endpoint = c.GetGrpsioMailingList() - data, err = mailinglistc.BuildGetGrpsioMailingListPayload(*mailingListGetGrpsioMailingListUIDFlag, *mailingListGetGrpsioMailingListVersionFlag, *mailingListGetGrpsioMailingListBearerTokenFlag) - case "update-grpsio-mailing-list": - endpoint = c.UpdateGrpsioMailingList() - data, err = mailinglistc.BuildUpdateGrpsioMailingListPayload(*mailingListUpdateGrpsioMailingListBodyFlag, *mailingListUpdateGrpsioMailingListUIDFlag, *mailingListUpdateGrpsioMailingListVersionFlag, *mailingListUpdateGrpsioMailingListBearerTokenFlag, *mailingListUpdateGrpsioMailingListIfMatchFlag) - case "delete-grpsio-mailing-list": - endpoint = c.DeleteGrpsioMailingList() - data, err = mailinglistc.BuildDeleteGrpsioMailingListPayload(*mailingListDeleteGrpsioMailingListUIDFlag, *mailingListDeleteGrpsioMailingListVersionFlag, *mailingListDeleteGrpsioMailingListBearerTokenFlag, *mailingListDeleteGrpsioMailingListIfMatchFlag) - case "get-grpsio-mailing-list-settings": - endpoint = c.GetGrpsioMailingListSettings() - data, err = mailinglistc.BuildGetGrpsioMailingListSettingsPayload(*mailingListGetGrpsioMailingListSettingsUIDFlag, *mailingListGetGrpsioMailingListSettingsVersionFlag, *mailingListGetGrpsioMailingListSettingsBearerTokenFlag) - case "update-grpsio-mailing-list-settings": - endpoint = c.UpdateGrpsioMailingListSettings() - data, err = mailinglistc.BuildUpdateGrpsioMailingListSettingsPayload(*mailingListUpdateGrpsioMailingListSettingsBodyFlag, *mailingListUpdateGrpsioMailingListSettingsUIDFlag, *mailingListUpdateGrpsioMailingListSettingsVersionFlag, *mailingListUpdateGrpsioMailingListSettingsBearerTokenFlag, *mailingListUpdateGrpsioMailingListSettingsIfMatchFlag) - case "create-grpsio-mailing-list-member": - endpoint = c.CreateGrpsioMailingListMember() - data, err = mailinglistc.BuildCreateGrpsioMailingListMemberPayload(*mailingListCreateGrpsioMailingListMemberBodyFlag, *mailingListCreateGrpsioMailingListMemberUIDFlag, *mailingListCreateGrpsioMailingListMemberVersionFlag, *mailingListCreateGrpsioMailingListMemberBearerTokenFlag) - case "get-grpsio-mailing-list-member": - endpoint = c.GetGrpsioMailingListMember() - data, err = mailinglistc.BuildGetGrpsioMailingListMemberPayload(*mailingListGetGrpsioMailingListMemberUIDFlag, *mailingListGetGrpsioMailingListMemberMemberUIDFlag, *mailingListGetGrpsioMailingListMemberVersionFlag, *mailingListGetGrpsioMailingListMemberBearerTokenFlag) - case "update-grpsio-mailing-list-member": - endpoint = c.UpdateGrpsioMailingListMember() - data, err = mailinglistc.BuildUpdateGrpsioMailingListMemberPayload(*mailingListUpdateGrpsioMailingListMemberBodyFlag, *mailingListUpdateGrpsioMailingListMemberUIDFlag, *mailingListUpdateGrpsioMailingListMemberMemberUIDFlag, *mailingListUpdateGrpsioMailingListMemberVersionFlag, *mailingListUpdateGrpsioMailingListMemberBearerTokenFlag, *mailingListUpdateGrpsioMailingListMemberIfMatchFlag) - case "delete-grpsio-mailing-list-member": - endpoint = c.DeleteGrpsioMailingListMember() - data, err = mailinglistc.BuildDeleteGrpsioMailingListMemberPayload(*mailingListDeleteGrpsioMailingListMemberUIDFlag, *mailingListDeleteGrpsioMailingListMemberMemberUIDFlag, *mailingListDeleteGrpsioMailingListMemberVersionFlag, *mailingListDeleteGrpsioMailingListMemberBearerTokenFlag, *mailingListDeleteGrpsioMailingListMemberIfMatchFlag) - case "groupsio-webhook": - endpoint = c.GroupsioWebhook() - data, err = mailinglistc.BuildGroupsioWebhookPayload(*mailingListGroupsioWebhookBodyFlag, *mailingListGroupsioWebhookSignatureFlag) + case "list-groupsio-services": + endpoint = c.ListGroupsioServices() + data, err = mailinglistc.BuildListGroupsioServicesPayload(*mailingListListGroupsioServicesProjectUIDFlag, *mailingListListGroupsioServicesBearerTokenFlag) + case "create-groupsio-service": + endpoint = c.CreateGroupsioService() + data, err = mailinglistc.BuildCreateGroupsioServicePayload(*mailingListCreateGroupsioServiceBodyFlag, *mailingListCreateGroupsioServiceBearerTokenFlag) + case "get-groupsio-service": + endpoint = c.GetGroupsioService() + data, err = mailinglistc.BuildGetGroupsioServicePayload(*mailingListGetGroupsioServiceServiceIDFlag, *mailingListGetGroupsioServiceBearerTokenFlag) + case "update-groupsio-service": + endpoint = c.UpdateGroupsioService() + data, err = mailinglistc.BuildUpdateGroupsioServicePayload(*mailingListUpdateGroupsioServiceBodyFlag, *mailingListUpdateGroupsioServiceServiceIDFlag, *mailingListUpdateGroupsioServiceBearerTokenFlag) + case "delete-groupsio-service": + endpoint = c.DeleteGroupsioService() + data, err = mailinglistc.BuildDeleteGroupsioServicePayload(*mailingListDeleteGroupsioServiceServiceIDFlag, *mailingListDeleteGroupsioServiceBearerTokenFlag) + case "get-groupsio-service-projects": + endpoint = c.GetGroupsioServiceProjects() + data, err = mailinglistc.BuildGetGroupsioServiceProjectsPayload(*mailingListGetGroupsioServiceProjectsBearerTokenFlag) + case "find-parent-groupsio-service": + endpoint = c.FindParentGroupsioService() + data, err = mailinglistc.BuildFindParentGroupsioServicePayload(*mailingListFindParentGroupsioServiceProjectUIDFlag, *mailingListFindParentGroupsioServiceBearerTokenFlag) + case "list-groupsio-subgroups": + endpoint = c.ListGroupsioSubgroups() + data, err = mailinglistc.BuildListGroupsioSubgroupsPayload(*mailingListListGroupsioSubgroupsProjectUIDFlag, *mailingListListGroupsioSubgroupsCommitteeUIDFlag, *mailingListListGroupsioSubgroupsBearerTokenFlag) + case "create-groupsio-subgroup": + endpoint = c.CreateGroupsioSubgroup() + data, err = mailinglistc.BuildCreateGroupsioSubgroupPayload(*mailingListCreateGroupsioSubgroupBodyFlag, *mailingListCreateGroupsioSubgroupBearerTokenFlag) + case "get-groupsio-subgroup": + endpoint = c.GetGroupsioSubgroup() + data, err = mailinglistc.BuildGetGroupsioSubgroupPayload(*mailingListGetGroupsioSubgroupSubgroupIDFlag, *mailingListGetGroupsioSubgroupBearerTokenFlag) + case "update-groupsio-subgroup": + endpoint = c.UpdateGroupsioSubgroup() + data, err = mailinglistc.BuildUpdateGroupsioSubgroupPayload(*mailingListUpdateGroupsioSubgroupBodyFlag, *mailingListUpdateGroupsioSubgroupSubgroupIDFlag, *mailingListUpdateGroupsioSubgroupBearerTokenFlag) + case "delete-groupsio-subgroup": + endpoint = c.DeleteGroupsioSubgroup() + data, err = mailinglistc.BuildDeleteGroupsioSubgroupPayload(*mailingListDeleteGroupsioSubgroupSubgroupIDFlag, *mailingListDeleteGroupsioSubgroupBearerTokenFlag) + case "get-groupsio-subgroup-count": + endpoint = c.GetGroupsioSubgroupCount() + data, err = mailinglistc.BuildGetGroupsioSubgroupCountPayload(*mailingListGetGroupsioSubgroupCountProjectUIDFlag, *mailingListGetGroupsioSubgroupCountBearerTokenFlag) + case "get-groupsio-subgroup-member-count": + endpoint = c.GetGroupsioSubgroupMemberCount() + data, err = mailinglistc.BuildGetGroupsioSubgroupMemberCountPayload(*mailingListGetGroupsioSubgroupMemberCountSubgroupIDFlag, *mailingListGetGroupsioSubgroupMemberCountBearerTokenFlag) + case "list-groupsio-members": + endpoint = c.ListGroupsioMembers() + data, err = mailinglistc.BuildListGroupsioMembersPayload(*mailingListListGroupsioMembersSubgroupIDFlag, *mailingListListGroupsioMembersBearerTokenFlag) + case "add-groupsio-member": + endpoint = c.AddGroupsioMember() + data, err = mailinglistc.BuildAddGroupsioMemberPayload(*mailingListAddGroupsioMemberBodyFlag, *mailingListAddGroupsioMemberSubgroupIDFlag, *mailingListAddGroupsioMemberBearerTokenFlag) + case "get-groupsio-member": + endpoint = c.GetGroupsioMember() + data, err = mailinglistc.BuildGetGroupsioMemberPayload(*mailingListGetGroupsioMemberSubgroupIDFlag, *mailingListGetGroupsioMemberMemberIDFlag, *mailingListGetGroupsioMemberBearerTokenFlag) + case "update-groupsio-member": + endpoint = c.UpdateGroupsioMember() + data, err = mailinglistc.BuildUpdateGroupsioMemberPayload(*mailingListUpdateGroupsioMemberBodyFlag, *mailingListUpdateGroupsioMemberSubgroupIDFlag, *mailingListUpdateGroupsioMemberMemberIDFlag, *mailingListUpdateGroupsioMemberBearerTokenFlag) + case "delete-groupsio-member": + endpoint = c.DeleteGroupsioMember() + data, err = mailinglistc.BuildDeleteGroupsioMemberPayload(*mailingListDeleteGroupsioMemberSubgroupIDFlag, *mailingListDeleteGroupsioMemberMemberIDFlag, *mailingListDeleteGroupsioMemberBearerTokenFlag) + case "invite-groupsio-members": + endpoint = c.InviteGroupsioMembers() + data, err = mailinglistc.BuildInviteGroupsioMembersPayload(*mailingListInviteGroupsioMembersBodyFlag, *mailingListInviteGroupsioMembersSubgroupIDFlag, *mailingListInviteGroupsioMembersBearerTokenFlag) + case "check-groupsio-subscriber": + endpoint = c.CheckGroupsioSubscriber() + data, err = mailinglistc.BuildCheckGroupsioSubscriberPayload(*mailingListCheckGroupsioSubscriberBodyFlag, *mailingListCheckGroupsioSubscriberBearerTokenFlag) } } } @@ -355,30 +374,34 @@ func ParseEndpoint( // mailingListUsage displays the usage of the mailing-list command and its // subcommands. func mailingListUsage() { - fmt.Fprintf(os.Stderr, `The mailing list service manages mailing lists and services + fmt.Fprintf(os.Stderr, `The mailing list service proxies GroupsIO operations to the ITX API Usage: %[1]s [globalflags] mailing-list COMMAND [flags] COMMAND: livez: Check if the service is alive. readyz: Check if the service is able to take inbound requests. - create-grpsio-service: Create GroupsIO service with type-specific validation rules - get-grpsio-service: Get groupsIO service details by ID - update-grpsio-service: Update GroupsIO service - delete-grpsio-service: Delete GroupsIO service - get-grpsio-service-settings: Get GroupsIO service settings (writers and auditors) - update-grpsio-service-settings: Update GroupsIO service settings (writers and auditors) - create-grpsio-mailing-list: Create GroupsIO mailing list/subgroup with comprehensive validation - get-grpsio-mailing-list: Get GroupsIO mailing list details by UID - update-grpsio-mailing-list: Update GroupsIO mailing list - delete-grpsio-mailing-list: Delete GroupsIO mailing list - get-grpsio-mailing-list-settings: Get GroupsIO mailing list settings (writers and auditors) - update-grpsio-mailing-list-settings: Update GroupsIO mailing list settings (writers and auditors) - create-grpsio-mailing-list-member: Create a new member for a GroupsIO mailing list - get-grpsio-mailing-list-member: Get a member of a GroupsIO mailing list by UID - update-grpsio-mailing-list-member: Update a member of a GroupsIO mailing list - delete-grpsio-mailing-list-member: Delete a member from a GroupsIO mailing list - groupsio-webhook: Handle GroupsIO webhook events for subgroup and member changes + list-groupsio-services: List GroupsIO services, optionally filtered by project UID + create-groupsio-service: Create a GroupsIO service + get-groupsio-service: Get a GroupsIO service by ID + update-groupsio-service: Update a GroupsIO service + delete-groupsio-service: Delete a GroupsIO service + get-groupsio-service-projects: Get projects that have GroupsIO services + find-parent-groupsio-service: Find the parent GroupsIO service for a project + list-groupsio-subgroups: List GroupsIO subgroups, optionally filtered by project UID and/or committee UID + create-groupsio-subgroup: Create a GroupsIO subgroup + get-groupsio-subgroup: Get a GroupsIO subgroup by ID + update-groupsio-subgroup: Update a GroupsIO subgroup + delete-groupsio-subgroup: Delete a GroupsIO subgroup + get-groupsio-subgroup-count: Get count of GroupsIO subgroups for a project + get-groupsio-subgroup-member-count: Get count of members in a GroupsIO subgroup + list-groupsio-members: List members of a GroupsIO subgroup + add-groupsio-member: Add a member to a GroupsIO subgroup + get-groupsio-member: Get a member of a GroupsIO subgroup by ID + update-groupsio-member: Update a member of a GroupsIO subgroup + delete-groupsio-member: Delete a member from a GroupsIO subgroup + invite-groupsio-members: Invite members to a GroupsIO subgroup by email + check-groupsio-subscriber: Check if an email address is subscribed to a GroupsIO subgroup Additional help: %[1]s mailing-list COMMAND --help @@ -404,547 +427,312 @@ Example: `, os.Args[0]) } -func mailingListCreateGrpsioServiceUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list create-grpsio-service -body JSON -version STRING -bearer-token STRING +func mailingListListGroupsioServicesUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list list-groupsio-services -project-uid STRING -bearer-token STRING + +List GroupsIO services, optionally filtered by project UID + -project-uid STRING: + -bearer-token STRING: + +Example: + %[1]s mailing-list list-groupsio-services --project-uid "37bc7bed-5a61-48a5-9e8f-35d27b68abd8" --bearer-token "eyJhbGci..." +`, os.Args[0]) +} + +func mailingListCreateGroupsioServiceUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list create-groupsio-service -body JSON -bearer-token STRING -Create GroupsIO service with type-specific validation rules +Create a GroupsIO service -body JSON: - -version STRING: -bearer-token STRING: Example: - %[1]s mailing-list create-grpsio-service --body '{ - "auditors": [ - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - } - ], - "domain": "lists.project.org", - "global_owners": [ - "admin@example.com" - ], - "group_id": 12345, - "group_name": "project-name", - "parent_service_uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee", - "prefix": "formation", - "project_slug": "cncf", + %[1]s mailing-list create-groupsio-service --body '{ + "domain": "Molestias amet aut molestiae sequi quisquam.", + "group_id": 7943249514373272995, + "prefix": "Non fuga a est et.", "project_uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee", - "public": true, - "status": "created", - "type": "primary", - "url": "https://lists.project.org", - "writers": [ - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - } - ] - }' --version "1" --bearer-token "eyJhbGci..." + "status": "Eum officiis voluptates.", + "type": "primary" + }' --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListGetGrpsioServiceUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list get-grpsio-service -uid STRING -version STRING -bearer-token STRING +func mailingListGetGroupsioServiceUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list get-groupsio-service -service-id STRING -bearer-token STRING -Get groupsIO service details by ID - -uid STRING: Service UID -- unique identifier for the service - -version STRING: +Get a GroupsIO service by ID + -service-id STRING: Service ID -bearer-token STRING: Example: - %[1]s mailing-list get-grpsio-service --uid "7cad5a8d-19d0-41a4-81a6-043453daf9ee" --version "1" --bearer-token "eyJhbGci..." + %[1]s mailing-list get-groupsio-service --service-id "Nihil et quaerat vero." --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListUpdateGrpsioServiceUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list update-grpsio-service -body JSON -uid STRING -version STRING -bearer-token STRING -if-match STRING +func mailingListUpdateGroupsioServiceUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list update-groupsio-service -body JSON -service-id STRING -bearer-token STRING -Update GroupsIO service +Update a GroupsIO service -body JSON: - -uid STRING: Service UID -- unique identifier for the service - -version STRING: + -service-id STRING: Service ID -bearer-token STRING: - -if-match STRING: Example: - %[1]s mailing-list update-grpsio-service --body '{ - "domain": "lists.project.org", - "global_owners": [ - "admin@example.com" - ], - "group_id": 12345, - "group_name": "project-name", - "parent_service_uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee", - "prefix": "formation", - "project_slug": "cncf", + %[1]s mailing-list update-groupsio-service --body '{ + "domain": "Velit molestias molestiae fuga.", + "group_id": 1590455940722915203, + "prefix": "Ea nisi sapiente minus qui aut.", "project_uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee", - "public": true, - "status": "created", - "type": "primary", - "url": "https://lists.project.org" - }' --uid "7cad5a8d-19d0-41a4-81a6-043453daf9ee" --version "1" --bearer-token "eyJhbGci..." --if-match "123" + "status": "Aliquid dicta non.", + "type": "primary" + }' --service-id "Atque omnis recusandae qui." --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListDeleteGrpsioServiceUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list delete-grpsio-service -uid STRING -version STRING -bearer-token STRING -if-match STRING +func mailingListDeleteGroupsioServiceUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list delete-groupsio-service -service-id STRING -bearer-token STRING -Delete GroupsIO service - -uid STRING: Service UID -- unique identifier for the service - -version STRING: +Delete a GroupsIO service + -service-id STRING: Service ID -bearer-token STRING: - -if-match STRING: Example: - %[1]s mailing-list delete-grpsio-service --uid "7cad5a8d-19d0-41a4-81a6-043453daf9ee" --version "1" --bearer-token "eyJhbGci..." --if-match "123" + %[1]s mailing-list delete-groupsio-service --service-id "Accusantium deleniti." --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListGetGrpsioServiceSettingsUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list get-grpsio-service-settings -uid STRING -version STRING -bearer-token STRING +func mailingListGetGroupsioServiceProjectsUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list get-groupsio-service-projects -bearer-token STRING -Get GroupsIO service settings (writers and auditors) - -uid STRING: Service UID -- unique identifier for the service - -version STRING: +Get projects that have GroupsIO services -bearer-token STRING: Example: - %[1]s mailing-list get-grpsio-service-settings --uid "7cad5a8d-19d0-41a4-81a6-043453daf9ee" --version "1" --bearer-token "eyJhbGci..." + %[1]s mailing-list get-groupsio-service-projects --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListUpdateGrpsioServiceSettingsUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list update-grpsio-service-settings -body JSON -uid STRING -version STRING -bearer-token STRING -if-match STRING +func mailingListFindParentGroupsioServiceUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list find-parent-groupsio-service -project-uid STRING -bearer-token STRING -Update GroupsIO service settings (writers and auditors) - -body JSON: - -uid STRING: Service UID -- unique identifier for the service - -version STRING: +Find the parent GroupsIO service for a project + -project-uid STRING: -bearer-token STRING: - -if-match STRING: Example: - %[1]s mailing-list update-grpsio-service-settings --body '{ - "auditors": [ - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - } - ], - "writers": [ - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - } - ] - }' --uid "7cad5a8d-19d0-41a4-81a6-043453daf9ee" --version "1" --bearer-token "eyJhbGci..." --if-match "123" + %[1]s mailing-list find-parent-groupsio-service --project-uid "40727538-026c-4e63-a1b4-ed76349a6c08" --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListCreateGrpsioMailingListUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list create-grpsio-mailing-list -body JSON -version STRING -bearer-token STRING +func mailingListListGroupsioSubgroupsUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list list-groupsio-subgroups -project-uid STRING -committee-uid STRING -bearer-token STRING -Create GroupsIO mailing list/subgroup with comprehensive validation +List GroupsIO subgroups, optionally filtered by project UID and/or committee UID + -project-uid STRING: + -committee-uid STRING: + -bearer-token STRING: + +Example: + %[1]s mailing-list list-groupsio-subgroups --project-uid "2f8f7a47-2ff4-4bd0-b04c-0125518cbee0" --committee-uid "8a7cad00-6dd2-457a-9007-0a665ad325fa" --bearer-token "eyJhbGci..." +`, os.Args[0]) +} + +func mailingListCreateGroupsioSubgroupUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list create-groupsio-subgroup -body JSON -bearer-token STRING + +Create a GroupsIO subgroup -body JSON: - -version STRING: -bearer-token STRING: Example: - %[1]s mailing-list create-grpsio-mailing-list --body '{ - "audience_access": "public", - "auditors": [ - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - } - ], - "committees": [ - { - "allowed_voting_statuses": [ - "Voting Rep", - "Alternate Voting Rep" - ], - "name": "Aliquid aliquid.", - "uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee" - }, - { - "allowed_voting_statuses": [ - "Voting Rep", - "Alternate Voting Rep" - ], - "name": "Aliquid aliquid.", - "uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee" - }, - { - "allowed_voting_statuses": [ - "Voting Rep", - "Alternate Voting Rep" - ], - "name": "Aliquid aliquid.", - "uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee" - }, - { - "allowed_voting_statuses": [ - "Voting Rep", - "Alternate Voting Rep" - ], - "name": "Aliquid aliquid.", - "uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee" - } - ], - "description": "Technical steering committee discussions", - "group_id": 12345, - "group_name": "technical-steering-committee", - "public": false, - "service_uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee", - "subject_tag": "[TSC]", - "subscriber_count": 42, - "title": "Technical Steering Committee", - "type": "discussion_moderated", - "writers": [ - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - } - ] - }' --version "1" --bearer-token "eyJhbGci..." + %[1]s mailing-list create-groupsio-subgroup --body '{ + "audience_access": "Aut iure.", + "committee_uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee", + "description": "Voluptates mollitia et pariatur modi.", + "group_id": 3213952105058865418, + "name": "Non quo debitis animi itaque.", + "project_uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee", + "type": "Atque vero asperiores iusto reiciendis sit asperiores." + }' --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListGetGrpsioMailingListUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list get-grpsio-mailing-list -uid STRING -version STRING -bearer-token STRING +func mailingListGetGroupsioSubgroupUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list get-groupsio-subgroup -subgroup-id STRING -bearer-token STRING -Get GroupsIO mailing list details by UID - -uid STRING: Mailing list UID -- unique identifier for the mailing list - -version STRING: +Get a GroupsIO subgroup by ID + -subgroup-id STRING: Subgroup ID -bearer-token STRING: Example: - %[1]s mailing-list get-grpsio-mailing-list --uid "7cad5a8d-19d0-41a4-81a6-043453daf9ee" --version "1" --bearer-token "eyJhbGci..." + %[1]s mailing-list get-groupsio-subgroup --subgroup-id "Ratione nihil magni aut accusantium aliquid enim." --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListUpdateGrpsioMailingListUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list update-grpsio-mailing-list -body JSON -uid STRING -version STRING -bearer-token STRING -if-match STRING +func mailingListUpdateGroupsioSubgroupUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list update-groupsio-subgroup -body JSON -subgroup-id STRING -bearer-token STRING -Update GroupsIO mailing list +Update a GroupsIO subgroup -body JSON: - -uid STRING: Mailing list UID -- unique identifier for the mailing list - -version STRING: + -subgroup-id STRING: Subgroup ID -bearer-token STRING: - -if-match STRING: Example: - %[1]s mailing-list update-grpsio-mailing-list --body '{ - "audience_access": "public", - "committees": [ - { - "allowed_voting_statuses": [ - "Voting Rep", - "Alternate Voting Rep" - ], - "name": "Aliquid aliquid.", - "uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee" - }, - { - "allowed_voting_statuses": [ - "Voting Rep", - "Alternate Voting Rep" - ], - "name": "Aliquid aliquid.", - "uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee" - }, - { - "allowed_voting_statuses": [ - "Voting Rep", - "Alternate Voting Rep" - ], - "name": "Aliquid aliquid.", - "uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee" - }, - { - "allowed_voting_statuses": [ - "Voting Rep", - "Alternate Voting Rep" - ], - "name": "Aliquid aliquid.", - "uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee" - } - ], - "description": "Technical steering committee discussions", - "group_id": 12345, - "group_name": "technical-steering-committee", - "public": false, - "service_uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee", - "subject_tag": "[TSC]", - "subscriber_count": 42, - "title": "Technical Steering Committee", - "type": "discussion_moderated" - }' --uid "7cad5a8d-19d0-41a4-81a6-043453daf9ee" --version "1" --bearer-token "eyJhbGci..." --if-match "123" + %[1]s mailing-list update-groupsio-subgroup --body '{ + "audience_access": "Quas tenetur eligendi facilis.", + "committee_uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee", + "description": "Qui inventore voluptatibus quas at suscipit.", + "group_id": 3362199226119147318, + "name": "Blanditiis consequatur molestiae odio quis enim et.", + "project_uid": "7cad5a8d-19d0-41a4-81a6-043453daf9ee", + "type": "Tenetur voluptatum sed optio incidunt." + }' --subgroup-id "Deserunt reiciendis facilis." --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListDeleteGrpsioMailingListUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list delete-grpsio-mailing-list -uid STRING -version STRING -bearer-token STRING -if-match STRING +func mailingListDeleteGroupsioSubgroupUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list delete-groupsio-subgroup -subgroup-id STRING -bearer-token STRING -Delete GroupsIO mailing list - -uid STRING: Mailing list UID -- unique identifier for the mailing list - -version STRING: +Delete a GroupsIO subgroup + -subgroup-id STRING: Subgroup ID -bearer-token STRING: - -if-match STRING: Example: - %[1]s mailing-list delete-grpsio-mailing-list --uid "7cad5a8d-19d0-41a4-81a6-043453daf9ee" --version "1" --bearer-token "eyJhbGci..." --if-match "123" + %[1]s mailing-list delete-groupsio-subgroup --subgroup-id "Ut labore." --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListGetGrpsioMailingListSettingsUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list get-grpsio-mailing-list-settings -uid STRING -version STRING -bearer-token STRING +func mailingListGetGroupsioSubgroupCountUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list get-groupsio-subgroup-count -project-uid STRING -bearer-token STRING -Get GroupsIO mailing list settings (writers and auditors) - -uid STRING: Mailing list UID -- unique identifier for the mailing list - -version STRING: +Get count of GroupsIO subgroups for a project + -project-uid STRING: -bearer-token STRING: Example: - %[1]s mailing-list get-grpsio-mailing-list-settings --uid "7cad5a8d-19d0-41a4-81a6-043453daf9ee" --version "1" --bearer-token "eyJhbGci..." + %[1]s mailing-list get-groupsio-subgroup-count --project-uid "429a3db9-1032-4809-ae6f-faf72bfc5dc4" --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListUpdateGrpsioMailingListSettingsUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list update-grpsio-mailing-list-settings -body JSON -uid STRING -version STRING -bearer-token STRING -if-match STRING +func mailingListGetGroupsioSubgroupMemberCountUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list get-groupsio-subgroup-member-count -subgroup-id STRING -bearer-token STRING -Update GroupsIO mailing list settings (writers and auditors) - -body JSON: - -uid STRING: Mailing list UID -- unique identifier for the mailing list - -version STRING: +Get count of members in a GroupsIO subgroup + -subgroup-id STRING: Subgroup ID -bearer-token STRING: - -if-match STRING: Example: - %[1]s mailing-list update-grpsio-mailing-list-settings --body '{ - "auditors": [ - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - } - ], - "writers": [ - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - }, - { - "avatar": "http://goldner.info/perry", - "email": "ernie.lueilwitz@altenwerth.info", - "name": "Eos non id at perspiciatis.", - "username": "Eaque quis possimus velit quasi quis occaecati." - } - ] - }' --uid "7cad5a8d-19d0-41a4-81a6-043453daf9ee" --version "1" --bearer-token "eyJhbGci..." --if-match "123" + %[1]s mailing-list get-groupsio-subgroup-member-count --subgroup-id "Praesentium velit non magni et totam tempora." --bearer-token "eyJhbGci..." +`, os.Args[0]) +} + +func mailingListListGroupsioMembersUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list list-groupsio-members -subgroup-id STRING -bearer-token STRING + +List members of a GroupsIO subgroup + -subgroup-id STRING: Subgroup ID + -bearer-token STRING: + +Example: + %[1]s mailing-list list-groupsio-members --subgroup-id "Qui in." --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListCreateGrpsioMailingListMemberUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list create-grpsio-mailing-list-member -body JSON -uid STRING -version STRING -bearer-token STRING +func mailingListAddGroupsioMemberUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list add-groupsio-member -body JSON -subgroup-id STRING -bearer-token STRING -Create a new member for a GroupsIO mailing list +Add a member to a GroupsIO subgroup -body JSON: - -uid STRING: Mailing list UID - -version STRING: + -subgroup-id STRING: Subgroup ID -bearer-token STRING: Example: - %[1]s mailing-list create-grpsio-mailing-list-member --body '{ - "delivery_mode": "none", - "email": "john.doe@example.com", - "first_name": "John", - "job_title": "Software Engineer", - "last_name": "Doe", - "last_reviewed_at": "2023-01-15T14:30:00Z", - "last_reviewed_by": "admin@example.com", - "member_type": "direct", + %[1]s mailing-list add-groupsio-member --body '{ + "delivery_mode": "digest", + "email": "talia@runolfsdottir.org", "mod_status": "moderator", - "organization": "Example Corp", - "username": "jdoe" - }' --uid "f47ac10b-58cc-4372-a567-0e02b2c3d479" --version "1" --bearer-token "eyJhbGci..." + "name": "Quidem illum aliquam ut." + }' --subgroup-id "Debitis quia suscipit odio necessitatibus." --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListGetGrpsioMailingListMemberUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list get-grpsio-mailing-list-member -uid STRING -member-uid STRING -version STRING -bearer-token STRING +func mailingListGetGroupsioMemberUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list get-groupsio-member -subgroup-id STRING -member-id STRING -bearer-token STRING -Get a member of a GroupsIO mailing list by UID - -uid STRING: Mailing list UID -- unique identifier for the mailing list - -member-uid STRING: Member UID -- unique identifier for the member - -version STRING: +Get a member of a GroupsIO subgroup by ID + -subgroup-id STRING: Subgroup ID + -member-id STRING: Member ID -bearer-token STRING: Example: - %[1]s mailing-list get-grpsio-mailing-list-member --uid "7cad5a8d-19d0-41a4-81a6-043453daf9ee" --member-uid "f47ac10b-58cc-4372-a567-0e02b2c3d479" --version "1" --bearer-token "eyJhbGci..." + %[1]s mailing-list get-groupsio-member --subgroup-id "Nam non aliquid molestias." --member-id "Molestiae deleniti asperiores et voluptatem id fuga." --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListUpdateGrpsioMailingListMemberUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list update-grpsio-mailing-list-member -body JSON -uid STRING -member-uid STRING -version STRING -bearer-token STRING -if-match STRING +func mailingListUpdateGroupsioMemberUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list update-groupsio-member -body JSON -subgroup-id STRING -member-id STRING -bearer-token STRING -Update a member of a GroupsIO mailing list +Update a member of a GroupsIO subgroup -body JSON: - -uid STRING: Mailing list UID -- unique identifier for the mailing list - -member-uid STRING: Member UID -- unique identifier for the member - -version STRING: + -subgroup-id STRING: Subgroup ID + -member-id STRING: Member ID -bearer-token STRING: - -if-match STRING: Example: - %[1]s mailing-list update-grpsio-mailing-list-member --body '{ - "delivery_mode": "none", - "first_name": "John", - "job_title": "Software Engineer", - "last_name": "Doe", + %[1]s mailing-list update-groupsio-member --body '{ + "delivery_mode": "normal", + "email": "katelynn_rempel@bruen.info", "mod_status": "none", - "organization": "Example Corp", - "username": "jdoe" - }' --uid "7cad5a8d-19d0-41a4-81a6-043453daf9ee" --member-uid "f47ac10b-58cc-4372-a567-0e02b2c3d479" --version "1" --bearer-token "eyJhbGci..." --if-match "123" + "name": "Inventore suscipit eveniet ipsum aut et." + }' --subgroup-id "Sit sed cupiditate qui nobis voluptas numquam." --member-id "Tempore autem illo et." --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListDeleteGrpsioMailingListMemberUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list delete-grpsio-mailing-list-member -uid STRING -member-uid STRING -version STRING -bearer-token STRING -if-match STRING +func mailingListDeleteGroupsioMemberUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list delete-groupsio-member -subgroup-id STRING -member-id STRING -bearer-token STRING -Delete a member from a GroupsIO mailing list - -uid STRING: Mailing list UID -- unique identifier for the mailing list - -member-uid STRING: Member UID -- unique identifier for the member - -version STRING: +Delete a member from a GroupsIO subgroup + -subgroup-id STRING: Subgroup ID + -member-id STRING: Member ID -bearer-token STRING: - -if-match STRING: Example: - %[1]s mailing-list delete-grpsio-mailing-list-member --uid "7cad5a8d-19d0-41a4-81a6-043453daf9ee" --member-uid "f47ac10b-58cc-4372-a567-0e02b2c3d479" --version "1" --bearer-token "eyJhbGci..." --if-match "123" + %[1]s mailing-list delete-groupsio-member --subgroup-id "Et voluptas id quas." --member-id "Ipsa sed quis dolor et et." --bearer-token "eyJhbGci..." `, os.Args[0]) } -func mailingListGroupsioWebhookUsage() { - fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list groupsio-webhook -body JSON -signature STRING +func mailingListInviteGroupsioMembersUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list invite-groupsio-members -body JSON -subgroup-id STRING -bearer-token STRING -Handle GroupsIO webhook events for subgroup and member changes +Invite members to a GroupsIO subgroup by email -body JSON: - -signature STRING: + -subgroup-id STRING: Subgroup ID + -bearer-token STRING: + +Example: + %[1]s mailing-list invite-groupsio-members --body '{ + "emails": [ + "Quo nihil quia blanditiis unde.", + "Qui commodi totam.", + "Voluptatem excepturi nam debitis quisquam voluptas velit.", + "Quibusdam voluptatum soluta sapiente error ut." + ] + }' --subgroup-id "Esse voluptas et iusto amet." --bearer-token "eyJhbGci..." +`, os.Args[0]) +} + +func mailingListCheckGroupsioSubscriberUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] mailing-list check-groupsio-subscriber -body JSON -bearer-token STRING + +Check if an email address is subscribed to a GroupsIO subgroup + -body JSON: + -bearer-token STRING: Example: - %[1]s mailing-list groupsio-webhook --body '{ - "action": "created_subgroup", - "extra": "Sint rerum laboriosam consequatur ut explicabo saepe.", - "extra_id": 500672409584855543, - "group": "Aut consequatur nihil repellat velit qui.", - "member_info": "Totam cumque totam alias." - }' --signature "Nostrum sed quo." + %[1]s mailing-list check-groupsio-subscriber --body '{ + "email": "murl.o\'keefe@gleason.biz", + "subgroup_id": "Voluptates animi totam." + }' --bearer-token "eyJhbGci..." `, os.Args[0]) } diff --git a/gen/http/mailing_list/client/cli.go b/gen/http/mailing_list/client/cli.go index 01fad7d..cb95441 100644 --- a/gen/http/mailing_list/client/cli.go +++ b/gen/http/mailing_list/client/cli.go @@ -11,1227 +11,634 @@ package client import ( "encoding/json" "fmt" - "unicode/utf8" mailinglist "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" goa "goa.design/goa/v3/pkg" ) -// BuildCreateGrpsioServicePayload builds the payload for the mailing-list -// create-grpsio-service endpoint from CLI flags. -func BuildCreateGrpsioServicePayload(mailingListCreateGrpsioServiceBody string, mailingListCreateGrpsioServiceVersion string, mailingListCreateGrpsioServiceBearerToken string) (*mailinglist.CreateGrpsioServicePayload, error) { +// BuildListGroupsioServicesPayload builds the payload for the mailing-list +// list-groupsio-services endpoint from CLI flags. +func BuildListGroupsioServicesPayload(mailingListListGroupsioServicesProjectUID string, mailingListListGroupsioServicesBearerToken string) (*mailinglist.ListGroupsioServicesPayload, error) { var err error - var body CreateGrpsioServiceRequestBody + var projectUID *string { - err = json.Unmarshal([]byte(mailingListCreateGrpsioServiceBody), &body) - if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"auditors\": [\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n }\n ],\n \"domain\": \"lists.project.org\",\n \"global_owners\": [\n \"admin@example.com\"\n ],\n \"group_id\": 12345,\n \"group_name\": \"project-name\",\n \"parent_service_uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\",\n \"prefix\": \"formation\",\n \"project_slug\": \"cncf\",\n \"project_uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\",\n \"public\": true,\n \"status\": \"created\",\n \"type\": \"primary\",\n \"url\": \"https://lists.project.org\",\n \"writers\": [\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n }\n ]\n }'") - } - if !(body.Type == "primary" || body.Type == "formation" || body.Type == "shared") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", body.Type, []any{"primary", "formation", "shared"})) - } - for _, e := range body.GlobalOwners { - err = goa.MergeErrors(err, goa.ValidateFormat("body.global_owners[*]", e, goa.FormatEmail)) - } - if body.ParentServiceUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.parent_service_uid", *body.ParentServiceUID, goa.FormatUUID)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_slug", *body.ProjectSlug, goa.FormatRegexp)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.project_slug", *body.ProjectSlug, "^[a-z][a-z0-9_\\-]*[a-z0-9]$")) - } - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", body.ProjectUID, goa.FormatUUID)) - if body.URL != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.url", *body.URL, goa.FormatURI)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } + if mailingListListGroupsioServicesProjectUID != "" { + projectUID = &mailingListListGroupsioServicesProjectUID + err = goa.MergeErrors(err, goa.ValidateFormat("project_uid", *projectUID, goa.FormatUUID)) + if err != nil { + return nil, err } } - if err != nil { - return nil, err - } - } - var version string - { - version = mailingListCreateGrpsioServiceVersion - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } - if err != nil { - return nil, err - } } var bearerToken *string { - if mailingListCreateGrpsioServiceBearerToken != "" { - bearerToken = &mailingListCreateGrpsioServiceBearerToken + if mailingListListGroupsioServicesBearerToken != "" { + bearerToken = &mailingListListGroupsioServicesBearerToken } } - v := &mailinglist.CreateGrpsioServicePayload{ - Type: body.Type, - Domain: body.Domain, - GroupID: body.GroupID, - Status: body.Status, - Prefix: body.Prefix, - ParentServiceUID: body.ParentServiceUID, - ProjectSlug: body.ProjectSlug, - ProjectUID: body.ProjectUID, - URL: body.URL, - GroupName: body.GroupName, - Public: body.Public, - } - if body.GlobalOwners != nil { - v.GlobalOwners = make([]string, len(body.GlobalOwners)) - for i, val := range body.GlobalOwners { - v.GlobalOwners[i] = val - } - } - { - var zero bool - if v.Public == zero { - v.Public = false - } - } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = marshalUserInfoRequestBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = marshalUserInfoRequestBodyToMailinglistUserInfo(val) - } - } - v.Version = version + v := &mailinglist.ListGroupsioServicesPayload{} + v.ProjectUID = projectUID v.BearerToken = bearerToken return v, nil } -// BuildGetGrpsioServicePayload builds the payload for the mailing-list -// get-grpsio-service endpoint from CLI flags. -func BuildGetGrpsioServicePayload(mailingListGetGrpsioServiceUID string, mailingListGetGrpsioServiceVersion string, mailingListGetGrpsioServiceBearerToken string) (*mailinglist.GetGrpsioServicePayload, error) { +// BuildCreateGroupsioServicePayload builds the payload for the mailing-list +// create-groupsio-service endpoint from CLI flags. +func BuildCreateGroupsioServicePayload(mailingListCreateGroupsioServiceBody string, mailingListCreateGroupsioServiceBearerToken string) (*mailinglist.CreateGroupsioServicePayload, error) { var err error - var uid string + var body CreateGroupsioServiceRequestBody { - uid = mailingListGetGrpsioServiceUID - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) + err = json.Unmarshal([]byte(mailingListCreateGroupsioServiceBody), &body) if err != nil { - return nil, err + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"domain\": \"Molestias amet aut molestiae sequi quisquam.\",\n \"group_id\": 7943249514373272995,\n \"prefix\": \"Non fuga a est et.\",\n \"project_uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\",\n \"status\": \"Eum officiis voluptates.\",\n \"type\": \"primary\"\n }'") } } - var version *string + var bearerToken *string { - if mailingListGetGrpsioServiceVersion != "" { - version = &mailingListGetGrpsioServiceVersion - if !(*version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", *version, []any{"1"})) - } - if err != nil { - return nil, err - } + if mailingListCreateGroupsioServiceBearerToken != "" { + bearerToken = &mailingListCreateGroupsioServiceBearerToken } } + v := &mailinglist.CreateGroupsioServicePayload{ + ProjectUID: body.ProjectUID, + Type: body.Type, + GroupID: body.GroupID, + Domain: body.Domain, + Prefix: body.Prefix, + Status: body.Status, + } + v.BearerToken = bearerToken + + return v, nil +} + +// BuildGetGroupsioServicePayload builds the payload for the mailing-list +// get-groupsio-service endpoint from CLI flags. +func BuildGetGroupsioServicePayload(mailingListGetGroupsioServiceServiceID string, mailingListGetGroupsioServiceBearerToken string) (*mailinglist.GetGroupsioServicePayload, error) { + var serviceID string + { + serviceID = mailingListGetGroupsioServiceServiceID + } var bearerToken *string { - if mailingListGetGrpsioServiceBearerToken != "" { - bearerToken = &mailingListGetGrpsioServiceBearerToken + if mailingListGetGroupsioServiceBearerToken != "" { + bearerToken = &mailingListGetGroupsioServiceBearerToken } } - v := &mailinglist.GetGrpsioServicePayload{} - v.UID = &uid - v.Version = version + v := &mailinglist.GetGroupsioServicePayload{} + v.ServiceID = serviceID v.BearerToken = bearerToken return v, nil } -// BuildUpdateGrpsioServicePayload builds the payload for the mailing-list -// update-grpsio-service endpoint from CLI flags. -func BuildUpdateGrpsioServicePayload(mailingListUpdateGrpsioServiceBody string, mailingListUpdateGrpsioServiceUID string, mailingListUpdateGrpsioServiceVersion string, mailingListUpdateGrpsioServiceBearerToken string, mailingListUpdateGrpsioServiceIfMatch string) (*mailinglist.UpdateGrpsioServicePayload, error) { +// BuildUpdateGroupsioServicePayload builds the payload for the mailing-list +// update-groupsio-service endpoint from CLI flags. +func BuildUpdateGroupsioServicePayload(mailingListUpdateGroupsioServiceBody string, mailingListUpdateGroupsioServiceServiceID string, mailingListUpdateGroupsioServiceBearerToken string) (*mailinglist.UpdateGroupsioServicePayload, error) { var err error - var body UpdateGrpsioServiceRequestBody + var body UpdateGroupsioServiceRequestBody { - err = json.Unmarshal([]byte(mailingListUpdateGrpsioServiceBody), &body) + err = json.Unmarshal([]byte(mailingListUpdateGroupsioServiceBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"domain\": \"lists.project.org\",\n \"global_owners\": [\n \"admin@example.com\"\n ],\n \"group_id\": 12345,\n \"group_name\": \"project-name\",\n \"parent_service_uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\",\n \"prefix\": \"formation\",\n \"project_slug\": \"cncf\",\n \"project_uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\",\n \"public\": true,\n \"status\": \"created\",\n \"type\": \"primary\",\n \"url\": \"https://lists.project.org\"\n }'") - } - if !(body.Type == "primary" || body.Type == "formation" || body.Type == "shared") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", body.Type, []any{"primary", "formation", "shared"})) - } - for _, e := range body.GlobalOwners { - err = goa.MergeErrors(err, goa.ValidateFormat("body.global_owners[*]", e, goa.FormatEmail)) - } - if body.ParentServiceUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.parent_service_uid", *body.ParentServiceUID, goa.FormatUUID)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_slug", *body.ProjectSlug, goa.FormatRegexp)) + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"domain\": \"Velit molestias molestiae fuga.\",\n \"group_id\": 1590455940722915203,\n \"prefix\": \"Ea nisi sapiente minus qui aut.\",\n \"project_uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\",\n \"status\": \"Aliquid dicta non.\",\n \"type\": \"primary\"\n }'") } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.project_slug", *body.ProjectSlug, "^[a-z][a-z0-9_\\-]*[a-z0-9]$")) + if body.ProjectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) } - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", body.ProjectUID, goa.FormatUUID)) - if body.URL != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.url", *body.URL, goa.FormatURI)) - } - if err != nil { - return nil, err + if body.Type != nil { + if !(*body.Type == "primary" || *body.Type == "formation" || *body.Type == "shared") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", *body.Type, []any{"primary", "formation", "shared"})) + } } - } - var uid string - { - uid = mailingListUpdateGrpsioServiceUID - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) if err != nil { return nil, err } } - var version string + var serviceID string { - version = mailingListUpdateGrpsioServiceVersion - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } - if err != nil { - return nil, err - } + serviceID = mailingListUpdateGroupsioServiceServiceID } var bearerToken *string { - if mailingListUpdateGrpsioServiceBearerToken != "" { - bearerToken = &mailingListUpdateGrpsioServiceBearerToken + if mailingListUpdateGroupsioServiceBearerToken != "" { + bearerToken = &mailingListUpdateGroupsioServiceBearerToken } } - var ifMatch *string - { - if mailingListUpdateGrpsioServiceIfMatch != "" { - ifMatch = &mailingListUpdateGrpsioServiceIfMatch - } - } - v := &mailinglist.UpdateGrpsioServicePayload{ - Type: body.Type, - Domain: body.Domain, - GroupID: body.GroupID, - Status: body.Status, - Prefix: body.Prefix, - ParentServiceUID: body.ParentServiceUID, - ProjectSlug: body.ProjectSlug, - ProjectUID: body.ProjectUID, - URL: body.URL, - GroupName: body.GroupName, - Public: body.Public, + v := &mailinglist.UpdateGroupsioServicePayload{ + ProjectUID: body.ProjectUID, + Type: body.Type, + GroupID: body.GroupID, + Domain: body.Domain, + Prefix: body.Prefix, + Status: body.Status, } - if body.GlobalOwners != nil { - v.GlobalOwners = make([]string, len(body.GlobalOwners)) - for i, val := range body.GlobalOwners { - v.GlobalOwners[i] = val - } - } - { - var zero bool - if v.Public == zero { - v.Public = false - } - } - v.UID = &uid - v.Version = version + v.ServiceID = serviceID v.BearerToken = bearerToken - v.IfMatch = ifMatch return v, nil } -// BuildDeleteGrpsioServicePayload builds the payload for the mailing-list -// delete-grpsio-service endpoint from CLI flags. -func BuildDeleteGrpsioServicePayload(mailingListDeleteGrpsioServiceUID string, mailingListDeleteGrpsioServiceVersion string, mailingListDeleteGrpsioServiceBearerToken string, mailingListDeleteGrpsioServiceIfMatch string) (*mailinglist.DeleteGrpsioServicePayload, error) { - var err error - var uid string +// BuildDeleteGroupsioServicePayload builds the payload for the mailing-list +// delete-groupsio-service endpoint from CLI flags. +func BuildDeleteGroupsioServicePayload(mailingListDeleteGroupsioServiceServiceID string, mailingListDeleteGroupsioServiceBearerToken string) (*mailinglist.DeleteGroupsioServicePayload, error) { + var serviceID string { - uid = mailingListDeleteGrpsioServiceUID - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - if err != nil { - return nil, err - } - } - var version *string - { - if mailingListDeleteGrpsioServiceVersion != "" { - version = &mailingListDeleteGrpsioServiceVersion - if !(*version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", *version, []any{"1"})) - } - if err != nil { - return nil, err - } - } + serviceID = mailingListDeleteGroupsioServiceServiceID } var bearerToken *string { - if mailingListDeleteGrpsioServiceBearerToken != "" { - bearerToken = &mailingListDeleteGrpsioServiceBearerToken + if mailingListDeleteGroupsioServiceBearerToken != "" { + bearerToken = &mailingListDeleteGroupsioServiceBearerToken } } - var ifMatch *string + v := &mailinglist.DeleteGroupsioServicePayload{} + v.ServiceID = serviceID + v.BearerToken = bearerToken + + return v, nil +} + +// BuildGetGroupsioServiceProjectsPayload builds the payload for the +// mailing-list get-groupsio-service-projects endpoint from CLI flags. +func BuildGetGroupsioServiceProjectsPayload(mailingListGetGroupsioServiceProjectsBearerToken string) (*mailinglist.GetGroupsioServiceProjectsPayload, error) { + var bearerToken *string { - if mailingListDeleteGrpsioServiceIfMatch != "" { - ifMatch = &mailingListDeleteGrpsioServiceIfMatch + if mailingListGetGroupsioServiceProjectsBearerToken != "" { + bearerToken = &mailingListGetGroupsioServiceProjectsBearerToken } } - v := &mailinglist.DeleteGrpsioServicePayload{} - v.UID = &uid - v.Version = version + v := &mailinglist.GetGroupsioServiceProjectsPayload{} v.BearerToken = bearerToken - v.IfMatch = ifMatch return v, nil } -// BuildGetGrpsioServiceSettingsPayload builds the payload for the mailing-list -// get-grpsio-service-settings endpoint from CLI flags. -func BuildGetGrpsioServiceSettingsPayload(mailingListGetGrpsioServiceSettingsUID string, mailingListGetGrpsioServiceSettingsVersion string, mailingListGetGrpsioServiceSettingsBearerToken string) (*mailinglist.GetGrpsioServiceSettingsPayload, error) { +// BuildFindParentGroupsioServicePayload builds the payload for the +// mailing-list find-parent-groupsio-service endpoint from CLI flags. +func BuildFindParentGroupsioServicePayload(mailingListFindParentGroupsioServiceProjectUID string, mailingListFindParentGroupsioServiceBearerToken string) (*mailinglist.FindParentGroupsioServicePayload, error) { var err error - var uid string + var projectUID string { - uid = mailingListGetGrpsioServiceSettingsUID - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) + projectUID = mailingListFindParentGroupsioServiceProjectUID + err = goa.MergeErrors(err, goa.ValidateFormat("project_uid", projectUID, goa.FormatUUID)) if err != nil { return nil, err } } - var version *string - { - if mailingListGetGrpsioServiceSettingsVersion != "" { - version = &mailingListGetGrpsioServiceSettingsVersion - if !(*version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", *version, []any{"1"})) - } - if err != nil { - return nil, err - } - } - } var bearerToken *string { - if mailingListGetGrpsioServiceSettingsBearerToken != "" { - bearerToken = &mailingListGetGrpsioServiceSettingsBearerToken + if mailingListFindParentGroupsioServiceBearerToken != "" { + bearerToken = &mailingListFindParentGroupsioServiceBearerToken } } - v := &mailinglist.GetGrpsioServiceSettingsPayload{} - v.UID = &uid - v.Version = version + v := &mailinglist.FindParentGroupsioServicePayload{} + v.ProjectUID = projectUID v.BearerToken = bearerToken return v, nil } -// BuildUpdateGrpsioServiceSettingsPayload builds the payload for the -// mailing-list update-grpsio-service-settings endpoint from CLI flags. -func BuildUpdateGrpsioServiceSettingsPayload(mailingListUpdateGrpsioServiceSettingsBody string, mailingListUpdateGrpsioServiceSettingsUID string, mailingListUpdateGrpsioServiceSettingsVersion string, mailingListUpdateGrpsioServiceSettingsBearerToken string, mailingListUpdateGrpsioServiceSettingsIfMatch string) (*mailinglist.UpdateGrpsioServiceSettingsPayload, error) { +// BuildListGroupsioSubgroupsPayload builds the payload for the mailing-list +// list-groupsio-subgroups endpoint from CLI flags. +func BuildListGroupsioSubgroupsPayload(mailingListListGroupsioSubgroupsProjectUID string, mailingListListGroupsioSubgroupsCommitteeUID string, mailingListListGroupsioSubgroupsBearerToken string) (*mailinglist.ListGroupsioSubgroupsPayload, error) { var err error - var body UpdateGrpsioServiceSettingsRequestBody + var projectUID *string { - err = json.Unmarshal([]byte(mailingListUpdateGrpsioServiceSettingsBody), &body) - if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"auditors\": [\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n }\n ],\n \"writers\": [\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n }\n ]\n }'") - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } + if mailingListListGroupsioSubgroupsProjectUID != "" { + projectUID = &mailingListListGroupsioSubgroupsProjectUID + err = goa.MergeErrors(err, goa.ValidateFormat("project_uid", *projectUID, goa.FormatUUID)) + if err != nil { + return nil, err } } - if err != nil { - return nil, err - } } - var uid string + var committeeUID *string { - uid = mailingListUpdateGrpsioServiceSettingsUID - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - if err != nil { - return nil, err - } - } - var version string - { - version = mailingListUpdateGrpsioServiceSettingsVersion - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } - if err != nil { - return nil, err + if mailingListListGroupsioSubgroupsCommitteeUID != "" { + committeeUID = &mailingListListGroupsioSubgroupsCommitteeUID + err = goa.MergeErrors(err, goa.ValidateFormat("committee_uid", *committeeUID, goa.FormatUUID)) + if err != nil { + return nil, err + } } } var bearerToken *string { - if mailingListUpdateGrpsioServiceSettingsBearerToken != "" { - bearerToken = &mailingListUpdateGrpsioServiceSettingsBearerToken - } - } - var ifMatch *string - { - if mailingListUpdateGrpsioServiceSettingsIfMatch != "" { - ifMatch = &mailingListUpdateGrpsioServiceSettingsIfMatch + if mailingListListGroupsioSubgroupsBearerToken != "" { + bearerToken = &mailingListListGroupsioSubgroupsBearerToken } } - v := &mailinglist.UpdateGrpsioServiceSettingsPayload{} - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = marshalUserInfoRequestBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = marshalUserInfoRequestBodyToMailinglistUserInfo(val) - } - } - v.UID = uid - v.Version = version + v := &mailinglist.ListGroupsioSubgroupsPayload{} + v.ProjectUID = projectUID + v.CommitteeUID = committeeUID v.BearerToken = bearerToken - v.IfMatch = ifMatch return v, nil } -// BuildCreateGrpsioMailingListPayload builds the payload for the mailing-list -// create-grpsio-mailing-list endpoint from CLI flags. -func BuildCreateGrpsioMailingListPayload(mailingListCreateGrpsioMailingListBody string, mailingListCreateGrpsioMailingListVersion string, mailingListCreateGrpsioMailingListBearerToken string) (*mailinglist.CreateGrpsioMailingListPayload, error) { +// BuildCreateGroupsioSubgroupPayload builds the payload for the mailing-list +// create-groupsio-subgroup endpoint from CLI flags. +func BuildCreateGroupsioSubgroupPayload(mailingListCreateGroupsioSubgroupBody string, mailingListCreateGroupsioSubgroupBearerToken string) (*mailinglist.CreateGroupsioSubgroupPayload, error) { var err error - var body CreateGrpsioMailingListRequestBody + var body CreateGroupsioSubgroupRequestBody { - err = json.Unmarshal([]byte(mailingListCreateGrpsioMailingListBody), &body) + err = json.Unmarshal([]byte(mailingListCreateGroupsioSubgroupBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"audience_access\": \"public\",\n \"auditors\": [\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n }\n ],\n \"committees\": [\n {\n \"allowed_voting_statuses\": [\n \"Voting Rep\",\n \"Alternate Voting Rep\"\n ],\n \"name\": \"Aliquid aliquid.\",\n \"uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\"\n },\n {\n \"allowed_voting_statuses\": [\n \"Voting Rep\",\n \"Alternate Voting Rep\"\n ],\n \"name\": \"Aliquid aliquid.\",\n \"uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\"\n },\n {\n \"allowed_voting_statuses\": [\n \"Voting Rep\",\n \"Alternate Voting Rep\"\n ],\n \"name\": \"Aliquid aliquid.\",\n \"uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\"\n },\n {\n \"allowed_voting_statuses\": [\n \"Voting Rep\",\n \"Alternate Voting Rep\"\n ],\n \"name\": \"Aliquid aliquid.\",\n \"uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\"\n }\n ],\n \"description\": \"Technical steering committee discussions\",\n \"group_id\": 12345,\n \"group_name\": \"technical-steering-committee\",\n \"public\": false,\n \"service_uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\",\n \"subject_tag\": \"[TSC]\",\n \"subscriber_count\": 42,\n \"title\": \"Technical Steering Committee\",\n \"type\": \"discussion_moderated\",\n \"writers\": [\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n }\n ]\n }'") - } - err = goa.MergeErrors(err, goa.ValidatePattern("body.group_name", body.GroupName, "^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$")) - if utf8.RuneCountInString(body.GroupName) < 3 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", body.GroupName, utf8.RuneCountInString(body.GroupName), 3, true)) - } - if utf8.RuneCountInString(body.GroupName) > 34 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", body.GroupName, utf8.RuneCountInString(body.GroupName), 34, false)) - } - if body.GroupID != nil { - if *body.GroupID < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.group_id", *body.GroupID, 0, true)) - } - } - if !(body.Type == "announcement" || body.Type == "discussion_moderated" || body.Type == "discussion_open") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", body.Type, []any{"announcement", "discussion_moderated", "discussion_open"})) - } - if !(body.AudienceAccess == "public" || body.AudienceAccess == "approval_required" || body.AudienceAccess == "invite_only") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.audience_access", body.AudienceAccess, []any{"public", "approval_required", "invite_only"})) - } - for _, e := range body.Committees { - if e != nil { - if err2 := ValidateCommitteeRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if utf8.RuneCountInString(body.Description) < 11 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", body.Description, utf8.RuneCountInString(body.Description), 11, true)) - } - if utf8.RuneCountInString(body.Description) > 500 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", body.Description, utf8.RuneCountInString(body.Description), 500, false)) - } - if utf8.RuneCountInString(body.Title) < 5 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", body.Title, utf8.RuneCountInString(body.Title), 5, true)) - } - if utf8.RuneCountInString(body.Title) > 100 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", body.Title, utf8.RuneCountInString(body.Title), 100, false)) - } - if body.SubjectTag != nil { - if utf8.RuneCountInString(*body.SubjectTag) > 50 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.subject_tag", *body.SubjectTag, utf8.RuneCountInString(*body.SubjectTag), 50, false)) - } - } - err = goa.MergeErrors(err, goa.ValidateFormat("body.service_uid", body.ServiceUID, goa.FormatUUID)) - if body.SubscriberCount != nil { - if *body.SubscriberCount < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.subscriber_count", *body.SubscriberCount, 0, true)) - } - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if err != nil { - return nil, err - } - } - var version string - { - version = mailingListCreateGrpsioMailingListVersion - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } - if err != nil { - return nil, err + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"audience_access\": \"Aut iure.\",\n \"committee_uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\",\n \"description\": \"Voluptates mollitia et pariatur modi.\",\n \"group_id\": 3213952105058865418,\n \"name\": \"Non quo debitis animi itaque.\",\n \"project_uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\",\n \"type\": \"Atque vero asperiores iusto reiciendis sit asperiores.\"\n }'") } } var bearerToken *string { - if mailingListCreateGrpsioMailingListBearerToken != "" { - bearerToken = &mailingListCreateGrpsioMailingListBearerToken - } - } - v := &mailinglist.CreateGrpsioMailingListPayload{ - GroupName: body.GroupName, - GroupID: body.GroupID, - Public: body.Public, - Type: body.Type, - AudienceAccess: body.AudienceAccess, - Description: body.Description, - Title: body.Title, - SubjectTag: body.SubjectTag, - ServiceUID: body.ServiceUID, - SubscriberCount: body.SubscriberCount, - } - { - var zero string - if v.AudienceAccess == zero { - v.AudienceAccess = "public" - } - } - if body.Committees != nil { - v.Committees = make([]*mailinglist.Committee, len(body.Committees)) - for i, val := range body.Committees { - v.Committees[i] = marshalCommitteeRequestBodyToMailinglistCommittee(val) + if mailingListCreateGroupsioSubgroupBearerToken != "" { + bearerToken = &mailingListCreateGroupsioSubgroupBearerToken } } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = marshalUserInfoRequestBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = marshalUserInfoRequestBodyToMailinglistUserInfo(val) - } + v := &mailinglist.CreateGroupsioSubgroupPayload{ + ProjectUID: body.ProjectUID, + CommitteeUID: body.CommitteeUID, + GroupID: body.GroupID, + Name: body.Name, + Description: body.Description, + Type: body.Type, + AudienceAccess: body.AudienceAccess, } - v.Version = version v.BearerToken = bearerToken return v, nil } -// BuildGetGrpsioMailingListPayload builds the payload for the mailing-list -// get-grpsio-mailing-list endpoint from CLI flags. -func BuildGetGrpsioMailingListPayload(mailingListGetGrpsioMailingListUID string, mailingListGetGrpsioMailingListVersion string, mailingListGetGrpsioMailingListBearerToken string) (*mailinglist.GetGrpsioMailingListPayload, error) { - var err error - var uid string +// BuildGetGroupsioSubgroupPayload builds the payload for the mailing-list +// get-groupsio-subgroup endpoint from CLI flags. +func BuildGetGroupsioSubgroupPayload(mailingListGetGroupsioSubgroupSubgroupID string, mailingListGetGroupsioSubgroupBearerToken string) (*mailinglist.GetGroupsioSubgroupPayload, error) { + var subgroupID string { - uid = mailingListGetGrpsioMailingListUID - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - if err != nil { - return nil, err - } + subgroupID = mailingListGetGroupsioSubgroupSubgroupID } - var version string + var bearerToken *string { - version = mailingListGetGrpsioMailingListVersion - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) + if mailingListGetGroupsioSubgroupBearerToken != "" { + bearerToken = &mailingListGetGroupsioSubgroupBearerToken } - if err != nil { - return nil, err - } - } - var bearerToken string - { - bearerToken = mailingListGetGrpsioMailingListBearerToken } - v := &mailinglist.GetGrpsioMailingListPayload{} - v.UID = &uid - v.Version = version + v := &mailinglist.GetGroupsioSubgroupPayload{} + v.SubgroupID = subgroupID v.BearerToken = bearerToken return v, nil } -// BuildUpdateGrpsioMailingListPayload builds the payload for the mailing-list -// update-grpsio-mailing-list endpoint from CLI flags. -func BuildUpdateGrpsioMailingListPayload(mailingListUpdateGrpsioMailingListBody string, mailingListUpdateGrpsioMailingListUID string, mailingListUpdateGrpsioMailingListVersion string, mailingListUpdateGrpsioMailingListBearerToken string, mailingListUpdateGrpsioMailingListIfMatch string) (*mailinglist.UpdateGrpsioMailingListPayload, error) { +// BuildUpdateGroupsioSubgroupPayload builds the payload for the mailing-list +// update-groupsio-subgroup endpoint from CLI flags. +func BuildUpdateGroupsioSubgroupPayload(mailingListUpdateGroupsioSubgroupBody string, mailingListUpdateGroupsioSubgroupSubgroupID string, mailingListUpdateGroupsioSubgroupBearerToken string) (*mailinglist.UpdateGroupsioSubgroupPayload, error) { var err error - var body UpdateGrpsioMailingListRequestBody + var body UpdateGroupsioSubgroupRequestBody { - err = json.Unmarshal([]byte(mailingListUpdateGrpsioMailingListBody), &body) + err = json.Unmarshal([]byte(mailingListUpdateGroupsioSubgroupBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"audience_access\": \"public\",\n \"committees\": [\n {\n \"allowed_voting_statuses\": [\n \"Voting Rep\",\n \"Alternate Voting Rep\"\n ],\n \"name\": \"Aliquid aliquid.\",\n \"uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\"\n },\n {\n \"allowed_voting_statuses\": [\n \"Voting Rep\",\n \"Alternate Voting Rep\"\n ],\n \"name\": \"Aliquid aliquid.\",\n \"uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\"\n },\n {\n \"allowed_voting_statuses\": [\n \"Voting Rep\",\n \"Alternate Voting Rep\"\n ],\n \"name\": \"Aliquid aliquid.\",\n \"uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\"\n },\n {\n \"allowed_voting_statuses\": [\n \"Voting Rep\",\n \"Alternate Voting Rep\"\n ],\n \"name\": \"Aliquid aliquid.\",\n \"uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\"\n }\n ],\n \"description\": \"Technical steering committee discussions\",\n \"group_id\": 12345,\n \"group_name\": \"technical-steering-committee\",\n \"public\": false,\n \"service_uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\",\n \"subject_tag\": \"[TSC]\",\n \"subscriber_count\": 42,\n \"title\": \"Technical Steering Committee\",\n \"type\": \"discussion_moderated\"\n }'") - } - err = goa.MergeErrors(err, goa.ValidatePattern("body.group_name", body.GroupName, "^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$")) - if utf8.RuneCountInString(body.GroupName) < 3 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", body.GroupName, utf8.RuneCountInString(body.GroupName), 3, true)) - } - if utf8.RuneCountInString(body.GroupName) > 34 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", body.GroupName, utf8.RuneCountInString(body.GroupName), 34, false)) - } - if body.GroupID != nil { - if *body.GroupID < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.group_id", *body.GroupID, 0, true)) - } - } - if !(body.Type == "announcement" || body.Type == "discussion_moderated" || body.Type == "discussion_open") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", body.Type, []any{"announcement", "discussion_moderated", "discussion_open"})) + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"audience_access\": \"Quas tenetur eligendi facilis.\",\n \"committee_uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\",\n \"description\": \"Qui inventore voluptatibus quas at suscipit.\",\n \"group_id\": 3362199226119147318,\n \"name\": \"Blanditiis consequatur molestiae odio quis enim et.\",\n \"project_uid\": \"7cad5a8d-19d0-41a4-81a6-043453daf9ee\",\n \"type\": \"Tenetur voluptatum sed optio incidunt.\"\n }'") } - if !(body.AudienceAccess == "public" || body.AudienceAccess == "approval_required" || body.AudienceAccess == "invite_only") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.audience_access", body.AudienceAccess, []any{"public", "approval_required", "invite_only"})) + if body.ProjectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) } - for _, e := range body.Committees { - if e != nil { - if err2 := ValidateCommitteeRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if utf8.RuneCountInString(body.Description) < 11 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", body.Description, utf8.RuneCountInString(body.Description), 11, true)) - } - if utf8.RuneCountInString(body.Description) > 500 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", body.Description, utf8.RuneCountInString(body.Description), 500, false)) - } - if utf8.RuneCountInString(body.Title) < 5 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", body.Title, utf8.RuneCountInString(body.Title), 5, true)) - } - if utf8.RuneCountInString(body.Title) > 100 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", body.Title, utf8.RuneCountInString(body.Title), 100, false)) - } - if body.SubjectTag != nil { - if utf8.RuneCountInString(*body.SubjectTag) > 50 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.subject_tag", *body.SubjectTag, utf8.RuneCountInString(*body.SubjectTag), 50, false)) - } - } - err = goa.MergeErrors(err, goa.ValidateFormat("body.service_uid", body.ServiceUID, goa.FormatUUID)) - if body.SubscriberCount != nil { - if *body.SubscriberCount < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.subscriber_count", *body.SubscriberCount, 0, true)) - } - } - if err != nil { - return nil, err + if body.CommitteeUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.committee_uid", *body.CommitteeUID, goa.FormatUUID)) } - } - var uid string - { - uid = mailingListUpdateGrpsioMailingListUID - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) if err != nil { return nil, err } } - var version string + var subgroupID string { - version = mailingListUpdateGrpsioMailingListVersion - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } - if err != nil { - return nil, err - } + subgroupID = mailingListUpdateGroupsioSubgroupSubgroupID } var bearerToken *string { - if mailingListUpdateGrpsioMailingListBearerToken != "" { - bearerToken = &mailingListUpdateGrpsioMailingListBearerToken + if mailingListUpdateGroupsioSubgroupBearerToken != "" { + bearerToken = &mailingListUpdateGroupsioSubgroupBearerToken } } - var ifMatch *string - { - if mailingListUpdateGrpsioMailingListIfMatch != "" { - ifMatch = &mailingListUpdateGrpsioMailingListIfMatch - } + v := &mailinglist.UpdateGroupsioSubgroupPayload{ + ProjectUID: body.ProjectUID, + CommitteeUID: body.CommitteeUID, + GroupID: body.GroupID, + Name: body.Name, + Description: body.Description, + Type: body.Type, + AudienceAccess: body.AudienceAccess, } - v := &mailinglist.UpdateGrpsioMailingListPayload{ - GroupName: body.GroupName, - GroupID: body.GroupID, - Public: body.Public, - Type: body.Type, - AudienceAccess: body.AudienceAccess, - Description: body.Description, - Title: body.Title, - SubjectTag: body.SubjectTag, - ServiceUID: body.ServiceUID, - SubscriberCount: body.SubscriberCount, - } - { - var zero string - if v.AudienceAccess == zero { - v.AudienceAccess = "public" - } - } - if body.Committees != nil { - v.Committees = make([]*mailinglist.Committee, len(body.Committees)) - for i, val := range body.Committees { - v.Committees[i] = marshalCommitteeRequestBodyToMailinglistCommittee(val) - } - } - v.UID = &uid - v.Version = version + v.SubgroupID = subgroupID v.BearerToken = bearerToken - v.IfMatch = ifMatch return v, nil } -// BuildDeleteGrpsioMailingListPayload builds the payload for the mailing-list -// delete-grpsio-mailing-list endpoint from CLI flags. -func BuildDeleteGrpsioMailingListPayload(mailingListDeleteGrpsioMailingListUID string, mailingListDeleteGrpsioMailingListVersion string, mailingListDeleteGrpsioMailingListBearerToken string, mailingListDeleteGrpsioMailingListIfMatch string) (*mailinglist.DeleteGrpsioMailingListPayload, error) { - var err error - var uid string +// BuildDeleteGroupsioSubgroupPayload builds the payload for the mailing-list +// delete-groupsio-subgroup endpoint from CLI flags. +func BuildDeleteGroupsioSubgroupPayload(mailingListDeleteGroupsioSubgroupSubgroupID string, mailingListDeleteGroupsioSubgroupBearerToken string) (*mailinglist.DeleteGroupsioSubgroupPayload, error) { + var subgroupID string { - uid = mailingListDeleteGrpsioMailingListUID - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - if err != nil { - return nil, err - } - } - var version *string - { - if mailingListDeleteGrpsioMailingListVersion != "" { - version = &mailingListDeleteGrpsioMailingListVersion - if !(*version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", *version, []any{"1"})) - } - if err != nil { - return nil, err - } - } + subgroupID = mailingListDeleteGroupsioSubgroupSubgroupID } var bearerToken *string { - if mailingListDeleteGrpsioMailingListBearerToken != "" { - bearerToken = &mailingListDeleteGrpsioMailingListBearerToken - } - } - var ifMatch *string - { - if mailingListDeleteGrpsioMailingListIfMatch != "" { - ifMatch = &mailingListDeleteGrpsioMailingListIfMatch + if mailingListDeleteGroupsioSubgroupBearerToken != "" { + bearerToken = &mailingListDeleteGroupsioSubgroupBearerToken } } - v := &mailinglist.DeleteGrpsioMailingListPayload{} - v.UID = &uid - v.Version = version + v := &mailinglist.DeleteGroupsioSubgroupPayload{} + v.SubgroupID = subgroupID v.BearerToken = bearerToken - v.IfMatch = ifMatch return v, nil } -// BuildGetGrpsioMailingListSettingsPayload builds the payload for the -// mailing-list get-grpsio-mailing-list-settings endpoint from CLI flags. -func BuildGetGrpsioMailingListSettingsPayload(mailingListGetGrpsioMailingListSettingsUID string, mailingListGetGrpsioMailingListSettingsVersion string, mailingListGetGrpsioMailingListSettingsBearerToken string) (*mailinglist.GetGrpsioMailingListSettingsPayload, error) { +// BuildGetGroupsioSubgroupCountPayload builds the payload for the mailing-list +// get-groupsio-subgroup-count endpoint from CLI flags. +func BuildGetGroupsioSubgroupCountPayload(mailingListGetGroupsioSubgroupCountProjectUID string, mailingListGetGroupsioSubgroupCountBearerToken string) (*mailinglist.GetGroupsioSubgroupCountPayload, error) { var err error - var uid string + var projectUID string { - uid = mailingListGetGrpsioMailingListSettingsUID - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) + projectUID = mailingListGetGroupsioSubgroupCountProjectUID + err = goa.MergeErrors(err, goa.ValidateFormat("project_uid", projectUID, goa.FormatUUID)) if err != nil { return nil, err } } - var version *string - { - if mailingListGetGrpsioMailingListSettingsVersion != "" { - version = &mailingListGetGrpsioMailingListSettingsVersion - if !(*version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", *version, []any{"1"})) - } - if err != nil { - return nil, err - } - } - } var bearerToken *string { - if mailingListGetGrpsioMailingListSettingsBearerToken != "" { - bearerToken = &mailingListGetGrpsioMailingListSettingsBearerToken + if mailingListGetGroupsioSubgroupCountBearerToken != "" { + bearerToken = &mailingListGetGroupsioSubgroupCountBearerToken } } - v := &mailinglist.GetGrpsioMailingListSettingsPayload{} - v.UID = &uid - v.Version = version + v := &mailinglist.GetGroupsioSubgroupCountPayload{} + v.ProjectUID = projectUID v.BearerToken = bearerToken return v, nil } -// BuildUpdateGrpsioMailingListSettingsPayload builds the payload for the -// mailing-list update-grpsio-mailing-list-settings endpoint from CLI flags. -func BuildUpdateGrpsioMailingListSettingsPayload(mailingListUpdateGrpsioMailingListSettingsBody string, mailingListUpdateGrpsioMailingListSettingsUID string, mailingListUpdateGrpsioMailingListSettingsVersion string, mailingListUpdateGrpsioMailingListSettingsBearerToken string, mailingListUpdateGrpsioMailingListSettingsIfMatch string) (*mailinglist.UpdateGrpsioMailingListSettingsPayload, error) { - var err error - var body UpdateGrpsioMailingListSettingsRequestBody +// BuildGetGroupsioSubgroupMemberCountPayload builds the payload for the +// mailing-list get-groupsio-subgroup-member-count endpoint from CLI flags. +func BuildGetGroupsioSubgroupMemberCountPayload(mailingListGetGroupsioSubgroupMemberCountSubgroupID string, mailingListGetGroupsioSubgroupMemberCountBearerToken string) (*mailinglist.GetGroupsioSubgroupMemberCountPayload, error) { + var subgroupID string { - err = json.Unmarshal([]byte(mailingListUpdateGrpsioMailingListSettingsBody), &body) - if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"auditors\": [\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n }\n ],\n \"writers\": [\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n },\n {\n \"avatar\": \"http://goldner.info/perry\",\n \"email\": \"ernie.lueilwitz@altenwerth.info\",\n \"name\": \"Eos non id at perspiciatis.\",\n \"username\": \"Eaque quis possimus velit quasi quis occaecati.\"\n }\n ]\n }'") - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if err != nil { - return nil, err - } + subgroupID = mailingListGetGroupsioSubgroupMemberCountSubgroupID } - var uid string + var bearerToken *string { - uid = mailingListUpdateGrpsioMailingListSettingsUID - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - if err != nil { - return nil, err + if mailingListGetGroupsioSubgroupMemberCountBearerToken != "" { + bearerToken = &mailingListGetGroupsioSubgroupMemberCountBearerToken } } - var version string + v := &mailinglist.GetGroupsioSubgroupMemberCountPayload{} + v.SubgroupID = subgroupID + v.BearerToken = bearerToken + + return v, nil +} + +// BuildListGroupsioMembersPayload builds the payload for the mailing-list +// list-groupsio-members endpoint from CLI flags. +func BuildListGroupsioMembersPayload(mailingListListGroupsioMembersSubgroupID string, mailingListListGroupsioMembersBearerToken string) (*mailinglist.ListGroupsioMembersPayload, error) { + var subgroupID string { - version = mailingListUpdateGrpsioMailingListSettingsVersion - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } - if err != nil { - return nil, err - } + subgroupID = mailingListListGroupsioMembersSubgroupID } var bearerToken *string { - if mailingListUpdateGrpsioMailingListSettingsBearerToken != "" { - bearerToken = &mailingListUpdateGrpsioMailingListSettingsBearerToken - } - } - var ifMatch *string - { - if mailingListUpdateGrpsioMailingListSettingsIfMatch != "" { - ifMatch = &mailingListUpdateGrpsioMailingListSettingsIfMatch - } - } - v := &mailinglist.UpdateGrpsioMailingListSettingsPayload{} - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = marshalUserInfoRequestBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = marshalUserInfoRequestBodyToMailinglistUserInfo(val) + if mailingListListGroupsioMembersBearerToken != "" { + bearerToken = &mailingListListGroupsioMembersBearerToken } } - v.UID = uid - v.Version = version + v := &mailinglist.ListGroupsioMembersPayload{} + v.SubgroupID = subgroupID v.BearerToken = bearerToken - v.IfMatch = ifMatch return v, nil } -// BuildCreateGrpsioMailingListMemberPayload builds the payload for the -// mailing-list create-grpsio-mailing-list-member endpoint from CLI flags. -func BuildCreateGrpsioMailingListMemberPayload(mailingListCreateGrpsioMailingListMemberBody string, mailingListCreateGrpsioMailingListMemberUID string, mailingListCreateGrpsioMailingListMemberVersion string, mailingListCreateGrpsioMailingListMemberBearerToken string) (*mailinglist.CreateGrpsioMailingListMemberPayload, error) { +// BuildAddGroupsioMemberPayload builds the payload for the mailing-list +// add-groupsio-member endpoint from CLI flags. +func BuildAddGroupsioMemberPayload(mailingListAddGroupsioMemberBody string, mailingListAddGroupsioMemberSubgroupID string, mailingListAddGroupsioMemberBearerToken string) (*mailinglist.AddGroupsioMemberPayload, error) { var err error - var body CreateGrpsioMailingListMemberRequestBody + var body AddGroupsioMemberRequestBody { - err = json.Unmarshal([]byte(mailingListCreateGrpsioMailingListMemberBody), &body) + err = json.Unmarshal([]byte(mailingListAddGroupsioMemberBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"delivery_mode\": \"none\",\n \"email\": \"john.doe@example.com\",\n \"first_name\": \"John\",\n \"job_title\": \"Software Engineer\",\n \"last_name\": \"Doe\",\n \"last_reviewed_at\": \"2023-01-15T14:30:00Z\",\n \"last_reviewed_by\": \"admin@example.com\",\n \"member_type\": \"direct\",\n \"mod_status\": \"moderator\",\n \"organization\": \"Example Corp\",\n \"username\": \"jdoe\"\n }'") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"delivery_mode\": \"digest\",\n \"email\": \"talia@runolfsdottir.org\",\n \"mod_status\": \"moderator\",\n \"name\": \"Quidem illum aliquam ut.\"\n }'") } - if body.Username != nil { - if utf8.RuneCountInString(*body.Username) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.username", *body.Username, utf8.RuneCountInString(*body.Username), 255, false)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 1, true)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 255, false)) - } + if body.Email != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 1, true)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 255, false)) - } - } - err = goa.MergeErrors(err, goa.ValidateFormat("body.email", body.Email, goa.FormatEmail)) - if body.Organization != nil { - if utf8.RuneCountInString(*body.Organization) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.organization", *body.Organization, utf8.RuneCountInString(*body.Organization), 255, false)) + if body.ModStatus != nil { + if !(*body.ModStatus == "none" || *body.ModStatus == "moderator" || *body.ModStatus == "owner") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.mod_status", *body.ModStatus, []any{"none", "moderator", "owner"})) } } - if body.JobTitle != nil { - if utf8.RuneCountInString(*body.JobTitle) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.job_title", *body.JobTitle, utf8.RuneCountInString(*body.JobTitle), 255, false)) + if body.DeliveryMode != nil { + if !(*body.DeliveryMode == "normal" || *body.DeliveryMode == "digest" || *body.DeliveryMode == "none") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.delivery_mode", *body.DeliveryMode, []any{"normal", "digest", "none"})) } } - if !(body.MemberType == "committee" || body.MemberType == "direct") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.member_type", body.MemberType, []any{"committee", "direct"})) - } - if !(body.DeliveryMode == "normal" || body.DeliveryMode == "digest" || body.DeliveryMode == "none") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.delivery_mode", body.DeliveryMode, []any{"normal", "digest", "none"})) - } - if !(body.ModStatus == "none" || body.ModStatus == "moderator" || body.ModStatus == "owner") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.mod_status", body.ModStatus, []any{"none", "moderator", "owner"})) - } - if body.LastReviewedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_reviewed_at", *body.LastReviewedAt, goa.FormatDateTime)) - } if err != nil { return nil, err } } - var uid string + var subgroupID string { - uid = mailingListCreateGrpsioMailingListMemberUID - } - var version string - { - version = mailingListCreateGrpsioMailingListMemberVersion - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } - if err != nil { - return nil, err - } + subgroupID = mailingListAddGroupsioMemberSubgroupID } var bearerToken *string { - if mailingListCreateGrpsioMailingListMemberBearerToken != "" { - bearerToken = &mailingListCreateGrpsioMailingListMemberBearerToken - } - } - v := &mailinglist.CreateGrpsioMailingListMemberPayload{ - Username: body.Username, - FirstName: body.FirstName, - LastName: body.LastName, - Email: body.Email, - Organization: body.Organization, - JobTitle: body.JobTitle, - MemberType: body.MemberType, - DeliveryMode: body.DeliveryMode, - ModStatus: body.ModStatus, - LastReviewedAt: body.LastReviewedAt, - LastReviewedBy: body.LastReviewedBy, - } - { - var zero string - if v.MemberType == zero { - v.MemberType = "direct" - } - } - { - var zero string - if v.DeliveryMode == zero { - v.DeliveryMode = "normal" + if mailingListAddGroupsioMemberBearerToken != "" { + bearerToken = &mailingListAddGroupsioMemberBearerToken } } - { - var zero string - if v.ModStatus == zero { - v.ModStatus = "none" - } + v := &mailinglist.AddGroupsioMemberPayload{ + Email: body.Email, + Name: body.Name, + ModStatus: body.ModStatus, + DeliveryMode: body.DeliveryMode, } - v.UID = uid - v.Version = version + v.SubgroupID = subgroupID v.BearerToken = bearerToken return v, nil } -// BuildGetGrpsioMailingListMemberPayload builds the payload for the -// mailing-list get-grpsio-mailing-list-member endpoint from CLI flags. -func BuildGetGrpsioMailingListMemberPayload(mailingListGetGrpsioMailingListMemberUID string, mailingListGetGrpsioMailingListMemberMemberUID string, mailingListGetGrpsioMailingListMemberVersion string, mailingListGetGrpsioMailingListMemberBearerToken string) (*mailinglist.GetGrpsioMailingListMemberPayload, error) { - var err error - var uid string +// BuildGetGroupsioMemberPayload builds the payload for the mailing-list +// get-groupsio-member endpoint from CLI flags. +func BuildGetGroupsioMemberPayload(mailingListGetGroupsioMemberSubgroupID string, mailingListGetGroupsioMemberMemberID string, mailingListGetGroupsioMemberBearerToken string) (*mailinglist.GetGroupsioMemberPayload, error) { + var subgroupID string { - uid = mailingListGetGrpsioMailingListMemberUID - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - if err != nil { - return nil, err - } + subgroupID = mailingListGetGroupsioMemberSubgroupID } - var memberUID string + var memberID string { - memberUID = mailingListGetGrpsioMailingListMemberMemberUID - err = goa.MergeErrors(err, goa.ValidateFormat("member_uid", memberUID, goa.FormatUUID)) - if err != nil { - return nil, err - } + memberID = mailingListGetGroupsioMemberMemberID } - var version string + var bearerToken *string { - version = mailingListGetGrpsioMailingListMemberVersion - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } - if err != nil { - return nil, err + if mailingListGetGroupsioMemberBearerToken != "" { + bearerToken = &mailingListGetGroupsioMemberBearerToken } } - var bearerToken string - { - bearerToken = mailingListGetGrpsioMailingListMemberBearerToken - } - v := &mailinglist.GetGrpsioMailingListMemberPayload{} - v.UID = uid - v.MemberUID = memberUID - v.Version = version + v := &mailinglist.GetGroupsioMemberPayload{} + v.SubgroupID = subgroupID + v.MemberID = memberID v.BearerToken = bearerToken return v, nil } -// BuildUpdateGrpsioMailingListMemberPayload builds the payload for the -// mailing-list update-grpsio-mailing-list-member endpoint from CLI flags. -func BuildUpdateGrpsioMailingListMemberPayload(mailingListUpdateGrpsioMailingListMemberBody string, mailingListUpdateGrpsioMailingListMemberUID string, mailingListUpdateGrpsioMailingListMemberMemberUID string, mailingListUpdateGrpsioMailingListMemberVersion string, mailingListUpdateGrpsioMailingListMemberBearerToken string, mailingListUpdateGrpsioMailingListMemberIfMatch string) (*mailinglist.UpdateGrpsioMailingListMemberPayload, error) { +// BuildUpdateGroupsioMemberPayload builds the payload for the mailing-list +// update-groupsio-member endpoint from CLI flags. +func BuildUpdateGroupsioMemberPayload(mailingListUpdateGroupsioMemberBody string, mailingListUpdateGroupsioMemberSubgroupID string, mailingListUpdateGroupsioMemberMemberID string, mailingListUpdateGroupsioMemberBearerToken string) (*mailinglist.UpdateGroupsioMemberPayload, error) { var err error - var body UpdateGrpsioMailingListMemberRequestBody + var body UpdateGroupsioMemberRequestBody { - err = json.Unmarshal([]byte(mailingListUpdateGrpsioMailingListMemberBody), &body) + err = json.Unmarshal([]byte(mailingListUpdateGroupsioMemberBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"delivery_mode\": \"none\",\n \"first_name\": \"John\",\n \"job_title\": \"Software Engineer\",\n \"last_name\": \"Doe\",\n \"mod_status\": \"none\",\n \"organization\": \"Example Corp\",\n \"username\": \"jdoe\"\n }'") - } - if body.Username != nil { - if utf8.RuneCountInString(*body.Username) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.username", *body.Username, utf8.RuneCountInString(*body.Username), 255, false)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 1, true)) - } + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"delivery_mode\": \"normal\",\n \"email\": \"katelynn_rempel@bruen.info\",\n \"mod_status\": \"none\",\n \"name\": \"Inventore suscipit eveniet ipsum aut et.\"\n }'") } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 255, false)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 1, true)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 255, false)) - } + if body.Email != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) } - if body.Organization != nil { - if utf8.RuneCountInString(*body.Organization) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.organization", *body.Organization, utf8.RuneCountInString(*body.Organization), 255, false)) + if body.ModStatus != nil { + if !(*body.ModStatus == "none" || *body.ModStatus == "moderator" || *body.ModStatus == "owner") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.mod_status", *body.ModStatus, []any{"none", "moderator", "owner"})) } } - if body.JobTitle != nil { - if utf8.RuneCountInString(*body.JobTitle) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.job_title", *body.JobTitle, utf8.RuneCountInString(*body.JobTitle), 255, false)) + if body.DeliveryMode != nil { + if !(*body.DeliveryMode == "normal" || *body.DeliveryMode == "digest" || *body.DeliveryMode == "none") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.delivery_mode", *body.DeliveryMode, []any{"normal", "digest", "none"})) } } - if !(body.DeliveryMode == "normal" || body.DeliveryMode == "digest" || body.DeliveryMode == "none") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.delivery_mode", body.DeliveryMode, []any{"normal", "digest", "none"})) - } - if !(body.ModStatus == "none" || body.ModStatus == "moderator" || body.ModStatus == "owner") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.mod_status", body.ModStatus, []any{"none", "moderator", "owner"})) - } if err != nil { return nil, err } } - var uid string + var subgroupID string { - uid = mailingListUpdateGrpsioMailingListMemberUID - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - if err != nil { - return nil, err - } + subgroupID = mailingListUpdateGroupsioMemberSubgroupID } - var memberUID string + var memberID string { - memberUID = mailingListUpdateGrpsioMailingListMemberMemberUID - err = goa.MergeErrors(err, goa.ValidateFormat("member_uid", memberUID, goa.FormatUUID)) - if err != nil { - return nil, err - } + memberID = mailingListUpdateGroupsioMemberMemberID } - var version string + var bearerToken *string { - version = mailingListUpdateGrpsioMailingListMemberVersion - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } - if err != nil { - return nil, err + if mailingListUpdateGroupsioMemberBearerToken != "" { + bearerToken = &mailingListUpdateGroupsioMemberBearerToken } } - var bearerToken string - { - bearerToken = mailingListUpdateGrpsioMailingListMemberBearerToken + v := &mailinglist.UpdateGroupsioMemberPayload{ + Email: body.Email, + Name: body.Name, + ModStatus: body.ModStatus, + DeliveryMode: body.DeliveryMode, } - var ifMatch string + v.SubgroupID = subgroupID + v.MemberID = memberID + v.BearerToken = bearerToken + + return v, nil +} + +// BuildDeleteGroupsioMemberPayload builds the payload for the mailing-list +// delete-groupsio-member endpoint from CLI flags. +func BuildDeleteGroupsioMemberPayload(mailingListDeleteGroupsioMemberSubgroupID string, mailingListDeleteGroupsioMemberMemberID string, mailingListDeleteGroupsioMemberBearerToken string) (*mailinglist.DeleteGroupsioMemberPayload, error) { + var subgroupID string { - ifMatch = mailingListUpdateGrpsioMailingListMemberIfMatch - } - v := &mailinglist.UpdateGrpsioMailingListMemberPayload{ - Username: body.Username, - FirstName: body.FirstName, - LastName: body.LastName, - Organization: body.Organization, - JobTitle: body.JobTitle, - DeliveryMode: body.DeliveryMode, - ModStatus: body.ModStatus, + subgroupID = mailingListDeleteGroupsioMemberSubgroupID } + var memberID string { - var zero string - if v.DeliveryMode == zero { - v.DeliveryMode = "normal" - } + memberID = mailingListDeleteGroupsioMemberMemberID } + var bearerToken *string { - var zero string - if v.ModStatus == zero { - v.ModStatus = "none" + if mailingListDeleteGroupsioMemberBearerToken != "" { + bearerToken = &mailingListDeleteGroupsioMemberBearerToken } } - v.UID = uid - v.MemberUID = memberUID - v.Version = version + v := &mailinglist.DeleteGroupsioMemberPayload{} + v.SubgroupID = subgroupID + v.MemberID = memberID v.BearerToken = bearerToken - v.IfMatch = ifMatch return v, nil } -// BuildDeleteGrpsioMailingListMemberPayload builds the payload for the -// mailing-list delete-grpsio-mailing-list-member endpoint from CLI flags. -func BuildDeleteGrpsioMailingListMemberPayload(mailingListDeleteGrpsioMailingListMemberUID string, mailingListDeleteGrpsioMailingListMemberMemberUID string, mailingListDeleteGrpsioMailingListMemberVersion string, mailingListDeleteGrpsioMailingListMemberBearerToken string, mailingListDeleteGrpsioMailingListMemberIfMatch string) (*mailinglist.DeleteGrpsioMailingListMemberPayload, error) { +// BuildInviteGroupsioMembersPayload builds the payload for the mailing-list +// invite-groupsio-members endpoint from CLI flags. +func BuildInviteGroupsioMembersPayload(mailingListInviteGroupsioMembersBody string, mailingListInviteGroupsioMembersSubgroupID string, mailingListInviteGroupsioMembersBearerToken string) (*mailinglist.InviteGroupsioMembersPayload, error) { var err error - var uid string + var body InviteGroupsioMembersRequestBody { - uid = mailingListDeleteGrpsioMailingListMemberUID - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) + err = json.Unmarshal([]byte(mailingListInviteGroupsioMembersBody), &body) if err != nil { - return nil, err + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"emails\": [\n \"Quo nihil quia blanditiis unde.\",\n \"Qui commodi totam.\",\n \"Voluptatem excepturi nam debitis quisquam voluptas velit.\",\n \"Quibusdam voluptatum soluta sapiente error ut.\"\n ]\n }'") } - } - var memberUID string - { - memberUID = mailingListDeleteGrpsioMailingListMemberMemberUID - err = goa.MergeErrors(err, goa.ValidateFormat("member_uid", memberUID, goa.FormatUUID)) - if err != nil { - return nil, err - } - } - var version string - { - version = mailingListDeleteGrpsioMailingListMemberVersion - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) + if body.Emails == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("emails", "body")) } if err != nil { return nil, err } } - var bearerToken string + var subgroupID string { - bearerToken = mailingListDeleteGrpsioMailingListMemberBearerToken + subgroupID = mailingListInviteGroupsioMembersSubgroupID } - var ifMatch string + var bearerToken *string { - ifMatch = mailingListDeleteGrpsioMailingListMemberIfMatch + if mailingListInviteGroupsioMembersBearerToken != "" { + bearerToken = &mailingListInviteGroupsioMembersBearerToken + } + } + v := &mailinglist.InviteGroupsioMembersPayload{} + if body.Emails != nil { + v.Emails = make([]string, len(body.Emails)) + for i, val := range body.Emails { + v.Emails[i] = val + } + } else { + v.Emails = []string{} } - v := &mailinglist.DeleteGrpsioMailingListMemberPayload{} - v.UID = uid - v.MemberUID = memberUID - v.Version = version + v.SubgroupID = subgroupID v.BearerToken = bearerToken - v.IfMatch = ifMatch return v, nil } -// BuildGroupsioWebhookPayload builds the payload for the mailing-list -// groupsio-webhook endpoint from CLI flags. -func BuildGroupsioWebhookPayload(mailingListGroupsioWebhookBody string, mailingListGroupsioWebhookSignature string) (*mailinglist.GroupsioWebhookPayload, error) { +// BuildCheckGroupsioSubscriberPayload builds the payload for the mailing-list +// check-groupsio-subscriber endpoint from CLI flags. +func BuildCheckGroupsioSubscriberPayload(mailingListCheckGroupsioSubscriberBody string, mailingListCheckGroupsioSubscriberBearerToken string) (*mailinglist.CheckGroupsioSubscriberPayload, error) { var err error - var body GroupsioWebhookRequestBody + var body CheckGroupsioSubscriberRequestBody { - err = json.Unmarshal([]byte(mailingListGroupsioWebhookBody), &body) + err = json.Unmarshal([]byte(mailingListCheckGroupsioSubscriberBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"action\": \"created_subgroup\",\n \"extra\": \"Sint rerum laboriosam consequatur ut explicabo saepe.\",\n \"extra_id\": 500672409584855543,\n \"group\": \"Aut consequatur nihil repellat velit qui.\",\n \"member_info\": \"Totam cumque totam alias.\"\n }'") - } - if !(body.Action == "created_subgroup" || body.Action == "deleted_subgroup" || body.Action == "added_member" || body.Action == "removed_member" || body.Action == "ban_members") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.action", body.Action, []any{"created_subgroup", "deleted_subgroup", "added_member", "removed_member", "ban_members"})) + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"email\": \"murl.o\\'keefe@gleason.biz\",\n \"subgroup_id\": \"Voluptates animi totam.\"\n }'") } + err = goa.MergeErrors(err, goa.ValidateFormat("body.email", body.Email, goa.FormatEmail)) if err != nil { return nil, err } } - var signature string + var bearerToken *string { - signature = mailingListGroupsioWebhookSignature + if mailingListCheckGroupsioSubscriberBearerToken != "" { + bearerToken = &mailingListCheckGroupsioSubscriberBearerToken + } } - v := &mailinglist.GroupsioWebhookPayload{ - Action: body.Action, - Group: body.Group, - MemberInfo: body.MemberInfo, - Extra: body.Extra, - ExtraID: body.ExtraID, + v := &mailinglist.CheckGroupsioSubscriberPayload{ + Email: body.Email, + SubgroupID: body.SubgroupID, } - v.Signature = signature + v.BearerToken = bearerToken return v, nil } diff --git a/gen/http/mailing_list/client/client.go b/gen/http/mailing_list/client/client.go index 0be9285..0b98eeb 100644 --- a/gen/http/mailing_list/client/client.go +++ b/gen/http/mailing_list/client/client.go @@ -24,73 +24,89 @@ type Client struct { // Readyz Doer is the HTTP client used to make requests to the readyz endpoint. ReadyzDoer goahttp.Doer - // CreateGrpsioService Doer is the HTTP client used to make requests to the - // create-grpsio-service endpoint. - CreateGrpsioServiceDoer goahttp.Doer + // ListGroupsioServices Doer is the HTTP client used to make requests to the + // list-groupsio-services endpoint. + ListGroupsioServicesDoer goahttp.Doer - // GetGrpsioService Doer is the HTTP client used to make requests to the - // get-grpsio-service endpoint. - GetGrpsioServiceDoer goahttp.Doer + // CreateGroupsioService Doer is the HTTP client used to make requests to the + // create-groupsio-service endpoint. + CreateGroupsioServiceDoer goahttp.Doer - // UpdateGrpsioService Doer is the HTTP client used to make requests to the - // update-grpsio-service endpoint. - UpdateGrpsioServiceDoer goahttp.Doer + // GetGroupsioService Doer is the HTTP client used to make requests to the + // get-groupsio-service endpoint. + GetGroupsioServiceDoer goahttp.Doer - // DeleteGrpsioService Doer is the HTTP client used to make requests to the - // delete-grpsio-service endpoint. - DeleteGrpsioServiceDoer goahttp.Doer + // UpdateGroupsioService Doer is the HTTP client used to make requests to the + // update-groupsio-service endpoint. + UpdateGroupsioServiceDoer goahttp.Doer - // GetGrpsioServiceSettings Doer is the HTTP client used to make requests to - // the get-grpsio-service-settings endpoint. - GetGrpsioServiceSettingsDoer goahttp.Doer + // DeleteGroupsioService Doer is the HTTP client used to make requests to the + // delete-groupsio-service endpoint. + DeleteGroupsioServiceDoer goahttp.Doer - // UpdateGrpsioServiceSettings Doer is the HTTP client used to make requests to - // the update-grpsio-service-settings endpoint. - UpdateGrpsioServiceSettingsDoer goahttp.Doer + // GetGroupsioServiceProjects Doer is the HTTP client used to make requests to + // the get-groupsio-service-projects endpoint. + GetGroupsioServiceProjectsDoer goahttp.Doer - // CreateGrpsioMailingList Doer is the HTTP client used to make requests to the - // create-grpsio-mailing-list endpoint. - CreateGrpsioMailingListDoer goahttp.Doer + // FindParentGroupsioService Doer is the HTTP client used to make requests to + // the find-parent-groupsio-service endpoint. + FindParentGroupsioServiceDoer goahttp.Doer - // GetGrpsioMailingList Doer is the HTTP client used to make requests to the - // get-grpsio-mailing-list endpoint. - GetGrpsioMailingListDoer goahttp.Doer + // ListGroupsioSubgroups Doer is the HTTP client used to make requests to the + // list-groupsio-subgroups endpoint. + ListGroupsioSubgroupsDoer goahttp.Doer - // UpdateGrpsioMailingList Doer is the HTTP client used to make requests to the - // update-grpsio-mailing-list endpoint. - UpdateGrpsioMailingListDoer goahttp.Doer + // CreateGroupsioSubgroup Doer is the HTTP client used to make requests to the + // create-groupsio-subgroup endpoint. + CreateGroupsioSubgroupDoer goahttp.Doer - // DeleteGrpsioMailingList Doer is the HTTP client used to make requests to the - // delete-grpsio-mailing-list endpoint. - DeleteGrpsioMailingListDoer goahttp.Doer + // GetGroupsioSubgroup Doer is the HTTP client used to make requests to the + // get-groupsio-subgroup endpoint. + GetGroupsioSubgroupDoer goahttp.Doer - // GetGrpsioMailingListSettings Doer is the HTTP client used to make requests - // to the get-grpsio-mailing-list-settings endpoint. - GetGrpsioMailingListSettingsDoer goahttp.Doer + // UpdateGroupsioSubgroup Doer is the HTTP client used to make requests to the + // update-groupsio-subgroup endpoint. + UpdateGroupsioSubgroupDoer goahttp.Doer - // UpdateGrpsioMailingListSettings Doer is the HTTP client used to make - // requests to the update-grpsio-mailing-list-settings endpoint. - UpdateGrpsioMailingListSettingsDoer goahttp.Doer + // DeleteGroupsioSubgroup Doer is the HTTP client used to make requests to the + // delete-groupsio-subgroup endpoint. + DeleteGroupsioSubgroupDoer goahttp.Doer - // CreateGrpsioMailingListMember Doer is the HTTP client used to make requests - // to the create-grpsio-mailing-list-member endpoint. - CreateGrpsioMailingListMemberDoer goahttp.Doer + // GetGroupsioSubgroupCount Doer is the HTTP client used to make requests to + // the get-groupsio-subgroup-count endpoint. + GetGroupsioSubgroupCountDoer goahttp.Doer - // GetGrpsioMailingListMember Doer is the HTTP client used to make requests to - // the get-grpsio-mailing-list-member endpoint. - GetGrpsioMailingListMemberDoer goahttp.Doer + // GetGroupsioSubgroupMemberCount Doer is the HTTP client used to make requests + // to the get-groupsio-subgroup-member-count endpoint. + GetGroupsioSubgroupMemberCountDoer goahttp.Doer - // UpdateGrpsioMailingListMember Doer is the HTTP client used to make requests - // to the update-grpsio-mailing-list-member endpoint. - UpdateGrpsioMailingListMemberDoer goahttp.Doer + // ListGroupsioMembers Doer is the HTTP client used to make requests to the + // list-groupsio-members endpoint. + ListGroupsioMembersDoer goahttp.Doer - // DeleteGrpsioMailingListMember Doer is the HTTP client used to make requests - // to the delete-grpsio-mailing-list-member endpoint. - DeleteGrpsioMailingListMemberDoer goahttp.Doer + // AddGroupsioMember Doer is the HTTP client used to make requests to the + // add-groupsio-member endpoint. + AddGroupsioMemberDoer goahttp.Doer - // GroupsioWebhook Doer is the HTTP client used to make requests to the - // groupsio-webhook endpoint. - GroupsioWebhookDoer goahttp.Doer + // GetGroupsioMember Doer is the HTTP client used to make requests to the + // get-groupsio-member endpoint. + GetGroupsioMemberDoer goahttp.Doer + + // UpdateGroupsioMember Doer is the HTTP client used to make requests to the + // update-groupsio-member endpoint. + UpdateGroupsioMemberDoer goahttp.Doer + + // DeleteGroupsioMember Doer is the HTTP client used to make requests to the + // delete-groupsio-member endpoint. + DeleteGroupsioMemberDoer goahttp.Doer + + // InviteGroupsioMembers Doer is the HTTP client used to make requests to the + // invite-groupsio-members endpoint. + InviteGroupsioMembersDoer goahttp.Doer + + // CheckGroupsioSubscriber Doer is the HTTP client used to make requests to the + // check-groupsio-subscriber endpoint. + CheckGroupsioSubscriberDoer goahttp.Doer // RestoreResponseBody controls whether the response bodies are reset after // decoding so they can be read again. @@ -112,30 +128,34 @@ func NewClient( restoreBody bool, ) *Client { return &Client{ - LivezDoer: doer, - ReadyzDoer: doer, - CreateGrpsioServiceDoer: doer, - GetGrpsioServiceDoer: doer, - UpdateGrpsioServiceDoer: doer, - DeleteGrpsioServiceDoer: doer, - GetGrpsioServiceSettingsDoer: doer, - UpdateGrpsioServiceSettingsDoer: doer, - CreateGrpsioMailingListDoer: doer, - GetGrpsioMailingListDoer: doer, - UpdateGrpsioMailingListDoer: doer, - DeleteGrpsioMailingListDoer: doer, - GetGrpsioMailingListSettingsDoer: doer, - UpdateGrpsioMailingListSettingsDoer: doer, - CreateGrpsioMailingListMemberDoer: doer, - GetGrpsioMailingListMemberDoer: doer, - UpdateGrpsioMailingListMemberDoer: doer, - DeleteGrpsioMailingListMemberDoer: doer, - GroupsioWebhookDoer: doer, - RestoreResponseBody: restoreBody, - scheme: scheme, - host: host, - decoder: dec, - encoder: enc, + LivezDoer: doer, + ReadyzDoer: doer, + ListGroupsioServicesDoer: doer, + CreateGroupsioServiceDoer: doer, + GetGroupsioServiceDoer: doer, + UpdateGroupsioServiceDoer: doer, + DeleteGroupsioServiceDoer: doer, + GetGroupsioServiceProjectsDoer: doer, + FindParentGroupsioServiceDoer: doer, + ListGroupsioSubgroupsDoer: doer, + CreateGroupsioSubgroupDoer: doer, + GetGroupsioSubgroupDoer: doer, + UpdateGroupsioSubgroupDoer: doer, + DeleteGroupsioSubgroupDoer: doer, + GetGroupsioSubgroupCountDoer: doer, + GetGroupsioSubgroupMemberCountDoer: doer, + ListGroupsioMembersDoer: doer, + AddGroupsioMemberDoer: doer, + GetGroupsioMemberDoer: doer, + UpdateGroupsioMemberDoer: doer, + DeleteGroupsioMemberDoer: doer, + InviteGroupsioMembersDoer: doer, + CheckGroupsioSubscriberDoer: doer, + RestoreResponseBody: restoreBody, + scheme: scheme, + host: host, + decoder: dec, + encoder: enc, } } @@ -177,15 +197,111 @@ func (c *Client) Readyz() goa.Endpoint { } } -// CreateGrpsioService returns an endpoint that makes HTTP requests to the -// mailing-list service create-grpsio-service server. -func (c *Client) CreateGrpsioService() goa.Endpoint { +// ListGroupsioServices returns an endpoint that makes HTTP requests to the +// mailing-list service list-groupsio-services server. +func (c *Client) ListGroupsioServices() goa.Endpoint { + var ( + encodeRequest = EncodeListGroupsioServicesRequest(c.encoder) + decodeResponse = DecodeListGroupsioServicesResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildListGroupsioServicesRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.ListGroupsioServicesDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("mailing-list", "list-groupsio-services", err) + } + return decodeResponse(resp) + } +} + +// CreateGroupsioService returns an endpoint that makes HTTP requests to the +// mailing-list service create-groupsio-service server. +func (c *Client) CreateGroupsioService() goa.Endpoint { + var ( + encodeRequest = EncodeCreateGroupsioServiceRequest(c.encoder) + decodeResponse = DecodeCreateGroupsioServiceResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildCreateGroupsioServiceRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.CreateGroupsioServiceDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("mailing-list", "create-groupsio-service", err) + } + return decodeResponse(resp) + } +} + +// GetGroupsioService returns an endpoint that makes HTTP requests to the +// mailing-list service get-groupsio-service server. +func (c *Client) GetGroupsioService() goa.Endpoint { + var ( + encodeRequest = EncodeGetGroupsioServiceRequest(c.encoder) + decodeResponse = DecodeGetGroupsioServiceResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildGetGroupsioServiceRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.GetGroupsioServiceDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("mailing-list", "get-groupsio-service", err) + } + return decodeResponse(resp) + } +} + +// UpdateGroupsioService returns an endpoint that makes HTTP requests to the +// mailing-list service update-groupsio-service server. +func (c *Client) UpdateGroupsioService() goa.Endpoint { + var ( + encodeRequest = EncodeUpdateGroupsioServiceRequest(c.encoder) + decodeResponse = DecodeUpdateGroupsioServiceResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildUpdateGroupsioServiceRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.UpdateGroupsioServiceDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("mailing-list", "update-groupsio-service", err) + } + return decodeResponse(resp) + } +} + +// DeleteGroupsioService returns an endpoint that makes HTTP requests to the +// mailing-list service delete-groupsio-service server. +func (c *Client) DeleteGroupsioService() goa.Endpoint { var ( - encodeRequest = EncodeCreateGrpsioServiceRequest(c.encoder) - decodeResponse = DecodeCreateGrpsioServiceResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeDeleteGroupsioServiceRequest(c.encoder) + decodeResponse = DecodeDeleteGroupsioServiceResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildCreateGrpsioServiceRequest(ctx, v) + req, err := c.BuildDeleteGroupsioServiceRequest(ctx, v) if err != nil { return nil, err } @@ -193,23 +309,23 @@ func (c *Client) CreateGrpsioService() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.CreateGrpsioServiceDoer.Do(req) + resp, err := c.DeleteGroupsioServiceDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "create-grpsio-service", err) + return nil, goahttp.ErrRequestError("mailing-list", "delete-groupsio-service", err) } return decodeResponse(resp) } } -// GetGrpsioService returns an endpoint that makes HTTP requests to the -// mailing-list service get-grpsio-service server. -func (c *Client) GetGrpsioService() goa.Endpoint { +// GetGroupsioServiceProjects returns an endpoint that makes HTTP requests to +// the mailing-list service get-groupsio-service-projects server. +func (c *Client) GetGroupsioServiceProjects() goa.Endpoint { var ( - encodeRequest = EncodeGetGrpsioServiceRequest(c.encoder) - decodeResponse = DecodeGetGrpsioServiceResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeGetGroupsioServiceProjectsRequest(c.encoder) + decodeResponse = DecodeGetGroupsioServiceProjectsResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildGetGrpsioServiceRequest(ctx, v) + req, err := c.BuildGetGroupsioServiceProjectsRequest(ctx, v) if err != nil { return nil, err } @@ -217,23 +333,23 @@ func (c *Client) GetGrpsioService() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.GetGrpsioServiceDoer.Do(req) + resp, err := c.GetGroupsioServiceProjectsDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "get-grpsio-service", err) + return nil, goahttp.ErrRequestError("mailing-list", "get-groupsio-service-projects", err) } return decodeResponse(resp) } } -// UpdateGrpsioService returns an endpoint that makes HTTP requests to the -// mailing-list service update-grpsio-service server. -func (c *Client) UpdateGrpsioService() goa.Endpoint { +// FindParentGroupsioService returns an endpoint that makes HTTP requests to +// the mailing-list service find-parent-groupsio-service server. +func (c *Client) FindParentGroupsioService() goa.Endpoint { var ( - encodeRequest = EncodeUpdateGrpsioServiceRequest(c.encoder) - decodeResponse = DecodeUpdateGrpsioServiceResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeFindParentGroupsioServiceRequest(c.encoder) + decodeResponse = DecodeFindParentGroupsioServiceResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildUpdateGrpsioServiceRequest(ctx, v) + req, err := c.BuildFindParentGroupsioServiceRequest(ctx, v) if err != nil { return nil, err } @@ -241,23 +357,23 @@ func (c *Client) UpdateGrpsioService() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.UpdateGrpsioServiceDoer.Do(req) + resp, err := c.FindParentGroupsioServiceDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "update-grpsio-service", err) + return nil, goahttp.ErrRequestError("mailing-list", "find-parent-groupsio-service", err) } return decodeResponse(resp) } } -// DeleteGrpsioService returns an endpoint that makes HTTP requests to the -// mailing-list service delete-grpsio-service server. -func (c *Client) DeleteGrpsioService() goa.Endpoint { +// ListGroupsioSubgroups returns an endpoint that makes HTTP requests to the +// mailing-list service list-groupsio-subgroups server. +func (c *Client) ListGroupsioSubgroups() goa.Endpoint { var ( - encodeRequest = EncodeDeleteGrpsioServiceRequest(c.encoder) - decodeResponse = DecodeDeleteGrpsioServiceResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeListGroupsioSubgroupsRequest(c.encoder) + decodeResponse = DecodeListGroupsioSubgroupsResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildDeleteGrpsioServiceRequest(ctx, v) + req, err := c.BuildListGroupsioSubgroupsRequest(ctx, v) if err != nil { return nil, err } @@ -265,23 +381,23 @@ func (c *Client) DeleteGrpsioService() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.DeleteGrpsioServiceDoer.Do(req) + resp, err := c.ListGroupsioSubgroupsDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "delete-grpsio-service", err) + return nil, goahttp.ErrRequestError("mailing-list", "list-groupsio-subgroups", err) } return decodeResponse(resp) } } -// GetGrpsioServiceSettings returns an endpoint that makes HTTP requests to the -// mailing-list service get-grpsio-service-settings server. -func (c *Client) GetGrpsioServiceSettings() goa.Endpoint { +// CreateGroupsioSubgroup returns an endpoint that makes HTTP requests to the +// mailing-list service create-groupsio-subgroup server. +func (c *Client) CreateGroupsioSubgroup() goa.Endpoint { var ( - encodeRequest = EncodeGetGrpsioServiceSettingsRequest(c.encoder) - decodeResponse = DecodeGetGrpsioServiceSettingsResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeCreateGroupsioSubgroupRequest(c.encoder) + decodeResponse = DecodeCreateGroupsioSubgroupResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildGetGrpsioServiceSettingsRequest(ctx, v) + req, err := c.BuildCreateGroupsioSubgroupRequest(ctx, v) if err != nil { return nil, err } @@ -289,23 +405,23 @@ func (c *Client) GetGrpsioServiceSettings() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.GetGrpsioServiceSettingsDoer.Do(req) + resp, err := c.CreateGroupsioSubgroupDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "get-grpsio-service-settings", err) + return nil, goahttp.ErrRequestError("mailing-list", "create-groupsio-subgroup", err) } return decodeResponse(resp) } } -// UpdateGrpsioServiceSettings returns an endpoint that makes HTTP requests to -// the mailing-list service update-grpsio-service-settings server. -func (c *Client) UpdateGrpsioServiceSettings() goa.Endpoint { +// GetGroupsioSubgroup returns an endpoint that makes HTTP requests to the +// mailing-list service get-groupsio-subgroup server. +func (c *Client) GetGroupsioSubgroup() goa.Endpoint { var ( - encodeRequest = EncodeUpdateGrpsioServiceSettingsRequest(c.encoder) - decodeResponse = DecodeUpdateGrpsioServiceSettingsResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeGetGroupsioSubgroupRequest(c.encoder) + decodeResponse = DecodeGetGroupsioSubgroupResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildUpdateGrpsioServiceSettingsRequest(ctx, v) + req, err := c.BuildGetGroupsioSubgroupRequest(ctx, v) if err != nil { return nil, err } @@ -313,23 +429,23 @@ func (c *Client) UpdateGrpsioServiceSettings() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.UpdateGrpsioServiceSettingsDoer.Do(req) + resp, err := c.GetGroupsioSubgroupDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "update-grpsio-service-settings", err) + return nil, goahttp.ErrRequestError("mailing-list", "get-groupsio-subgroup", err) } return decodeResponse(resp) } } -// CreateGrpsioMailingList returns an endpoint that makes HTTP requests to the -// mailing-list service create-grpsio-mailing-list server. -func (c *Client) CreateGrpsioMailingList() goa.Endpoint { +// UpdateGroupsioSubgroup returns an endpoint that makes HTTP requests to the +// mailing-list service update-groupsio-subgroup server. +func (c *Client) UpdateGroupsioSubgroup() goa.Endpoint { var ( - encodeRequest = EncodeCreateGrpsioMailingListRequest(c.encoder) - decodeResponse = DecodeCreateGrpsioMailingListResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeUpdateGroupsioSubgroupRequest(c.encoder) + decodeResponse = DecodeUpdateGroupsioSubgroupResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildCreateGrpsioMailingListRequest(ctx, v) + req, err := c.BuildUpdateGroupsioSubgroupRequest(ctx, v) if err != nil { return nil, err } @@ -337,23 +453,23 @@ func (c *Client) CreateGrpsioMailingList() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.CreateGrpsioMailingListDoer.Do(req) + resp, err := c.UpdateGroupsioSubgroupDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "create-grpsio-mailing-list", err) + return nil, goahttp.ErrRequestError("mailing-list", "update-groupsio-subgroup", err) } return decodeResponse(resp) } } -// GetGrpsioMailingList returns an endpoint that makes HTTP requests to the -// mailing-list service get-grpsio-mailing-list server. -func (c *Client) GetGrpsioMailingList() goa.Endpoint { +// DeleteGroupsioSubgroup returns an endpoint that makes HTTP requests to the +// mailing-list service delete-groupsio-subgroup server. +func (c *Client) DeleteGroupsioSubgroup() goa.Endpoint { var ( - encodeRequest = EncodeGetGrpsioMailingListRequest(c.encoder) - decodeResponse = DecodeGetGrpsioMailingListResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeDeleteGroupsioSubgroupRequest(c.encoder) + decodeResponse = DecodeDeleteGroupsioSubgroupResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildGetGrpsioMailingListRequest(ctx, v) + req, err := c.BuildDeleteGroupsioSubgroupRequest(ctx, v) if err != nil { return nil, err } @@ -361,23 +477,23 @@ func (c *Client) GetGrpsioMailingList() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.GetGrpsioMailingListDoer.Do(req) + resp, err := c.DeleteGroupsioSubgroupDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "get-grpsio-mailing-list", err) + return nil, goahttp.ErrRequestError("mailing-list", "delete-groupsio-subgroup", err) } return decodeResponse(resp) } } -// UpdateGrpsioMailingList returns an endpoint that makes HTTP requests to the -// mailing-list service update-grpsio-mailing-list server. -func (c *Client) UpdateGrpsioMailingList() goa.Endpoint { +// GetGroupsioSubgroupCount returns an endpoint that makes HTTP requests to the +// mailing-list service get-groupsio-subgroup-count server. +func (c *Client) GetGroupsioSubgroupCount() goa.Endpoint { var ( - encodeRequest = EncodeUpdateGrpsioMailingListRequest(c.encoder) - decodeResponse = DecodeUpdateGrpsioMailingListResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeGetGroupsioSubgroupCountRequest(c.encoder) + decodeResponse = DecodeGetGroupsioSubgroupCountResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildUpdateGrpsioMailingListRequest(ctx, v) + req, err := c.BuildGetGroupsioSubgroupCountRequest(ctx, v) if err != nil { return nil, err } @@ -385,23 +501,23 @@ func (c *Client) UpdateGrpsioMailingList() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.UpdateGrpsioMailingListDoer.Do(req) + resp, err := c.GetGroupsioSubgroupCountDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "update-grpsio-mailing-list", err) + return nil, goahttp.ErrRequestError("mailing-list", "get-groupsio-subgroup-count", err) } return decodeResponse(resp) } } -// DeleteGrpsioMailingList returns an endpoint that makes HTTP requests to the -// mailing-list service delete-grpsio-mailing-list server. -func (c *Client) DeleteGrpsioMailingList() goa.Endpoint { +// GetGroupsioSubgroupMemberCount returns an endpoint that makes HTTP requests +// to the mailing-list service get-groupsio-subgroup-member-count server. +func (c *Client) GetGroupsioSubgroupMemberCount() goa.Endpoint { var ( - encodeRequest = EncodeDeleteGrpsioMailingListRequest(c.encoder) - decodeResponse = DecodeDeleteGrpsioMailingListResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeGetGroupsioSubgroupMemberCountRequest(c.encoder) + decodeResponse = DecodeGetGroupsioSubgroupMemberCountResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildDeleteGrpsioMailingListRequest(ctx, v) + req, err := c.BuildGetGroupsioSubgroupMemberCountRequest(ctx, v) if err != nil { return nil, err } @@ -409,23 +525,23 @@ func (c *Client) DeleteGrpsioMailingList() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.DeleteGrpsioMailingListDoer.Do(req) + resp, err := c.GetGroupsioSubgroupMemberCountDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "delete-grpsio-mailing-list", err) + return nil, goahttp.ErrRequestError("mailing-list", "get-groupsio-subgroup-member-count", err) } return decodeResponse(resp) } } -// GetGrpsioMailingListSettings returns an endpoint that makes HTTP requests to -// the mailing-list service get-grpsio-mailing-list-settings server. -func (c *Client) GetGrpsioMailingListSettings() goa.Endpoint { +// ListGroupsioMembers returns an endpoint that makes HTTP requests to the +// mailing-list service list-groupsio-members server. +func (c *Client) ListGroupsioMembers() goa.Endpoint { var ( - encodeRequest = EncodeGetGrpsioMailingListSettingsRequest(c.encoder) - decodeResponse = DecodeGetGrpsioMailingListSettingsResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeListGroupsioMembersRequest(c.encoder) + decodeResponse = DecodeListGroupsioMembersResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildGetGrpsioMailingListSettingsRequest(ctx, v) + req, err := c.BuildListGroupsioMembersRequest(ctx, v) if err != nil { return nil, err } @@ -433,23 +549,23 @@ func (c *Client) GetGrpsioMailingListSettings() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.GetGrpsioMailingListSettingsDoer.Do(req) + resp, err := c.ListGroupsioMembersDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "get-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrRequestError("mailing-list", "list-groupsio-members", err) } return decodeResponse(resp) } } -// UpdateGrpsioMailingListSettings returns an endpoint that makes HTTP requests -// to the mailing-list service update-grpsio-mailing-list-settings server. -func (c *Client) UpdateGrpsioMailingListSettings() goa.Endpoint { +// AddGroupsioMember returns an endpoint that makes HTTP requests to the +// mailing-list service add-groupsio-member server. +func (c *Client) AddGroupsioMember() goa.Endpoint { var ( - encodeRequest = EncodeUpdateGrpsioMailingListSettingsRequest(c.encoder) - decodeResponse = DecodeUpdateGrpsioMailingListSettingsResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeAddGroupsioMemberRequest(c.encoder) + decodeResponse = DecodeAddGroupsioMemberResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildUpdateGrpsioMailingListSettingsRequest(ctx, v) + req, err := c.BuildAddGroupsioMemberRequest(ctx, v) if err != nil { return nil, err } @@ -457,23 +573,23 @@ func (c *Client) UpdateGrpsioMailingListSettings() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.UpdateGrpsioMailingListSettingsDoer.Do(req) + resp, err := c.AddGroupsioMemberDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "update-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrRequestError("mailing-list", "add-groupsio-member", err) } return decodeResponse(resp) } } -// CreateGrpsioMailingListMember returns an endpoint that makes HTTP requests -// to the mailing-list service create-grpsio-mailing-list-member server. -func (c *Client) CreateGrpsioMailingListMember() goa.Endpoint { +// GetGroupsioMember returns an endpoint that makes HTTP requests to the +// mailing-list service get-groupsio-member server. +func (c *Client) GetGroupsioMember() goa.Endpoint { var ( - encodeRequest = EncodeCreateGrpsioMailingListMemberRequest(c.encoder) - decodeResponse = DecodeCreateGrpsioMailingListMemberResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeGetGroupsioMemberRequest(c.encoder) + decodeResponse = DecodeGetGroupsioMemberResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildCreateGrpsioMailingListMemberRequest(ctx, v) + req, err := c.BuildGetGroupsioMemberRequest(ctx, v) if err != nil { return nil, err } @@ -481,23 +597,23 @@ func (c *Client) CreateGrpsioMailingListMember() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.CreateGrpsioMailingListMemberDoer.Do(req) + resp, err := c.GetGroupsioMemberDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "create-grpsio-mailing-list-member", err) + return nil, goahttp.ErrRequestError("mailing-list", "get-groupsio-member", err) } return decodeResponse(resp) } } -// GetGrpsioMailingListMember returns an endpoint that makes HTTP requests to -// the mailing-list service get-grpsio-mailing-list-member server. -func (c *Client) GetGrpsioMailingListMember() goa.Endpoint { +// UpdateGroupsioMember returns an endpoint that makes HTTP requests to the +// mailing-list service update-groupsio-member server. +func (c *Client) UpdateGroupsioMember() goa.Endpoint { var ( - encodeRequest = EncodeGetGrpsioMailingListMemberRequest(c.encoder) - decodeResponse = DecodeGetGrpsioMailingListMemberResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeUpdateGroupsioMemberRequest(c.encoder) + decodeResponse = DecodeUpdateGroupsioMemberResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildGetGrpsioMailingListMemberRequest(ctx, v) + req, err := c.BuildUpdateGroupsioMemberRequest(ctx, v) if err != nil { return nil, err } @@ -505,23 +621,23 @@ func (c *Client) GetGrpsioMailingListMember() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.GetGrpsioMailingListMemberDoer.Do(req) + resp, err := c.UpdateGroupsioMemberDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "get-grpsio-mailing-list-member", err) + return nil, goahttp.ErrRequestError("mailing-list", "update-groupsio-member", err) } return decodeResponse(resp) } } -// UpdateGrpsioMailingListMember returns an endpoint that makes HTTP requests -// to the mailing-list service update-grpsio-mailing-list-member server. -func (c *Client) UpdateGrpsioMailingListMember() goa.Endpoint { +// DeleteGroupsioMember returns an endpoint that makes HTTP requests to the +// mailing-list service delete-groupsio-member server. +func (c *Client) DeleteGroupsioMember() goa.Endpoint { var ( - encodeRequest = EncodeUpdateGrpsioMailingListMemberRequest(c.encoder) - decodeResponse = DecodeUpdateGrpsioMailingListMemberResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeDeleteGroupsioMemberRequest(c.encoder) + decodeResponse = DecodeDeleteGroupsioMemberResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildUpdateGrpsioMailingListMemberRequest(ctx, v) + req, err := c.BuildDeleteGroupsioMemberRequest(ctx, v) if err != nil { return nil, err } @@ -529,23 +645,23 @@ func (c *Client) UpdateGrpsioMailingListMember() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.UpdateGrpsioMailingListMemberDoer.Do(req) + resp, err := c.DeleteGroupsioMemberDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "update-grpsio-mailing-list-member", err) + return nil, goahttp.ErrRequestError("mailing-list", "delete-groupsio-member", err) } return decodeResponse(resp) } } -// DeleteGrpsioMailingListMember returns an endpoint that makes HTTP requests -// to the mailing-list service delete-grpsio-mailing-list-member server. -func (c *Client) DeleteGrpsioMailingListMember() goa.Endpoint { +// InviteGroupsioMembers returns an endpoint that makes HTTP requests to the +// mailing-list service invite-groupsio-members server. +func (c *Client) InviteGroupsioMembers() goa.Endpoint { var ( - encodeRequest = EncodeDeleteGrpsioMailingListMemberRequest(c.encoder) - decodeResponse = DecodeDeleteGrpsioMailingListMemberResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeInviteGroupsioMembersRequest(c.encoder) + decodeResponse = DecodeInviteGroupsioMembersResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildDeleteGrpsioMailingListMemberRequest(ctx, v) + req, err := c.BuildInviteGroupsioMembersRequest(ctx, v) if err != nil { return nil, err } @@ -553,23 +669,23 @@ func (c *Client) DeleteGrpsioMailingListMember() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.DeleteGrpsioMailingListMemberDoer.Do(req) + resp, err := c.InviteGroupsioMembersDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "delete-grpsio-mailing-list-member", err) + return nil, goahttp.ErrRequestError("mailing-list", "invite-groupsio-members", err) } return decodeResponse(resp) } } -// GroupsioWebhook returns an endpoint that makes HTTP requests to the -// mailing-list service groupsio-webhook server. -func (c *Client) GroupsioWebhook() goa.Endpoint { +// CheckGroupsioSubscriber returns an endpoint that makes HTTP requests to the +// mailing-list service check-groupsio-subscriber server. +func (c *Client) CheckGroupsioSubscriber() goa.Endpoint { var ( - encodeRequest = EncodeGroupsioWebhookRequest(c.encoder) - decodeResponse = DecodeGroupsioWebhookResponse(c.decoder, c.RestoreResponseBody) + encodeRequest = EncodeCheckGroupsioSubscriberRequest(c.encoder) + decodeResponse = DecodeCheckGroupsioSubscriberResponse(c.decoder, c.RestoreResponseBody) ) return func(ctx context.Context, v any) (any, error) { - req, err := c.BuildGroupsioWebhookRequest(ctx, v) + req, err := c.BuildCheckGroupsioSubscriberRequest(ctx, v) if err != nil { return nil, err } @@ -577,9 +693,9 @@ func (c *Client) GroupsioWebhook() goa.Endpoint { if err != nil { return nil, err } - resp, err := c.GroupsioWebhookDoer.Do(req) + resp, err := c.CheckGroupsioSubscriberDoer.Do(req) if err != nil { - return nil, goahttp.ErrRequestError("mailing-list", "groupsio-webhook", err) + return nil, goahttp.ErrRequestError("mailing-list", "check-groupsio-subscriber", err) } return decodeResponse(resp) } diff --git a/gen/http/mailing_list/client/encode_decode.go b/gen/http/mailing_list/client/encode_decode.go index e7d408e..bd001dd 100644 --- a/gen/http/mailing_list/client/encode_decode.go +++ b/gen/http/mailing_list/client/encode_decode.go @@ -137,14 +137,14 @@ func DecodeReadyzResponse(decoder func(*http.Response) goahttp.Decoder, restoreB } } -// BuildCreateGrpsioServiceRequest instantiates a HTTP request object with +// BuildListGroupsioServicesRequest instantiates a HTTP request object with // method and path set to call the "mailing-list" service -// "create-grpsio-service" endpoint -func (c *Client) BuildCreateGrpsioServiceRequest(ctx context.Context, v any) (*http.Request, error) { - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: CreateGrpsioServiceMailingListPath()} - req, err := http.NewRequest("POST", u.String(), nil) +// "list-groupsio-services" endpoint +func (c *Client) BuildListGroupsioServicesRequest(ctx context.Context, v any) (*http.Request, error) { + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: ListGroupsioServicesMailingListPath()} + req, err := http.NewRequest("GET", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "create-grpsio-service", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "list-groupsio-services", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -153,13 +153,13 @@ func (c *Client) BuildCreateGrpsioServiceRequest(ctx context.Context, v any) (*h return req, nil } -// EncodeCreateGrpsioServiceRequest returns an encoder for requests sent to the -// mailing-list create-grpsio-service server. -func EncodeCreateGrpsioServiceRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeListGroupsioServicesRequest returns an encoder for requests sent to +// the mailing-list list-groupsio-services server. +func EncodeListGroupsioServicesRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.CreateGrpsioServicePayload) + p, ok := v.(*mailinglist.ListGroupsioServicesPayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "create-grpsio-service", "*mailinglist.CreateGrpsioServicePayload", v) + return goahttp.ErrInvalidType("mailing-list", "list-groupsio-services", "*mailinglist.ListGroupsioServicesPayload", v) } if p.BearerToken != nil { head := *p.BearerToken @@ -170,27 +170,23 @@ func EncodeCreateGrpsioServiceRequest(encoder func(*http.Request) goahttp.Encode } } values := req.URL.Query() - values.Add("v", p.Version) - req.URL.RawQuery = values.Encode() - body := NewCreateGrpsioServiceRequestBody(p) - if err := encoder(req).Encode(&body); err != nil { - return goahttp.ErrEncodingError("mailing-list", "create-grpsio-service", err) + if p.ProjectUID != nil { + values.Add("project_uid", *p.ProjectUID) } + req.URL.RawQuery = values.Encode() return nil } } -// DecodeCreateGrpsioServiceResponse returns a decoder for responses returned -// by the mailing-list create-grpsio-service endpoint. restoreBody controls +// DecodeListGroupsioServicesResponse returns a decoder for responses returned +// by the mailing-list list-groupsio-services endpoint. restoreBody controls // whether the response body should be restored after having been read. -// DecodeCreateGrpsioServiceResponse may return the following errors: +// DecodeListGroupsioServicesResponse may return the following errors: // - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest -// - "Conflict" (type *mailinglist.ConflictError): http.StatusConflict // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError -// - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeCreateGrpsioServiceResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeListGroupsioServicesResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -205,117 +201,78 @@ func DecodeCreateGrpsioServiceResponse(decoder func(*http.Response) goahttp.Deco defer resp.Body.Close() } switch resp.StatusCode { - case http.StatusCreated: + case http.StatusOK: var ( - body CreateGrpsioServiceResponseBody + body ListGroupsioServicesResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "list-groupsio-services", err) } - err = ValidateCreateGrpsioServiceResponseBody(&body) + err = ValidateListGroupsioServicesResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "list-groupsio-services", err) } - res := NewCreateGrpsioServiceGrpsIoServiceFullCreated(&body) + res := NewListGroupsioServicesGroupsioServiceListOK(&body) return res, nil case http.StatusBadRequest: var ( - body CreateGrpsioServiceBadRequestResponseBody - err error - ) - err = decoder(resp).Decode(&body) - if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-service", err) - } - err = ValidateCreateGrpsioServiceBadRequestResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-service", err) - } - return nil, NewCreateGrpsioServiceBadRequest(&body) - case http.StatusConflict: - var ( - body CreateGrpsioServiceConflictResponseBody + body ListGroupsioServicesBadRequestResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "list-groupsio-services", err) } - err = ValidateCreateGrpsioServiceConflictResponseBody(&body) + err = ValidateListGroupsioServicesBadRequestResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "list-groupsio-services", err) } - return nil, NewCreateGrpsioServiceConflict(&body) + return nil, NewListGroupsioServicesBadRequest(&body) case http.StatusInternalServerError: var ( - body CreateGrpsioServiceInternalServerErrorResponseBody - err error - ) - err = decoder(resp).Decode(&body) - if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-service", err) - } - err = ValidateCreateGrpsioServiceInternalServerErrorResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-service", err) - } - return nil, NewCreateGrpsioServiceInternalServerError(&body) - case http.StatusNotFound: - var ( - body CreateGrpsioServiceNotFoundResponseBody + body ListGroupsioServicesInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "list-groupsio-services", err) } - err = ValidateCreateGrpsioServiceNotFoundResponseBody(&body) + err = ValidateListGroupsioServicesInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "list-groupsio-services", err) } - return nil, NewCreateGrpsioServiceNotFound(&body) + return nil, NewListGroupsioServicesInternalServerError(&body) case http.StatusServiceUnavailable: var ( - body CreateGrpsioServiceServiceUnavailableResponseBody + body ListGroupsioServicesServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "list-groupsio-services", err) } - err = ValidateCreateGrpsioServiceServiceUnavailableResponseBody(&body) + err = ValidateListGroupsioServicesServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "list-groupsio-services", err) } - return nil, NewCreateGrpsioServiceServiceUnavailable(&body) + return nil, NewListGroupsioServicesServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "create-grpsio-service", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "list-groupsio-services", resp.StatusCode, string(body)) } } } -// BuildGetGrpsioServiceRequest instantiates a HTTP request object with method -// and path set to call the "mailing-list" service "get-grpsio-service" endpoint -func (c *Client) BuildGetGrpsioServiceRequest(ctx context.Context, v any) (*http.Request, error) { - var ( - uid string - ) - { - p, ok := v.(*mailinglist.GetGrpsioServicePayload) - if !ok { - return nil, goahttp.ErrInvalidType("mailing-list", "get-grpsio-service", "*mailinglist.GetGrpsioServicePayload", v) - } - if p.UID != nil { - uid = *p.UID - } - } - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GetGrpsioServiceMailingListPath(uid)} - req, err := http.NewRequest("GET", u.String(), nil) +// BuildCreateGroupsioServiceRequest instantiates a HTTP request object with +// method and path set to call the "mailing-list" service +// "create-groupsio-service" endpoint +func (c *Client) BuildCreateGroupsioServiceRequest(ctx context.Context, v any) (*http.Request, error) { + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: CreateGroupsioServiceMailingListPath()} + req, err := http.NewRequest("POST", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "get-grpsio-service", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "create-groupsio-service", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -324,13 +281,13 @@ func (c *Client) BuildGetGrpsioServiceRequest(ctx context.Context, v any) (*http return req, nil } -// EncodeGetGrpsioServiceRequest returns an encoder for requests sent to the -// mailing-list get-grpsio-service server. -func EncodeGetGrpsioServiceRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeCreateGroupsioServiceRequest returns an encoder for requests sent to +// the mailing-list create-groupsio-service server. +func EncodeCreateGroupsioServiceRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.GetGrpsioServicePayload) + p, ok := v.(*mailinglist.CreateGroupsioServicePayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "get-grpsio-service", "*mailinglist.GetGrpsioServicePayload", v) + return goahttp.ErrInvalidType("mailing-list", "create-groupsio-service", "*mailinglist.CreateGroupsioServicePayload", v) } if p.BearerToken != nil { head := *p.BearerToken @@ -340,25 +297,24 @@ func EncodeGetGrpsioServiceRequest(encoder func(*http.Request) goahttp.Encoder) req.Header.Set("Authorization", head) } } - values := req.URL.Query() - if p.Version != nil { - values.Add("v", *p.Version) + body := NewCreateGroupsioServiceRequestBody(p) + if err := encoder(req).Encode(&body); err != nil { + return goahttp.ErrEncodingError("mailing-list", "create-groupsio-service", err) } - req.URL.RawQuery = values.Encode() return nil } } -// DecodeGetGrpsioServiceResponse returns a decoder for responses returned by -// the mailing-list get-grpsio-service endpoint. restoreBody controls whether -// the response body should be restored after having been read. -// DecodeGetGrpsioServiceResponse may return the following errors: +// DecodeCreateGroupsioServiceResponse returns a decoder for responses returned +// by the mailing-list create-groupsio-service endpoint. restoreBody controls +// whether the response body should be restored after having been read. +// DecodeCreateGroupsioServiceResponse may return the following errors: // - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest +// - "Conflict" (type *mailinglist.ConflictError): http.StatusConflict // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError -// - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeGetGrpsioServiceResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeCreateGroupsioServiceResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -373,111 +329,102 @@ func DecodeGetGrpsioServiceResponse(decoder func(*http.Response) goahttp.Decoder defer resp.Body.Close() } switch resp.StatusCode { - case http.StatusOK: + case http.StatusCreated: var ( - body GetGrpsioServiceResponseBody + body CreateGroupsioServiceResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "create-groupsio-service", err) } - err = ValidateGetGrpsioServiceResponseBody(&body) + err = ValidateCreateGroupsioServiceResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-service", err) - } - var ( - etag *string - ) - etagRaw := resp.Header.Get("Etag") - if etagRaw != "" { - etag = &etagRaw + return nil, goahttp.ErrValidationError("mailing-list", "create-groupsio-service", err) } - res := NewGetGrpsioServiceResultOK(&body, etag) + res := NewCreateGroupsioServiceGroupsioServiceCreated(&body) return res, nil case http.StatusBadRequest: var ( - body GetGrpsioServiceBadRequestResponseBody + body CreateGroupsioServiceBadRequestResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "create-groupsio-service", err) } - err = ValidateGetGrpsioServiceBadRequestResponseBody(&body) + err = ValidateCreateGroupsioServiceBadRequestResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "create-groupsio-service", err) } - return nil, NewGetGrpsioServiceBadRequest(&body) - case http.StatusInternalServerError: + return nil, NewCreateGroupsioServiceBadRequest(&body) + case http.StatusConflict: var ( - body GetGrpsioServiceInternalServerErrorResponseBody + body CreateGroupsioServiceConflictResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "create-groupsio-service", err) } - err = ValidateGetGrpsioServiceInternalServerErrorResponseBody(&body) + err = ValidateCreateGroupsioServiceConflictResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "create-groupsio-service", err) } - return nil, NewGetGrpsioServiceInternalServerError(&body) - case http.StatusNotFound: + return nil, NewCreateGroupsioServiceConflict(&body) + case http.StatusInternalServerError: var ( - body GetGrpsioServiceNotFoundResponseBody + body CreateGroupsioServiceInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "create-groupsio-service", err) } - err = ValidateGetGrpsioServiceNotFoundResponseBody(&body) + err = ValidateCreateGroupsioServiceInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "create-groupsio-service", err) } - return nil, NewGetGrpsioServiceNotFound(&body) + return nil, NewCreateGroupsioServiceInternalServerError(&body) case http.StatusServiceUnavailable: var ( - body GetGrpsioServiceServiceUnavailableResponseBody + body CreateGroupsioServiceServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "create-groupsio-service", err) } - err = ValidateGetGrpsioServiceServiceUnavailableResponseBody(&body) + err = ValidateCreateGroupsioServiceServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "create-groupsio-service", err) } - return nil, NewGetGrpsioServiceServiceUnavailable(&body) + return nil, NewCreateGroupsioServiceServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "get-grpsio-service", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "create-groupsio-service", resp.StatusCode, string(body)) } } } -// BuildUpdateGrpsioServiceRequest instantiates a HTTP request object with +// BuildGetGroupsioServiceRequest instantiates a HTTP request object with // method and path set to call the "mailing-list" service -// "update-grpsio-service" endpoint -func (c *Client) BuildUpdateGrpsioServiceRequest(ctx context.Context, v any) (*http.Request, error) { +// "get-groupsio-service" endpoint +func (c *Client) BuildGetGroupsioServiceRequest(ctx context.Context, v any) (*http.Request, error) { var ( - uid string + serviceID string ) { - p, ok := v.(*mailinglist.UpdateGrpsioServicePayload) + p, ok := v.(*mailinglist.GetGroupsioServicePayload) if !ok { - return nil, goahttp.ErrInvalidType("mailing-list", "update-grpsio-service", "*mailinglist.UpdateGrpsioServicePayload", v) - } - if p.UID != nil { - uid = *p.UID + return nil, goahttp.ErrInvalidType("mailing-list", "get-groupsio-service", "*mailinglist.GetGroupsioServicePayload", v) } + serviceID = p.ServiceID } - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: UpdateGrpsioServiceMailingListPath(uid)} - req, err := http.NewRequest("PUT", u.String(), nil) + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GetGroupsioServiceMailingListPath(serviceID)} + req, err := http.NewRequest("GET", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "update-grpsio-service", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "get-groupsio-service", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -486,13 +433,13 @@ func (c *Client) BuildUpdateGrpsioServiceRequest(ctx context.Context, v any) (*h return req, nil } -// EncodeUpdateGrpsioServiceRequest returns an encoder for requests sent to the -// mailing-list update-grpsio-service server. -func EncodeUpdateGrpsioServiceRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeGetGroupsioServiceRequest returns an encoder for requests sent to the +// mailing-list get-groupsio-service server. +func EncodeGetGroupsioServiceRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.UpdateGrpsioServicePayload) + p, ok := v.(*mailinglist.GetGroupsioServicePayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "update-grpsio-service", "*mailinglist.UpdateGrpsioServicePayload", v) + return goahttp.ErrInvalidType("mailing-list", "get-groupsio-service", "*mailinglist.GetGroupsioServicePayload", v) } if p.BearerToken != nil { head := *p.BearerToken @@ -502,32 +449,19 @@ func EncodeUpdateGrpsioServiceRequest(encoder func(*http.Request) goahttp.Encode req.Header.Set("Authorization", head) } } - if p.IfMatch != nil { - head := *p.IfMatch - req.Header.Set("If-Match", head) - } - values := req.URL.Query() - values.Add("v", p.Version) - req.URL.RawQuery = values.Encode() - body := NewUpdateGrpsioServiceRequestBody(p) - if err := encoder(req).Encode(&body); err != nil { - return goahttp.ErrEncodingError("mailing-list", "update-grpsio-service", err) - } return nil } } -// DecodeUpdateGrpsioServiceResponse returns a decoder for responses returned -// by the mailing-list update-grpsio-service endpoint. restoreBody controls -// whether the response body should be restored after having been read. -// DecodeUpdateGrpsioServiceResponse may return the following errors: -// - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest -// - "Conflict" (type *mailinglist.ConflictError): http.StatusConflict +// DecodeGetGroupsioServiceResponse returns a decoder for responses returned by +// the mailing-list get-groupsio-service endpoint. restoreBody controls whether +// the response body should be restored after having been read. +// DecodeGetGroupsioServiceResponse may return the following errors: // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError // - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeUpdateGrpsioServiceResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeGetGroupsioServiceResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -544,116 +478,86 @@ func DecodeUpdateGrpsioServiceResponse(decoder func(*http.Response) goahttp.Deco switch resp.StatusCode { case http.StatusOK: var ( - body UpdateGrpsioServiceResponseBody + body GetGroupsioServiceResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-service", err) } - err = ValidateUpdateGrpsioServiceResponseBody(&body) + err = ValidateGetGroupsioServiceResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-service", err) } - res := NewUpdateGrpsioServiceGrpsIoServiceWithReadonlyAttributesOK(&body) + res := NewGetGroupsioServiceGroupsioServiceOK(&body) return res, nil - case http.StatusBadRequest: - var ( - body UpdateGrpsioServiceBadRequestResponseBody - err error - ) - err = decoder(resp).Decode(&body) - if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-service", err) - } - err = ValidateUpdateGrpsioServiceBadRequestResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-service", err) - } - return nil, NewUpdateGrpsioServiceBadRequest(&body) - case http.StatusConflict: - var ( - body UpdateGrpsioServiceConflictResponseBody - err error - ) - err = decoder(resp).Decode(&body) - if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-service", err) - } - err = ValidateUpdateGrpsioServiceConflictResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-service", err) - } - return nil, NewUpdateGrpsioServiceConflict(&body) case http.StatusInternalServerError: var ( - body UpdateGrpsioServiceInternalServerErrorResponseBody + body GetGroupsioServiceInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-service", err) } - err = ValidateUpdateGrpsioServiceInternalServerErrorResponseBody(&body) + err = ValidateGetGroupsioServiceInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-service", err) } - return nil, NewUpdateGrpsioServiceInternalServerError(&body) + return nil, NewGetGroupsioServiceInternalServerError(&body) case http.StatusNotFound: var ( - body UpdateGrpsioServiceNotFoundResponseBody + body GetGroupsioServiceNotFoundResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-service", err) } - err = ValidateUpdateGrpsioServiceNotFoundResponseBody(&body) + err = ValidateGetGroupsioServiceNotFoundResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-service", err) } - return nil, NewUpdateGrpsioServiceNotFound(&body) + return nil, NewGetGroupsioServiceNotFound(&body) case http.StatusServiceUnavailable: var ( - body UpdateGrpsioServiceServiceUnavailableResponseBody + body GetGroupsioServiceServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-service", err) } - err = ValidateUpdateGrpsioServiceServiceUnavailableResponseBody(&body) + err = ValidateGetGroupsioServiceServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-service", err) } - return nil, NewUpdateGrpsioServiceServiceUnavailable(&body) + return nil, NewGetGroupsioServiceServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "update-grpsio-service", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "get-groupsio-service", resp.StatusCode, string(body)) } } } -// BuildDeleteGrpsioServiceRequest instantiates a HTTP request object with +// BuildUpdateGroupsioServiceRequest instantiates a HTTP request object with // method and path set to call the "mailing-list" service -// "delete-grpsio-service" endpoint -func (c *Client) BuildDeleteGrpsioServiceRequest(ctx context.Context, v any) (*http.Request, error) { +// "update-groupsio-service" endpoint +func (c *Client) BuildUpdateGroupsioServiceRequest(ctx context.Context, v any) (*http.Request, error) { var ( - uid string + serviceID string ) { - p, ok := v.(*mailinglist.DeleteGrpsioServicePayload) + p, ok := v.(*mailinglist.UpdateGroupsioServicePayload) if !ok { - return nil, goahttp.ErrInvalidType("mailing-list", "delete-grpsio-service", "*mailinglist.DeleteGrpsioServicePayload", v) - } - if p.UID != nil { - uid = *p.UID + return nil, goahttp.ErrInvalidType("mailing-list", "update-groupsio-service", "*mailinglist.UpdateGroupsioServicePayload", v) } + serviceID = p.ServiceID } - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: DeleteGrpsioServiceMailingListPath(uid)} - req, err := http.NewRequest("DELETE", u.String(), nil) + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: UpdateGroupsioServiceMailingListPath(serviceID)} + req, err := http.NewRequest("PUT", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "delete-grpsio-service", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "update-groupsio-service", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -662,13 +566,13 @@ func (c *Client) BuildDeleteGrpsioServiceRequest(ctx context.Context, v any) (*h return req, nil } -// EncodeDeleteGrpsioServiceRequest returns an encoder for requests sent to the -// mailing-list delete-grpsio-service server. -func EncodeDeleteGrpsioServiceRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeUpdateGroupsioServiceRequest returns an encoder for requests sent to +// the mailing-list update-groupsio-service server. +func EncodeUpdateGroupsioServiceRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.DeleteGrpsioServicePayload) + p, ok := v.(*mailinglist.UpdateGroupsioServicePayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "delete-grpsio-service", "*mailinglist.DeleteGrpsioServicePayload", v) + return goahttp.ErrInvalidType("mailing-list", "update-groupsio-service", "*mailinglist.UpdateGroupsioServicePayload", v) } if p.BearerToken != nil { head := *p.BearerToken @@ -678,30 +582,24 @@ func EncodeDeleteGrpsioServiceRequest(encoder func(*http.Request) goahttp.Encode req.Header.Set("Authorization", head) } } - if p.IfMatch != nil { - head := *p.IfMatch - req.Header.Set("If-Match", head) - } - values := req.URL.Query() - if p.Version != nil { - values.Add("v", *p.Version) + body := NewUpdateGroupsioServiceRequestBody(p) + if err := encoder(req).Encode(&body); err != nil { + return goahttp.ErrEncodingError("mailing-list", "update-groupsio-service", err) } - req.URL.RawQuery = values.Encode() return nil } } -// DecodeDeleteGrpsioServiceResponse returns a decoder for responses returned -// by the mailing-list delete-grpsio-service endpoint. restoreBody controls +// DecodeUpdateGroupsioServiceResponse returns a decoder for responses returned +// by the mailing-list update-groupsio-service endpoint. restoreBody controls // whether the response body should be restored after having been read. -// DecodeDeleteGrpsioServiceResponse may return the following errors: +// DecodeUpdateGroupsioServiceResponse may return the following errors: // - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest -// - "Conflict" (type *mailinglist.ConflictError): http.StatusConflict // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError // - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeDeleteGrpsioServiceResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeUpdateGroupsioServiceResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -716,105 +614,102 @@ func DecodeDeleteGrpsioServiceResponse(decoder func(*http.Response) goahttp.Deco defer resp.Body.Close() } switch resp.StatusCode { - case http.StatusNoContent: - return nil, nil - case http.StatusBadRequest: + case http.StatusOK: var ( - body DeleteGrpsioServiceBadRequestResponseBody + body UpdateGroupsioServiceResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-service", err) } - err = ValidateDeleteGrpsioServiceBadRequestResponseBody(&body) + err = ValidateUpdateGroupsioServiceResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-service", err) } - return nil, NewDeleteGrpsioServiceBadRequest(&body) - case http.StatusConflict: + res := NewUpdateGroupsioServiceGroupsioServiceOK(&body) + return res, nil + case http.StatusBadRequest: var ( - body DeleteGrpsioServiceConflictResponseBody + body UpdateGroupsioServiceBadRequestResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-service", err) } - err = ValidateDeleteGrpsioServiceConflictResponseBody(&body) + err = ValidateUpdateGroupsioServiceBadRequestResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-service", err) } - return nil, NewDeleteGrpsioServiceConflict(&body) + return nil, NewUpdateGroupsioServiceBadRequest(&body) case http.StatusInternalServerError: var ( - body DeleteGrpsioServiceInternalServerErrorResponseBody + body UpdateGroupsioServiceInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-service", err) } - err = ValidateDeleteGrpsioServiceInternalServerErrorResponseBody(&body) + err = ValidateUpdateGroupsioServiceInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-service", err) } - return nil, NewDeleteGrpsioServiceInternalServerError(&body) + return nil, NewUpdateGroupsioServiceInternalServerError(&body) case http.StatusNotFound: var ( - body DeleteGrpsioServiceNotFoundResponseBody + body UpdateGroupsioServiceNotFoundResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-service", err) } - err = ValidateDeleteGrpsioServiceNotFoundResponseBody(&body) + err = ValidateUpdateGroupsioServiceNotFoundResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-service", err) } - return nil, NewDeleteGrpsioServiceNotFound(&body) + return nil, NewUpdateGroupsioServiceNotFound(&body) case http.StatusServiceUnavailable: var ( - body DeleteGrpsioServiceServiceUnavailableResponseBody + body UpdateGroupsioServiceServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-service", err) + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-service", err) } - err = ValidateDeleteGrpsioServiceServiceUnavailableResponseBody(&body) + err = ValidateUpdateGroupsioServiceServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-service", err) + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-service", err) } - return nil, NewDeleteGrpsioServiceServiceUnavailable(&body) + return nil, NewUpdateGroupsioServiceServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "delete-grpsio-service", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "update-groupsio-service", resp.StatusCode, string(body)) } } } -// BuildGetGrpsioServiceSettingsRequest instantiates a HTTP request object with +// BuildDeleteGroupsioServiceRequest instantiates a HTTP request object with // method and path set to call the "mailing-list" service -// "get-grpsio-service-settings" endpoint -func (c *Client) BuildGetGrpsioServiceSettingsRequest(ctx context.Context, v any) (*http.Request, error) { +// "delete-groupsio-service" endpoint +func (c *Client) BuildDeleteGroupsioServiceRequest(ctx context.Context, v any) (*http.Request, error) { var ( - uid string + serviceID string ) { - p, ok := v.(*mailinglist.GetGrpsioServiceSettingsPayload) + p, ok := v.(*mailinglist.DeleteGroupsioServicePayload) if !ok { - return nil, goahttp.ErrInvalidType("mailing-list", "get-grpsio-service-settings", "*mailinglist.GetGrpsioServiceSettingsPayload", v) - } - if p.UID != nil { - uid = *p.UID + return nil, goahttp.ErrInvalidType("mailing-list", "delete-groupsio-service", "*mailinglist.DeleteGroupsioServicePayload", v) } + serviceID = p.ServiceID } - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GetGrpsioServiceSettingsMailingListPath(uid)} - req, err := http.NewRequest("GET", u.String(), nil) + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: DeleteGroupsioServiceMailingListPath(serviceID)} + req, err := http.NewRequest("DELETE", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "get-grpsio-service-settings", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "delete-groupsio-service", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -823,13 +718,13 @@ func (c *Client) BuildGetGrpsioServiceSettingsRequest(ctx context.Context, v any return req, nil } -// EncodeGetGrpsioServiceSettingsRequest returns an encoder for requests sent -// to the mailing-list get-grpsio-service-settings server. -func EncodeGetGrpsioServiceSettingsRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeDeleteGroupsioServiceRequest returns an encoder for requests sent to +// the mailing-list delete-groupsio-service server. +func EncodeDeleteGroupsioServiceRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.GetGrpsioServiceSettingsPayload) + p, ok := v.(*mailinglist.DeleteGroupsioServicePayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "get-grpsio-service-settings", "*mailinglist.GetGrpsioServiceSettingsPayload", v) + return goahttp.ErrInvalidType("mailing-list", "delete-groupsio-service", "*mailinglist.DeleteGroupsioServicePayload", v) } if p.BearerToken != nil { head := *p.BearerToken @@ -839,26 +734,19 @@ func EncodeGetGrpsioServiceSettingsRequest(encoder func(*http.Request) goahttp.E req.Header.Set("Authorization", head) } } - values := req.URL.Query() - if p.Version != nil { - values.Add("v", *p.Version) - } - req.URL.RawQuery = values.Encode() return nil } } -// DecodeGetGrpsioServiceSettingsResponse returns a decoder for responses -// returned by the mailing-list get-grpsio-service-settings endpoint. -// restoreBody controls whether the response body should be restored after -// having been read. -// DecodeGetGrpsioServiceSettingsResponse may return the following errors: -// - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest +// DecodeDeleteGroupsioServiceResponse returns a decoder for responses returned +// by the mailing-list delete-groupsio-service endpoint. restoreBody controls +// whether the response body should be restored after having been read. +// DecodeDeleteGroupsioServiceResponse may return the following errors: // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError // - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeGetGrpsioServiceSettingsResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeDeleteGroupsioServiceResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -873,109 +761,65 @@ func DecodeGetGrpsioServiceSettingsResponse(decoder func(*http.Response) goahttp defer resp.Body.Close() } switch resp.StatusCode { - case http.StatusOK: - var ( - body GetGrpsioServiceSettingsResponseBody - err error - ) - err = decoder(resp).Decode(&body) - if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-service-settings", err) - } - err = ValidateGetGrpsioServiceSettingsResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-service-settings", err) - } - var ( - etag *string - ) - etagRaw := resp.Header.Get("Etag") - if etagRaw != "" { - etag = &etagRaw - } - res := NewGetGrpsioServiceSettingsResultOK(&body, etag) - return res, nil - case http.StatusBadRequest: - var ( - body GetGrpsioServiceSettingsBadRequestResponseBody - err error - ) - err = decoder(resp).Decode(&body) - if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-service-settings", err) - } - err = ValidateGetGrpsioServiceSettingsBadRequestResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-service-settings", err) - } - return nil, NewGetGrpsioServiceSettingsBadRequest(&body) + case http.StatusNoContent: + return nil, nil case http.StatusInternalServerError: var ( - body GetGrpsioServiceSettingsInternalServerErrorResponseBody + body DeleteGroupsioServiceInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-service-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "delete-groupsio-service", err) } - err = ValidateGetGrpsioServiceSettingsInternalServerErrorResponseBody(&body) + err = ValidateDeleteGroupsioServiceInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-service-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "delete-groupsio-service", err) } - return nil, NewGetGrpsioServiceSettingsInternalServerError(&body) + return nil, NewDeleteGroupsioServiceInternalServerError(&body) case http.StatusNotFound: var ( - body GetGrpsioServiceSettingsNotFoundResponseBody + body DeleteGroupsioServiceNotFoundResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-service-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "delete-groupsio-service", err) } - err = ValidateGetGrpsioServiceSettingsNotFoundResponseBody(&body) + err = ValidateDeleteGroupsioServiceNotFoundResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-service-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "delete-groupsio-service", err) } - return nil, NewGetGrpsioServiceSettingsNotFound(&body) + return nil, NewDeleteGroupsioServiceNotFound(&body) case http.StatusServiceUnavailable: var ( - body GetGrpsioServiceSettingsServiceUnavailableResponseBody + body DeleteGroupsioServiceServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-service-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "delete-groupsio-service", err) } - err = ValidateGetGrpsioServiceSettingsServiceUnavailableResponseBody(&body) + err = ValidateDeleteGroupsioServiceServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-service-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "delete-groupsio-service", err) } - return nil, NewGetGrpsioServiceSettingsServiceUnavailable(&body) + return nil, NewDeleteGroupsioServiceServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "get-grpsio-service-settings", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "delete-groupsio-service", resp.StatusCode, string(body)) } } } -// BuildUpdateGrpsioServiceSettingsRequest instantiates a HTTP request object +// BuildGetGroupsioServiceProjectsRequest instantiates a HTTP request object // with method and path set to call the "mailing-list" service -// "update-grpsio-service-settings" endpoint -func (c *Client) BuildUpdateGrpsioServiceSettingsRequest(ctx context.Context, v any) (*http.Request, error) { - var ( - uid string - ) - { - p, ok := v.(*mailinglist.UpdateGrpsioServiceSettingsPayload) - if !ok { - return nil, goahttp.ErrInvalidType("mailing-list", "update-grpsio-service-settings", "*mailinglist.UpdateGrpsioServiceSettingsPayload", v) - } - uid = p.UID - } - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: UpdateGrpsioServiceSettingsMailingListPath(uid)} - req, err := http.NewRequest("PUT", u.String(), nil) +// "get-groupsio-service-projects" endpoint +func (c *Client) BuildGetGroupsioServiceProjectsRequest(ctx context.Context, v any) (*http.Request, error) { + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GetGroupsioServiceProjectsMailingListPath()} + req, err := http.NewRequest("GET", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "update-grpsio-service-settings", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "get-groupsio-service-projects", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -984,13 +828,13 @@ func (c *Client) BuildUpdateGrpsioServiceSettingsRequest(ctx context.Context, v return req, nil } -// EncodeUpdateGrpsioServiceSettingsRequest returns an encoder for requests -// sent to the mailing-list update-grpsio-service-settings server. -func EncodeUpdateGrpsioServiceSettingsRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeGetGroupsioServiceProjectsRequest returns an encoder for requests sent +// to the mailing-list get-groupsio-service-projects server. +func EncodeGetGroupsioServiceProjectsRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.UpdateGrpsioServiceSettingsPayload) + p, ok := v.(*mailinglist.GetGroupsioServiceProjectsPayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "update-grpsio-service-settings", "*mailinglist.UpdateGrpsioServiceSettingsPayload", v) + return goahttp.ErrInvalidType("mailing-list", "get-groupsio-service-projects", "*mailinglist.GetGroupsioServiceProjectsPayload", v) } if p.BearerToken != nil { head := *p.BearerToken @@ -1000,33 +844,19 @@ func EncodeUpdateGrpsioServiceSettingsRequest(encoder func(*http.Request) goahtt req.Header.Set("Authorization", head) } } - if p.IfMatch != nil { - head := *p.IfMatch - req.Header.Set("If-Match", head) - } - values := req.URL.Query() - values.Add("v", p.Version) - req.URL.RawQuery = values.Encode() - body := NewUpdateGrpsioServiceSettingsRequestBody(p) - if err := encoder(req).Encode(&body); err != nil { - return goahttp.ErrEncodingError("mailing-list", "update-grpsio-service-settings", err) - } return nil } } -// DecodeUpdateGrpsioServiceSettingsResponse returns a decoder for responses -// returned by the mailing-list update-grpsio-service-settings endpoint. +// DecodeGetGroupsioServiceProjectsResponse returns a decoder for responses +// returned by the mailing-list get-groupsio-service-projects endpoint. // restoreBody controls whether the response body should be restored after // having been read. -// DecodeUpdateGrpsioServiceSettingsResponse may return the following errors: -// - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest -// - "Conflict" (type *mailinglist.ConflictError): http.StatusConflict +// DecodeGetGroupsioServiceProjectsResponse may return the following errors: // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError -// - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeUpdateGrpsioServiceSettingsResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeGetGroupsioServiceProjectsResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -1043,104 +873,58 @@ func DecodeUpdateGrpsioServiceSettingsResponse(decoder func(*http.Response) goah switch resp.StatusCode { case http.StatusOK: var ( - body UpdateGrpsioServiceSettingsResponseBody + body GetGroupsioServiceProjectsResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-service-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-service-projects", err) } - err = ValidateUpdateGrpsioServiceSettingsResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-service-settings", err) - } - res := NewUpdateGrpsioServiceSettingsGrpsIoServiceSettingsOK(&body) + res := NewGetGroupsioServiceProjectsGroupsioProjectsResponseOK(&body) return res, nil - case http.StatusBadRequest: - var ( - body UpdateGrpsioServiceSettingsBadRequestResponseBody - err error - ) - err = decoder(resp).Decode(&body) - if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-service-settings", err) - } - err = ValidateUpdateGrpsioServiceSettingsBadRequestResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-service-settings", err) - } - return nil, NewUpdateGrpsioServiceSettingsBadRequest(&body) - case http.StatusConflict: - var ( - body UpdateGrpsioServiceSettingsConflictResponseBody - err error - ) - err = decoder(resp).Decode(&body) - if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-service-settings", err) - } - err = ValidateUpdateGrpsioServiceSettingsConflictResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-service-settings", err) - } - return nil, NewUpdateGrpsioServiceSettingsConflict(&body) case http.StatusInternalServerError: var ( - body UpdateGrpsioServiceSettingsInternalServerErrorResponseBody - err error - ) - err = decoder(resp).Decode(&body) - if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-service-settings", err) - } - err = ValidateUpdateGrpsioServiceSettingsInternalServerErrorResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-service-settings", err) - } - return nil, NewUpdateGrpsioServiceSettingsInternalServerError(&body) - case http.StatusNotFound: - var ( - body UpdateGrpsioServiceSettingsNotFoundResponseBody + body GetGroupsioServiceProjectsInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-service-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-service-projects", err) } - err = ValidateUpdateGrpsioServiceSettingsNotFoundResponseBody(&body) + err = ValidateGetGroupsioServiceProjectsInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-service-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-service-projects", err) } - return nil, NewUpdateGrpsioServiceSettingsNotFound(&body) + return nil, NewGetGroupsioServiceProjectsInternalServerError(&body) case http.StatusServiceUnavailable: var ( - body UpdateGrpsioServiceSettingsServiceUnavailableResponseBody + body GetGroupsioServiceProjectsServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-service-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-service-projects", err) } - err = ValidateUpdateGrpsioServiceSettingsServiceUnavailableResponseBody(&body) + err = ValidateGetGroupsioServiceProjectsServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-service-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-service-projects", err) } - return nil, NewUpdateGrpsioServiceSettingsServiceUnavailable(&body) + return nil, NewGetGroupsioServiceProjectsServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "update-grpsio-service-settings", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "get-groupsio-service-projects", resp.StatusCode, string(body)) } } } -// BuildCreateGrpsioMailingListRequest instantiates a HTTP request object with -// method and path set to call the "mailing-list" service -// "create-grpsio-mailing-list" endpoint -func (c *Client) BuildCreateGrpsioMailingListRequest(ctx context.Context, v any) (*http.Request, error) { - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: CreateGrpsioMailingListMailingListPath()} - req, err := http.NewRequest("POST", u.String(), nil) +// BuildFindParentGroupsioServiceRequest instantiates a HTTP request object +// with method and path set to call the "mailing-list" service +// "find-parent-groupsio-service" endpoint +func (c *Client) BuildFindParentGroupsioServiceRequest(ctx context.Context, v any) (*http.Request, error) { + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: FindParentGroupsioServiceMailingListPath()} + req, err := http.NewRequest("GET", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "create-grpsio-mailing-list", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "find-parent-groupsio-service", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -1149,13 +933,13 @@ func (c *Client) BuildCreateGrpsioMailingListRequest(ctx context.Context, v any) return req, nil } -// EncodeCreateGrpsioMailingListRequest returns an encoder for requests sent to -// the mailing-list create-grpsio-mailing-list server. -func EncodeCreateGrpsioMailingListRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeFindParentGroupsioServiceRequest returns an encoder for requests sent +// to the mailing-list find-parent-groupsio-service server. +func EncodeFindParentGroupsioServiceRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.CreateGrpsioMailingListPayload) + p, ok := v.(*mailinglist.FindParentGroupsioServicePayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "create-grpsio-mailing-list", "*mailinglist.CreateGrpsioMailingListPayload", v) + return goahttp.ErrInvalidType("mailing-list", "find-parent-groupsio-service", "*mailinglist.FindParentGroupsioServicePayload", v) } if p.BearerToken != nil { head := *p.BearerToken @@ -1166,28 +950,23 @@ func EncodeCreateGrpsioMailingListRequest(encoder func(*http.Request) goahttp.En } } values := req.URL.Query() - values.Add("v", p.Version) + values.Add("project_uid", p.ProjectUID) req.URL.RawQuery = values.Encode() - body := NewCreateGrpsioMailingListRequestBody(p) - if err := encoder(req).Encode(&body); err != nil { - return goahttp.ErrEncodingError("mailing-list", "create-grpsio-mailing-list", err) - } return nil } } -// DecodeCreateGrpsioMailingListResponse returns a decoder for responses -// returned by the mailing-list create-grpsio-mailing-list endpoint. +// DecodeFindParentGroupsioServiceResponse returns a decoder for responses +// returned by the mailing-list find-parent-groupsio-service endpoint. // restoreBody controls whether the response body should be restored after // having been read. -// DecodeCreateGrpsioMailingListResponse may return the following errors: +// DecodeFindParentGroupsioServiceResponse may return the following errors: // - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest -// - "Conflict" (type *mailinglist.ConflictError): http.StatusConflict // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError // - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeCreateGrpsioMailingListResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeFindParentGroupsioServiceResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -1202,118 +981,92 @@ func DecodeCreateGrpsioMailingListResponse(decoder func(*http.Response) goahttp. defer resp.Body.Close() } switch resp.StatusCode { - case http.StatusCreated: + case http.StatusOK: var ( - body CreateGrpsioMailingListResponseBody + body FindParentGroupsioServiceResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "find-parent-groupsio-service", err) } - err = ValidateCreateGrpsioMailingListResponseBody(&body) + err = ValidateFindParentGroupsioServiceResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "find-parent-groupsio-service", err) } - res := NewCreateGrpsioMailingListGrpsIoMailingListFullCreated(&body) + res := NewFindParentGroupsioServiceGroupsioServiceOK(&body) return res, nil case http.StatusBadRequest: var ( - body CreateGrpsioMailingListBadRequestResponseBody - err error - ) - err = decoder(resp).Decode(&body) - if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-mailing-list", err) - } - err = ValidateCreateGrpsioMailingListBadRequestResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-mailing-list", err) - } - return nil, NewCreateGrpsioMailingListBadRequest(&body) - case http.StatusConflict: - var ( - body CreateGrpsioMailingListConflictResponseBody + body FindParentGroupsioServiceBadRequestResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "find-parent-groupsio-service", err) } - err = ValidateCreateGrpsioMailingListConflictResponseBody(&body) + err = ValidateFindParentGroupsioServiceBadRequestResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "find-parent-groupsio-service", err) } - return nil, NewCreateGrpsioMailingListConflict(&body) + return nil, NewFindParentGroupsioServiceBadRequest(&body) case http.StatusInternalServerError: var ( - body CreateGrpsioMailingListInternalServerErrorResponseBody + body FindParentGroupsioServiceInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "find-parent-groupsio-service", err) } - err = ValidateCreateGrpsioMailingListInternalServerErrorResponseBody(&body) + err = ValidateFindParentGroupsioServiceInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "find-parent-groupsio-service", err) } - return nil, NewCreateGrpsioMailingListInternalServerError(&body) + return nil, NewFindParentGroupsioServiceInternalServerError(&body) case http.StatusNotFound: var ( - body CreateGrpsioMailingListNotFoundResponseBody + body FindParentGroupsioServiceNotFoundResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "find-parent-groupsio-service", err) } - err = ValidateCreateGrpsioMailingListNotFoundResponseBody(&body) + err = ValidateFindParentGroupsioServiceNotFoundResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "find-parent-groupsio-service", err) } - return nil, NewCreateGrpsioMailingListNotFound(&body) + return nil, NewFindParentGroupsioServiceNotFound(&body) case http.StatusServiceUnavailable: var ( - body CreateGrpsioMailingListServiceUnavailableResponseBody + body FindParentGroupsioServiceServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "find-parent-groupsio-service", err) } - err = ValidateCreateGrpsioMailingListServiceUnavailableResponseBody(&body) + err = ValidateFindParentGroupsioServiceServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "find-parent-groupsio-service", err) } - return nil, NewCreateGrpsioMailingListServiceUnavailable(&body) + return nil, NewFindParentGroupsioServiceServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "create-grpsio-mailing-list", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "find-parent-groupsio-service", resp.StatusCode, string(body)) } } } -// BuildGetGrpsioMailingListRequest instantiates a HTTP request object with +// BuildListGroupsioSubgroupsRequest instantiates a HTTP request object with // method and path set to call the "mailing-list" service -// "get-grpsio-mailing-list" endpoint -func (c *Client) BuildGetGrpsioMailingListRequest(ctx context.Context, v any) (*http.Request, error) { - var ( - uid string - ) - { - p, ok := v.(*mailinglist.GetGrpsioMailingListPayload) - if !ok { - return nil, goahttp.ErrInvalidType("mailing-list", "get-grpsio-mailing-list", "*mailinglist.GetGrpsioMailingListPayload", v) - } - if p.UID != nil { - uid = *p.UID - } - } - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GetGrpsioMailingListMailingListPath(uid)} +// "list-groupsio-subgroups" endpoint +func (c *Client) BuildListGroupsioSubgroupsRequest(ctx context.Context, v any) (*http.Request, error) { + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: ListGroupsioSubgroupsMailingListPath()} req, err := http.NewRequest("GET", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "get-grpsio-mailing-list", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "list-groupsio-subgroups", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -1322,16 +1075,16 @@ func (c *Client) BuildGetGrpsioMailingListRequest(ctx context.Context, v any) (* return req, nil } -// EncodeGetGrpsioMailingListRequest returns an encoder for requests sent to -// the mailing-list get-grpsio-mailing-list server. -func EncodeGetGrpsioMailingListRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeListGroupsioSubgroupsRequest returns an encoder for requests sent to +// the mailing-list list-groupsio-subgroups server. +func EncodeListGroupsioSubgroupsRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.GetGrpsioMailingListPayload) + p, ok := v.(*mailinglist.ListGroupsioSubgroupsPayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "get-grpsio-mailing-list", "*mailinglist.GetGrpsioMailingListPayload", v) + return goahttp.ErrInvalidType("mailing-list", "list-groupsio-subgroups", "*mailinglist.ListGroupsioSubgroupsPayload", v) } - { - head := p.BearerToken + if p.BearerToken != nil { + head := *p.BearerToken if !strings.Contains(head, " ") { req.Header.Set("Authorization", "Bearer "+head) } else { @@ -1339,22 +1092,26 @@ func EncodeGetGrpsioMailingListRequest(encoder func(*http.Request) goahttp.Encod } } values := req.URL.Query() - values.Add("v", p.Version) + if p.ProjectUID != nil { + values.Add("project_uid", *p.ProjectUID) + } + if p.CommitteeUID != nil { + values.Add("committee_uid", *p.CommitteeUID) + } req.URL.RawQuery = values.Encode() return nil } } -// DecodeGetGrpsioMailingListResponse returns a decoder for responses returned -// by the mailing-list get-grpsio-mailing-list endpoint. restoreBody controls +// DecodeListGroupsioSubgroupsResponse returns a decoder for responses returned +// by the mailing-list list-groupsio-subgroups endpoint. restoreBody controls // whether the response body should be restored after having been read. -// DecodeGetGrpsioMailingListResponse may return the following errors: +// DecodeListGroupsioSubgroupsResponse may return the following errors: // - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError -// - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeGetGrpsioMailingListResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeListGroupsioSubgroupsResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -1371,109 +1128,76 @@ func DecodeGetGrpsioMailingListResponse(decoder func(*http.Response) goahttp.Dec switch resp.StatusCode { case http.StatusOK: var ( - body GetGrpsioMailingListResponseBody + body ListGroupsioSubgroupsResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "list-groupsio-subgroups", err) } - err = ValidateGetGrpsioMailingListResponseBody(&body) + err = ValidateListGroupsioSubgroupsResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list", err) - } - var ( - etag *string - ) - etagRaw := resp.Header.Get("Etag") - if etagRaw != "" { - etag = &etagRaw + return nil, goahttp.ErrValidationError("mailing-list", "list-groupsio-subgroups", err) } - res := NewGetGrpsioMailingListResultOK(&body, etag) + res := NewListGroupsioSubgroupsGroupsioSubgroupListOK(&body) return res, nil case http.StatusBadRequest: var ( - body GetGrpsioMailingListBadRequestResponseBody + body ListGroupsioSubgroupsBadRequestResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "list-groupsio-subgroups", err) } - err = ValidateGetGrpsioMailingListBadRequestResponseBody(&body) + err = ValidateListGroupsioSubgroupsBadRequestResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "list-groupsio-subgroups", err) } - return nil, NewGetGrpsioMailingListBadRequest(&body) + return nil, NewListGroupsioSubgroupsBadRequest(&body) case http.StatusInternalServerError: var ( - body GetGrpsioMailingListInternalServerErrorResponseBody - err error - ) - err = decoder(resp).Decode(&body) - if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list", err) - } - err = ValidateGetGrpsioMailingListInternalServerErrorResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list", err) - } - return nil, NewGetGrpsioMailingListInternalServerError(&body) - case http.StatusNotFound: - var ( - body GetGrpsioMailingListNotFoundResponseBody + body ListGroupsioSubgroupsInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "list-groupsio-subgroups", err) } - err = ValidateGetGrpsioMailingListNotFoundResponseBody(&body) + err = ValidateListGroupsioSubgroupsInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "list-groupsio-subgroups", err) } - return nil, NewGetGrpsioMailingListNotFound(&body) + return nil, NewListGroupsioSubgroupsInternalServerError(&body) case http.StatusServiceUnavailable: var ( - body GetGrpsioMailingListServiceUnavailableResponseBody + body ListGroupsioSubgroupsServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "list-groupsio-subgroups", err) } - err = ValidateGetGrpsioMailingListServiceUnavailableResponseBody(&body) + err = ValidateListGroupsioSubgroupsServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "list-groupsio-subgroups", err) } - return nil, NewGetGrpsioMailingListServiceUnavailable(&body) + return nil, NewListGroupsioSubgroupsServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "get-grpsio-mailing-list", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "list-groupsio-subgroups", resp.StatusCode, string(body)) } } } -// BuildUpdateGrpsioMailingListRequest instantiates a HTTP request object with +// BuildCreateGroupsioSubgroupRequest instantiates a HTTP request object with // method and path set to call the "mailing-list" service -// "update-grpsio-mailing-list" endpoint -func (c *Client) BuildUpdateGrpsioMailingListRequest(ctx context.Context, v any) (*http.Request, error) { - var ( - uid string - ) - { - p, ok := v.(*mailinglist.UpdateGrpsioMailingListPayload) - if !ok { - return nil, goahttp.ErrInvalidType("mailing-list", "update-grpsio-mailing-list", "*mailinglist.UpdateGrpsioMailingListPayload", v) - } - if p.UID != nil { - uid = *p.UID - } - } - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: UpdateGrpsioMailingListMailingListPath(uid)} - req, err := http.NewRequest("PUT", u.String(), nil) +// "create-groupsio-subgroup" endpoint +func (c *Client) BuildCreateGroupsioSubgroupRequest(ctx context.Context, v any) (*http.Request, error) { + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: CreateGroupsioSubgroupMailingListPath()} + req, err := http.NewRequest("POST", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "update-grpsio-mailing-list", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "create-groupsio-subgroup", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -1482,13 +1206,13 @@ func (c *Client) BuildUpdateGrpsioMailingListRequest(ctx context.Context, v any) return req, nil } -// EncodeUpdateGrpsioMailingListRequest returns an encoder for requests sent to -// the mailing-list update-grpsio-mailing-list server. -func EncodeUpdateGrpsioMailingListRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeCreateGroupsioSubgroupRequest returns an encoder for requests sent to +// the mailing-list create-groupsio-subgroup server. +func EncodeCreateGroupsioSubgroupRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.UpdateGrpsioMailingListPayload) + p, ok := v.(*mailinglist.CreateGroupsioSubgroupPayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "update-grpsio-mailing-list", "*mailinglist.UpdateGrpsioMailingListPayload", v) + return goahttp.ErrInvalidType("mailing-list", "create-groupsio-subgroup", "*mailinglist.CreateGroupsioSubgroupPayload", v) } if p.BearerToken != nil { head := *p.BearerToken @@ -1498,33 +1222,24 @@ func EncodeUpdateGrpsioMailingListRequest(encoder func(*http.Request) goahttp.En req.Header.Set("Authorization", head) } } - if p.IfMatch != nil { - head := *p.IfMatch - req.Header.Set("If-Match", head) - } - values := req.URL.Query() - values.Add("v", p.Version) - req.URL.RawQuery = values.Encode() - body := NewUpdateGrpsioMailingListRequestBody(p) + body := NewCreateGroupsioSubgroupRequestBody(p) if err := encoder(req).Encode(&body); err != nil { - return goahttp.ErrEncodingError("mailing-list", "update-grpsio-mailing-list", err) + return goahttp.ErrEncodingError("mailing-list", "create-groupsio-subgroup", err) } return nil } } -// DecodeUpdateGrpsioMailingListResponse returns a decoder for responses -// returned by the mailing-list update-grpsio-mailing-list endpoint. -// restoreBody controls whether the response body should be restored after -// having been read. -// DecodeUpdateGrpsioMailingListResponse may return the following errors: +// DecodeCreateGroupsioSubgroupResponse returns a decoder for responses +// returned by the mailing-list create-groupsio-subgroup endpoint. restoreBody +// controls whether the response body should be restored after having been read. +// DecodeCreateGroupsioSubgroupResponse may return the following errors: // - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest // - "Conflict" (type *mailinglist.ConflictError): http.StatusConflict // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError -// - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeUpdateGrpsioMailingListResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeCreateGroupsioSubgroupResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -1539,118 +1254,102 @@ func DecodeUpdateGrpsioMailingListResponse(decoder func(*http.Response) goahttp. defer resp.Body.Close() } switch resp.StatusCode { - case http.StatusOK: + case http.StatusCreated: var ( - body UpdateGrpsioMailingListResponseBody + body CreateGroupsioSubgroupResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "create-groupsio-subgroup", err) } - err = ValidateUpdateGrpsioMailingListResponseBody(&body) + err = ValidateCreateGroupsioSubgroupResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "create-groupsio-subgroup", err) } - res := NewUpdateGrpsioMailingListGrpsIoMailingListWithReadonlyAttributesOK(&body) + res := NewCreateGroupsioSubgroupGroupsioSubgroupCreated(&body) return res, nil case http.StatusBadRequest: var ( - body UpdateGrpsioMailingListBadRequestResponseBody + body CreateGroupsioSubgroupBadRequestResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "create-groupsio-subgroup", err) } - err = ValidateUpdateGrpsioMailingListBadRequestResponseBody(&body) + err = ValidateCreateGroupsioSubgroupBadRequestResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "create-groupsio-subgroup", err) } - return nil, NewUpdateGrpsioMailingListBadRequest(&body) + return nil, NewCreateGroupsioSubgroupBadRequest(&body) case http.StatusConflict: var ( - body UpdateGrpsioMailingListConflictResponseBody + body CreateGroupsioSubgroupConflictResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "create-groupsio-subgroup", err) } - err = ValidateUpdateGrpsioMailingListConflictResponseBody(&body) + err = ValidateCreateGroupsioSubgroupConflictResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "create-groupsio-subgroup", err) } - return nil, NewUpdateGrpsioMailingListConflict(&body) + return nil, NewCreateGroupsioSubgroupConflict(&body) case http.StatusInternalServerError: var ( - body UpdateGrpsioMailingListInternalServerErrorResponseBody - err error - ) - err = decoder(resp).Decode(&body) - if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list", err) - } - err = ValidateUpdateGrpsioMailingListInternalServerErrorResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list", err) - } - return nil, NewUpdateGrpsioMailingListInternalServerError(&body) - case http.StatusNotFound: - var ( - body UpdateGrpsioMailingListNotFoundResponseBody + body CreateGroupsioSubgroupInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "create-groupsio-subgroup", err) } - err = ValidateUpdateGrpsioMailingListNotFoundResponseBody(&body) + err = ValidateCreateGroupsioSubgroupInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "create-groupsio-subgroup", err) } - return nil, NewUpdateGrpsioMailingListNotFound(&body) + return nil, NewCreateGroupsioSubgroupInternalServerError(&body) case http.StatusServiceUnavailable: var ( - body UpdateGrpsioMailingListServiceUnavailableResponseBody + body CreateGroupsioSubgroupServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "create-groupsio-subgroup", err) } - err = ValidateUpdateGrpsioMailingListServiceUnavailableResponseBody(&body) + err = ValidateCreateGroupsioSubgroupServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "create-groupsio-subgroup", err) } - return nil, NewUpdateGrpsioMailingListServiceUnavailable(&body) + return nil, NewCreateGroupsioSubgroupServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "update-grpsio-mailing-list", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "create-groupsio-subgroup", resp.StatusCode, string(body)) } } } -// BuildDeleteGrpsioMailingListRequest instantiates a HTTP request object with +// BuildGetGroupsioSubgroupRequest instantiates a HTTP request object with // method and path set to call the "mailing-list" service -// "delete-grpsio-mailing-list" endpoint -func (c *Client) BuildDeleteGrpsioMailingListRequest(ctx context.Context, v any) (*http.Request, error) { +// "get-groupsio-subgroup" endpoint +func (c *Client) BuildGetGroupsioSubgroupRequest(ctx context.Context, v any) (*http.Request, error) { var ( - uid string + subgroupID string ) { - p, ok := v.(*mailinglist.DeleteGrpsioMailingListPayload) + p, ok := v.(*mailinglist.GetGroupsioSubgroupPayload) if !ok { - return nil, goahttp.ErrInvalidType("mailing-list", "delete-grpsio-mailing-list", "*mailinglist.DeleteGrpsioMailingListPayload", v) - } - if p.UID != nil { - uid = *p.UID + return nil, goahttp.ErrInvalidType("mailing-list", "get-groupsio-subgroup", "*mailinglist.GetGroupsioSubgroupPayload", v) } + subgroupID = p.SubgroupID } - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: DeleteGrpsioMailingListMailingListPath(uid)} - req, err := http.NewRequest("DELETE", u.String(), nil) + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GetGroupsioSubgroupMailingListPath(subgroupID)} + req, err := http.NewRequest("GET", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "delete-grpsio-mailing-list", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "get-groupsio-subgroup", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -1659,13 +1358,13 @@ func (c *Client) BuildDeleteGrpsioMailingListRequest(ctx context.Context, v any) return req, nil } -// EncodeDeleteGrpsioMailingListRequest returns an encoder for requests sent to -// the mailing-list delete-grpsio-mailing-list server. -func EncodeDeleteGrpsioMailingListRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeGetGroupsioSubgroupRequest returns an encoder for requests sent to the +// mailing-list get-groupsio-subgroup server. +func EncodeGetGroupsioSubgroupRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.DeleteGrpsioMailingListPayload) + p, ok := v.(*mailinglist.GetGroupsioSubgroupPayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "delete-grpsio-mailing-list", "*mailinglist.DeleteGrpsioMailingListPayload", v) + return goahttp.ErrInvalidType("mailing-list", "get-groupsio-subgroup", "*mailinglist.GetGroupsioSubgroupPayload", v) } if p.BearerToken != nil { head := *p.BearerToken @@ -1675,31 +1374,19 @@ func EncodeDeleteGrpsioMailingListRequest(encoder func(*http.Request) goahttp.En req.Header.Set("Authorization", head) } } - if p.IfMatch != nil { - head := *p.IfMatch - req.Header.Set("If-Match", head) - } - values := req.URL.Query() - if p.Version != nil { - values.Add("v", *p.Version) - } - req.URL.RawQuery = values.Encode() return nil } } -// DecodeDeleteGrpsioMailingListResponse returns a decoder for responses -// returned by the mailing-list delete-grpsio-mailing-list endpoint. -// restoreBody controls whether the response body should be restored after -// having been read. -// DecodeDeleteGrpsioMailingListResponse may return the following errors: -// - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest -// - "Conflict" (type *mailinglist.ConflictError): http.StatusConflict +// DecodeGetGroupsioSubgroupResponse returns a decoder for responses returned +// by the mailing-list get-groupsio-subgroup endpoint. restoreBody controls +// whether the response body should be restored after having been read. +// DecodeGetGroupsioSubgroupResponse may return the following errors: // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError // - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeDeleteGrpsioMailingListResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeGetGroupsioSubgroupResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -1714,105 +1401,240 @@ func DecodeDeleteGrpsioMailingListResponse(decoder func(*http.Response) goahttp. defer resp.Body.Close() } switch resp.StatusCode { - case http.StatusNoContent: - return nil, nil - case http.StatusBadRequest: + case http.StatusOK: var ( - body DeleteGrpsioMailingListBadRequestResponseBody + body GetGroupsioSubgroupResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-subgroup", err) } - err = ValidateDeleteGrpsioMailingListBadRequestResponseBody(&body) + err = ValidateGetGroupsioSubgroupResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-subgroup", err) } - return nil, NewDeleteGrpsioMailingListBadRequest(&body) - case http.StatusConflict: + res := NewGetGroupsioSubgroupGroupsioSubgroupOK(&body) + return res, nil + case http.StatusInternalServerError: + var ( + body GetGroupsioSubgroupInternalServerErrorResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-subgroup", err) + } + err = ValidateGetGroupsioSubgroupInternalServerErrorResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-subgroup", err) + } + return nil, NewGetGroupsioSubgroupInternalServerError(&body) + case http.StatusNotFound: + var ( + body GetGroupsioSubgroupNotFoundResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-subgroup", err) + } + err = ValidateGetGroupsioSubgroupNotFoundResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-subgroup", err) + } + return nil, NewGetGroupsioSubgroupNotFound(&body) + case http.StatusServiceUnavailable: + var ( + body GetGroupsioSubgroupServiceUnavailableResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-subgroup", err) + } + err = ValidateGetGroupsioSubgroupServiceUnavailableResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-subgroup", err) + } + return nil, NewGetGroupsioSubgroupServiceUnavailable(&body) + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("mailing-list", "get-groupsio-subgroup", resp.StatusCode, string(body)) + } + } +} + +// BuildUpdateGroupsioSubgroupRequest instantiates a HTTP request object with +// method and path set to call the "mailing-list" service +// "update-groupsio-subgroup" endpoint +func (c *Client) BuildUpdateGroupsioSubgroupRequest(ctx context.Context, v any) (*http.Request, error) { + var ( + subgroupID string + ) + { + p, ok := v.(*mailinglist.UpdateGroupsioSubgroupPayload) + if !ok { + return nil, goahttp.ErrInvalidType("mailing-list", "update-groupsio-subgroup", "*mailinglist.UpdateGroupsioSubgroupPayload", v) + } + subgroupID = p.SubgroupID + } + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: UpdateGroupsioSubgroupMailingListPath(subgroupID)} + req, err := http.NewRequest("PUT", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("mailing-list", "update-groupsio-subgroup", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeUpdateGroupsioSubgroupRequest returns an encoder for requests sent to +// the mailing-list update-groupsio-subgroup server. +func EncodeUpdateGroupsioSubgroupRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*mailinglist.UpdateGroupsioSubgroupPayload) + if !ok { + return goahttp.ErrInvalidType("mailing-list", "update-groupsio-subgroup", "*mailinglist.UpdateGroupsioSubgroupPayload", v) + } + if p.BearerToken != nil { + head := *p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } + } + body := NewUpdateGroupsioSubgroupRequestBody(p) + if err := encoder(req).Encode(&body); err != nil { + return goahttp.ErrEncodingError("mailing-list", "update-groupsio-subgroup", err) + } + return nil + } +} + +// DecodeUpdateGroupsioSubgroupResponse returns a decoder for responses +// returned by the mailing-list update-groupsio-subgroup endpoint. restoreBody +// controls whether the response body should be restored after having been read. +// DecodeUpdateGroupsioSubgroupResponse may return the following errors: +// - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest +// - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError +// - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound +// - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable +// - error: internal error +func DecodeUpdateGroupsioSubgroupResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusOK: var ( - body DeleteGrpsioMailingListConflictResponseBody + body UpdateGroupsioSubgroupResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-subgroup", err) } - err = ValidateDeleteGrpsioMailingListConflictResponseBody(&body) + err = ValidateUpdateGroupsioSubgroupResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-subgroup", err) } - return nil, NewDeleteGrpsioMailingListConflict(&body) + res := NewUpdateGroupsioSubgroupGroupsioSubgroupOK(&body) + return res, nil + case http.StatusBadRequest: + var ( + body UpdateGroupsioSubgroupBadRequestResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-subgroup", err) + } + err = ValidateUpdateGroupsioSubgroupBadRequestResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-subgroup", err) + } + return nil, NewUpdateGroupsioSubgroupBadRequest(&body) case http.StatusInternalServerError: var ( - body DeleteGrpsioMailingListInternalServerErrorResponseBody + body UpdateGroupsioSubgroupInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-subgroup", err) } - err = ValidateDeleteGrpsioMailingListInternalServerErrorResponseBody(&body) + err = ValidateUpdateGroupsioSubgroupInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-subgroup", err) } - return nil, NewDeleteGrpsioMailingListInternalServerError(&body) + return nil, NewUpdateGroupsioSubgroupInternalServerError(&body) case http.StatusNotFound: var ( - body DeleteGrpsioMailingListNotFoundResponseBody + body UpdateGroupsioSubgroupNotFoundResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-subgroup", err) } - err = ValidateDeleteGrpsioMailingListNotFoundResponseBody(&body) + err = ValidateUpdateGroupsioSubgroupNotFoundResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-subgroup", err) } - return nil, NewDeleteGrpsioMailingListNotFound(&body) + return nil, NewUpdateGroupsioSubgroupNotFound(&body) case http.StatusServiceUnavailable: var ( - body DeleteGrpsioMailingListServiceUnavailableResponseBody + body UpdateGroupsioSubgroupServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-mailing-list", err) + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-subgroup", err) } - err = ValidateDeleteGrpsioMailingListServiceUnavailableResponseBody(&body) + err = ValidateUpdateGroupsioSubgroupServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-mailing-list", err) + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-subgroup", err) } - return nil, NewDeleteGrpsioMailingListServiceUnavailable(&body) + return nil, NewUpdateGroupsioSubgroupServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "delete-grpsio-mailing-list", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "update-groupsio-subgroup", resp.StatusCode, string(body)) } } } -// BuildGetGrpsioMailingListSettingsRequest instantiates a HTTP request object -// with method and path set to call the "mailing-list" service -// "get-grpsio-mailing-list-settings" endpoint -func (c *Client) BuildGetGrpsioMailingListSettingsRequest(ctx context.Context, v any) (*http.Request, error) { +// BuildDeleteGroupsioSubgroupRequest instantiates a HTTP request object with +// method and path set to call the "mailing-list" service +// "delete-groupsio-subgroup" endpoint +func (c *Client) BuildDeleteGroupsioSubgroupRequest(ctx context.Context, v any) (*http.Request, error) { var ( - uid string + subgroupID string ) { - p, ok := v.(*mailinglist.GetGrpsioMailingListSettingsPayload) + p, ok := v.(*mailinglist.DeleteGroupsioSubgroupPayload) if !ok { - return nil, goahttp.ErrInvalidType("mailing-list", "get-grpsio-mailing-list-settings", "*mailinglist.GetGrpsioMailingListSettingsPayload", v) - } - if p.UID != nil { - uid = *p.UID + return nil, goahttp.ErrInvalidType("mailing-list", "delete-groupsio-subgroup", "*mailinglist.DeleteGroupsioSubgroupPayload", v) } + subgroupID = p.SubgroupID } - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GetGrpsioMailingListSettingsMailingListPath(uid)} - req, err := http.NewRequest("GET", u.String(), nil) + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: DeleteGroupsioSubgroupMailingListPath(subgroupID)} + req, err := http.NewRequest("DELETE", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "get-grpsio-mailing-list-settings", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "delete-groupsio-subgroup", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -1821,13 +1643,13 @@ func (c *Client) BuildGetGrpsioMailingListSettingsRequest(ctx context.Context, v return req, nil } -// EncodeGetGrpsioMailingListSettingsRequest returns an encoder for requests -// sent to the mailing-list get-grpsio-mailing-list-settings server. -func EncodeGetGrpsioMailingListSettingsRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeDeleteGroupsioSubgroupRequest returns an encoder for requests sent to +// the mailing-list delete-groupsio-subgroup server. +func EncodeDeleteGroupsioSubgroupRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.GetGrpsioMailingListSettingsPayload) + p, ok := v.(*mailinglist.DeleteGroupsioSubgroupPayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "get-grpsio-mailing-list-settings", "*mailinglist.GetGrpsioMailingListSettingsPayload", v) + return goahttp.ErrInvalidType("mailing-list", "delete-groupsio-subgroup", "*mailinglist.DeleteGroupsioSubgroupPayload", v) } if p.BearerToken != nil { head := *p.BearerToken @@ -1837,26 +1659,19 @@ func EncodeGetGrpsioMailingListSettingsRequest(encoder func(*http.Request) goaht req.Header.Set("Authorization", head) } } - values := req.URL.Query() - if p.Version != nil { - values.Add("v", *p.Version) - } - req.URL.RawQuery = values.Encode() return nil } } -// DecodeGetGrpsioMailingListSettingsResponse returns a decoder for responses -// returned by the mailing-list get-grpsio-mailing-list-settings endpoint. -// restoreBody controls whether the response body should be restored after -// having been read. -// DecodeGetGrpsioMailingListSettingsResponse may return the following errors: -// - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest +// DecodeDeleteGroupsioSubgroupResponse returns a decoder for responses +// returned by the mailing-list delete-groupsio-subgroup endpoint. restoreBody +// controls whether the response body should be restored after having been read. +// DecodeDeleteGroupsioSubgroupResponse may return the following errors: // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError // - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeGetGrpsioMailingListSettingsResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeDeleteGroupsioSubgroupResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -1871,109 +1686,202 @@ func DecodeGetGrpsioMailingListSettingsResponse(decoder func(*http.Response) goa defer resp.Body.Close() } switch resp.StatusCode { - case http.StatusOK: + case http.StatusNoContent: + return nil, nil + case http.StatusInternalServerError: + var ( + body DeleteGroupsioSubgroupInternalServerErrorResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("mailing-list", "delete-groupsio-subgroup", err) + } + err = ValidateDeleteGroupsioSubgroupInternalServerErrorResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("mailing-list", "delete-groupsio-subgroup", err) + } + return nil, NewDeleteGroupsioSubgroupInternalServerError(&body) + case http.StatusNotFound: var ( - body GetGrpsioMailingListSettingsResponseBody + body DeleteGroupsioSubgroupNotFoundResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "delete-groupsio-subgroup", err) } - err = ValidateGetGrpsioMailingListSettingsResponseBody(&body) + err = ValidateDeleteGroupsioSubgroupNotFoundResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "delete-groupsio-subgroup", err) } + return nil, NewDeleteGroupsioSubgroupNotFound(&body) + case http.StatusServiceUnavailable: var ( - etag *string + body DeleteGroupsioSubgroupServiceUnavailableResponseBody + err error ) - etagRaw := resp.Header.Get("Etag") - if etagRaw != "" { - etag = &etagRaw + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("mailing-list", "delete-groupsio-subgroup", err) } - res := NewGetGrpsioMailingListSettingsResultOK(&body, etag) - return res, nil - case http.StatusBadRequest: + err = ValidateDeleteGroupsioSubgroupServiceUnavailableResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("mailing-list", "delete-groupsio-subgroup", err) + } + return nil, NewDeleteGroupsioSubgroupServiceUnavailable(&body) + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("mailing-list", "delete-groupsio-subgroup", resp.StatusCode, string(body)) + } + } +} + +// BuildGetGroupsioSubgroupCountRequest instantiates a HTTP request object with +// method and path set to call the "mailing-list" service +// "get-groupsio-subgroup-count" endpoint +func (c *Client) BuildGetGroupsioSubgroupCountRequest(ctx context.Context, v any) (*http.Request, error) { + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GetGroupsioSubgroupCountMailingListPath()} + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("mailing-list", "get-groupsio-subgroup-count", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeGetGroupsioSubgroupCountRequest returns an encoder for requests sent +// to the mailing-list get-groupsio-subgroup-count server. +func EncodeGetGroupsioSubgroupCountRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*mailinglist.GetGroupsioSubgroupCountPayload) + if !ok { + return goahttp.ErrInvalidType("mailing-list", "get-groupsio-subgroup-count", "*mailinglist.GetGroupsioSubgroupCountPayload", v) + } + if p.BearerToken != nil { + head := *p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } + } + values := req.URL.Query() + values.Add("project_uid", p.ProjectUID) + req.URL.RawQuery = values.Encode() + return nil + } +} + +// DecodeGetGroupsioSubgroupCountResponse returns a decoder for responses +// returned by the mailing-list get-groupsio-subgroup-count endpoint. +// restoreBody controls whether the response body should be restored after +// having been read. +// DecodeGetGroupsioSubgroupCountResponse may return the following errors: +// - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest +// - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError +// - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable +// - error: internal error +func DecodeGetGroupsioSubgroupCountResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusOK: var ( - body GetGrpsioMailingListSettingsBadRequestResponseBody + body GetGroupsioSubgroupCountResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-subgroup-count", err) } - err = ValidateGetGrpsioMailingListSettingsBadRequestResponseBody(&body) + err = ValidateGetGroupsioSubgroupCountResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-subgroup-count", err) } - return nil, NewGetGrpsioMailingListSettingsBadRequest(&body) - case http.StatusInternalServerError: + res := NewGetGroupsioSubgroupCountGroupsioCountOK(&body) + return res, nil + case http.StatusBadRequest: var ( - body GetGrpsioMailingListSettingsInternalServerErrorResponseBody + body GetGroupsioSubgroupCountBadRequestResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-subgroup-count", err) } - err = ValidateGetGrpsioMailingListSettingsInternalServerErrorResponseBody(&body) + err = ValidateGetGroupsioSubgroupCountBadRequestResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-subgroup-count", err) } - return nil, NewGetGrpsioMailingListSettingsInternalServerError(&body) - case http.StatusNotFound: + return nil, NewGetGroupsioSubgroupCountBadRequest(&body) + case http.StatusInternalServerError: var ( - body GetGrpsioMailingListSettingsNotFoundResponseBody + body GetGroupsioSubgroupCountInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-subgroup-count", err) } - err = ValidateGetGrpsioMailingListSettingsNotFoundResponseBody(&body) + err = ValidateGetGroupsioSubgroupCountInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-subgroup-count", err) } - return nil, NewGetGrpsioMailingListSettingsNotFound(&body) + return nil, NewGetGroupsioSubgroupCountInternalServerError(&body) case http.StatusServiceUnavailable: var ( - body GetGrpsioMailingListSettingsServiceUnavailableResponseBody + body GetGroupsioSubgroupCountServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-subgroup-count", err) } - err = ValidateGetGrpsioMailingListSettingsServiceUnavailableResponseBody(&body) + err = ValidateGetGroupsioSubgroupCountServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-subgroup-count", err) } - return nil, NewGetGrpsioMailingListSettingsServiceUnavailable(&body) + return nil, NewGetGroupsioSubgroupCountServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "get-grpsio-mailing-list-settings", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "get-groupsio-subgroup-count", resp.StatusCode, string(body)) } } } -// BuildUpdateGrpsioMailingListSettingsRequest instantiates a HTTP request +// BuildGetGroupsioSubgroupMemberCountRequest instantiates a HTTP request // object with method and path set to call the "mailing-list" service -// "update-grpsio-mailing-list-settings" endpoint -func (c *Client) BuildUpdateGrpsioMailingListSettingsRequest(ctx context.Context, v any) (*http.Request, error) { +// "get-groupsio-subgroup-member-count" endpoint +func (c *Client) BuildGetGroupsioSubgroupMemberCountRequest(ctx context.Context, v any) (*http.Request, error) { var ( - uid string + subgroupID string ) { - p, ok := v.(*mailinglist.UpdateGrpsioMailingListSettingsPayload) + p, ok := v.(*mailinglist.GetGroupsioSubgroupMemberCountPayload) if !ok { - return nil, goahttp.ErrInvalidType("mailing-list", "update-grpsio-mailing-list-settings", "*mailinglist.UpdateGrpsioMailingListSettingsPayload", v) + return nil, goahttp.ErrInvalidType("mailing-list", "get-groupsio-subgroup-member-count", "*mailinglist.GetGroupsioSubgroupMemberCountPayload", v) } - uid = p.UID + subgroupID = p.SubgroupID } - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: UpdateGrpsioMailingListSettingsMailingListPath(uid)} - req, err := http.NewRequest("PUT", u.String(), nil) + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GetGroupsioSubgroupMemberCountMailingListPath(subgroupID)} + req, err := http.NewRequest("GET", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "update-grpsio-mailing-list-settings", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "get-groupsio-subgroup-member-count", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -1982,13 +1890,13 @@ func (c *Client) BuildUpdateGrpsioMailingListSettingsRequest(ctx context.Context return req, nil } -// EncodeUpdateGrpsioMailingListSettingsRequest returns an encoder for requests -// sent to the mailing-list update-grpsio-mailing-list-settings server. -func EncodeUpdateGrpsioMailingListSettingsRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeGetGroupsioSubgroupMemberCountRequest returns an encoder for requests +// sent to the mailing-list get-groupsio-subgroup-member-count server. +func EncodeGetGroupsioSubgroupMemberCountRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.UpdateGrpsioMailingListSettingsPayload) + p, ok := v.(*mailinglist.GetGroupsioSubgroupMemberCountPayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "update-grpsio-mailing-list-settings", "*mailinglist.UpdateGrpsioMailingListSettingsPayload", v) + return goahttp.ErrInvalidType("mailing-list", "get-groupsio-subgroup-member-count", "*mailinglist.GetGroupsioSubgroupMemberCountPayload", v) } if p.BearerToken != nil { head := *p.BearerToken @@ -1998,34 +1906,20 @@ func EncodeUpdateGrpsioMailingListSettingsRequest(encoder func(*http.Request) go req.Header.Set("Authorization", head) } } - if p.IfMatch != nil { - head := *p.IfMatch - req.Header.Set("If-Match", head) - } - values := req.URL.Query() - values.Add("v", p.Version) - req.URL.RawQuery = values.Encode() - body := NewUpdateGrpsioMailingListSettingsRequestBody(p) - if err := encoder(req).Encode(&body); err != nil { - return goahttp.ErrEncodingError("mailing-list", "update-grpsio-mailing-list-settings", err) - } return nil } } -// DecodeUpdateGrpsioMailingListSettingsResponse returns a decoder for -// responses returned by the mailing-list update-grpsio-mailing-list-settings -// endpoint. restoreBody controls whether the response body should be restored -// after having been read. -// DecodeUpdateGrpsioMailingListSettingsResponse may return the following -// errors: -// - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest -// - "Conflict" (type *mailinglist.ConflictError): http.StatusConflict +// DecodeGetGroupsioSubgroupMemberCountResponse returns a decoder for responses +// returned by the mailing-list get-groupsio-subgroup-member-count endpoint. +// restoreBody controls whether the response body should be restored after +// having been read. +// DecodeGetGroupsioSubgroupMemberCountResponse may return the following errors: // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError // - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeUpdateGrpsioMailingListSettingsResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeGetGroupsioSubgroupMemberCountResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -2042,114 +1936,219 @@ func DecodeUpdateGrpsioMailingListSettingsResponse(decoder func(*http.Response) switch resp.StatusCode { case http.StatusOK: var ( - body UpdateGrpsioMailingListSettingsResponseBody + body GetGroupsioSubgroupMemberCountResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-subgroup-member-count", err) } - err = ValidateUpdateGrpsioMailingListSettingsResponseBody(&body) + err = ValidateGetGroupsioSubgroupMemberCountResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-subgroup-member-count", err) } - res := NewUpdateGrpsioMailingListSettingsGrpsIoMailingListSettingsOK(&body) + res := NewGetGroupsioSubgroupMemberCountGroupsioCountOK(&body) return res, nil - case http.StatusBadRequest: + case http.StatusInternalServerError: var ( - body UpdateGrpsioMailingListSettingsBadRequestResponseBody + body GetGroupsioSubgroupMemberCountInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-subgroup-member-count", err) } - err = ValidateUpdateGrpsioMailingListSettingsBadRequestResponseBody(&body) + err = ValidateGetGroupsioSubgroupMemberCountInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-subgroup-member-count", err) } - return nil, NewUpdateGrpsioMailingListSettingsBadRequest(&body) - case http.StatusConflict: + return nil, NewGetGroupsioSubgroupMemberCountInternalServerError(&body) + case http.StatusNotFound: + var ( + body GetGroupsioSubgroupMemberCountNotFoundResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-subgroup-member-count", err) + } + err = ValidateGetGroupsioSubgroupMemberCountNotFoundResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-subgroup-member-count", err) + } + return nil, NewGetGroupsioSubgroupMemberCountNotFound(&body) + case http.StatusServiceUnavailable: + var ( + body GetGroupsioSubgroupMemberCountServiceUnavailableResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-subgroup-member-count", err) + } + err = ValidateGetGroupsioSubgroupMemberCountServiceUnavailableResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-subgroup-member-count", err) + } + return nil, NewGetGroupsioSubgroupMemberCountServiceUnavailable(&body) + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("mailing-list", "get-groupsio-subgroup-member-count", resp.StatusCode, string(body)) + } + } +} + +// BuildListGroupsioMembersRequest instantiates a HTTP request object with +// method and path set to call the "mailing-list" service +// "list-groupsio-members" endpoint +func (c *Client) BuildListGroupsioMembersRequest(ctx context.Context, v any) (*http.Request, error) { + var ( + subgroupID string + ) + { + p, ok := v.(*mailinglist.ListGroupsioMembersPayload) + if !ok { + return nil, goahttp.ErrInvalidType("mailing-list", "list-groupsio-members", "*mailinglist.ListGroupsioMembersPayload", v) + } + subgroupID = p.SubgroupID + } + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: ListGroupsioMembersMailingListPath(subgroupID)} + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("mailing-list", "list-groupsio-members", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeListGroupsioMembersRequest returns an encoder for requests sent to the +// mailing-list list-groupsio-members server. +func EncodeListGroupsioMembersRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*mailinglist.ListGroupsioMembersPayload) + if !ok { + return goahttp.ErrInvalidType("mailing-list", "list-groupsio-members", "*mailinglist.ListGroupsioMembersPayload", v) + } + if p.BearerToken != nil { + head := *p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } + } + return nil + } +} + +// DecodeListGroupsioMembersResponse returns a decoder for responses returned +// by the mailing-list list-groupsio-members endpoint. restoreBody controls +// whether the response body should be restored after having been read. +// DecodeListGroupsioMembersResponse may return the following errors: +// - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError +// - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound +// - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable +// - error: internal error +func DecodeListGroupsioMembersResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusOK: var ( - body UpdateGrpsioMailingListSettingsConflictResponseBody + body ListGroupsioMembersResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "list-groupsio-members", err) } - err = ValidateUpdateGrpsioMailingListSettingsConflictResponseBody(&body) + err = ValidateListGroupsioMembersResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "list-groupsio-members", err) } - return nil, NewUpdateGrpsioMailingListSettingsConflict(&body) + res := NewListGroupsioMembersGroupsioMemberListOK(&body) + return res, nil case http.StatusInternalServerError: var ( - body UpdateGrpsioMailingListSettingsInternalServerErrorResponseBody + body ListGroupsioMembersInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "list-groupsio-members", err) } - err = ValidateUpdateGrpsioMailingListSettingsInternalServerErrorResponseBody(&body) + err = ValidateListGroupsioMembersInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "list-groupsio-members", err) } - return nil, NewUpdateGrpsioMailingListSettingsInternalServerError(&body) + return nil, NewListGroupsioMembersInternalServerError(&body) case http.StatusNotFound: var ( - body UpdateGrpsioMailingListSettingsNotFoundResponseBody + body ListGroupsioMembersNotFoundResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "list-groupsio-members", err) } - err = ValidateUpdateGrpsioMailingListSettingsNotFoundResponseBody(&body) + err = ValidateListGroupsioMembersNotFoundResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "list-groupsio-members", err) } - return nil, NewUpdateGrpsioMailingListSettingsNotFound(&body) + return nil, NewListGroupsioMembersNotFound(&body) case http.StatusServiceUnavailable: var ( - body UpdateGrpsioMailingListSettingsServiceUnavailableResponseBody + body ListGroupsioMembersServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrDecodingError("mailing-list", "list-groupsio-members", err) } - err = ValidateUpdateGrpsioMailingListSettingsServiceUnavailableResponseBody(&body) + err = ValidateListGroupsioMembersServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list-settings", err) + return nil, goahttp.ErrValidationError("mailing-list", "list-groupsio-members", err) } - return nil, NewUpdateGrpsioMailingListSettingsServiceUnavailable(&body) + return nil, NewListGroupsioMembersServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "update-grpsio-mailing-list-settings", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "list-groupsio-members", resp.StatusCode, string(body)) } } } -// BuildCreateGrpsioMailingListMemberRequest instantiates a HTTP request object -// with method and path set to call the "mailing-list" service -// "create-grpsio-mailing-list-member" endpoint -func (c *Client) BuildCreateGrpsioMailingListMemberRequest(ctx context.Context, v any) (*http.Request, error) { +// BuildAddGroupsioMemberRequest instantiates a HTTP request object with method +// and path set to call the "mailing-list" service "add-groupsio-member" +// endpoint +func (c *Client) BuildAddGroupsioMemberRequest(ctx context.Context, v any) (*http.Request, error) { var ( - uid string + subgroupID string ) { - p, ok := v.(*mailinglist.CreateGrpsioMailingListMemberPayload) + p, ok := v.(*mailinglist.AddGroupsioMemberPayload) if !ok { - return nil, goahttp.ErrInvalidType("mailing-list", "create-grpsio-mailing-list-member", "*mailinglist.CreateGrpsioMailingListMemberPayload", v) + return nil, goahttp.ErrInvalidType("mailing-list", "add-groupsio-member", "*mailinglist.AddGroupsioMemberPayload", v) } - uid = p.UID + subgroupID = p.SubgroupID } - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: CreateGrpsioMailingListMemberMailingListPath(uid)} + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: AddGroupsioMemberMailingListPath(subgroupID)} req, err := http.NewRequest("POST", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "create-grpsio-mailing-list-member", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "add-groupsio-member", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -2158,13 +2157,13 @@ func (c *Client) BuildCreateGrpsioMailingListMemberRequest(ctx context.Context, return req, nil } -// EncodeCreateGrpsioMailingListMemberRequest returns an encoder for requests -// sent to the mailing-list create-grpsio-mailing-list-member server. -func EncodeCreateGrpsioMailingListMemberRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeAddGroupsioMemberRequest returns an encoder for requests sent to the +// mailing-list add-groupsio-member server. +func EncodeAddGroupsioMemberRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.CreateGrpsioMailingListMemberPayload) + p, ok := v.(*mailinglist.AddGroupsioMemberPayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "create-grpsio-mailing-list-member", "*mailinglist.CreateGrpsioMailingListMemberPayload", v) + return goahttp.ErrInvalidType("mailing-list", "add-groupsio-member", "*mailinglist.AddGroupsioMemberPayload", v) } if p.BearerToken != nil { head := *p.BearerToken @@ -2174,29 +2173,25 @@ func EncodeCreateGrpsioMailingListMemberRequest(encoder func(*http.Request) goah req.Header.Set("Authorization", head) } } - values := req.URL.Query() - values.Add("v", p.Version) - req.URL.RawQuery = values.Encode() - body := NewCreateGrpsioMailingListMemberRequestBody(p) + body := NewAddGroupsioMemberRequestBody(p) if err := encoder(req).Encode(&body); err != nil { - return goahttp.ErrEncodingError("mailing-list", "create-grpsio-mailing-list-member", err) + return goahttp.ErrEncodingError("mailing-list", "add-groupsio-member", err) } return nil } } -// DecodeCreateGrpsioMailingListMemberResponse returns a decoder for responses -// returned by the mailing-list create-grpsio-mailing-list-member endpoint. -// restoreBody controls whether the response body should be restored after -// having been read. -// DecodeCreateGrpsioMailingListMemberResponse may return the following errors: +// DecodeAddGroupsioMemberResponse returns a decoder for responses returned by +// the mailing-list add-groupsio-member endpoint. restoreBody controls whether +// the response body should be restored after having been read. +// DecodeAddGroupsioMemberResponse may return the following errors: // - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest // - "Conflict" (type *mailinglist.ConflictError): http.StatusConflict // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError // - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeCreateGrpsioMailingListMemberResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeAddGroupsioMemberResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -2213,116 +2208,116 @@ func DecodeCreateGrpsioMailingListMemberResponse(decoder func(*http.Response) go switch resp.StatusCode { case http.StatusCreated: var ( - body CreateGrpsioMailingListMemberResponseBody + body AddGroupsioMemberResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "add-groupsio-member", err) } - err = ValidateCreateGrpsioMailingListMemberResponseBody(&body) + err = ValidateAddGroupsioMemberResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "add-groupsio-member", err) } - res := NewCreateGrpsioMailingListMemberGrpsIoMemberFullCreated(&body) + res := NewAddGroupsioMemberGroupsioMemberCreated(&body) return res, nil case http.StatusBadRequest: var ( - body CreateGrpsioMailingListMemberBadRequestResponseBody + body AddGroupsioMemberBadRequestResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "add-groupsio-member", err) } - err = ValidateCreateGrpsioMailingListMemberBadRequestResponseBody(&body) + err = ValidateAddGroupsioMemberBadRequestResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "add-groupsio-member", err) } - return nil, NewCreateGrpsioMailingListMemberBadRequest(&body) + return nil, NewAddGroupsioMemberBadRequest(&body) case http.StatusConflict: var ( - body CreateGrpsioMailingListMemberConflictResponseBody + body AddGroupsioMemberConflictResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "add-groupsio-member", err) } - err = ValidateCreateGrpsioMailingListMemberConflictResponseBody(&body) + err = ValidateAddGroupsioMemberConflictResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "add-groupsio-member", err) } - return nil, NewCreateGrpsioMailingListMemberConflict(&body) + return nil, NewAddGroupsioMemberConflict(&body) case http.StatusInternalServerError: var ( - body CreateGrpsioMailingListMemberInternalServerErrorResponseBody + body AddGroupsioMemberInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "add-groupsio-member", err) } - err = ValidateCreateGrpsioMailingListMemberInternalServerErrorResponseBody(&body) + err = ValidateAddGroupsioMemberInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "add-groupsio-member", err) } - return nil, NewCreateGrpsioMailingListMemberInternalServerError(&body) + return nil, NewAddGroupsioMemberInternalServerError(&body) case http.StatusNotFound: var ( - body CreateGrpsioMailingListMemberNotFoundResponseBody + body AddGroupsioMemberNotFoundResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "add-groupsio-member", err) } - err = ValidateCreateGrpsioMailingListMemberNotFoundResponseBody(&body) + err = ValidateAddGroupsioMemberNotFoundResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "add-groupsio-member", err) } - return nil, NewCreateGrpsioMailingListMemberNotFound(&body) + return nil, NewAddGroupsioMemberNotFound(&body) case http.StatusServiceUnavailable: var ( - body CreateGrpsioMailingListMemberServiceUnavailableResponseBody + body AddGroupsioMemberServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "create-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "add-groupsio-member", err) } - err = ValidateCreateGrpsioMailingListMemberServiceUnavailableResponseBody(&body) + err = ValidateAddGroupsioMemberServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "create-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "add-groupsio-member", err) } - return nil, NewCreateGrpsioMailingListMemberServiceUnavailable(&body) + return nil, NewAddGroupsioMemberServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "create-grpsio-mailing-list-member", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "add-groupsio-member", resp.StatusCode, string(body)) } } } -// BuildGetGrpsioMailingListMemberRequest instantiates a HTTP request object -// with method and path set to call the "mailing-list" service -// "get-grpsio-mailing-list-member" endpoint -func (c *Client) BuildGetGrpsioMailingListMemberRequest(ctx context.Context, v any) (*http.Request, error) { +// BuildGetGroupsioMemberRequest instantiates a HTTP request object with method +// and path set to call the "mailing-list" service "get-groupsio-member" +// endpoint +func (c *Client) BuildGetGroupsioMemberRequest(ctx context.Context, v any) (*http.Request, error) { var ( - uid string - memberUID string + subgroupID string + memberID string ) { - p, ok := v.(*mailinglist.GetGrpsioMailingListMemberPayload) + p, ok := v.(*mailinglist.GetGroupsioMemberPayload) if !ok { - return nil, goahttp.ErrInvalidType("mailing-list", "get-grpsio-mailing-list-member", "*mailinglist.GetGrpsioMailingListMemberPayload", v) + return nil, goahttp.ErrInvalidType("mailing-list", "get-groupsio-member", "*mailinglist.GetGroupsioMemberPayload", v) } - uid = p.UID - memberUID = p.MemberUID + subgroupID = p.SubgroupID + memberID = p.MemberID } - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GetGrpsioMailingListMemberMailingListPath(uid, memberUID)} + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GetGroupsioMemberMailingListPath(subgroupID, memberID)} req, err := http.NewRequest("GET", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "get-grpsio-mailing-list-member", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "get-groupsio-member", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -2331,40 +2326,35 @@ func (c *Client) BuildGetGrpsioMailingListMemberRequest(ctx context.Context, v a return req, nil } -// EncodeGetGrpsioMailingListMemberRequest returns an encoder for requests sent -// to the mailing-list get-grpsio-mailing-list-member server. -func EncodeGetGrpsioMailingListMemberRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeGetGroupsioMemberRequest returns an encoder for requests sent to the +// mailing-list get-groupsio-member server. +func EncodeGetGroupsioMemberRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.GetGrpsioMailingListMemberPayload) + p, ok := v.(*mailinglist.GetGroupsioMemberPayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "get-grpsio-mailing-list-member", "*mailinglist.GetGrpsioMailingListMemberPayload", v) + return goahttp.ErrInvalidType("mailing-list", "get-groupsio-member", "*mailinglist.GetGroupsioMemberPayload", v) } - { - head := p.BearerToken + if p.BearerToken != nil { + head := *p.BearerToken if !strings.Contains(head, " ") { req.Header.Set("Authorization", "Bearer "+head) } else { req.Header.Set("Authorization", head) } } - values := req.URL.Query() - values.Add("v", p.Version) - req.URL.RawQuery = values.Encode() return nil } } -// DecodeGetGrpsioMailingListMemberResponse returns a decoder for responses -// returned by the mailing-list get-grpsio-mailing-list-member endpoint. -// restoreBody controls whether the response body should be restored after -// having been read. -// DecodeGetGrpsioMailingListMemberResponse may return the following errors: -// - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest +// DecodeGetGroupsioMemberResponse returns a decoder for responses returned by +// the mailing-list get-groupsio-member endpoint. restoreBody controls whether +// the response body should be restored after having been read. +// DecodeGetGroupsioMemberResponse may return the following errors: // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError // - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeGetGrpsioMailingListMemberResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeGetGroupsioMemberResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -2381,109 +2371,88 @@ func DecodeGetGrpsioMailingListMemberResponse(decoder func(*http.Response) goaht switch resp.StatusCode { case http.StatusOK: var ( - body GetGrpsioMailingListMemberResponseBody + body GetGroupsioMemberResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-member", err) } - err = ValidateGetGrpsioMailingListMemberResponseBody(&body) + err = ValidateGetGroupsioMemberResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list-member", err) - } - var ( - etag *string - ) - etagRaw := resp.Header.Get("Etag") - if etagRaw != "" { - etag = &etagRaw + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-member", err) } - res := NewGetGrpsioMailingListMemberResultOK(&body, etag) + res := NewGetGroupsioMemberGroupsioMemberOK(&body) return res, nil - case http.StatusBadRequest: - var ( - body GetGrpsioMailingListMemberBadRequestResponseBody - err error - ) - err = decoder(resp).Decode(&body) - if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list-member", err) - } - err = ValidateGetGrpsioMailingListMemberBadRequestResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list-member", err) - } - return nil, NewGetGrpsioMailingListMemberBadRequest(&body) case http.StatusInternalServerError: var ( - body GetGrpsioMailingListMemberInternalServerErrorResponseBody + body GetGroupsioMemberInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-member", err) } - err = ValidateGetGrpsioMailingListMemberInternalServerErrorResponseBody(&body) + err = ValidateGetGroupsioMemberInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-member", err) } - return nil, NewGetGrpsioMailingListMemberInternalServerError(&body) + return nil, NewGetGroupsioMemberInternalServerError(&body) case http.StatusNotFound: var ( - body GetGrpsioMailingListMemberNotFoundResponseBody + body GetGroupsioMemberNotFoundResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-member", err) } - err = ValidateGetGrpsioMailingListMemberNotFoundResponseBody(&body) + err = ValidateGetGroupsioMemberNotFoundResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-member", err) } - return nil, NewGetGrpsioMailingListMemberNotFound(&body) + return nil, NewGetGroupsioMemberNotFound(&body) case http.StatusServiceUnavailable: var ( - body GetGrpsioMailingListMemberServiceUnavailableResponseBody + body GetGroupsioMemberServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "get-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "get-groupsio-member", err) } - err = ValidateGetGrpsioMailingListMemberServiceUnavailableResponseBody(&body) + err = ValidateGetGroupsioMemberServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "get-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "get-groupsio-member", err) } - return nil, NewGetGrpsioMailingListMemberServiceUnavailable(&body) + return nil, NewGetGroupsioMemberServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "get-grpsio-mailing-list-member", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "get-groupsio-member", resp.StatusCode, string(body)) } } } -// BuildUpdateGrpsioMailingListMemberRequest instantiates a HTTP request object -// with method and path set to call the "mailing-list" service -// "update-grpsio-mailing-list-member" endpoint -func (c *Client) BuildUpdateGrpsioMailingListMemberRequest(ctx context.Context, v any) (*http.Request, error) { +// BuildUpdateGroupsioMemberRequest instantiates a HTTP request object with +// method and path set to call the "mailing-list" service +// "update-groupsio-member" endpoint +func (c *Client) BuildUpdateGroupsioMemberRequest(ctx context.Context, v any) (*http.Request, error) { var ( - uid string - memberUID string + subgroupID string + memberID string ) { - p, ok := v.(*mailinglist.UpdateGrpsioMailingListMemberPayload) + p, ok := v.(*mailinglist.UpdateGroupsioMemberPayload) if !ok { - return nil, goahttp.ErrInvalidType("mailing-list", "update-grpsio-mailing-list-member", "*mailinglist.UpdateGrpsioMailingListMemberPayload", v) + return nil, goahttp.ErrInvalidType("mailing-list", "update-groupsio-member", "*mailinglist.UpdateGroupsioMemberPayload", v) } - uid = p.UID - memberUID = p.MemberUID + subgroupID = p.SubgroupID + memberID = p.MemberID } - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: UpdateGrpsioMailingListMemberMailingListPath(uid, memberUID)} + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: UpdateGroupsioMemberMailingListPath(subgroupID, memberID)} req, err := http.NewRequest("PUT", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "update-grpsio-mailing-list-member", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "update-groupsio-member", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -2492,49 +2461,40 @@ func (c *Client) BuildUpdateGrpsioMailingListMemberRequest(ctx context.Context, return req, nil } -// EncodeUpdateGrpsioMailingListMemberRequest returns an encoder for requests -// sent to the mailing-list update-grpsio-mailing-list-member server. -func EncodeUpdateGrpsioMailingListMemberRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeUpdateGroupsioMemberRequest returns an encoder for requests sent to +// the mailing-list update-groupsio-member server. +func EncodeUpdateGroupsioMemberRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.UpdateGrpsioMailingListMemberPayload) + p, ok := v.(*mailinglist.UpdateGroupsioMemberPayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "update-grpsio-mailing-list-member", "*mailinglist.UpdateGrpsioMailingListMemberPayload", v) + return goahttp.ErrInvalidType("mailing-list", "update-groupsio-member", "*mailinglist.UpdateGroupsioMemberPayload", v) } - { - head := p.BearerToken + if p.BearerToken != nil { + head := *p.BearerToken if !strings.Contains(head, " ") { req.Header.Set("Authorization", "Bearer "+head) } else { req.Header.Set("Authorization", head) } } - { - head := p.IfMatch - req.Header.Set("If-Match", head) - } - values := req.URL.Query() - values.Add("v", p.Version) - req.URL.RawQuery = values.Encode() - body := NewUpdateGrpsioMailingListMemberRequestBody(p) + body := NewUpdateGroupsioMemberRequestBody(p) if err := encoder(req).Encode(&body); err != nil { - return goahttp.ErrEncodingError("mailing-list", "update-grpsio-mailing-list-member", err) + return goahttp.ErrEncodingError("mailing-list", "update-groupsio-member", err) } return nil } } -// DecodeUpdateGrpsioMailingListMemberResponse returns a decoder for responses -// returned by the mailing-list update-grpsio-mailing-list-member endpoint. -// restoreBody controls whether the response body should be restored after -// having been read. -// DecodeUpdateGrpsioMailingListMemberResponse may return the following errors: +// DecodeUpdateGroupsioMemberResponse returns a decoder for responses returned +// by the mailing-list update-groupsio-member endpoint. restoreBody controls +// whether the response body should be restored after having been read. +// DecodeUpdateGroupsioMemberResponse may return the following errors: // - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest -// - "Conflict" (type *mailinglist.ConflictError): http.StatusConflict // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError // - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeUpdateGrpsioMailingListMemberResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeUpdateGroupsioMemberResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -2551,116 +2511,222 @@ func DecodeUpdateGrpsioMailingListMemberResponse(decoder func(*http.Response) go switch resp.StatusCode { case http.StatusOK: var ( - body UpdateGrpsioMailingListMemberResponseBody + body UpdateGroupsioMemberResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-member", err) } - err = ValidateUpdateGrpsioMailingListMemberResponseBody(&body) + err = ValidateUpdateGroupsioMemberResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-member", err) } - res := NewUpdateGrpsioMailingListMemberGrpsIoMemberWithReadonlyAttributesOK(&body) + res := NewUpdateGroupsioMemberGroupsioMemberOK(&body) return res, nil case http.StatusBadRequest: var ( - body UpdateGrpsioMailingListMemberBadRequestResponseBody + body UpdateGroupsioMemberBadRequestResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-member", err) } - err = ValidateUpdateGrpsioMailingListMemberBadRequestResponseBody(&body) + err = ValidateUpdateGroupsioMemberBadRequestResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-member", err) } - return nil, NewUpdateGrpsioMailingListMemberBadRequest(&body) - case http.StatusConflict: + return nil, NewUpdateGroupsioMemberBadRequest(&body) + case http.StatusInternalServerError: + var ( + body UpdateGroupsioMemberInternalServerErrorResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-member", err) + } + err = ValidateUpdateGroupsioMemberInternalServerErrorResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-member", err) + } + return nil, NewUpdateGroupsioMemberInternalServerError(&body) + case http.StatusNotFound: var ( - body UpdateGrpsioMailingListMemberConflictResponseBody + body UpdateGroupsioMemberNotFoundResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-member", err) + } + err = ValidateUpdateGroupsioMemberNotFoundResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-member", err) + } + return nil, NewUpdateGroupsioMemberNotFound(&body) + case http.StatusServiceUnavailable: + var ( + body UpdateGroupsioMemberServiceUnavailableResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("mailing-list", "update-groupsio-member", err) + } + err = ValidateUpdateGroupsioMemberServiceUnavailableResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("mailing-list", "update-groupsio-member", err) + } + return nil, NewUpdateGroupsioMemberServiceUnavailable(&body) + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("mailing-list", "update-groupsio-member", resp.StatusCode, string(body)) + } + } +} + +// BuildDeleteGroupsioMemberRequest instantiates a HTTP request object with +// method and path set to call the "mailing-list" service +// "delete-groupsio-member" endpoint +func (c *Client) BuildDeleteGroupsioMemberRequest(ctx context.Context, v any) (*http.Request, error) { + var ( + subgroupID string + memberID string + ) + { + p, ok := v.(*mailinglist.DeleteGroupsioMemberPayload) + if !ok { + return nil, goahttp.ErrInvalidType("mailing-list", "delete-groupsio-member", "*mailinglist.DeleteGroupsioMemberPayload", v) + } + subgroupID = p.SubgroupID + memberID = p.MemberID + } + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: DeleteGroupsioMemberMailingListPath(subgroupID, memberID)} + req, err := http.NewRequest("DELETE", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("mailing-list", "delete-groupsio-member", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeDeleteGroupsioMemberRequest returns an encoder for requests sent to +// the mailing-list delete-groupsio-member server. +func EncodeDeleteGroupsioMemberRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*mailinglist.DeleteGroupsioMemberPayload) + if !ok { + return goahttp.ErrInvalidType("mailing-list", "delete-groupsio-member", "*mailinglist.DeleteGroupsioMemberPayload", v) + } + if p.BearerToken != nil { + head := *p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) } - err = ValidateUpdateGrpsioMailingListMemberConflictResponseBody(&body) + } + return nil + } +} + +// DecodeDeleteGroupsioMemberResponse returns a decoder for responses returned +// by the mailing-list delete-groupsio-member endpoint. restoreBody controls +// whether the response body should be restored after having been read. +// DecodeDeleteGroupsioMemberResponse may return the following errors: +// - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError +// - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound +// - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable +// - error: internal error +func DecodeDeleteGroupsioMemberResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list-member", err) + return nil, err } - return nil, NewUpdateGrpsioMailingListMemberConflict(&body) + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusNoContent: + return nil, nil case http.StatusInternalServerError: var ( - body UpdateGrpsioMailingListMemberInternalServerErrorResponseBody + body DeleteGroupsioMemberInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "delete-groupsio-member", err) } - err = ValidateUpdateGrpsioMailingListMemberInternalServerErrorResponseBody(&body) + err = ValidateDeleteGroupsioMemberInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "delete-groupsio-member", err) } - return nil, NewUpdateGrpsioMailingListMemberInternalServerError(&body) + return nil, NewDeleteGroupsioMemberInternalServerError(&body) case http.StatusNotFound: var ( - body UpdateGrpsioMailingListMemberNotFoundResponseBody + body DeleteGroupsioMemberNotFoundResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "delete-groupsio-member", err) } - err = ValidateUpdateGrpsioMailingListMemberNotFoundResponseBody(&body) + err = ValidateDeleteGroupsioMemberNotFoundResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "delete-groupsio-member", err) } - return nil, NewUpdateGrpsioMailingListMemberNotFound(&body) + return nil, NewDeleteGroupsioMemberNotFound(&body) case http.StatusServiceUnavailable: var ( - body UpdateGrpsioMailingListMemberServiceUnavailableResponseBody + body DeleteGroupsioMemberServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "update-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "delete-groupsio-member", err) } - err = ValidateUpdateGrpsioMailingListMemberServiceUnavailableResponseBody(&body) + err = ValidateDeleteGroupsioMemberServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "update-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "delete-groupsio-member", err) } - return nil, NewUpdateGrpsioMailingListMemberServiceUnavailable(&body) + return nil, NewDeleteGroupsioMemberServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "update-grpsio-mailing-list-member", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "delete-groupsio-member", resp.StatusCode, string(body)) } } } -// BuildDeleteGrpsioMailingListMemberRequest instantiates a HTTP request object -// with method and path set to call the "mailing-list" service -// "delete-grpsio-mailing-list-member" endpoint -func (c *Client) BuildDeleteGrpsioMailingListMemberRequest(ctx context.Context, v any) (*http.Request, error) { +// BuildInviteGroupsioMembersRequest instantiates a HTTP request object with +// method and path set to call the "mailing-list" service +// "invite-groupsio-members" endpoint +func (c *Client) BuildInviteGroupsioMembersRequest(ctx context.Context, v any) (*http.Request, error) { var ( - uid string - memberUID string + subgroupID string ) { - p, ok := v.(*mailinglist.DeleteGrpsioMailingListMemberPayload) + p, ok := v.(*mailinglist.InviteGroupsioMembersPayload) if !ok { - return nil, goahttp.ErrInvalidType("mailing-list", "delete-grpsio-mailing-list-member", "*mailinglist.DeleteGrpsioMailingListMemberPayload", v) + return nil, goahttp.ErrInvalidType("mailing-list", "invite-groupsio-members", "*mailinglist.InviteGroupsioMembersPayload", v) } - uid = p.UID - memberUID = p.MemberUID + subgroupID = p.SubgroupID } - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: DeleteGrpsioMailingListMemberMailingListPath(uid, memberUID)} - req, err := http.NewRequest("DELETE", u.String(), nil) + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: InviteGroupsioMembersMailingListPath(subgroupID)} + req, err := http.NewRequest("POST", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "delete-grpsio-mailing-list-member", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "invite-groupsio-members", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -2669,45 +2735,40 @@ func (c *Client) BuildDeleteGrpsioMailingListMemberRequest(ctx context.Context, return req, nil } -// EncodeDeleteGrpsioMailingListMemberRequest returns an encoder for requests -// sent to the mailing-list delete-grpsio-mailing-list-member server. -func EncodeDeleteGrpsioMailingListMemberRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeInviteGroupsioMembersRequest returns an encoder for requests sent to +// the mailing-list invite-groupsio-members server. +func EncodeInviteGroupsioMembersRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.DeleteGrpsioMailingListMemberPayload) + p, ok := v.(*mailinglist.InviteGroupsioMembersPayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "delete-grpsio-mailing-list-member", "*mailinglist.DeleteGrpsioMailingListMemberPayload", v) + return goahttp.ErrInvalidType("mailing-list", "invite-groupsio-members", "*mailinglist.InviteGroupsioMembersPayload", v) } - { - head := p.BearerToken + if p.BearerToken != nil { + head := *p.BearerToken if !strings.Contains(head, " ") { req.Header.Set("Authorization", "Bearer "+head) } else { req.Header.Set("Authorization", head) } } - { - head := p.IfMatch - req.Header.Set("If-Match", head) + body := NewInviteGroupsioMembersRequestBody(p) + if err := encoder(req).Encode(&body); err != nil { + return goahttp.ErrEncodingError("mailing-list", "invite-groupsio-members", err) } - values := req.URL.Query() - values.Add("v", p.Version) - req.URL.RawQuery = values.Encode() return nil } } -// DecodeDeleteGrpsioMailingListMemberResponse returns a decoder for responses -// returned by the mailing-list delete-grpsio-mailing-list-member endpoint. -// restoreBody controls whether the response body should be restored after -// having been read. -// DecodeDeleteGrpsioMailingListMemberResponse may return the following errors: +// DecodeInviteGroupsioMembersResponse returns a decoder for responses returned +// by the mailing-list invite-groupsio-members endpoint. restoreBody controls +// whether the response body should be restored after having been read. +// DecodeInviteGroupsioMembersResponse may return the following errors: // - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest -// - "Conflict" (type *mailinglist.ConflictError): http.StatusConflict // - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError // - "NotFound" (type *mailinglist.NotFoundError): http.StatusNotFound // - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeDeleteGrpsioMailingListMemberResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeInviteGroupsioMembersResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -2726,88 +2787,75 @@ func DecodeDeleteGrpsioMailingListMemberResponse(decoder func(*http.Response) go return nil, nil case http.StatusBadRequest: var ( - body DeleteGrpsioMailingListMemberBadRequestResponseBody + body InviteGroupsioMembersBadRequestResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "invite-groupsio-members", err) } - err = ValidateDeleteGrpsioMailingListMemberBadRequestResponseBody(&body) + err = ValidateInviteGroupsioMembersBadRequestResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "invite-groupsio-members", err) } - return nil, NewDeleteGrpsioMailingListMemberBadRequest(&body) - case http.StatusConflict: - var ( - body DeleteGrpsioMailingListMemberConflictResponseBody - err error - ) - err = decoder(resp).Decode(&body) - if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-mailing-list-member", err) - } - err = ValidateDeleteGrpsioMailingListMemberConflictResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-mailing-list-member", err) - } - return nil, NewDeleteGrpsioMailingListMemberConflict(&body) + return nil, NewInviteGroupsioMembersBadRequest(&body) case http.StatusInternalServerError: var ( - body DeleteGrpsioMailingListMemberInternalServerErrorResponseBody + body InviteGroupsioMembersInternalServerErrorResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "invite-groupsio-members", err) } - err = ValidateDeleteGrpsioMailingListMemberInternalServerErrorResponseBody(&body) + err = ValidateInviteGroupsioMembersInternalServerErrorResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "invite-groupsio-members", err) } - return nil, NewDeleteGrpsioMailingListMemberInternalServerError(&body) + return nil, NewInviteGroupsioMembersInternalServerError(&body) case http.StatusNotFound: var ( - body DeleteGrpsioMailingListMemberNotFoundResponseBody + body InviteGroupsioMembersNotFoundResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "invite-groupsio-members", err) } - err = ValidateDeleteGrpsioMailingListMemberNotFoundResponseBody(&body) + err = ValidateInviteGroupsioMembersNotFoundResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "invite-groupsio-members", err) } - return nil, NewDeleteGrpsioMailingListMemberNotFound(&body) + return nil, NewInviteGroupsioMembersNotFound(&body) case http.StatusServiceUnavailable: var ( - body DeleteGrpsioMailingListMemberServiceUnavailableResponseBody + body InviteGroupsioMembersServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "delete-grpsio-mailing-list-member", err) + return nil, goahttp.ErrDecodingError("mailing-list", "invite-groupsio-members", err) } - err = ValidateDeleteGrpsioMailingListMemberServiceUnavailableResponseBody(&body) + err = ValidateInviteGroupsioMembersServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "delete-grpsio-mailing-list-member", err) + return nil, goahttp.ErrValidationError("mailing-list", "invite-groupsio-members", err) } - return nil, NewDeleteGrpsioMailingListMemberServiceUnavailable(&body) + return nil, NewInviteGroupsioMembersServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "delete-grpsio-mailing-list-member", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "invite-groupsio-members", resp.StatusCode, string(body)) } } } -// BuildGroupsioWebhookRequest instantiates a HTTP request object with method -// and path set to call the "mailing-list" service "groupsio-webhook" endpoint -func (c *Client) BuildGroupsioWebhookRequest(ctx context.Context, v any) (*http.Request, error) { - u := &url.URL{Scheme: c.scheme, Host: c.host, Path: GroupsioWebhookMailingListPath()} +// BuildCheckGroupsioSubscriberRequest instantiates a HTTP request object with +// method and path set to call the "mailing-list" service +// "check-groupsio-subscriber" endpoint +func (c *Client) BuildCheckGroupsioSubscriberRequest(ctx context.Context, v any) (*http.Request, error) { + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: CheckGroupsioSubscriberMailingListPath()} req, err := http.NewRequest("POST", u.String(), nil) if err != nil { - return nil, goahttp.ErrInvalidURL("mailing-list", "groupsio-webhook", u.String(), err) + return nil, goahttp.ErrInvalidURL("mailing-list", "check-groupsio-subscriber", u.String(), err) } if ctx != nil { req = req.WithContext(ctx) @@ -2816,34 +2864,39 @@ func (c *Client) BuildGroupsioWebhookRequest(ctx context.Context, v any) (*http. return req, nil } -// EncodeGroupsioWebhookRequest returns an encoder for requests sent to the -// mailing-list groupsio-webhook server. -func EncodeGroupsioWebhookRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { +// EncodeCheckGroupsioSubscriberRequest returns an encoder for requests sent to +// the mailing-list check-groupsio-subscriber server. +func EncodeCheckGroupsioSubscriberRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { return func(req *http.Request, v any) error { - p, ok := v.(*mailinglist.GroupsioWebhookPayload) + p, ok := v.(*mailinglist.CheckGroupsioSubscriberPayload) if !ok { - return goahttp.ErrInvalidType("mailing-list", "groupsio-webhook", "*mailinglist.GroupsioWebhookPayload", v) + return goahttp.ErrInvalidType("mailing-list", "check-groupsio-subscriber", "*mailinglist.CheckGroupsioSubscriberPayload", v) } - { - head := p.Signature - req.Header.Set("x-groupsio-signature", head) + if p.BearerToken != nil { + head := *p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } } - body := NewGroupsioWebhookRequestBody(p) + body := NewCheckGroupsioSubscriberRequestBody(p) if err := encoder(req).Encode(&body); err != nil { - return goahttp.ErrEncodingError("mailing-list", "groupsio-webhook", err) + return goahttp.ErrEncodingError("mailing-list", "check-groupsio-subscriber", err) } return nil } } -// DecodeGroupsioWebhookResponse returns a decoder for responses returned by -// the mailing-list groupsio-webhook endpoint. restoreBody controls whether the -// response body should be restored after having been read. -// DecodeGroupsioWebhookResponse may return the following errors: +// DecodeCheckGroupsioSubscriberResponse returns a decoder for responses +// returned by the mailing-list check-groupsio-subscriber endpoint. restoreBody +// controls whether the response body should be restored after having been read. +// DecodeCheckGroupsioSubscriberResponse may return the following errors: // - "BadRequest" (type *mailinglist.BadRequestError): http.StatusBadRequest -// - "Unauthorized" (type *mailinglist.UnauthorizedError): http.StatusUnauthorized +// - "InternalServerError" (type *mailinglist.InternalServerError): http.StatusInternalServerError +// - "ServiceUnavailable" (type *mailinglist.ServiceUnavailableError): http.StatusServiceUnavailable // - error: internal error -func DecodeGroupsioWebhookResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { +func DecodeCheckGroupsioSubscriberResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { return func(resp *http.Response) (any, error) { if restoreBody { b, err := io.ReadAll(resp.Body) @@ -2858,146 +2911,134 @@ func DecodeGroupsioWebhookResponse(decoder func(*http.Response) goahttp.Decoder, defer resp.Body.Close() } switch resp.StatusCode { - case http.StatusNoContent: - return nil, nil + case http.StatusOK: + var ( + body CheckGroupsioSubscriberResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("mailing-list", "check-groupsio-subscriber", err) + } + err = ValidateCheckGroupsioSubscriberResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("mailing-list", "check-groupsio-subscriber", err) + } + res := NewCheckGroupsioSubscriberGroupsioCheckSubscriberResponseOK(&body) + return res, nil case http.StatusBadRequest: var ( - body GroupsioWebhookBadRequestResponseBody + body CheckGroupsioSubscriberBadRequestResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "groupsio-webhook", err) + return nil, goahttp.ErrDecodingError("mailing-list", "check-groupsio-subscriber", err) } - err = ValidateGroupsioWebhookBadRequestResponseBody(&body) + err = ValidateCheckGroupsioSubscriberBadRequestResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "groupsio-webhook", err) + return nil, goahttp.ErrValidationError("mailing-list", "check-groupsio-subscriber", err) } - return nil, NewGroupsioWebhookBadRequest(&body) - case http.StatusUnauthorized: + return nil, NewCheckGroupsioSubscriberBadRequest(&body) + case http.StatusInternalServerError: + var ( + body CheckGroupsioSubscriberInternalServerErrorResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("mailing-list", "check-groupsio-subscriber", err) + } + err = ValidateCheckGroupsioSubscriberInternalServerErrorResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("mailing-list", "check-groupsio-subscriber", err) + } + return nil, NewCheckGroupsioSubscriberInternalServerError(&body) + case http.StatusServiceUnavailable: var ( - body GroupsioWebhookUnauthorizedResponseBody + body CheckGroupsioSubscriberServiceUnavailableResponseBody err error ) err = decoder(resp).Decode(&body) if err != nil { - return nil, goahttp.ErrDecodingError("mailing-list", "groupsio-webhook", err) + return nil, goahttp.ErrDecodingError("mailing-list", "check-groupsio-subscriber", err) } - err = ValidateGroupsioWebhookUnauthorizedResponseBody(&body) + err = ValidateCheckGroupsioSubscriberServiceUnavailableResponseBody(&body) if err != nil { - return nil, goahttp.ErrValidationError("mailing-list", "groupsio-webhook", err) + return nil, goahttp.ErrValidationError("mailing-list", "check-groupsio-subscriber", err) } - return nil, NewGroupsioWebhookUnauthorized(&body) + return nil, NewCheckGroupsioSubscriberServiceUnavailable(&body) default: body, _ := io.ReadAll(resp.Body) - return nil, goahttp.ErrInvalidResponse("mailing-list", "groupsio-webhook", resp.StatusCode, string(body)) + return nil, goahttp.ErrInvalidResponse("mailing-list", "check-groupsio-subscriber", resp.StatusCode, string(body)) } } } -// marshalMailinglistUserInfoToUserInfoRequestBody builds a value of type -// *UserInfoRequestBody from a value of type *mailinglist.UserInfo. -func marshalMailinglistUserInfoToUserInfoRequestBody(v *mailinglist.UserInfo) *UserInfoRequestBody { +// unmarshalGroupsioServiceResponseBodyToMailinglistGroupsioService builds a +// value of type *mailinglist.GroupsioService from a value of type +// *GroupsioServiceResponseBody. +func unmarshalGroupsioServiceResponseBodyToMailinglistGroupsioService(v *GroupsioServiceResponseBody) *mailinglist.GroupsioService { if v == nil { return nil } - res := &UserInfoRequestBody{ - Name: v.Name, - Email: v.Email, - Username: v.Username, - Avatar: v.Avatar, + res := &mailinglist.GroupsioService{ + ID: v.ID, + ProjectUID: v.ProjectUID, + Type: v.Type, + GroupID: v.GroupID, + Domain: v.Domain, + Prefix: v.Prefix, + Status: v.Status, + CreatedAt: v.CreatedAt, + UpdatedAt: v.UpdatedAt, } return res } -// marshalUserInfoRequestBodyToMailinglistUserInfo builds a value of type -// *mailinglist.UserInfo from a value of type *UserInfoRequestBody. -func marshalUserInfoRequestBodyToMailinglistUserInfo(v *UserInfoRequestBody) *mailinglist.UserInfo { +// unmarshalGroupsioSubgroupResponseBodyToMailinglistGroupsioSubgroup builds a +// value of type *mailinglist.GroupsioSubgroup from a value of type +// *GroupsioSubgroupResponseBody. +func unmarshalGroupsioSubgroupResponseBodyToMailinglistGroupsioSubgroup(v *GroupsioSubgroupResponseBody) *mailinglist.GroupsioSubgroup { if v == nil { return nil } - res := &mailinglist.UserInfo{ - Name: v.Name, - Email: v.Email, - Username: v.Username, - Avatar: v.Avatar, + res := &mailinglist.GroupsioSubgroup{ + ID: v.ID, + ProjectUID: v.ProjectUID, + CommitteeUID: v.CommitteeUID, + GroupID: v.GroupID, + Name: v.Name, + Description: v.Description, + Type: v.Type, + AudienceAccess: v.AudienceAccess, + CreatedAt: v.CreatedAt, + UpdatedAt: v.UpdatedAt, } return res } -// unmarshalUserInfoResponseBodyToMailinglistUserInfo builds a value of type -// *mailinglist.UserInfo from a value of type *UserInfoResponseBody. -func unmarshalUserInfoResponseBodyToMailinglistUserInfo(v *UserInfoResponseBody) *mailinglist.UserInfo { +// unmarshalGroupsioMemberResponseBodyToMailinglistGroupsioMember builds a +// value of type *mailinglist.GroupsioMember from a value of type +// *GroupsioMemberResponseBody. +func unmarshalGroupsioMemberResponseBodyToMailinglistGroupsioMember(v *GroupsioMemberResponseBody) *mailinglist.GroupsioMember { if v == nil { return nil } - res := &mailinglist.UserInfo{ - Name: v.Name, - Email: v.Email, - Username: v.Username, - Avatar: v.Avatar, - } - - return res -} - -// marshalMailinglistCommitteeToCommitteeRequestBody builds a value of type -// *CommitteeRequestBody from a value of type *mailinglist.Committee. -func marshalMailinglistCommitteeToCommitteeRequestBody(v *mailinglist.Committee) *CommitteeRequestBody { - if v == nil { - return nil - } - res := &CommitteeRequestBody{ - UID: v.UID, - Name: v.Name, - } - if v.AllowedVotingStatuses != nil { - res.AllowedVotingStatuses = make([]string, len(v.AllowedVotingStatuses)) - for i, val := range v.AllowedVotingStatuses { - res.AllowedVotingStatuses[i] = val - } - } - - return res -} - -// marshalCommitteeRequestBodyToMailinglistCommittee builds a value of type -// *mailinglist.Committee from a value of type *CommitteeRequestBody. -func marshalCommitteeRequestBodyToMailinglistCommittee(v *CommitteeRequestBody) *mailinglist.Committee { - if v == nil { - return nil - } - res := &mailinglist.Committee{ - UID: v.UID, - Name: v.Name, - } - if v.AllowedVotingStatuses != nil { - res.AllowedVotingStatuses = make([]string, len(v.AllowedVotingStatuses)) - for i, val := range v.AllowedVotingStatuses { - res.AllowedVotingStatuses[i] = val - } - } - - return res -} - -// unmarshalCommitteeResponseBodyToMailinglistCommittee builds a value of type -// *mailinglist.Committee from a value of type *CommitteeResponseBody. -func unmarshalCommitteeResponseBodyToMailinglistCommittee(v *CommitteeResponseBody) *mailinglist.Committee { - if v == nil { - return nil - } - res := &mailinglist.Committee{ - UID: *v.UID, - Name: v.Name, - } - if v.AllowedVotingStatuses != nil { - res.AllowedVotingStatuses = make([]string, len(v.AllowedVotingStatuses)) - for i, val := range v.AllowedVotingStatuses { - res.AllowedVotingStatuses[i] = val - } + res := &mailinglist.GroupsioMember{ + ID: v.ID, + SubgroupID: v.SubgroupID, + Email: v.Email, + Name: v.Name, + FirstName: v.FirstName, + LastName: v.LastName, + ModStatus: v.ModStatus, + DeliveryMode: v.DeliveryMode, + Status: v.Status, + CreatedAt: v.CreatedAt, + UpdatedAt: v.UpdatedAt, } return res diff --git a/gen/http/mailing_list/client/paths.go b/gen/http/mailing_list/client/paths.go index 42946e1..91f71cc 100644 --- a/gen/http/mailing_list/client/paths.go +++ b/gen/http/mailing_list/client/paths.go @@ -22,87 +22,107 @@ func ReadyzMailingListPath() string { return "/readyz" } -// CreateGrpsioServiceMailingListPath returns the URL path to the mailing-list service create-grpsio-service HTTP endpoint. -func CreateGrpsioServiceMailingListPath() string { +// ListGroupsioServicesMailingListPath returns the URL path to the mailing-list service list-groupsio-services HTTP endpoint. +func ListGroupsioServicesMailingListPath() string { return "/groupsio/services" } -// GetGrpsioServiceMailingListPath returns the URL path to the mailing-list service get-grpsio-service HTTP endpoint. -func GetGrpsioServiceMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/services/%v", uid) +// CreateGroupsioServiceMailingListPath returns the URL path to the mailing-list service create-groupsio-service HTTP endpoint. +func CreateGroupsioServiceMailingListPath() string { + return "/groupsio/services" +} + +// GetGroupsioServiceMailingListPath returns the URL path to the mailing-list service get-groupsio-service HTTP endpoint. +func GetGroupsioServiceMailingListPath(serviceID string) string { + return fmt.Sprintf("/groupsio/services/%v", serviceID) +} + +// UpdateGroupsioServiceMailingListPath returns the URL path to the mailing-list service update-groupsio-service HTTP endpoint. +func UpdateGroupsioServiceMailingListPath(serviceID string) string { + return fmt.Sprintf("/groupsio/services/%v", serviceID) +} + +// DeleteGroupsioServiceMailingListPath returns the URL path to the mailing-list service delete-groupsio-service HTTP endpoint. +func DeleteGroupsioServiceMailingListPath(serviceID string) string { + return fmt.Sprintf("/groupsio/services/%v", serviceID) +} + +// GetGroupsioServiceProjectsMailingListPath returns the URL path to the mailing-list service get-groupsio-service-projects HTTP endpoint. +func GetGroupsioServiceProjectsMailingListPath() string { + return "/groupsio/services/_projects" } -// UpdateGrpsioServiceMailingListPath returns the URL path to the mailing-list service update-grpsio-service HTTP endpoint. -func UpdateGrpsioServiceMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/services/%v", uid) +// FindParentGroupsioServiceMailingListPath returns the URL path to the mailing-list service find-parent-groupsio-service HTTP endpoint. +func FindParentGroupsioServiceMailingListPath() string { + return "/groupsio/services/find_parent" } -// DeleteGrpsioServiceMailingListPath returns the URL path to the mailing-list service delete-grpsio-service HTTP endpoint. -func DeleteGrpsioServiceMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/services/%v", uid) +// ListGroupsioSubgroupsMailingListPath returns the URL path to the mailing-list service list-groupsio-subgroups HTTP endpoint. +func ListGroupsioSubgroupsMailingListPath() string { + return "/groupsio/subgroups" } -// GetGrpsioServiceSettingsMailingListPath returns the URL path to the mailing-list service get-grpsio-service-settings HTTP endpoint. -func GetGrpsioServiceSettingsMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/services/%v/settings", uid) +// CreateGroupsioSubgroupMailingListPath returns the URL path to the mailing-list service create-groupsio-subgroup HTTP endpoint. +func CreateGroupsioSubgroupMailingListPath() string { + return "/groupsio/subgroups" } -// UpdateGrpsioServiceSettingsMailingListPath returns the URL path to the mailing-list service update-grpsio-service-settings HTTP endpoint. -func UpdateGrpsioServiceSettingsMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/services/%v/settings", uid) +// GetGroupsioSubgroupMailingListPath returns the URL path to the mailing-list service get-groupsio-subgroup HTTP endpoint. +func GetGroupsioSubgroupMailingListPath(subgroupID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v", subgroupID) } -// CreateGrpsioMailingListMailingListPath returns the URL path to the mailing-list service create-grpsio-mailing-list HTTP endpoint. -func CreateGrpsioMailingListMailingListPath() string { - return "/groupsio/mailing-lists" +// UpdateGroupsioSubgroupMailingListPath returns the URL path to the mailing-list service update-groupsio-subgroup HTTP endpoint. +func UpdateGroupsioSubgroupMailingListPath(subgroupID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v", subgroupID) } -// GetGrpsioMailingListMailingListPath returns the URL path to the mailing-list service get-grpsio-mailing-list HTTP endpoint. -func GetGrpsioMailingListMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v", uid) +// DeleteGroupsioSubgroupMailingListPath returns the URL path to the mailing-list service delete-groupsio-subgroup HTTP endpoint. +func DeleteGroupsioSubgroupMailingListPath(subgroupID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v", subgroupID) } -// UpdateGrpsioMailingListMailingListPath returns the URL path to the mailing-list service update-grpsio-mailing-list HTTP endpoint. -func UpdateGrpsioMailingListMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v", uid) +// GetGroupsioSubgroupCountMailingListPath returns the URL path to the mailing-list service get-groupsio-subgroup-count HTTP endpoint. +func GetGroupsioSubgroupCountMailingListPath() string { + return "/groupsio/subgroups/count" } -// DeleteGrpsioMailingListMailingListPath returns the URL path to the mailing-list service delete-grpsio-mailing-list HTTP endpoint. -func DeleteGrpsioMailingListMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v", uid) +// GetGroupsioSubgroupMemberCountMailingListPath returns the URL path to the mailing-list service get-groupsio-subgroup-member-count HTTP endpoint. +func GetGroupsioSubgroupMemberCountMailingListPath(subgroupID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v/member_count", subgroupID) } -// GetGrpsioMailingListSettingsMailingListPath returns the URL path to the mailing-list service get-grpsio-mailing-list-settings HTTP endpoint. -func GetGrpsioMailingListSettingsMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v/settings", uid) +// ListGroupsioMembersMailingListPath returns the URL path to the mailing-list service list-groupsio-members HTTP endpoint. +func ListGroupsioMembersMailingListPath(subgroupID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v/members", subgroupID) } -// UpdateGrpsioMailingListSettingsMailingListPath returns the URL path to the mailing-list service update-grpsio-mailing-list-settings HTTP endpoint. -func UpdateGrpsioMailingListSettingsMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v/settings", uid) +// AddGroupsioMemberMailingListPath returns the URL path to the mailing-list service add-groupsio-member HTTP endpoint. +func AddGroupsioMemberMailingListPath(subgroupID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v/members", subgroupID) } -// CreateGrpsioMailingListMemberMailingListPath returns the URL path to the mailing-list service create-grpsio-mailing-list-member HTTP endpoint. -func CreateGrpsioMailingListMemberMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v/members", uid) +// GetGroupsioMemberMailingListPath returns the URL path to the mailing-list service get-groupsio-member HTTP endpoint. +func GetGroupsioMemberMailingListPath(subgroupID string, memberID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v/members/%v", subgroupID, memberID) } -// GetGrpsioMailingListMemberMailingListPath returns the URL path to the mailing-list service get-grpsio-mailing-list-member HTTP endpoint. -func GetGrpsioMailingListMemberMailingListPath(uid string, memberUID string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v/members/%v", uid, memberUID) +// UpdateGroupsioMemberMailingListPath returns the URL path to the mailing-list service update-groupsio-member HTTP endpoint. +func UpdateGroupsioMemberMailingListPath(subgroupID string, memberID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v/members/%v", subgroupID, memberID) } -// UpdateGrpsioMailingListMemberMailingListPath returns the URL path to the mailing-list service update-grpsio-mailing-list-member HTTP endpoint. -func UpdateGrpsioMailingListMemberMailingListPath(uid string, memberUID string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v/members/%v", uid, memberUID) +// DeleteGroupsioMemberMailingListPath returns the URL path to the mailing-list service delete-groupsio-member HTTP endpoint. +func DeleteGroupsioMemberMailingListPath(subgroupID string, memberID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v/members/%v", subgroupID, memberID) } -// DeleteGrpsioMailingListMemberMailingListPath returns the URL path to the mailing-list service delete-grpsio-mailing-list-member HTTP endpoint. -func DeleteGrpsioMailingListMemberMailingListPath(uid string, memberUID string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v/members/%v", uid, memberUID) +// InviteGroupsioMembersMailingListPath returns the URL path to the mailing-list service invite-groupsio-members HTTP endpoint. +func InviteGroupsioMembersMailingListPath(subgroupID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v/invitemembers", subgroupID) } -// GroupsioWebhookMailingListPath returns the URL path to the mailing-list service groupsio-webhook HTTP endpoint. -func GroupsioWebhookMailingListPath() string { - return "/webhooks/groupsio" +// CheckGroupsioSubscriberMailingListPath returns the URL path to the mailing-list service check-groupsio-subscriber HTTP endpoint. +func CheckGroupsioSubscriberMailingListPath() string { + return "/groupsio/checksubscriber" } diff --git a/gen/http/mailing_list/client/types.go b/gen/http/mailing_list/client/types.go index 833805c..ed16fc0 100644 --- a/gen/http/mailing_list/client/types.go +++ b/gen/http/mailing_list/client/types.go @@ -9,538 +9,425 @@ package client import ( - "unicode/utf8" - mailinglist "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" goa "goa.design/goa/v3/pkg" ) -// CreateGrpsioServiceRequestBody is the type of the "mailing-list" service -// "create-grpsio-service" endpoint HTTP request body. -type CreateGrpsioServiceRequestBody struct { +// CreateGroupsioServiceRequestBody is the type of the "mailing-list" service +// "create-groupsio-service" endpoint HTTP request body. +type CreateGroupsioServiceRequestBody struct { + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` // Service type - Type string `form:"type" json:"type" xml:"type"` - // Service domain - Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` + // Service domain + Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + // Email prefix + Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` // Service status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // List of global owner email addresses (required for primary, forbidden for - // shared) - GlobalOwners []string `form:"global_owners,omitempty" json:"global_owners,omitempty" xml:"global_owners,omitempty"` - // Email prefix (required for formation and shared, forbidden for primary) - Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` - // Parent primary service UID (automatically set for shared type services) - ParentServiceUID *string `form:"parent_service_uid,omitempty" json:"parent_service_uid,omitempty" xml:"parent_service_uid,omitempty"` - // Project slug identifier - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // LFXv2 Project UID - ProjectUID string `form:"project_uid" json:"project_uid" xml:"project_uid"` - // Service URL - URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` - // GroupsIO group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Whether the service is publicly accessible - Public bool `form:"public" json:"public" xml:"public"` - // Manager users who can edit/modify this resource - Writers []*UserInfoRequestBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoRequestBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// UpdateGrpsioServiceRequestBody is the type of the "mailing-list" service -// "update-grpsio-service" endpoint HTTP request body. -type UpdateGrpsioServiceRequestBody struct { +} + +// UpdateGroupsioServiceRequestBody is the type of the "mailing-list" service +// "update-groupsio-service" endpoint HTTP request body. +type UpdateGroupsioServiceRequestBody struct { + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` // Service type - Type string `form:"type" json:"type" xml:"type"` - // Service domain - Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` + // Service domain + Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + // Email prefix + Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` // Service status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // List of global owner email addresses (required for primary, forbidden for - // shared) - GlobalOwners []string `form:"global_owners,omitempty" json:"global_owners,omitempty" xml:"global_owners,omitempty"` - // Email prefix (required for formation and shared, forbidden for primary) - Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` - // Parent primary service UID (automatically set for shared type services) - ParentServiceUID *string `form:"parent_service_uid,omitempty" json:"parent_service_uid,omitempty" xml:"parent_service_uid,omitempty"` - // Project slug identifier - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // LFXv2 Project UID - ProjectUID string `form:"project_uid" json:"project_uid" xml:"project_uid"` - // Service URL - URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` - // GroupsIO group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Whether the service is publicly accessible - Public bool `form:"public" json:"public" xml:"public"` -} - -// UpdateGrpsioServiceSettingsRequestBody is the type of the "mailing-list" -// service "update-grpsio-service-settings" endpoint HTTP request body. -type UpdateGrpsioServiceSettingsRequestBody struct { - // Manager users who can edit/modify this resource - Writers []*UserInfoRequestBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoRequestBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// CreateGrpsioMailingListRequestBody is the type of the "mailing-list" service -// "create-grpsio-mailing-list" endpoint HTTP request body. -type CreateGrpsioMailingListRequestBody struct { - // Mailing list group name - GroupName string `form:"group_name" json:"group_name" xml:"group_name"` - // Mailing list group ID +} + +// CreateGroupsioSubgroupRequestBody is the type of the "mailing-list" service +// "create-groupsio-subgroup" endpoint HTTP request body. +type CreateGroupsioSubgroupRequestBody struct { + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // LFX v2 committee UID + CommitteeUID *string `form:"committee_uid,omitempty" json:"committee_uid,omitempty" xml:"committee_uid,omitempty"` + // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // Whether the mailing list is publicly accessible - Public bool `form:"public" json:"public" xml:"public"` - // Mailing list type - Type string `form:"type" json:"type" xml:"type"` - // public: Anyone can join. approval_required: Users must request to join and - // be approved. invite_only: Only invited users can join. - AudienceAccess string `form:"audience_access" json:"audience_access" xml:"audience_access"` - // Committees associated with this mailing list (OR logic for access control) - Committees []*CommitteeRequestBody `form:"committees,omitempty" json:"committees,omitempty" xml:"committees,omitempty"` - // Mailing list description (11-500 characters) - Description string `form:"description" json:"description" xml:"description"` - // Mailing list title - Title string `form:"title" json:"title" xml:"title"` - // Subject tag prefix - SubjectTag *string `form:"subject_tag,omitempty" json:"subject_tag,omitempty" xml:"subject_tag,omitempty"` - // Service UUID - ServiceUID string `form:"service_uid" json:"service_uid" xml:"service_uid"` - // Number of subscribers in this mailing list (read-only, maintained by service) - SubscriberCount *int `form:"subscriber_count,omitempty" json:"subscriber_count,omitempty" xml:"subscriber_count,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoRequestBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoRequestBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// UpdateGrpsioMailingListRequestBody is the type of the "mailing-list" service -// "update-grpsio-mailing-list" endpoint HTTP request body. -type UpdateGrpsioMailingListRequestBody struct { - // Mailing list group name - GroupName string `form:"group_name" json:"group_name" xml:"group_name"` - // Mailing list group ID + // Subgroup name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // Subgroup description + Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` + // Subgroup type + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` + // Audience access setting + AudienceAccess *string `form:"audience_access,omitempty" json:"audience_access,omitempty" xml:"audience_access,omitempty"` +} + +// UpdateGroupsioSubgroupRequestBody is the type of the "mailing-list" service +// "update-groupsio-subgroup" endpoint HTTP request body. +type UpdateGroupsioSubgroupRequestBody struct { + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // LFX v2 committee UID + CommitteeUID *string `form:"committee_uid,omitempty" json:"committee_uid,omitempty" xml:"committee_uid,omitempty"` + // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // Whether the mailing list is publicly accessible - Public bool `form:"public" json:"public" xml:"public"` - // Mailing list type - Type string `form:"type" json:"type" xml:"type"` - // public: Anyone can join. approval_required: Users must request to join and - // be approved. invite_only: Only invited users can join. - AudienceAccess string `form:"audience_access" json:"audience_access" xml:"audience_access"` - // Committees associated with this mailing list (OR logic for access control) - Committees []*CommitteeRequestBody `form:"committees,omitempty" json:"committees,omitempty" xml:"committees,omitempty"` - // Mailing list description (11-500 characters) - Description string `form:"description" json:"description" xml:"description"` - // Mailing list title - Title string `form:"title" json:"title" xml:"title"` - // Subject tag prefix - SubjectTag *string `form:"subject_tag,omitempty" json:"subject_tag,omitempty" xml:"subject_tag,omitempty"` - // Service UUID - ServiceUID string `form:"service_uid" json:"service_uid" xml:"service_uid"` - // Number of subscribers in this mailing list (read-only, maintained by service) - SubscriberCount *int `form:"subscriber_count,omitempty" json:"subscriber_count,omitempty" xml:"subscriber_count,omitempty"` -} - -// UpdateGrpsioMailingListSettingsRequestBody is the type of the "mailing-list" -// service "update-grpsio-mailing-list-settings" endpoint HTTP request body. -type UpdateGrpsioMailingListSettingsRequestBody struct { - // Manager users who can edit/modify this resource - Writers []*UserInfoRequestBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoRequestBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// CreateGrpsioMailingListMemberRequestBody is the type of the "mailing-list" -// service "create-grpsio-mailing-list-member" endpoint HTTP request body. -type CreateGrpsioMailingListMemberRequestBody struct { - // Member username - Username *string `form:"username,omitempty" json:"username,omitempty" xml:"username,omitempty"` - // Member first name - FirstName *string `form:"first_name,omitempty" json:"first_name,omitempty" xml:"first_name,omitempty"` - // Member last name - LastName *string `form:"last_name,omitempty" json:"last_name,omitempty" xml:"last_name,omitempty"` + // Subgroup name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // Subgroup description + Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` + // Subgroup type + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` + // Audience access setting + AudienceAccess *string `form:"audience_access,omitempty" json:"audience_access,omitempty" xml:"audience_access,omitempty"` +} + +// AddGroupsioMemberRequestBody is the type of the "mailing-list" service +// "add-groupsio-member" endpoint HTTP request body. +type AddGroupsioMemberRequestBody struct { // Member email address - Email string `form:"email" json:"email" xml:"email"` - // Member organization - Organization *string `form:"organization,omitempty" json:"organization,omitempty" xml:"organization,omitempty"` - // Member job title - JobTitle *string `form:"job_title,omitempty" json:"job_title,omitempty" xml:"job_title,omitempty"` - // Member type - MemberType string `form:"member_type" json:"member_type" xml:"member_type"` - // Email delivery mode - DeliveryMode string `form:"delivery_mode" json:"delivery_mode" xml:"delivery_mode"` + Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` + // Member display name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` // Moderation status - ModStatus string `form:"mod_status" json:"mod_status" xml:"mod_status"` - // Last reviewed timestamp - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // Last reviewed by user ID - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` -} - -// UpdateGrpsioMailingListMemberRequestBody is the type of the "mailing-list" -// service "update-grpsio-mailing-list-member" endpoint HTTP request body. -type UpdateGrpsioMailingListMemberRequestBody struct { - // Member username - Username *string `form:"username,omitempty" json:"username,omitempty" xml:"username,omitempty"` - // Member first name - FirstName *string `form:"first_name,omitempty" json:"first_name,omitempty" xml:"first_name,omitempty"` - // Member last name - LastName *string `form:"last_name,omitempty" json:"last_name,omitempty" xml:"last_name,omitempty"` - // Member organization - Organization *string `form:"organization,omitempty" json:"organization,omitempty" xml:"organization,omitempty"` - // Member job title - JobTitle *string `form:"job_title,omitempty" json:"job_title,omitempty" xml:"job_title,omitempty"` + ModStatus *string `form:"mod_status,omitempty" json:"mod_status,omitempty" xml:"mod_status,omitempty"` // Email delivery mode - DeliveryMode string `form:"delivery_mode" json:"delivery_mode" xml:"delivery_mode"` + DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` +} + +// UpdateGroupsioMemberRequestBody is the type of the "mailing-list" service +// "update-groupsio-member" endpoint HTTP request body. +type UpdateGroupsioMemberRequestBody struct { + // Member email address + Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` + // Member display name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` // Moderation status - ModStatus string `form:"mod_status" json:"mod_status" xml:"mod_status"` -} - -// GroupsioWebhookRequestBody is the type of the "mailing-list" service -// "groupsio-webhook" endpoint HTTP request body. -type GroupsioWebhookRequestBody struct { - // The type of webhook event - Action string `form:"action" json:"action" xml:"action"` - // Contains subgroup data from Groups.io - Group any `form:"group,omitempty" json:"group,omitempty" xml:"group,omitempty"` - // Contains member data from Groups.io - MemberInfo any `form:"member_info,omitempty" json:"member_info,omitempty" xml:"member_info,omitempty"` - // Extra data field (subgroup suffix) - Extra *string `form:"extra,omitempty" json:"extra,omitempty" xml:"extra,omitempty"` - // Extra ID field (subgroup ID for deletion) - ExtraID *int `form:"extra_id,omitempty" json:"extra_id,omitempty" xml:"extra_id,omitempty"` -} - -// CreateGrpsioServiceResponseBody is the type of the "mailing-list" service -// "create-grpsio-service" endpoint HTTP response body. -type CreateGrpsioServiceResponseBody struct { - // Service UID -- unique identifier for the service - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` + ModStatus *string `form:"mod_status,omitempty" json:"mod_status,omitempty" xml:"mod_status,omitempty"` + // Email delivery mode + DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` +} + +// InviteGroupsioMembersRequestBody is the type of the "mailing-list" service +// "invite-groupsio-members" endpoint HTTP request body. +type InviteGroupsioMembersRequestBody struct { + // Email addresses to invite + Emails []string `form:"emails" json:"emails" xml:"emails"` +} + +// CheckGroupsioSubscriberRequestBody is the type of the "mailing-list" service +// "check-groupsio-subscriber" endpoint HTTP request body. +type CheckGroupsioSubscriberRequestBody struct { + // Email address to check + Email string `form:"email" json:"email" xml:"email"` + // Subgroup ID + SubgroupID string `form:"subgroup_id" json:"subgroup_id" xml:"subgroup_id"` +} + +// ListGroupsioServicesResponseBody is the type of the "mailing-list" service +// "list-groupsio-services" endpoint HTTP response body. +type ListGroupsioServicesResponseBody struct { + // List of services + Items []*GroupsioServiceResponseBody `form:"items,omitempty" json:"items,omitempty" xml:"items,omitempty"` + // Total count + Total *int `form:"total,omitempty" json:"total,omitempty" xml:"total,omitempty"` +} + +// CreateGroupsioServiceResponseBody is the type of the "mailing-list" service +// "create-groupsio-service" endpoint HTTP response body. +type CreateGroupsioServiceResponseBody struct { + // Service ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` // Service type Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` - // Service domain - Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` + // Service domain + Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + // Email prefix + Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` // Service status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // List of global owner email addresses (required for primary, forbidden for - // shared) - GlobalOwners []string `form:"global_owners,omitempty" json:"global_owners,omitempty" xml:"global_owners,omitempty"` - // Email prefix (required for formation and shared, forbidden for primary) - Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` - // Parent primary service UID (automatically set for shared type services) - ParentServiceUID *string `form:"parent_service_uid,omitempty" json:"parent_service_uid,omitempty" xml:"parent_service_uid,omitempty"` - // Project slug identifier - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // LFXv2 Project UID - ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` - // Service URL - URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` - // GroupsIO group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Whether the service is publicly accessible - Public *bool `form:"public,omitempty" json:"public,omitempty" xml:"public,omitempty"` - // Project name (read-only) - ProjectName *string `form:"project_name,omitempty" json:"project_name,omitempty" xml:"project_name,omitempty"` - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` } -// GetGrpsioServiceResponseBody is the type of the "mailing-list" service -// "get-grpsio-service" endpoint HTTP response body. -type GetGrpsioServiceResponseBody GrpsIoServiceWithReadonlyAttributesResponseBody - -// UpdateGrpsioServiceResponseBody is the type of the "mailing-list" service -// "update-grpsio-service" endpoint HTTP response body. -type UpdateGrpsioServiceResponseBody struct { - // Service UID -- unique identifier for the service - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` +// GetGroupsioServiceResponseBody is the type of the "mailing-list" service +// "get-groupsio-service" endpoint HTTP response body. +type GetGroupsioServiceResponseBody struct { + // Service ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` // Service type Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` + // GroupsIO group ID + GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` // Service domain Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + // Email prefix + Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` + // Service status + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Creation timestamp + CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` + // Last update timestamp + UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` +} + +// UpdateGroupsioServiceResponseBody is the type of the "mailing-list" service +// "update-groupsio-service" endpoint HTTP response body. +type UpdateGroupsioServiceResponseBody struct { + // Service ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // Service type + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` + // Service domain + Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + // Email prefix + Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` // Service status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // List of global owner email addresses (required for primary, forbidden for - // shared) - GlobalOwners []string `form:"global_owners,omitempty" json:"global_owners,omitempty" xml:"global_owners,omitempty"` - // Email prefix (required for formation and shared, forbidden for primary) - Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` - // Parent primary service UID (automatically set for shared type services) - ParentServiceUID *string `form:"parent_service_uid,omitempty" json:"parent_service_uid,omitempty" xml:"parent_service_uid,omitempty"` - // Project slug identifier - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // LFXv2 Project UID - ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` - // Service URL - URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` - // GroupsIO group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Whether the service is publicly accessible - Public *bool `form:"public,omitempty" json:"public,omitempty" xml:"public,omitempty"` - // Project name (read-only) - ProjectName *string `form:"project_name,omitempty" json:"project_name,omitempty" xml:"project_name,omitempty"` - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// GetGrpsioServiceSettingsResponseBody is the type of the "mailing-list" -// service "get-grpsio-service-settings" endpoint HTTP response body. -type GetGrpsioServiceSettingsResponseBody GrpsIoServiceSettingsResponseBody - -// UpdateGrpsioServiceSettingsResponseBody is the type of the "mailing-list" -// service "update-grpsio-service-settings" endpoint HTTP response body. -type UpdateGrpsioServiceSettingsResponseBody struct { - // Service UID -- unique identifier for the service - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` - // The timestamp when the service was last reviewed in RFC3339 format - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // The user ID who last reviewed this service - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` - // The user ID who last audited the service - LastAuditedBy *string `form:"last_audited_by,omitempty" json:"last_audited_by,omitempty" xml:"last_audited_by,omitempty"` - // The timestamp when the service was last audited - LastAuditedTime *string `form:"last_audited_time,omitempty" json:"last_audited_time,omitempty" xml:"last_audited_time,omitempty"` - // The timestamp when the service was created (read-only) +} + +// GetGroupsioServiceProjectsResponseBody is the type of the "mailing-list" +// service "get-groupsio-service-projects" endpoint HTTP response body. +type GetGroupsioServiceProjectsResponseBody struct { + // List of project identifiers + Projects []string `form:"projects,omitempty" json:"projects,omitempty" xml:"projects,omitempty"` +} + +// FindParentGroupsioServiceResponseBody is the type of the "mailing-list" +// service "find-parent-groupsio-service" endpoint HTTP response body. +type FindParentGroupsioServiceResponseBody struct { + // Service ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // Service type + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` + // GroupsIO group ID + GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` + // Service domain + Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + // Email prefix + Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` + // Service status + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` } -// CreateGrpsioMailingListResponseBody is the type of the "mailing-list" -// service "create-grpsio-mailing-list" endpoint HTTP response body. -type CreateGrpsioMailingListResponseBody struct { - // Mailing list UID -- unique identifier for the mailing list - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Mailing list group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Mailing list group ID +// ListGroupsioSubgroupsResponseBody is the type of the "mailing-list" service +// "list-groupsio-subgroups" endpoint HTTP response body. +type ListGroupsioSubgroupsResponseBody struct { + // List of subgroups + Items []*GroupsioSubgroupResponseBody `form:"items,omitempty" json:"items,omitempty" xml:"items,omitempty"` + // Total count + Total *int `form:"total,omitempty" json:"total,omitempty" xml:"total,omitempty"` +} + +// CreateGroupsioSubgroupResponseBody is the type of the "mailing-list" service +// "create-groupsio-subgroup" endpoint HTTP response body. +type CreateGroupsioSubgroupResponseBody struct { + // Subgroup ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // LFX v2 committee UID + CommitteeUID *string `form:"committee_uid,omitempty" json:"committee_uid,omitempty" xml:"committee_uid,omitempty"` + // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // Whether the mailing list is publicly accessible - Public *bool `form:"public,omitempty" json:"public,omitempty" xml:"public,omitempty"` - // Mailing list type + // Subgroup name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // Subgroup description + Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` + // Subgroup type Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` - // public: Anyone can join. approval_required: Users must request to join and - // be approved. invite_only: Only invited users can join. + // Audience access setting AudienceAccess *string `form:"audience_access,omitempty" json:"audience_access,omitempty" xml:"audience_access,omitempty"` - // Committees associated with this mailing list (OR logic for access control) - Committees []*CommitteeResponseBody `form:"committees,omitempty" json:"committees,omitempty" xml:"committees,omitempty"` - // Mailing list description (11-500 characters) - Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` - // Mailing list title - Title *string `form:"title,omitempty" json:"title,omitempty" xml:"title,omitempty"` - // Subject tag prefix - SubjectTag *string `form:"subject_tag,omitempty" json:"subject_tag,omitempty" xml:"subject_tag,omitempty"` - // Service UUID - ServiceUID *string `form:"service_uid,omitempty" json:"service_uid,omitempty" xml:"service_uid,omitempty"` - // Number of subscribers in this mailing list (read-only, maintained by service) - SubscriberCount *int `form:"subscriber_count,omitempty" json:"subscriber_count,omitempty" xml:"subscriber_count,omitempty"` - // LFXv2 Project UID (inherited from parent service) - ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` - // Project name (read-only) - ProjectName *string `form:"project_name,omitempty" json:"project_name,omitempty" xml:"project_name,omitempty"` - // Project slug identifier (read-only) - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` } -// GetGrpsioMailingListResponseBody is the type of the "mailing-list" service -// "get-grpsio-mailing-list" endpoint HTTP response body. -type GetGrpsioMailingListResponseBody GrpsIoMailingListWithReadonlyAttributesResponseBody - -// UpdateGrpsioMailingListResponseBody is the type of the "mailing-list" -// service "update-grpsio-mailing-list" endpoint HTTP response body. -type UpdateGrpsioMailingListResponseBody struct { - // Mailing list UID -- unique identifier for the mailing list - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Mailing list group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Mailing list group ID +// GetGroupsioSubgroupResponseBody is the type of the "mailing-list" service +// "get-groupsio-subgroup" endpoint HTTP response body. +type GetGroupsioSubgroupResponseBody struct { + // Subgroup ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // LFX v2 committee UID + CommitteeUID *string `form:"committee_uid,omitempty" json:"committee_uid,omitempty" xml:"committee_uid,omitempty"` + // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // Whether the mailing list is publicly accessible - Public *bool `form:"public,omitempty" json:"public,omitempty" xml:"public,omitempty"` - // Mailing list type + // Subgroup name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // Subgroup description + Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` + // Subgroup type Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` - // public: Anyone can join. approval_required: Users must request to join and - // be approved. invite_only: Only invited users can join. + // Audience access setting AudienceAccess *string `form:"audience_access,omitempty" json:"audience_access,omitempty" xml:"audience_access,omitempty"` - // Committees associated with this mailing list (OR logic for access control) - Committees []*CommitteeResponseBody `form:"committees,omitempty" json:"committees,omitempty" xml:"committees,omitempty"` - // Mailing list description (11-500 characters) - Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` - // Mailing list title - Title *string `form:"title,omitempty" json:"title,omitempty" xml:"title,omitempty"` - // Subject tag prefix - SubjectTag *string `form:"subject_tag,omitempty" json:"subject_tag,omitempty" xml:"subject_tag,omitempty"` - // Service UUID - ServiceUID *string `form:"service_uid,omitempty" json:"service_uid,omitempty" xml:"service_uid,omitempty"` - // Number of subscribers in this mailing list (read-only, maintained by service) - SubscriberCount *int `form:"subscriber_count,omitempty" json:"subscriber_count,omitempty" xml:"subscriber_count,omitempty"` - // LFXv2 Project UID (inherited from parent service) - ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` - // Project name (read-only) - ProjectName *string `form:"project_name,omitempty" json:"project_name,omitempty" xml:"project_name,omitempty"` - // Project slug identifier (read-only) - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` } -// GetGrpsioMailingListSettingsResponseBody is the type of the "mailing-list" -// service "get-grpsio-mailing-list-settings" endpoint HTTP response body. -type GetGrpsioMailingListSettingsResponseBody GrpsIoMailingListSettingsResponseBody - -// UpdateGrpsioMailingListSettingsResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list-settings" endpoint HTTP -// response body. -type UpdateGrpsioMailingListSettingsResponseBody struct { - // Mailing list UID -- unique identifier for the mailing list - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` - // The timestamp when the service was last reviewed in RFC3339 format - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // The user ID who last reviewed this service - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` - // The user ID who last audited the service - LastAuditedBy *string `form:"last_audited_by,omitempty" json:"last_audited_by,omitempty" xml:"last_audited_by,omitempty"` - // The timestamp when the service was last audited - LastAuditedTime *string `form:"last_audited_time,omitempty" json:"last_audited_time,omitempty" xml:"last_audited_time,omitempty"` - // The timestamp when the service was created (read-only) +// UpdateGroupsioSubgroupResponseBody is the type of the "mailing-list" service +// "update-groupsio-subgroup" endpoint HTTP response body. +type UpdateGroupsioSubgroupResponseBody struct { + // Subgroup ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // LFX v2 committee UID + CommitteeUID *string `form:"committee_uid,omitempty" json:"committee_uid,omitempty" xml:"committee_uid,omitempty"` + // GroupsIO group ID + GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` + // Subgroup name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // Subgroup description + Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` + // Subgroup type + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` + // Audience access setting + AudienceAccess *string `form:"audience_access,omitempty" json:"audience_access,omitempty" xml:"audience_access,omitempty"` + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` } -// CreateGrpsioMailingListMemberResponseBody is the type of the "mailing-list" -// service "create-grpsio-mailing-list-member" endpoint HTTP response body. -type CreateGrpsioMailingListMemberResponseBody struct { - // Member UID - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Mailing list UID - MailingListUID *string `form:"mailing_list_uid,omitempty" json:"mailing_list_uid,omitempty" xml:"mailing_list_uid,omitempty"` - // Member username - Username *string `form:"username,omitempty" json:"username,omitempty" xml:"username,omitempty"` +// GetGroupsioSubgroupCountResponseBody is the type of the "mailing-list" +// service "get-groupsio-subgroup-count" endpoint HTTP response body. +type GetGroupsioSubgroupCountResponseBody struct { + // Count value + Count *int `form:"count,omitempty" json:"count,omitempty" xml:"count,omitempty"` +} + +// GetGroupsioSubgroupMemberCountResponseBody is the type of the "mailing-list" +// service "get-groupsio-subgroup-member-count" endpoint HTTP response body. +type GetGroupsioSubgroupMemberCountResponseBody struct { + // Count value + Count *int `form:"count,omitempty" json:"count,omitempty" xml:"count,omitempty"` +} + +// ListGroupsioMembersResponseBody is the type of the "mailing-list" service +// "list-groupsio-members" endpoint HTTP response body. +type ListGroupsioMembersResponseBody struct { + // List of members + Items []*GroupsioMemberResponseBody `form:"items,omitempty" json:"items,omitempty" xml:"items,omitempty"` + // Total count + Total *int `form:"total,omitempty" json:"total,omitempty" xml:"total,omitempty"` +} + +// AddGroupsioMemberResponseBody is the type of the "mailing-list" service +// "add-groupsio-member" endpoint HTTP response body. +type AddGroupsioMemberResponseBody struct { + // Member ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // Subgroup ID + SubgroupID *string `form:"subgroup_id,omitempty" json:"subgroup_id,omitempty" xml:"subgroup_id,omitempty"` + // Member email address + Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` + // Member display name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` // Member first name FirstName *string `form:"first_name,omitempty" json:"first_name,omitempty" xml:"first_name,omitempty"` // Member last name LastName *string `form:"last_name,omitempty" json:"last_name,omitempty" xml:"last_name,omitempty"` - // Member email address - Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` - // Member organization - Organization *string `form:"organization,omitempty" json:"organization,omitempty" xml:"organization,omitempty"` - // Member job title - JobTitle *string `form:"job_title,omitempty" json:"job_title,omitempty" xml:"job_title,omitempty"` - // Member type - MemberType *string `form:"member_type,omitempty" json:"member_type,omitempty" xml:"member_type,omitempty"` - // Email delivery mode - DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` // Moderation status ModStatus *string `form:"mod_status,omitempty" json:"mod_status,omitempty" xml:"mod_status,omitempty"` - // Last reviewed timestamp - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // Last reviewed by user ID - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` + // Email delivery mode + DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` // Member status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // Groups.io member ID - MemberID *int64 `form:"member_id,omitempty" json:"member_id,omitempty" xml:"member_id,omitempty"` - // Groups.io group ID - GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// GetGrpsioMailingListMemberResponseBody is the type of the "mailing-list" -// service "get-grpsio-mailing-list-member" endpoint HTTP response body. -type GetGrpsioMailingListMemberResponseBody GrpsIoMemberWithReadonlyAttributesResponseBody - -// UpdateGrpsioMailingListMemberResponseBody is the type of the "mailing-list" -// service "update-grpsio-mailing-list-member" endpoint HTTP response body. -type UpdateGrpsioMailingListMemberResponseBody struct { - // Member UID - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Mailing list UID - MailingListUID *string `form:"mailing_list_uid,omitempty" json:"mailing_list_uid,omitempty" xml:"mailing_list_uid,omitempty"` - // Member username - Username *string `form:"username,omitempty" json:"username,omitempty" xml:"username,omitempty"` +} + +// GetGroupsioMemberResponseBody is the type of the "mailing-list" service +// "get-groupsio-member" endpoint HTTP response body. +type GetGroupsioMemberResponseBody struct { + // Member ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // Subgroup ID + SubgroupID *string `form:"subgroup_id,omitempty" json:"subgroup_id,omitempty" xml:"subgroup_id,omitempty"` + // Member email address + Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` + // Member display name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` // Member first name FirstName *string `form:"first_name,omitempty" json:"first_name,omitempty" xml:"first_name,omitempty"` // Member last name LastName *string `form:"last_name,omitempty" json:"last_name,omitempty" xml:"last_name,omitempty"` - // Member email address - Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` - // Member organization - Organization *string `form:"organization,omitempty" json:"organization,omitempty" xml:"organization,omitempty"` - // Member job title - JobTitle *string `form:"job_title,omitempty" json:"job_title,omitempty" xml:"job_title,omitempty"` - // Member type - MemberType *string `form:"member_type,omitempty" json:"member_type,omitempty" xml:"member_type,omitempty"` + // Moderation status + ModStatus *string `form:"mod_status,omitempty" json:"mod_status,omitempty" xml:"mod_status,omitempty"` // Email delivery mode DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` + // Member status + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Creation timestamp + CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` + // Last update timestamp + UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` +} + +// UpdateGroupsioMemberResponseBody is the type of the "mailing-list" service +// "update-groupsio-member" endpoint HTTP response body. +type UpdateGroupsioMemberResponseBody struct { + // Member ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // Subgroup ID + SubgroupID *string `form:"subgroup_id,omitempty" json:"subgroup_id,omitempty" xml:"subgroup_id,omitempty"` + // Member email address + Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` + // Member display name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // Member first name + FirstName *string `form:"first_name,omitempty" json:"first_name,omitempty" xml:"first_name,omitempty"` + // Member last name + LastName *string `form:"last_name,omitempty" json:"last_name,omitempty" xml:"last_name,omitempty"` // Moderation status ModStatus *string `form:"mod_status,omitempty" json:"mod_status,omitempty" xml:"mod_status,omitempty"` - // Last reviewed timestamp - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // Last reviewed by user ID - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` + // Email delivery mode + DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` // Member status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // Groups.io member ID - MemberID *int64 `form:"member_id,omitempty" json:"member_id,omitempty" xml:"member_id,omitempty"` - // Groups.io group ID - GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` +} + +// CheckGroupsioSubscriberResponseBody is the type of the "mailing-list" +// service "check-groupsio-subscriber" endpoint HTTP response body. +type CheckGroupsioSubscriberResponseBody struct { + // Whether the email is subscribed + Subscribed *bool `form:"subscribed,omitempty" json:"subscribed,omitempty" xml:"subscribed,omitempty"` } // ReadyzServiceUnavailableResponseBody is the type of the "mailing-list" @@ -551,1104 +438,756 @@ type ReadyzServiceUnavailableResponseBody struct { Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// CreateGrpsioServiceBadRequestResponseBody is the type of the "mailing-list" -// service "create-grpsio-service" endpoint HTTP response body for the +// ListGroupsioServicesBadRequestResponseBody is the type of the "mailing-list" +// service "list-groupsio-services" endpoint HTTP response body for the // "BadRequest" error. -type CreateGrpsioServiceBadRequestResponseBody struct { +type ListGroupsioServicesBadRequestResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// CreateGrpsioServiceConflictResponseBody is the type of the "mailing-list" -// service "create-grpsio-service" endpoint HTTP response body for the -// "Conflict" error. -type CreateGrpsioServiceConflictResponseBody struct { +// ListGroupsioServicesInternalServerErrorResponseBody is the type of the +// "mailing-list" service "list-groupsio-services" endpoint HTTP response body +// for the "InternalServerError" error. +type ListGroupsioServicesInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// CreateGrpsioServiceInternalServerErrorResponseBody is the type of the -// "mailing-list" service "create-grpsio-service" endpoint HTTP response body -// for the "InternalServerError" error. -type CreateGrpsioServiceInternalServerErrorResponseBody struct { +// ListGroupsioServicesServiceUnavailableResponseBody is the type of the +// "mailing-list" service "list-groupsio-services" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type ListGroupsioServicesServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// CreateGrpsioServiceNotFoundResponseBody is the type of the "mailing-list" -// service "create-grpsio-service" endpoint HTTP response body for the -// "NotFound" error. -type CreateGrpsioServiceNotFoundResponseBody struct { +// CreateGroupsioServiceBadRequestResponseBody is the type of the +// "mailing-list" service "create-groupsio-service" endpoint HTTP response body +// for the "BadRequest" error. +type CreateGroupsioServiceBadRequestResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// CreateGrpsioServiceServiceUnavailableResponseBody is the type of the -// "mailing-list" service "create-grpsio-service" endpoint HTTP response body -// for the "ServiceUnavailable" error. -type CreateGrpsioServiceServiceUnavailableResponseBody struct { +// CreateGroupsioServiceConflictResponseBody is the type of the "mailing-list" +// service "create-groupsio-service" endpoint HTTP response body for the +// "Conflict" error. +type CreateGroupsioServiceConflictResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioServiceBadRequestResponseBody is the type of the "mailing-list" -// service "get-grpsio-service" endpoint HTTP response body for the -// "BadRequest" error. -type GetGrpsioServiceBadRequestResponseBody struct { +// CreateGroupsioServiceInternalServerErrorResponseBody is the type of the +// "mailing-list" service "create-groupsio-service" endpoint HTTP response body +// for the "InternalServerError" error. +type CreateGroupsioServiceInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioServiceInternalServerErrorResponseBody is the type of the -// "mailing-list" service "get-grpsio-service" endpoint HTTP response body for -// the "InternalServerError" error. -type GetGrpsioServiceInternalServerErrorResponseBody struct { +// CreateGroupsioServiceServiceUnavailableResponseBody is the type of the +// "mailing-list" service "create-groupsio-service" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type CreateGroupsioServiceServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioServiceNotFoundResponseBody is the type of the "mailing-list" -// service "get-grpsio-service" endpoint HTTP response body for the "NotFound" -// error. -type GetGrpsioServiceNotFoundResponseBody struct { +// GetGroupsioServiceInternalServerErrorResponseBody is the type of the +// "mailing-list" service "get-groupsio-service" endpoint HTTP response body +// for the "InternalServerError" error. +type GetGroupsioServiceInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioServiceServiceUnavailableResponseBody is the type of the -// "mailing-list" service "get-grpsio-service" endpoint HTTP response body for -// the "ServiceUnavailable" error. -type GetGrpsioServiceServiceUnavailableResponseBody struct { +// GetGroupsioServiceNotFoundResponseBody is the type of the "mailing-list" +// service "get-groupsio-service" endpoint HTTP response body for the +// "NotFound" error. +type GetGroupsioServiceNotFoundResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioServiceBadRequestResponseBody is the type of the "mailing-list" -// service "update-grpsio-service" endpoint HTTP response body for the -// "BadRequest" error. -type UpdateGrpsioServiceBadRequestResponseBody struct { +// GetGroupsioServiceServiceUnavailableResponseBody is the type of the +// "mailing-list" service "get-groupsio-service" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type GetGroupsioServiceServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioServiceConflictResponseBody is the type of the "mailing-list" -// service "update-grpsio-service" endpoint HTTP response body for the -// "Conflict" error. -type UpdateGrpsioServiceConflictResponseBody struct { +// UpdateGroupsioServiceBadRequestResponseBody is the type of the +// "mailing-list" service "update-groupsio-service" endpoint HTTP response body +// for the "BadRequest" error. +type UpdateGroupsioServiceBadRequestResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioServiceInternalServerErrorResponseBody is the type of the -// "mailing-list" service "update-grpsio-service" endpoint HTTP response body +// UpdateGroupsioServiceInternalServerErrorResponseBody is the type of the +// "mailing-list" service "update-groupsio-service" endpoint HTTP response body // for the "InternalServerError" error. -type UpdateGrpsioServiceInternalServerErrorResponseBody struct { +type UpdateGroupsioServiceInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioServiceNotFoundResponseBody is the type of the "mailing-list" -// service "update-grpsio-service" endpoint HTTP response body for the +// UpdateGroupsioServiceNotFoundResponseBody is the type of the "mailing-list" +// service "update-groupsio-service" endpoint HTTP response body for the // "NotFound" error. -type UpdateGrpsioServiceNotFoundResponseBody struct { +type UpdateGroupsioServiceNotFoundResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioServiceServiceUnavailableResponseBody is the type of the -// "mailing-list" service "update-grpsio-service" endpoint HTTP response body +// UpdateGroupsioServiceServiceUnavailableResponseBody is the type of the +// "mailing-list" service "update-groupsio-service" endpoint HTTP response body // for the "ServiceUnavailable" error. -type UpdateGrpsioServiceServiceUnavailableResponseBody struct { +type UpdateGroupsioServiceServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioServiceBadRequestResponseBody is the type of the "mailing-list" -// service "delete-grpsio-service" endpoint HTTP response body for the -// "BadRequest" error. -type DeleteGrpsioServiceBadRequestResponseBody struct { +// DeleteGroupsioServiceInternalServerErrorResponseBody is the type of the +// "mailing-list" service "delete-groupsio-service" endpoint HTTP response body +// for the "InternalServerError" error. +type DeleteGroupsioServiceInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioServiceConflictResponseBody is the type of the "mailing-list" -// service "delete-grpsio-service" endpoint HTTP response body for the -// "Conflict" error. -type DeleteGrpsioServiceConflictResponseBody struct { +// DeleteGroupsioServiceNotFoundResponseBody is the type of the "mailing-list" +// service "delete-groupsio-service" endpoint HTTP response body for the +// "NotFound" error. +type DeleteGroupsioServiceNotFoundResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioServiceInternalServerErrorResponseBody is the type of the -// "mailing-list" service "delete-grpsio-service" endpoint HTTP response body -// for the "InternalServerError" error. -type DeleteGrpsioServiceInternalServerErrorResponseBody struct { +// DeleteGroupsioServiceServiceUnavailableResponseBody is the type of the +// "mailing-list" service "delete-groupsio-service" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type DeleteGroupsioServiceServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioServiceNotFoundResponseBody is the type of the "mailing-list" -// service "delete-grpsio-service" endpoint HTTP response body for the -// "NotFound" error. -type DeleteGrpsioServiceNotFoundResponseBody struct { +// GetGroupsioServiceProjectsInternalServerErrorResponseBody is the type of the +// "mailing-list" service "get-groupsio-service-projects" endpoint HTTP +// response body for the "InternalServerError" error. +type GetGroupsioServiceProjectsInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioServiceServiceUnavailableResponseBody is the type of the -// "mailing-list" service "delete-grpsio-service" endpoint HTTP response body -// for the "ServiceUnavailable" error. -type DeleteGrpsioServiceServiceUnavailableResponseBody struct { +// GetGroupsioServiceProjectsServiceUnavailableResponseBody is the type of the +// "mailing-list" service "get-groupsio-service-projects" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type GetGroupsioServiceProjectsServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioServiceSettingsBadRequestResponseBody is the type of the -// "mailing-list" service "get-grpsio-service-settings" endpoint HTTP response +// FindParentGroupsioServiceBadRequestResponseBody is the type of the +// "mailing-list" service "find-parent-groupsio-service" endpoint HTTP response // body for the "BadRequest" error. -type GetGrpsioServiceSettingsBadRequestResponseBody struct { +type FindParentGroupsioServiceBadRequestResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioServiceSettingsInternalServerErrorResponseBody is the type of the -// "mailing-list" service "get-grpsio-service-settings" endpoint HTTP response +// FindParentGroupsioServiceInternalServerErrorResponseBody is the type of the +// "mailing-list" service "find-parent-groupsio-service" endpoint HTTP response // body for the "InternalServerError" error. -type GetGrpsioServiceSettingsInternalServerErrorResponseBody struct { +type FindParentGroupsioServiceInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioServiceSettingsNotFoundResponseBody is the type of the -// "mailing-list" service "get-grpsio-service-settings" endpoint HTTP response +// FindParentGroupsioServiceNotFoundResponseBody is the type of the +// "mailing-list" service "find-parent-groupsio-service" endpoint HTTP response // body for the "NotFound" error. -type GetGrpsioServiceSettingsNotFoundResponseBody struct { +type FindParentGroupsioServiceNotFoundResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioServiceSettingsServiceUnavailableResponseBody is the type of the -// "mailing-list" service "get-grpsio-service-settings" endpoint HTTP response +// FindParentGroupsioServiceServiceUnavailableResponseBody is the type of the +// "mailing-list" service "find-parent-groupsio-service" endpoint HTTP response // body for the "ServiceUnavailable" error. -type GetGrpsioServiceSettingsServiceUnavailableResponseBody struct { - // Error message - Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` -} - -// UpdateGrpsioServiceSettingsBadRequestResponseBody is the type of the -// "mailing-list" service "update-grpsio-service-settings" endpoint HTTP -// response body for the "BadRequest" error. -type UpdateGrpsioServiceSettingsBadRequestResponseBody struct { - // Error message - Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` -} - -// UpdateGrpsioServiceSettingsConflictResponseBody is the type of the -// "mailing-list" service "update-grpsio-service-settings" endpoint HTTP -// response body for the "Conflict" error. -type UpdateGrpsioServiceSettingsConflictResponseBody struct { +type FindParentGroupsioServiceServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioServiceSettingsInternalServerErrorResponseBody is the type of -// the "mailing-list" service "update-grpsio-service-settings" endpoint HTTP -// response body for the "InternalServerError" error. -type UpdateGrpsioServiceSettingsInternalServerErrorResponseBody struct { +// ListGroupsioSubgroupsBadRequestResponseBody is the type of the +// "mailing-list" service "list-groupsio-subgroups" endpoint HTTP response body +// for the "BadRequest" error. +type ListGroupsioSubgroupsBadRequestResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioServiceSettingsNotFoundResponseBody is the type of the -// "mailing-list" service "update-grpsio-service-settings" endpoint HTTP -// response body for the "NotFound" error. -type UpdateGrpsioServiceSettingsNotFoundResponseBody struct { +// ListGroupsioSubgroupsInternalServerErrorResponseBody is the type of the +// "mailing-list" service "list-groupsio-subgroups" endpoint HTTP response body +// for the "InternalServerError" error. +type ListGroupsioSubgroupsInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioServiceSettingsServiceUnavailableResponseBody is the type of the -// "mailing-list" service "update-grpsio-service-settings" endpoint HTTP -// response body for the "ServiceUnavailable" error. -type UpdateGrpsioServiceSettingsServiceUnavailableResponseBody struct { +// ListGroupsioSubgroupsServiceUnavailableResponseBody is the type of the +// "mailing-list" service "list-groupsio-subgroups" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type ListGroupsioSubgroupsServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// CreateGrpsioMailingListBadRequestResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list" endpoint HTTP response +// CreateGroupsioSubgroupBadRequestResponseBody is the type of the +// "mailing-list" service "create-groupsio-subgroup" endpoint HTTP response // body for the "BadRequest" error. -type CreateGrpsioMailingListBadRequestResponseBody struct { +type CreateGroupsioSubgroupBadRequestResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// CreateGrpsioMailingListConflictResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list" endpoint HTTP response -// body for the "Conflict" error. -type CreateGrpsioMailingListConflictResponseBody struct { +// CreateGroupsioSubgroupConflictResponseBody is the type of the "mailing-list" +// service "create-groupsio-subgroup" endpoint HTTP response body for the +// "Conflict" error. +type CreateGroupsioSubgroupConflictResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// CreateGrpsioMailingListInternalServerErrorResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list" endpoint HTTP response +// CreateGroupsioSubgroupInternalServerErrorResponseBody is the type of the +// "mailing-list" service "create-groupsio-subgroup" endpoint HTTP response // body for the "InternalServerError" error. -type CreateGrpsioMailingListInternalServerErrorResponseBody struct { +type CreateGroupsioSubgroupInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// CreateGrpsioMailingListNotFoundResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list" endpoint HTTP response -// body for the "NotFound" error. -type CreateGrpsioMailingListNotFoundResponseBody struct { +// CreateGroupsioSubgroupServiceUnavailableResponseBody is the type of the +// "mailing-list" service "create-groupsio-subgroup" endpoint HTTP response +// body for the "ServiceUnavailable" error. +type CreateGroupsioSubgroupServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// CreateGrpsioMailingListServiceUnavailableResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list" endpoint HTTP response -// body for the "ServiceUnavailable" error. -type CreateGrpsioMailingListServiceUnavailableResponseBody struct { +// GetGroupsioSubgroupInternalServerErrorResponseBody is the type of the +// "mailing-list" service "get-groupsio-subgroup" endpoint HTTP response body +// for the "InternalServerError" error. +type GetGroupsioSubgroupInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioMailingListBadRequestResponseBody is the type of the "mailing-list" -// service "get-grpsio-mailing-list" endpoint HTTP response body for the -// "BadRequest" error. -type GetGrpsioMailingListBadRequestResponseBody struct { +// GetGroupsioSubgroupNotFoundResponseBody is the type of the "mailing-list" +// service "get-groupsio-subgroup" endpoint HTTP response body for the +// "NotFound" error. +type GetGroupsioSubgroupNotFoundResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioMailingListInternalServerErrorResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list" endpoint HTTP response body -// for the "InternalServerError" error. -type GetGrpsioMailingListInternalServerErrorResponseBody struct { +// GetGroupsioSubgroupServiceUnavailableResponseBody is the type of the +// "mailing-list" service "get-groupsio-subgroup" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type GetGroupsioSubgroupServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioMailingListNotFoundResponseBody is the type of the "mailing-list" -// service "get-grpsio-mailing-list" endpoint HTTP response body for the -// "NotFound" error. -type GetGrpsioMailingListNotFoundResponseBody struct { +// UpdateGroupsioSubgroupBadRequestResponseBody is the type of the +// "mailing-list" service "update-groupsio-subgroup" endpoint HTTP response +// body for the "BadRequest" error. +type UpdateGroupsioSubgroupBadRequestResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioMailingListServiceUnavailableResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list" endpoint HTTP response body -// for the "ServiceUnavailable" error. -type GetGrpsioMailingListServiceUnavailableResponseBody struct { +// UpdateGroupsioSubgroupInternalServerErrorResponseBody is the type of the +// "mailing-list" service "update-groupsio-subgroup" endpoint HTTP response +// body for the "InternalServerError" error. +type UpdateGroupsioSubgroupInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListBadRequestResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list" endpoint HTTP response -// body for the "BadRequest" error. -type UpdateGrpsioMailingListBadRequestResponseBody struct { +// UpdateGroupsioSubgroupNotFoundResponseBody is the type of the "mailing-list" +// service "update-groupsio-subgroup" endpoint HTTP response body for the +// "NotFound" error. +type UpdateGroupsioSubgroupNotFoundResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListConflictResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list" endpoint HTTP response -// body for the "Conflict" error. -type UpdateGrpsioMailingListConflictResponseBody struct { +// UpdateGroupsioSubgroupServiceUnavailableResponseBody is the type of the +// "mailing-list" service "update-groupsio-subgroup" endpoint HTTP response +// body for the "ServiceUnavailable" error. +type UpdateGroupsioSubgroupServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListInternalServerErrorResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list" endpoint HTTP response +// DeleteGroupsioSubgroupInternalServerErrorResponseBody is the type of the +// "mailing-list" service "delete-groupsio-subgroup" endpoint HTTP response // body for the "InternalServerError" error. -type UpdateGrpsioMailingListInternalServerErrorResponseBody struct { +type DeleteGroupsioSubgroupInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListNotFoundResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list" endpoint HTTP response -// body for the "NotFound" error. -type UpdateGrpsioMailingListNotFoundResponseBody struct { +// DeleteGroupsioSubgroupNotFoundResponseBody is the type of the "mailing-list" +// service "delete-groupsio-subgroup" endpoint HTTP response body for the +// "NotFound" error. +type DeleteGroupsioSubgroupNotFoundResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListServiceUnavailableResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list" endpoint HTTP response +// DeleteGroupsioSubgroupServiceUnavailableResponseBody is the type of the +// "mailing-list" service "delete-groupsio-subgroup" endpoint HTTP response // body for the "ServiceUnavailable" error. -type UpdateGrpsioMailingListServiceUnavailableResponseBody struct { +type DeleteGroupsioSubgroupServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioMailingListBadRequestResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list" endpoint HTTP response +// GetGroupsioSubgroupCountBadRequestResponseBody is the type of the +// "mailing-list" service "get-groupsio-subgroup-count" endpoint HTTP response // body for the "BadRequest" error. -type DeleteGrpsioMailingListBadRequestResponseBody struct { +type GetGroupsioSubgroupCountBadRequestResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioMailingListConflictResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list" endpoint HTTP response -// body for the "Conflict" error. -type DeleteGrpsioMailingListConflictResponseBody struct { +// GetGroupsioSubgroupCountInternalServerErrorResponseBody is the type of the +// "mailing-list" service "get-groupsio-subgroup-count" endpoint HTTP response +// body for the "InternalServerError" error. +type GetGroupsioSubgroupCountInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioMailingListInternalServerErrorResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list" endpoint HTTP response -// body for the "InternalServerError" error. -type DeleteGrpsioMailingListInternalServerErrorResponseBody struct { +// GetGroupsioSubgroupCountServiceUnavailableResponseBody is the type of the +// "mailing-list" service "get-groupsio-subgroup-count" endpoint HTTP response +// body for the "ServiceUnavailable" error. +type GetGroupsioSubgroupCountServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioMailingListNotFoundResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list" endpoint HTTP response -// body for the "NotFound" error. -type DeleteGrpsioMailingListNotFoundResponseBody struct { +// GetGroupsioSubgroupMemberCountInternalServerErrorResponseBody is the type of +// the "mailing-list" service "get-groupsio-subgroup-member-count" endpoint +// HTTP response body for the "InternalServerError" error. +type GetGroupsioSubgroupMemberCountInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioMailingListServiceUnavailableResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list" endpoint HTTP response -// body for the "ServiceUnavailable" error. -type DeleteGrpsioMailingListServiceUnavailableResponseBody struct { +// GetGroupsioSubgroupMemberCountNotFoundResponseBody is the type of the +// "mailing-list" service "get-groupsio-subgroup-member-count" endpoint HTTP +// response body for the "NotFound" error. +type GetGroupsioSubgroupMemberCountNotFoundResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioMailingListSettingsBadRequestResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list-settings" endpoint HTTP -// response body for the "BadRequest" error. -type GetGrpsioMailingListSettingsBadRequestResponseBody struct { +// GetGroupsioSubgroupMemberCountServiceUnavailableResponseBody is the type of +// the "mailing-list" service "get-groupsio-subgroup-member-count" endpoint +// HTTP response body for the "ServiceUnavailable" error. +type GetGroupsioSubgroupMemberCountServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioMailingListSettingsInternalServerErrorResponseBody is the type of -// the "mailing-list" service "get-grpsio-mailing-list-settings" endpoint HTTP -// response body for the "InternalServerError" error. -type GetGrpsioMailingListSettingsInternalServerErrorResponseBody struct { +// ListGroupsioMembersInternalServerErrorResponseBody is the type of the +// "mailing-list" service "list-groupsio-members" endpoint HTTP response body +// for the "InternalServerError" error. +type ListGroupsioMembersInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioMailingListSettingsNotFoundResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list-settings" endpoint HTTP -// response body for the "NotFound" error. -type GetGrpsioMailingListSettingsNotFoundResponseBody struct { +// ListGroupsioMembersNotFoundResponseBody is the type of the "mailing-list" +// service "list-groupsio-members" endpoint HTTP response body for the +// "NotFound" error. +type ListGroupsioMembersNotFoundResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioMailingListSettingsServiceUnavailableResponseBody is the type of -// the "mailing-list" service "get-grpsio-mailing-list-settings" endpoint HTTP -// response body for the "ServiceUnavailable" error. -type GetGrpsioMailingListSettingsServiceUnavailableResponseBody struct { +// ListGroupsioMembersServiceUnavailableResponseBody is the type of the +// "mailing-list" service "list-groupsio-members" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type ListGroupsioMembersServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListSettingsBadRequestResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list-settings" endpoint HTTP -// response body for the "BadRequest" error. -type UpdateGrpsioMailingListSettingsBadRequestResponseBody struct { +// AddGroupsioMemberBadRequestResponseBody is the type of the "mailing-list" +// service "add-groupsio-member" endpoint HTTP response body for the +// "BadRequest" error. +type AddGroupsioMemberBadRequestResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListSettingsConflictResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list-settings" endpoint HTTP -// response body for the "Conflict" error. -type UpdateGrpsioMailingListSettingsConflictResponseBody struct { +// AddGroupsioMemberConflictResponseBody is the type of the "mailing-list" +// service "add-groupsio-member" endpoint HTTP response body for the "Conflict" +// error. +type AddGroupsioMemberConflictResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListSettingsInternalServerErrorResponseBody is the type -// of the "mailing-list" service "update-grpsio-mailing-list-settings" endpoint -// HTTP response body for the "InternalServerError" error. -type UpdateGrpsioMailingListSettingsInternalServerErrorResponseBody struct { +// AddGroupsioMemberInternalServerErrorResponseBody is the type of the +// "mailing-list" service "add-groupsio-member" endpoint HTTP response body for +// the "InternalServerError" error. +type AddGroupsioMemberInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListSettingsNotFoundResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list-settings" endpoint HTTP -// response body for the "NotFound" error. -type UpdateGrpsioMailingListSettingsNotFoundResponseBody struct { +// AddGroupsioMemberNotFoundResponseBody is the type of the "mailing-list" +// service "add-groupsio-member" endpoint HTTP response body for the "NotFound" +// error. +type AddGroupsioMemberNotFoundResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListSettingsServiceUnavailableResponseBody is the type of -// the "mailing-list" service "update-grpsio-mailing-list-settings" endpoint -// HTTP response body for the "ServiceUnavailable" error. -type UpdateGrpsioMailingListSettingsServiceUnavailableResponseBody struct { +// AddGroupsioMemberServiceUnavailableResponseBody is the type of the +// "mailing-list" service "add-groupsio-member" endpoint HTTP response body for +// the "ServiceUnavailable" error. +type AddGroupsioMemberServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// CreateGrpsioMailingListMemberBadRequestResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list-member" endpoint HTTP -// response body for the "BadRequest" error. -type CreateGrpsioMailingListMemberBadRequestResponseBody struct { +// GetGroupsioMemberInternalServerErrorResponseBody is the type of the +// "mailing-list" service "get-groupsio-member" endpoint HTTP response body for +// the "InternalServerError" error. +type GetGroupsioMemberInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// CreateGrpsioMailingListMemberConflictResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list-member" endpoint HTTP -// response body for the "Conflict" error. -type CreateGrpsioMailingListMemberConflictResponseBody struct { - // Error message - Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` -} - -// CreateGrpsioMailingListMemberInternalServerErrorResponseBody is the type of -// the "mailing-list" service "create-grpsio-mailing-list-member" endpoint HTTP -// response body for the "InternalServerError" error. -type CreateGrpsioMailingListMemberInternalServerErrorResponseBody struct { - // Error message - Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` -} - -// CreateGrpsioMailingListMemberNotFoundResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list-member" endpoint HTTP -// response body for the "NotFound" error. -type CreateGrpsioMailingListMemberNotFoundResponseBody struct { - // Error message - Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` -} - -// CreateGrpsioMailingListMemberServiceUnavailableResponseBody is the type of -// the "mailing-list" service "create-grpsio-mailing-list-member" endpoint HTTP -// response body for the "ServiceUnavailable" error. -type CreateGrpsioMailingListMemberServiceUnavailableResponseBody struct { - // Error message - Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` -} - -// GetGrpsioMailingListMemberBadRequestResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list-member" endpoint HTTP -// response body for the "BadRequest" error. -type GetGrpsioMailingListMemberBadRequestResponseBody struct { +// GetGroupsioMemberNotFoundResponseBody is the type of the "mailing-list" +// service "get-groupsio-member" endpoint HTTP response body for the "NotFound" +// error. +type GetGroupsioMemberNotFoundResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioMailingListMemberInternalServerErrorResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list-member" endpoint HTTP -// response body for the "InternalServerError" error. -type GetGrpsioMailingListMemberInternalServerErrorResponseBody struct { +// GetGroupsioMemberServiceUnavailableResponseBody is the type of the +// "mailing-list" service "get-groupsio-member" endpoint HTTP response body for +// the "ServiceUnavailable" error. +type GetGroupsioMemberServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioMailingListMemberNotFoundResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list-member" endpoint HTTP -// response body for the "NotFound" error. -type GetGrpsioMailingListMemberNotFoundResponseBody struct { +// UpdateGroupsioMemberBadRequestResponseBody is the type of the "mailing-list" +// service "update-groupsio-member" endpoint HTTP response body for the +// "BadRequest" error. +type UpdateGroupsioMemberBadRequestResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GetGrpsioMailingListMemberServiceUnavailableResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list-member" endpoint HTTP -// response body for the "ServiceUnavailable" error. -type GetGrpsioMailingListMemberServiceUnavailableResponseBody struct { +// UpdateGroupsioMemberInternalServerErrorResponseBody is the type of the +// "mailing-list" service "update-groupsio-member" endpoint HTTP response body +// for the "InternalServerError" error. +type UpdateGroupsioMemberInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListMemberBadRequestResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list-member" endpoint HTTP -// response body for the "BadRequest" error. -type UpdateGrpsioMailingListMemberBadRequestResponseBody struct { +// UpdateGroupsioMemberNotFoundResponseBody is the type of the "mailing-list" +// service "update-groupsio-member" endpoint HTTP response body for the +// "NotFound" error. +type UpdateGroupsioMemberNotFoundResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListMemberConflictResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list-member" endpoint HTTP -// response body for the "Conflict" error. -type UpdateGrpsioMailingListMemberConflictResponseBody struct { +// UpdateGroupsioMemberServiceUnavailableResponseBody is the type of the +// "mailing-list" service "update-groupsio-member" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type UpdateGroupsioMemberServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListMemberInternalServerErrorResponseBody is the type of -// the "mailing-list" service "update-grpsio-mailing-list-member" endpoint HTTP -// response body for the "InternalServerError" error. -type UpdateGrpsioMailingListMemberInternalServerErrorResponseBody struct { +// DeleteGroupsioMemberInternalServerErrorResponseBody is the type of the +// "mailing-list" service "delete-groupsio-member" endpoint HTTP response body +// for the "InternalServerError" error. +type DeleteGroupsioMemberInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListMemberNotFoundResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list-member" endpoint HTTP -// response body for the "NotFound" error. -type UpdateGrpsioMailingListMemberNotFoundResponseBody struct { +// DeleteGroupsioMemberNotFoundResponseBody is the type of the "mailing-list" +// service "delete-groupsio-member" endpoint HTTP response body for the +// "NotFound" error. +type DeleteGroupsioMemberNotFoundResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UpdateGrpsioMailingListMemberServiceUnavailableResponseBody is the type of -// the "mailing-list" service "update-grpsio-mailing-list-member" endpoint HTTP -// response body for the "ServiceUnavailable" error. -type UpdateGrpsioMailingListMemberServiceUnavailableResponseBody struct { +// DeleteGroupsioMemberServiceUnavailableResponseBody is the type of the +// "mailing-list" service "delete-groupsio-member" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type DeleteGroupsioMemberServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioMailingListMemberBadRequestResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list-member" endpoint HTTP -// response body for the "BadRequest" error. -type DeleteGrpsioMailingListMemberBadRequestResponseBody struct { +// InviteGroupsioMembersBadRequestResponseBody is the type of the +// "mailing-list" service "invite-groupsio-members" endpoint HTTP response body +// for the "BadRequest" error. +type InviteGroupsioMembersBadRequestResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioMailingListMemberConflictResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list-member" endpoint HTTP -// response body for the "Conflict" error. -type DeleteGrpsioMailingListMemberConflictResponseBody struct { +// InviteGroupsioMembersInternalServerErrorResponseBody is the type of the +// "mailing-list" service "invite-groupsio-members" endpoint HTTP response body +// for the "InternalServerError" error. +type InviteGroupsioMembersInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioMailingListMemberInternalServerErrorResponseBody is the type of -// the "mailing-list" service "delete-grpsio-mailing-list-member" endpoint HTTP -// response body for the "InternalServerError" error. -type DeleteGrpsioMailingListMemberInternalServerErrorResponseBody struct { +// InviteGroupsioMembersNotFoundResponseBody is the type of the "mailing-list" +// service "invite-groupsio-members" endpoint HTTP response body for the +// "NotFound" error. +type InviteGroupsioMembersNotFoundResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioMailingListMemberNotFoundResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list-member" endpoint HTTP -// response body for the "NotFound" error. -type DeleteGrpsioMailingListMemberNotFoundResponseBody struct { +// InviteGroupsioMembersServiceUnavailableResponseBody is the type of the +// "mailing-list" service "invite-groupsio-members" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type InviteGroupsioMembersServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// DeleteGrpsioMailingListMemberServiceUnavailableResponseBody is the type of -// the "mailing-list" service "delete-grpsio-mailing-list-member" endpoint HTTP -// response body for the "ServiceUnavailable" error. -type DeleteGrpsioMailingListMemberServiceUnavailableResponseBody struct { +// CheckGroupsioSubscriberBadRequestResponseBody is the type of the +// "mailing-list" service "check-groupsio-subscriber" endpoint HTTP response +// body for the "BadRequest" error. +type CheckGroupsioSubscriberBadRequestResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GroupsioWebhookBadRequestResponseBody is the type of the "mailing-list" -// service "groupsio-webhook" endpoint HTTP response body for the "BadRequest" -// error. -type GroupsioWebhookBadRequestResponseBody struct { +// CheckGroupsioSubscriberInternalServerErrorResponseBody is the type of the +// "mailing-list" service "check-groupsio-subscriber" endpoint HTTP response +// body for the "InternalServerError" error. +type CheckGroupsioSubscriberInternalServerErrorResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// GroupsioWebhookUnauthorizedResponseBody is the type of the "mailing-list" -// service "groupsio-webhook" endpoint HTTP response body for the -// "Unauthorized" error. -type GroupsioWebhookUnauthorizedResponseBody struct { +// CheckGroupsioSubscriberServiceUnavailableResponseBody is the type of the +// "mailing-list" service "check-groupsio-subscriber" endpoint HTTP response +// body for the "ServiceUnavailable" error. +type CheckGroupsioSubscriberServiceUnavailableResponseBody struct { // Error message Message *string `form:"message,omitempty" json:"message,omitempty" xml:"message,omitempty"` } -// UserInfoRequestBody is used to define fields on request body types. -type UserInfoRequestBody struct { - // The full name of the user - Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` - // The email address of the user - Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` - // The username/LFID of the user - Username *string `form:"username,omitempty" json:"username,omitempty" xml:"username,omitempty"` - // The avatar URL of the user - Avatar *string `form:"avatar,omitempty" json:"avatar,omitempty" xml:"avatar,omitempty"` -} - -// UserInfoResponseBody is used to define fields on response body types. -type UserInfoResponseBody struct { - // The full name of the user - Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` - // The email address of the user - Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` - // The username/LFID of the user - Username *string `form:"username,omitempty" json:"username,omitempty" xml:"username,omitempty"` - // The avatar URL of the user - Avatar *string `form:"avatar,omitempty" json:"avatar,omitempty" xml:"avatar,omitempty"` -} - -// GrpsIoServiceWithReadonlyAttributesResponseBody is used to define fields on -// response body types. -type GrpsIoServiceWithReadonlyAttributesResponseBody struct { - // Service UID -- unique identifier for the service - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` +// GroupsioServiceResponseBody is used to define fields on response body types. +type GroupsioServiceResponseBody struct { + // Service ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` // Service type Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` - // Service domain - Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` + // Service domain + Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + // Email prefix + Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` // Service status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // List of global owner email addresses (required for primary, forbidden for - // shared) - GlobalOwners []string `form:"global_owners,omitempty" json:"global_owners,omitempty" xml:"global_owners,omitempty"` - // Email prefix (required for formation and shared, forbidden for primary) - Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` - // Parent primary service UID (automatically set for shared type services) - ParentServiceUID *string `form:"parent_service_uid,omitempty" json:"parent_service_uid,omitempty" xml:"parent_service_uid,omitempty"` - // Project slug identifier - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // LFXv2 Project UID - ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` - // Service URL - URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` - // GroupsIO group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Whether the service is publicly accessible - Public *bool `form:"public,omitempty" json:"public,omitempty" xml:"public,omitempty"` - // Project name (read-only) - ProjectName *string `form:"project_name,omitempty" json:"project_name,omitempty" xml:"project_name,omitempty"` - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// GrpsIoServiceSettingsResponseBody is used to define fields on response body -// types. -type GrpsIoServiceSettingsResponseBody struct { - // Service UID -- unique identifier for the service - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` - // The timestamp when the service was last reviewed in RFC3339 format - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // The user ID who last reviewed this service - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` - // The user ID who last audited the service - LastAuditedBy *string `form:"last_audited_by,omitempty" json:"last_audited_by,omitempty" xml:"last_audited_by,omitempty"` - // The timestamp when the service was last audited - LastAuditedTime *string `form:"last_audited_time,omitempty" json:"last_audited_time,omitempty" xml:"last_audited_time,omitempty"` - // The timestamp when the service was created (read-only) - CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) - UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` -} - -// CommitteeRequestBody is used to define fields on request body types. -type CommitteeRequestBody struct { - // Committee UUID - UID string `form:"uid" json:"uid" xml:"uid"` - // Committee name (read-only, populated by server) - Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` - // Committee member voting statuses that determine which members are synced - AllowedVotingStatuses []string `form:"allowed_voting_statuses,omitempty" json:"allowed_voting_statuses,omitempty" xml:"allowed_voting_statuses,omitempty"` } -// CommitteeResponseBody is used to define fields on response body types. -type CommitteeResponseBody struct { - // Committee UUID - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Committee name (read-only, populated by server) - Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` - // Committee member voting statuses that determine which members are synced - AllowedVotingStatuses []string `form:"allowed_voting_statuses,omitempty" json:"allowed_voting_statuses,omitempty" xml:"allowed_voting_statuses,omitempty"` -} - -// GrpsIoMailingListWithReadonlyAttributesResponseBody is used to define fields -// on response body types. -type GrpsIoMailingListWithReadonlyAttributesResponseBody struct { - // Mailing list UID -- unique identifier for the mailing list - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Mailing list group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Mailing list group ID +// GroupsioSubgroupResponseBody is used to define fields on response body types. +type GroupsioSubgroupResponseBody struct { + // Subgroup ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // LFX v2 committee UID + CommitteeUID *string `form:"committee_uid,omitempty" json:"committee_uid,omitempty" xml:"committee_uid,omitempty"` + // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // Whether the mailing list is publicly accessible - Public *bool `form:"public,omitempty" json:"public,omitempty" xml:"public,omitempty"` - // Mailing list type + // Subgroup name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // Subgroup description + Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` + // Subgroup type Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` - // public: Anyone can join. approval_required: Users must request to join and - // be approved. invite_only: Only invited users can join. + // Audience access setting AudienceAccess *string `form:"audience_access,omitempty" json:"audience_access,omitempty" xml:"audience_access,omitempty"` - // Committees associated with this mailing list (OR logic for access control) - Committees []*CommitteeResponseBody `form:"committees,omitempty" json:"committees,omitempty" xml:"committees,omitempty"` - // Mailing list description (11-500 characters) - Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` - // Mailing list title - Title *string `form:"title,omitempty" json:"title,omitempty" xml:"title,omitempty"` - // Subject tag prefix - SubjectTag *string `form:"subject_tag,omitempty" json:"subject_tag,omitempty" xml:"subject_tag,omitempty"` - // Service UUID - ServiceUID *string `form:"service_uid,omitempty" json:"service_uid,omitempty" xml:"service_uid,omitempty"` - // Number of subscribers in this mailing list (read-only, maintained by service) - SubscriberCount *int `form:"subscriber_count,omitempty" json:"subscriber_count,omitempty" xml:"subscriber_count,omitempty"` - // LFXv2 Project UID (inherited from parent service) - ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` - // Project name (read-only) - ProjectName *string `form:"project_name,omitempty" json:"project_name,omitempty" xml:"project_name,omitempty"` - // Project slug identifier (read-only) - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` } -// GrpsIoMailingListSettingsResponseBody is used to define fields on response -// body types. -type GrpsIoMailingListSettingsResponseBody struct { - // Mailing list UID -- unique identifier for the mailing list - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` - // The timestamp when the service was last reviewed in RFC3339 format - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // The user ID who last reviewed this service - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` - // The user ID who last audited the service - LastAuditedBy *string `form:"last_audited_by,omitempty" json:"last_audited_by,omitempty" xml:"last_audited_by,omitempty"` - // The timestamp when the service was last audited - LastAuditedTime *string `form:"last_audited_time,omitempty" json:"last_audited_time,omitempty" xml:"last_audited_time,omitempty"` - // The timestamp when the service was created (read-only) - CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) - UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` -} - -// GrpsIoMemberWithReadonlyAttributesResponseBody is used to define fields on -// response body types. -type GrpsIoMemberWithReadonlyAttributesResponseBody struct { - // Member UID - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Mailing list UID - MailingListUID *string `form:"mailing_list_uid,omitempty" json:"mailing_list_uid,omitempty" xml:"mailing_list_uid,omitempty"` - // Member username - Username *string `form:"username,omitempty" json:"username,omitempty" xml:"username,omitempty"` +// GroupsioMemberResponseBody is used to define fields on response body types. +type GroupsioMemberResponseBody struct { + // Member ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // Subgroup ID + SubgroupID *string `form:"subgroup_id,omitempty" json:"subgroup_id,omitempty" xml:"subgroup_id,omitempty"` + // Member email address + Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` + // Member display name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` // Member first name FirstName *string `form:"first_name,omitempty" json:"first_name,omitempty" xml:"first_name,omitempty"` // Member last name LastName *string `form:"last_name,omitempty" json:"last_name,omitempty" xml:"last_name,omitempty"` - // Member email address - Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` - // Member organization - Organization *string `form:"organization,omitempty" json:"organization,omitempty" xml:"organization,omitempty"` - // Member job title - JobTitle *string `form:"job_title,omitempty" json:"job_title,omitempty" xml:"job_title,omitempty"` - // Member type - MemberType *string `form:"member_type,omitempty" json:"member_type,omitempty" xml:"member_type,omitempty"` - // Email delivery mode - DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` // Moderation status ModStatus *string `form:"mod_status,omitempty" json:"mod_status,omitempty" xml:"mod_status,omitempty"` - // Last reviewed timestamp - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // Last reviewed by user ID - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` + // Email delivery mode + DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` // Member status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // Groups.io member ID - MemberID *int64 `form:"member_id,omitempty" json:"member_id,omitempty" xml:"member_id,omitempty"` - // Groups.io group ID - GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` } -// NewCreateGrpsioServiceRequestBody builds the HTTP request body from the -// payload of the "create-grpsio-service" endpoint of the "mailing-list" +// NewCreateGroupsioServiceRequestBody builds the HTTP request body from the +// payload of the "create-groupsio-service" endpoint of the "mailing-list" // service. -func NewCreateGrpsioServiceRequestBody(p *mailinglist.CreateGrpsioServicePayload) *CreateGrpsioServiceRequestBody { - body := &CreateGrpsioServiceRequestBody{ - Type: p.Type, - Domain: p.Domain, - GroupID: p.GroupID, - Status: p.Status, - Prefix: p.Prefix, - ParentServiceUID: p.ParentServiceUID, - ProjectSlug: p.ProjectSlug, - ProjectUID: p.ProjectUID, - URL: p.URL, - GroupName: p.GroupName, - Public: p.Public, - } - if p.GlobalOwners != nil { - body.GlobalOwners = make([]string, len(p.GlobalOwners)) - for i, val := range p.GlobalOwners { - body.GlobalOwners[i] = val - } - } - { - var zero bool - if body.Public == zero { - body.Public = false - } - } - if p.Writers != nil { - body.Writers = make([]*UserInfoRequestBody, len(p.Writers)) - for i, val := range p.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoRequestBody(val) - } - } - if p.Auditors != nil { - body.Auditors = make([]*UserInfoRequestBody, len(p.Auditors)) - for i, val := range p.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoRequestBody(val) - } +func NewCreateGroupsioServiceRequestBody(p *mailinglist.CreateGroupsioServicePayload) *CreateGroupsioServiceRequestBody { + body := &CreateGroupsioServiceRequestBody{ + ProjectUID: p.ProjectUID, + Type: p.Type, + GroupID: p.GroupID, + Domain: p.Domain, + Prefix: p.Prefix, + Status: p.Status, } return body } -// NewUpdateGrpsioServiceRequestBody builds the HTTP request body from the -// payload of the "update-grpsio-service" endpoint of the "mailing-list" +// NewUpdateGroupsioServiceRequestBody builds the HTTP request body from the +// payload of the "update-groupsio-service" endpoint of the "mailing-list" // service. -func NewUpdateGrpsioServiceRequestBody(p *mailinglist.UpdateGrpsioServicePayload) *UpdateGrpsioServiceRequestBody { - body := &UpdateGrpsioServiceRequestBody{ - Type: p.Type, - Domain: p.Domain, - GroupID: p.GroupID, - Status: p.Status, - Prefix: p.Prefix, - ParentServiceUID: p.ParentServiceUID, - ProjectSlug: p.ProjectSlug, - ProjectUID: p.ProjectUID, - URL: p.URL, - GroupName: p.GroupName, - Public: p.Public, - } - if p.GlobalOwners != nil { - body.GlobalOwners = make([]string, len(p.GlobalOwners)) - for i, val := range p.GlobalOwners { - body.GlobalOwners[i] = val - } - } - { - var zero bool - if body.Public == zero { - body.Public = false - } +func NewUpdateGroupsioServiceRequestBody(p *mailinglist.UpdateGroupsioServicePayload) *UpdateGroupsioServiceRequestBody { + body := &UpdateGroupsioServiceRequestBody{ + ProjectUID: p.ProjectUID, + Type: p.Type, + GroupID: p.GroupID, + Domain: p.Domain, + Prefix: p.Prefix, + Status: p.Status, } return body } -// NewUpdateGrpsioServiceSettingsRequestBody builds the HTTP request body from -// the payload of the "update-grpsio-service-settings" endpoint of the -// "mailing-list" service. -func NewUpdateGrpsioServiceSettingsRequestBody(p *mailinglist.UpdateGrpsioServiceSettingsPayload) *UpdateGrpsioServiceSettingsRequestBody { - body := &UpdateGrpsioServiceSettingsRequestBody{} - if p.Writers != nil { - body.Writers = make([]*UserInfoRequestBody, len(p.Writers)) - for i, val := range p.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoRequestBody(val) - } - } - if p.Auditors != nil { - body.Auditors = make([]*UserInfoRequestBody, len(p.Auditors)) - for i, val := range p.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoRequestBody(val) - } - } - return body -} - -// NewCreateGrpsioMailingListRequestBody builds the HTTP request body from the -// payload of the "create-grpsio-mailing-list" endpoint of the "mailing-list" +// NewCreateGroupsioSubgroupRequestBody builds the HTTP request body from the +// payload of the "create-groupsio-subgroup" endpoint of the "mailing-list" // service. -func NewCreateGrpsioMailingListRequestBody(p *mailinglist.CreateGrpsioMailingListPayload) *CreateGrpsioMailingListRequestBody { - body := &CreateGrpsioMailingListRequestBody{ - GroupName: p.GroupName, - GroupID: p.GroupID, - Public: p.Public, - Type: p.Type, - AudienceAccess: p.AudienceAccess, - Description: p.Description, - Title: p.Title, - SubjectTag: p.SubjectTag, - ServiceUID: p.ServiceUID, - SubscriberCount: p.SubscriberCount, - } - { - var zero string - if body.AudienceAccess == zero { - body.AudienceAccess = "public" - } - } - if p.Committees != nil { - body.Committees = make([]*CommitteeRequestBody, len(p.Committees)) - for i, val := range p.Committees { - body.Committees[i] = marshalMailinglistCommitteeToCommitteeRequestBody(val) - } - } - if p.Writers != nil { - body.Writers = make([]*UserInfoRequestBody, len(p.Writers)) - for i, val := range p.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoRequestBody(val) - } - } - if p.Auditors != nil { - body.Auditors = make([]*UserInfoRequestBody, len(p.Auditors)) - for i, val := range p.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoRequestBody(val) - } +func NewCreateGroupsioSubgroupRequestBody(p *mailinglist.CreateGroupsioSubgroupPayload) *CreateGroupsioSubgroupRequestBody { + body := &CreateGroupsioSubgroupRequestBody{ + ProjectUID: p.ProjectUID, + CommitteeUID: p.CommitteeUID, + GroupID: p.GroupID, + Name: p.Name, + Description: p.Description, + Type: p.Type, + AudienceAccess: p.AudienceAccess, } return body } -// NewUpdateGrpsioMailingListRequestBody builds the HTTP request body from the -// payload of the "update-grpsio-mailing-list" endpoint of the "mailing-list" +// NewUpdateGroupsioSubgroupRequestBody builds the HTTP request body from the +// payload of the "update-groupsio-subgroup" endpoint of the "mailing-list" // service. -func NewUpdateGrpsioMailingListRequestBody(p *mailinglist.UpdateGrpsioMailingListPayload) *UpdateGrpsioMailingListRequestBody { - body := &UpdateGrpsioMailingListRequestBody{ - GroupName: p.GroupName, - GroupID: p.GroupID, - Public: p.Public, - Type: p.Type, - AudienceAccess: p.AudienceAccess, - Description: p.Description, - Title: p.Title, - SubjectTag: p.SubjectTag, - ServiceUID: p.ServiceUID, - SubscriberCount: p.SubscriberCount, - } - { - var zero string - if body.AudienceAccess == zero { - body.AudienceAccess = "public" - } - } - if p.Committees != nil { - body.Committees = make([]*CommitteeRequestBody, len(p.Committees)) - for i, val := range p.Committees { - body.Committees[i] = marshalMailinglistCommitteeToCommitteeRequestBody(val) - } +func NewUpdateGroupsioSubgroupRequestBody(p *mailinglist.UpdateGroupsioSubgroupPayload) *UpdateGroupsioSubgroupRequestBody { + body := &UpdateGroupsioSubgroupRequestBody{ + ProjectUID: p.ProjectUID, + CommitteeUID: p.CommitteeUID, + GroupID: p.GroupID, + Name: p.Name, + Description: p.Description, + Type: p.Type, + AudienceAccess: p.AudienceAccess, } return body } -// NewUpdateGrpsioMailingListSettingsRequestBody builds the HTTP request body -// from the payload of the "update-grpsio-mailing-list-settings" endpoint of -// the "mailing-list" service. -func NewUpdateGrpsioMailingListSettingsRequestBody(p *mailinglist.UpdateGrpsioMailingListSettingsPayload) *UpdateGrpsioMailingListSettingsRequestBody { - body := &UpdateGrpsioMailingListSettingsRequestBody{} - if p.Writers != nil { - body.Writers = make([]*UserInfoRequestBody, len(p.Writers)) - for i, val := range p.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoRequestBody(val) - } - } - if p.Auditors != nil { - body.Auditors = make([]*UserInfoRequestBody, len(p.Auditors)) - for i, val := range p.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoRequestBody(val) - } +// NewAddGroupsioMemberRequestBody builds the HTTP request body from the +// payload of the "add-groupsio-member" endpoint of the "mailing-list" service. +func NewAddGroupsioMemberRequestBody(p *mailinglist.AddGroupsioMemberPayload) *AddGroupsioMemberRequestBody { + body := &AddGroupsioMemberRequestBody{ + Email: p.Email, + Name: p.Name, + ModStatus: p.ModStatus, + DeliveryMode: p.DeliveryMode, } return body } -// NewCreateGrpsioMailingListMemberRequestBody builds the HTTP request body -// from the payload of the "create-grpsio-mailing-list-member" endpoint of the -// "mailing-list" service. -func NewCreateGrpsioMailingListMemberRequestBody(p *mailinglist.CreateGrpsioMailingListMemberPayload) *CreateGrpsioMailingListMemberRequestBody { - body := &CreateGrpsioMailingListMemberRequestBody{ - Username: p.Username, - FirstName: p.FirstName, - LastName: p.LastName, - Email: p.Email, - Organization: p.Organization, - JobTitle: p.JobTitle, - MemberType: p.MemberType, - DeliveryMode: p.DeliveryMode, - ModStatus: p.ModStatus, - LastReviewedAt: p.LastReviewedAt, - LastReviewedBy: p.LastReviewedBy, - } - { - var zero string - if body.MemberType == zero { - body.MemberType = "direct" - } - } - { - var zero string - if body.DeliveryMode == zero { - body.DeliveryMode = "normal" - } - } - { - var zero string - if body.ModStatus == zero { - body.ModStatus = "none" - } +// NewUpdateGroupsioMemberRequestBody builds the HTTP request body from the +// payload of the "update-groupsio-member" endpoint of the "mailing-list" +// service. +func NewUpdateGroupsioMemberRequestBody(p *mailinglist.UpdateGroupsioMemberPayload) *UpdateGroupsioMemberRequestBody { + body := &UpdateGroupsioMemberRequestBody{ + Email: p.Email, + Name: p.Name, + ModStatus: p.ModStatus, + DeliveryMode: p.DeliveryMode, } return body } -// NewUpdateGrpsioMailingListMemberRequestBody builds the HTTP request body -// from the payload of the "update-grpsio-mailing-list-member" endpoint of the -// "mailing-list" service. -func NewUpdateGrpsioMailingListMemberRequestBody(p *mailinglist.UpdateGrpsioMailingListMemberPayload) *UpdateGrpsioMailingListMemberRequestBody { - body := &UpdateGrpsioMailingListMemberRequestBody{ - Username: p.Username, - FirstName: p.FirstName, - LastName: p.LastName, - Organization: p.Organization, - JobTitle: p.JobTitle, - DeliveryMode: p.DeliveryMode, - ModStatus: p.ModStatus, - } - { - var zero string - if body.DeliveryMode == zero { - body.DeliveryMode = "normal" - } - } - { - var zero string - if body.ModStatus == zero { - body.ModStatus = "none" +// NewInviteGroupsioMembersRequestBody builds the HTTP request body from the +// payload of the "invite-groupsio-members" endpoint of the "mailing-list" +// service. +func NewInviteGroupsioMembersRequestBody(p *mailinglist.InviteGroupsioMembersPayload) *InviteGroupsioMembersRequestBody { + body := &InviteGroupsioMembersRequestBody{} + if p.Emails != nil { + body.Emails = make([]string, len(p.Emails)) + for i, val := range p.Emails { + body.Emails[i] = val } + } else { + body.Emails = []string{} } return body } -// NewGroupsioWebhookRequestBody builds the HTTP request body from the payload -// of the "groupsio-webhook" endpoint of the "mailing-list" service. -func NewGroupsioWebhookRequestBody(p *mailinglist.GroupsioWebhookPayload) *GroupsioWebhookRequestBody { - body := &GroupsioWebhookRequestBody{ - Action: p.Action, - Group: p.Group, - MemberInfo: p.MemberInfo, - Extra: p.Extra, - ExtraID: p.ExtraID, +// NewCheckGroupsioSubscriberRequestBody builds the HTTP request body from the +// payload of the "check-groupsio-subscriber" endpoint of the "mailing-list" +// service. +func NewCheckGroupsioSubscriberRequestBody(p *mailinglist.CheckGroupsioSubscriberPayload) *CheckGroupsioSubscriberRequestBody { + body := &CheckGroupsioSubscriberRequestBody{ + Email: p.Email, + SubgroupID: p.SubgroupID, } return body } @@ -1663,57 +1202,25 @@ func NewReadyzServiceUnavailable(body *ReadyzServiceUnavailableResponseBody) *ma return v } -// NewCreateGrpsioServiceGrpsIoServiceFullCreated builds a "mailing-list" -// service "create-grpsio-service" endpoint result from a HTTP "Created" -// response. -func NewCreateGrpsioServiceGrpsIoServiceFullCreated(body *CreateGrpsioServiceResponseBody) *mailinglist.GrpsIoServiceFull { - v := &mailinglist.GrpsIoServiceFull{ - UID: body.UID, - Type: *body.Type, - Domain: body.Domain, - GroupID: body.GroupID, - Status: body.Status, - Prefix: body.Prefix, - ParentServiceUID: body.ParentServiceUID, - ProjectSlug: body.ProjectSlug, - ProjectUID: *body.ProjectUID, - URL: body.URL, - GroupName: body.GroupName, - ProjectName: body.ProjectName, - CreatedAt: body.CreatedAt, - UpdatedAt: body.UpdatedAt, - } - if body.Public != nil { - v.Public = *body.Public - } - if body.GlobalOwners != nil { - v.GlobalOwners = make([]string, len(body.GlobalOwners)) - for i, val := range body.GlobalOwners { - v.GlobalOwners[i] = val - } - } - if body.Public == nil { - v.Public = false +// NewListGroupsioServicesGroupsioServiceListOK builds a "mailing-list" service +// "list-groupsio-services" endpoint result from a HTTP "OK" response. +func NewListGroupsioServicesGroupsioServiceListOK(body *ListGroupsioServicesResponseBody) *mailinglist.GroupsioServiceList { + v := &mailinglist.GroupsioServiceList{ + Total: body.Total, } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) + if body.Items != nil { + v.Items = make([]*mailinglist.GroupsioService, len(body.Items)) + for i, val := range body.Items { + v.Items[i] = unmarshalGroupsioServiceResponseBodyToMailinglistGroupsioService(val) } } return v } -// NewCreateGrpsioServiceBadRequest builds a mailing-list service -// create-grpsio-service endpoint BadRequest error. -func NewCreateGrpsioServiceBadRequest(body *CreateGrpsioServiceBadRequestResponseBody) *mailinglist.BadRequestError { +// NewListGroupsioServicesBadRequest builds a mailing-list service +// list-groupsio-services endpoint BadRequest error. +func NewListGroupsioServicesBadRequest(body *ListGroupsioServicesBadRequestResponseBody) *mailinglist.BadRequestError { v := &mailinglist.BadRequestError{ Message: *body.Message, } @@ -1721,110 +1228,106 @@ func NewCreateGrpsioServiceBadRequest(body *CreateGrpsioServiceBadRequestRespons return v } -// NewCreateGrpsioServiceConflict builds a mailing-list service -// create-grpsio-service endpoint Conflict error. -func NewCreateGrpsioServiceConflict(body *CreateGrpsioServiceConflictResponseBody) *mailinglist.ConflictError { - v := &mailinglist.ConflictError{ +// NewListGroupsioServicesInternalServerError builds a mailing-list service +// list-groupsio-services endpoint InternalServerError error. +func NewListGroupsioServicesInternalServerError(body *ListGroupsioServicesInternalServerErrorResponseBody) *mailinglist.InternalServerError { + v := &mailinglist.InternalServerError{ Message: *body.Message, } return v } -// NewCreateGrpsioServiceInternalServerError builds a mailing-list service -// create-grpsio-service endpoint InternalServerError error. -func NewCreateGrpsioServiceInternalServerError(body *CreateGrpsioServiceInternalServerErrorResponseBody) *mailinglist.InternalServerError { - v := &mailinglist.InternalServerError{ +// NewListGroupsioServicesServiceUnavailable builds a mailing-list service +// list-groupsio-services endpoint ServiceUnavailable error. +func NewListGroupsioServicesServiceUnavailable(body *ListGroupsioServicesServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { + v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } return v } -// NewCreateGrpsioServiceNotFound builds a mailing-list service -// create-grpsio-service endpoint NotFound error. -func NewCreateGrpsioServiceNotFound(body *CreateGrpsioServiceNotFoundResponseBody) *mailinglist.NotFoundError { - v := &mailinglist.NotFoundError{ - Message: *body.Message, +// NewCreateGroupsioServiceGroupsioServiceCreated builds a "mailing-list" +// service "create-groupsio-service" endpoint result from a HTTP "Created" +// response. +func NewCreateGroupsioServiceGroupsioServiceCreated(body *CreateGroupsioServiceResponseBody) *mailinglist.GroupsioService { + v := &mailinglist.GroupsioService{ + ID: body.ID, + ProjectUID: body.ProjectUID, + Type: body.Type, + GroupID: body.GroupID, + Domain: body.Domain, + Prefix: body.Prefix, + Status: body.Status, + CreatedAt: body.CreatedAt, + UpdatedAt: body.UpdatedAt, } return v } -// NewCreateGrpsioServiceServiceUnavailable builds a mailing-list service -// create-grpsio-service endpoint ServiceUnavailable error. -func NewCreateGrpsioServiceServiceUnavailable(body *CreateGrpsioServiceServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { - v := &mailinglist.ServiceUnavailableError{ +// NewCreateGroupsioServiceBadRequest builds a mailing-list service +// create-groupsio-service endpoint BadRequest error. +func NewCreateGroupsioServiceBadRequest(body *CreateGroupsioServiceBadRequestResponseBody) *mailinglist.BadRequestError { + v := &mailinglist.BadRequestError{ Message: *body.Message, } return v } -// NewGetGrpsioServiceResultOK builds a "mailing-list" service -// "get-grpsio-service" endpoint result from a HTTP "OK" response. -func NewGetGrpsioServiceResultOK(body *GetGrpsioServiceResponseBody, etag *string) *mailinglist.GetGrpsioServiceResult { - v := &mailinglist.GrpsIoServiceWithReadonlyAttributes{ - UID: body.UID, - Type: *body.Type, - Domain: body.Domain, - GroupID: body.GroupID, - Status: body.Status, - Prefix: body.Prefix, - ParentServiceUID: body.ParentServiceUID, - ProjectSlug: body.ProjectSlug, - ProjectUID: *body.ProjectUID, - URL: body.URL, - GroupName: body.GroupName, - ProjectName: body.ProjectName, - CreatedAt: body.CreatedAt, - UpdatedAt: body.UpdatedAt, - } - if body.Public != nil { - v.Public = *body.Public - } - if body.GlobalOwners != nil { - v.GlobalOwners = make([]string, len(body.GlobalOwners)) - for i, val := range body.GlobalOwners { - v.GlobalOwners[i] = val - } - } - if body.Public == nil { - v.Public = false - } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } +// NewCreateGroupsioServiceConflict builds a mailing-list service +// create-groupsio-service endpoint Conflict error. +func NewCreateGroupsioServiceConflict(body *CreateGroupsioServiceConflictResponseBody) *mailinglist.ConflictError { + v := &mailinglist.ConflictError{ + Message: *body.Message, } - res := &mailinglist.GetGrpsioServiceResult{ - Service: v, + + return v +} + +// NewCreateGroupsioServiceInternalServerError builds a mailing-list service +// create-groupsio-service endpoint InternalServerError error. +func NewCreateGroupsioServiceInternalServerError(body *CreateGroupsioServiceInternalServerErrorResponseBody) *mailinglist.InternalServerError { + v := &mailinglist.InternalServerError{ + Message: *body.Message, } - res.Etag = etag - return res + return v } -// NewGetGrpsioServiceBadRequest builds a mailing-list service -// get-grpsio-service endpoint BadRequest error. -func NewGetGrpsioServiceBadRequest(body *GetGrpsioServiceBadRequestResponseBody) *mailinglist.BadRequestError { - v := &mailinglist.BadRequestError{ +// NewCreateGroupsioServiceServiceUnavailable builds a mailing-list service +// create-groupsio-service endpoint ServiceUnavailable error. +func NewCreateGroupsioServiceServiceUnavailable(body *CreateGroupsioServiceServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { + v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } return v } -// NewGetGrpsioServiceInternalServerError builds a mailing-list service -// get-grpsio-service endpoint InternalServerError error. -func NewGetGrpsioServiceInternalServerError(body *GetGrpsioServiceInternalServerErrorResponseBody) *mailinglist.InternalServerError { +// NewGetGroupsioServiceGroupsioServiceOK builds a "mailing-list" service +// "get-groupsio-service" endpoint result from a HTTP "OK" response. +func NewGetGroupsioServiceGroupsioServiceOK(body *GetGroupsioServiceResponseBody) *mailinglist.GroupsioService { + v := &mailinglist.GroupsioService{ + ID: body.ID, + ProjectUID: body.ProjectUID, + Type: body.Type, + GroupID: body.GroupID, + Domain: body.Domain, + Prefix: body.Prefix, + Status: body.Status, + CreatedAt: body.CreatedAt, + UpdatedAt: body.UpdatedAt, + } + + return v +} + +// NewGetGroupsioServiceInternalServerError builds a mailing-list service +// get-groupsio-service endpoint InternalServerError error. +func NewGetGroupsioServiceInternalServerError(body *GetGroupsioServiceInternalServerErrorResponseBody) *mailinglist.InternalServerError { v := &mailinglist.InternalServerError{ Message: *body.Message, } @@ -1832,9 +1335,9 @@ func NewGetGrpsioServiceInternalServerError(body *GetGrpsioServiceInternalServer return v } -// NewGetGrpsioServiceNotFound builds a mailing-list service get-grpsio-service -// endpoint NotFound error. -func NewGetGrpsioServiceNotFound(body *GetGrpsioServiceNotFoundResponseBody) *mailinglist.NotFoundError { +// NewGetGroupsioServiceNotFound builds a mailing-list service +// get-groupsio-service endpoint NotFound error. +func NewGetGroupsioServiceNotFound(body *GetGroupsioServiceNotFoundResponseBody) *mailinglist.NotFoundError { v := &mailinglist.NotFoundError{ Message: *body.Message, } @@ -1842,9 +1345,9 @@ func NewGetGrpsioServiceNotFound(body *GetGrpsioServiceNotFoundResponseBody) *ma return v } -// NewGetGrpsioServiceServiceUnavailable builds a mailing-list service -// get-grpsio-service endpoint ServiceUnavailable error. -func NewGetGrpsioServiceServiceUnavailable(body *GetGrpsioServiceServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { +// NewGetGroupsioServiceServiceUnavailable builds a mailing-list service +// get-groupsio-service endpoint ServiceUnavailable error. +func NewGetGroupsioServiceServiceUnavailable(body *GetGroupsioServiceServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } @@ -1852,57 +1355,27 @@ func NewGetGrpsioServiceServiceUnavailable(body *GetGrpsioServiceServiceUnavaila return v } -// NewUpdateGrpsioServiceGrpsIoServiceWithReadonlyAttributesOK builds a -// "mailing-list" service "update-grpsio-service" endpoint result from a HTTP -// "OK" response. -func NewUpdateGrpsioServiceGrpsIoServiceWithReadonlyAttributesOK(body *UpdateGrpsioServiceResponseBody) *mailinglist.GrpsIoServiceWithReadonlyAttributes { - v := &mailinglist.GrpsIoServiceWithReadonlyAttributes{ - UID: body.UID, - Type: *body.Type, - Domain: body.Domain, - GroupID: body.GroupID, - Status: body.Status, - Prefix: body.Prefix, - ParentServiceUID: body.ParentServiceUID, - ProjectSlug: body.ProjectSlug, - ProjectUID: *body.ProjectUID, - URL: body.URL, - GroupName: body.GroupName, - ProjectName: body.ProjectName, - CreatedAt: body.CreatedAt, - UpdatedAt: body.UpdatedAt, - } - if body.Public != nil { - v.Public = *body.Public - } - if body.GlobalOwners != nil { - v.GlobalOwners = make([]string, len(body.GlobalOwners)) - for i, val := range body.GlobalOwners { - v.GlobalOwners[i] = val - } - } - if body.Public == nil { - v.Public = false - } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } +// NewUpdateGroupsioServiceGroupsioServiceOK builds a "mailing-list" service +// "update-groupsio-service" endpoint result from a HTTP "OK" response. +func NewUpdateGroupsioServiceGroupsioServiceOK(body *UpdateGroupsioServiceResponseBody) *mailinglist.GroupsioService { + v := &mailinglist.GroupsioService{ + ID: body.ID, + ProjectUID: body.ProjectUID, + Type: body.Type, + GroupID: body.GroupID, + Domain: body.Domain, + Prefix: body.Prefix, + Status: body.Status, + CreatedAt: body.CreatedAt, + UpdatedAt: body.UpdatedAt, } return v } -// NewUpdateGrpsioServiceBadRequest builds a mailing-list service -// update-grpsio-service endpoint BadRequest error. -func NewUpdateGrpsioServiceBadRequest(body *UpdateGrpsioServiceBadRequestResponseBody) *mailinglist.BadRequestError { +// NewUpdateGroupsioServiceBadRequest builds a mailing-list service +// update-groupsio-service endpoint BadRequest error. +func NewUpdateGroupsioServiceBadRequest(body *UpdateGroupsioServiceBadRequestResponseBody) *mailinglist.BadRequestError { v := &mailinglist.BadRequestError{ Message: *body.Message, } @@ -1910,89 +1383,94 @@ func NewUpdateGrpsioServiceBadRequest(body *UpdateGrpsioServiceBadRequestRespons return v } -// NewUpdateGrpsioServiceConflict builds a mailing-list service -// update-grpsio-service endpoint Conflict error. -func NewUpdateGrpsioServiceConflict(body *UpdateGrpsioServiceConflictResponseBody) *mailinglist.ConflictError { - v := &mailinglist.ConflictError{ +// NewUpdateGroupsioServiceInternalServerError builds a mailing-list service +// update-groupsio-service endpoint InternalServerError error. +func NewUpdateGroupsioServiceInternalServerError(body *UpdateGroupsioServiceInternalServerErrorResponseBody) *mailinglist.InternalServerError { + v := &mailinglist.InternalServerError{ Message: *body.Message, } return v } -// NewUpdateGrpsioServiceInternalServerError builds a mailing-list service -// update-grpsio-service endpoint InternalServerError error. -func NewUpdateGrpsioServiceInternalServerError(body *UpdateGrpsioServiceInternalServerErrorResponseBody) *mailinglist.InternalServerError { - v := &mailinglist.InternalServerError{ +// NewUpdateGroupsioServiceNotFound builds a mailing-list service +// update-groupsio-service endpoint NotFound error. +func NewUpdateGroupsioServiceNotFound(body *UpdateGroupsioServiceNotFoundResponseBody) *mailinglist.NotFoundError { + v := &mailinglist.NotFoundError{ Message: *body.Message, } return v } -// NewUpdateGrpsioServiceNotFound builds a mailing-list service -// update-grpsio-service endpoint NotFound error. -func NewUpdateGrpsioServiceNotFound(body *UpdateGrpsioServiceNotFoundResponseBody) *mailinglist.NotFoundError { - v := &mailinglist.NotFoundError{ +// NewUpdateGroupsioServiceServiceUnavailable builds a mailing-list service +// update-groupsio-service endpoint ServiceUnavailable error. +func NewUpdateGroupsioServiceServiceUnavailable(body *UpdateGroupsioServiceServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { + v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } return v } -// NewUpdateGrpsioServiceServiceUnavailable builds a mailing-list service -// update-grpsio-service endpoint ServiceUnavailable error. -func NewUpdateGrpsioServiceServiceUnavailable(body *UpdateGrpsioServiceServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { - v := &mailinglist.ServiceUnavailableError{ +// NewDeleteGroupsioServiceInternalServerError builds a mailing-list service +// delete-groupsio-service endpoint InternalServerError error. +func NewDeleteGroupsioServiceInternalServerError(body *DeleteGroupsioServiceInternalServerErrorResponseBody) *mailinglist.InternalServerError { + v := &mailinglist.InternalServerError{ Message: *body.Message, } return v } -// NewDeleteGrpsioServiceBadRequest builds a mailing-list service -// delete-grpsio-service endpoint BadRequest error. -func NewDeleteGrpsioServiceBadRequest(body *DeleteGrpsioServiceBadRequestResponseBody) *mailinglist.BadRequestError { - v := &mailinglist.BadRequestError{ +// NewDeleteGroupsioServiceNotFound builds a mailing-list service +// delete-groupsio-service endpoint NotFound error. +func NewDeleteGroupsioServiceNotFound(body *DeleteGroupsioServiceNotFoundResponseBody) *mailinglist.NotFoundError { + v := &mailinglist.NotFoundError{ Message: *body.Message, } return v } -// NewDeleteGrpsioServiceConflict builds a mailing-list service -// delete-grpsio-service endpoint Conflict error. -func NewDeleteGrpsioServiceConflict(body *DeleteGrpsioServiceConflictResponseBody) *mailinglist.ConflictError { - v := &mailinglist.ConflictError{ +// NewDeleteGroupsioServiceServiceUnavailable builds a mailing-list service +// delete-groupsio-service endpoint ServiceUnavailable error. +func NewDeleteGroupsioServiceServiceUnavailable(body *DeleteGroupsioServiceServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { + v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } return v } -// NewDeleteGrpsioServiceInternalServerError builds a mailing-list service -// delete-grpsio-service endpoint InternalServerError error. -func NewDeleteGrpsioServiceInternalServerError(body *DeleteGrpsioServiceInternalServerErrorResponseBody) *mailinglist.InternalServerError { - v := &mailinglist.InternalServerError{ - Message: *body.Message, +// NewGetGroupsioServiceProjectsGroupsioProjectsResponseOK builds a +// "mailing-list" service "get-groupsio-service-projects" endpoint result from +// a HTTP "OK" response. +func NewGetGroupsioServiceProjectsGroupsioProjectsResponseOK(body *GetGroupsioServiceProjectsResponseBody) *mailinglist.GroupsioProjectsResponse { + v := &mailinglist.GroupsioProjectsResponse{} + if body.Projects != nil { + v.Projects = make([]string, len(body.Projects)) + for i, val := range body.Projects { + v.Projects[i] = val + } } return v } -// NewDeleteGrpsioServiceNotFound builds a mailing-list service -// delete-grpsio-service endpoint NotFound error. -func NewDeleteGrpsioServiceNotFound(body *DeleteGrpsioServiceNotFoundResponseBody) *mailinglist.NotFoundError { - v := &mailinglist.NotFoundError{ +// NewGetGroupsioServiceProjectsInternalServerError builds a mailing-list +// service get-groupsio-service-projects endpoint InternalServerError error. +func NewGetGroupsioServiceProjectsInternalServerError(body *GetGroupsioServiceProjectsInternalServerErrorResponseBody) *mailinglist.InternalServerError { + v := &mailinglist.InternalServerError{ Message: *body.Message, } return v } -// NewDeleteGrpsioServiceServiceUnavailable builds a mailing-list service -// delete-grpsio-service endpoint ServiceUnavailable error. -func NewDeleteGrpsioServiceServiceUnavailable(body *DeleteGrpsioServiceServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { +// NewGetGroupsioServiceProjectsServiceUnavailable builds a mailing-list +// service get-groupsio-service-projects endpoint ServiceUnavailable error. +func NewGetGroupsioServiceProjectsServiceUnavailable(body *GetGroupsioServiceProjectsServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } @@ -2000,41 +1478,28 @@ func NewDeleteGrpsioServiceServiceUnavailable(body *DeleteGrpsioServiceServiceUn return v } -// NewGetGrpsioServiceSettingsResultOK builds a "mailing-list" service -// "get-grpsio-service-settings" endpoint result from a HTTP "OK" response. -func NewGetGrpsioServiceSettingsResultOK(body *GetGrpsioServiceSettingsResponseBody, etag *string) *mailinglist.GetGrpsioServiceSettingsResult { - v := &mailinglist.GrpsIoServiceSettings{ - UID: body.UID, - LastReviewedAt: body.LastReviewedAt, - LastReviewedBy: body.LastReviewedBy, - LastAuditedBy: body.LastAuditedBy, - LastAuditedTime: body.LastAuditedTime, - CreatedAt: body.CreatedAt, - UpdatedAt: body.UpdatedAt, - } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } - } - res := &mailinglist.GetGrpsioServiceSettingsResult{ - ServiceSettings: v, +// NewFindParentGroupsioServiceGroupsioServiceOK builds a "mailing-list" +// service "find-parent-groupsio-service" endpoint result from a HTTP "OK" +// response. +func NewFindParentGroupsioServiceGroupsioServiceOK(body *FindParentGroupsioServiceResponseBody) *mailinglist.GroupsioService { + v := &mailinglist.GroupsioService{ + ID: body.ID, + ProjectUID: body.ProjectUID, + Type: body.Type, + GroupID: body.GroupID, + Domain: body.Domain, + Prefix: body.Prefix, + Status: body.Status, + CreatedAt: body.CreatedAt, + UpdatedAt: body.UpdatedAt, } - res.Etag = etag - return res + return v } -// NewGetGrpsioServiceSettingsBadRequest builds a mailing-list service -// get-grpsio-service-settings endpoint BadRequest error. -func NewGetGrpsioServiceSettingsBadRequest(body *GetGrpsioServiceSettingsBadRequestResponseBody) *mailinglist.BadRequestError { +// NewFindParentGroupsioServiceBadRequest builds a mailing-list service +// find-parent-groupsio-service endpoint BadRequest error. +func NewFindParentGroupsioServiceBadRequest(body *FindParentGroupsioServiceBadRequestResponseBody) *mailinglist.BadRequestError { v := &mailinglist.BadRequestError{ Message: *body.Message, } @@ -2042,9 +1507,9 @@ func NewGetGrpsioServiceSettingsBadRequest(body *GetGrpsioServiceSettingsBadRequ return v } -// NewGetGrpsioServiceSettingsInternalServerError builds a mailing-list service -// get-grpsio-service-settings endpoint InternalServerError error. -func NewGetGrpsioServiceSettingsInternalServerError(body *GetGrpsioServiceSettingsInternalServerErrorResponseBody) *mailinglist.InternalServerError { +// NewFindParentGroupsioServiceInternalServerError builds a mailing-list +// service find-parent-groupsio-service endpoint InternalServerError error. +func NewFindParentGroupsioServiceInternalServerError(body *FindParentGroupsioServiceInternalServerErrorResponseBody) *mailinglist.InternalServerError { v := &mailinglist.InternalServerError{ Message: *body.Message, } @@ -2052,9 +1517,9 @@ func NewGetGrpsioServiceSettingsInternalServerError(body *GetGrpsioServiceSettin return v } -// NewGetGrpsioServiceSettingsNotFound builds a mailing-list service -// get-grpsio-service-settings endpoint NotFound error. -func NewGetGrpsioServiceSettingsNotFound(body *GetGrpsioServiceSettingsNotFoundResponseBody) *mailinglist.NotFoundError { +// NewFindParentGroupsioServiceNotFound builds a mailing-list service +// find-parent-groupsio-service endpoint NotFound error. +func NewFindParentGroupsioServiceNotFound(body *FindParentGroupsioServiceNotFoundResponseBody) *mailinglist.NotFoundError { v := &mailinglist.NotFoundError{ Message: *body.Message, } @@ -2062,9 +1527,9 @@ func NewGetGrpsioServiceSettingsNotFound(body *GetGrpsioServiceSettingsNotFoundR return v } -// NewGetGrpsioServiceSettingsServiceUnavailable builds a mailing-list service -// get-grpsio-service-settings endpoint ServiceUnavailable error. -func NewGetGrpsioServiceSettingsServiceUnavailable(body *GetGrpsioServiceSettingsServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { +// NewFindParentGroupsioServiceServiceUnavailable builds a mailing-list service +// find-parent-groupsio-service endpoint ServiceUnavailable error. +func NewFindParentGroupsioServiceServiceUnavailable(body *FindParentGroupsioServiceServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } @@ -2072,38 +1537,25 @@ func NewGetGrpsioServiceSettingsServiceUnavailable(body *GetGrpsioServiceSetting return v } -// NewUpdateGrpsioServiceSettingsGrpsIoServiceSettingsOK builds a -// "mailing-list" service "update-grpsio-service-settings" endpoint result from -// a HTTP "OK" response. -func NewUpdateGrpsioServiceSettingsGrpsIoServiceSettingsOK(body *UpdateGrpsioServiceSettingsResponseBody) *mailinglist.GrpsIoServiceSettings { - v := &mailinglist.GrpsIoServiceSettings{ - UID: body.UID, - LastReviewedAt: body.LastReviewedAt, - LastReviewedBy: body.LastReviewedBy, - LastAuditedBy: body.LastAuditedBy, - LastAuditedTime: body.LastAuditedTime, - CreatedAt: body.CreatedAt, - UpdatedAt: body.UpdatedAt, - } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } +// NewListGroupsioSubgroupsGroupsioSubgroupListOK builds a "mailing-list" +// service "list-groupsio-subgroups" endpoint result from a HTTP "OK" response. +func NewListGroupsioSubgroupsGroupsioSubgroupListOK(body *ListGroupsioSubgroupsResponseBody) *mailinglist.GroupsioSubgroupList { + v := &mailinglist.GroupsioSubgroupList{ + Total: body.Total, } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) + if body.Items != nil { + v.Items = make([]*mailinglist.GroupsioSubgroup, len(body.Items)) + for i, val := range body.Items { + v.Items[i] = unmarshalGroupsioSubgroupResponseBodyToMailinglistGroupsioSubgroup(val) } } return v } -// NewUpdateGrpsioServiceSettingsBadRequest builds a mailing-list service -// update-grpsio-service-settings endpoint BadRequest error. -func NewUpdateGrpsioServiceSettingsBadRequest(body *UpdateGrpsioServiceSettingsBadRequestResponseBody) *mailinglist.BadRequestError { +// NewListGroupsioSubgroupsBadRequest builds a mailing-list service +// list-groupsio-subgroups endpoint BadRequest error. +func NewListGroupsioSubgroupsBadRequest(body *ListGroupsioSubgroupsBadRequestResponseBody) *mailinglist.BadRequestError { v := &mailinglist.BadRequestError{ Message: *body.Message, } @@ -2111,123 +1563,108 @@ func NewUpdateGrpsioServiceSettingsBadRequest(body *UpdateGrpsioServiceSettingsB return v } -// NewUpdateGrpsioServiceSettingsConflict builds a mailing-list service -// update-grpsio-service-settings endpoint Conflict error. -func NewUpdateGrpsioServiceSettingsConflict(body *UpdateGrpsioServiceSettingsConflictResponseBody) *mailinglist.ConflictError { - v := &mailinglist.ConflictError{ +// NewListGroupsioSubgroupsInternalServerError builds a mailing-list service +// list-groupsio-subgroups endpoint InternalServerError error. +func NewListGroupsioSubgroupsInternalServerError(body *ListGroupsioSubgroupsInternalServerErrorResponseBody) *mailinglist.InternalServerError { + v := &mailinglist.InternalServerError{ Message: *body.Message, } return v } -// NewUpdateGrpsioServiceSettingsInternalServerError builds a mailing-list -// service update-grpsio-service-settings endpoint InternalServerError error. -func NewUpdateGrpsioServiceSettingsInternalServerError(body *UpdateGrpsioServiceSettingsInternalServerErrorResponseBody) *mailinglist.InternalServerError { - v := &mailinglist.InternalServerError{ +// NewListGroupsioSubgroupsServiceUnavailable builds a mailing-list service +// list-groupsio-subgroups endpoint ServiceUnavailable error. +func NewListGroupsioSubgroupsServiceUnavailable(body *ListGroupsioSubgroupsServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { + v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } return v } -// NewUpdateGrpsioServiceSettingsNotFound builds a mailing-list service -// update-grpsio-service-settings endpoint NotFound error. -func NewUpdateGrpsioServiceSettingsNotFound(body *UpdateGrpsioServiceSettingsNotFoundResponseBody) *mailinglist.NotFoundError { - v := &mailinglist.NotFoundError{ - Message: *body.Message, +// NewCreateGroupsioSubgroupGroupsioSubgroupCreated builds a "mailing-list" +// service "create-groupsio-subgroup" endpoint result from a HTTP "Created" +// response. +func NewCreateGroupsioSubgroupGroupsioSubgroupCreated(body *CreateGroupsioSubgroupResponseBody) *mailinglist.GroupsioSubgroup { + v := &mailinglist.GroupsioSubgroup{ + ID: body.ID, + ProjectUID: body.ProjectUID, + CommitteeUID: body.CommitteeUID, + GroupID: body.GroupID, + Name: body.Name, + Description: body.Description, + Type: body.Type, + AudienceAccess: body.AudienceAccess, + CreatedAt: body.CreatedAt, + UpdatedAt: body.UpdatedAt, } return v } -// NewUpdateGrpsioServiceSettingsServiceUnavailable builds a mailing-list -// service update-grpsio-service-settings endpoint ServiceUnavailable error. -func NewUpdateGrpsioServiceSettingsServiceUnavailable(body *UpdateGrpsioServiceSettingsServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { - v := &mailinglist.ServiceUnavailableError{ +// NewCreateGroupsioSubgroupBadRequest builds a mailing-list service +// create-groupsio-subgroup endpoint BadRequest error. +func NewCreateGroupsioSubgroupBadRequest(body *CreateGroupsioSubgroupBadRequestResponseBody) *mailinglist.BadRequestError { + v := &mailinglist.BadRequestError{ Message: *body.Message, } return v } -// NewCreateGrpsioMailingListGrpsIoMailingListFullCreated builds a -// "mailing-list" service "create-grpsio-mailing-list" endpoint result from a -// HTTP "Created" response. -func NewCreateGrpsioMailingListGrpsIoMailingListFullCreated(body *CreateGrpsioMailingListResponseBody) *mailinglist.GrpsIoMailingListFull { - v := &mailinglist.GrpsIoMailingListFull{ - UID: body.UID, - GroupName: body.GroupName, - GroupID: body.GroupID, - Type: body.Type, - Description: body.Description, - Title: body.Title, - SubjectTag: body.SubjectTag, - ServiceUID: body.ServiceUID, - SubscriberCount: body.SubscriberCount, - ProjectUID: body.ProjectUID, - ProjectName: body.ProjectName, - ProjectSlug: body.ProjectSlug, - CreatedAt: body.CreatedAt, - UpdatedAt: body.UpdatedAt, - } - if body.Public != nil { - v.Public = *body.Public - } - if body.AudienceAccess != nil { - v.AudienceAccess = *body.AudienceAccess - } - if body.Public == nil { - v.Public = false - } - if body.AudienceAccess == nil { - v.AudienceAccess = "public" - } - if body.Committees != nil { - v.Committees = make([]*mailinglist.Committee, len(body.Committees)) - for i, val := range body.Committees { - v.Committees[i] = unmarshalCommitteeResponseBodyToMailinglistCommittee(val) - } - } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } +// NewCreateGroupsioSubgroupConflict builds a mailing-list service +// create-groupsio-subgroup endpoint Conflict error. +func NewCreateGroupsioSubgroupConflict(body *CreateGroupsioSubgroupConflictResponseBody) *mailinglist.ConflictError { + v := &mailinglist.ConflictError{ + Message: *body.Message, } return v } -// NewCreateGrpsioMailingListBadRequest builds a mailing-list service -// create-grpsio-mailing-list endpoint BadRequest error. -func NewCreateGrpsioMailingListBadRequest(body *CreateGrpsioMailingListBadRequestResponseBody) *mailinglist.BadRequestError { - v := &mailinglist.BadRequestError{ +// NewCreateGroupsioSubgroupInternalServerError builds a mailing-list service +// create-groupsio-subgroup endpoint InternalServerError error. +func NewCreateGroupsioSubgroupInternalServerError(body *CreateGroupsioSubgroupInternalServerErrorResponseBody) *mailinglist.InternalServerError { + v := &mailinglist.InternalServerError{ Message: *body.Message, } return v } -// NewCreateGrpsioMailingListConflict builds a mailing-list service -// create-grpsio-mailing-list endpoint Conflict error. -func NewCreateGrpsioMailingListConflict(body *CreateGrpsioMailingListConflictResponseBody) *mailinglist.ConflictError { - v := &mailinglist.ConflictError{ +// NewCreateGroupsioSubgroupServiceUnavailable builds a mailing-list service +// create-groupsio-subgroup endpoint ServiceUnavailable error. +func NewCreateGroupsioSubgroupServiceUnavailable(body *CreateGroupsioSubgroupServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { + v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } return v } -// NewCreateGrpsioMailingListInternalServerError builds a mailing-list service -// create-grpsio-mailing-list endpoint InternalServerError error. -func NewCreateGrpsioMailingListInternalServerError(body *CreateGrpsioMailingListInternalServerErrorResponseBody) *mailinglist.InternalServerError { +// NewGetGroupsioSubgroupGroupsioSubgroupOK builds a "mailing-list" service +// "get-groupsio-subgroup" endpoint result from a HTTP "OK" response. +func NewGetGroupsioSubgroupGroupsioSubgroupOK(body *GetGroupsioSubgroupResponseBody) *mailinglist.GroupsioSubgroup { + v := &mailinglist.GroupsioSubgroup{ + ID: body.ID, + ProjectUID: body.ProjectUID, + CommitteeUID: body.CommitteeUID, + GroupID: body.GroupID, + Name: body.Name, + Description: body.Description, + Type: body.Type, + AudienceAccess: body.AudienceAccess, + CreatedAt: body.CreatedAt, + UpdatedAt: body.UpdatedAt, + } + + return v +} + +// NewGetGroupsioSubgroupInternalServerError builds a mailing-list service +// get-groupsio-subgroup endpoint InternalServerError error. +func NewGetGroupsioSubgroupInternalServerError(body *GetGroupsioSubgroupInternalServerErrorResponseBody) *mailinglist.InternalServerError { v := &mailinglist.InternalServerError{ Message: *body.Message, } @@ -2235,9 +1672,9 @@ func NewCreateGrpsioMailingListInternalServerError(body *CreateGrpsioMailingList return v } -// NewCreateGrpsioMailingListNotFound builds a mailing-list service -// create-grpsio-mailing-list endpoint NotFound error. -func NewCreateGrpsioMailingListNotFound(body *CreateGrpsioMailingListNotFoundResponseBody) *mailinglist.NotFoundError { +// NewGetGroupsioSubgroupNotFound builds a mailing-list service +// get-groupsio-subgroup endpoint NotFound error. +func NewGetGroupsioSubgroupNotFound(body *GetGroupsioSubgroupNotFoundResponseBody) *mailinglist.NotFoundError { v := &mailinglist.NotFoundError{ Message: *body.Message, } @@ -2245,9 +1682,9 @@ func NewCreateGrpsioMailingListNotFound(body *CreateGrpsioMailingListNotFoundRes return v } -// NewCreateGrpsioMailingListServiceUnavailable builds a mailing-list service -// create-grpsio-mailing-list endpoint ServiceUnavailable error. -func NewCreateGrpsioMailingListServiceUnavailable(body *CreateGrpsioMailingListServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { +// NewGetGroupsioSubgroupServiceUnavailable builds a mailing-list service +// get-groupsio-subgroup endpoint ServiceUnavailable error. +func NewGetGroupsioSubgroupServiceUnavailable(body *GetGroupsioSubgroupServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } @@ -2255,54 +1692,28 @@ func NewCreateGrpsioMailingListServiceUnavailable(body *CreateGrpsioMailingListS return v } -// NewGetGrpsioMailingListResultOK builds a "mailing-list" service -// "get-grpsio-mailing-list" endpoint result from a HTTP "OK" response. -func NewGetGrpsioMailingListResultOK(body *GetGrpsioMailingListResponseBody, etag *string) *mailinglist.GetGrpsioMailingListResult { - v := &mailinglist.GrpsIoMailingListWithReadonlyAttributes{ - UID: body.UID, - GroupName: body.GroupName, - GroupID: body.GroupID, - Type: body.Type, - Description: body.Description, - Title: body.Title, - SubjectTag: body.SubjectTag, - ServiceUID: body.ServiceUID, - SubscriberCount: body.SubscriberCount, - ProjectUID: body.ProjectUID, - ProjectName: body.ProjectName, - ProjectSlug: body.ProjectSlug, - CreatedAt: body.CreatedAt, - UpdatedAt: body.UpdatedAt, - } - if body.Public != nil { - v.Public = *body.Public - } - if body.AudienceAccess != nil { - v.AudienceAccess = *body.AudienceAccess - } - if body.Public == nil { - v.Public = false - } - if body.AudienceAccess == nil { - v.AudienceAccess = "public" - } - if body.Committees != nil { - v.Committees = make([]*mailinglist.Committee, len(body.Committees)) - for i, val := range body.Committees { - v.Committees[i] = unmarshalCommitteeResponseBodyToMailinglistCommittee(val) - } - } - res := &mailinglist.GetGrpsioMailingListResult{ - MailingList: v, +// NewUpdateGroupsioSubgroupGroupsioSubgroupOK builds a "mailing-list" service +// "update-groupsio-subgroup" endpoint result from a HTTP "OK" response. +func NewUpdateGroupsioSubgroupGroupsioSubgroupOK(body *UpdateGroupsioSubgroupResponseBody) *mailinglist.GroupsioSubgroup { + v := &mailinglist.GroupsioSubgroup{ + ID: body.ID, + ProjectUID: body.ProjectUID, + CommitteeUID: body.CommitteeUID, + GroupID: body.GroupID, + Name: body.Name, + Description: body.Description, + Type: body.Type, + AudienceAccess: body.AudienceAccess, + CreatedAt: body.CreatedAt, + UpdatedAt: body.UpdatedAt, } - res.Etag = etag - return res + return v } -// NewGetGrpsioMailingListBadRequest builds a mailing-list service -// get-grpsio-mailing-list endpoint BadRequest error. -func NewGetGrpsioMailingListBadRequest(body *GetGrpsioMailingListBadRequestResponseBody) *mailinglist.BadRequestError { +// NewUpdateGroupsioSubgroupBadRequest builds a mailing-list service +// update-groupsio-subgroup endpoint BadRequest error. +func NewUpdateGroupsioSubgroupBadRequest(body *UpdateGroupsioSubgroupBadRequestResponseBody) *mailinglist.BadRequestError { v := &mailinglist.BadRequestError{ Message: *body.Message, } @@ -2310,9 +1721,9 @@ func NewGetGrpsioMailingListBadRequest(body *GetGrpsioMailingListBadRequestRespo return v } -// NewGetGrpsioMailingListInternalServerError builds a mailing-list service -// get-grpsio-mailing-list endpoint InternalServerError error. -func NewGetGrpsioMailingListInternalServerError(body *GetGrpsioMailingListInternalServerErrorResponseBody) *mailinglist.InternalServerError { +// NewUpdateGroupsioSubgroupInternalServerError builds a mailing-list service +// update-groupsio-subgroup endpoint InternalServerError error. +func NewUpdateGroupsioSubgroupInternalServerError(body *UpdateGroupsioSubgroupInternalServerErrorResponseBody) *mailinglist.InternalServerError { v := &mailinglist.InternalServerError{ Message: *body.Message, } @@ -2320,9 +1731,9 @@ func NewGetGrpsioMailingListInternalServerError(body *GetGrpsioMailingListIntern return v } -// NewGetGrpsioMailingListNotFound builds a mailing-list service -// get-grpsio-mailing-list endpoint NotFound error. -func NewGetGrpsioMailingListNotFound(body *GetGrpsioMailingListNotFoundResponseBody) *mailinglist.NotFoundError { +// NewUpdateGroupsioSubgroupNotFound builds a mailing-list service +// update-groupsio-subgroup endpoint NotFound error. +func NewUpdateGroupsioSubgroupNotFound(body *UpdateGroupsioSubgroupNotFoundResponseBody) *mailinglist.NotFoundError { v := &mailinglist.NotFoundError{ Message: *body.Message, } @@ -2330,9 +1741,9 @@ func NewGetGrpsioMailingListNotFound(body *GetGrpsioMailingListNotFoundResponseB return v } -// NewGetGrpsioMailingListServiceUnavailable builds a mailing-list service -// get-grpsio-mailing-list endpoint ServiceUnavailable error. -func NewGetGrpsioMailingListServiceUnavailable(body *GetGrpsioMailingListServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { +// NewUpdateGroupsioSubgroupServiceUnavailable builds a mailing-list service +// update-groupsio-subgroup endpoint ServiceUnavailable error. +func NewUpdateGroupsioSubgroupServiceUnavailable(body *UpdateGroupsioSubgroupServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } @@ -2340,121 +1751,91 @@ func NewGetGrpsioMailingListServiceUnavailable(body *GetGrpsioMailingListService return v } -// NewUpdateGrpsioMailingListGrpsIoMailingListWithReadonlyAttributesOK builds a -// "mailing-list" service "update-grpsio-mailing-list" endpoint result from a -// HTTP "OK" response. -func NewUpdateGrpsioMailingListGrpsIoMailingListWithReadonlyAttributesOK(body *UpdateGrpsioMailingListResponseBody) *mailinglist.GrpsIoMailingListWithReadonlyAttributes { - v := &mailinglist.GrpsIoMailingListWithReadonlyAttributes{ - UID: body.UID, - GroupName: body.GroupName, - GroupID: body.GroupID, - Type: body.Type, - Description: body.Description, - Title: body.Title, - SubjectTag: body.SubjectTag, - ServiceUID: body.ServiceUID, - SubscriberCount: body.SubscriberCount, - ProjectUID: body.ProjectUID, - ProjectName: body.ProjectName, - ProjectSlug: body.ProjectSlug, - CreatedAt: body.CreatedAt, - UpdatedAt: body.UpdatedAt, - } - if body.Public != nil { - v.Public = *body.Public - } - if body.AudienceAccess != nil { - v.AudienceAccess = *body.AudienceAccess - } - if body.Public == nil { - v.Public = false - } - if body.AudienceAccess == nil { - v.AudienceAccess = "public" - } - if body.Committees != nil { - v.Committees = make([]*mailinglist.Committee, len(body.Committees)) - for i, val := range body.Committees { - v.Committees[i] = unmarshalCommitteeResponseBodyToMailinglistCommittee(val) - } +// NewDeleteGroupsioSubgroupInternalServerError builds a mailing-list service +// delete-groupsio-subgroup endpoint InternalServerError error. +func NewDeleteGroupsioSubgroupInternalServerError(body *DeleteGroupsioSubgroupInternalServerErrorResponseBody) *mailinglist.InternalServerError { + v := &mailinglist.InternalServerError{ + Message: *body.Message, } return v } -// NewUpdateGrpsioMailingListBadRequest builds a mailing-list service -// update-grpsio-mailing-list endpoint BadRequest error. -func NewUpdateGrpsioMailingListBadRequest(body *UpdateGrpsioMailingListBadRequestResponseBody) *mailinglist.BadRequestError { - v := &mailinglist.BadRequestError{ +// NewDeleteGroupsioSubgroupNotFound builds a mailing-list service +// delete-groupsio-subgroup endpoint NotFound error. +func NewDeleteGroupsioSubgroupNotFound(body *DeleteGroupsioSubgroupNotFoundResponseBody) *mailinglist.NotFoundError { + v := &mailinglist.NotFoundError{ Message: *body.Message, } return v } -// NewUpdateGrpsioMailingListConflict builds a mailing-list service -// update-grpsio-mailing-list endpoint Conflict error. -func NewUpdateGrpsioMailingListConflict(body *UpdateGrpsioMailingListConflictResponseBody) *mailinglist.ConflictError { - v := &mailinglist.ConflictError{ +// NewDeleteGroupsioSubgroupServiceUnavailable builds a mailing-list service +// delete-groupsio-subgroup endpoint ServiceUnavailable error. +func NewDeleteGroupsioSubgroupServiceUnavailable(body *DeleteGroupsioSubgroupServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { + v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } return v } -// NewUpdateGrpsioMailingListInternalServerError builds a mailing-list service -// update-grpsio-mailing-list endpoint InternalServerError error. -func NewUpdateGrpsioMailingListInternalServerError(body *UpdateGrpsioMailingListInternalServerErrorResponseBody) *mailinglist.InternalServerError { - v := &mailinglist.InternalServerError{ - Message: *body.Message, +// NewGetGroupsioSubgroupCountGroupsioCountOK builds a "mailing-list" service +// "get-groupsio-subgroup-count" endpoint result from a HTTP "OK" response. +func NewGetGroupsioSubgroupCountGroupsioCountOK(body *GetGroupsioSubgroupCountResponseBody) *mailinglist.GroupsioCount { + v := &mailinglist.GroupsioCount{ + Count: *body.Count, } return v } -// NewUpdateGrpsioMailingListNotFound builds a mailing-list service -// update-grpsio-mailing-list endpoint NotFound error. -func NewUpdateGrpsioMailingListNotFound(body *UpdateGrpsioMailingListNotFoundResponseBody) *mailinglist.NotFoundError { - v := &mailinglist.NotFoundError{ +// NewGetGroupsioSubgroupCountBadRequest builds a mailing-list service +// get-groupsio-subgroup-count endpoint BadRequest error. +func NewGetGroupsioSubgroupCountBadRequest(body *GetGroupsioSubgroupCountBadRequestResponseBody) *mailinglist.BadRequestError { + v := &mailinglist.BadRequestError{ Message: *body.Message, } return v } -// NewUpdateGrpsioMailingListServiceUnavailable builds a mailing-list service -// update-grpsio-mailing-list endpoint ServiceUnavailable error. -func NewUpdateGrpsioMailingListServiceUnavailable(body *UpdateGrpsioMailingListServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { - v := &mailinglist.ServiceUnavailableError{ +// NewGetGroupsioSubgroupCountInternalServerError builds a mailing-list service +// get-groupsio-subgroup-count endpoint InternalServerError error. +func NewGetGroupsioSubgroupCountInternalServerError(body *GetGroupsioSubgroupCountInternalServerErrorResponseBody) *mailinglist.InternalServerError { + v := &mailinglist.InternalServerError{ Message: *body.Message, } return v } -// NewDeleteGrpsioMailingListBadRequest builds a mailing-list service -// delete-grpsio-mailing-list endpoint BadRequest error. -func NewDeleteGrpsioMailingListBadRequest(body *DeleteGrpsioMailingListBadRequestResponseBody) *mailinglist.BadRequestError { - v := &mailinglist.BadRequestError{ +// NewGetGroupsioSubgroupCountServiceUnavailable builds a mailing-list service +// get-groupsio-subgroup-count endpoint ServiceUnavailable error. +func NewGetGroupsioSubgroupCountServiceUnavailable(body *GetGroupsioSubgroupCountServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { + v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } return v } -// NewDeleteGrpsioMailingListConflict builds a mailing-list service -// delete-grpsio-mailing-list endpoint Conflict error. -func NewDeleteGrpsioMailingListConflict(body *DeleteGrpsioMailingListConflictResponseBody) *mailinglist.ConflictError { - v := &mailinglist.ConflictError{ - Message: *body.Message, +// NewGetGroupsioSubgroupMemberCountGroupsioCountOK builds a "mailing-list" +// service "get-groupsio-subgroup-member-count" endpoint result from a HTTP +// "OK" response. +func NewGetGroupsioSubgroupMemberCountGroupsioCountOK(body *GetGroupsioSubgroupMemberCountResponseBody) *mailinglist.GroupsioCount { + v := &mailinglist.GroupsioCount{ + Count: *body.Count, } return v } -// NewDeleteGrpsioMailingListInternalServerError builds a mailing-list service -// delete-grpsio-mailing-list endpoint InternalServerError error. -func NewDeleteGrpsioMailingListInternalServerError(body *DeleteGrpsioMailingListInternalServerErrorResponseBody) *mailinglist.InternalServerError { +// NewGetGroupsioSubgroupMemberCountInternalServerError builds a mailing-list +// service get-groupsio-subgroup-member-count endpoint InternalServerError +// error. +func NewGetGroupsioSubgroupMemberCountInternalServerError(body *GetGroupsioSubgroupMemberCountInternalServerErrorResponseBody) *mailinglist.InternalServerError { v := &mailinglist.InternalServerError{ Message: *body.Message, } @@ -2462,9 +1843,9 @@ func NewDeleteGrpsioMailingListInternalServerError(body *DeleteGrpsioMailingList return v } -// NewDeleteGrpsioMailingListNotFound builds a mailing-list service -// delete-grpsio-mailing-list endpoint NotFound error. -func NewDeleteGrpsioMailingListNotFound(body *DeleteGrpsioMailingListNotFoundResponseBody) *mailinglist.NotFoundError { +// NewGetGroupsioSubgroupMemberCountNotFound builds a mailing-list service +// get-groupsio-subgroup-member-count endpoint NotFound error. +func NewGetGroupsioSubgroupMemberCountNotFound(body *GetGroupsioSubgroupMemberCountNotFoundResponseBody) *mailinglist.NotFoundError { v := &mailinglist.NotFoundError{ Message: *body.Message, } @@ -2472,9 +1853,9 @@ func NewDeleteGrpsioMailingListNotFound(body *DeleteGrpsioMailingListNotFoundRes return v } -// NewDeleteGrpsioMailingListServiceUnavailable builds a mailing-list service -// delete-grpsio-mailing-list endpoint ServiceUnavailable error. -func NewDeleteGrpsioMailingListServiceUnavailable(body *DeleteGrpsioMailingListServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { +// NewGetGroupsioSubgroupMemberCountServiceUnavailable builds a mailing-list +// service get-groupsio-subgroup-member-count endpoint ServiceUnavailable error. +func NewGetGroupsioSubgroupMemberCountServiceUnavailable(body *GetGroupsioSubgroupMemberCountServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } @@ -2482,51 +1863,25 @@ func NewDeleteGrpsioMailingListServiceUnavailable(body *DeleteGrpsioMailingListS return v } -// NewGetGrpsioMailingListSettingsResultOK builds a "mailing-list" service -// "get-grpsio-mailing-list-settings" endpoint result from a HTTP "OK" response. -func NewGetGrpsioMailingListSettingsResultOK(body *GetGrpsioMailingListSettingsResponseBody, etag *string) *mailinglist.GetGrpsioMailingListSettingsResult { - v := &mailinglist.GrpsIoMailingListSettings{ - UID: body.UID, - LastReviewedAt: body.LastReviewedAt, - LastReviewedBy: body.LastReviewedBy, - LastAuditedBy: body.LastAuditedBy, - LastAuditedTime: body.LastAuditedTime, - CreatedAt: body.CreatedAt, - UpdatedAt: body.UpdatedAt, - } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } +// NewListGroupsioMembersGroupsioMemberListOK builds a "mailing-list" service +// "list-groupsio-members" endpoint result from a HTTP "OK" response. +func NewListGroupsioMembersGroupsioMemberListOK(body *ListGroupsioMembersResponseBody) *mailinglist.GroupsioMemberList { + v := &mailinglist.GroupsioMemberList{ + Total: body.Total, } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) + if body.Items != nil { + v.Items = make([]*mailinglist.GroupsioMember, len(body.Items)) + for i, val := range body.Items { + v.Items[i] = unmarshalGroupsioMemberResponseBodyToMailinglistGroupsioMember(val) } } - res := &mailinglist.GetGrpsioMailingListSettingsResult{ - MailingListSettings: v, - } - res.Etag = etag - - return res -} - -// NewGetGrpsioMailingListSettingsBadRequest builds a mailing-list service -// get-grpsio-mailing-list-settings endpoint BadRequest error. -func NewGetGrpsioMailingListSettingsBadRequest(body *GetGrpsioMailingListSettingsBadRequestResponseBody) *mailinglist.BadRequestError { - v := &mailinglist.BadRequestError{ - Message: *body.Message, - } return v } -// NewGetGrpsioMailingListSettingsInternalServerError builds a mailing-list -// service get-grpsio-mailing-list-settings endpoint InternalServerError error. -func NewGetGrpsioMailingListSettingsInternalServerError(body *GetGrpsioMailingListSettingsInternalServerErrorResponseBody) *mailinglist.InternalServerError { +// NewListGroupsioMembersInternalServerError builds a mailing-list service +// list-groupsio-members endpoint InternalServerError error. +func NewListGroupsioMembersInternalServerError(body *ListGroupsioMembersInternalServerErrorResponseBody) *mailinglist.InternalServerError { v := &mailinglist.InternalServerError{ Message: *body.Message, } @@ -2534,9 +1889,9 @@ func NewGetGrpsioMailingListSettingsInternalServerError(body *GetGrpsioMailingLi return v } -// NewGetGrpsioMailingListSettingsNotFound builds a mailing-list service -// get-grpsio-mailing-list-settings endpoint NotFound error. -func NewGetGrpsioMailingListSettingsNotFound(body *GetGrpsioMailingListSettingsNotFoundResponseBody) *mailinglist.NotFoundError { +// NewListGroupsioMembersNotFound builds a mailing-list service +// list-groupsio-members endpoint NotFound error. +func NewListGroupsioMembersNotFound(body *ListGroupsioMembersNotFoundResponseBody) *mailinglist.NotFoundError { v := &mailinglist.NotFoundError{ Message: *body.Message, } @@ -2544,9 +1899,9 @@ func NewGetGrpsioMailingListSettingsNotFound(body *GetGrpsioMailingListSettingsN return v } -// NewGetGrpsioMailingListSettingsServiceUnavailable builds a mailing-list -// service get-grpsio-mailing-list-settings endpoint ServiceUnavailable error. -func NewGetGrpsioMailingListSettingsServiceUnavailable(body *GetGrpsioMailingListSettingsServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { +// NewListGroupsioMembersServiceUnavailable builds a mailing-list service +// list-groupsio-members endpoint ServiceUnavailable error. +func NewListGroupsioMembersServiceUnavailable(body *ListGroupsioMembersServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } @@ -2554,38 +1909,29 @@ func NewGetGrpsioMailingListSettingsServiceUnavailable(body *GetGrpsioMailingLis return v } -// NewUpdateGrpsioMailingListSettingsGrpsIoMailingListSettingsOK builds a -// "mailing-list" service "update-grpsio-mailing-list-settings" endpoint result -// from a HTTP "OK" response. -func NewUpdateGrpsioMailingListSettingsGrpsIoMailingListSettingsOK(body *UpdateGrpsioMailingListSettingsResponseBody) *mailinglist.GrpsIoMailingListSettings { - v := &mailinglist.GrpsIoMailingListSettings{ - UID: body.UID, - LastReviewedAt: body.LastReviewedAt, - LastReviewedBy: body.LastReviewedBy, - LastAuditedBy: body.LastAuditedBy, - LastAuditedTime: body.LastAuditedTime, - CreatedAt: body.CreatedAt, - UpdatedAt: body.UpdatedAt, - } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } +// NewAddGroupsioMemberGroupsioMemberCreated builds a "mailing-list" service +// "add-groupsio-member" endpoint result from a HTTP "Created" response. +func NewAddGroupsioMemberGroupsioMemberCreated(body *AddGroupsioMemberResponseBody) *mailinglist.GroupsioMember { + v := &mailinglist.GroupsioMember{ + ID: body.ID, + SubgroupID: body.SubgroupID, + Email: body.Email, + Name: body.Name, + FirstName: body.FirstName, + LastName: body.LastName, + ModStatus: body.ModStatus, + DeliveryMode: body.DeliveryMode, + Status: body.Status, + CreatedAt: body.CreatedAt, + UpdatedAt: body.UpdatedAt, } return v } -// NewUpdateGrpsioMailingListSettingsBadRequest builds a mailing-list service -// update-grpsio-mailing-list-settings endpoint BadRequest error. -func NewUpdateGrpsioMailingListSettingsBadRequest(body *UpdateGrpsioMailingListSettingsBadRequestResponseBody) *mailinglist.BadRequestError { +// NewAddGroupsioMemberBadRequest builds a mailing-list service +// add-groupsio-member endpoint BadRequest error. +func NewAddGroupsioMemberBadRequest(body *AddGroupsioMemberBadRequestResponseBody) *mailinglist.BadRequestError { v := &mailinglist.BadRequestError{ Message: *body.Message, } @@ -2593,9 +1939,9 @@ func NewUpdateGrpsioMailingListSettingsBadRequest(body *UpdateGrpsioMailingListS return v } -// NewUpdateGrpsioMailingListSettingsConflict builds a mailing-list service -// update-grpsio-mailing-list-settings endpoint Conflict error. -func NewUpdateGrpsioMailingListSettingsConflict(body *UpdateGrpsioMailingListSettingsConflictResponseBody) *mailinglist.ConflictError { +// NewAddGroupsioMemberConflict builds a mailing-list service +// add-groupsio-member endpoint Conflict error. +func NewAddGroupsioMemberConflict(body *AddGroupsioMemberConflictResponseBody) *mailinglist.ConflictError { v := &mailinglist.ConflictError{ Message: *body.Message, } @@ -2603,10 +1949,9 @@ func NewUpdateGrpsioMailingListSettingsConflict(body *UpdateGrpsioMailingListSet return v } -// NewUpdateGrpsioMailingListSettingsInternalServerError builds a mailing-list -// service update-grpsio-mailing-list-settings endpoint InternalServerError -// error. -func NewUpdateGrpsioMailingListSettingsInternalServerError(body *UpdateGrpsioMailingListSettingsInternalServerErrorResponseBody) *mailinglist.InternalServerError { +// NewAddGroupsioMemberInternalServerError builds a mailing-list service +// add-groupsio-member endpoint InternalServerError error. +func NewAddGroupsioMemberInternalServerError(body *AddGroupsioMemberInternalServerErrorResponseBody) *mailinglist.InternalServerError { v := &mailinglist.InternalServerError{ Message: *body.Message, } @@ -2614,9 +1959,9 @@ func NewUpdateGrpsioMailingListSettingsInternalServerError(body *UpdateGrpsioMai return v } -// NewUpdateGrpsioMailingListSettingsNotFound builds a mailing-list service -// update-grpsio-mailing-list-settings endpoint NotFound error. -func NewUpdateGrpsioMailingListSettingsNotFound(body *UpdateGrpsioMailingListSettingsNotFoundResponseBody) *mailinglist.NotFoundError { +// NewAddGroupsioMemberNotFound builds a mailing-list service +// add-groupsio-member endpoint NotFound error. +func NewAddGroupsioMemberNotFound(body *AddGroupsioMemberNotFoundResponseBody) *mailinglist.NotFoundError { v := &mailinglist.NotFoundError{ Message: *body.Message, } @@ -2624,10 +1969,9 @@ func NewUpdateGrpsioMailingListSettingsNotFound(body *UpdateGrpsioMailingListSet return v } -// NewUpdateGrpsioMailingListSettingsServiceUnavailable builds a mailing-list -// service update-grpsio-mailing-list-settings endpoint ServiceUnavailable -// error. -func NewUpdateGrpsioMailingListSettingsServiceUnavailable(body *UpdateGrpsioMailingListSettingsServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { +// NewAddGroupsioMemberServiceUnavailable builds a mailing-list service +// add-groupsio-member endpoint ServiceUnavailable error. +func NewAddGroupsioMemberServiceUnavailable(body *AddGroupsioMemberServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } @@ -2635,69 +1979,29 @@ func NewUpdateGrpsioMailingListSettingsServiceUnavailable(body *UpdateGrpsioMail return v } -// NewCreateGrpsioMailingListMemberGrpsIoMemberFullCreated builds a -// "mailing-list" service "create-grpsio-mailing-list-member" endpoint result -// from a HTTP "Created" response. -func NewCreateGrpsioMailingListMemberGrpsIoMemberFullCreated(body *CreateGrpsioMailingListMemberResponseBody) *mailinglist.GrpsIoMemberFull { - v := &mailinglist.GrpsIoMemberFull{ - UID: *body.UID, - MailingListUID: *body.MailingListUID, - Username: body.Username, - FirstName: *body.FirstName, - LastName: *body.LastName, - Email: *body.Email, - Organization: body.Organization, - JobTitle: body.JobTitle, - MemberType: *body.MemberType, - DeliveryMode: *body.DeliveryMode, - ModStatus: *body.ModStatus, - LastReviewedAt: body.LastReviewedAt, - LastReviewedBy: body.LastReviewedBy, - Status: *body.Status, - MemberID: body.MemberID, - GroupID: body.GroupID, - CreatedAt: *body.CreatedAt, - UpdatedAt: *body.UpdatedAt, - } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } - } - - return v -} - -// NewCreateGrpsioMailingListMemberBadRequest builds a mailing-list service -// create-grpsio-mailing-list-member endpoint BadRequest error. -func NewCreateGrpsioMailingListMemberBadRequest(body *CreateGrpsioMailingListMemberBadRequestResponseBody) *mailinglist.BadRequestError { - v := &mailinglist.BadRequestError{ - Message: *body.Message, - } - - return v -} - -// NewCreateGrpsioMailingListMemberConflict builds a mailing-list service -// create-grpsio-mailing-list-member endpoint Conflict error. -func NewCreateGrpsioMailingListMemberConflict(body *CreateGrpsioMailingListMemberConflictResponseBody) *mailinglist.ConflictError { - v := &mailinglist.ConflictError{ - Message: *body.Message, +// NewGetGroupsioMemberGroupsioMemberOK builds a "mailing-list" service +// "get-groupsio-member" endpoint result from a HTTP "OK" response. +func NewGetGroupsioMemberGroupsioMemberOK(body *GetGroupsioMemberResponseBody) *mailinglist.GroupsioMember { + v := &mailinglist.GroupsioMember{ + ID: body.ID, + SubgroupID: body.SubgroupID, + Email: body.Email, + Name: body.Name, + FirstName: body.FirstName, + LastName: body.LastName, + ModStatus: body.ModStatus, + DeliveryMode: body.DeliveryMode, + Status: body.Status, + CreatedAt: body.CreatedAt, + UpdatedAt: body.UpdatedAt, } return v } -// NewCreateGrpsioMailingListMemberInternalServerError builds a mailing-list -// service create-grpsio-mailing-list-member endpoint InternalServerError error. -func NewCreateGrpsioMailingListMemberInternalServerError(body *CreateGrpsioMailingListMemberInternalServerErrorResponseBody) *mailinglist.InternalServerError { +// NewGetGroupsioMemberInternalServerError builds a mailing-list service +// get-groupsio-member endpoint InternalServerError error. +func NewGetGroupsioMemberInternalServerError(body *GetGroupsioMemberInternalServerErrorResponseBody) *mailinglist.InternalServerError { v := &mailinglist.InternalServerError{ Message: *body.Message, } @@ -2705,9 +2009,9 @@ func NewCreateGrpsioMailingListMemberInternalServerError(body *CreateGrpsioMaili return v } -// NewCreateGrpsioMailingListMemberNotFound builds a mailing-list service -// create-grpsio-mailing-list-member endpoint NotFound error. -func NewCreateGrpsioMailingListMemberNotFound(body *CreateGrpsioMailingListMemberNotFoundResponseBody) *mailinglist.NotFoundError { +// NewGetGroupsioMemberNotFound builds a mailing-list service +// get-groupsio-member endpoint NotFound error. +func NewGetGroupsioMemberNotFound(body *GetGroupsioMemberNotFoundResponseBody) *mailinglist.NotFoundError { v := &mailinglist.NotFoundError{ Message: *body.Message, } @@ -2715,9 +2019,9 @@ func NewCreateGrpsioMailingListMemberNotFound(body *CreateGrpsioMailingListMembe return v } -// NewCreateGrpsioMailingListMemberServiceUnavailable builds a mailing-list -// service create-grpsio-mailing-list-member endpoint ServiceUnavailable error. -func NewCreateGrpsioMailingListMemberServiceUnavailable(body *CreateGrpsioMailingListMemberServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { +// NewGetGroupsioMemberServiceUnavailable builds a mailing-list service +// get-groupsio-member endpoint ServiceUnavailable error. +func NewGetGroupsioMemberServiceUnavailable(body *GetGroupsioMemberServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } @@ -2725,67 +2029,29 @@ func NewCreateGrpsioMailingListMemberServiceUnavailable(body *CreateGrpsioMailin return v } -// NewGetGrpsioMailingListMemberResultOK builds a "mailing-list" service -// "get-grpsio-mailing-list-member" endpoint result from a HTTP "OK" response. -func NewGetGrpsioMailingListMemberResultOK(body *GetGrpsioMailingListMemberResponseBody, etag *string) *mailinglist.GetGrpsioMailingListMemberResult { - v := &mailinglist.GrpsIoMemberWithReadonlyAttributes{ - UID: body.UID, - MailingListUID: body.MailingListUID, - Username: body.Username, - FirstName: body.FirstName, - LastName: body.LastName, - Email: body.Email, - Organization: body.Organization, - JobTitle: body.JobTitle, - LastReviewedAt: body.LastReviewedAt, - LastReviewedBy: body.LastReviewedBy, - Status: body.Status, - MemberID: body.MemberID, - GroupID: body.GroupID, - CreatedAt: body.CreatedAt, - UpdatedAt: body.UpdatedAt, - } - if body.MemberType != nil { - v.MemberType = *body.MemberType +// NewUpdateGroupsioMemberGroupsioMemberOK builds a "mailing-list" service +// "update-groupsio-member" endpoint result from a HTTP "OK" response. +func NewUpdateGroupsioMemberGroupsioMemberOK(body *UpdateGroupsioMemberResponseBody) *mailinglist.GroupsioMember { + v := &mailinglist.GroupsioMember{ + ID: body.ID, + SubgroupID: body.SubgroupID, + Email: body.Email, + Name: body.Name, + FirstName: body.FirstName, + LastName: body.LastName, + ModStatus: body.ModStatus, + DeliveryMode: body.DeliveryMode, + Status: body.Status, + CreatedAt: body.CreatedAt, + UpdatedAt: body.UpdatedAt, } - if body.DeliveryMode != nil { - v.DeliveryMode = *body.DeliveryMode - } - if body.ModStatus != nil { - v.ModStatus = *body.ModStatus - } - if body.MemberType == nil { - v.MemberType = "direct" - } - if body.DeliveryMode == nil { - v.DeliveryMode = "normal" - } - if body.ModStatus == nil { - v.ModStatus = "none" - } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } - } - res := &mailinglist.GetGrpsioMailingListMemberResult{ - Member: v, - } - res.Etag = etag - return res + return v } -// NewGetGrpsioMailingListMemberBadRequest builds a mailing-list service -// get-grpsio-mailing-list-member endpoint BadRequest error. -func NewGetGrpsioMailingListMemberBadRequest(body *GetGrpsioMailingListMemberBadRequestResponseBody) *mailinglist.BadRequestError { +// NewUpdateGroupsioMemberBadRequest builds a mailing-list service +// update-groupsio-member endpoint BadRequest error. +func NewUpdateGroupsioMemberBadRequest(body *UpdateGroupsioMemberBadRequestResponseBody) *mailinglist.BadRequestError { v := &mailinglist.BadRequestError{ Message: *body.Message, } @@ -2793,9 +2059,9 @@ func NewGetGrpsioMailingListMemberBadRequest(body *GetGrpsioMailingListMemberBad return v } -// NewGetGrpsioMailingListMemberInternalServerError builds a mailing-list -// service get-grpsio-mailing-list-member endpoint InternalServerError error. -func NewGetGrpsioMailingListMemberInternalServerError(body *GetGrpsioMailingListMemberInternalServerErrorResponseBody) *mailinglist.InternalServerError { +// NewUpdateGroupsioMemberInternalServerError builds a mailing-list service +// update-groupsio-member endpoint InternalServerError error. +func NewUpdateGroupsioMemberInternalServerError(body *UpdateGroupsioMemberInternalServerErrorResponseBody) *mailinglist.InternalServerError { v := &mailinglist.InternalServerError{ Message: *body.Message, } @@ -2803,9 +2069,9 @@ func NewGetGrpsioMailingListMemberInternalServerError(body *GetGrpsioMailingList return v } -// NewGetGrpsioMailingListMemberNotFound builds a mailing-list service -// get-grpsio-mailing-list-member endpoint NotFound error. -func NewGetGrpsioMailingListMemberNotFound(body *GetGrpsioMailingListMemberNotFoundResponseBody) *mailinglist.NotFoundError { +// NewUpdateGroupsioMemberNotFound builds a mailing-list service +// update-groupsio-member endpoint NotFound error. +func NewUpdateGroupsioMemberNotFound(body *UpdateGroupsioMemberNotFoundResponseBody) *mailinglist.NotFoundError { v := &mailinglist.NotFoundError{ Message: *body.Message, } @@ -2813,9 +2079,9 @@ func NewGetGrpsioMailingListMemberNotFound(body *GetGrpsioMailingListMemberNotFo return v } -// NewGetGrpsioMailingListMemberServiceUnavailable builds a mailing-list -// service get-grpsio-mailing-list-member endpoint ServiceUnavailable error. -func NewGetGrpsioMailingListMemberServiceUnavailable(body *GetGrpsioMailingListMemberServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { +// NewUpdateGroupsioMemberServiceUnavailable builds a mailing-list service +// update-groupsio-member endpoint ServiceUnavailable error. +func NewUpdateGroupsioMemberServiceUnavailable(body *UpdateGroupsioMemberServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } @@ -2823,84 +2089,9 @@ func NewGetGrpsioMailingListMemberServiceUnavailable(body *GetGrpsioMailingListM return v } -// NewUpdateGrpsioMailingListMemberGrpsIoMemberWithReadonlyAttributesOK builds -// a "mailing-list" service "update-grpsio-mailing-list-member" endpoint result -// from a HTTP "OK" response. -func NewUpdateGrpsioMailingListMemberGrpsIoMemberWithReadonlyAttributesOK(body *UpdateGrpsioMailingListMemberResponseBody) *mailinglist.GrpsIoMemberWithReadonlyAttributes { - v := &mailinglist.GrpsIoMemberWithReadonlyAttributes{ - UID: body.UID, - MailingListUID: body.MailingListUID, - Username: body.Username, - FirstName: body.FirstName, - LastName: body.LastName, - Email: body.Email, - Organization: body.Organization, - JobTitle: body.JobTitle, - LastReviewedAt: body.LastReviewedAt, - LastReviewedBy: body.LastReviewedBy, - Status: body.Status, - MemberID: body.MemberID, - GroupID: body.GroupID, - CreatedAt: body.CreatedAt, - UpdatedAt: body.UpdatedAt, - } - if body.MemberType != nil { - v.MemberType = *body.MemberType - } - if body.DeliveryMode != nil { - v.DeliveryMode = *body.DeliveryMode - } - if body.ModStatus != nil { - v.ModStatus = *body.ModStatus - } - if body.MemberType == nil { - v.MemberType = "direct" - } - if body.DeliveryMode == nil { - v.DeliveryMode = "normal" - } - if body.ModStatus == nil { - v.ModStatus = "none" - } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoResponseBodyToMailinglistUserInfo(val) - } - } - - return v -} - -// NewUpdateGrpsioMailingListMemberBadRequest builds a mailing-list service -// update-grpsio-mailing-list-member endpoint BadRequest error. -func NewUpdateGrpsioMailingListMemberBadRequest(body *UpdateGrpsioMailingListMemberBadRequestResponseBody) *mailinglist.BadRequestError { - v := &mailinglist.BadRequestError{ - Message: *body.Message, - } - - return v -} - -// NewUpdateGrpsioMailingListMemberConflict builds a mailing-list service -// update-grpsio-mailing-list-member endpoint Conflict error. -func NewUpdateGrpsioMailingListMemberConflict(body *UpdateGrpsioMailingListMemberConflictResponseBody) *mailinglist.ConflictError { - v := &mailinglist.ConflictError{ - Message: *body.Message, - } - - return v -} - -// NewUpdateGrpsioMailingListMemberInternalServerError builds a mailing-list -// service update-grpsio-mailing-list-member endpoint InternalServerError error. -func NewUpdateGrpsioMailingListMemberInternalServerError(body *UpdateGrpsioMailingListMemberInternalServerErrorResponseBody) *mailinglist.InternalServerError { +// NewDeleteGroupsioMemberInternalServerError builds a mailing-list service +// delete-groupsio-member endpoint InternalServerError error. +func NewDeleteGroupsioMemberInternalServerError(body *DeleteGroupsioMemberInternalServerErrorResponseBody) *mailinglist.InternalServerError { v := &mailinglist.InternalServerError{ Message: *body.Message, } @@ -2908,9 +2099,9 @@ func NewUpdateGrpsioMailingListMemberInternalServerError(body *UpdateGrpsioMaili return v } -// NewUpdateGrpsioMailingListMemberNotFound builds a mailing-list service -// update-grpsio-mailing-list-member endpoint NotFound error. -func NewUpdateGrpsioMailingListMemberNotFound(body *UpdateGrpsioMailingListMemberNotFoundResponseBody) *mailinglist.NotFoundError { +// NewDeleteGroupsioMemberNotFound builds a mailing-list service +// delete-groupsio-member endpoint NotFound error. +func NewDeleteGroupsioMemberNotFound(body *DeleteGroupsioMemberNotFoundResponseBody) *mailinglist.NotFoundError { v := &mailinglist.NotFoundError{ Message: *body.Message, } @@ -2918,9 +2109,9 @@ func NewUpdateGrpsioMailingListMemberNotFound(body *UpdateGrpsioMailingListMembe return v } -// NewUpdateGrpsioMailingListMemberServiceUnavailable builds a mailing-list -// service update-grpsio-mailing-list-member endpoint ServiceUnavailable error. -func NewUpdateGrpsioMailingListMemberServiceUnavailable(body *UpdateGrpsioMailingListMemberServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { +// NewDeleteGroupsioMemberServiceUnavailable builds a mailing-list service +// delete-groupsio-member endpoint ServiceUnavailable error. +func NewDeleteGroupsioMemberServiceUnavailable(body *DeleteGroupsioMemberServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } @@ -2928,9 +2119,9 @@ func NewUpdateGrpsioMailingListMemberServiceUnavailable(body *UpdateGrpsioMailin return v } -// NewDeleteGrpsioMailingListMemberBadRequest builds a mailing-list service -// delete-grpsio-mailing-list-member endpoint BadRequest error. -func NewDeleteGrpsioMailingListMemberBadRequest(body *DeleteGrpsioMailingListMemberBadRequestResponseBody) *mailinglist.BadRequestError { +// NewInviteGroupsioMembersBadRequest builds a mailing-list service +// invite-groupsio-members endpoint BadRequest error. +func NewInviteGroupsioMembersBadRequest(body *InviteGroupsioMembersBadRequestResponseBody) *mailinglist.BadRequestError { v := &mailinglist.BadRequestError{ Message: *body.Message, } @@ -2938,19 +2129,9 @@ func NewDeleteGrpsioMailingListMemberBadRequest(body *DeleteGrpsioMailingListMem return v } -// NewDeleteGrpsioMailingListMemberConflict builds a mailing-list service -// delete-grpsio-mailing-list-member endpoint Conflict error. -func NewDeleteGrpsioMailingListMemberConflict(body *DeleteGrpsioMailingListMemberConflictResponseBody) *mailinglist.ConflictError { - v := &mailinglist.ConflictError{ - Message: *body.Message, - } - - return v -} - -// NewDeleteGrpsioMailingListMemberInternalServerError builds a mailing-list -// service delete-grpsio-mailing-list-member endpoint InternalServerError error. -func NewDeleteGrpsioMailingListMemberInternalServerError(body *DeleteGrpsioMailingListMemberInternalServerErrorResponseBody) *mailinglist.InternalServerError { +// NewInviteGroupsioMembersInternalServerError builds a mailing-list service +// invite-groupsio-members endpoint InternalServerError error. +func NewInviteGroupsioMembersInternalServerError(body *InviteGroupsioMembersInternalServerErrorResponseBody) *mailinglist.InternalServerError { v := &mailinglist.InternalServerError{ Message: *body.Message, } @@ -2958,9 +2139,9 @@ func NewDeleteGrpsioMailingListMemberInternalServerError(body *DeleteGrpsioMaili return v } -// NewDeleteGrpsioMailingListMemberNotFound builds a mailing-list service -// delete-grpsio-mailing-list-member endpoint NotFound error. -func NewDeleteGrpsioMailingListMemberNotFound(body *DeleteGrpsioMailingListMemberNotFoundResponseBody) *mailinglist.NotFoundError { +// NewInviteGroupsioMembersNotFound builds a mailing-list service +// invite-groupsio-members endpoint NotFound error. +func NewInviteGroupsioMembersNotFound(body *InviteGroupsioMembersNotFoundResponseBody) *mailinglist.NotFoundError { v := &mailinglist.NotFoundError{ Message: *body.Message, } @@ -2968,9 +2149,9 @@ func NewDeleteGrpsioMailingListMemberNotFound(body *DeleteGrpsioMailingListMembe return v } -// NewDeleteGrpsioMailingListMemberServiceUnavailable builds a mailing-list -// service delete-grpsio-mailing-list-member endpoint ServiceUnavailable error. -func NewDeleteGrpsioMailingListMemberServiceUnavailable(body *DeleteGrpsioMailingListMemberServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { +// NewInviteGroupsioMembersServiceUnavailable builds a mailing-list service +// invite-groupsio-members endpoint ServiceUnavailable error. +func NewInviteGroupsioMembersServiceUnavailable(body *InviteGroupsioMembersServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { v := &mailinglist.ServiceUnavailableError{ Message: *body.Message, } @@ -2978,920 +2159,102 @@ func NewDeleteGrpsioMailingListMemberServiceUnavailable(body *DeleteGrpsioMailin return v } -// NewGroupsioWebhookBadRequest builds a mailing-list service groupsio-webhook -// endpoint BadRequest error. -func NewGroupsioWebhookBadRequest(body *GroupsioWebhookBadRequestResponseBody) *mailinglist.BadRequestError { - v := &mailinglist.BadRequestError{ - Message: *body.Message, +// NewCheckGroupsioSubscriberGroupsioCheckSubscriberResponseOK builds a +// "mailing-list" service "check-groupsio-subscriber" endpoint result from a +// HTTP "OK" response. +func NewCheckGroupsioSubscriberGroupsioCheckSubscriberResponseOK(body *CheckGroupsioSubscriberResponseBody) *mailinglist.GroupsioCheckSubscriberResponse { + v := &mailinglist.GroupsioCheckSubscriberResponse{ + Subscribed: *body.Subscribed, } return v } -// NewGroupsioWebhookUnauthorized builds a mailing-list service -// groupsio-webhook endpoint Unauthorized error. -func NewGroupsioWebhookUnauthorized(body *GroupsioWebhookUnauthorizedResponseBody) *mailinglist.UnauthorizedError { - v := &mailinglist.UnauthorizedError{ +// NewCheckGroupsioSubscriberBadRequest builds a mailing-list service +// check-groupsio-subscriber endpoint BadRequest error. +func NewCheckGroupsioSubscriberBadRequest(body *CheckGroupsioSubscriberBadRequestResponseBody) *mailinglist.BadRequestError { + v := &mailinglist.BadRequestError{ Message: *body.Message, } return v } -// ValidateCreateGrpsioServiceResponseBody runs the validations defined on -// Create-Grpsio-ServiceResponseBody -func ValidateCreateGrpsioServiceResponseBody(body *CreateGrpsioServiceResponseBody) (err error) { - if body.Type == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("type", "body")) - } - if body.ProjectUID == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("project_uid", "body")) - } - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - if body.Type != nil { - if !(*body.Type == "primary" || *body.Type == "formation" || *body.Type == "shared") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", *body.Type, []any{"primary", "formation", "shared"})) - } - } - for _, e := range body.GlobalOwners { - err = goa.MergeErrors(err, goa.ValidateFormat("body.global_owners[*]", e, goa.FormatEmail)) - } - if body.ParentServiceUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.parent_service_uid", *body.ParentServiceUID, goa.FormatUUID)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_slug", *body.ProjectSlug, goa.FormatRegexp)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.project_slug", *body.ProjectSlug, "^[a-z][a-z0-9_\\-]*[a-z0-9]$")) - } - if body.ProjectUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) - } - if body.URL != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.url", *body.URL, goa.FormatURI)) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - return -} - -// ValidateGetGrpsioServiceResponseBody runs the validations defined on -// Get-Grpsio-ServiceResponseBody -func ValidateGetGrpsioServiceResponseBody(body *GetGrpsioServiceResponseBody) (err error) { - if body.Type == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("type", "body")) - } - if body.ProjectUID == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("project_uid", "body")) - } - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - if body.Type != nil { - if !(*body.Type == "primary" || *body.Type == "formation" || *body.Type == "shared") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", *body.Type, []any{"primary", "formation", "shared"})) - } - } - for _, e := range body.GlobalOwners { - err = goa.MergeErrors(err, goa.ValidateFormat("body.global_owners[*]", e, goa.FormatEmail)) - } - if body.ParentServiceUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.parent_service_uid", *body.ParentServiceUID, goa.FormatUUID)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_slug", *body.ProjectSlug, goa.FormatRegexp)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.project_slug", *body.ProjectSlug, "^[a-z][a-z0-9_\\-]*[a-z0-9]$")) - } - if body.ProjectUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) - } - if body.URL != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.url", *body.URL, goa.FormatURI)) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - return -} - -// ValidateUpdateGrpsioServiceResponseBody runs the validations defined on -// Update-Grpsio-ServiceResponseBody -func ValidateUpdateGrpsioServiceResponseBody(body *UpdateGrpsioServiceResponseBody) (err error) { - if body.Type == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("type", "body")) - } - if body.ProjectUID == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("project_uid", "body")) - } - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - if body.Type != nil { - if !(*body.Type == "primary" || *body.Type == "formation" || *body.Type == "shared") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", *body.Type, []any{"primary", "formation", "shared"})) - } - } - for _, e := range body.GlobalOwners { - err = goa.MergeErrors(err, goa.ValidateFormat("body.global_owners[*]", e, goa.FormatEmail)) - } - if body.ParentServiceUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.parent_service_uid", *body.ParentServiceUID, goa.FormatUUID)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_slug", *body.ProjectSlug, goa.FormatRegexp)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.project_slug", *body.ProjectSlug, "^[a-z][a-z0-9_\\-]*[a-z0-9]$")) - } - if body.ProjectUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) - } - if body.URL != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.url", *body.URL, goa.FormatURI)) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - return -} - -// ValidateGetGrpsioServiceSettingsResponseBody runs the validations defined on -// Get-Grpsio-Service-SettingsResponseBody -func ValidateGetGrpsioServiceSettingsResponseBody(body *GetGrpsioServiceSettingsResponseBody) (err error) { - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if body.LastReviewedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_reviewed_at", *body.LastReviewedAt, goa.FormatDateTime)) - } - if body.LastAuditedTime != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_audited_time", *body.LastAuditedTime, goa.FormatDateTime)) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) - } - return -} - -// ValidateUpdateGrpsioServiceSettingsResponseBody runs the validations defined -// on Update-Grpsio-Service-SettingsResponseBody -func ValidateUpdateGrpsioServiceSettingsResponseBody(body *UpdateGrpsioServiceSettingsResponseBody) (err error) { - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if body.LastReviewedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_reviewed_at", *body.LastReviewedAt, goa.FormatDateTime)) - } - if body.LastAuditedTime != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_audited_time", *body.LastAuditedTime, goa.FormatDateTime)) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) - } - return -} - -// ValidateCreateGrpsioMailingListResponseBody runs the validations defined on -// Create-Grpsio-Mailing-ListResponseBody -func ValidateCreateGrpsioMailingListResponseBody(body *CreateGrpsioMailingListResponseBody) (err error) { - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - if body.GroupName != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.group_name", *body.GroupName, "^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$")) - } - if body.GroupName != nil { - if utf8.RuneCountInString(*body.GroupName) < 3 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", *body.GroupName, utf8.RuneCountInString(*body.GroupName), 3, true)) - } - } - if body.GroupName != nil { - if utf8.RuneCountInString(*body.GroupName) > 34 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", *body.GroupName, utf8.RuneCountInString(*body.GroupName), 34, false)) - } - } - if body.GroupID != nil { - if *body.GroupID < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.group_id", *body.GroupID, 0, true)) - } - } - if body.Type != nil { - if !(*body.Type == "announcement" || *body.Type == "discussion_moderated" || *body.Type == "discussion_open") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", *body.Type, []any{"announcement", "discussion_moderated", "discussion_open"})) - } - } - if body.AudienceAccess != nil { - if !(*body.AudienceAccess == "public" || *body.AudienceAccess == "approval_required" || *body.AudienceAccess == "invite_only") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.audience_access", *body.AudienceAccess, []any{"public", "approval_required", "invite_only"})) - } - } - for _, e := range body.Committees { - if e != nil { - if err2 := ValidateCommitteeResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if body.Description != nil { - if utf8.RuneCountInString(*body.Description) < 11 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", *body.Description, utf8.RuneCountInString(*body.Description), 11, true)) - } - } - if body.Description != nil { - if utf8.RuneCountInString(*body.Description) > 500 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", *body.Description, utf8.RuneCountInString(*body.Description), 500, false)) - } - } - if body.Title != nil { - if utf8.RuneCountInString(*body.Title) < 5 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", *body.Title, utf8.RuneCountInString(*body.Title), 5, true)) - } - } - if body.Title != nil { - if utf8.RuneCountInString(*body.Title) > 100 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", *body.Title, utf8.RuneCountInString(*body.Title), 100, false)) - } - } - if body.SubjectTag != nil { - if utf8.RuneCountInString(*body.SubjectTag) > 50 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.subject_tag", *body.SubjectTag, utf8.RuneCountInString(*body.SubjectTag), 50, false)) - } - } - if body.ServiceUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.service_uid", *body.ServiceUID, goa.FormatUUID)) - } - if body.SubscriberCount != nil { - if *body.SubscriberCount < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.subscriber_count", *body.SubscriberCount, 0, true)) - } - } - if body.ProjectUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_slug", *body.ProjectSlug, goa.FormatRegexp)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.project_slug", *body.ProjectSlug, "^[a-z][a-z0-9_\\-]*[a-z0-9]$")) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) - } - return -} - -// ValidateGetGrpsioMailingListResponseBody runs the validations defined on -// Get-Grpsio-Mailing-ListResponseBody -func ValidateGetGrpsioMailingListResponseBody(body *GetGrpsioMailingListResponseBody) (err error) { - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - if body.GroupName != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.group_name", *body.GroupName, "^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$")) - } - if body.GroupName != nil { - if utf8.RuneCountInString(*body.GroupName) < 3 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", *body.GroupName, utf8.RuneCountInString(*body.GroupName), 3, true)) - } - } - if body.GroupName != nil { - if utf8.RuneCountInString(*body.GroupName) > 34 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", *body.GroupName, utf8.RuneCountInString(*body.GroupName), 34, false)) - } - } - if body.GroupID != nil { - if *body.GroupID < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.group_id", *body.GroupID, 0, true)) - } - } - if body.Type != nil { - if !(*body.Type == "announcement" || *body.Type == "discussion_moderated" || *body.Type == "discussion_open") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", *body.Type, []any{"announcement", "discussion_moderated", "discussion_open"})) - } - } - if body.AudienceAccess != nil { - if !(*body.AudienceAccess == "public" || *body.AudienceAccess == "approval_required" || *body.AudienceAccess == "invite_only") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.audience_access", *body.AudienceAccess, []any{"public", "approval_required", "invite_only"})) - } - } - for _, e := range body.Committees { - if e != nil { - if err2 := ValidateCommitteeResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if body.Description != nil { - if utf8.RuneCountInString(*body.Description) < 11 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", *body.Description, utf8.RuneCountInString(*body.Description), 11, true)) - } - } - if body.Description != nil { - if utf8.RuneCountInString(*body.Description) > 500 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", *body.Description, utf8.RuneCountInString(*body.Description), 500, false)) - } - } - if body.Title != nil { - if utf8.RuneCountInString(*body.Title) < 5 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", *body.Title, utf8.RuneCountInString(*body.Title), 5, true)) - } - } - if body.Title != nil { - if utf8.RuneCountInString(*body.Title) > 100 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", *body.Title, utf8.RuneCountInString(*body.Title), 100, false)) - } - } - if body.SubjectTag != nil { - if utf8.RuneCountInString(*body.SubjectTag) > 50 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.subject_tag", *body.SubjectTag, utf8.RuneCountInString(*body.SubjectTag), 50, false)) - } - } - if body.ServiceUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.service_uid", *body.ServiceUID, goa.FormatUUID)) - } - if body.SubscriberCount != nil { - if *body.SubscriberCount < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.subscriber_count", *body.SubscriberCount, 0, true)) - } - } - if body.ProjectUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_slug", *body.ProjectSlug, goa.FormatRegexp)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.project_slug", *body.ProjectSlug, "^[a-z][a-z0-9_\\-]*[a-z0-9]$")) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) - } - return -} - -// ValidateUpdateGrpsioMailingListResponseBody runs the validations defined on -// Update-Grpsio-Mailing-ListResponseBody -func ValidateUpdateGrpsioMailingListResponseBody(body *UpdateGrpsioMailingListResponseBody) (err error) { - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - if body.GroupName != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.group_name", *body.GroupName, "^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$")) - } - if body.GroupName != nil { - if utf8.RuneCountInString(*body.GroupName) < 3 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", *body.GroupName, utf8.RuneCountInString(*body.GroupName), 3, true)) - } - } - if body.GroupName != nil { - if utf8.RuneCountInString(*body.GroupName) > 34 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", *body.GroupName, utf8.RuneCountInString(*body.GroupName), 34, false)) - } - } - if body.GroupID != nil { - if *body.GroupID < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.group_id", *body.GroupID, 0, true)) - } - } - if body.Type != nil { - if !(*body.Type == "announcement" || *body.Type == "discussion_moderated" || *body.Type == "discussion_open") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", *body.Type, []any{"announcement", "discussion_moderated", "discussion_open"})) - } - } - if body.AudienceAccess != nil { - if !(*body.AudienceAccess == "public" || *body.AudienceAccess == "approval_required" || *body.AudienceAccess == "invite_only") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.audience_access", *body.AudienceAccess, []any{"public", "approval_required", "invite_only"})) - } - } - for _, e := range body.Committees { - if e != nil { - if err2 := ValidateCommitteeResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if body.Description != nil { - if utf8.RuneCountInString(*body.Description) < 11 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", *body.Description, utf8.RuneCountInString(*body.Description), 11, true)) - } - } - if body.Description != nil { - if utf8.RuneCountInString(*body.Description) > 500 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", *body.Description, utf8.RuneCountInString(*body.Description), 500, false)) - } - } - if body.Title != nil { - if utf8.RuneCountInString(*body.Title) < 5 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", *body.Title, utf8.RuneCountInString(*body.Title), 5, true)) - } - } - if body.Title != nil { - if utf8.RuneCountInString(*body.Title) > 100 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", *body.Title, utf8.RuneCountInString(*body.Title), 100, false)) - } - } - if body.SubjectTag != nil { - if utf8.RuneCountInString(*body.SubjectTag) > 50 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.subject_tag", *body.SubjectTag, utf8.RuneCountInString(*body.SubjectTag), 50, false)) - } - } - if body.ServiceUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.service_uid", *body.ServiceUID, goa.FormatUUID)) - } - if body.SubscriberCount != nil { - if *body.SubscriberCount < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.subscriber_count", *body.SubscriberCount, 0, true)) - } - } - if body.ProjectUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_slug", *body.ProjectSlug, goa.FormatRegexp)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.project_slug", *body.ProjectSlug, "^[a-z][a-z0-9_\\-]*[a-z0-9]$")) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) +// NewCheckGroupsioSubscriberInternalServerError builds a mailing-list service +// check-groupsio-subscriber endpoint InternalServerError error. +func NewCheckGroupsioSubscriberInternalServerError(body *CheckGroupsioSubscriberInternalServerErrorResponseBody) *mailinglist.InternalServerError { + v := &mailinglist.InternalServerError{ + Message: *body.Message, } - return -} -// ValidateGetGrpsioMailingListSettingsResponseBody runs the validations -// defined on Get-Grpsio-Mailing-List-SettingsResponseBody -func ValidateGetGrpsioMailingListSettingsResponseBody(body *GetGrpsioMailingListSettingsResponseBody) (err error) { - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if body.LastReviewedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_reviewed_at", *body.LastReviewedAt, goa.FormatDateTime)) - } - if body.LastAuditedTime != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_audited_time", *body.LastAuditedTime, goa.FormatDateTime)) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) - } - return + return v } -// ValidateUpdateGrpsioMailingListSettingsResponseBody runs the validations -// defined on Update-Grpsio-Mailing-List-SettingsResponseBody -func ValidateUpdateGrpsioMailingListSettingsResponseBody(body *UpdateGrpsioMailingListSettingsResponseBody) (err error) { - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if body.LastReviewedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_reviewed_at", *body.LastReviewedAt, goa.FormatDateTime)) - } - if body.LastAuditedTime != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_audited_time", *body.LastAuditedTime, goa.FormatDateTime)) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) +// NewCheckGroupsioSubscriberServiceUnavailable builds a mailing-list service +// check-groupsio-subscriber endpoint ServiceUnavailable error. +func NewCheckGroupsioSubscriberServiceUnavailable(body *CheckGroupsioSubscriberServiceUnavailableResponseBody) *mailinglist.ServiceUnavailableError { + v := &mailinglist.ServiceUnavailableError{ + Message: *body.Message, } - return -} -// ValidateCreateGrpsioMailingListMemberResponseBody runs the validations -// defined on Create-Grpsio-Mailing-List-MemberResponseBody -func ValidateCreateGrpsioMailingListMemberResponseBody(body *CreateGrpsioMailingListMemberResponseBody) (err error) { - if body.UID == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("uid", "body")) - } - if body.MailingListUID == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("mailing_list_uid", "body")) - } - if body.FirstName == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("first_name", "body")) - } - if body.LastName == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("last_name", "body")) - } - if body.Email == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("email", "body")) - } - if body.MemberType == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("member_type", "body")) - } - if body.DeliveryMode == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("delivery_mode", "body")) - } - if body.ModStatus == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("mod_status", "body")) - } - if body.Status == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("status", "body")) - } - if body.CreatedAt == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("created_at", "body")) - } - if body.UpdatedAt == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("updated_at", "body")) - } - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - if body.MailingListUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.mailing_list_uid", *body.MailingListUID, goa.FormatUUID)) - } - if body.Username != nil { - if utf8.RuneCountInString(*body.Username) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.username", *body.Username, utf8.RuneCountInString(*body.Username), 255, false)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 1, true)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 255, false)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 1, true)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 255, false)) - } - } - if body.Email != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) - } - if body.Organization != nil { - if utf8.RuneCountInString(*body.Organization) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.organization", *body.Organization, utf8.RuneCountInString(*body.Organization), 255, false)) - } - } - if body.JobTitle != nil { - if utf8.RuneCountInString(*body.JobTitle) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.job_title", *body.JobTitle, utf8.RuneCountInString(*body.JobTitle), 255, false)) - } - } - if body.MemberType != nil { - if !(*body.MemberType == "committee" || *body.MemberType == "direct") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.member_type", *body.MemberType, []any{"committee", "direct"})) - } - } - if body.DeliveryMode != nil { - if !(*body.DeliveryMode == "normal" || *body.DeliveryMode == "digest" || *body.DeliveryMode == "none") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.delivery_mode", *body.DeliveryMode, []any{"normal", "digest", "none"})) - } - } - if body.ModStatus != nil { - if !(*body.ModStatus == "none" || *body.ModStatus == "moderator" || *body.ModStatus == "owner") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.mod_status", *body.ModStatus, []any{"none", "moderator", "owner"})) - } - } - if body.LastReviewedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_reviewed_at", *body.LastReviewedAt, goa.FormatDateTime)) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - return + return v } -// ValidateGetGrpsioMailingListMemberResponseBody runs the validations defined -// on Get-Grpsio-Mailing-List-MemberResponseBody -func ValidateGetGrpsioMailingListMemberResponseBody(body *GetGrpsioMailingListMemberResponseBody) (err error) { - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - if body.MailingListUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.mailing_list_uid", *body.MailingListUID, goa.FormatUUID)) - } - if body.Username != nil { - if utf8.RuneCountInString(*body.Username) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.username", *body.Username, utf8.RuneCountInString(*body.Username), 255, false)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 1, true)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 255, false)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 1, true)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 255, false)) - } - } - if body.Email != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) - } - if body.Organization != nil { - if utf8.RuneCountInString(*body.Organization) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.organization", *body.Organization, utf8.RuneCountInString(*body.Organization), 255, false)) - } - } - if body.JobTitle != nil { - if utf8.RuneCountInString(*body.JobTitle) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.job_title", *body.JobTitle, utf8.RuneCountInString(*body.JobTitle), 255, false)) - } - } - if body.MemberType != nil { - if !(*body.MemberType == "committee" || *body.MemberType == "direct") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.member_type", *body.MemberType, []any{"committee", "direct"})) - } - } - if body.DeliveryMode != nil { - if !(*body.DeliveryMode == "normal" || *body.DeliveryMode == "digest" || *body.DeliveryMode == "none") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.delivery_mode", *body.DeliveryMode, []any{"normal", "digest", "none"})) - } - } - if body.ModStatus != nil { - if !(*body.ModStatus == "none" || *body.ModStatus == "moderator" || *body.ModStatus == "owner") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.mod_status", *body.ModStatus, []any{"none", "moderator", "owner"})) - } - } - if body.LastReviewedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_reviewed_at", *body.LastReviewedAt, goa.FormatDateTime)) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) - } - for _, e := range body.Writers { +// ValidateListGroupsioServicesResponseBody runs the validations defined on +// List-Groupsio-ServicesResponseBody +func ValidateListGroupsioServicesResponseBody(body *ListGroupsioServicesResponseBody) (err error) { + for _, e := range body.Items { if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { + if err2 := ValidateGroupsioServiceResponseBody(e); err2 != nil { err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - return -} - -// ValidateUpdateGrpsioMailingListMemberResponseBody runs the validations -// defined on Update-Grpsio-Mailing-List-MemberResponseBody -func ValidateUpdateGrpsioMailingListMemberResponseBody(body *UpdateGrpsioMailingListMemberResponseBody) (err error) { - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - if body.MailingListUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.mailing_list_uid", *body.MailingListUID, goa.FormatUUID)) - } - if body.Username != nil { - if utf8.RuneCountInString(*body.Username) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.username", *body.Username, utf8.RuneCountInString(*body.Username), 255, false)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 1, true)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 255, false)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 1, true)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 255, false)) - } - } - if body.Email != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) - } - if body.Organization != nil { - if utf8.RuneCountInString(*body.Organization) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.organization", *body.Organization, utf8.RuneCountInString(*body.Organization), 255, false)) - } - } - if body.JobTitle != nil { - if utf8.RuneCountInString(*body.JobTitle) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.job_title", *body.JobTitle, utf8.RuneCountInString(*body.JobTitle), 255, false)) - } - } - if body.MemberType != nil { - if !(*body.MemberType == "committee" || *body.MemberType == "direct") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.member_type", *body.MemberType, []any{"committee", "direct"})) - } - } - if body.DeliveryMode != nil { - if !(*body.DeliveryMode == "normal" || *body.DeliveryMode == "digest" || *body.DeliveryMode == "none") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.delivery_mode", *body.DeliveryMode, []any{"normal", "digest", "none"})) - } - } - if body.ModStatus != nil { - if !(*body.ModStatus == "none" || *body.ModStatus == "moderator" || *body.ModStatus == "owner") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.mod_status", *body.ModStatus, []any{"none", "moderator", "owner"})) + } } } - if body.LastReviewedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_reviewed_at", *body.LastReviewedAt, goa.FormatDateTime)) + return +} + +// ValidateCreateGroupsioServiceResponseBody runs the validations defined on +// Create-Groupsio-ServiceResponseBody +func ValidateCreateGroupsioServiceResponseBody(body *CreateGroupsioServiceResponseBody) (err error) { + if body.ProjectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) + return +} + +// ValidateGetGroupsioServiceResponseBody runs the validations defined on +// Get-Groupsio-ServiceResponseBody +func ValidateGetGroupsioServiceResponseBody(body *GetGroupsioServiceResponseBody) (err error) { + if body.ProjectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) + return +} + +// ValidateUpdateGroupsioServiceResponseBody runs the validations defined on +// Update-Groupsio-ServiceResponseBody +func ValidateUpdateGroupsioServiceResponseBody(body *UpdateGroupsioServiceResponseBody) (err error) { + if body.ProjectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } + return +} + +// ValidateFindParentGroupsioServiceResponseBody runs the validations defined +// on Find-Parent-Groupsio-ServiceResponseBody +func ValidateFindParentGroupsioServiceResponseBody(body *FindParentGroupsioServiceResponseBody) (err error) { + if body.ProjectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) } - for _, e := range body.Auditors { + return +} + +// ValidateListGroupsioSubgroupsResponseBody runs the validations defined on +// List-Groupsio-SubgroupsResponseBody +func ValidateListGroupsioSubgroupsResponseBody(body *ListGroupsioSubgroupsResponseBody) (err error) { + for _, e := range body.Items { if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { + if err2 := ValidateGroupsioSubgroupResponseBody(e); err2 != nil { err = goa.MergeErrors(err, err2) } } @@ -3899,1109 +2262,818 @@ func ValidateUpdateGrpsioMailingListMemberResponseBody(body *UpdateGrpsioMailing return } -// ValidateReadyzServiceUnavailableResponseBody runs the validations defined on -// readyz_ServiceUnavailable_response_body -func ValidateReadyzServiceUnavailableResponseBody(body *ReadyzServiceUnavailableResponseBody) (err error) { - if body.Message == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) +// ValidateCreateGroupsioSubgroupResponseBody runs the validations defined on +// Create-Groupsio-SubgroupResponseBody +func ValidateCreateGroupsioSubgroupResponseBody(body *CreateGroupsioSubgroupResponseBody) (err error) { + if body.ProjectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) + } + if body.CommitteeUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.committee_uid", *body.CommitteeUID, goa.FormatUUID)) } return } -// ValidateCreateGrpsioServiceBadRequestResponseBody runs the validations -// defined on create-grpsio-service_BadRequest_response_body -func ValidateCreateGrpsioServiceBadRequestResponseBody(body *CreateGrpsioServiceBadRequestResponseBody) (err error) { - if body.Message == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) +// ValidateGetGroupsioSubgroupResponseBody runs the validations defined on +// Get-Groupsio-SubgroupResponseBody +func ValidateGetGroupsioSubgroupResponseBody(body *GetGroupsioSubgroupResponseBody) (err error) { + if body.ProjectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) + } + if body.CommitteeUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.committee_uid", *body.CommitteeUID, goa.FormatUUID)) } return } -// ValidateCreateGrpsioServiceConflictResponseBody runs the validations defined -// on create-grpsio-service_Conflict_response_body -func ValidateCreateGrpsioServiceConflictResponseBody(body *CreateGrpsioServiceConflictResponseBody) (err error) { - if body.Message == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) +// ValidateUpdateGroupsioSubgroupResponseBody runs the validations defined on +// Update-Groupsio-SubgroupResponseBody +func ValidateUpdateGroupsioSubgroupResponseBody(body *UpdateGroupsioSubgroupResponseBody) (err error) { + if body.ProjectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) + } + if body.CommitteeUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.committee_uid", *body.CommitteeUID, goa.FormatUUID)) } return } -// ValidateCreateGrpsioServiceInternalServerErrorResponseBody runs the -// validations defined on -// create-grpsio-service_InternalServerError_response_body -func ValidateCreateGrpsioServiceInternalServerErrorResponseBody(body *CreateGrpsioServiceInternalServerErrorResponseBody) (err error) { - if body.Message == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) +// ValidateGetGroupsioSubgroupCountResponseBody runs the validations defined on +// Get-Groupsio-Subgroup-CountResponseBody +func ValidateGetGroupsioSubgroupCountResponseBody(body *GetGroupsioSubgroupCountResponseBody) (err error) { + if body.Count == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("count", "body")) } return } -// ValidateCreateGrpsioServiceNotFoundResponseBody runs the validations defined -// on create-grpsio-service_NotFound_response_body -func ValidateCreateGrpsioServiceNotFoundResponseBody(body *CreateGrpsioServiceNotFoundResponseBody) (err error) { - if body.Message == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) +// ValidateGetGroupsioSubgroupMemberCountResponseBody runs the validations +// defined on Get-Groupsio-Subgroup-Member-CountResponseBody +func ValidateGetGroupsioSubgroupMemberCountResponseBody(body *GetGroupsioSubgroupMemberCountResponseBody) (err error) { + if body.Count == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("count", "body")) } return } -// ValidateCreateGrpsioServiceServiceUnavailableResponseBody runs the -// validations defined on create-grpsio-service_ServiceUnavailable_response_body -func ValidateCreateGrpsioServiceServiceUnavailableResponseBody(body *CreateGrpsioServiceServiceUnavailableResponseBody) (err error) { - if body.Message == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) +// ValidateListGroupsioMembersResponseBody runs the validations defined on +// List-Groupsio-MembersResponseBody +func ValidateListGroupsioMembersResponseBody(body *ListGroupsioMembersResponseBody) (err error) { + for _, e := range body.Items { + if e != nil { + if err2 := ValidateGroupsioMemberResponseBody(e); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } } return } -// ValidateGetGrpsioServiceBadRequestResponseBody runs the validations defined -// on get-grpsio-service_BadRequest_response_body -func ValidateGetGrpsioServiceBadRequestResponseBody(body *GetGrpsioServiceBadRequestResponseBody) (err error) { - if body.Message == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) +// ValidateAddGroupsioMemberResponseBody runs the validations defined on +// Add-Groupsio-MemberResponseBody +func ValidateAddGroupsioMemberResponseBody(body *AddGroupsioMemberResponseBody) (err error) { + if body.Email != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) } return } -// ValidateGetGrpsioServiceInternalServerErrorResponseBody runs the validations -// defined on get-grpsio-service_InternalServerError_response_body -func ValidateGetGrpsioServiceInternalServerErrorResponseBody(body *GetGrpsioServiceInternalServerErrorResponseBody) (err error) { - if body.Message == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) +// ValidateGetGroupsioMemberResponseBody runs the validations defined on +// Get-Groupsio-MemberResponseBody +func ValidateGetGroupsioMemberResponseBody(body *GetGroupsioMemberResponseBody) (err error) { + if body.Email != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) } return } -// ValidateGetGrpsioServiceNotFoundResponseBody runs the validations defined on -// get-grpsio-service_NotFound_response_body -func ValidateGetGrpsioServiceNotFoundResponseBody(body *GetGrpsioServiceNotFoundResponseBody) (err error) { - if body.Message == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) +// ValidateUpdateGroupsioMemberResponseBody runs the validations defined on +// Update-Groupsio-MemberResponseBody +func ValidateUpdateGroupsioMemberResponseBody(body *UpdateGroupsioMemberResponseBody) (err error) { + if body.Email != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) } return } -// ValidateGetGrpsioServiceServiceUnavailableResponseBody runs the validations -// defined on get-grpsio-service_ServiceUnavailable_response_body -func ValidateGetGrpsioServiceServiceUnavailableResponseBody(body *GetGrpsioServiceServiceUnavailableResponseBody) (err error) { - if body.Message == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) +// ValidateCheckGroupsioSubscriberResponseBody runs the validations defined on +// Check-Groupsio-SubscriberResponseBody +func ValidateCheckGroupsioSubscriberResponseBody(body *CheckGroupsioSubscriberResponseBody) (err error) { + if body.Subscribed == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("subscribed", "body")) } return } -// ValidateUpdateGrpsioServiceBadRequestResponseBody runs the validations -// defined on update-grpsio-service_BadRequest_response_body -func ValidateUpdateGrpsioServiceBadRequestResponseBody(body *UpdateGrpsioServiceBadRequestResponseBody) (err error) { +// ValidateReadyzServiceUnavailableResponseBody runs the validations defined on +// readyz_ServiceUnavailable_response_body +func ValidateReadyzServiceUnavailableResponseBody(body *ReadyzServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioServiceConflictResponseBody runs the validations defined -// on update-grpsio-service_Conflict_response_body -func ValidateUpdateGrpsioServiceConflictResponseBody(body *UpdateGrpsioServiceConflictResponseBody) (err error) { +// ValidateListGroupsioServicesBadRequestResponseBody runs the validations +// defined on list-groupsio-services_BadRequest_response_body +func ValidateListGroupsioServicesBadRequestResponseBody(body *ListGroupsioServicesBadRequestResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioServiceInternalServerErrorResponseBody runs the +// ValidateListGroupsioServicesInternalServerErrorResponseBody runs the // validations defined on -// update-grpsio-service_InternalServerError_response_body -func ValidateUpdateGrpsioServiceInternalServerErrorResponseBody(body *UpdateGrpsioServiceInternalServerErrorResponseBody) (err error) { +// list-groupsio-services_InternalServerError_response_body +func ValidateListGroupsioServicesInternalServerErrorResponseBody(body *ListGroupsioServicesInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioServiceNotFoundResponseBody runs the validations defined -// on update-grpsio-service_NotFound_response_body -func ValidateUpdateGrpsioServiceNotFoundResponseBody(body *UpdateGrpsioServiceNotFoundResponseBody) (err error) { +// ValidateListGroupsioServicesServiceUnavailableResponseBody runs the +// validations defined on +// list-groupsio-services_ServiceUnavailable_response_body +func ValidateListGroupsioServicesServiceUnavailableResponseBody(body *ListGroupsioServicesServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioServiceServiceUnavailableResponseBody runs the -// validations defined on update-grpsio-service_ServiceUnavailable_response_body -func ValidateUpdateGrpsioServiceServiceUnavailableResponseBody(body *UpdateGrpsioServiceServiceUnavailableResponseBody) (err error) { +// ValidateCreateGroupsioServiceBadRequestResponseBody runs the validations +// defined on create-groupsio-service_BadRequest_response_body +func ValidateCreateGroupsioServiceBadRequestResponseBody(body *CreateGroupsioServiceBadRequestResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioServiceBadRequestResponseBody runs the validations -// defined on delete-grpsio-service_BadRequest_response_body -func ValidateDeleteGrpsioServiceBadRequestResponseBody(body *DeleteGrpsioServiceBadRequestResponseBody) (err error) { +// ValidateCreateGroupsioServiceConflictResponseBody runs the validations +// defined on create-groupsio-service_Conflict_response_body +func ValidateCreateGroupsioServiceConflictResponseBody(body *CreateGroupsioServiceConflictResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioServiceConflictResponseBody runs the validations defined -// on delete-grpsio-service_Conflict_response_body -func ValidateDeleteGrpsioServiceConflictResponseBody(body *DeleteGrpsioServiceConflictResponseBody) (err error) { +// ValidateCreateGroupsioServiceInternalServerErrorResponseBody runs the +// validations defined on +// create-groupsio-service_InternalServerError_response_body +func ValidateCreateGroupsioServiceInternalServerErrorResponseBody(body *CreateGroupsioServiceInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioServiceInternalServerErrorResponseBody runs the +// ValidateCreateGroupsioServiceServiceUnavailableResponseBody runs the // validations defined on -// delete-grpsio-service_InternalServerError_response_body -func ValidateDeleteGrpsioServiceInternalServerErrorResponseBody(body *DeleteGrpsioServiceInternalServerErrorResponseBody) (err error) { +// create-groupsio-service_ServiceUnavailable_response_body +func ValidateCreateGroupsioServiceServiceUnavailableResponseBody(body *CreateGroupsioServiceServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioServiceNotFoundResponseBody runs the validations defined -// on delete-grpsio-service_NotFound_response_body -func ValidateDeleteGrpsioServiceNotFoundResponseBody(body *DeleteGrpsioServiceNotFoundResponseBody) (err error) { +// ValidateGetGroupsioServiceInternalServerErrorResponseBody runs the +// validations defined on get-groupsio-service_InternalServerError_response_body +func ValidateGetGroupsioServiceInternalServerErrorResponseBody(body *GetGroupsioServiceInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioServiceServiceUnavailableResponseBody runs the -// validations defined on delete-grpsio-service_ServiceUnavailable_response_body -func ValidateDeleteGrpsioServiceServiceUnavailableResponseBody(body *DeleteGrpsioServiceServiceUnavailableResponseBody) (err error) { +// ValidateGetGroupsioServiceNotFoundResponseBody runs the validations defined +// on get-groupsio-service_NotFound_response_body +func ValidateGetGroupsioServiceNotFoundResponseBody(body *GetGroupsioServiceNotFoundResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioServiceSettingsBadRequestResponseBody runs the validations -// defined on get-grpsio-service-settings_BadRequest_response_body -func ValidateGetGrpsioServiceSettingsBadRequestResponseBody(body *GetGrpsioServiceSettingsBadRequestResponseBody) (err error) { +// ValidateGetGroupsioServiceServiceUnavailableResponseBody runs the +// validations defined on get-groupsio-service_ServiceUnavailable_response_body +func ValidateGetGroupsioServiceServiceUnavailableResponseBody(body *GetGroupsioServiceServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioServiceSettingsInternalServerErrorResponseBody runs the -// validations defined on -// get-grpsio-service-settings_InternalServerError_response_body -func ValidateGetGrpsioServiceSettingsInternalServerErrorResponseBody(body *GetGrpsioServiceSettingsInternalServerErrorResponseBody) (err error) { +// ValidateUpdateGroupsioServiceBadRequestResponseBody runs the validations +// defined on update-groupsio-service_BadRequest_response_body +func ValidateUpdateGroupsioServiceBadRequestResponseBody(body *UpdateGroupsioServiceBadRequestResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioServiceSettingsNotFoundResponseBody runs the validations -// defined on get-grpsio-service-settings_NotFound_response_body -func ValidateGetGrpsioServiceSettingsNotFoundResponseBody(body *GetGrpsioServiceSettingsNotFoundResponseBody) (err error) { +// ValidateUpdateGroupsioServiceInternalServerErrorResponseBody runs the +// validations defined on +// update-groupsio-service_InternalServerError_response_body +func ValidateUpdateGroupsioServiceInternalServerErrorResponseBody(body *UpdateGroupsioServiceInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioServiceSettingsServiceUnavailableResponseBody runs the -// validations defined on -// get-grpsio-service-settings_ServiceUnavailable_response_body -func ValidateGetGrpsioServiceSettingsServiceUnavailableResponseBody(body *GetGrpsioServiceSettingsServiceUnavailableResponseBody) (err error) { +// ValidateUpdateGroupsioServiceNotFoundResponseBody runs the validations +// defined on update-groupsio-service_NotFound_response_body +func ValidateUpdateGroupsioServiceNotFoundResponseBody(body *UpdateGroupsioServiceNotFoundResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioServiceSettingsBadRequestResponseBody runs the +// ValidateUpdateGroupsioServiceServiceUnavailableResponseBody runs the // validations defined on -// update-grpsio-service-settings_BadRequest_response_body -func ValidateUpdateGrpsioServiceSettingsBadRequestResponseBody(body *UpdateGrpsioServiceSettingsBadRequestResponseBody) (err error) { +// update-groupsio-service_ServiceUnavailable_response_body +func ValidateUpdateGroupsioServiceServiceUnavailableResponseBody(body *UpdateGroupsioServiceServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioServiceSettingsConflictResponseBody runs the validations -// defined on update-grpsio-service-settings_Conflict_response_body -func ValidateUpdateGrpsioServiceSettingsConflictResponseBody(body *UpdateGrpsioServiceSettingsConflictResponseBody) (err error) { +// ValidateDeleteGroupsioServiceInternalServerErrorResponseBody runs the +// validations defined on +// delete-groupsio-service_InternalServerError_response_body +func ValidateDeleteGroupsioServiceInternalServerErrorResponseBody(body *DeleteGroupsioServiceInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioServiceSettingsInternalServerErrorResponseBody runs the -// validations defined on -// update-grpsio-service-settings_InternalServerError_response_body -func ValidateUpdateGrpsioServiceSettingsInternalServerErrorResponseBody(body *UpdateGrpsioServiceSettingsInternalServerErrorResponseBody) (err error) { +// ValidateDeleteGroupsioServiceNotFoundResponseBody runs the validations +// defined on delete-groupsio-service_NotFound_response_body +func ValidateDeleteGroupsioServiceNotFoundResponseBody(body *DeleteGroupsioServiceNotFoundResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioServiceSettingsNotFoundResponseBody runs the validations -// defined on update-grpsio-service-settings_NotFound_response_body -func ValidateUpdateGrpsioServiceSettingsNotFoundResponseBody(body *UpdateGrpsioServiceSettingsNotFoundResponseBody) (err error) { +// ValidateDeleteGroupsioServiceServiceUnavailableResponseBody runs the +// validations defined on +// delete-groupsio-service_ServiceUnavailable_response_body +func ValidateDeleteGroupsioServiceServiceUnavailableResponseBody(body *DeleteGroupsioServiceServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioServiceSettingsServiceUnavailableResponseBody runs the +// ValidateGetGroupsioServiceProjectsInternalServerErrorResponseBody runs the // validations defined on -// update-grpsio-service-settings_ServiceUnavailable_response_body -func ValidateUpdateGrpsioServiceSettingsServiceUnavailableResponseBody(body *UpdateGrpsioServiceSettingsServiceUnavailableResponseBody) (err error) { +// get-groupsio-service-projects_InternalServerError_response_body +func ValidateGetGroupsioServiceProjectsInternalServerErrorResponseBody(body *GetGroupsioServiceProjectsInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateCreateGrpsioMailingListBadRequestResponseBody runs the validations -// defined on create-grpsio-mailing-list_BadRequest_response_body -func ValidateCreateGrpsioMailingListBadRequestResponseBody(body *CreateGrpsioMailingListBadRequestResponseBody) (err error) { +// ValidateGetGroupsioServiceProjectsServiceUnavailableResponseBody runs the +// validations defined on +// get-groupsio-service-projects_ServiceUnavailable_response_body +func ValidateGetGroupsioServiceProjectsServiceUnavailableResponseBody(body *GetGroupsioServiceProjectsServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateCreateGrpsioMailingListConflictResponseBody runs the validations -// defined on create-grpsio-mailing-list_Conflict_response_body -func ValidateCreateGrpsioMailingListConflictResponseBody(body *CreateGrpsioMailingListConflictResponseBody) (err error) { +// ValidateFindParentGroupsioServiceBadRequestResponseBody runs the validations +// defined on find-parent-groupsio-service_BadRequest_response_body +func ValidateFindParentGroupsioServiceBadRequestResponseBody(body *FindParentGroupsioServiceBadRequestResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateCreateGrpsioMailingListInternalServerErrorResponseBody runs the +// ValidateFindParentGroupsioServiceInternalServerErrorResponseBody runs the // validations defined on -// create-grpsio-mailing-list_InternalServerError_response_body -func ValidateCreateGrpsioMailingListInternalServerErrorResponseBody(body *CreateGrpsioMailingListInternalServerErrorResponseBody) (err error) { +// find-parent-groupsio-service_InternalServerError_response_body +func ValidateFindParentGroupsioServiceInternalServerErrorResponseBody(body *FindParentGroupsioServiceInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateCreateGrpsioMailingListNotFoundResponseBody runs the validations -// defined on create-grpsio-mailing-list_NotFound_response_body -func ValidateCreateGrpsioMailingListNotFoundResponseBody(body *CreateGrpsioMailingListNotFoundResponseBody) (err error) { +// ValidateFindParentGroupsioServiceNotFoundResponseBody runs the validations +// defined on find-parent-groupsio-service_NotFound_response_body +func ValidateFindParentGroupsioServiceNotFoundResponseBody(body *FindParentGroupsioServiceNotFoundResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateCreateGrpsioMailingListServiceUnavailableResponseBody runs the +// ValidateFindParentGroupsioServiceServiceUnavailableResponseBody runs the // validations defined on -// create-grpsio-mailing-list_ServiceUnavailable_response_body -func ValidateCreateGrpsioMailingListServiceUnavailableResponseBody(body *CreateGrpsioMailingListServiceUnavailableResponseBody) (err error) { +// find-parent-groupsio-service_ServiceUnavailable_response_body +func ValidateFindParentGroupsioServiceServiceUnavailableResponseBody(body *FindParentGroupsioServiceServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioMailingListBadRequestResponseBody runs the validations -// defined on get-grpsio-mailing-list_BadRequest_response_body -func ValidateGetGrpsioMailingListBadRequestResponseBody(body *GetGrpsioMailingListBadRequestResponseBody) (err error) { +// ValidateListGroupsioSubgroupsBadRequestResponseBody runs the validations +// defined on list-groupsio-subgroups_BadRequest_response_body +func ValidateListGroupsioSubgroupsBadRequestResponseBody(body *ListGroupsioSubgroupsBadRequestResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioMailingListInternalServerErrorResponseBody runs the +// ValidateListGroupsioSubgroupsInternalServerErrorResponseBody runs the // validations defined on -// get-grpsio-mailing-list_InternalServerError_response_body -func ValidateGetGrpsioMailingListInternalServerErrorResponseBody(body *GetGrpsioMailingListInternalServerErrorResponseBody) (err error) { +// list-groupsio-subgroups_InternalServerError_response_body +func ValidateListGroupsioSubgroupsInternalServerErrorResponseBody(body *ListGroupsioSubgroupsInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioMailingListNotFoundResponseBody runs the validations -// defined on get-grpsio-mailing-list_NotFound_response_body -func ValidateGetGrpsioMailingListNotFoundResponseBody(body *GetGrpsioMailingListNotFoundResponseBody) (err error) { +// ValidateListGroupsioSubgroupsServiceUnavailableResponseBody runs the +// validations defined on +// list-groupsio-subgroups_ServiceUnavailable_response_body +func ValidateListGroupsioSubgroupsServiceUnavailableResponseBody(body *ListGroupsioSubgroupsServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioMailingListServiceUnavailableResponseBody runs the -// validations defined on -// get-grpsio-mailing-list_ServiceUnavailable_response_body -func ValidateGetGrpsioMailingListServiceUnavailableResponseBody(body *GetGrpsioMailingListServiceUnavailableResponseBody) (err error) { +// ValidateCreateGroupsioSubgroupBadRequestResponseBody runs the validations +// defined on create-groupsio-subgroup_BadRequest_response_body +func ValidateCreateGroupsioSubgroupBadRequestResponseBody(body *CreateGroupsioSubgroupBadRequestResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListBadRequestResponseBody runs the validations -// defined on update-grpsio-mailing-list_BadRequest_response_body -func ValidateUpdateGrpsioMailingListBadRequestResponseBody(body *UpdateGrpsioMailingListBadRequestResponseBody) (err error) { +// ValidateCreateGroupsioSubgroupConflictResponseBody runs the validations +// defined on create-groupsio-subgroup_Conflict_response_body +func ValidateCreateGroupsioSubgroupConflictResponseBody(body *CreateGroupsioSubgroupConflictResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListConflictResponseBody runs the validations -// defined on update-grpsio-mailing-list_Conflict_response_body -func ValidateUpdateGrpsioMailingListConflictResponseBody(body *UpdateGrpsioMailingListConflictResponseBody) (err error) { +// ValidateCreateGroupsioSubgroupInternalServerErrorResponseBody runs the +// validations defined on +// create-groupsio-subgroup_InternalServerError_response_body +func ValidateCreateGroupsioSubgroupInternalServerErrorResponseBody(body *CreateGroupsioSubgroupInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListInternalServerErrorResponseBody runs the +// ValidateCreateGroupsioSubgroupServiceUnavailableResponseBody runs the // validations defined on -// update-grpsio-mailing-list_InternalServerError_response_body -func ValidateUpdateGrpsioMailingListInternalServerErrorResponseBody(body *UpdateGrpsioMailingListInternalServerErrorResponseBody) (err error) { +// create-groupsio-subgroup_ServiceUnavailable_response_body +func ValidateCreateGroupsioSubgroupServiceUnavailableResponseBody(body *CreateGroupsioSubgroupServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListNotFoundResponseBody runs the validations -// defined on update-grpsio-mailing-list_NotFound_response_body -func ValidateUpdateGrpsioMailingListNotFoundResponseBody(body *UpdateGrpsioMailingListNotFoundResponseBody) (err error) { +// ValidateGetGroupsioSubgroupInternalServerErrorResponseBody runs the +// validations defined on +// get-groupsio-subgroup_InternalServerError_response_body +func ValidateGetGroupsioSubgroupInternalServerErrorResponseBody(body *GetGroupsioSubgroupInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListServiceUnavailableResponseBody runs the -// validations defined on -// update-grpsio-mailing-list_ServiceUnavailable_response_body -func ValidateUpdateGrpsioMailingListServiceUnavailableResponseBody(body *UpdateGrpsioMailingListServiceUnavailableResponseBody) (err error) { +// ValidateGetGroupsioSubgroupNotFoundResponseBody runs the validations defined +// on get-groupsio-subgroup_NotFound_response_body +func ValidateGetGroupsioSubgroupNotFoundResponseBody(body *GetGroupsioSubgroupNotFoundResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioMailingListBadRequestResponseBody runs the validations -// defined on delete-grpsio-mailing-list_BadRequest_response_body -func ValidateDeleteGrpsioMailingListBadRequestResponseBody(body *DeleteGrpsioMailingListBadRequestResponseBody) (err error) { +// ValidateGetGroupsioSubgroupServiceUnavailableResponseBody runs the +// validations defined on get-groupsio-subgroup_ServiceUnavailable_response_body +func ValidateGetGroupsioSubgroupServiceUnavailableResponseBody(body *GetGroupsioSubgroupServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioMailingListConflictResponseBody runs the validations -// defined on delete-grpsio-mailing-list_Conflict_response_body -func ValidateDeleteGrpsioMailingListConflictResponseBody(body *DeleteGrpsioMailingListConflictResponseBody) (err error) { +// ValidateUpdateGroupsioSubgroupBadRequestResponseBody runs the validations +// defined on update-groupsio-subgroup_BadRequest_response_body +func ValidateUpdateGroupsioSubgroupBadRequestResponseBody(body *UpdateGroupsioSubgroupBadRequestResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioMailingListInternalServerErrorResponseBody runs the +// ValidateUpdateGroupsioSubgroupInternalServerErrorResponseBody runs the // validations defined on -// delete-grpsio-mailing-list_InternalServerError_response_body -func ValidateDeleteGrpsioMailingListInternalServerErrorResponseBody(body *DeleteGrpsioMailingListInternalServerErrorResponseBody) (err error) { +// update-groupsio-subgroup_InternalServerError_response_body +func ValidateUpdateGroupsioSubgroupInternalServerErrorResponseBody(body *UpdateGroupsioSubgroupInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioMailingListNotFoundResponseBody runs the validations -// defined on delete-grpsio-mailing-list_NotFound_response_body -func ValidateDeleteGrpsioMailingListNotFoundResponseBody(body *DeleteGrpsioMailingListNotFoundResponseBody) (err error) { +// ValidateUpdateGroupsioSubgroupNotFoundResponseBody runs the validations +// defined on update-groupsio-subgroup_NotFound_response_body +func ValidateUpdateGroupsioSubgroupNotFoundResponseBody(body *UpdateGroupsioSubgroupNotFoundResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioMailingListServiceUnavailableResponseBody runs the +// ValidateUpdateGroupsioSubgroupServiceUnavailableResponseBody runs the // validations defined on -// delete-grpsio-mailing-list_ServiceUnavailable_response_body -func ValidateDeleteGrpsioMailingListServiceUnavailableResponseBody(body *DeleteGrpsioMailingListServiceUnavailableResponseBody) (err error) { +// update-groupsio-subgroup_ServiceUnavailable_response_body +func ValidateUpdateGroupsioSubgroupServiceUnavailableResponseBody(body *UpdateGroupsioSubgroupServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioMailingListSettingsBadRequestResponseBody runs the +// ValidateDeleteGroupsioSubgroupInternalServerErrorResponseBody runs the // validations defined on -// get-grpsio-mailing-list-settings_BadRequest_response_body -func ValidateGetGrpsioMailingListSettingsBadRequestResponseBody(body *GetGrpsioMailingListSettingsBadRequestResponseBody) (err error) { +// delete-groupsio-subgroup_InternalServerError_response_body +func ValidateDeleteGroupsioSubgroupInternalServerErrorResponseBody(body *DeleteGroupsioSubgroupInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioMailingListSettingsInternalServerErrorResponseBody runs the -// validations defined on -// get-grpsio-mailing-list-settings_InternalServerError_response_body -func ValidateGetGrpsioMailingListSettingsInternalServerErrorResponseBody(body *GetGrpsioMailingListSettingsInternalServerErrorResponseBody) (err error) { +// ValidateDeleteGroupsioSubgroupNotFoundResponseBody runs the validations +// defined on delete-groupsio-subgroup_NotFound_response_body +func ValidateDeleteGroupsioSubgroupNotFoundResponseBody(body *DeleteGroupsioSubgroupNotFoundResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioMailingListSettingsNotFoundResponseBody runs the +// ValidateDeleteGroupsioSubgroupServiceUnavailableResponseBody runs the // validations defined on -// get-grpsio-mailing-list-settings_NotFound_response_body -func ValidateGetGrpsioMailingListSettingsNotFoundResponseBody(body *GetGrpsioMailingListSettingsNotFoundResponseBody) (err error) { +// delete-groupsio-subgroup_ServiceUnavailable_response_body +func ValidateDeleteGroupsioSubgroupServiceUnavailableResponseBody(body *DeleteGroupsioSubgroupServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioMailingListSettingsServiceUnavailableResponseBody runs the -// validations defined on -// get-grpsio-mailing-list-settings_ServiceUnavailable_response_body -func ValidateGetGrpsioMailingListSettingsServiceUnavailableResponseBody(body *GetGrpsioMailingListSettingsServiceUnavailableResponseBody) (err error) { +// ValidateGetGroupsioSubgroupCountBadRequestResponseBody runs the validations +// defined on get-groupsio-subgroup-count_BadRequest_response_body +func ValidateGetGroupsioSubgroupCountBadRequestResponseBody(body *GetGroupsioSubgroupCountBadRequestResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListSettingsBadRequestResponseBody runs the +// ValidateGetGroupsioSubgroupCountInternalServerErrorResponseBody runs the // validations defined on -// update-grpsio-mailing-list-settings_BadRequest_response_body -func ValidateUpdateGrpsioMailingListSettingsBadRequestResponseBody(body *UpdateGrpsioMailingListSettingsBadRequestResponseBody) (err error) { +// get-groupsio-subgroup-count_InternalServerError_response_body +func ValidateGetGroupsioSubgroupCountInternalServerErrorResponseBody(body *GetGroupsioSubgroupCountInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListSettingsConflictResponseBody runs the +// ValidateGetGroupsioSubgroupCountServiceUnavailableResponseBody runs the // validations defined on -// update-grpsio-mailing-list-settings_Conflict_response_body -func ValidateUpdateGrpsioMailingListSettingsConflictResponseBody(body *UpdateGrpsioMailingListSettingsConflictResponseBody) (err error) { +// get-groupsio-subgroup-count_ServiceUnavailable_response_body +func ValidateGetGroupsioSubgroupCountServiceUnavailableResponseBody(body *GetGroupsioSubgroupCountServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListSettingsInternalServerErrorResponseBody runs +// ValidateGetGroupsioSubgroupMemberCountInternalServerErrorResponseBody runs // the validations defined on -// update-grpsio-mailing-list-settings_InternalServerError_response_body -func ValidateUpdateGrpsioMailingListSettingsInternalServerErrorResponseBody(body *UpdateGrpsioMailingListSettingsInternalServerErrorResponseBody) (err error) { +// get-groupsio-subgroup-member-count_InternalServerError_response_body +func ValidateGetGroupsioSubgroupMemberCountInternalServerErrorResponseBody(body *GetGroupsioSubgroupMemberCountInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListSettingsNotFoundResponseBody runs the +// ValidateGetGroupsioSubgroupMemberCountNotFoundResponseBody runs the // validations defined on -// update-grpsio-mailing-list-settings_NotFound_response_body -func ValidateUpdateGrpsioMailingListSettingsNotFoundResponseBody(body *UpdateGrpsioMailingListSettingsNotFoundResponseBody) (err error) { +// get-groupsio-subgroup-member-count_NotFound_response_body +func ValidateGetGroupsioSubgroupMemberCountNotFoundResponseBody(body *GetGroupsioSubgroupMemberCountNotFoundResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListSettingsServiceUnavailableResponseBody runs +// ValidateGetGroupsioSubgroupMemberCountServiceUnavailableResponseBody runs // the validations defined on -// update-grpsio-mailing-list-settings_ServiceUnavailable_response_body -func ValidateUpdateGrpsioMailingListSettingsServiceUnavailableResponseBody(body *UpdateGrpsioMailingListSettingsServiceUnavailableResponseBody) (err error) { +// get-groupsio-subgroup-member-count_ServiceUnavailable_response_body +func ValidateGetGroupsioSubgroupMemberCountServiceUnavailableResponseBody(body *GetGroupsioSubgroupMemberCountServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateCreateGrpsioMailingListMemberBadRequestResponseBody runs the +// ValidateListGroupsioMembersInternalServerErrorResponseBody runs the // validations defined on -// create-grpsio-mailing-list-member_BadRequest_response_body -func ValidateCreateGrpsioMailingListMemberBadRequestResponseBody(body *CreateGrpsioMailingListMemberBadRequestResponseBody) (err error) { +// list-groupsio-members_InternalServerError_response_body +func ValidateListGroupsioMembersInternalServerErrorResponseBody(body *ListGroupsioMembersInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateCreateGrpsioMailingListMemberConflictResponseBody runs the -// validations defined on -// create-grpsio-mailing-list-member_Conflict_response_body -func ValidateCreateGrpsioMailingListMemberConflictResponseBody(body *CreateGrpsioMailingListMemberConflictResponseBody) (err error) { +// ValidateListGroupsioMembersNotFoundResponseBody runs the validations defined +// on list-groupsio-members_NotFound_response_body +func ValidateListGroupsioMembersNotFoundResponseBody(body *ListGroupsioMembersNotFoundResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateCreateGrpsioMailingListMemberInternalServerErrorResponseBody runs -// the validations defined on -// create-grpsio-mailing-list-member_InternalServerError_response_body -func ValidateCreateGrpsioMailingListMemberInternalServerErrorResponseBody(body *CreateGrpsioMailingListMemberInternalServerErrorResponseBody) (err error) { +// ValidateListGroupsioMembersServiceUnavailableResponseBody runs the +// validations defined on list-groupsio-members_ServiceUnavailable_response_body +func ValidateListGroupsioMembersServiceUnavailableResponseBody(body *ListGroupsioMembersServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateCreateGrpsioMailingListMemberNotFoundResponseBody runs the -// validations defined on -// create-grpsio-mailing-list-member_NotFound_response_body -func ValidateCreateGrpsioMailingListMemberNotFoundResponseBody(body *CreateGrpsioMailingListMemberNotFoundResponseBody) (err error) { +// ValidateAddGroupsioMemberBadRequestResponseBody runs the validations defined +// on add-groupsio-member_BadRequest_response_body +func ValidateAddGroupsioMemberBadRequestResponseBody(body *AddGroupsioMemberBadRequestResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateCreateGrpsioMailingListMemberServiceUnavailableResponseBody runs the -// validations defined on -// create-grpsio-mailing-list-member_ServiceUnavailable_response_body -func ValidateCreateGrpsioMailingListMemberServiceUnavailableResponseBody(body *CreateGrpsioMailingListMemberServiceUnavailableResponseBody) (err error) { +// ValidateAddGroupsioMemberConflictResponseBody runs the validations defined +// on add-groupsio-member_Conflict_response_body +func ValidateAddGroupsioMemberConflictResponseBody(body *AddGroupsioMemberConflictResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioMailingListMemberBadRequestResponseBody runs the -// validations defined on -// get-grpsio-mailing-list-member_BadRequest_response_body -func ValidateGetGrpsioMailingListMemberBadRequestResponseBody(body *GetGrpsioMailingListMemberBadRequestResponseBody) (err error) { +// ValidateAddGroupsioMemberInternalServerErrorResponseBody runs the +// validations defined on add-groupsio-member_InternalServerError_response_body +func ValidateAddGroupsioMemberInternalServerErrorResponseBody(body *AddGroupsioMemberInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioMailingListMemberInternalServerErrorResponseBody runs the -// validations defined on -// get-grpsio-mailing-list-member_InternalServerError_response_body -func ValidateGetGrpsioMailingListMemberInternalServerErrorResponseBody(body *GetGrpsioMailingListMemberInternalServerErrorResponseBody) (err error) { +// ValidateAddGroupsioMemberNotFoundResponseBody runs the validations defined +// on add-groupsio-member_NotFound_response_body +func ValidateAddGroupsioMemberNotFoundResponseBody(body *AddGroupsioMemberNotFoundResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioMailingListMemberNotFoundResponseBody runs the validations -// defined on get-grpsio-mailing-list-member_NotFound_response_body -func ValidateGetGrpsioMailingListMemberNotFoundResponseBody(body *GetGrpsioMailingListMemberNotFoundResponseBody) (err error) { +// ValidateAddGroupsioMemberServiceUnavailableResponseBody runs the validations +// defined on add-groupsio-member_ServiceUnavailable_response_body +func ValidateAddGroupsioMemberServiceUnavailableResponseBody(body *AddGroupsioMemberServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGetGrpsioMailingListMemberServiceUnavailableResponseBody runs the -// validations defined on -// get-grpsio-mailing-list-member_ServiceUnavailable_response_body -func ValidateGetGrpsioMailingListMemberServiceUnavailableResponseBody(body *GetGrpsioMailingListMemberServiceUnavailableResponseBody) (err error) { +// ValidateGetGroupsioMemberInternalServerErrorResponseBody runs the +// validations defined on get-groupsio-member_InternalServerError_response_body +func ValidateGetGroupsioMemberInternalServerErrorResponseBody(body *GetGroupsioMemberInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListMemberBadRequestResponseBody runs the -// validations defined on -// update-grpsio-mailing-list-member_BadRequest_response_body -func ValidateUpdateGrpsioMailingListMemberBadRequestResponseBody(body *UpdateGrpsioMailingListMemberBadRequestResponseBody) (err error) { +// ValidateGetGroupsioMemberNotFoundResponseBody runs the validations defined +// on get-groupsio-member_NotFound_response_body +func ValidateGetGroupsioMemberNotFoundResponseBody(body *GetGroupsioMemberNotFoundResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListMemberConflictResponseBody runs the -// validations defined on -// update-grpsio-mailing-list-member_Conflict_response_body -func ValidateUpdateGrpsioMailingListMemberConflictResponseBody(body *UpdateGrpsioMailingListMemberConflictResponseBody) (err error) { +// ValidateGetGroupsioMemberServiceUnavailableResponseBody runs the validations +// defined on get-groupsio-member_ServiceUnavailable_response_body +func ValidateGetGroupsioMemberServiceUnavailableResponseBody(body *GetGroupsioMemberServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListMemberInternalServerErrorResponseBody runs -// the validations defined on -// update-grpsio-mailing-list-member_InternalServerError_response_body -func ValidateUpdateGrpsioMailingListMemberInternalServerErrorResponseBody(body *UpdateGrpsioMailingListMemberInternalServerErrorResponseBody) (err error) { +// ValidateUpdateGroupsioMemberBadRequestResponseBody runs the validations +// defined on update-groupsio-member_BadRequest_response_body +func ValidateUpdateGroupsioMemberBadRequestResponseBody(body *UpdateGroupsioMemberBadRequestResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListMemberNotFoundResponseBody runs the +// ValidateUpdateGroupsioMemberInternalServerErrorResponseBody runs the // validations defined on -// update-grpsio-mailing-list-member_NotFound_response_body -func ValidateUpdateGrpsioMailingListMemberNotFoundResponseBody(body *UpdateGrpsioMailingListMemberNotFoundResponseBody) (err error) { +// update-groupsio-member_InternalServerError_response_body +func ValidateUpdateGroupsioMemberInternalServerErrorResponseBody(body *UpdateGroupsioMemberInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUpdateGrpsioMailingListMemberServiceUnavailableResponseBody runs the -// validations defined on -// update-grpsio-mailing-list-member_ServiceUnavailable_response_body -func ValidateUpdateGrpsioMailingListMemberServiceUnavailableResponseBody(body *UpdateGrpsioMailingListMemberServiceUnavailableResponseBody) (err error) { +// ValidateUpdateGroupsioMemberNotFoundResponseBody runs the validations +// defined on update-groupsio-member_NotFound_response_body +func ValidateUpdateGroupsioMemberNotFoundResponseBody(body *UpdateGroupsioMemberNotFoundResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioMailingListMemberBadRequestResponseBody runs the +// ValidateUpdateGroupsioMemberServiceUnavailableResponseBody runs the // validations defined on -// delete-grpsio-mailing-list-member_BadRequest_response_body -func ValidateDeleteGrpsioMailingListMemberBadRequestResponseBody(body *DeleteGrpsioMailingListMemberBadRequestResponseBody) (err error) { +// update-groupsio-member_ServiceUnavailable_response_body +func ValidateUpdateGroupsioMemberServiceUnavailableResponseBody(body *UpdateGroupsioMemberServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioMailingListMemberConflictResponseBody runs the +// ValidateDeleteGroupsioMemberInternalServerErrorResponseBody runs the // validations defined on -// delete-grpsio-mailing-list-member_Conflict_response_body -func ValidateDeleteGrpsioMailingListMemberConflictResponseBody(body *DeleteGrpsioMailingListMemberConflictResponseBody) (err error) { +// delete-groupsio-member_InternalServerError_response_body +func ValidateDeleteGroupsioMemberInternalServerErrorResponseBody(body *DeleteGroupsioMemberInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioMailingListMemberInternalServerErrorResponseBody runs -// the validations defined on -// delete-grpsio-mailing-list-member_InternalServerError_response_body -func ValidateDeleteGrpsioMailingListMemberInternalServerErrorResponseBody(body *DeleteGrpsioMailingListMemberInternalServerErrorResponseBody) (err error) { +// ValidateDeleteGroupsioMemberNotFoundResponseBody runs the validations +// defined on delete-groupsio-member_NotFound_response_body +func ValidateDeleteGroupsioMemberNotFoundResponseBody(body *DeleteGroupsioMemberNotFoundResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioMailingListMemberNotFoundResponseBody runs the +// ValidateDeleteGroupsioMemberServiceUnavailableResponseBody runs the // validations defined on -// delete-grpsio-mailing-list-member_NotFound_response_body -func ValidateDeleteGrpsioMailingListMemberNotFoundResponseBody(body *DeleteGrpsioMailingListMemberNotFoundResponseBody) (err error) { +// delete-groupsio-member_ServiceUnavailable_response_body +func ValidateDeleteGroupsioMemberServiceUnavailableResponseBody(body *DeleteGroupsioMemberServiceUnavailableResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateDeleteGrpsioMailingListMemberServiceUnavailableResponseBody runs the -// validations defined on -// delete-grpsio-mailing-list-member_ServiceUnavailable_response_body -func ValidateDeleteGrpsioMailingListMemberServiceUnavailableResponseBody(body *DeleteGrpsioMailingListMemberServiceUnavailableResponseBody) (err error) { +// ValidateInviteGroupsioMembersBadRequestResponseBody runs the validations +// defined on invite-groupsio-members_BadRequest_response_body +func ValidateInviteGroupsioMembersBadRequestResponseBody(body *InviteGroupsioMembersBadRequestResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGroupsioWebhookBadRequestResponseBody runs the validations defined -// on groupsio-webhook_BadRequest_response_body -func ValidateGroupsioWebhookBadRequestResponseBody(body *GroupsioWebhookBadRequestResponseBody) (err error) { +// ValidateInviteGroupsioMembersInternalServerErrorResponseBody runs the +// validations defined on +// invite-groupsio-members_InternalServerError_response_body +func ValidateInviteGroupsioMembersInternalServerErrorResponseBody(body *InviteGroupsioMembersInternalServerErrorResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGroupsioWebhookUnauthorizedResponseBody runs the validations defined -// on groupsio-webhook_Unauthorized_response_body -func ValidateGroupsioWebhookUnauthorizedResponseBody(body *GroupsioWebhookUnauthorizedResponseBody) (err error) { +// ValidateInviteGroupsioMembersNotFoundResponseBody runs the validations +// defined on invite-groupsio-members_NotFound_response_body +func ValidateInviteGroupsioMembersNotFoundResponseBody(body *InviteGroupsioMembersNotFoundResponseBody) (err error) { if body.Message == nil { err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateUserInfoRequestBody runs the validations defined on -// UserInfoRequestBody -func ValidateUserInfoRequestBody(body *UserInfoRequestBody) (err error) { - if body.Email != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) - } - if body.Avatar != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.avatar", *body.Avatar, goa.FormatURI)) - } - return -} - -// ValidateUserInfoResponseBody runs the validations defined on -// UserInfoResponseBody -func ValidateUserInfoResponseBody(body *UserInfoResponseBody) (err error) { - if body.Email != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) - } - if body.Avatar != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.avatar", *body.Avatar, goa.FormatURI)) - } - return -} - -// ValidateGrpsIoServiceWithReadonlyAttributesResponseBody runs the validations -// defined on grps-io-service-with-readonly-attributesResponseBody -func ValidateGrpsIoServiceWithReadonlyAttributesResponseBody(body *GrpsIoServiceWithReadonlyAttributesResponseBody) (err error) { - if body.Type == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("type", "body")) - } - if body.ProjectUID == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("project_uid", "body")) - } - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - if body.Type != nil { - if !(*body.Type == "primary" || *body.Type == "formation" || *body.Type == "shared") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", *body.Type, []any{"primary", "formation", "shared"})) - } - } - for _, e := range body.GlobalOwners { - err = goa.MergeErrors(err, goa.ValidateFormat("body.global_owners[*]", e, goa.FormatEmail)) - } - if body.ParentServiceUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.parent_service_uid", *body.ParentServiceUID, goa.FormatUUID)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_slug", *body.ProjectSlug, goa.FormatRegexp)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.project_slug", *body.ProjectSlug, "^[a-z][a-z0-9_\\-]*[a-z0-9]$")) - } - if body.ProjectUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) - } - if body.URL != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.url", *body.URL, goa.FormatURI)) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } +// ValidateInviteGroupsioMembersServiceUnavailableResponseBody runs the +// validations defined on +// invite-groupsio-members_ServiceUnavailable_response_body +func ValidateInviteGroupsioMembersServiceUnavailableResponseBody(body *InviteGroupsioMembersServiceUnavailableResponseBody) (err error) { + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGrpsIoServiceSettingsResponseBody runs the validations defined on -// grps-io-service-settingsResponseBody -func ValidateGrpsIoServiceSettingsResponseBody(body *GrpsIoServiceSettingsResponseBody) (err error) { - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if body.LastReviewedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_reviewed_at", *body.LastReviewedAt, goa.FormatDateTime)) - } - if body.LastAuditedTime != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_audited_time", *body.LastAuditedTime, goa.FormatDateTime)) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) +// ValidateCheckGroupsioSubscriberBadRequestResponseBody runs the validations +// defined on check-groupsio-subscriber_BadRequest_response_body +func ValidateCheckGroupsioSubscriberBadRequestResponseBody(body *CheckGroupsioSubscriberBadRequestResponseBody) (err error) { + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateCommitteeRequestBody runs the validations defined on -// CommitteeRequestBody -func ValidateCommitteeRequestBody(body *CommitteeRequestBody) (err error) { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", body.UID, goa.FormatUUID)) - for _, e := range body.AllowedVotingStatuses { - if !(e == "Voting Rep" || e == "Alternate Voting Rep" || e == "Observer" || e == "Emeritus" || e == "None") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.allowed_voting_statuses[*]", e, []any{"Voting Rep", "Alternate Voting Rep", "Observer", "Emeritus", "None"})) - } +// ValidateCheckGroupsioSubscriberInternalServerErrorResponseBody runs the +// validations defined on +// check-groupsio-subscriber_InternalServerError_response_body +func ValidateCheckGroupsioSubscriberInternalServerErrorResponseBody(body *CheckGroupsioSubscriberInternalServerErrorResponseBody) (err error) { + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateCommitteeResponseBody runs the validations defined on -// CommitteeResponseBody -func ValidateCommitteeResponseBody(body *CommitteeResponseBody) (err error) { - if body.UID == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("uid", "body")) - } - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - for _, e := range body.AllowedVotingStatuses { - if !(e == "Voting Rep" || e == "Alternate Voting Rep" || e == "Observer" || e == "Emeritus" || e == "None") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.allowed_voting_statuses[*]", e, []any{"Voting Rep", "Alternate Voting Rep", "Observer", "Emeritus", "None"})) - } +// ValidateCheckGroupsioSubscriberServiceUnavailableResponseBody runs the +// validations defined on +// check-groupsio-subscriber_ServiceUnavailable_response_body +func ValidateCheckGroupsioSubscriberServiceUnavailableResponseBody(body *CheckGroupsioSubscriberServiceUnavailableResponseBody) (err error) { + if body.Message == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("message", "body")) } return } -// ValidateGrpsIoMailingListWithReadonlyAttributesResponseBody runs the -// validations defined on -// grps-io-mailing-list-with-readonly-attributesResponseBody -func ValidateGrpsIoMailingListWithReadonlyAttributesResponseBody(body *GrpsIoMailingListWithReadonlyAttributesResponseBody) (err error) { - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - if body.GroupName != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.group_name", *body.GroupName, "^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$")) - } - if body.GroupName != nil { - if utf8.RuneCountInString(*body.GroupName) < 3 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", *body.GroupName, utf8.RuneCountInString(*body.GroupName), 3, true)) - } - } - if body.GroupName != nil { - if utf8.RuneCountInString(*body.GroupName) > 34 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", *body.GroupName, utf8.RuneCountInString(*body.GroupName), 34, false)) - } - } - if body.GroupID != nil { - if *body.GroupID < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.group_id", *body.GroupID, 0, true)) - } - } - if body.Type != nil { - if !(*body.Type == "announcement" || *body.Type == "discussion_moderated" || *body.Type == "discussion_open") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", *body.Type, []any{"announcement", "discussion_moderated", "discussion_open"})) - } - } - if body.AudienceAccess != nil { - if !(*body.AudienceAccess == "public" || *body.AudienceAccess == "approval_required" || *body.AudienceAccess == "invite_only") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.audience_access", *body.AudienceAccess, []any{"public", "approval_required", "invite_only"})) - } - } - for _, e := range body.Committees { - if e != nil { - if err2 := ValidateCommitteeResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if body.Description != nil { - if utf8.RuneCountInString(*body.Description) < 11 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", *body.Description, utf8.RuneCountInString(*body.Description), 11, true)) - } - } - if body.Description != nil { - if utf8.RuneCountInString(*body.Description) > 500 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", *body.Description, utf8.RuneCountInString(*body.Description), 500, false)) - } - } - if body.Title != nil { - if utf8.RuneCountInString(*body.Title) < 5 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", *body.Title, utf8.RuneCountInString(*body.Title), 5, true)) - } - } - if body.Title != nil { - if utf8.RuneCountInString(*body.Title) > 100 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", *body.Title, utf8.RuneCountInString(*body.Title), 100, false)) - } - } - if body.SubjectTag != nil { - if utf8.RuneCountInString(*body.SubjectTag) > 50 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.subject_tag", *body.SubjectTag, utf8.RuneCountInString(*body.SubjectTag), 50, false)) - } - } - if body.ServiceUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.service_uid", *body.ServiceUID, goa.FormatUUID)) - } - if body.SubscriberCount != nil { - if *body.SubscriberCount < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.subscriber_count", *body.SubscriberCount, 0, true)) - } - } +// ValidateGroupsioServiceResponseBody runs the validations defined on +// groupsio-serviceResponseBody +func ValidateGroupsioServiceResponseBody(body *GroupsioServiceResponseBody) (err error) { if body.ProjectUID != nil { err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_slug", *body.ProjectSlug, goa.FormatRegexp)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.project_slug", *body.ProjectSlug, "^[a-z][a-z0-9_\\-]*[a-z0-9]$")) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) - } return } -// ValidateGrpsIoMailingListSettingsResponseBody runs the validations defined -// on grps-io-mailing-list-settingsResponseBody -func ValidateGrpsIoMailingListSettingsResponseBody(body *GrpsIoMailingListSettingsResponseBody) (err error) { - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if body.LastReviewedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_reviewed_at", *body.LastReviewedAt, goa.FormatDateTime)) - } - if body.LastAuditedTime != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_audited_time", *body.LastAuditedTime, goa.FormatDateTime)) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) +// ValidateGroupsioSubgroupResponseBody runs the validations defined on +// groupsio-subgroupResponseBody +func ValidateGroupsioSubgroupResponseBody(body *GroupsioSubgroupResponseBody) (err error) { + if body.ProjectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) + if body.CommitteeUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.committee_uid", *body.CommitteeUID, goa.FormatUUID)) } return } -// ValidateGrpsIoMemberWithReadonlyAttributesResponseBody runs the validations -// defined on grps-io-member-with-readonly-attributesResponseBody -func ValidateGrpsIoMemberWithReadonlyAttributesResponseBody(body *GrpsIoMemberWithReadonlyAttributesResponseBody) (err error) { - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) - } - if body.MailingListUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.mailing_list_uid", *body.MailingListUID, goa.FormatUUID)) - } - if body.Username != nil { - if utf8.RuneCountInString(*body.Username) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.username", *body.Username, utf8.RuneCountInString(*body.Username), 255, false)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 1, true)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 255, false)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 1, true)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 255, false)) - } - } +// ValidateGroupsioMemberResponseBody runs the validations defined on +// groupsio-memberResponseBody +func ValidateGroupsioMemberResponseBody(body *GroupsioMemberResponseBody) (err error) { if body.Email != nil { err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) } - if body.Organization != nil { - if utf8.RuneCountInString(*body.Organization) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.organization", *body.Organization, utf8.RuneCountInString(*body.Organization), 255, false)) - } - } - if body.JobTitle != nil { - if utf8.RuneCountInString(*body.JobTitle) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.job_title", *body.JobTitle, utf8.RuneCountInString(*body.JobTitle), 255, false)) - } - } - if body.MemberType != nil { - if !(*body.MemberType == "committee" || *body.MemberType == "direct") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.member_type", *body.MemberType, []any{"committee", "direct"})) - } - } - if body.DeliveryMode != nil { - if !(*body.DeliveryMode == "normal" || *body.DeliveryMode == "digest" || *body.DeliveryMode == "none") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.delivery_mode", *body.DeliveryMode, []any{"normal", "digest", "none"})) - } - } - if body.ModStatus != nil { - if !(*body.ModStatus == "none" || *body.ModStatus == "moderator" || *body.ModStatus == "owner") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.mod_status", *body.ModStatus, []any{"none", "moderator", "owner"})) - } - } - if body.LastReviewedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_reviewed_at", *body.LastReviewedAt, goa.FormatDateTime)) - } - if body.CreatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.created_at", *body.CreatedAt, goa.FormatDateTime)) - } - if body.UpdatedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.updated_at", *body.UpdatedAt, goa.FormatDateTime)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoResponseBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } return } diff --git a/gen/http/mailing_list/server/encode_decode.go b/gen/http/mailing_list/server/encode_decode.go index 35fae9d..f243575 100644 --- a/gen/http/mailing_list/server/encode_decode.go +++ b/gen/http/mailing_list/server/encode_decode.go @@ -75,24 +75,127 @@ func EncodeReadyzError(encoder func(context.Context, http.ResponseWriter) goahtt } } -// EncodeCreateGrpsioServiceResponse returns an encoder for responses returned -// by the mailing-list create-grpsio-service endpoint. -func EncodeCreateGrpsioServiceResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeListGroupsioServicesResponse returns an encoder for responses returned +// by the mailing-list list-groupsio-services endpoint. +func EncodeListGroupsioServicesResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - res, _ := v.(*mailinglist.GrpsIoServiceFull) + res, _ := v.(*mailinglist.GroupsioServiceList) enc := encoder(ctx, w) - body := NewCreateGrpsioServiceResponseBody(res) + body := NewListGroupsioServicesResponseBody(res) + w.WriteHeader(http.StatusOK) + return enc.Encode(body) + } +} + +// DecodeListGroupsioServicesRequest returns a decoder for requests sent to the +// mailing-list list-groupsio-services endpoint. +func DecodeListGroupsioServicesRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { + return func(r *http.Request) (any, error) { + var ( + projectUID *string + bearerToken *string + err error + ) + projectUIDRaw := r.URL.Query().Get("project_uid") + if projectUIDRaw != "" { + projectUID = &projectUIDRaw + } + if projectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("project_uid", *projectUID, goa.FormatUUID)) + } + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + if err != nil { + return nil, err + } + payload := NewListGroupsioServicesPayload(projectUID, bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } + } + + return payload, nil + } +} + +// EncodeListGroupsioServicesError returns an encoder for errors returned by +// the list-groupsio-services mailing-list endpoint. +func EncodeListGroupsioServicesError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { + encodeError := goahttp.ErrorEncoder(encoder, formatter) + return func(ctx context.Context, w http.ResponseWriter, v error) error { + var en goa.GoaErrorNamer + if !errors.As(v, &en) { + return encodeError(ctx, w, v) + } + switch en.GoaErrorName() { + case "BadRequest": + var res *mailinglist.BadRequestError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewListGroupsioServicesBadRequestResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusBadRequest) + return enc.Encode(body) + case "InternalServerError": + var res *mailinglist.InternalServerError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewListGroupsioServicesInternalServerErrorResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusInternalServerError) + return enc.Encode(body) + case "ServiceUnavailable": + var res *mailinglist.ServiceUnavailableError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewListGroupsioServicesServiceUnavailableResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusServiceUnavailable) + return enc.Encode(body) + default: + return encodeError(ctx, w, v) + } + } +} + +// EncodeCreateGroupsioServiceResponse returns an encoder for responses +// returned by the mailing-list create-groupsio-service endpoint. +func EncodeCreateGroupsioServiceResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + res, _ := v.(*mailinglist.GroupsioService) + enc := encoder(ctx, w) + body := NewCreateGroupsioServiceResponseBody(res) w.WriteHeader(http.StatusCreated) return enc.Encode(body) } } -// DecodeCreateGrpsioServiceRequest returns a decoder for requests sent to the -// mailing-list create-grpsio-service endpoint. -func DecodeCreateGrpsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeCreateGroupsioServiceRequest returns a decoder for requests sent to +// the mailing-list create-groupsio-service endpoint. +func DecodeCreateGroupsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - body CreateGrpsioServiceRequestBody + body CreateGroupsioServiceRequestBody err error ) err = decoder(r).Decode(&body) @@ -106,30 +209,19 @@ func DecodeCreateGrpsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Requ } return nil, goa.DecodePayloadError(err.Error()) } - err = ValidateCreateGrpsioServiceRequestBody(&body) + err = ValidateCreateGroupsioServiceRequestBody(&body) if err != nil { return nil, err } var ( - version string bearerToken *string ) - version = r.URL.Query().Get("v") - if version == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("version", "query string")) - } - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } bearerTokenRaw := r.Header.Get("Authorization") if bearerTokenRaw != "" { bearerToken = &bearerTokenRaw } - if err != nil { - return nil, err - } - payload := NewCreateGrpsioServicePayload(&body, version, bearerToken) + payload := NewCreateGroupsioServicePayload(&body, bearerToken) if payload.BearerToken != nil { if strings.Contains(*payload.BearerToken, " ") { // Remove authorization scheme prefix (e.g. "Bearer") @@ -142,9 +234,9 @@ func DecodeCreateGrpsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Requ } } -// EncodeCreateGrpsioServiceError returns an encoder for errors returned by the -// create-grpsio-service mailing-list endpoint. -func EncodeCreateGrpsioServiceError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeCreateGroupsioServiceError returns an encoder for errors returned by +// the create-groupsio-service mailing-list endpoint. +func EncodeCreateGroupsioServiceError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -160,7 +252,7 @@ func EncodeCreateGrpsioServiceError(encoder func(context.Context, http.ResponseW if formatter != nil { body = formatter(ctx, res) } else { - body = NewCreateGrpsioServiceBadRequestResponseBody(res) + body = NewCreateGroupsioServiceBadRequestResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusBadRequest) @@ -173,7 +265,7 @@ func EncodeCreateGrpsioServiceError(encoder func(context.Context, http.ResponseW if formatter != nil { body = formatter(ctx, res) } else { - body = NewCreateGrpsioServiceConflictResponseBody(res) + body = NewCreateGroupsioServiceConflictResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusConflict) @@ -186,24 +278,11 @@ func EncodeCreateGrpsioServiceError(encoder func(context.Context, http.ResponseW if formatter != nil { body = formatter(ctx, res) } else { - body = NewCreateGrpsioServiceInternalServerErrorResponseBody(res) + body = NewCreateGroupsioServiceInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) return enc.Encode(body) - case "NotFound": - var res *mailinglist.NotFoundError - errors.As(v, &res) - enc := encoder(ctx, w) - var body any - if formatter != nil { - body = formatter(ctx, res) - } else { - body = NewCreateGrpsioServiceNotFoundResponseBody(res) - } - w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusNotFound) - return enc.Encode(body) case "ServiceUnavailable": var res *mailinglist.ServiceUnavailableError errors.As(v, &res) @@ -212,7 +291,7 @@ func EncodeCreateGrpsioServiceError(encoder func(context.Context, http.ResponseW if formatter != nil { body = formatter(ctx, res) } else { - body = NewCreateGrpsioServiceServiceUnavailableResponseBody(res) + body = NewCreateGroupsioServiceServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -223,52 +302,34 @@ func EncodeCreateGrpsioServiceError(encoder func(context.Context, http.ResponseW } } -// EncodeGetGrpsioServiceResponse returns an encoder for responses returned by -// the mailing-list get-grpsio-service endpoint. -func EncodeGetGrpsioServiceResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeGetGroupsioServiceResponse returns an encoder for responses returned +// by the mailing-list get-groupsio-service endpoint. +func EncodeGetGroupsioServiceResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - res, _ := v.(*mailinglist.GetGrpsioServiceResult) + res, _ := v.(*mailinglist.GroupsioService) enc := encoder(ctx, w) - body := NewGetGrpsioServiceResponseBody(res) - if res.Etag != nil { - w.Header().Set("Etag", *res.Etag) - } + body := NewGetGroupsioServiceResponseBody(res) w.WriteHeader(http.StatusOK) return enc.Encode(body) } } -// DecodeGetGrpsioServiceRequest returns a decoder for requests sent to the -// mailing-list get-grpsio-service endpoint. -func DecodeGetGrpsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeGetGroupsioServiceRequest returns a decoder for requests sent to the +// mailing-list get-groupsio-service endpoint. +func DecodeGetGroupsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - uid string - version *string + serviceID string bearerToken *string - err error params = mux.Vars(r) ) - uid = params["uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - versionRaw := r.URL.Query().Get("v") - if versionRaw != "" { - version = &versionRaw - } - if version != nil { - if !(*version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", *version, []any{"1"})) - } - } + serviceID = params["service_id"] bearerTokenRaw := r.Header.Get("Authorization") if bearerTokenRaw != "" { bearerToken = &bearerTokenRaw } - if err != nil { - return nil, err - } - payload := NewGetGrpsioServicePayload(uid, version, bearerToken) + payload := NewGetGroupsioServicePayload(serviceID, bearerToken) if payload.BearerToken != nil { if strings.Contains(*payload.BearerToken, " ") { // Remove authorization scheme prefix (e.g. "Bearer") @@ -281,9 +342,9 @@ func DecodeGetGrpsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Request } } -// EncodeGetGrpsioServiceError returns an encoder for errors returned by the -// get-grpsio-service mailing-list endpoint. -func EncodeGetGrpsioServiceError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeGetGroupsioServiceError returns an encoder for errors returned by the +// get-groupsio-service mailing-list endpoint. +func EncodeGetGroupsioServiceError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -291,19 +352,6 @@ func EncodeGetGrpsioServiceError(encoder func(context.Context, http.ResponseWrit return encodeError(ctx, w, v) } switch en.GoaErrorName() { - case "BadRequest": - var res *mailinglist.BadRequestError - errors.As(v, &res) - enc := encoder(ctx, w) - var body any - if formatter != nil { - body = formatter(ctx, res) - } else { - body = NewGetGrpsioServiceBadRequestResponseBody(res) - } - w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusBadRequest) - return enc.Encode(body) case "InternalServerError": var res *mailinglist.InternalServerError errors.As(v, &res) @@ -312,7 +360,7 @@ func EncodeGetGrpsioServiceError(encoder func(context.Context, http.ResponseWrit if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioServiceInternalServerErrorResponseBody(res) + body = NewGetGroupsioServiceInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) @@ -325,7 +373,7 @@ func EncodeGetGrpsioServiceError(encoder func(context.Context, http.ResponseWrit if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioServiceNotFoundResponseBody(res) + body = NewGetGroupsioServiceNotFoundResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusNotFound) @@ -338,7 +386,7 @@ func EncodeGetGrpsioServiceError(encoder func(context.Context, http.ResponseWrit if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioServiceServiceUnavailableResponseBody(res) + body = NewGetGroupsioServiceServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -349,24 +397,24 @@ func EncodeGetGrpsioServiceError(encoder func(context.Context, http.ResponseWrit } } -// EncodeUpdateGrpsioServiceResponse returns an encoder for responses returned -// by the mailing-list update-grpsio-service endpoint. -func EncodeUpdateGrpsioServiceResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeUpdateGroupsioServiceResponse returns an encoder for responses +// returned by the mailing-list update-groupsio-service endpoint. +func EncodeUpdateGroupsioServiceResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - res, _ := v.(*mailinglist.GrpsIoServiceWithReadonlyAttributes) + res, _ := v.(*mailinglist.GroupsioService) enc := encoder(ctx, w) - body := NewUpdateGrpsioServiceResponseBody(res) + body := NewUpdateGroupsioServiceResponseBody(res) w.WriteHeader(http.StatusOK) return enc.Encode(body) } } -// DecodeUpdateGrpsioServiceRequest returns a decoder for requests sent to the -// mailing-list update-grpsio-service endpoint. -func DecodeUpdateGrpsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeUpdateGroupsioServiceRequest returns a decoder for requests sent to +// the mailing-list update-groupsio-service endpoint. +func DecodeUpdateGroupsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - body UpdateGrpsioServiceRequestBody + body UpdateGroupsioServiceRequestBody err error ) err = decoder(r).Decode(&body) @@ -380,40 +428,23 @@ func DecodeUpdateGrpsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Requ } return nil, goa.DecodePayloadError(err.Error()) } - err = ValidateUpdateGrpsioServiceRequestBody(&body) + err = ValidateUpdateGroupsioServiceRequestBody(&body) if err != nil { return nil, err } var ( - uid string - version string + serviceID string bearerToken *string - ifMatch *string params = mux.Vars(r) ) - uid = params["uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - version = r.URL.Query().Get("v") - if version == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("version", "query string")) - } - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } + serviceID = params["service_id"] bearerTokenRaw := r.Header.Get("Authorization") if bearerTokenRaw != "" { bearerToken = &bearerTokenRaw } - ifMatchRaw := r.Header.Get("If-Match") - if ifMatchRaw != "" { - ifMatch = &ifMatchRaw - } - if err != nil { - return nil, err - } - payload := NewUpdateGrpsioServicePayload(&body, uid, version, bearerToken, ifMatch) + payload := NewUpdateGroupsioServicePayload(&body, serviceID, bearerToken) if payload.BearerToken != nil { if strings.Contains(*payload.BearerToken, " ") { // Remove authorization scheme prefix (e.g. "Bearer") @@ -426,9 +457,9 @@ func DecodeUpdateGrpsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Requ } } -// EncodeUpdateGrpsioServiceError returns an encoder for errors returned by the -// update-grpsio-service mailing-list endpoint. -func EncodeUpdateGrpsioServiceError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeUpdateGroupsioServiceError returns an encoder for errors returned by +// the update-groupsio-service mailing-list endpoint. +func EncodeUpdateGroupsioServiceError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -444,24 +475,11 @@ func EncodeUpdateGrpsioServiceError(encoder func(context.Context, http.ResponseW if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioServiceBadRequestResponseBody(res) + body = NewUpdateGroupsioServiceBadRequestResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusBadRequest) return enc.Encode(body) - case "Conflict": - var res *mailinglist.ConflictError - errors.As(v, &res) - enc := encoder(ctx, w) - var body any - if formatter != nil { - body = formatter(ctx, res) - } else { - body = NewUpdateGrpsioServiceConflictResponseBody(res) - } - w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusConflict) - return enc.Encode(body) case "InternalServerError": var res *mailinglist.InternalServerError errors.As(v, &res) @@ -470,7 +488,7 @@ func EncodeUpdateGrpsioServiceError(encoder func(context.Context, http.ResponseW if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioServiceInternalServerErrorResponseBody(res) + body = NewUpdateGroupsioServiceInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) @@ -483,7 +501,7 @@ func EncodeUpdateGrpsioServiceError(encoder func(context.Context, http.ResponseW if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioServiceNotFoundResponseBody(res) + body = NewUpdateGroupsioServiceNotFoundResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusNotFound) @@ -496,7 +514,7 @@ func EncodeUpdateGrpsioServiceError(encoder func(context.Context, http.ResponseW if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioServiceServiceUnavailableResponseBody(res) + body = NewUpdateGroupsioServiceServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -507,51 +525,31 @@ func EncodeUpdateGrpsioServiceError(encoder func(context.Context, http.ResponseW } } -// EncodeDeleteGrpsioServiceResponse returns an encoder for responses returned -// by the mailing-list delete-grpsio-service endpoint. -func EncodeDeleteGrpsioServiceResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeDeleteGroupsioServiceResponse returns an encoder for responses +// returned by the mailing-list delete-groupsio-service endpoint. +func EncodeDeleteGroupsioServiceResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { w.WriteHeader(http.StatusNoContent) return nil } } -// DecodeDeleteGrpsioServiceRequest returns a decoder for requests sent to the -// mailing-list delete-grpsio-service endpoint. -func DecodeDeleteGrpsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeDeleteGroupsioServiceRequest returns a decoder for requests sent to +// the mailing-list delete-groupsio-service endpoint. +func DecodeDeleteGroupsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - uid string - version *string + serviceID string bearerToken *string - ifMatch *string - err error params = mux.Vars(r) ) - uid = params["uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - versionRaw := r.URL.Query().Get("v") - if versionRaw != "" { - version = &versionRaw - } - if version != nil { - if !(*version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", *version, []any{"1"})) - } - } + serviceID = params["service_id"] bearerTokenRaw := r.Header.Get("Authorization") if bearerTokenRaw != "" { bearerToken = &bearerTokenRaw } - ifMatchRaw := r.Header.Get("If-Match") - if ifMatchRaw != "" { - ifMatch = &ifMatchRaw - } - if err != nil { - return nil, err - } - payload := NewDeleteGrpsioServicePayload(uid, version, bearerToken, ifMatch) + payload := NewDeleteGroupsioServicePayload(serviceID, bearerToken) if payload.BearerToken != nil { if strings.Contains(*payload.BearerToken, " ") { // Remove authorization scheme prefix (e.g. "Bearer") @@ -564,9 +562,9 @@ func DecodeDeleteGrpsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Requ } } -// EncodeDeleteGrpsioServiceError returns an encoder for errors returned by the -// delete-grpsio-service mailing-list endpoint. -func EncodeDeleteGrpsioServiceError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeDeleteGroupsioServiceError returns an encoder for errors returned by +// the delete-groupsio-service mailing-list endpoint. +func EncodeDeleteGroupsioServiceError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -574,57 +572,109 @@ func EncodeDeleteGrpsioServiceError(encoder func(context.Context, http.ResponseW return encodeError(ctx, w, v) } switch en.GoaErrorName() { - case "BadRequest": - var res *mailinglist.BadRequestError + case "InternalServerError": + var res *mailinglist.InternalServerError errors.As(v, &res) enc := encoder(ctx, w) var body any if formatter != nil { body = formatter(ctx, res) } else { - body = NewDeleteGrpsioServiceBadRequestResponseBody(res) + body = NewDeleteGroupsioServiceInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusBadRequest) + w.WriteHeader(http.StatusInternalServerError) return enc.Encode(body) - case "Conflict": - var res *mailinglist.ConflictError + case "NotFound": + var res *mailinglist.NotFoundError errors.As(v, &res) enc := encoder(ctx, w) var body any if formatter != nil { body = formatter(ctx, res) } else { - body = NewDeleteGrpsioServiceConflictResponseBody(res) + body = NewDeleteGroupsioServiceNotFoundResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusConflict) + w.WriteHeader(http.StatusNotFound) return enc.Encode(body) - case "InternalServerError": - var res *mailinglist.InternalServerError + case "ServiceUnavailable": + var res *mailinglist.ServiceUnavailableError errors.As(v, &res) enc := encoder(ctx, w) var body any if formatter != nil { body = formatter(ctx, res) } else { - body = NewDeleteGrpsioServiceInternalServerErrorResponseBody(res) + body = NewDeleteGroupsioServiceServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusServiceUnavailable) return enc.Encode(body) - case "NotFound": - var res *mailinglist.NotFoundError + default: + return encodeError(ctx, w, v) + } + } +} + +// EncodeGetGroupsioServiceProjectsResponse returns an encoder for responses +// returned by the mailing-list get-groupsio-service-projects endpoint. +func EncodeGetGroupsioServiceProjectsResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + res, _ := v.(*mailinglist.GroupsioProjectsResponse) + enc := encoder(ctx, w) + body := NewGetGroupsioServiceProjectsResponseBody(res) + w.WriteHeader(http.StatusOK) + return enc.Encode(body) + } +} + +// DecodeGetGroupsioServiceProjectsRequest returns a decoder for requests sent +// to the mailing-list get-groupsio-service-projects endpoint. +func DecodeGetGroupsioServiceProjectsRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { + return func(r *http.Request) (any, error) { + var ( + bearerToken *string + ) + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + payload := NewGetGroupsioServiceProjectsPayload(bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } + } + + return payload, nil + } +} + +// EncodeGetGroupsioServiceProjectsError returns an encoder for errors returned +// by the get-groupsio-service-projects mailing-list endpoint. +func EncodeGetGroupsioServiceProjectsError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { + encodeError := goahttp.ErrorEncoder(encoder, formatter) + return func(ctx context.Context, w http.ResponseWriter, v error) error { + var en goa.GoaErrorNamer + if !errors.As(v, &en) { + return encodeError(ctx, w, v) + } + switch en.GoaErrorName() { + case "InternalServerError": + var res *mailinglist.InternalServerError errors.As(v, &res) enc := encoder(ctx, w) var body any if formatter != nil { body = formatter(ctx, res) } else { - body = NewDeleteGrpsioServiceNotFoundResponseBody(res) + body = NewGetGroupsioServiceProjectsInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusNotFound) + w.WriteHeader(http.StatusInternalServerError) return enc.Encode(body) case "ServiceUnavailable": var res *mailinglist.ServiceUnavailableError @@ -634,7 +684,7 @@ func EncodeDeleteGrpsioServiceError(encoder func(context.Context, http.ResponseW if formatter != nil { body = formatter(ctx, res) } else { - body = NewDeleteGrpsioServiceServiceUnavailableResponseBody(res) + body = NewGetGroupsioServiceProjectsServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -645,44 +695,32 @@ func EncodeDeleteGrpsioServiceError(encoder func(context.Context, http.ResponseW } } -// EncodeGetGrpsioServiceSettingsResponse returns an encoder for responses -// returned by the mailing-list get-grpsio-service-settings endpoint. -func EncodeGetGrpsioServiceSettingsResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeFindParentGroupsioServiceResponse returns an encoder for responses +// returned by the mailing-list find-parent-groupsio-service endpoint. +func EncodeFindParentGroupsioServiceResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - res, _ := v.(*mailinglist.GetGrpsioServiceSettingsResult) + res, _ := v.(*mailinglist.GroupsioService) enc := encoder(ctx, w) - body := NewGetGrpsioServiceSettingsResponseBody(res) - if res.Etag != nil { - w.Header().Set("Etag", *res.Etag) - } + body := NewFindParentGroupsioServiceResponseBody(res) w.WriteHeader(http.StatusOK) return enc.Encode(body) } } -// DecodeGetGrpsioServiceSettingsRequest returns a decoder for requests sent to -// the mailing-list get-grpsio-service-settings endpoint. -func DecodeGetGrpsioServiceSettingsRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeFindParentGroupsioServiceRequest returns a decoder for requests sent +// to the mailing-list find-parent-groupsio-service endpoint. +func DecodeFindParentGroupsioServiceRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - uid string - version *string + projectUID string bearerToken *string err error - - params = mux.Vars(r) ) - uid = params["uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - versionRaw := r.URL.Query().Get("v") - if versionRaw != "" { - version = &versionRaw - } - if version != nil { - if !(*version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", *version, []any{"1"})) - } + projectUID = r.URL.Query().Get("project_uid") + if projectUID == "" { + err = goa.MergeErrors(err, goa.MissingFieldError("project_uid", "query string")) } + err = goa.MergeErrors(err, goa.ValidateFormat("project_uid", projectUID, goa.FormatUUID)) bearerTokenRaw := r.Header.Get("Authorization") if bearerTokenRaw != "" { bearerToken = &bearerTokenRaw @@ -690,7 +728,7 @@ func DecodeGetGrpsioServiceSettingsRequest(mux goahttp.Muxer, decoder func(*http if err != nil { return nil, err } - payload := NewGetGrpsioServiceSettingsPayload(uid, version, bearerToken) + payload := NewFindParentGroupsioServicePayload(projectUID, bearerToken) if payload.BearerToken != nil { if strings.Contains(*payload.BearerToken, " ") { // Remove authorization scheme prefix (e.g. "Bearer") @@ -703,9 +741,9 @@ func DecodeGetGrpsioServiceSettingsRequest(mux goahttp.Muxer, decoder func(*http } } -// EncodeGetGrpsioServiceSettingsError returns an encoder for errors returned -// by the get-grpsio-service-settings mailing-list endpoint. -func EncodeGetGrpsioServiceSettingsError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeFindParentGroupsioServiceError returns an encoder for errors returned +// by the find-parent-groupsio-service mailing-list endpoint. +func EncodeFindParentGroupsioServiceError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -721,7 +759,7 @@ func EncodeGetGrpsioServiceSettingsError(encoder func(context.Context, http.Resp if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioServiceSettingsBadRequestResponseBody(res) + body = NewFindParentGroupsioServiceBadRequestResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusBadRequest) @@ -734,7 +772,7 @@ func EncodeGetGrpsioServiceSettingsError(encoder func(context.Context, http.Resp if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioServiceSettingsInternalServerErrorResponseBody(res) + body = NewFindParentGroupsioServiceInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) @@ -747,7 +785,7 @@ func EncodeGetGrpsioServiceSettingsError(encoder func(context.Context, http.Resp if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioServiceSettingsNotFoundResponseBody(res) + body = NewFindParentGroupsioServiceNotFoundResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusNotFound) @@ -760,7 +798,7 @@ func EncodeGetGrpsioServiceSettingsError(encoder func(context.Context, http.Resp if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioServiceSettingsServiceUnavailableResponseBody(res) + body = NewFindParentGroupsioServiceServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -771,71 +809,51 @@ func EncodeGetGrpsioServiceSettingsError(encoder func(context.Context, http.Resp } } -// EncodeUpdateGrpsioServiceSettingsResponse returns an encoder for responses -// returned by the mailing-list update-grpsio-service-settings endpoint. -func EncodeUpdateGrpsioServiceSettingsResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeListGroupsioSubgroupsResponse returns an encoder for responses +// returned by the mailing-list list-groupsio-subgroups endpoint. +func EncodeListGroupsioSubgroupsResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - res, _ := v.(*mailinglist.GrpsIoServiceSettings) + res, _ := v.(*mailinglist.GroupsioSubgroupList) enc := encoder(ctx, w) - body := NewUpdateGrpsioServiceSettingsResponseBody(res) + body := NewListGroupsioSubgroupsResponseBody(res) w.WriteHeader(http.StatusOK) return enc.Encode(body) } } -// DecodeUpdateGrpsioServiceSettingsRequest returns a decoder for requests sent -// to the mailing-list update-grpsio-service-settings endpoint. -func DecodeUpdateGrpsioServiceSettingsRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeListGroupsioSubgroupsRequest returns a decoder for requests sent to +// the mailing-list list-groupsio-subgroups endpoint. +func DecodeListGroupsioSubgroupsRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - body UpdateGrpsioServiceSettingsRequestBody - err error + projectUID *string + committeeUID *string + bearerToken *string + err error ) - err = decoder(r).Decode(&body) - if err != nil { - if errors.Is(err, io.EOF) { - return nil, goa.MissingPayloadError() - } - var gerr *goa.ServiceError - if errors.As(err, &gerr) { - return nil, gerr - } - return nil, goa.DecodePayloadError(err.Error()) + qp := r.URL.Query() + projectUIDRaw := qp.Get("project_uid") + if projectUIDRaw != "" { + projectUID = &projectUIDRaw } - err = ValidateUpdateGrpsioServiceSettingsRequestBody(&body) - if err != nil { - return nil, err + if projectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("project_uid", *projectUID, goa.FormatUUID)) } - - var ( - uid string - version string - bearerToken *string - ifMatch *string - - params = mux.Vars(r) - ) - uid = params["uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - version = r.URL.Query().Get("v") - if version == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("version", "query string")) + committeeUIDRaw := qp.Get("committee_uid") + if committeeUIDRaw != "" { + committeeUID = &committeeUIDRaw } - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) + if committeeUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("committee_uid", *committeeUID, goa.FormatUUID)) } bearerTokenRaw := r.Header.Get("Authorization") if bearerTokenRaw != "" { bearerToken = &bearerTokenRaw } - ifMatchRaw := r.Header.Get("If-Match") - if ifMatchRaw != "" { - ifMatch = &ifMatchRaw - } if err != nil { return nil, err } - payload := NewUpdateGrpsioServiceSettingsPayload(&body, uid, version, bearerToken, ifMatch) + payload := NewListGroupsioSubgroupsPayload(projectUID, committeeUID, bearerToken) if payload.BearerToken != nil { if strings.Contains(*payload.BearerToken, " ") { // Remove authorization scheme prefix (e.g. "Bearer") @@ -848,9 +866,9 @@ func DecodeUpdateGrpsioServiceSettingsRequest(mux goahttp.Muxer, decoder func(*h } } -// EncodeUpdateGrpsioServiceSettingsError returns an encoder for errors -// returned by the update-grpsio-service-settings mailing-list endpoint. -func EncodeUpdateGrpsioServiceSettingsError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeListGroupsioSubgroupsError returns an encoder for errors returned by +// the list-groupsio-subgroups mailing-list endpoint. +func EncodeListGroupsioSubgroupsError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -866,24 +884,11 @@ func EncodeUpdateGrpsioServiceSettingsError(encoder func(context.Context, http.R if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioServiceSettingsBadRequestResponseBody(res) + body = NewListGroupsioSubgroupsBadRequestResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusBadRequest) return enc.Encode(body) - case "Conflict": - var res *mailinglist.ConflictError - errors.As(v, &res) - enc := encoder(ctx, w) - var body any - if formatter != nil { - body = formatter(ctx, res) - } else { - body = NewUpdateGrpsioServiceSettingsConflictResponseBody(res) - } - w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusConflict) - return enc.Encode(body) case "InternalServerError": var res *mailinglist.InternalServerError errors.As(v, &res) @@ -892,24 +897,11 @@ func EncodeUpdateGrpsioServiceSettingsError(encoder func(context.Context, http.R if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioServiceSettingsInternalServerErrorResponseBody(res) + body = NewListGroupsioSubgroupsInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) return enc.Encode(body) - case "NotFound": - var res *mailinglist.NotFoundError - errors.As(v, &res) - enc := encoder(ctx, w) - var body any - if formatter != nil { - body = formatter(ctx, res) - } else { - body = NewUpdateGrpsioServiceSettingsNotFoundResponseBody(res) - } - w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusNotFound) - return enc.Encode(body) case "ServiceUnavailable": var res *mailinglist.ServiceUnavailableError errors.As(v, &res) @@ -918,7 +910,7 @@ func EncodeUpdateGrpsioServiceSettingsError(encoder func(context.Context, http.R if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioServiceSettingsServiceUnavailableResponseBody(res) + body = NewListGroupsioSubgroupsServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -929,24 +921,24 @@ func EncodeUpdateGrpsioServiceSettingsError(encoder func(context.Context, http.R } } -// EncodeCreateGrpsioMailingListResponse returns an encoder for responses -// returned by the mailing-list create-grpsio-mailing-list endpoint. -func EncodeCreateGrpsioMailingListResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeCreateGroupsioSubgroupResponse returns an encoder for responses +// returned by the mailing-list create-groupsio-subgroup endpoint. +func EncodeCreateGroupsioSubgroupResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - res, _ := v.(*mailinglist.GrpsIoMailingListFull) + res, _ := v.(*mailinglist.GroupsioSubgroup) enc := encoder(ctx, w) - body := NewCreateGrpsioMailingListResponseBody(res) + body := NewCreateGroupsioSubgroupResponseBody(res) w.WriteHeader(http.StatusCreated) return enc.Encode(body) } } -// DecodeCreateGrpsioMailingListRequest returns a decoder for requests sent to -// the mailing-list create-grpsio-mailing-list endpoint. -func DecodeCreateGrpsioMailingListRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeCreateGroupsioSubgroupRequest returns a decoder for requests sent to +// the mailing-list create-groupsio-subgroup endpoint. +func DecodeCreateGroupsioSubgroupRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - body CreateGrpsioMailingListRequestBody + body CreateGroupsioSubgroupRequestBody err error ) err = decoder(r).Decode(&body) @@ -960,30 +952,19 @@ func DecodeCreateGrpsioMailingListRequest(mux goahttp.Muxer, decoder func(*http. } return nil, goa.DecodePayloadError(err.Error()) } - err = ValidateCreateGrpsioMailingListRequestBody(&body) + err = ValidateCreateGroupsioSubgroupRequestBody(&body) if err != nil { return nil, err } var ( - version string bearerToken *string ) - version = r.URL.Query().Get("v") - if version == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("version", "query string")) - } - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } bearerTokenRaw := r.Header.Get("Authorization") if bearerTokenRaw != "" { bearerToken = &bearerTokenRaw } - if err != nil { - return nil, err - } - payload := NewCreateGrpsioMailingListPayload(&body, version, bearerToken) + payload := NewCreateGroupsioSubgroupPayload(&body, bearerToken) if payload.BearerToken != nil { if strings.Contains(*payload.BearerToken, " ") { // Remove authorization scheme prefix (e.g. "Bearer") @@ -996,9 +977,9 @@ func DecodeCreateGrpsioMailingListRequest(mux goahttp.Muxer, decoder func(*http. } } -// EncodeCreateGrpsioMailingListError returns an encoder for errors returned by -// the create-grpsio-mailing-list mailing-list endpoint. -func EncodeCreateGrpsioMailingListError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeCreateGroupsioSubgroupError returns an encoder for errors returned by +// the create-groupsio-subgroup mailing-list endpoint. +func EncodeCreateGroupsioSubgroupError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -1014,7 +995,7 @@ func EncodeCreateGrpsioMailingListError(encoder func(context.Context, http.Respo if formatter != nil { body = formatter(ctx, res) } else { - body = NewCreateGrpsioMailingListBadRequestResponseBody(res) + body = NewCreateGroupsioSubgroupBadRequestResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusBadRequest) @@ -1027,7 +1008,7 @@ func EncodeCreateGrpsioMailingListError(encoder func(context.Context, http.Respo if formatter != nil { body = formatter(ctx, res) } else { - body = NewCreateGrpsioMailingListConflictResponseBody(res) + body = NewCreateGroupsioSubgroupConflictResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusConflict) @@ -1040,24 +1021,11 @@ func EncodeCreateGrpsioMailingListError(encoder func(context.Context, http.Respo if formatter != nil { body = formatter(ctx, res) } else { - body = NewCreateGrpsioMailingListInternalServerErrorResponseBody(res) + body = NewCreateGroupsioSubgroupInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) return enc.Encode(body) - case "NotFound": - var res *mailinglist.NotFoundError - errors.As(v, &res) - enc := encoder(ctx, w) - var body any - if formatter != nil { - body = formatter(ctx, res) - } else { - body = NewCreateGrpsioMailingListNotFoundResponseBody(res) - } - w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusNotFound) - return enc.Encode(body) case "ServiceUnavailable": var res *mailinglist.ServiceUnavailableError errors.As(v, &res) @@ -1066,7 +1034,7 @@ func EncodeCreateGrpsioMailingListError(encoder func(context.Context, http.Respo if formatter != nil { body = formatter(ctx, res) } else { - body = NewCreateGrpsioMailingListServiceUnavailableResponseBody(res) + body = NewCreateGroupsioSubgroupServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -1077,63 +1045,49 @@ func EncodeCreateGrpsioMailingListError(encoder func(context.Context, http.Respo } } -// EncodeGetGrpsioMailingListResponse returns an encoder for responses returned -// by the mailing-list get-grpsio-mailing-list endpoint. -func EncodeGetGrpsioMailingListResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeGetGroupsioSubgroupResponse returns an encoder for responses returned +// by the mailing-list get-groupsio-subgroup endpoint. +func EncodeGetGroupsioSubgroupResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - res, _ := v.(*mailinglist.GetGrpsioMailingListResult) + res, _ := v.(*mailinglist.GroupsioSubgroup) enc := encoder(ctx, w) - body := NewGetGrpsioMailingListResponseBody(res) - if res.Etag != nil { - w.Header().Set("Etag", *res.Etag) - } + body := NewGetGroupsioSubgroupResponseBody(res) w.WriteHeader(http.StatusOK) return enc.Encode(body) } } -// DecodeGetGrpsioMailingListRequest returns a decoder for requests sent to the -// mailing-list get-grpsio-mailing-list endpoint. -func DecodeGetGrpsioMailingListRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeGetGroupsioSubgroupRequest returns a decoder for requests sent to the +// mailing-list get-groupsio-subgroup endpoint. +func DecodeGetGroupsioSubgroupRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - uid string - version string - bearerToken string - err error + subgroupID string + bearerToken *string params = mux.Vars(r) ) - uid = params["uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - version = r.URL.Query().Get("v") - if version == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("version", "query string")) - } - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } - bearerToken = r.Header.Get("Authorization") - if bearerToken == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("bearer_token", "header")) - } - if err != nil { - return nil, err + subgroupID = params["subgroup_id"] + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw } - payload := NewGetGrpsioMailingListPayload(uid, version, bearerToken) - if strings.Contains(payload.BearerToken, " ") { - // Remove authorization scheme prefix (e.g. "Bearer") - cred := strings.SplitN(payload.BearerToken, " ", 2)[1] - payload.BearerToken = cred + payload := NewGetGroupsioSubgroupPayload(subgroupID, bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } } return payload, nil } } -// EncodeGetGrpsioMailingListError returns an encoder for errors returned by -// the get-grpsio-mailing-list mailing-list endpoint. -func EncodeGetGrpsioMailingListError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeGetGroupsioSubgroupError returns an encoder for errors returned by the +// get-groupsio-subgroup mailing-list endpoint. +func EncodeGetGroupsioSubgroupError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -1141,19 +1095,6 @@ func EncodeGetGrpsioMailingListError(encoder func(context.Context, http.Response return encodeError(ctx, w, v) } switch en.GoaErrorName() { - case "BadRequest": - var res *mailinglist.BadRequestError - errors.As(v, &res) - enc := encoder(ctx, w) - var body any - if formatter != nil { - body = formatter(ctx, res) - } else { - body = NewGetGrpsioMailingListBadRequestResponseBody(res) - } - w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusBadRequest) - return enc.Encode(body) case "InternalServerError": var res *mailinglist.InternalServerError errors.As(v, &res) @@ -1162,7 +1103,7 @@ func EncodeGetGrpsioMailingListError(encoder func(context.Context, http.Response if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioMailingListInternalServerErrorResponseBody(res) + body = NewGetGroupsioSubgroupInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) @@ -1175,7 +1116,7 @@ func EncodeGetGrpsioMailingListError(encoder func(context.Context, http.Response if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioMailingListNotFoundResponseBody(res) + body = NewGetGroupsioSubgroupNotFoundResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusNotFound) @@ -1188,7 +1129,7 @@ func EncodeGetGrpsioMailingListError(encoder func(context.Context, http.Response if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioMailingListServiceUnavailableResponseBody(res) + body = NewGetGroupsioSubgroupServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -1199,24 +1140,24 @@ func EncodeGetGrpsioMailingListError(encoder func(context.Context, http.Response } } -// EncodeUpdateGrpsioMailingListResponse returns an encoder for responses -// returned by the mailing-list update-grpsio-mailing-list endpoint. -func EncodeUpdateGrpsioMailingListResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeUpdateGroupsioSubgroupResponse returns an encoder for responses +// returned by the mailing-list update-groupsio-subgroup endpoint. +func EncodeUpdateGroupsioSubgroupResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - res, _ := v.(*mailinglist.GrpsIoMailingListWithReadonlyAttributes) + res, _ := v.(*mailinglist.GroupsioSubgroup) enc := encoder(ctx, w) - body := NewUpdateGrpsioMailingListResponseBody(res) + body := NewUpdateGroupsioSubgroupResponseBody(res) w.WriteHeader(http.StatusOK) return enc.Encode(body) } } -// DecodeUpdateGrpsioMailingListRequest returns a decoder for requests sent to -// the mailing-list update-grpsio-mailing-list endpoint. -func DecodeUpdateGrpsioMailingListRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeUpdateGroupsioSubgroupRequest returns a decoder for requests sent to +// the mailing-list update-groupsio-subgroup endpoint. +func DecodeUpdateGroupsioSubgroupRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - body UpdateGrpsioMailingListRequestBody + body UpdateGroupsioSubgroupRequestBody err error ) err = decoder(r).Decode(&body) @@ -1230,40 +1171,23 @@ func DecodeUpdateGrpsioMailingListRequest(mux goahttp.Muxer, decoder func(*http. } return nil, goa.DecodePayloadError(err.Error()) } - err = ValidateUpdateGrpsioMailingListRequestBody(&body) + err = ValidateUpdateGroupsioSubgroupRequestBody(&body) if err != nil { return nil, err } var ( - uid string - version string + subgroupID string bearerToken *string - ifMatch *string params = mux.Vars(r) ) - uid = params["uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - version = r.URL.Query().Get("v") - if version == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("version", "query string")) - } - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } + subgroupID = params["subgroup_id"] bearerTokenRaw := r.Header.Get("Authorization") if bearerTokenRaw != "" { bearerToken = &bearerTokenRaw } - ifMatchRaw := r.Header.Get("If-Match") - if ifMatchRaw != "" { - ifMatch = &ifMatchRaw - } - if err != nil { - return nil, err - } - payload := NewUpdateGrpsioMailingListPayload(&body, uid, version, bearerToken, ifMatch) + payload := NewUpdateGroupsioSubgroupPayload(&body, subgroupID, bearerToken) if payload.BearerToken != nil { if strings.Contains(*payload.BearerToken, " ") { // Remove authorization scheme prefix (e.g. "Bearer") @@ -1276,9 +1200,9 @@ func DecodeUpdateGrpsioMailingListRequest(mux goahttp.Muxer, decoder func(*http. } } -// EncodeUpdateGrpsioMailingListError returns an encoder for errors returned by -// the update-grpsio-mailing-list mailing-list endpoint. -func EncodeUpdateGrpsioMailingListError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeUpdateGroupsioSubgroupError returns an encoder for errors returned by +// the update-groupsio-subgroup mailing-list endpoint. +func EncodeUpdateGroupsioSubgroupError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -1294,24 +1218,103 @@ func EncodeUpdateGrpsioMailingListError(encoder func(context.Context, http.Respo if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioMailingListBadRequestResponseBody(res) + body = NewUpdateGroupsioSubgroupBadRequestResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusBadRequest) return enc.Encode(body) - case "Conflict": - var res *mailinglist.ConflictError + case "InternalServerError": + var res *mailinglist.InternalServerError errors.As(v, &res) enc := encoder(ctx, w) var body any if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioMailingListConflictResponseBody(res) + body = NewUpdateGroupsioSubgroupInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusConflict) + w.WriteHeader(http.StatusInternalServerError) return enc.Encode(body) + case "NotFound": + var res *mailinglist.NotFoundError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateGroupsioSubgroupNotFoundResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusNotFound) + return enc.Encode(body) + case "ServiceUnavailable": + var res *mailinglist.ServiceUnavailableError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewUpdateGroupsioSubgroupServiceUnavailableResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusServiceUnavailable) + return enc.Encode(body) + default: + return encodeError(ctx, w, v) + } + } +} + +// EncodeDeleteGroupsioSubgroupResponse returns an encoder for responses +// returned by the mailing-list delete-groupsio-subgroup endpoint. +func EncodeDeleteGroupsioSubgroupResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + w.WriteHeader(http.StatusNoContent) + return nil + } +} + +// DecodeDeleteGroupsioSubgroupRequest returns a decoder for requests sent to +// the mailing-list delete-groupsio-subgroup endpoint. +func DecodeDeleteGroupsioSubgroupRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { + return func(r *http.Request) (any, error) { + var ( + subgroupID string + bearerToken *string + + params = mux.Vars(r) + ) + subgroupID = params["subgroup_id"] + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + payload := NewDeleteGroupsioSubgroupPayload(subgroupID, bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } + } + + return payload, nil + } +} + +// EncodeDeleteGroupsioSubgroupError returns an encoder for errors returned by +// the delete-groupsio-subgroup mailing-list endpoint. +func EncodeDeleteGroupsioSubgroupError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { + encodeError := goahttp.ErrorEncoder(encoder, formatter) + return func(ctx context.Context, w http.ResponseWriter, v error) error { + var en goa.GoaErrorNamer + if !errors.As(v, &en) { + return encodeError(ctx, w, v) + } + switch en.GoaErrorName() { case "InternalServerError": var res *mailinglist.InternalServerError errors.As(v, &res) @@ -1320,7 +1323,7 @@ func EncodeUpdateGrpsioMailingListError(encoder func(context.Context, http.Respo if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioMailingListInternalServerErrorResponseBody(res) + body = NewDeleteGroupsioSubgroupInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) @@ -1333,7 +1336,7 @@ func EncodeUpdateGrpsioMailingListError(encoder func(context.Context, http.Respo if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioMailingListNotFoundResponseBody(res) + body = NewDeleteGroupsioSubgroupNotFoundResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusNotFound) @@ -1346,7 +1349,7 @@ func EncodeUpdateGrpsioMailingListError(encoder func(context.Context, http.Respo if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioMailingListServiceUnavailableResponseBody(res) + body = NewDeleteGroupsioSubgroupServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -1357,51 +1360,40 @@ func EncodeUpdateGrpsioMailingListError(encoder func(context.Context, http.Respo } } -// EncodeDeleteGrpsioMailingListResponse returns an encoder for responses -// returned by the mailing-list delete-grpsio-mailing-list endpoint. -func EncodeDeleteGrpsioMailingListResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeGetGroupsioSubgroupCountResponse returns an encoder for responses +// returned by the mailing-list get-groupsio-subgroup-count endpoint. +func EncodeGetGroupsioSubgroupCountResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - w.WriteHeader(http.StatusNoContent) - return nil + res, _ := v.(*mailinglist.GroupsioCount) + enc := encoder(ctx, w) + body := NewGetGroupsioSubgroupCountResponseBody(res) + w.WriteHeader(http.StatusOK) + return enc.Encode(body) } } -// DecodeDeleteGrpsioMailingListRequest returns a decoder for requests sent to -// the mailing-list delete-grpsio-mailing-list endpoint. -func DecodeDeleteGrpsioMailingListRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeGetGroupsioSubgroupCountRequest returns a decoder for requests sent to +// the mailing-list get-groupsio-subgroup-count endpoint. +func DecodeGetGroupsioSubgroupCountRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - uid string - version *string + projectUID string bearerToken *string - ifMatch *string err error - - params = mux.Vars(r) ) - uid = params["uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - versionRaw := r.URL.Query().Get("v") - if versionRaw != "" { - version = &versionRaw - } - if version != nil { - if !(*version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", *version, []any{"1"})) - } + projectUID = r.URL.Query().Get("project_uid") + if projectUID == "" { + err = goa.MergeErrors(err, goa.MissingFieldError("project_uid", "query string")) } + err = goa.MergeErrors(err, goa.ValidateFormat("project_uid", projectUID, goa.FormatUUID)) bearerTokenRaw := r.Header.Get("Authorization") if bearerTokenRaw != "" { bearerToken = &bearerTokenRaw } - ifMatchRaw := r.Header.Get("If-Match") - if ifMatchRaw != "" { - ifMatch = &ifMatchRaw - } if err != nil { return nil, err } - payload := NewDeleteGrpsioMailingListPayload(uid, version, bearerToken, ifMatch) + payload := NewGetGroupsioSubgroupCountPayload(projectUID, bearerToken) if payload.BearerToken != nil { if strings.Contains(*payload.BearerToken, " ") { // Remove authorization scheme prefix (e.g. "Bearer") @@ -1414,9 +1406,9 @@ func DecodeDeleteGrpsioMailingListRequest(mux goahttp.Muxer, decoder func(*http. } } -// EncodeDeleteGrpsioMailingListError returns an encoder for errors returned by -// the delete-grpsio-mailing-list mailing-list endpoint. -func EncodeDeleteGrpsioMailingListError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeGetGroupsioSubgroupCountError returns an encoder for errors returned +// by the get-groupsio-subgroup-count mailing-list endpoint. +func EncodeGetGroupsioSubgroupCountError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -1432,24 +1424,94 @@ func EncodeDeleteGrpsioMailingListError(encoder func(context.Context, http.Respo if formatter != nil { body = formatter(ctx, res) } else { - body = NewDeleteGrpsioMailingListBadRequestResponseBody(res) + body = NewGetGroupsioSubgroupCountBadRequestResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusBadRequest) return enc.Encode(body) - case "Conflict": - var res *mailinglist.ConflictError + case "InternalServerError": + var res *mailinglist.InternalServerError errors.As(v, &res) enc := encoder(ctx, w) var body any if formatter != nil { body = formatter(ctx, res) } else { - body = NewDeleteGrpsioMailingListConflictResponseBody(res) + body = NewGetGroupsioSubgroupCountInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusConflict) + w.WriteHeader(http.StatusInternalServerError) return enc.Encode(body) + case "ServiceUnavailable": + var res *mailinglist.ServiceUnavailableError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewGetGroupsioSubgroupCountServiceUnavailableResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusServiceUnavailable) + return enc.Encode(body) + default: + return encodeError(ctx, w, v) + } + } +} + +// EncodeGetGroupsioSubgroupMemberCountResponse returns an encoder for +// responses returned by the mailing-list get-groupsio-subgroup-member-count +// endpoint. +func EncodeGetGroupsioSubgroupMemberCountResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + res, _ := v.(*mailinglist.GroupsioCount) + enc := encoder(ctx, w) + body := NewGetGroupsioSubgroupMemberCountResponseBody(res) + w.WriteHeader(http.StatusOK) + return enc.Encode(body) + } +} + +// DecodeGetGroupsioSubgroupMemberCountRequest returns a decoder for requests +// sent to the mailing-list get-groupsio-subgroup-member-count endpoint. +func DecodeGetGroupsioSubgroupMemberCountRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { + return func(r *http.Request) (any, error) { + var ( + subgroupID string + bearerToken *string + + params = mux.Vars(r) + ) + subgroupID = params["subgroup_id"] + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + payload := NewGetGroupsioSubgroupMemberCountPayload(subgroupID, bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } + } + + return payload, nil + } +} + +// EncodeGetGroupsioSubgroupMemberCountError returns an encoder for errors +// returned by the get-groupsio-subgroup-member-count mailing-list endpoint. +func EncodeGetGroupsioSubgroupMemberCountError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { + encodeError := goahttp.ErrorEncoder(encoder, formatter) + return func(ctx context.Context, w http.ResponseWriter, v error) error { + var en goa.GoaErrorNamer + if !errors.As(v, &en) { + return encodeError(ctx, w, v) + } + switch en.GoaErrorName() { case "InternalServerError": var res *mailinglist.InternalServerError errors.As(v, &res) @@ -1458,7 +1520,7 @@ func EncodeDeleteGrpsioMailingListError(encoder func(context.Context, http.Respo if formatter != nil { body = formatter(ctx, res) } else { - body = NewDeleteGrpsioMailingListInternalServerErrorResponseBody(res) + body = NewGetGroupsioSubgroupMemberCountInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) @@ -1471,7 +1533,7 @@ func EncodeDeleteGrpsioMailingListError(encoder func(context.Context, http.Respo if formatter != nil { body = formatter(ctx, res) } else { - body = NewDeleteGrpsioMailingListNotFoundResponseBody(res) + body = NewGetGroupsioSubgroupMemberCountNotFoundResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusNotFound) @@ -1484,7 +1546,7 @@ func EncodeDeleteGrpsioMailingListError(encoder func(context.Context, http.Respo if formatter != nil { body = formatter(ctx, res) } else { - body = NewDeleteGrpsioMailingListServiceUnavailableResponseBody(res) + body = NewGetGroupsioSubgroupMemberCountServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -1495,52 +1557,34 @@ func EncodeDeleteGrpsioMailingListError(encoder func(context.Context, http.Respo } } -// EncodeGetGrpsioMailingListSettingsResponse returns an encoder for responses -// returned by the mailing-list get-grpsio-mailing-list-settings endpoint. -func EncodeGetGrpsioMailingListSettingsResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeListGroupsioMembersResponse returns an encoder for responses returned +// by the mailing-list list-groupsio-members endpoint. +func EncodeListGroupsioMembersResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - res, _ := v.(*mailinglist.GetGrpsioMailingListSettingsResult) + res, _ := v.(*mailinglist.GroupsioMemberList) enc := encoder(ctx, w) - body := NewGetGrpsioMailingListSettingsResponseBody(res) - if res.Etag != nil { - w.Header().Set("Etag", *res.Etag) - } + body := NewListGroupsioMembersResponseBody(res) w.WriteHeader(http.StatusOK) return enc.Encode(body) } } -// DecodeGetGrpsioMailingListSettingsRequest returns a decoder for requests -// sent to the mailing-list get-grpsio-mailing-list-settings endpoint. -func DecodeGetGrpsioMailingListSettingsRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeListGroupsioMembersRequest returns a decoder for requests sent to the +// mailing-list list-groupsio-members endpoint. +func DecodeListGroupsioMembersRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - uid string - version *string + subgroupID string bearerToken *string - err error params = mux.Vars(r) ) - uid = params["uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - versionRaw := r.URL.Query().Get("v") - if versionRaw != "" { - version = &versionRaw - } - if version != nil { - if !(*version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", *version, []any{"1"})) - } - } + subgroupID = params["subgroup_id"] bearerTokenRaw := r.Header.Get("Authorization") if bearerTokenRaw != "" { - bearerToken = &bearerTokenRaw - } - if err != nil { - return nil, err + bearerToken = &bearerTokenRaw } - payload := NewGetGrpsioMailingListSettingsPayload(uid, version, bearerToken) + payload := NewListGroupsioMembersPayload(subgroupID, bearerToken) if payload.BearerToken != nil { if strings.Contains(*payload.BearerToken, " ") { // Remove authorization scheme prefix (e.g. "Bearer") @@ -1553,9 +1597,9 @@ func DecodeGetGrpsioMailingListSettingsRequest(mux goahttp.Muxer, decoder func(* } } -// EncodeGetGrpsioMailingListSettingsError returns an encoder for errors -// returned by the get-grpsio-mailing-list-settings mailing-list endpoint. -func EncodeGetGrpsioMailingListSettingsError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeListGroupsioMembersError returns an encoder for errors returned by the +// list-groupsio-members mailing-list endpoint. +func EncodeListGroupsioMembersError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -1563,19 +1607,6 @@ func EncodeGetGrpsioMailingListSettingsError(encoder func(context.Context, http. return encodeError(ctx, w, v) } switch en.GoaErrorName() { - case "BadRequest": - var res *mailinglist.BadRequestError - errors.As(v, &res) - enc := encoder(ctx, w) - var body any - if formatter != nil { - body = formatter(ctx, res) - } else { - body = NewGetGrpsioMailingListSettingsBadRequestResponseBody(res) - } - w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusBadRequest) - return enc.Encode(body) case "InternalServerError": var res *mailinglist.InternalServerError errors.As(v, &res) @@ -1584,7 +1615,7 @@ func EncodeGetGrpsioMailingListSettingsError(encoder func(context.Context, http. if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioMailingListSettingsInternalServerErrorResponseBody(res) + body = NewListGroupsioMembersInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) @@ -1597,7 +1628,7 @@ func EncodeGetGrpsioMailingListSettingsError(encoder func(context.Context, http. if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioMailingListSettingsNotFoundResponseBody(res) + body = NewListGroupsioMembersNotFoundResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusNotFound) @@ -1610,7 +1641,7 @@ func EncodeGetGrpsioMailingListSettingsError(encoder func(context.Context, http. if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioMailingListSettingsServiceUnavailableResponseBody(res) + body = NewListGroupsioMembersServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -1621,25 +1652,24 @@ func EncodeGetGrpsioMailingListSettingsError(encoder func(context.Context, http. } } -// EncodeUpdateGrpsioMailingListSettingsResponse returns an encoder for -// responses returned by the mailing-list update-grpsio-mailing-list-settings -// endpoint. -func EncodeUpdateGrpsioMailingListSettingsResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeAddGroupsioMemberResponse returns an encoder for responses returned by +// the mailing-list add-groupsio-member endpoint. +func EncodeAddGroupsioMemberResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - res, _ := v.(*mailinglist.GrpsIoMailingListSettings) + res, _ := v.(*mailinglist.GroupsioMember) enc := encoder(ctx, w) - body := NewUpdateGrpsioMailingListSettingsResponseBody(res) - w.WriteHeader(http.StatusOK) + body := NewAddGroupsioMemberResponseBody(res) + w.WriteHeader(http.StatusCreated) return enc.Encode(body) } } -// DecodeUpdateGrpsioMailingListSettingsRequest returns a decoder for requests -// sent to the mailing-list update-grpsio-mailing-list-settings endpoint. -func DecodeUpdateGrpsioMailingListSettingsRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeAddGroupsioMemberRequest returns a decoder for requests sent to the +// mailing-list add-groupsio-member endpoint. +func DecodeAddGroupsioMemberRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - body UpdateGrpsioMailingListSettingsRequestBody + body AddGroupsioMemberRequestBody err error ) err = decoder(r).Decode(&body) @@ -1653,40 +1683,23 @@ func DecodeUpdateGrpsioMailingListSettingsRequest(mux goahttp.Muxer, decoder fun } return nil, goa.DecodePayloadError(err.Error()) } - err = ValidateUpdateGrpsioMailingListSettingsRequestBody(&body) + err = ValidateAddGroupsioMemberRequestBody(&body) if err != nil { return nil, err } var ( - uid string - version string + subgroupID string bearerToken *string - ifMatch *string params = mux.Vars(r) ) - uid = params["uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - version = r.URL.Query().Get("v") - if version == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("version", "query string")) - } - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } + subgroupID = params["subgroup_id"] bearerTokenRaw := r.Header.Get("Authorization") if bearerTokenRaw != "" { bearerToken = &bearerTokenRaw } - ifMatchRaw := r.Header.Get("If-Match") - if ifMatchRaw != "" { - ifMatch = &ifMatchRaw - } - if err != nil { - return nil, err - } - payload := NewUpdateGrpsioMailingListSettingsPayload(&body, uid, version, bearerToken, ifMatch) + payload := NewAddGroupsioMemberPayload(&body, subgroupID, bearerToken) if payload.BearerToken != nil { if strings.Contains(*payload.BearerToken, " ") { // Remove authorization scheme prefix (e.g. "Bearer") @@ -1699,9 +1712,9 @@ func DecodeUpdateGrpsioMailingListSettingsRequest(mux goahttp.Muxer, decoder fun } } -// EncodeUpdateGrpsioMailingListSettingsError returns an encoder for errors -// returned by the update-grpsio-mailing-list-settings mailing-list endpoint. -func EncodeUpdateGrpsioMailingListSettingsError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeAddGroupsioMemberError returns an encoder for errors returned by the +// add-groupsio-member mailing-list endpoint. +func EncodeAddGroupsioMemberError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -1717,7 +1730,7 @@ func EncodeUpdateGrpsioMailingListSettingsError(encoder func(context.Context, ht if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioMailingListSettingsBadRequestResponseBody(res) + body = NewAddGroupsioMemberBadRequestResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusBadRequest) @@ -1730,7 +1743,7 @@ func EncodeUpdateGrpsioMailingListSettingsError(encoder func(context.Context, ht if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioMailingListSettingsConflictResponseBody(res) + body = NewAddGroupsioMemberConflictResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusConflict) @@ -1743,7 +1756,7 @@ func EncodeUpdateGrpsioMailingListSettingsError(encoder func(context.Context, ht if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioMailingListSettingsInternalServerErrorResponseBody(res) + body = NewAddGroupsioMemberInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) @@ -1756,7 +1769,7 @@ func EncodeUpdateGrpsioMailingListSettingsError(encoder func(context.Context, ht if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioMailingListSettingsNotFoundResponseBody(res) + body = NewAddGroupsioMemberNotFoundResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusNotFound) @@ -1769,7 +1782,7 @@ func EncodeUpdateGrpsioMailingListSettingsError(encoder func(context.Context, ht if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioMailingListSettingsServiceUnavailableResponseBody(res) + body = NewAddGroupsioMemberServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -1780,65 +1793,36 @@ func EncodeUpdateGrpsioMailingListSettingsError(encoder func(context.Context, ht } } -// EncodeCreateGrpsioMailingListMemberResponse returns an encoder for responses -// returned by the mailing-list create-grpsio-mailing-list-member endpoint. -func EncodeCreateGrpsioMailingListMemberResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeGetGroupsioMemberResponse returns an encoder for responses returned by +// the mailing-list get-groupsio-member endpoint. +func EncodeGetGroupsioMemberResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - res, _ := v.(*mailinglist.GrpsIoMemberFull) + res, _ := v.(*mailinglist.GroupsioMember) enc := encoder(ctx, w) - body := NewCreateGrpsioMailingListMemberResponseBody(res) - w.WriteHeader(http.StatusCreated) + body := NewGetGroupsioMemberResponseBody(res) + w.WriteHeader(http.StatusOK) return enc.Encode(body) } } -// DecodeCreateGrpsioMailingListMemberRequest returns a decoder for requests -// sent to the mailing-list create-grpsio-mailing-list-member endpoint. -func DecodeCreateGrpsioMailingListMemberRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeGetGroupsioMemberRequest returns a decoder for requests sent to the +// mailing-list get-groupsio-member endpoint. +func DecodeGetGroupsioMemberRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - body CreateGrpsioMailingListMemberRequestBody - err error - ) - err = decoder(r).Decode(&body) - if err != nil { - if errors.Is(err, io.EOF) { - return nil, goa.MissingPayloadError() - } - var gerr *goa.ServiceError - if errors.As(err, &gerr) { - return nil, gerr - } - return nil, goa.DecodePayloadError(err.Error()) - } - err = ValidateCreateGrpsioMailingListMemberRequestBody(&body) - if err != nil { - return nil, err - } - - var ( - uid string - version string + subgroupID string + memberID string bearerToken *string params = mux.Vars(r) ) - uid = params["uid"] - version = r.URL.Query().Get("v") - if version == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("version", "query string")) - } - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } + subgroupID = params["subgroup_id"] + memberID = params["member_id"] bearerTokenRaw := r.Header.Get("Authorization") if bearerTokenRaw != "" { bearerToken = &bearerTokenRaw } - if err != nil { - return nil, err - } - payload := NewCreateGrpsioMailingListMemberPayload(&body, uid, version, bearerToken) + payload := NewGetGroupsioMemberPayload(subgroupID, memberID, bearerToken) if payload.BearerToken != nil { if strings.Contains(*payload.BearerToken, " ") { // Remove authorization scheme prefix (e.g. "Bearer") @@ -1851,9 +1835,9 @@ func DecodeCreateGrpsioMailingListMemberRequest(mux goahttp.Muxer, decoder func( } } -// EncodeCreateGrpsioMailingListMemberError returns an encoder for errors -// returned by the create-grpsio-mailing-list-member mailing-list endpoint. -func EncodeCreateGrpsioMailingListMemberError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeGetGroupsioMemberError returns an encoder for errors returned by the +// get-groupsio-member mailing-list endpoint. +func EncodeGetGroupsioMemberError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -1861,32 +1845,6 @@ func EncodeCreateGrpsioMailingListMemberError(encoder func(context.Context, http return encodeError(ctx, w, v) } switch en.GoaErrorName() { - case "BadRequest": - var res *mailinglist.BadRequestError - errors.As(v, &res) - enc := encoder(ctx, w) - var body any - if formatter != nil { - body = formatter(ctx, res) - } else { - body = NewCreateGrpsioMailingListMemberBadRequestResponseBody(res) - } - w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusBadRequest) - return enc.Encode(body) - case "Conflict": - var res *mailinglist.ConflictError - errors.As(v, &res) - enc := encoder(ctx, w) - var body any - if formatter != nil { - body = formatter(ctx, res) - } else { - body = NewCreateGrpsioMailingListMemberConflictResponseBody(res) - } - w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusConflict) - return enc.Encode(body) case "InternalServerError": var res *mailinglist.InternalServerError errors.As(v, &res) @@ -1895,7 +1853,7 @@ func EncodeCreateGrpsioMailingListMemberError(encoder func(context.Context, http if formatter != nil { body = formatter(ctx, res) } else { - body = NewCreateGrpsioMailingListMemberInternalServerErrorResponseBody(res) + body = NewGetGroupsioMemberInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) @@ -1908,7 +1866,7 @@ func EncodeCreateGrpsioMailingListMemberError(encoder func(context.Context, http if formatter != nil { body = formatter(ctx, res) } else { - body = NewCreateGrpsioMailingListMemberNotFoundResponseBody(res) + body = NewGetGroupsioMemberNotFoundResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusNotFound) @@ -1921,7 +1879,7 @@ func EncodeCreateGrpsioMailingListMemberError(encoder func(context.Context, http if formatter != nil { body = formatter(ctx, res) } else { - body = NewCreateGrpsioMailingListMemberServiceUnavailableResponseBody(res) + body = NewGetGroupsioMemberServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -1932,66 +1890,71 @@ func EncodeCreateGrpsioMailingListMemberError(encoder func(context.Context, http } } -// EncodeGetGrpsioMailingListMemberResponse returns an encoder for responses -// returned by the mailing-list get-grpsio-mailing-list-member endpoint. -func EncodeGetGrpsioMailingListMemberResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeUpdateGroupsioMemberResponse returns an encoder for responses returned +// by the mailing-list update-groupsio-member endpoint. +func EncodeUpdateGroupsioMemberResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - res, _ := v.(*mailinglist.GetGrpsioMailingListMemberResult) + res, _ := v.(*mailinglist.GroupsioMember) enc := encoder(ctx, w) - body := NewGetGrpsioMailingListMemberResponseBody(res) - if res.Etag != nil { - w.Header().Set("Etag", *res.Etag) - } + body := NewUpdateGroupsioMemberResponseBody(res) w.WriteHeader(http.StatusOK) return enc.Encode(body) } } -// DecodeGetGrpsioMailingListMemberRequest returns a decoder for requests sent -// to the mailing-list get-grpsio-mailing-list-member endpoint. -func DecodeGetGrpsioMailingListMemberRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeUpdateGroupsioMemberRequest returns a decoder for requests sent to the +// mailing-list update-groupsio-member endpoint. +func DecodeUpdateGroupsioMemberRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - uid string - memberUID string - version string - bearerToken string - err error - - params = mux.Vars(r) + body UpdateGroupsioMemberRequestBody + err error ) - uid = params["uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - memberUID = params["member_uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("member_uid", memberUID, goa.FormatUUID)) - version = r.URL.Query().Get("v") - if version == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("version", "query string")) - } - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } - bearerToken = r.Header.Get("Authorization") - if bearerToken == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("bearer_token", "header")) + err = decoder(r).Decode(&body) + if err != nil { + if errors.Is(err, io.EOF) { + return nil, goa.MissingPayloadError() + } + var gerr *goa.ServiceError + if errors.As(err, &gerr) { + return nil, gerr + } + return nil, goa.DecodePayloadError(err.Error()) } + err = ValidateUpdateGroupsioMemberRequestBody(&body) if err != nil { return nil, err } - payload := NewGetGrpsioMailingListMemberPayload(uid, memberUID, version, bearerToken) - if strings.Contains(payload.BearerToken, " ") { - // Remove authorization scheme prefix (e.g. "Bearer") - cred := strings.SplitN(payload.BearerToken, " ", 2)[1] - payload.BearerToken = cred + + var ( + subgroupID string + memberID string + bearerToken *string + + params = mux.Vars(r) + ) + subgroupID = params["subgroup_id"] + memberID = params["member_id"] + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + payload := NewUpdateGroupsioMemberPayload(&body, subgroupID, memberID, bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } } return payload, nil } } -// EncodeGetGrpsioMailingListMemberError returns an encoder for errors returned -// by the get-grpsio-mailing-list-member mailing-list endpoint. -func EncodeGetGrpsioMailingListMemberError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeUpdateGroupsioMemberError returns an encoder for errors returned by +// the update-groupsio-member mailing-list endpoint. +func EncodeUpdateGroupsioMemberError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -2007,7 +1970,7 @@ func EncodeGetGrpsioMailingListMemberError(encoder func(context.Context, http.Re if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioMailingListMemberBadRequestResponseBody(res) + body = NewUpdateGroupsioMemberBadRequestResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusBadRequest) @@ -2020,7 +1983,7 @@ func EncodeGetGrpsioMailingListMemberError(encoder func(context.Context, http.Re if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioMailingListMemberInternalServerErrorResponseBody(res) + body = NewUpdateGroupsioMemberInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) @@ -2033,7 +1996,7 @@ func EncodeGetGrpsioMailingListMemberError(encoder func(context.Context, http.Re if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioMailingListMemberNotFoundResponseBody(res) + body = NewUpdateGroupsioMemberNotFoundResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusNotFound) @@ -2046,7 +2009,7 @@ func EncodeGetGrpsioMailingListMemberError(encoder func(context.Context, http.Re if formatter != nil { body = formatter(ctx, res) } else { - body = NewGetGrpsioMailingListMemberServiceUnavailableResponseBody(res) + body = NewUpdateGroupsioMemberServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -2057,87 +2020,48 @@ func EncodeGetGrpsioMailingListMemberError(encoder func(context.Context, http.Re } } -// EncodeUpdateGrpsioMailingListMemberResponse returns an encoder for responses -// returned by the mailing-list update-grpsio-mailing-list-member endpoint. -func EncodeUpdateGrpsioMailingListMemberResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeDeleteGroupsioMemberResponse returns an encoder for responses returned +// by the mailing-list delete-groupsio-member endpoint. +func EncodeDeleteGroupsioMemberResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - res, _ := v.(*mailinglist.GrpsIoMemberWithReadonlyAttributes) - enc := encoder(ctx, w) - body := NewUpdateGrpsioMailingListMemberResponseBody(res) - w.WriteHeader(http.StatusOK) - return enc.Encode(body) + w.WriteHeader(http.StatusNoContent) + return nil } } -// DecodeUpdateGrpsioMailingListMemberRequest returns a decoder for requests -// sent to the mailing-list update-grpsio-mailing-list-member endpoint. -func DecodeUpdateGrpsioMailingListMemberRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeDeleteGroupsioMemberRequest returns a decoder for requests sent to the +// mailing-list delete-groupsio-member endpoint. +func DecodeDeleteGroupsioMemberRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - body UpdateGrpsioMailingListMemberRequestBody - err error - ) - err = decoder(r).Decode(&body) - if err != nil { - if errors.Is(err, io.EOF) { - return nil, goa.MissingPayloadError() - } - var gerr *goa.ServiceError - if errors.As(err, &gerr) { - return nil, gerr - } - return nil, goa.DecodePayloadError(err.Error()) - } - err = ValidateUpdateGrpsioMailingListMemberRequestBody(&body) - if err != nil { - return nil, err - } - - var ( - uid string - memberUID string - version string - bearerToken string - ifMatch string + subgroupID string + memberID string + bearerToken *string params = mux.Vars(r) ) - uid = params["uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - memberUID = params["member_uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("member_uid", memberUID, goa.FormatUUID)) - version = r.URL.Query().Get("v") - if version == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("version", "query string")) - } - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } - bearerToken = r.Header.Get("Authorization") - if bearerToken == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("bearer_token", "header")) - } - ifMatch = r.Header.Get("If-Match") - if ifMatch == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("if_match", "header")) - } - if err != nil { - return nil, err + subgroupID = params["subgroup_id"] + memberID = params["member_id"] + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw } - payload := NewUpdateGrpsioMailingListMemberPayload(&body, uid, memberUID, version, bearerToken, ifMatch) - if strings.Contains(payload.BearerToken, " ") { - // Remove authorization scheme prefix (e.g. "Bearer") - cred := strings.SplitN(payload.BearerToken, " ", 2)[1] - payload.BearerToken = cred + payload := NewDeleteGroupsioMemberPayload(subgroupID, memberID, bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } } return payload, nil } } -// EncodeUpdateGrpsioMailingListMemberError returns an encoder for errors -// returned by the update-grpsio-mailing-list-member mailing-list endpoint. -func EncodeUpdateGrpsioMailingListMemberError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeDeleteGroupsioMemberError returns an encoder for errors returned by +// the delete-groupsio-member mailing-list endpoint. +func EncodeDeleteGroupsioMemberError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -2145,32 +2069,6 @@ func EncodeUpdateGrpsioMailingListMemberError(encoder func(context.Context, http return encodeError(ctx, w, v) } switch en.GoaErrorName() { - case "BadRequest": - var res *mailinglist.BadRequestError - errors.As(v, &res) - enc := encoder(ctx, w) - var body any - if formatter != nil { - body = formatter(ctx, res) - } else { - body = NewUpdateGrpsioMailingListMemberBadRequestResponseBody(res) - } - w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusBadRequest) - return enc.Encode(body) - case "Conflict": - var res *mailinglist.ConflictError - errors.As(v, &res) - enc := encoder(ctx, w) - var body any - if formatter != nil { - body = formatter(ctx, res) - } else { - body = NewUpdateGrpsioMailingListMemberConflictResponseBody(res) - } - w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusConflict) - return enc.Encode(body) case "InternalServerError": var res *mailinglist.InternalServerError errors.As(v, &res) @@ -2179,7 +2077,7 @@ func EncodeUpdateGrpsioMailingListMemberError(encoder func(context.Context, http if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioMailingListMemberInternalServerErrorResponseBody(res) + body = NewDeleteGroupsioMemberInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) @@ -2192,7 +2090,7 @@ func EncodeUpdateGrpsioMailingListMemberError(encoder func(context.Context, http if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioMailingListMemberNotFoundResponseBody(res) + body = NewDeleteGroupsioMemberNotFoundResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusNotFound) @@ -2205,7 +2103,7 @@ func EncodeUpdateGrpsioMailingListMemberError(encoder func(context.Context, http if formatter != nil { body = formatter(ctx, res) } else { - body = NewUpdateGrpsioMailingListMemberServiceUnavailableResponseBody(res) + body = NewDeleteGroupsioMemberServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -2216,65 +2114,66 @@ func EncodeUpdateGrpsioMailingListMemberError(encoder func(context.Context, http } } -// EncodeDeleteGrpsioMailingListMemberResponse returns an encoder for responses -// returned by the mailing-list delete-grpsio-mailing-list-member endpoint. -func EncodeDeleteGrpsioMailingListMemberResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeInviteGroupsioMembersResponse returns an encoder for responses +// returned by the mailing-list invite-groupsio-members endpoint. +func EncodeInviteGroupsioMembersResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { w.WriteHeader(http.StatusNoContent) return nil } } -// DecodeDeleteGrpsioMailingListMemberRequest returns a decoder for requests -// sent to the mailing-list delete-grpsio-mailing-list-member endpoint. -func DecodeDeleteGrpsioMailingListMemberRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeInviteGroupsioMembersRequest returns a decoder for requests sent to +// the mailing-list invite-groupsio-members endpoint. +func DecodeInviteGroupsioMembersRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - uid string - memberUID string - version string - bearerToken string - ifMatch string - err error - - params = mux.Vars(r) + body InviteGroupsioMembersRequestBody + err error ) - uid = params["uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("uid", uid, goa.FormatUUID)) - memberUID = params["member_uid"] - err = goa.MergeErrors(err, goa.ValidateFormat("member_uid", memberUID, goa.FormatUUID)) - version = r.URL.Query().Get("v") - if version == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("version", "query string")) - } - if !(version == "1") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("version", version, []any{"1"})) - } - bearerToken = r.Header.Get("Authorization") - if bearerToken == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("bearer_token", "header")) - } - ifMatch = r.Header.Get("If-Match") - if ifMatch == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("if_match", "header")) + err = decoder(r).Decode(&body) + if err != nil { + if errors.Is(err, io.EOF) { + return nil, goa.MissingPayloadError() + } + var gerr *goa.ServiceError + if errors.As(err, &gerr) { + return nil, gerr + } + return nil, goa.DecodePayloadError(err.Error()) } + err = ValidateInviteGroupsioMembersRequestBody(&body) if err != nil { return nil, err } - payload := NewDeleteGrpsioMailingListMemberPayload(uid, memberUID, version, bearerToken, ifMatch) - if strings.Contains(payload.BearerToken, " ") { - // Remove authorization scheme prefix (e.g. "Bearer") - cred := strings.SplitN(payload.BearerToken, " ", 2)[1] - payload.BearerToken = cred + + var ( + subgroupID string + bearerToken *string + + params = mux.Vars(r) + ) + subgroupID = params["subgroup_id"] + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw + } + payload := NewInviteGroupsioMembersPayload(&body, subgroupID, bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } } return payload, nil } } -// EncodeDeleteGrpsioMailingListMemberError returns an encoder for errors -// returned by the delete-grpsio-mailing-list-member mailing-list endpoint. -func EncodeDeleteGrpsioMailingListMemberError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeInviteGroupsioMembersError returns an encoder for errors returned by +// the invite-groupsio-members mailing-list endpoint. +func EncodeInviteGroupsioMembersError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -2290,24 +2189,11 @@ func EncodeDeleteGrpsioMailingListMemberError(encoder func(context.Context, http if formatter != nil { body = formatter(ctx, res) } else { - body = NewDeleteGrpsioMailingListMemberBadRequestResponseBody(res) + body = NewInviteGroupsioMembersBadRequestResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusBadRequest) return enc.Encode(body) - case "Conflict": - var res *mailinglist.ConflictError - errors.As(v, &res) - enc := encoder(ctx, w) - var body any - if formatter != nil { - body = formatter(ctx, res) - } else { - body = NewDeleteGrpsioMailingListMemberConflictResponseBody(res) - } - w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusConflict) - return enc.Encode(body) case "InternalServerError": var res *mailinglist.InternalServerError errors.As(v, &res) @@ -2316,7 +2202,7 @@ func EncodeDeleteGrpsioMailingListMemberError(encoder func(context.Context, http if formatter != nil { body = formatter(ctx, res) } else { - body = NewDeleteGrpsioMailingListMemberInternalServerErrorResponseBody(res) + body = NewInviteGroupsioMembersInternalServerErrorResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusInternalServerError) @@ -2329,7 +2215,7 @@ func EncodeDeleteGrpsioMailingListMemberError(encoder func(context.Context, http if formatter != nil { body = formatter(ctx, res) } else { - body = NewDeleteGrpsioMailingListMemberNotFoundResponseBody(res) + body = NewInviteGroupsioMembersNotFoundResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusNotFound) @@ -2342,7 +2228,7 @@ func EncodeDeleteGrpsioMailingListMemberError(encoder func(context.Context, http if formatter != nil { body = formatter(ctx, res) } else { - body = NewDeleteGrpsioMailingListMemberServiceUnavailableResponseBody(res) + body = NewInviteGroupsioMembersServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusServiceUnavailable) @@ -2353,21 +2239,24 @@ func EncodeDeleteGrpsioMailingListMemberError(encoder func(context.Context, http } } -// EncodeGroupsioWebhookResponse returns an encoder for responses returned by -// the mailing-list groupsio-webhook endpoint. -func EncodeGroupsioWebhookResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { +// EncodeCheckGroupsioSubscriberResponse returns an encoder for responses +// returned by the mailing-list check-groupsio-subscriber endpoint. +func EncodeCheckGroupsioSubscriberResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { return func(ctx context.Context, w http.ResponseWriter, v any) error { - w.WriteHeader(http.StatusNoContent) - return nil + res, _ := v.(*mailinglist.GroupsioCheckSubscriberResponse) + enc := encoder(ctx, w) + body := NewCheckGroupsioSubscriberResponseBody(res) + w.WriteHeader(http.StatusOK) + return enc.Encode(body) } } -// DecodeGroupsioWebhookRequest returns a decoder for requests sent to the -// mailing-list groupsio-webhook endpoint. -func DecodeGroupsioWebhookRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { +// DecodeCheckGroupsioSubscriberRequest returns a decoder for requests sent to +// the mailing-list check-groupsio-subscriber endpoint. +func DecodeCheckGroupsioSubscriberRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) { return func(r *http.Request) (any, error) { var ( - body GroupsioWebhookRequestBody + body CheckGroupsioSubscriberRequestBody err error ) err = decoder(r).Decode(&body) @@ -2381,30 +2270,34 @@ func DecodeGroupsioWebhookRequest(mux goahttp.Muxer, decoder func(*http.Request) } return nil, goa.DecodePayloadError(err.Error()) } - err = ValidateGroupsioWebhookRequestBody(&body) + err = ValidateCheckGroupsioSubscriberRequestBody(&body) if err != nil { return nil, err } var ( - signature string + bearerToken *string ) - signature = r.Header.Get("x-groupsio-signature") - if signature == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("signature", "header")) + bearerTokenRaw := r.Header.Get("Authorization") + if bearerTokenRaw != "" { + bearerToken = &bearerTokenRaw } - if err != nil { - return nil, err + payload := NewCheckGroupsioSubscriberPayload(&body, bearerToken) + if payload.BearerToken != nil { + if strings.Contains(*payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(*payload.BearerToken, " ", 2)[1] + payload.BearerToken = &cred + } } - payload := NewGroupsioWebhookPayload(&body, signature) return payload, nil } } -// EncodeGroupsioWebhookError returns an encoder for errors returned by the -// groupsio-webhook mailing-list endpoint. -func EncodeGroupsioWebhookError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { +// EncodeCheckGroupsioSubscriberError returns an encoder for errors returned by +// the check-groupsio-subscriber mailing-list endpoint. +func EncodeCheckGroupsioSubscriberError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { encodeError := goahttp.ErrorEncoder(encoder, formatter) return func(ctx context.Context, w http.ResponseWriter, v error) error { var en goa.GoaErrorNamer @@ -2420,23 +2313,36 @@ func EncodeGroupsioWebhookError(encoder func(context.Context, http.ResponseWrite if formatter != nil { body = formatter(ctx, res) } else { - body = NewGroupsioWebhookBadRequestResponseBody(res) + body = NewCheckGroupsioSubscriberBadRequestResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) w.WriteHeader(http.StatusBadRequest) return enc.Encode(body) - case "Unauthorized": - var res *mailinglist.UnauthorizedError + case "InternalServerError": + var res *mailinglist.InternalServerError + errors.As(v, &res) + enc := encoder(ctx, w) + var body any + if formatter != nil { + body = formatter(ctx, res) + } else { + body = NewCheckGroupsioSubscriberInternalServerErrorResponseBody(res) + } + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusInternalServerError) + return enc.Encode(body) + case "ServiceUnavailable": + var res *mailinglist.ServiceUnavailableError errors.As(v, &res) enc := encoder(ctx, w) var body any if formatter != nil { body = formatter(ctx, res) } else { - body = NewGroupsioWebhookUnauthorizedResponseBody(res) + body = NewCheckGroupsioSubscriberServiceUnavailableResponseBody(res) } w.Header().Set("goa-error", res.GoaErrorName()) - w.WriteHeader(http.StatusUnauthorized) + w.WriteHeader(http.StatusServiceUnavailable) return enc.Encode(body) default: return encodeError(ctx, w, v) @@ -2444,73 +2350,70 @@ func EncodeGroupsioWebhookError(encoder func(context.Context, http.ResponseWrite } } -// unmarshalUserInfoRequestBodyToMailinglistUserInfo builds a value of type -// *mailinglist.UserInfo from a value of type *UserInfoRequestBody. -func unmarshalUserInfoRequestBodyToMailinglistUserInfo(v *UserInfoRequestBody) *mailinglist.UserInfo { - if v == nil { - return nil - } - res := &mailinglist.UserInfo{ - Name: v.Name, - Email: v.Email, - Username: v.Username, - Avatar: v.Avatar, - } - - return res -} - -// marshalMailinglistUserInfoToUserInfoResponseBody builds a value of type -// *UserInfoResponseBody from a value of type *mailinglist.UserInfo. -func marshalMailinglistUserInfoToUserInfoResponseBody(v *mailinglist.UserInfo) *UserInfoResponseBody { +// marshalMailinglistGroupsioServiceToGroupsioServiceResponseBody builds a +// value of type *GroupsioServiceResponseBody from a value of type +// *mailinglist.GroupsioService. +func marshalMailinglistGroupsioServiceToGroupsioServiceResponseBody(v *mailinglist.GroupsioService) *GroupsioServiceResponseBody { if v == nil { return nil } - res := &UserInfoResponseBody{ - Name: v.Name, - Email: v.Email, - Username: v.Username, - Avatar: v.Avatar, + res := &GroupsioServiceResponseBody{ + ID: v.ID, + ProjectUID: v.ProjectUID, + Type: v.Type, + GroupID: v.GroupID, + Domain: v.Domain, + Prefix: v.Prefix, + Status: v.Status, + CreatedAt: v.CreatedAt, + UpdatedAt: v.UpdatedAt, } return res } -// unmarshalCommitteeRequestBodyToMailinglistCommittee builds a value of type -// *mailinglist.Committee from a value of type *CommitteeRequestBody. -func unmarshalCommitteeRequestBodyToMailinglistCommittee(v *CommitteeRequestBody) *mailinglist.Committee { +// marshalMailinglistGroupsioSubgroupToGroupsioSubgroupResponseBody builds a +// value of type *GroupsioSubgroupResponseBody from a value of type +// *mailinglist.GroupsioSubgroup. +func marshalMailinglistGroupsioSubgroupToGroupsioSubgroupResponseBody(v *mailinglist.GroupsioSubgroup) *GroupsioSubgroupResponseBody { if v == nil { return nil } - res := &mailinglist.Committee{ - UID: *v.UID, - Name: v.Name, - } - if v.AllowedVotingStatuses != nil { - res.AllowedVotingStatuses = make([]string, len(v.AllowedVotingStatuses)) - for i, val := range v.AllowedVotingStatuses { - res.AllowedVotingStatuses[i] = val - } + res := &GroupsioSubgroupResponseBody{ + ID: v.ID, + ProjectUID: v.ProjectUID, + CommitteeUID: v.CommitteeUID, + GroupID: v.GroupID, + Name: v.Name, + Description: v.Description, + Type: v.Type, + AudienceAccess: v.AudienceAccess, + CreatedAt: v.CreatedAt, + UpdatedAt: v.UpdatedAt, } return res } -// marshalMailinglistCommitteeToCommitteeResponseBody builds a value of type -// *CommitteeResponseBody from a value of type *mailinglist.Committee. -func marshalMailinglistCommitteeToCommitteeResponseBody(v *mailinglist.Committee) *CommitteeResponseBody { +// marshalMailinglistGroupsioMemberToGroupsioMemberResponseBody builds a value +// of type *GroupsioMemberResponseBody from a value of type +// *mailinglist.GroupsioMember. +func marshalMailinglistGroupsioMemberToGroupsioMemberResponseBody(v *mailinglist.GroupsioMember) *GroupsioMemberResponseBody { if v == nil { return nil } - res := &CommitteeResponseBody{ - UID: v.UID, - Name: v.Name, - } - if v.AllowedVotingStatuses != nil { - res.AllowedVotingStatuses = make([]string, len(v.AllowedVotingStatuses)) - for i, val := range v.AllowedVotingStatuses { - res.AllowedVotingStatuses[i] = val - } + res := &GroupsioMemberResponseBody{ + ID: v.ID, + SubgroupID: v.SubgroupID, + Email: v.Email, + Name: v.Name, + FirstName: v.FirstName, + LastName: v.LastName, + ModStatus: v.ModStatus, + DeliveryMode: v.DeliveryMode, + Status: v.Status, + CreatedAt: v.CreatedAt, + UpdatedAt: v.UpdatedAt, } return res diff --git a/gen/http/mailing_list/server/paths.go b/gen/http/mailing_list/server/paths.go index f4203fc..ca808eb 100644 --- a/gen/http/mailing_list/server/paths.go +++ b/gen/http/mailing_list/server/paths.go @@ -22,87 +22,107 @@ func ReadyzMailingListPath() string { return "/readyz" } -// CreateGrpsioServiceMailingListPath returns the URL path to the mailing-list service create-grpsio-service HTTP endpoint. -func CreateGrpsioServiceMailingListPath() string { +// ListGroupsioServicesMailingListPath returns the URL path to the mailing-list service list-groupsio-services HTTP endpoint. +func ListGroupsioServicesMailingListPath() string { return "/groupsio/services" } -// GetGrpsioServiceMailingListPath returns the URL path to the mailing-list service get-grpsio-service HTTP endpoint. -func GetGrpsioServiceMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/services/%v", uid) +// CreateGroupsioServiceMailingListPath returns the URL path to the mailing-list service create-groupsio-service HTTP endpoint. +func CreateGroupsioServiceMailingListPath() string { + return "/groupsio/services" +} + +// GetGroupsioServiceMailingListPath returns the URL path to the mailing-list service get-groupsio-service HTTP endpoint. +func GetGroupsioServiceMailingListPath(serviceID string) string { + return fmt.Sprintf("/groupsio/services/%v", serviceID) +} + +// UpdateGroupsioServiceMailingListPath returns the URL path to the mailing-list service update-groupsio-service HTTP endpoint. +func UpdateGroupsioServiceMailingListPath(serviceID string) string { + return fmt.Sprintf("/groupsio/services/%v", serviceID) +} + +// DeleteGroupsioServiceMailingListPath returns the URL path to the mailing-list service delete-groupsio-service HTTP endpoint. +func DeleteGroupsioServiceMailingListPath(serviceID string) string { + return fmt.Sprintf("/groupsio/services/%v", serviceID) +} + +// GetGroupsioServiceProjectsMailingListPath returns the URL path to the mailing-list service get-groupsio-service-projects HTTP endpoint. +func GetGroupsioServiceProjectsMailingListPath() string { + return "/groupsio/services/_projects" } -// UpdateGrpsioServiceMailingListPath returns the URL path to the mailing-list service update-grpsio-service HTTP endpoint. -func UpdateGrpsioServiceMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/services/%v", uid) +// FindParentGroupsioServiceMailingListPath returns the URL path to the mailing-list service find-parent-groupsio-service HTTP endpoint. +func FindParentGroupsioServiceMailingListPath() string { + return "/groupsio/services/find_parent" } -// DeleteGrpsioServiceMailingListPath returns the URL path to the mailing-list service delete-grpsio-service HTTP endpoint. -func DeleteGrpsioServiceMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/services/%v", uid) +// ListGroupsioSubgroupsMailingListPath returns the URL path to the mailing-list service list-groupsio-subgroups HTTP endpoint. +func ListGroupsioSubgroupsMailingListPath() string { + return "/groupsio/subgroups" } -// GetGrpsioServiceSettingsMailingListPath returns the URL path to the mailing-list service get-grpsio-service-settings HTTP endpoint. -func GetGrpsioServiceSettingsMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/services/%v/settings", uid) +// CreateGroupsioSubgroupMailingListPath returns the URL path to the mailing-list service create-groupsio-subgroup HTTP endpoint. +func CreateGroupsioSubgroupMailingListPath() string { + return "/groupsio/subgroups" } -// UpdateGrpsioServiceSettingsMailingListPath returns the URL path to the mailing-list service update-grpsio-service-settings HTTP endpoint. -func UpdateGrpsioServiceSettingsMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/services/%v/settings", uid) +// GetGroupsioSubgroupMailingListPath returns the URL path to the mailing-list service get-groupsio-subgroup HTTP endpoint. +func GetGroupsioSubgroupMailingListPath(subgroupID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v", subgroupID) } -// CreateGrpsioMailingListMailingListPath returns the URL path to the mailing-list service create-grpsio-mailing-list HTTP endpoint. -func CreateGrpsioMailingListMailingListPath() string { - return "/groupsio/mailing-lists" +// UpdateGroupsioSubgroupMailingListPath returns the URL path to the mailing-list service update-groupsio-subgroup HTTP endpoint. +func UpdateGroupsioSubgroupMailingListPath(subgroupID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v", subgroupID) } -// GetGrpsioMailingListMailingListPath returns the URL path to the mailing-list service get-grpsio-mailing-list HTTP endpoint. -func GetGrpsioMailingListMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v", uid) +// DeleteGroupsioSubgroupMailingListPath returns the URL path to the mailing-list service delete-groupsio-subgroup HTTP endpoint. +func DeleteGroupsioSubgroupMailingListPath(subgroupID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v", subgroupID) } -// UpdateGrpsioMailingListMailingListPath returns the URL path to the mailing-list service update-grpsio-mailing-list HTTP endpoint. -func UpdateGrpsioMailingListMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v", uid) +// GetGroupsioSubgroupCountMailingListPath returns the URL path to the mailing-list service get-groupsio-subgroup-count HTTP endpoint. +func GetGroupsioSubgroupCountMailingListPath() string { + return "/groupsio/subgroups/count" } -// DeleteGrpsioMailingListMailingListPath returns the URL path to the mailing-list service delete-grpsio-mailing-list HTTP endpoint. -func DeleteGrpsioMailingListMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v", uid) +// GetGroupsioSubgroupMemberCountMailingListPath returns the URL path to the mailing-list service get-groupsio-subgroup-member-count HTTP endpoint. +func GetGroupsioSubgroupMemberCountMailingListPath(subgroupID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v/member_count", subgroupID) } -// GetGrpsioMailingListSettingsMailingListPath returns the URL path to the mailing-list service get-grpsio-mailing-list-settings HTTP endpoint. -func GetGrpsioMailingListSettingsMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v/settings", uid) +// ListGroupsioMembersMailingListPath returns the URL path to the mailing-list service list-groupsio-members HTTP endpoint. +func ListGroupsioMembersMailingListPath(subgroupID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v/members", subgroupID) } -// UpdateGrpsioMailingListSettingsMailingListPath returns the URL path to the mailing-list service update-grpsio-mailing-list-settings HTTP endpoint. -func UpdateGrpsioMailingListSettingsMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v/settings", uid) +// AddGroupsioMemberMailingListPath returns the URL path to the mailing-list service add-groupsio-member HTTP endpoint. +func AddGroupsioMemberMailingListPath(subgroupID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v/members", subgroupID) } -// CreateGrpsioMailingListMemberMailingListPath returns the URL path to the mailing-list service create-grpsio-mailing-list-member HTTP endpoint. -func CreateGrpsioMailingListMemberMailingListPath(uid string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v/members", uid) +// GetGroupsioMemberMailingListPath returns the URL path to the mailing-list service get-groupsio-member HTTP endpoint. +func GetGroupsioMemberMailingListPath(subgroupID string, memberID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v/members/%v", subgroupID, memberID) } -// GetGrpsioMailingListMemberMailingListPath returns the URL path to the mailing-list service get-grpsio-mailing-list-member HTTP endpoint. -func GetGrpsioMailingListMemberMailingListPath(uid string, memberUID string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v/members/%v", uid, memberUID) +// UpdateGroupsioMemberMailingListPath returns the URL path to the mailing-list service update-groupsio-member HTTP endpoint. +func UpdateGroupsioMemberMailingListPath(subgroupID string, memberID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v/members/%v", subgroupID, memberID) } -// UpdateGrpsioMailingListMemberMailingListPath returns the URL path to the mailing-list service update-grpsio-mailing-list-member HTTP endpoint. -func UpdateGrpsioMailingListMemberMailingListPath(uid string, memberUID string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v/members/%v", uid, memberUID) +// DeleteGroupsioMemberMailingListPath returns the URL path to the mailing-list service delete-groupsio-member HTTP endpoint. +func DeleteGroupsioMemberMailingListPath(subgroupID string, memberID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v/members/%v", subgroupID, memberID) } -// DeleteGrpsioMailingListMemberMailingListPath returns the URL path to the mailing-list service delete-grpsio-mailing-list-member HTTP endpoint. -func DeleteGrpsioMailingListMemberMailingListPath(uid string, memberUID string) string { - return fmt.Sprintf("/groupsio/mailing-lists/%v/members/%v", uid, memberUID) +// InviteGroupsioMembersMailingListPath returns the URL path to the mailing-list service invite-groupsio-members HTTP endpoint. +func InviteGroupsioMembersMailingListPath(subgroupID string) string { + return fmt.Sprintf("/groupsio/subgroups/%v/invitemembers", subgroupID) } -// GroupsioWebhookMailingListPath returns the URL path to the mailing-list service groupsio-webhook HTTP endpoint. -func GroupsioWebhookMailingListPath() string { - return "/webhooks/groupsio" +// CheckGroupsioSubscriberMailingListPath returns the URL path to the mailing-list service check-groupsio-subscriber HTTP endpoint. +func CheckGroupsioSubscriberMailingListPath() string { + return "/groupsio/checksubscriber" } diff --git a/gen/http/mailing_list/server/server.go b/gen/http/mailing_list/server/server.go index b92ef3e..e0ece6d 100644 --- a/gen/http/mailing_list/server/server.go +++ b/gen/http/mailing_list/server/server.go @@ -20,27 +20,31 @@ import ( // Server lists the mailing-list service endpoint HTTP handlers. type Server struct { - Mounts []*MountPoint - Livez http.Handler - Readyz http.Handler - CreateGrpsioService http.Handler - GetGrpsioService http.Handler - UpdateGrpsioService http.Handler - DeleteGrpsioService http.Handler - GetGrpsioServiceSettings http.Handler - UpdateGrpsioServiceSettings http.Handler - CreateGrpsioMailingList http.Handler - GetGrpsioMailingList http.Handler - UpdateGrpsioMailingList http.Handler - DeleteGrpsioMailingList http.Handler - GetGrpsioMailingListSettings http.Handler - UpdateGrpsioMailingListSettings http.Handler - CreateGrpsioMailingListMember http.Handler - GetGrpsioMailingListMember http.Handler - UpdateGrpsioMailingListMember http.Handler - DeleteGrpsioMailingListMember http.Handler - GroupsioWebhook http.Handler - GenHTTPOpenapi3JSON http.Handler + Mounts []*MountPoint + Livez http.Handler + Readyz http.Handler + ListGroupsioServices http.Handler + CreateGroupsioService http.Handler + GetGroupsioService http.Handler + UpdateGroupsioService http.Handler + DeleteGroupsioService http.Handler + GetGroupsioServiceProjects http.Handler + FindParentGroupsioService http.Handler + ListGroupsioSubgroups http.Handler + CreateGroupsioSubgroup http.Handler + GetGroupsioSubgroup http.Handler + UpdateGroupsioSubgroup http.Handler + DeleteGroupsioSubgroup http.Handler + GetGroupsioSubgroupCount http.Handler + GetGroupsioSubgroupMemberCount http.Handler + ListGroupsioMembers http.Handler + AddGroupsioMember http.Handler + GetGroupsioMember http.Handler + UpdateGroupsioMember http.Handler + DeleteGroupsioMember http.Handler + InviteGroupsioMembers http.Handler + CheckGroupsioSubscriber http.Handler + GenHTTPOpenapi3JSON http.Handler } // MountPoint holds information about the mounted endpoints. @@ -77,45 +81,53 @@ func New( Mounts: []*MountPoint{ {"Livez", "GET", "/livez"}, {"Readyz", "GET", "/readyz"}, - {"CreateGrpsioService", "POST", "/groupsio/services"}, - {"GetGrpsioService", "GET", "/groupsio/services/{uid}"}, - {"UpdateGrpsioService", "PUT", "/groupsio/services/{uid}"}, - {"DeleteGrpsioService", "DELETE", "/groupsio/services/{uid}"}, - {"GetGrpsioServiceSettings", "GET", "/groupsio/services/{uid}/settings"}, - {"UpdateGrpsioServiceSettings", "PUT", "/groupsio/services/{uid}/settings"}, - {"CreateGrpsioMailingList", "POST", "/groupsio/mailing-lists"}, - {"GetGrpsioMailingList", "GET", "/groupsio/mailing-lists/{uid}"}, - {"UpdateGrpsioMailingList", "PUT", "/groupsio/mailing-lists/{uid}"}, - {"DeleteGrpsioMailingList", "DELETE", "/groupsio/mailing-lists/{uid}"}, - {"GetGrpsioMailingListSettings", "GET", "/groupsio/mailing-lists/{uid}/settings"}, - {"UpdateGrpsioMailingListSettings", "PUT", "/groupsio/mailing-lists/{uid}/settings"}, - {"CreateGrpsioMailingListMember", "POST", "/groupsio/mailing-lists/{uid}/members"}, - {"GetGrpsioMailingListMember", "GET", "/groupsio/mailing-lists/{uid}/members/{member_uid}"}, - {"UpdateGrpsioMailingListMember", "PUT", "/groupsio/mailing-lists/{uid}/members/{member_uid}"}, - {"DeleteGrpsioMailingListMember", "DELETE", "/groupsio/mailing-lists/{uid}/members/{member_uid}"}, - {"GroupsioWebhook", "POST", "/webhooks/groupsio"}, + {"ListGroupsioServices", "GET", "/groupsio/services"}, + {"CreateGroupsioService", "POST", "/groupsio/services"}, + {"GetGroupsioService", "GET", "/groupsio/services/{service_id}"}, + {"UpdateGroupsioService", "PUT", "/groupsio/services/{service_id}"}, + {"DeleteGroupsioService", "DELETE", "/groupsio/services/{service_id}"}, + {"GetGroupsioServiceProjects", "GET", "/groupsio/services/_projects"}, + {"FindParentGroupsioService", "GET", "/groupsio/services/find_parent"}, + {"ListGroupsioSubgroups", "GET", "/groupsio/subgroups"}, + {"CreateGroupsioSubgroup", "POST", "/groupsio/subgroups"}, + {"GetGroupsioSubgroup", "GET", "/groupsio/subgroups/{subgroup_id}"}, + {"UpdateGroupsioSubgroup", "PUT", "/groupsio/subgroups/{subgroup_id}"}, + {"DeleteGroupsioSubgroup", "DELETE", "/groupsio/subgroups/{subgroup_id}"}, + {"GetGroupsioSubgroupCount", "GET", "/groupsio/subgroups/count"}, + {"GetGroupsioSubgroupMemberCount", "GET", "/groupsio/subgroups/{subgroup_id}/member_count"}, + {"ListGroupsioMembers", "GET", "/groupsio/subgroups/{subgroup_id}/members"}, + {"AddGroupsioMember", "POST", "/groupsio/subgroups/{subgroup_id}/members"}, + {"GetGroupsioMember", "GET", "/groupsio/subgroups/{subgroup_id}/members/{member_id}"}, + {"UpdateGroupsioMember", "PUT", "/groupsio/subgroups/{subgroup_id}/members/{member_id}"}, + {"DeleteGroupsioMember", "DELETE", "/groupsio/subgroups/{subgroup_id}/members/{member_id}"}, + {"InviteGroupsioMembers", "POST", "/groupsio/subgroups/{subgroup_id}/invitemembers"}, + {"CheckGroupsioSubscriber", "POST", "/groupsio/checksubscriber"}, {"Serve gen/http/openapi3.json", "GET", "/openapi.json"}, }, - Livez: NewLivezHandler(e.Livez, mux, decoder, encoder, errhandler, formatter), - Readyz: NewReadyzHandler(e.Readyz, mux, decoder, encoder, errhandler, formatter), - CreateGrpsioService: NewCreateGrpsioServiceHandler(e.CreateGrpsioService, mux, decoder, encoder, errhandler, formatter), - GetGrpsioService: NewGetGrpsioServiceHandler(e.GetGrpsioService, mux, decoder, encoder, errhandler, formatter), - UpdateGrpsioService: NewUpdateGrpsioServiceHandler(e.UpdateGrpsioService, mux, decoder, encoder, errhandler, formatter), - DeleteGrpsioService: NewDeleteGrpsioServiceHandler(e.DeleteGrpsioService, mux, decoder, encoder, errhandler, formatter), - GetGrpsioServiceSettings: NewGetGrpsioServiceSettingsHandler(e.GetGrpsioServiceSettings, mux, decoder, encoder, errhandler, formatter), - UpdateGrpsioServiceSettings: NewUpdateGrpsioServiceSettingsHandler(e.UpdateGrpsioServiceSettings, mux, decoder, encoder, errhandler, formatter), - CreateGrpsioMailingList: NewCreateGrpsioMailingListHandler(e.CreateGrpsioMailingList, mux, decoder, encoder, errhandler, formatter), - GetGrpsioMailingList: NewGetGrpsioMailingListHandler(e.GetGrpsioMailingList, mux, decoder, encoder, errhandler, formatter), - UpdateGrpsioMailingList: NewUpdateGrpsioMailingListHandler(e.UpdateGrpsioMailingList, mux, decoder, encoder, errhandler, formatter), - DeleteGrpsioMailingList: NewDeleteGrpsioMailingListHandler(e.DeleteGrpsioMailingList, mux, decoder, encoder, errhandler, formatter), - GetGrpsioMailingListSettings: NewGetGrpsioMailingListSettingsHandler(e.GetGrpsioMailingListSettings, mux, decoder, encoder, errhandler, formatter), - UpdateGrpsioMailingListSettings: NewUpdateGrpsioMailingListSettingsHandler(e.UpdateGrpsioMailingListSettings, mux, decoder, encoder, errhandler, formatter), - CreateGrpsioMailingListMember: NewCreateGrpsioMailingListMemberHandler(e.CreateGrpsioMailingListMember, mux, decoder, encoder, errhandler, formatter), - GetGrpsioMailingListMember: NewGetGrpsioMailingListMemberHandler(e.GetGrpsioMailingListMember, mux, decoder, encoder, errhandler, formatter), - UpdateGrpsioMailingListMember: NewUpdateGrpsioMailingListMemberHandler(e.UpdateGrpsioMailingListMember, mux, decoder, encoder, errhandler, formatter), - DeleteGrpsioMailingListMember: NewDeleteGrpsioMailingListMemberHandler(e.DeleteGrpsioMailingListMember, mux, decoder, encoder, errhandler, formatter), - GroupsioWebhook: NewGroupsioWebhookHandler(e.GroupsioWebhook, mux, decoder, encoder, errhandler, formatter), - GenHTTPOpenapi3JSON: http.FileServer(fileSystemGenHTTPOpenapi3JSON), + Livez: NewLivezHandler(e.Livez, mux, decoder, encoder, errhandler, formatter), + Readyz: NewReadyzHandler(e.Readyz, mux, decoder, encoder, errhandler, formatter), + ListGroupsioServices: NewListGroupsioServicesHandler(e.ListGroupsioServices, mux, decoder, encoder, errhandler, formatter), + CreateGroupsioService: NewCreateGroupsioServiceHandler(e.CreateGroupsioService, mux, decoder, encoder, errhandler, formatter), + GetGroupsioService: NewGetGroupsioServiceHandler(e.GetGroupsioService, mux, decoder, encoder, errhandler, formatter), + UpdateGroupsioService: NewUpdateGroupsioServiceHandler(e.UpdateGroupsioService, mux, decoder, encoder, errhandler, formatter), + DeleteGroupsioService: NewDeleteGroupsioServiceHandler(e.DeleteGroupsioService, mux, decoder, encoder, errhandler, formatter), + GetGroupsioServiceProjects: NewGetGroupsioServiceProjectsHandler(e.GetGroupsioServiceProjects, mux, decoder, encoder, errhandler, formatter), + FindParentGroupsioService: NewFindParentGroupsioServiceHandler(e.FindParentGroupsioService, mux, decoder, encoder, errhandler, formatter), + ListGroupsioSubgroups: NewListGroupsioSubgroupsHandler(e.ListGroupsioSubgroups, mux, decoder, encoder, errhandler, formatter), + CreateGroupsioSubgroup: NewCreateGroupsioSubgroupHandler(e.CreateGroupsioSubgroup, mux, decoder, encoder, errhandler, formatter), + GetGroupsioSubgroup: NewGetGroupsioSubgroupHandler(e.GetGroupsioSubgroup, mux, decoder, encoder, errhandler, formatter), + UpdateGroupsioSubgroup: NewUpdateGroupsioSubgroupHandler(e.UpdateGroupsioSubgroup, mux, decoder, encoder, errhandler, formatter), + DeleteGroupsioSubgroup: NewDeleteGroupsioSubgroupHandler(e.DeleteGroupsioSubgroup, mux, decoder, encoder, errhandler, formatter), + GetGroupsioSubgroupCount: NewGetGroupsioSubgroupCountHandler(e.GetGroupsioSubgroupCount, mux, decoder, encoder, errhandler, formatter), + GetGroupsioSubgroupMemberCount: NewGetGroupsioSubgroupMemberCountHandler(e.GetGroupsioSubgroupMemberCount, mux, decoder, encoder, errhandler, formatter), + ListGroupsioMembers: NewListGroupsioMembersHandler(e.ListGroupsioMembers, mux, decoder, encoder, errhandler, formatter), + AddGroupsioMember: NewAddGroupsioMemberHandler(e.AddGroupsioMember, mux, decoder, encoder, errhandler, formatter), + GetGroupsioMember: NewGetGroupsioMemberHandler(e.GetGroupsioMember, mux, decoder, encoder, errhandler, formatter), + UpdateGroupsioMember: NewUpdateGroupsioMemberHandler(e.UpdateGroupsioMember, mux, decoder, encoder, errhandler, formatter), + DeleteGroupsioMember: NewDeleteGroupsioMemberHandler(e.DeleteGroupsioMember, mux, decoder, encoder, errhandler, formatter), + InviteGroupsioMembers: NewInviteGroupsioMembersHandler(e.InviteGroupsioMembers, mux, decoder, encoder, errhandler, formatter), + CheckGroupsioSubscriber: NewCheckGroupsioSubscriberHandler(e.CheckGroupsioSubscriber, mux, decoder, encoder, errhandler, formatter), + GenHTTPOpenapi3JSON: http.FileServer(fileSystemGenHTTPOpenapi3JSON), } } @@ -126,23 +138,27 @@ func (s *Server) Service() string { return "mailing-list" } func (s *Server) Use(m func(http.Handler) http.Handler) { s.Livez = m(s.Livez) s.Readyz = m(s.Readyz) - s.CreateGrpsioService = m(s.CreateGrpsioService) - s.GetGrpsioService = m(s.GetGrpsioService) - s.UpdateGrpsioService = m(s.UpdateGrpsioService) - s.DeleteGrpsioService = m(s.DeleteGrpsioService) - s.GetGrpsioServiceSettings = m(s.GetGrpsioServiceSettings) - s.UpdateGrpsioServiceSettings = m(s.UpdateGrpsioServiceSettings) - s.CreateGrpsioMailingList = m(s.CreateGrpsioMailingList) - s.GetGrpsioMailingList = m(s.GetGrpsioMailingList) - s.UpdateGrpsioMailingList = m(s.UpdateGrpsioMailingList) - s.DeleteGrpsioMailingList = m(s.DeleteGrpsioMailingList) - s.GetGrpsioMailingListSettings = m(s.GetGrpsioMailingListSettings) - s.UpdateGrpsioMailingListSettings = m(s.UpdateGrpsioMailingListSettings) - s.CreateGrpsioMailingListMember = m(s.CreateGrpsioMailingListMember) - s.GetGrpsioMailingListMember = m(s.GetGrpsioMailingListMember) - s.UpdateGrpsioMailingListMember = m(s.UpdateGrpsioMailingListMember) - s.DeleteGrpsioMailingListMember = m(s.DeleteGrpsioMailingListMember) - s.GroupsioWebhook = m(s.GroupsioWebhook) + s.ListGroupsioServices = m(s.ListGroupsioServices) + s.CreateGroupsioService = m(s.CreateGroupsioService) + s.GetGroupsioService = m(s.GetGroupsioService) + s.UpdateGroupsioService = m(s.UpdateGroupsioService) + s.DeleteGroupsioService = m(s.DeleteGroupsioService) + s.GetGroupsioServiceProjects = m(s.GetGroupsioServiceProjects) + s.FindParentGroupsioService = m(s.FindParentGroupsioService) + s.ListGroupsioSubgroups = m(s.ListGroupsioSubgroups) + s.CreateGroupsioSubgroup = m(s.CreateGroupsioSubgroup) + s.GetGroupsioSubgroup = m(s.GetGroupsioSubgroup) + s.UpdateGroupsioSubgroup = m(s.UpdateGroupsioSubgroup) + s.DeleteGroupsioSubgroup = m(s.DeleteGroupsioSubgroup) + s.GetGroupsioSubgroupCount = m(s.GetGroupsioSubgroupCount) + s.GetGroupsioSubgroupMemberCount = m(s.GetGroupsioSubgroupMemberCount) + s.ListGroupsioMembers = m(s.ListGroupsioMembers) + s.AddGroupsioMember = m(s.AddGroupsioMember) + s.GetGroupsioMember = m(s.GetGroupsioMember) + s.UpdateGroupsioMember = m(s.UpdateGroupsioMember) + s.DeleteGroupsioMember = m(s.DeleteGroupsioMember) + s.InviteGroupsioMembers = m(s.InviteGroupsioMembers) + s.CheckGroupsioSubscriber = m(s.CheckGroupsioSubscriber) } // MethodNames returns the methods served. @@ -152,23 +168,27 @@ func (s *Server) MethodNames() []string { return mailinglist.MethodNames[:] } func Mount(mux goahttp.Muxer, h *Server) { MountLivezHandler(mux, h.Livez) MountReadyzHandler(mux, h.Readyz) - MountCreateGrpsioServiceHandler(mux, h.CreateGrpsioService) - MountGetGrpsioServiceHandler(mux, h.GetGrpsioService) - MountUpdateGrpsioServiceHandler(mux, h.UpdateGrpsioService) - MountDeleteGrpsioServiceHandler(mux, h.DeleteGrpsioService) - MountGetGrpsioServiceSettingsHandler(mux, h.GetGrpsioServiceSettings) - MountUpdateGrpsioServiceSettingsHandler(mux, h.UpdateGrpsioServiceSettings) - MountCreateGrpsioMailingListHandler(mux, h.CreateGrpsioMailingList) - MountGetGrpsioMailingListHandler(mux, h.GetGrpsioMailingList) - MountUpdateGrpsioMailingListHandler(mux, h.UpdateGrpsioMailingList) - MountDeleteGrpsioMailingListHandler(mux, h.DeleteGrpsioMailingList) - MountGetGrpsioMailingListSettingsHandler(mux, h.GetGrpsioMailingListSettings) - MountUpdateGrpsioMailingListSettingsHandler(mux, h.UpdateGrpsioMailingListSettings) - MountCreateGrpsioMailingListMemberHandler(mux, h.CreateGrpsioMailingListMember) - MountGetGrpsioMailingListMemberHandler(mux, h.GetGrpsioMailingListMember) - MountUpdateGrpsioMailingListMemberHandler(mux, h.UpdateGrpsioMailingListMember) - MountDeleteGrpsioMailingListMemberHandler(mux, h.DeleteGrpsioMailingListMember) - MountGroupsioWebhookHandler(mux, h.GroupsioWebhook) + MountListGroupsioServicesHandler(mux, h.ListGroupsioServices) + MountCreateGroupsioServiceHandler(mux, h.CreateGroupsioService) + MountGetGroupsioServiceHandler(mux, h.GetGroupsioService) + MountUpdateGroupsioServiceHandler(mux, h.UpdateGroupsioService) + MountDeleteGroupsioServiceHandler(mux, h.DeleteGroupsioService) + MountGetGroupsioServiceProjectsHandler(mux, h.GetGroupsioServiceProjects) + MountFindParentGroupsioServiceHandler(mux, h.FindParentGroupsioService) + MountListGroupsioSubgroupsHandler(mux, h.ListGroupsioSubgroups) + MountCreateGroupsioSubgroupHandler(mux, h.CreateGroupsioSubgroup) + MountGetGroupsioSubgroupHandler(mux, h.GetGroupsioSubgroup) + MountUpdateGroupsioSubgroupHandler(mux, h.UpdateGroupsioSubgroup) + MountDeleteGroupsioSubgroupHandler(mux, h.DeleteGroupsioSubgroup) + MountGetGroupsioSubgroupCountHandler(mux, h.GetGroupsioSubgroupCount) + MountGetGroupsioSubgroupMemberCountHandler(mux, h.GetGroupsioSubgroupMemberCount) + MountListGroupsioMembersHandler(mux, h.ListGroupsioMembers) + MountAddGroupsioMemberHandler(mux, h.AddGroupsioMember) + MountGetGroupsioMemberHandler(mux, h.GetGroupsioMember) + MountUpdateGroupsioMemberHandler(mux, h.UpdateGroupsioMember) + MountDeleteGroupsioMemberHandler(mux, h.DeleteGroupsioMember) + MountInviteGroupsioMembersHandler(mux, h.InviteGroupsioMembers) + MountCheckGroupsioSubscriberHandler(mux, h.CheckGroupsioSubscriber) MountGenHTTPOpenapi3JSON(mux, h.GenHTTPOpenapi3JSON) } @@ -269,9 +289,63 @@ func NewReadyzHandler( }) } -// MountCreateGrpsioServiceHandler configures the mux to serve the -// "mailing-list" service "create-grpsio-service" endpoint. -func MountCreateGrpsioServiceHandler(mux goahttp.Muxer, h http.Handler) { +// MountListGroupsioServicesHandler configures the mux to serve the +// "mailing-list" service "list-groupsio-services" endpoint. +func MountListGroupsioServicesHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("GET", "/groupsio/services", f) +} + +// NewListGroupsioServicesHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "list-groupsio-services" +// endpoint. +func NewListGroupsioServicesHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeListGroupsioServicesRequest(mux, decoder) + encodeResponse = EncodeListGroupsioServicesResponse(encoder) + encodeError = EncodeListGroupsioServicesError(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "list-groupsio-services") + ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} + +// MountCreateGroupsioServiceHandler configures the mux to serve the +// "mailing-list" service "create-groupsio-service" endpoint. +func MountCreateGroupsioServiceHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { @@ -281,10 +355,10 @@ func MountCreateGrpsioServiceHandler(mux goahttp.Muxer, h http.Handler) { mux.Handle("POST", "/groupsio/services", f) } -// NewCreateGrpsioServiceHandler creates a HTTP handler which loads the HTTP -// request and calls the "mailing-list" service "create-grpsio-service" +// NewCreateGroupsioServiceHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "create-groupsio-service" // endpoint. -func NewCreateGrpsioServiceHandler( +func NewCreateGroupsioServiceHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -293,13 +367,13 @@ func NewCreateGrpsioServiceHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeCreateGrpsioServiceRequest(mux, decoder) - encodeResponse = EncodeCreateGrpsioServiceResponse(encoder) - encodeError = EncodeCreateGrpsioServiceError(encoder, formatter) + decodeRequest = DecodeCreateGroupsioServiceRequest(mux, decoder) + encodeResponse = EncodeCreateGroupsioServiceResponse(encoder) + encodeError = EncodeCreateGroupsioServiceError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "create-grpsio-service") + ctx = context.WithValue(ctx, goa.MethodKey, "create-groupsio-service") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -323,21 +397,21 @@ func NewCreateGrpsioServiceHandler( }) } -// MountGetGrpsioServiceHandler configures the mux to serve the "mailing-list" -// service "get-grpsio-service" endpoint. -func MountGetGrpsioServiceHandler(mux goahttp.Muxer, h http.Handler) { +// MountGetGroupsioServiceHandler configures the mux to serve the +// "mailing-list" service "get-groupsio-service" endpoint. +func MountGetGroupsioServiceHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("GET", "/groupsio/services/{uid}", f) + mux.Handle("GET", "/groupsio/services/{service_id}", f) } -// NewGetGrpsioServiceHandler creates a HTTP handler which loads the HTTP -// request and calls the "mailing-list" service "get-grpsio-service" endpoint. -func NewGetGrpsioServiceHandler( +// NewGetGroupsioServiceHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "get-groupsio-service" endpoint. +func NewGetGroupsioServiceHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -346,13 +420,13 @@ func NewGetGrpsioServiceHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeGetGrpsioServiceRequest(mux, decoder) - encodeResponse = EncodeGetGrpsioServiceResponse(encoder) - encodeError = EncodeGetGrpsioServiceError(encoder, formatter) + decodeRequest = DecodeGetGroupsioServiceRequest(mux, decoder) + encodeResponse = EncodeGetGroupsioServiceResponse(encoder) + encodeError = EncodeGetGroupsioServiceError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "get-grpsio-service") + ctx = context.WithValue(ctx, goa.MethodKey, "get-groupsio-service") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -376,22 +450,22 @@ func NewGetGrpsioServiceHandler( }) } -// MountUpdateGrpsioServiceHandler configures the mux to serve the -// "mailing-list" service "update-grpsio-service" endpoint. -func MountUpdateGrpsioServiceHandler(mux goahttp.Muxer, h http.Handler) { +// MountUpdateGroupsioServiceHandler configures the mux to serve the +// "mailing-list" service "update-groupsio-service" endpoint. +func MountUpdateGroupsioServiceHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("PUT", "/groupsio/services/{uid}", f) + mux.Handle("PUT", "/groupsio/services/{service_id}", f) } -// NewUpdateGrpsioServiceHandler creates a HTTP handler which loads the HTTP -// request and calls the "mailing-list" service "update-grpsio-service" +// NewUpdateGroupsioServiceHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "update-groupsio-service" // endpoint. -func NewUpdateGrpsioServiceHandler( +func NewUpdateGroupsioServiceHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -400,13 +474,13 @@ func NewUpdateGrpsioServiceHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeUpdateGrpsioServiceRequest(mux, decoder) - encodeResponse = EncodeUpdateGrpsioServiceResponse(encoder) - encodeError = EncodeUpdateGrpsioServiceError(encoder, formatter) + decodeRequest = DecodeUpdateGroupsioServiceRequest(mux, decoder) + encodeResponse = EncodeUpdateGroupsioServiceResponse(encoder) + encodeError = EncodeUpdateGroupsioServiceError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "update-grpsio-service") + ctx = context.WithValue(ctx, goa.MethodKey, "update-groupsio-service") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -430,22 +504,22 @@ func NewUpdateGrpsioServiceHandler( }) } -// MountDeleteGrpsioServiceHandler configures the mux to serve the -// "mailing-list" service "delete-grpsio-service" endpoint. -func MountDeleteGrpsioServiceHandler(mux goahttp.Muxer, h http.Handler) { +// MountDeleteGroupsioServiceHandler configures the mux to serve the +// "mailing-list" service "delete-groupsio-service" endpoint. +func MountDeleteGroupsioServiceHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("DELETE", "/groupsio/services/{uid}", f) + mux.Handle("DELETE", "/groupsio/services/{service_id}", f) } -// NewDeleteGrpsioServiceHandler creates a HTTP handler which loads the HTTP -// request and calls the "mailing-list" service "delete-grpsio-service" +// NewDeleteGroupsioServiceHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "delete-groupsio-service" // endpoint. -func NewDeleteGrpsioServiceHandler( +func NewDeleteGroupsioServiceHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -454,13 +528,13 @@ func NewDeleteGrpsioServiceHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeDeleteGrpsioServiceRequest(mux, decoder) - encodeResponse = EncodeDeleteGrpsioServiceResponse(encoder) - encodeError = EncodeDeleteGrpsioServiceError(encoder, formatter) + decodeRequest = DecodeDeleteGroupsioServiceRequest(mux, decoder) + encodeResponse = EncodeDeleteGroupsioServiceResponse(encoder) + encodeError = EncodeDeleteGroupsioServiceError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "delete-grpsio-service") + ctx = context.WithValue(ctx, goa.MethodKey, "delete-groupsio-service") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -484,22 +558,22 @@ func NewDeleteGrpsioServiceHandler( }) } -// MountGetGrpsioServiceSettingsHandler configures the mux to serve the -// "mailing-list" service "get-grpsio-service-settings" endpoint. -func MountGetGrpsioServiceSettingsHandler(mux goahttp.Muxer, h http.Handler) { +// MountGetGroupsioServiceProjectsHandler configures the mux to serve the +// "mailing-list" service "get-groupsio-service-projects" endpoint. +func MountGetGroupsioServiceProjectsHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("GET", "/groupsio/services/{uid}/settings", f) + mux.Handle("GET", "/groupsio/services/_projects", f) } -// NewGetGrpsioServiceSettingsHandler creates a HTTP handler which loads the +// NewGetGroupsioServiceProjectsHandler creates a HTTP handler which loads the // HTTP request and calls the "mailing-list" service -// "get-grpsio-service-settings" endpoint. -func NewGetGrpsioServiceSettingsHandler( +// "get-groupsio-service-projects" endpoint. +func NewGetGroupsioServiceProjectsHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -508,13 +582,13 @@ func NewGetGrpsioServiceSettingsHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeGetGrpsioServiceSettingsRequest(mux, decoder) - encodeResponse = EncodeGetGrpsioServiceSettingsResponse(encoder) - encodeError = EncodeGetGrpsioServiceSettingsError(encoder, formatter) + decodeRequest = DecodeGetGroupsioServiceProjectsRequest(mux, decoder) + encodeResponse = EncodeGetGroupsioServiceProjectsResponse(encoder) + encodeError = EncodeGetGroupsioServiceProjectsError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "get-grpsio-service-settings") + ctx = context.WithValue(ctx, goa.MethodKey, "get-groupsio-service-projects") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -538,22 +612,22 @@ func NewGetGrpsioServiceSettingsHandler( }) } -// MountUpdateGrpsioServiceSettingsHandler configures the mux to serve the -// "mailing-list" service "update-grpsio-service-settings" endpoint. -func MountUpdateGrpsioServiceSettingsHandler(mux goahttp.Muxer, h http.Handler) { +// MountFindParentGroupsioServiceHandler configures the mux to serve the +// "mailing-list" service "find-parent-groupsio-service" endpoint. +func MountFindParentGroupsioServiceHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("PUT", "/groupsio/services/{uid}/settings", f) + mux.Handle("GET", "/groupsio/services/find_parent", f) } -// NewUpdateGrpsioServiceSettingsHandler creates a HTTP handler which loads the +// NewFindParentGroupsioServiceHandler creates a HTTP handler which loads the // HTTP request and calls the "mailing-list" service -// "update-grpsio-service-settings" endpoint. -func NewUpdateGrpsioServiceSettingsHandler( +// "find-parent-groupsio-service" endpoint. +func NewFindParentGroupsioServiceHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -562,13 +636,13 @@ func NewUpdateGrpsioServiceSettingsHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeUpdateGrpsioServiceSettingsRequest(mux, decoder) - encodeResponse = EncodeUpdateGrpsioServiceSettingsResponse(encoder) - encodeError = EncodeUpdateGrpsioServiceSettingsError(encoder, formatter) + decodeRequest = DecodeFindParentGroupsioServiceRequest(mux, decoder) + encodeResponse = EncodeFindParentGroupsioServiceResponse(encoder) + encodeError = EncodeFindParentGroupsioServiceError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "update-grpsio-service-settings") + ctx = context.WithValue(ctx, goa.MethodKey, "find-parent-groupsio-service") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -592,22 +666,22 @@ func NewUpdateGrpsioServiceSettingsHandler( }) } -// MountCreateGrpsioMailingListHandler configures the mux to serve the -// "mailing-list" service "create-grpsio-mailing-list" endpoint. -func MountCreateGrpsioMailingListHandler(mux goahttp.Muxer, h http.Handler) { +// MountListGroupsioSubgroupsHandler configures the mux to serve the +// "mailing-list" service "list-groupsio-subgroups" endpoint. +func MountListGroupsioSubgroupsHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("POST", "/groupsio/mailing-lists", f) + mux.Handle("GET", "/groupsio/subgroups", f) } -// NewCreateGrpsioMailingListHandler creates a HTTP handler which loads the -// HTTP request and calls the "mailing-list" service -// "create-grpsio-mailing-list" endpoint. -func NewCreateGrpsioMailingListHandler( +// NewListGroupsioSubgroupsHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "list-groupsio-subgroups" +// endpoint. +func NewListGroupsioSubgroupsHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -616,13 +690,13 @@ func NewCreateGrpsioMailingListHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeCreateGrpsioMailingListRequest(mux, decoder) - encodeResponse = EncodeCreateGrpsioMailingListResponse(encoder) - encodeError = EncodeCreateGrpsioMailingListError(encoder, formatter) + decodeRequest = DecodeListGroupsioSubgroupsRequest(mux, decoder) + encodeResponse = EncodeListGroupsioSubgroupsResponse(encoder) + encodeError = EncodeListGroupsioSubgroupsError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "create-grpsio-mailing-list") + ctx = context.WithValue(ctx, goa.MethodKey, "list-groupsio-subgroups") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -646,22 +720,22 @@ func NewCreateGrpsioMailingListHandler( }) } -// MountGetGrpsioMailingListHandler configures the mux to serve the -// "mailing-list" service "get-grpsio-mailing-list" endpoint. -func MountGetGrpsioMailingListHandler(mux goahttp.Muxer, h http.Handler) { +// MountCreateGroupsioSubgroupHandler configures the mux to serve the +// "mailing-list" service "create-groupsio-subgroup" endpoint. +func MountCreateGroupsioSubgroupHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("GET", "/groupsio/mailing-lists/{uid}", f) + mux.Handle("POST", "/groupsio/subgroups", f) } -// NewGetGrpsioMailingListHandler creates a HTTP handler which loads the HTTP -// request and calls the "mailing-list" service "get-grpsio-mailing-list" +// NewCreateGroupsioSubgroupHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "create-groupsio-subgroup" // endpoint. -func NewGetGrpsioMailingListHandler( +func NewCreateGroupsioSubgroupHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -670,13 +744,13 @@ func NewGetGrpsioMailingListHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeGetGrpsioMailingListRequest(mux, decoder) - encodeResponse = EncodeGetGrpsioMailingListResponse(encoder) - encodeError = EncodeGetGrpsioMailingListError(encoder, formatter) + decodeRequest = DecodeCreateGroupsioSubgroupRequest(mux, decoder) + encodeResponse = EncodeCreateGroupsioSubgroupResponse(encoder) + encodeError = EncodeCreateGroupsioSubgroupError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "get-grpsio-mailing-list") + ctx = context.WithValue(ctx, goa.MethodKey, "create-groupsio-subgroup") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -700,22 +774,22 @@ func NewGetGrpsioMailingListHandler( }) } -// MountUpdateGrpsioMailingListHandler configures the mux to serve the -// "mailing-list" service "update-grpsio-mailing-list" endpoint. -func MountUpdateGrpsioMailingListHandler(mux goahttp.Muxer, h http.Handler) { +// MountGetGroupsioSubgroupHandler configures the mux to serve the +// "mailing-list" service "get-groupsio-subgroup" endpoint. +func MountGetGroupsioSubgroupHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("PUT", "/groupsio/mailing-lists/{uid}", f) + mux.Handle("GET", "/groupsio/subgroups/{subgroup_id}", f) } -// NewUpdateGrpsioMailingListHandler creates a HTTP handler which loads the -// HTTP request and calls the "mailing-list" service -// "update-grpsio-mailing-list" endpoint. -func NewUpdateGrpsioMailingListHandler( +// NewGetGroupsioSubgroupHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "get-groupsio-subgroup" +// endpoint. +func NewGetGroupsioSubgroupHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -724,13 +798,13 @@ func NewUpdateGrpsioMailingListHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeUpdateGrpsioMailingListRequest(mux, decoder) - encodeResponse = EncodeUpdateGrpsioMailingListResponse(encoder) - encodeError = EncodeUpdateGrpsioMailingListError(encoder, formatter) + decodeRequest = DecodeGetGroupsioSubgroupRequest(mux, decoder) + encodeResponse = EncodeGetGroupsioSubgroupResponse(encoder) + encodeError = EncodeGetGroupsioSubgroupError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "update-grpsio-mailing-list") + ctx = context.WithValue(ctx, goa.MethodKey, "get-groupsio-subgroup") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -754,22 +828,130 @@ func NewUpdateGrpsioMailingListHandler( }) } -// MountDeleteGrpsioMailingListHandler configures the mux to serve the -// "mailing-list" service "delete-grpsio-mailing-list" endpoint. -func MountDeleteGrpsioMailingListHandler(mux goahttp.Muxer, h http.Handler) { +// MountUpdateGroupsioSubgroupHandler configures the mux to serve the +// "mailing-list" service "update-groupsio-subgroup" endpoint. +func MountUpdateGroupsioSubgroupHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("DELETE", "/groupsio/mailing-lists/{uid}", f) + mux.Handle("PUT", "/groupsio/subgroups/{subgroup_id}", f) } -// NewDeleteGrpsioMailingListHandler creates a HTTP handler which loads the +// NewUpdateGroupsioSubgroupHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "update-groupsio-subgroup" +// endpoint. +func NewUpdateGroupsioSubgroupHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeUpdateGroupsioSubgroupRequest(mux, decoder) + encodeResponse = EncodeUpdateGroupsioSubgroupResponse(encoder) + encodeError = EncodeUpdateGroupsioSubgroupError(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "update-groupsio-subgroup") + ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} + +// MountDeleteGroupsioSubgroupHandler configures the mux to serve the +// "mailing-list" service "delete-groupsio-subgroup" endpoint. +func MountDeleteGroupsioSubgroupHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("DELETE", "/groupsio/subgroups/{subgroup_id}", f) +} + +// NewDeleteGroupsioSubgroupHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "delete-groupsio-subgroup" +// endpoint. +func NewDeleteGroupsioSubgroupHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeDeleteGroupsioSubgroupRequest(mux, decoder) + encodeResponse = EncodeDeleteGroupsioSubgroupResponse(encoder) + encodeError = EncodeDeleteGroupsioSubgroupError(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "delete-groupsio-subgroup") + ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} + +// MountGetGroupsioSubgroupCountHandler configures the mux to serve the +// "mailing-list" service "get-groupsio-subgroup-count" endpoint. +func MountGetGroupsioSubgroupCountHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("GET", "/groupsio/subgroups/count", f) +} + +// NewGetGroupsioSubgroupCountHandler creates a HTTP handler which loads the // HTTP request and calls the "mailing-list" service -// "delete-grpsio-mailing-list" endpoint. -func NewDeleteGrpsioMailingListHandler( +// "get-groupsio-subgroup-count" endpoint. +func NewGetGroupsioSubgroupCountHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -778,13 +960,13 @@ func NewDeleteGrpsioMailingListHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeDeleteGrpsioMailingListRequest(mux, decoder) - encodeResponse = EncodeDeleteGrpsioMailingListResponse(encoder) - encodeError = EncodeDeleteGrpsioMailingListError(encoder, formatter) + decodeRequest = DecodeGetGroupsioSubgroupCountRequest(mux, decoder) + encodeResponse = EncodeGetGroupsioSubgroupCountResponse(encoder) + encodeError = EncodeGetGroupsioSubgroupCountError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "delete-grpsio-mailing-list") + ctx = context.WithValue(ctx, goa.MethodKey, "get-groupsio-subgroup-count") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -808,22 +990,22 @@ func NewDeleteGrpsioMailingListHandler( }) } -// MountGetGrpsioMailingListSettingsHandler configures the mux to serve the -// "mailing-list" service "get-grpsio-mailing-list-settings" endpoint. -func MountGetGrpsioMailingListSettingsHandler(mux goahttp.Muxer, h http.Handler) { +// MountGetGroupsioSubgroupMemberCountHandler configures the mux to serve the +// "mailing-list" service "get-groupsio-subgroup-member-count" endpoint. +func MountGetGroupsioSubgroupMemberCountHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("GET", "/groupsio/mailing-lists/{uid}/settings", f) + mux.Handle("GET", "/groupsio/subgroups/{subgroup_id}/member_count", f) } -// NewGetGrpsioMailingListSettingsHandler creates a HTTP handler which loads +// NewGetGroupsioSubgroupMemberCountHandler creates a HTTP handler which loads // the HTTP request and calls the "mailing-list" service -// "get-grpsio-mailing-list-settings" endpoint. -func NewGetGrpsioMailingListSettingsHandler( +// "get-groupsio-subgroup-member-count" endpoint. +func NewGetGroupsioSubgroupMemberCountHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -832,13 +1014,13 @@ func NewGetGrpsioMailingListSettingsHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeGetGrpsioMailingListSettingsRequest(mux, decoder) - encodeResponse = EncodeGetGrpsioMailingListSettingsResponse(encoder) - encodeError = EncodeGetGrpsioMailingListSettingsError(encoder, formatter) + decodeRequest = DecodeGetGroupsioSubgroupMemberCountRequest(mux, decoder) + encodeResponse = EncodeGetGroupsioSubgroupMemberCountResponse(encoder) + encodeError = EncodeGetGroupsioSubgroupMemberCountError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "get-grpsio-mailing-list-settings") + ctx = context.WithValue(ctx, goa.MethodKey, "get-groupsio-subgroup-member-count") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -862,22 +1044,22 @@ func NewGetGrpsioMailingListSettingsHandler( }) } -// MountUpdateGrpsioMailingListSettingsHandler configures the mux to serve the -// "mailing-list" service "update-grpsio-mailing-list-settings" endpoint. -func MountUpdateGrpsioMailingListSettingsHandler(mux goahttp.Muxer, h http.Handler) { +// MountListGroupsioMembersHandler configures the mux to serve the +// "mailing-list" service "list-groupsio-members" endpoint. +func MountListGroupsioMembersHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("PUT", "/groupsio/mailing-lists/{uid}/settings", f) + mux.Handle("GET", "/groupsio/subgroups/{subgroup_id}/members", f) } -// NewUpdateGrpsioMailingListSettingsHandler creates a HTTP handler which loads -// the HTTP request and calls the "mailing-list" service -// "update-grpsio-mailing-list-settings" endpoint. -func NewUpdateGrpsioMailingListSettingsHandler( +// NewListGroupsioMembersHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "list-groupsio-members" +// endpoint. +func NewListGroupsioMembersHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -886,13 +1068,13 @@ func NewUpdateGrpsioMailingListSettingsHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeUpdateGrpsioMailingListSettingsRequest(mux, decoder) - encodeResponse = EncodeUpdateGrpsioMailingListSettingsResponse(encoder) - encodeError = EncodeUpdateGrpsioMailingListSettingsError(encoder, formatter) + decodeRequest = DecodeListGroupsioMembersRequest(mux, decoder) + encodeResponse = EncodeListGroupsioMembersResponse(encoder) + encodeError = EncodeListGroupsioMembersError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "update-grpsio-mailing-list-settings") + ctx = context.WithValue(ctx, goa.MethodKey, "list-groupsio-members") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -916,22 +1098,21 @@ func NewUpdateGrpsioMailingListSettingsHandler( }) } -// MountCreateGrpsioMailingListMemberHandler configures the mux to serve the -// "mailing-list" service "create-grpsio-mailing-list-member" endpoint. -func MountCreateGrpsioMailingListMemberHandler(mux goahttp.Muxer, h http.Handler) { +// MountAddGroupsioMemberHandler configures the mux to serve the "mailing-list" +// service "add-groupsio-member" endpoint. +func MountAddGroupsioMemberHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("POST", "/groupsio/mailing-lists/{uid}/members", f) + mux.Handle("POST", "/groupsio/subgroups/{subgroup_id}/members", f) } -// NewCreateGrpsioMailingListMemberHandler creates a HTTP handler which loads -// the HTTP request and calls the "mailing-list" service -// "create-grpsio-mailing-list-member" endpoint. -func NewCreateGrpsioMailingListMemberHandler( +// NewAddGroupsioMemberHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "add-groupsio-member" endpoint. +func NewAddGroupsioMemberHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -940,13 +1121,13 @@ func NewCreateGrpsioMailingListMemberHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeCreateGrpsioMailingListMemberRequest(mux, decoder) - encodeResponse = EncodeCreateGrpsioMailingListMemberResponse(encoder) - encodeError = EncodeCreateGrpsioMailingListMemberError(encoder, formatter) + decodeRequest = DecodeAddGroupsioMemberRequest(mux, decoder) + encodeResponse = EncodeAddGroupsioMemberResponse(encoder) + encodeError = EncodeAddGroupsioMemberError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "create-grpsio-mailing-list-member") + ctx = context.WithValue(ctx, goa.MethodKey, "add-groupsio-member") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -970,22 +1151,21 @@ func NewCreateGrpsioMailingListMemberHandler( }) } -// MountGetGrpsioMailingListMemberHandler configures the mux to serve the -// "mailing-list" service "get-grpsio-mailing-list-member" endpoint. -func MountGetGrpsioMailingListMemberHandler(mux goahttp.Muxer, h http.Handler) { +// MountGetGroupsioMemberHandler configures the mux to serve the "mailing-list" +// service "get-groupsio-member" endpoint. +func MountGetGroupsioMemberHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("GET", "/groupsio/mailing-lists/{uid}/members/{member_uid}", f) + mux.Handle("GET", "/groupsio/subgroups/{subgroup_id}/members/{member_id}", f) } -// NewGetGrpsioMailingListMemberHandler creates a HTTP handler which loads the -// HTTP request and calls the "mailing-list" service -// "get-grpsio-mailing-list-member" endpoint. -func NewGetGrpsioMailingListMemberHandler( +// NewGetGroupsioMemberHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "get-groupsio-member" endpoint. +func NewGetGroupsioMemberHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -994,13 +1174,13 @@ func NewGetGrpsioMailingListMemberHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeGetGrpsioMailingListMemberRequest(mux, decoder) - encodeResponse = EncodeGetGrpsioMailingListMemberResponse(encoder) - encodeError = EncodeGetGrpsioMailingListMemberError(encoder, formatter) + decodeRequest = DecodeGetGroupsioMemberRequest(mux, decoder) + encodeResponse = EncodeGetGroupsioMemberResponse(encoder) + encodeError = EncodeGetGroupsioMemberError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "get-grpsio-mailing-list-member") + ctx = context.WithValue(ctx, goa.MethodKey, "get-groupsio-member") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -1024,22 +1204,22 @@ func NewGetGrpsioMailingListMemberHandler( }) } -// MountUpdateGrpsioMailingListMemberHandler configures the mux to serve the -// "mailing-list" service "update-grpsio-mailing-list-member" endpoint. -func MountUpdateGrpsioMailingListMemberHandler(mux goahttp.Muxer, h http.Handler) { +// MountUpdateGroupsioMemberHandler configures the mux to serve the +// "mailing-list" service "update-groupsio-member" endpoint. +func MountUpdateGroupsioMemberHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("PUT", "/groupsio/mailing-lists/{uid}/members/{member_uid}", f) + mux.Handle("PUT", "/groupsio/subgroups/{subgroup_id}/members/{member_id}", f) } -// NewUpdateGrpsioMailingListMemberHandler creates a HTTP handler which loads -// the HTTP request and calls the "mailing-list" service -// "update-grpsio-mailing-list-member" endpoint. -func NewUpdateGrpsioMailingListMemberHandler( +// NewUpdateGroupsioMemberHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "update-groupsio-member" +// endpoint. +func NewUpdateGroupsioMemberHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -1048,13 +1228,13 @@ func NewUpdateGrpsioMailingListMemberHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeUpdateGrpsioMailingListMemberRequest(mux, decoder) - encodeResponse = EncodeUpdateGrpsioMailingListMemberResponse(encoder) - encodeError = EncodeUpdateGrpsioMailingListMemberError(encoder, formatter) + decodeRequest = DecodeUpdateGroupsioMemberRequest(mux, decoder) + encodeResponse = EncodeUpdateGroupsioMemberResponse(encoder) + encodeError = EncodeUpdateGroupsioMemberError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "update-grpsio-mailing-list-member") + ctx = context.WithValue(ctx, goa.MethodKey, "update-groupsio-member") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -1078,22 +1258,76 @@ func NewUpdateGrpsioMailingListMemberHandler( }) } -// MountDeleteGrpsioMailingListMemberHandler configures the mux to serve the -// "mailing-list" service "delete-grpsio-mailing-list-member" endpoint. -func MountDeleteGrpsioMailingListMemberHandler(mux goahttp.Muxer, h http.Handler) { +// MountDeleteGroupsioMemberHandler configures the mux to serve the +// "mailing-list" service "delete-groupsio-member" endpoint. +func MountDeleteGroupsioMemberHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("DELETE", "/groupsio/mailing-lists/{uid}/members/{member_uid}", f) + mux.Handle("DELETE", "/groupsio/subgroups/{subgroup_id}/members/{member_id}", f) } -// NewDeleteGrpsioMailingListMemberHandler creates a HTTP handler which loads -// the HTTP request and calls the "mailing-list" service -// "delete-grpsio-mailing-list-member" endpoint. -func NewDeleteGrpsioMailingListMemberHandler( +// NewDeleteGroupsioMemberHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "delete-groupsio-member" +// endpoint. +func NewDeleteGroupsioMemberHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeDeleteGroupsioMemberRequest(mux, decoder) + encodeResponse = EncodeDeleteGroupsioMemberResponse(encoder) + encodeError = EncodeDeleteGroupsioMemberError(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "delete-groupsio-member") + ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} + +// MountInviteGroupsioMembersHandler configures the mux to serve the +// "mailing-list" service "invite-groupsio-members" endpoint. +func MountInviteGroupsioMembersHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("POST", "/groupsio/subgroups/{subgroup_id}/invitemembers", f) +} + +// NewInviteGroupsioMembersHandler creates a HTTP handler which loads the HTTP +// request and calls the "mailing-list" service "invite-groupsio-members" +// endpoint. +func NewInviteGroupsioMembersHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -1102,13 +1336,13 @@ func NewDeleteGrpsioMailingListMemberHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeDeleteGrpsioMailingListMemberRequest(mux, decoder) - encodeResponse = EncodeDeleteGrpsioMailingListMemberResponse(encoder) - encodeError = EncodeDeleteGrpsioMailingListMemberError(encoder, formatter) + decodeRequest = DecodeInviteGroupsioMembersRequest(mux, decoder) + encodeResponse = EncodeInviteGroupsioMembersResponse(encoder) + encodeError = EncodeInviteGroupsioMembersError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "delete-grpsio-mailing-list-member") + ctx = context.WithValue(ctx, goa.MethodKey, "invite-groupsio-members") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { @@ -1132,21 +1366,22 @@ func NewDeleteGrpsioMailingListMemberHandler( }) } -// MountGroupsioWebhookHandler configures the mux to serve the "mailing-list" -// service "groupsio-webhook" endpoint. -func MountGroupsioWebhookHandler(mux goahttp.Muxer, h http.Handler) { +// MountCheckGroupsioSubscriberHandler configures the mux to serve the +// "mailing-list" service "check-groupsio-subscriber" endpoint. +func MountCheckGroupsioSubscriberHandler(mux goahttp.Muxer, h http.Handler) { f, ok := h.(http.HandlerFunc) if !ok { f = func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } } - mux.Handle("POST", "/webhooks/groupsio", f) + mux.Handle("POST", "/groupsio/checksubscriber", f) } -// NewGroupsioWebhookHandler creates a HTTP handler which loads the HTTP -// request and calls the "mailing-list" service "groupsio-webhook" endpoint. -func NewGroupsioWebhookHandler( +// NewCheckGroupsioSubscriberHandler creates a HTTP handler which loads the +// HTTP request and calls the "mailing-list" service +// "check-groupsio-subscriber" endpoint. +func NewCheckGroupsioSubscriberHandler( endpoint goa.Endpoint, mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder, @@ -1155,13 +1390,13 @@ func NewGroupsioWebhookHandler( formatter func(ctx context.Context, err error) goahttp.Statuser, ) http.Handler { var ( - decodeRequest = DecodeGroupsioWebhookRequest(mux, decoder) - encodeResponse = EncodeGroupsioWebhookResponse(encoder) - encodeError = EncodeGroupsioWebhookError(encoder, formatter) + decodeRequest = DecodeCheckGroupsioSubscriberRequest(mux, decoder) + encodeResponse = EncodeCheckGroupsioSubscriberResponse(encoder) + encodeError = EncodeCheckGroupsioSubscriberError(encoder, formatter) ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) - ctx = context.WithValue(ctx, goa.MethodKey, "groupsio-webhook") + ctx = context.WithValue(ctx, goa.MethodKey, "check-groupsio-subscriber") ctx = context.WithValue(ctx, goa.ServiceKey, "mailing-list") payload, err := decodeRequest(r) if err != nil { diff --git a/gen/http/mailing_list/server/types.go b/gen/http/mailing_list/server/types.go index 80e780d..7572ee6 100644 --- a/gen/http/mailing_list/server/types.go +++ b/gen/http/mailing_list/server/types.go @@ -9,538 +9,425 @@ package server import ( - "unicode/utf8" - mailinglist "github.com/linuxfoundation/lfx-v2-mailing-list-service/gen/mailing_list" goa "goa.design/goa/v3/pkg" ) -// CreateGrpsioServiceRequestBody is the type of the "mailing-list" service -// "create-grpsio-service" endpoint HTTP request body. -type CreateGrpsioServiceRequestBody struct { +// CreateGroupsioServiceRequestBody is the type of the "mailing-list" service +// "create-groupsio-service" endpoint HTTP request body. +type CreateGroupsioServiceRequestBody struct { + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` // Service type Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` - // Service domain - Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` + // Service domain + Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + // Email prefix + Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` // Service status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // List of global owner email addresses (required for primary, forbidden for - // shared) - GlobalOwners []string `form:"global_owners,omitempty" json:"global_owners,omitempty" xml:"global_owners,omitempty"` - // Email prefix (required for formation and shared, forbidden for primary) - Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` - // Parent primary service UID (automatically set for shared type services) - ParentServiceUID *string `form:"parent_service_uid,omitempty" json:"parent_service_uid,omitempty" xml:"parent_service_uid,omitempty"` - // Project slug identifier - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // LFXv2 Project UID +} + +// UpdateGroupsioServiceRequestBody is the type of the "mailing-list" service +// "update-groupsio-service" endpoint HTTP request body. +type UpdateGroupsioServiceRequestBody struct { + // LFX v2 project UID ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` - // Service URL - URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` - // GroupsIO group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Whether the service is publicly accessible - Public *bool `form:"public,omitempty" json:"public,omitempty" xml:"public,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoRequestBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoRequestBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// UpdateGrpsioServiceRequestBody is the type of the "mailing-list" service -// "update-grpsio-service" endpoint HTTP request body. -type UpdateGrpsioServiceRequestBody struct { // Service type Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` - // Service domain - Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` + // Service domain + Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + // Email prefix + Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` // Service status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // List of global owner email addresses (required for primary, forbidden for - // shared) - GlobalOwners []string `form:"global_owners,omitempty" json:"global_owners,omitempty" xml:"global_owners,omitempty"` - // Email prefix (required for formation and shared, forbidden for primary) - Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` - // Parent primary service UID (automatically set for shared type services) - ParentServiceUID *string `form:"parent_service_uid,omitempty" json:"parent_service_uid,omitempty" xml:"parent_service_uid,omitempty"` - // Project slug identifier - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // LFXv2 Project UID +} + +// CreateGroupsioSubgroupRequestBody is the type of the "mailing-list" service +// "create-groupsio-subgroup" endpoint HTTP request body. +type CreateGroupsioSubgroupRequestBody struct { + // LFX v2 project UID ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` - // Service URL - URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` - // GroupsIO group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Whether the service is publicly accessible - Public *bool `form:"public,omitempty" json:"public,omitempty" xml:"public,omitempty"` -} - -// UpdateGrpsioServiceSettingsRequestBody is the type of the "mailing-list" -// service "update-grpsio-service-settings" endpoint HTTP request body. -type UpdateGrpsioServiceSettingsRequestBody struct { - // Manager users who can edit/modify this resource - Writers []*UserInfoRequestBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoRequestBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// CreateGrpsioMailingListRequestBody is the type of the "mailing-list" service -// "create-grpsio-mailing-list" endpoint HTTP request body. -type CreateGrpsioMailingListRequestBody struct { - // Mailing list group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Mailing list group ID + // LFX v2 committee UID + CommitteeUID *string `form:"committee_uid,omitempty" json:"committee_uid,omitempty" xml:"committee_uid,omitempty"` + // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // Whether the mailing list is publicly accessible - Public *bool `form:"public,omitempty" json:"public,omitempty" xml:"public,omitempty"` - // Mailing list type + // Subgroup name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // Subgroup description + Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` + // Subgroup type Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` - // public: Anyone can join. approval_required: Users must request to join and - // be approved. invite_only: Only invited users can join. + // Audience access setting AudienceAccess *string `form:"audience_access,omitempty" json:"audience_access,omitempty" xml:"audience_access,omitempty"` - // Committees associated with this mailing list (OR logic for access control) - Committees []*CommitteeRequestBody `form:"committees,omitempty" json:"committees,omitempty" xml:"committees,omitempty"` - // Mailing list description (11-500 characters) - Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` - // Mailing list title - Title *string `form:"title,omitempty" json:"title,omitempty" xml:"title,omitempty"` - // Subject tag prefix - SubjectTag *string `form:"subject_tag,omitempty" json:"subject_tag,omitempty" xml:"subject_tag,omitempty"` - // Service UUID - ServiceUID *string `form:"service_uid,omitempty" json:"service_uid,omitempty" xml:"service_uid,omitempty"` - // Number of subscribers in this mailing list (read-only, maintained by service) - SubscriberCount *int `form:"subscriber_count,omitempty" json:"subscriber_count,omitempty" xml:"subscriber_count,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoRequestBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoRequestBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// UpdateGrpsioMailingListRequestBody is the type of the "mailing-list" service -// "update-grpsio-mailing-list" endpoint HTTP request body. -type UpdateGrpsioMailingListRequestBody struct { - // Mailing list group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Mailing list group ID +} + +// UpdateGroupsioSubgroupRequestBody is the type of the "mailing-list" service +// "update-groupsio-subgroup" endpoint HTTP request body. +type UpdateGroupsioSubgroupRequestBody struct { + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // LFX v2 committee UID + CommitteeUID *string `form:"committee_uid,omitempty" json:"committee_uid,omitempty" xml:"committee_uid,omitempty"` + // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // Whether the mailing list is publicly accessible - Public *bool `form:"public,omitempty" json:"public,omitempty" xml:"public,omitempty"` - // Mailing list type + // Subgroup name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // Subgroup description + Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` + // Subgroup type Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` - // public: Anyone can join. approval_required: Users must request to join and - // be approved. invite_only: Only invited users can join. + // Audience access setting AudienceAccess *string `form:"audience_access,omitempty" json:"audience_access,omitempty" xml:"audience_access,omitempty"` - // Committees associated with this mailing list (OR logic for access control) - Committees []*CommitteeRequestBody `form:"committees,omitempty" json:"committees,omitempty" xml:"committees,omitempty"` - // Mailing list description (11-500 characters) - Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` - // Mailing list title - Title *string `form:"title,omitempty" json:"title,omitempty" xml:"title,omitempty"` - // Subject tag prefix - SubjectTag *string `form:"subject_tag,omitempty" json:"subject_tag,omitempty" xml:"subject_tag,omitempty"` - // Service UUID - ServiceUID *string `form:"service_uid,omitempty" json:"service_uid,omitempty" xml:"service_uid,omitempty"` - // Number of subscribers in this mailing list (read-only, maintained by service) - SubscriberCount *int `form:"subscriber_count,omitempty" json:"subscriber_count,omitempty" xml:"subscriber_count,omitempty"` -} - -// UpdateGrpsioMailingListSettingsRequestBody is the type of the "mailing-list" -// service "update-grpsio-mailing-list-settings" endpoint HTTP request body. -type UpdateGrpsioMailingListSettingsRequestBody struct { - // Manager users who can edit/modify this resource - Writers []*UserInfoRequestBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoRequestBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// CreateGrpsioMailingListMemberRequestBody is the type of the "mailing-list" -// service "create-grpsio-mailing-list-member" endpoint HTTP request body. -type CreateGrpsioMailingListMemberRequestBody struct { - // Member username - Username *string `form:"username,omitempty" json:"username,omitempty" xml:"username,omitempty"` - // Member first name - FirstName *string `form:"first_name,omitempty" json:"first_name,omitempty" xml:"first_name,omitempty"` - // Member last name - LastName *string `form:"last_name,omitempty" json:"last_name,omitempty" xml:"last_name,omitempty"` +} + +// AddGroupsioMemberRequestBody is the type of the "mailing-list" service +// "add-groupsio-member" endpoint HTTP request body. +type AddGroupsioMemberRequestBody struct { // Member email address Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` - // Member organization - Organization *string `form:"organization,omitempty" json:"organization,omitempty" xml:"organization,omitempty"` - // Member job title - JobTitle *string `form:"job_title,omitempty" json:"job_title,omitempty" xml:"job_title,omitempty"` - // Member type - MemberType *string `form:"member_type,omitempty" json:"member_type,omitempty" xml:"member_type,omitempty"` - // Email delivery mode - DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` + // Member display name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` // Moderation status ModStatus *string `form:"mod_status,omitempty" json:"mod_status,omitempty" xml:"mod_status,omitempty"` - // Last reviewed timestamp - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // Last reviewed by user ID - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` -} - -// UpdateGrpsioMailingListMemberRequestBody is the type of the "mailing-list" -// service "update-grpsio-mailing-list-member" endpoint HTTP request body. -type UpdateGrpsioMailingListMemberRequestBody struct { - // Member username - Username *string `form:"username,omitempty" json:"username,omitempty" xml:"username,omitempty"` - // Member first name - FirstName *string `form:"first_name,omitempty" json:"first_name,omitempty" xml:"first_name,omitempty"` - // Member last name - LastName *string `form:"last_name,omitempty" json:"last_name,omitempty" xml:"last_name,omitempty"` - // Member organization - Organization *string `form:"organization,omitempty" json:"organization,omitempty" xml:"organization,omitempty"` - // Member job title - JobTitle *string `form:"job_title,omitempty" json:"job_title,omitempty" xml:"job_title,omitempty"` // Email delivery mode DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` +} + +// UpdateGroupsioMemberRequestBody is the type of the "mailing-list" service +// "update-groupsio-member" endpoint HTTP request body. +type UpdateGroupsioMemberRequestBody struct { + // Member email address + Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` + // Member display name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` // Moderation status ModStatus *string `form:"mod_status,omitempty" json:"mod_status,omitempty" xml:"mod_status,omitempty"` + // Email delivery mode + DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` +} + +// InviteGroupsioMembersRequestBody is the type of the "mailing-list" service +// "invite-groupsio-members" endpoint HTTP request body. +type InviteGroupsioMembersRequestBody struct { + // Email addresses to invite + Emails []string `form:"emails,omitempty" json:"emails,omitempty" xml:"emails,omitempty"` +} + +// CheckGroupsioSubscriberRequestBody is the type of the "mailing-list" service +// "check-groupsio-subscriber" endpoint HTTP request body. +type CheckGroupsioSubscriberRequestBody struct { + // Email address to check + Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` + // Subgroup ID + SubgroupID *string `form:"subgroup_id,omitempty" json:"subgroup_id,omitempty" xml:"subgroup_id,omitempty"` } -// GroupsioWebhookRequestBody is the type of the "mailing-list" service -// "groupsio-webhook" endpoint HTTP request body. -type GroupsioWebhookRequestBody struct { - // The type of webhook event - Action *string `form:"action,omitempty" json:"action,omitempty" xml:"action,omitempty"` - // Contains subgroup data from Groups.io - Group any `form:"group,omitempty" json:"group,omitempty" xml:"group,omitempty"` - // Contains member data from Groups.io - MemberInfo any `form:"member_info,omitempty" json:"member_info,omitempty" xml:"member_info,omitempty"` - // Extra data field (subgroup suffix) - Extra *string `form:"extra,omitempty" json:"extra,omitempty" xml:"extra,omitempty"` - // Extra ID field (subgroup ID for deletion) - ExtraID *int `form:"extra_id,omitempty" json:"extra_id,omitempty" xml:"extra_id,omitempty"` -} - -// CreateGrpsioServiceResponseBody is the type of the "mailing-list" service -// "create-grpsio-service" endpoint HTTP response body. -type CreateGrpsioServiceResponseBody struct { - // Service UID -- unique identifier for the service - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` +// ListGroupsioServicesResponseBody is the type of the "mailing-list" service +// "list-groupsio-services" endpoint HTTP response body. +type ListGroupsioServicesResponseBody struct { + // List of services + Items []*GroupsioServiceResponseBody `form:"items,omitempty" json:"items,omitempty" xml:"items,omitempty"` + // Total count + Total *int `form:"total,omitempty" json:"total,omitempty" xml:"total,omitempty"` +} + +// CreateGroupsioServiceResponseBody is the type of the "mailing-list" service +// "create-groupsio-service" endpoint HTTP response body. +type CreateGroupsioServiceResponseBody struct { + // Service ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` // Service type - Type string `form:"type" json:"type" xml:"type"` - // Service domain - Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` + // Service domain + Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + // Email prefix + Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` // Service status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // List of global owner email addresses (required for primary, forbidden for - // shared) - GlobalOwners []string `form:"global_owners,omitempty" json:"global_owners,omitempty" xml:"global_owners,omitempty"` - // Email prefix (required for formation and shared, forbidden for primary) - Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` - // Parent primary service UID (automatically set for shared type services) - ParentServiceUID *string `form:"parent_service_uid,omitempty" json:"parent_service_uid,omitempty" xml:"parent_service_uid,omitempty"` - // Project slug identifier - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // LFXv2 Project UID - ProjectUID string `form:"project_uid" json:"project_uid" xml:"project_uid"` - // Service URL - URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` - // GroupsIO group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Whether the service is publicly accessible - Public bool `form:"public" json:"public" xml:"public"` - // Project name (read-only) - ProjectName *string `form:"project_name,omitempty" json:"project_name,omitempty" xml:"project_name,omitempty"` - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` } -// GetGrpsioServiceResponseBody is the type of the "mailing-list" service -// "get-grpsio-service" endpoint HTTP response body. -type GetGrpsioServiceResponseBody GrpsIoServiceWithReadonlyAttributesResponseBody - -// UpdateGrpsioServiceResponseBody is the type of the "mailing-list" service -// "update-grpsio-service" endpoint HTTP response body. -type UpdateGrpsioServiceResponseBody struct { - // Service UID -- unique identifier for the service - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` +// GetGroupsioServiceResponseBody is the type of the "mailing-list" service +// "get-groupsio-service" endpoint HTTP response body. +type GetGroupsioServiceResponseBody struct { + // Service ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` // Service type - Type string `form:"type" json:"type" xml:"type"` + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` + // GroupsIO group ID + GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` // Service domain Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + // Email prefix + Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` + // Service status + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Creation timestamp + CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` + // Last update timestamp + UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` +} + +// UpdateGroupsioServiceResponseBody is the type of the "mailing-list" service +// "update-groupsio-service" endpoint HTTP response body. +type UpdateGroupsioServiceResponseBody struct { + // Service ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // Service type + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` + // Service domain + Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + // Email prefix + Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` // Service status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // List of global owner email addresses (required for primary, forbidden for - // shared) - GlobalOwners []string `form:"global_owners,omitempty" json:"global_owners,omitempty" xml:"global_owners,omitempty"` - // Email prefix (required for formation and shared, forbidden for primary) - Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` - // Parent primary service UID (automatically set for shared type services) - ParentServiceUID *string `form:"parent_service_uid,omitempty" json:"parent_service_uid,omitempty" xml:"parent_service_uid,omitempty"` - // Project slug identifier - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // LFXv2 Project UID - ProjectUID string `form:"project_uid" json:"project_uid" xml:"project_uid"` - // Service URL - URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` - // GroupsIO group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Whether the service is publicly accessible - Public bool `form:"public" json:"public" xml:"public"` - // Project name (read-only) - ProjectName *string `form:"project_name,omitempty" json:"project_name,omitempty" xml:"project_name,omitempty"` - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// GetGrpsioServiceSettingsResponseBody is the type of the "mailing-list" -// service "get-grpsio-service-settings" endpoint HTTP response body. -type GetGrpsioServiceSettingsResponseBody GrpsIoServiceSettingsResponseBody - -// UpdateGrpsioServiceSettingsResponseBody is the type of the "mailing-list" -// service "update-grpsio-service-settings" endpoint HTTP response body. -type UpdateGrpsioServiceSettingsResponseBody struct { - // Service UID -- unique identifier for the service - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` - // The timestamp when the service was last reviewed in RFC3339 format - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // The user ID who last reviewed this service - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` - // The user ID who last audited the service - LastAuditedBy *string `form:"last_audited_by,omitempty" json:"last_audited_by,omitempty" xml:"last_audited_by,omitempty"` - // The timestamp when the service was last audited - LastAuditedTime *string `form:"last_audited_time,omitempty" json:"last_audited_time,omitempty" xml:"last_audited_time,omitempty"` - // The timestamp when the service was created (read-only) +} + +// GetGroupsioServiceProjectsResponseBody is the type of the "mailing-list" +// service "get-groupsio-service-projects" endpoint HTTP response body. +type GetGroupsioServiceProjectsResponseBody struct { + // List of project identifiers + Projects []string `form:"projects,omitempty" json:"projects,omitempty" xml:"projects,omitempty"` +} + +// FindParentGroupsioServiceResponseBody is the type of the "mailing-list" +// service "find-parent-groupsio-service" endpoint HTTP response body. +type FindParentGroupsioServiceResponseBody struct { + // Service ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // Service type + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` + // GroupsIO group ID + GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` + // Service domain + Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + // Email prefix + Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` + // Service status + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` } -// CreateGrpsioMailingListResponseBody is the type of the "mailing-list" -// service "create-grpsio-mailing-list" endpoint HTTP response body. -type CreateGrpsioMailingListResponseBody struct { - // Mailing list UID -- unique identifier for the mailing list - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Mailing list group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Mailing list group ID +// ListGroupsioSubgroupsResponseBody is the type of the "mailing-list" service +// "list-groupsio-subgroups" endpoint HTTP response body. +type ListGroupsioSubgroupsResponseBody struct { + // List of subgroups + Items []*GroupsioSubgroupResponseBody `form:"items,omitempty" json:"items,omitempty" xml:"items,omitempty"` + // Total count + Total *int `form:"total,omitempty" json:"total,omitempty" xml:"total,omitempty"` +} + +// CreateGroupsioSubgroupResponseBody is the type of the "mailing-list" service +// "create-groupsio-subgroup" endpoint HTTP response body. +type CreateGroupsioSubgroupResponseBody struct { + // Subgroup ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // LFX v2 committee UID + CommitteeUID *string `form:"committee_uid,omitempty" json:"committee_uid,omitempty" xml:"committee_uid,omitempty"` + // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // Whether the mailing list is publicly accessible - Public bool `form:"public" json:"public" xml:"public"` - // Mailing list type - Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` - // public: Anyone can join. approval_required: Users must request to join and - // be approved. invite_only: Only invited users can join. - AudienceAccess string `form:"audience_access" json:"audience_access" xml:"audience_access"` - // Committees associated with this mailing list (OR logic for access control) - Committees []*CommitteeResponseBody `form:"committees,omitempty" json:"committees,omitempty" xml:"committees,omitempty"` - // Mailing list description (11-500 characters) + // Subgroup name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // Subgroup description Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` - // Mailing list title - Title *string `form:"title,omitempty" json:"title,omitempty" xml:"title,omitempty"` - // Subject tag prefix - SubjectTag *string `form:"subject_tag,omitempty" json:"subject_tag,omitempty" xml:"subject_tag,omitempty"` - // Service UUID - ServiceUID *string `form:"service_uid,omitempty" json:"service_uid,omitempty" xml:"service_uid,omitempty"` - // Number of subscribers in this mailing list (read-only, maintained by service) - SubscriberCount *int `form:"subscriber_count,omitempty" json:"subscriber_count,omitempty" xml:"subscriber_count,omitempty"` - // LFXv2 Project UID (inherited from parent service) - ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` - // Project name (read-only) - ProjectName *string `form:"project_name,omitempty" json:"project_name,omitempty" xml:"project_name,omitempty"` - // Project slug identifier (read-only) - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // The timestamp when the service was created (read-only) + // Subgroup type + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` + // Audience access setting + AudienceAccess *string `form:"audience_access,omitempty" json:"audience_access,omitempty" xml:"audience_access,omitempty"` + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` } -// GetGrpsioMailingListResponseBody is the type of the "mailing-list" service -// "get-grpsio-mailing-list" endpoint HTTP response body. -type GetGrpsioMailingListResponseBody GrpsIoMailingListWithReadonlyAttributesResponseBody - -// UpdateGrpsioMailingListResponseBody is the type of the "mailing-list" -// service "update-grpsio-mailing-list" endpoint HTTP response body. -type UpdateGrpsioMailingListResponseBody struct { - // Mailing list UID -- unique identifier for the mailing list - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Mailing list group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Mailing list group ID +// GetGroupsioSubgroupResponseBody is the type of the "mailing-list" service +// "get-groupsio-subgroup" endpoint HTTP response body. +type GetGroupsioSubgroupResponseBody struct { + // Subgroup ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // LFX v2 committee UID + CommitteeUID *string `form:"committee_uid,omitempty" json:"committee_uid,omitempty" xml:"committee_uid,omitempty"` + // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // Whether the mailing list is publicly accessible - Public bool `form:"public" json:"public" xml:"public"` - // Mailing list type - Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` - // public: Anyone can join. approval_required: Users must request to join and - // be approved. invite_only: Only invited users can join. - AudienceAccess string `form:"audience_access" json:"audience_access" xml:"audience_access"` - // Committees associated with this mailing list (OR logic for access control) - Committees []*CommitteeResponseBody `form:"committees,omitempty" json:"committees,omitempty" xml:"committees,omitempty"` - // Mailing list description (11-500 characters) + // Subgroup name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // Subgroup description Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` - // Mailing list title - Title *string `form:"title,omitempty" json:"title,omitempty" xml:"title,omitempty"` - // Subject tag prefix - SubjectTag *string `form:"subject_tag,omitempty" json:"subject_tag,omitempty" xml:"subject_tag,omitempty"` - // Service UUID - ServiceUID *string `form:"service_uid,omitempty" json:"service_uid,omitempty" xml:"service_uid,omitempty"` - // Number of subscribers in this mailing list (read-only, maintained by service) - SubscriberCount *int `form:"subscriber_count,omitempty" json:"subscriber_count,omitempty" xml:"subscriber_count,omitempty"` - // LFXv2 Project UID (inherited from parent service) - ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` - // Project name (read-only) - ProjectName *string `form:"project_name,omitempty" json:"project_name,omitempty" xml:"project_name,omitempty"` - // Project slug identifier (read-only) - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // The timestamp when the service was created (read-only) + // Subgroup type + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` + // Audience access setting + AudienceAccess *string `form:"audience_access,omitempty" json:"audience_access,omitempty" xml:"audience_access,omitempty"` + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` } -// GetGrpsioMailingListSettingsResponseBody is the type of the "mailing-list" -// service "get-grpsio-mailing-list-settings" endpoint HTTP response body. -type GetGrpsioMailingListSettingsResponseBody GrpsIoMailingListSettingsResponseBody - -// UpdateGrpsioMailingListSettingsResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list-settings" endpoint HTTP -// response body. -type UpdateGrpsioMailingListSettingsResponseBody struct { - // Mailing list UID -- unique identifier for the mailing list - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` - // The timestamp when the service was last reviewed in RFC3339 format - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // The user ID who last reviewed this service - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` - // The user ID who last audited the service - LastAuditedBy *string `form:"last_audited_by,omitempty" json:"last_audited_by,omitempty" xml:"last_audited_by,omitempty"` - // The timestamp when the service was last audited - LastAuditedTime *string `form:"last_audited_time,omitempty" json:"last_audited_time,omitempty" xml:"last_audited_time,omitempty"` - // The timestamp when the service was created (read-only) +// UpdateGroupsioSubgroupResponseBody is the type of the "mailing-list" service +// "update-groupsio-subgroup" endpoint HTTP response body. +type UpdateGroupsioSubgroupResponseBody struct { + // Subgroup ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // LFX v2 committee UID + CommitteeUID *string `form:"committee_uid,omitempty" json:"committee_uid,omitempty" xml:"committee_uid,omitempty"` + // GroupsIO group ID + GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` + // Subgroup name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // Subgroup description + Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` + // Subgroup type + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` + // Audience access setting + AudienceAccess *string `form:"audience_access,omitempty" json:"audience_access,omitempty" xml:"audience_access,omitempty"` + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` } -// CreateGrpsioMailingListMemberResponseBody is the type of the "mailing-list" -// service "create-grpsio-mailing-list-member" endpoint HTTP response body. -type CreateGrpsioMailingListMemberResponseBody struct { - // Member UID - UID string `form:"uid" json:"uid" xml:"uid"` - // Mailing list UID - MailingListUID string `form:"mailing_list_uid" json:"mailing_list_uid" xml:"mailing_list_uid"` - // Member username - Username *string `form:"username,omitempty" json:"username,omitempty" xml:"username,omitempty"` +// GetGroupsioSubgroupCountResponseBody is the type of the "mailing-list" +// service "get-groupsio-subgroup-count" endpoint HTTP response body. +type GetGroupsioSubgroupCountResponseBody struct { + // Count value + Count int `form:"count" json:"count" xml:"count"` +} + +// GetGroupsioSubgroupMemberCountResponseBody is the type of the "mailing-list" +// service "get-groupsio-subgroup-member-count" endpoint HTTP response body. +type GetGroupsioSubgroupMemberCountResponseBody struct { + // Count value + Count int `form:"count" json:"count" xml:"count"` +} + +// ListGroupsioMembersResponseBody is the type of the "mailing-list" service +// "list-groupsio-members" endpoint HTTP response body. +type ListGroupsioMembersResponseBody struct { + // List of members + Items []*GroupsioMemberResponseBody `form:"items,omitempty" json:"items,omitempty" xml:"items,omitempty"` + // Total count + Total *int `form:"total,omitempty" json:"total,omitempty" xml:"total,omitempty"` +} + +// AddGroupsioMemberResponseBody is the type of the "mailing-list" service +// "add-groupsio-member" endpoint HTTP response body. +type AddGroupsioMemberResponseBody struct { + // Member ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // Subgroup ID + SubgroupID *string `form:"subgroup_id,omitempty" json:"subgroup_id,omitempty" xml:"subgroup_id,omitempty"` + // Member email address + Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` + // Member display name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` // Member first name - FirstName string `form:"first_name" json:"first_name" xml:"first_name"` + FirstName *string `form:"first_name,omitempty" json:"first_name,omitempty" xml:"first_name,omitempty"` // Member last name - LastName string `form:"last_name" json:"last_name" xml:"last_name"` - // Member email address - Email string `form:"email" json:"email" xml:"email"` - // Member organization - Organization *string `form:"organization,omitempty" json:"organization,omitempty" xml:"organization,omitempty"` - // Member job title - JobTitle *string `form:"job_title,omitempty" json:"job_title,omitempty" xml:"job_title,omitempty"` - // Member type - MemberType string `form:"member_type" json:"member_type" xml:"member_type"` - // Email delivery mode - DeliveryMode string `form:"delivery_mode" json:"delivery_mode" xml:"delivery_mode"` + LastName *string `form:"last_name,omitempty" json:"last_name,omitempty" xml:"last_name,omitempty"` // Moderation status - ModStatus string `form:"mod_status" json:"mod_status" xml:"mod_status"` - // Last reviewed timestamp - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // Last reviewed by user ID - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` + ModStatus *string `form:"mod_status,omitempty" json:"mod_status,omitempty" xml:"mod_status,omitempty"` + // Email delivery mode + DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` // Member status - Status string `form:"status" json:"status" xml:"status"` - // Groups.io member ID - MemberID *int64 `form:"member_id,omitempty" json:"member_id,omitempty" xml:"member_id,omitempty"` - // Groups.io group ID - GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // The timestamp when the service was created (read-only) - CreatedAt string `form:"created_at" json:"created_at" xml:"created_at"` - // The timestamp when the service was last updated (read-only) - UpdatedAt string `form:"updated_at" json:"updated_at" xml:"updated_at"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// GetGrpsioMailingListMemberResponseBody is the type of the "mailing-list" -// service "get-grpsio-mailing-list-member" endpoint HTTP response body. -type GetGrpsioMailingListMemberResponseBody GrpsIoMemberWithReadonlyAttributesResponseBody - -// UpdateGrpsioMailingListMemberResponseBody is the type of the "mailing-list" -// service "update-grpsio-mailing-list-member" endpoint HTTP response body. -type UpdateGrpsioMailingListMemberResponseBody struct { - // Member UID - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Mailing list UID - MailingListUID *string `form:"mailing_list_uid,omitempty" json:"mailing_list_uid,omitempty" xml:"mailing_list_uid,omitempty"` - // Member username - Username *string `form:"username,omitempty" json:"username,omitempty" xml:"username,omitempty"` + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Creation timestamp + CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` + // Last update timestamp + UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` +} + +// GetGroupsioMemberResponseBody is the type of the "mailing-list" service +// "get-groupsio-member" endpoint HTTP response body. +type GetGroupsioMemberResponseBody struct { + // Member ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // Subgroup ID + SubgroupID *string `form:"subgroup_id,omitempty" json:"subgroup_id,omitempty" xml:"subgroup_id,omitempty"` + // Member email address + Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` + // Member display name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` // Member first name FirstName *string `form:"first_name,omitempty" json:"first_name,omitempty" xml:"first_name,omitempty"` // Member last name LastName *string `form:"last_name,omitempty" json:"last_name,omitempty" xml:"last_name,omitempty"` + // Moderation status + ModStatus *string `form:"mod_status,omitempty" json:"mod_status,omitempty" xml:"mod_status,omitempty"` + // Email delivery mode + DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` + // Member status + Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` + // Creation timestamp + CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` + // Last update timestamp + UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` +} + +// UpdateGroupsioMemberResponseBody is the type of the "mailing-list" service +// "update-groupsio-member" endpoint HTTP response body. +type UpdateGroupsioMemberResponseBody struct { + // Member ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // Subgroup ID + SubgroupID *string `form:"subgroup_id,omitempty" json:"subgroup_id,omitempty" xml:"subgroup_id,omitempty"` // Member email address Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` - // Member organization - Organization *string `form:"organization,omitempty" json:"organization,omitempty" xml:"organization,omitempty"` - // Member job title - JobTitle *string `form:"job_title,omitempty" json:"job_title,omitempty" xml:"job_title,omitempty"` - // Member type - MemberType string `form:"member_type" json:"member_type" xml:"member_type"` - // Email delivery mode - DeliveryMode string `form:"delivery_mode" json:"delivery_mode" xml:"delivery_mode"` + // Member display name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // Member first name + FirstName *string `form:"first_name,omitempty" json:"first_name,omitempty" xml:"first_name,omitempty"` + // Member last name + LastName *string `form:"last_name,omitempty" json:"last_name,omitempty" xml:"last_name,omitempty"` // Moderation status - ModStatus string `form:"mod_status" json:"mod_status" xml:"mod_status"` - // Last reviewed timestamp - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // Last reviewed by user ID - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` + ModStatus *string `form:"mod_status,omitempty" json:"mod_status,omitempty" xml:"mod_status,omitempty"` + // Email delivery mode + DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` // Member status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // Groups.io member ID - MemberID *int64 `form:"member_id,omitempty" json:"member_id,omitempty" xml:"member_id,omitempty"` - // Groups.io group ID - GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` +} + +// CheckGroupsioSubscriberResponseBody is the type of the "mailing-list" +// service "check-groupsio-subscriber" endpoint HTTP response body. +type CheckGroupsioSubscriberResponseBody struct { + // Whether the email is subscribed + Subscribed bool `form:"subscribed" json:"subscribed" xml:"subscribed"` } // ReadyzServiceUnavailableResponseBody is the type of the "mailing-list" @@ -551,1382 +438,918 @@ type ReadyzServiceUnavailableResponseBody struct { Message string `form:"message" json:"message" xml:"message"` } -// CreateGrpsioServiceBadRequestResponseBody is the type of the "mailing-list" -// service "create-grpsio-service" endpoint HTTP response body for the +// ListGroupsioServicesBadRequestResponseBody is the type of the "mailing-list" +// service "list-groupsio-services" endpoint HTTP response body for the // "BadRequest" error. -type CreateGrpsioServiceBadRequestResponseBody struct { +type ListGroupsioServicesBadRequestResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// CreateGrpsioServiceConflictResponseBody is the type of the "mailing-list" -// service "create-grpsio-service" endpoint HTTP response body for the -// "Conflict" error. -type CreateGrpsioServiceConflictResponseBody struct { +// ListGroupsioServicesInternalServerErrorResponseBody is the type of the +// "mailing-list" service "list-groupsio-services" endpoint HTTP response body +// for the "InternalServerError" error. +type ListGroupsioServicesInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// CreateGrpsioServiceInternalServerErrorResponseBody is the type of the -// "mailing-list" service "create-grpsio-service" endpoint HTTP response body -// for the "InternalServerError" error. -type CreateGrpsioServiceInternalServerErrorResponseBody struct { +// ListGroupsioServicesServiceUnavailableResponseBody is the type of the +// "mailing-list" service "list-groupsio-services" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type ListGroupsioServicesServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// CreateGrpsioServiceNotFoundResponseBody is the type of the "mailing-list" -// service "create-grpsio-service" endpoint HTTP response body for the -// "NotFound" error. -type CreateGrpsioServiceNotFoundResponseBody struct { +// CreateGroupsioServiceBadRequestResponseBody is the type of the +// "mailing-list" service "create-groupsio-service" endpoint HTTP response body +// for the "BadRequest" error. +type CreateGroupsioServiceBadRequestResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// CreateGrpsioServiceServiceUnavailableResponseBody is the type of the -// "mailing-list" service "create-grpsio-service" endpoint HTTP response body -// for the "ServiceUnavailable" error. -type CreateGrpsioServiceServiceUnavailableResponseBody struct { +// CreateGroupsioServiceConflictResponseBody is the type of the "mailing-list" +// service "create-groupsio-service" endpoint HTTP response body for the +// "Conflict" error. +type CreateGroupsioServiceConflictResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioServiceBadRequestResponseBody is the type of the "mailing-list" -// service "get-grpsio-service" endpoint HTTP response body for the -// "BadRequest" error. -type GetGrpsioServiceBadRequestResponseBody struct { +// CreateGroupsioServiceInternalServerErrorResponseBody is the type of the +// "mailing-list" service "create-groupsio-service" endpoint HTTP response body +// for the "InternalServerError" error. +type CreateGroupsioServiceInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioServiceInternalServerErrorResponseBody is the type of the -// "mailing-list" service "get-grpsio-service" endpoint HTTP response body for -// the "InternalServerError" error. -type GetGrpsioServiceInternalServerErrorResponseBody struct { +// CreateGroupsioServiceServiceUnavailableResponseBody is the type of the +// "mailing-list" service "create-groupsio-service" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type CreateGroupsioServiceServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioServiceNotFoundResponseBody is the type of the "mailing-list" -// service "get-grpsio-service" endpoint HTTP response body for the "NotFound" -// error. -type GetGrpsioServiceNotFoundResponseBody struct { +// GetGroupsioServiceInternalServerErrorResponseBody is the type of the +// "mailing-list" service "get-groupsio-service" endpoint HTTP response body +// for the "InternalServerError" error. +type GetGroupsioServiceInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioServiceServiceUnavailableResponseBody is the type of the -// "mailing-list" service "get-grpsio-service" endpoint HTTP response body for -// the "ServiceUnavailable" error. -type GetGrpsioServiceServiceUnavailableResponseBody struct { +// GetGroupsioServiceNotFoundResponseBody is the type of the "mailing-list" +// service "get-groupsio-service" endpoint HTTP response body for the +// "NotFound" error. +type GetGroupsioServiceNotFoundResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioServiceBadRequestResponseBody is the type of the "mailing-list" -// service "update-grpsio-service" endpoint HTTP response body for the -// "BadRequest" error. -type UpdateGrpsioServiceBadRequestResponseBody struct { +// GetGroupsioServiceServiceUnavailableResponseBody is the type of the +// "mailing-list" service "get-groupsio-service" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type GetGroupsioServiceServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioServiceConflictResponseBody is the type of the "mailing-list" -// service "update-grpsio-service" endpoint HTTP response body for the -// "Conflict" error. -type UpdateGrpsioServiceConflictResponseBody struct { +// UpdateGroupsioServiceBadRequestResponseBody is the type of the +// "mailing-list" service "update-groupsio-service" endpoint HTTP response body +// for the "BadRequest" error. +type UpdateGroupsioServiceBadRequestResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioServiceInternalServerErrorResponseBody is the type of the -// "mailing-list" service "update-grpsio-service" endpoint HTTP response body +// UpdateGroupsioServiceInternalServerErrorResponseBody is the type of the +// "mailing-list" service "update-groupsio-service" endpoint HTTP response body // for the "InternalServerError" error. -type UpdateGrpsioServiceInternalServerErrorResponseBody struct { +type UpdateGroupsioServiceInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioServiceNotFoundResponseBody is the type of the "mailing-list" -// service "update-grpsio-service" endpoint HTTP response body for the +// UpdateGroupsioServiceNotFoundResponseBody is the type of the "mailing-list" +// service "update-groupsio-service" endpoint HTTP response body for the // "NotFound" error. -type UpdateGrpsioServiceNotFoundResponseBody struct { +type UpdateGroupsioServiceNotFoundResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioServiceServiceUnavailableResponseBody is the type of the -// "mailing-list" service "update-grpsio-service" endpoint HTTP response body +// UpdateGroupsioServiceServiceUnavailableResponseBody is the type of the +// "mailing-list" service "update-groupsio-service" endpoint HTTP response body // for the "ServiceUnavailable" error. -type UpdateGrpsioServiceServiceUnavailableResponseBody struct { +type UpdateGroupsioServiceServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioServiceBadRequestResponseBody is the type of the "mailing-list" -// service "delete-grpsio-service" endpoint HTTP response body for the -// "BadRequest" error. -type DeleteGrpsioServiceBadRequestResponseBody struct { +// DeleteGroupsioServiceInternalServerErrorResponseBody is the type of the +// "mailing-list" service "delete-groupsio-service" endpoint HTTP response body +// for the "InternalServerError" error. +type DeleteGroupsioServiceInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioServiceConflictResponseBody is the type of the "mailing-list" -// service "delete-grpsio-service" endpoint HTTP response body for the -// "Conflict" error. -type DeleteGrpsioServiceConflictResponseBody struct { +// DeleteGroupsioServiceNotFoundResponseBody is the type of the "mailing-list" +// service "delete-groupsio-service" endpoint HTTP response body for the +// "NotFound" error. +type DeleteGroupsioServiceNotFoundResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioServiceInternalServerErrorResponseBody is the type of the -// "mailing-list" service "delete-grpsio-service" endpoint HTTP response body -// for the "InternalServerError" error. -type DeleteGrpsioServiceInternalServerErrorResponseBody struct { +// DeleteGroupsioServiceServiceUnavailableResponseBody is the type of the +// "mailing-list" service "delete-groupsio-service" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type DeleteGroupsioServiceServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioServiceNotFoundResponseBody is the type of the "mailing-list" -// service "delete-grpsio-service" endpoint HTTP response body for the -// "NotFound" error. -type DeleteGrpsioServiceNotFoundResponseBody struct { +// GetGroupsioServiceProjectsInternalServerErrorResponseBody is the type of the +// "mailing-list" service "get-groupsio-service-projects" endpoint HTTP +// response body for the "InternalServerError" error. +type GetGroupsioServiceProjectsInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioServiceServiceUnavailableResponseBody is the type of the -// "mailing-list" service "delete-grpsio-service" endpoint HTTP response body -// for the "ServiceUnavailable" error. -type DeleteGrpsioServiceServiceUnavailableResponseBody struct { +// GetGroupsioServiceProjectsServiceUnavailableResponseBody is the type of the +// "mailing-list" service "get-groupsio-service-projects" endpoint HTTP +// response body for the "ServiceUnavailable" error. +type GetGroupsioServiceProjectsServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioServiceSettingsBadRequestResponseBody is the type of the -// "mailing-list" service "get-grpsio-service-settings" endpoint HTTP response +// FindParentGroupsioServiceBadRequestResponseBody is the type of the +// "mailing-list" service "find-parent-groupsio-service" endpoint HTTP response // body for the "BadRequest" error. -type GetGrpsioServiceSettingsBadRequestResponseBody struct { +type FindParentGroupsioServiceBadRequestResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioServiceSettingsInternalServerErrorResponseBody is the type of the -// "mailing-list" service "get-grpsio-service-settings" endpoint HTTP response +// FindParentGroupsioServiceInternalServerErrorResponseBody is the type of the +// "mailing-list" service "find-parent-groupsio-service" endpoint HTTP response // body for the "InternalServerError" error. -type GetGrpsioServiceSettingsInternalServerErrorResponseBody struct { +type FindParentGroupsioServiceInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioServiceSettingsNotFoundResponseBody is the type of the -// "mailing-list" service "get-grpsio-service-settings" endpoint HTTP response +// FindParentGroupsioServiceNotFoundResponseBody is the type of the +// "mailing-list" service "find-parent-groupsio-service" endpoint HTTP response // body for the "NotFound" error. -type GetGrpsioServiceSettingsNotFoundResponseBody struct { +type FindParentGroupsioServiceNotFoundResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioServiceSettingsServiceUnavailableResponseBody is the type of the -// "mailing-list" service "get-grpsio-service-settings" endpoint HTTP response +// FindParentGroupsioServiceServiceUnavailableResponseBody is the type of the +// "mailing-list" service "find-parent-groupsio-service" endpoint HTTP response // body for the "ServiceUnavailable" error. -type GetGrpsioServiceSettingsServiceUnavailableResponseBody struct { - // Error message - Message string `form:"message" json:"message" xml:"message"` -} - -// UpdateGrpsioServiceSettingsBadRequestResponseBody is the type of the -// "mailing-list" service "update-grpsio-service-settings" endpoint HTTP -// response body for the "BadRequest" error. -type UpdateGrpsioServiceSettingsBadRequestResponseBody struct { - // Error message - Message string `form:"message" json:"message" xml:"message"` -} - -// UpdateGrpsioServiceSettingsConflictResponseBody is the type of the -// "mailing-list" service "update-grpsio-service-settings" endpoint HTTP -// response body for the "Conflict" error. -type UpdateGrpsioServiceSettingsConflictResponseBody struct { +type FindParentGroupsioServiceServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioServiceSettingsInternalServerErrorResponseBody is the type of -// the "mailing-list" service "update-grpsio-service-settings" endpoint HTTP -// response body for the "InternalServerError" error. -type UpdateGrpsioServiceSettingsInternalServerErrorResponseBody struct { +// ListGroupsioSubgroupsBadRequestResponseBody is the type of the +// "mailing-list" service "list-groupsio-subgroups" endpoint HTTP response body +// for the "BadRequest" error. +type ListGroupsioSubgroupsBadRequestResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioServiceSettingsNotFoundResponseBody is the type of the -// "mailing-list" service "update-grpsio-service-settings" endpoint HTTP -// response body for the "NotFound" error. -type UpdateGrpsioServiceSettingsNotFoundResponseBody struct { +// ListGroupsioSubgroupsInternalServerErrorResponseBody is the type of the +// "mailing-list" service "list-groupsio-subgroups" endpoint HTTP response body +// for the "InternalServerError" error. +type ListGroupsioSubgroupsInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioServiceSettingsServiceUnavailableResponseBody is the type of the -// "mailing-list" service "update-grpsio-service-settings" endpoint HTTP -// response body for the "ServiceUnavailable" error. -type UpdateGrpsioServiceSettingsServiceUnavailableResponseBody struct { +// ListGroupsioSubgroupsServiceUnavailableResponseBody is the type of the +// "mailing-list" service "list-groupsio-subgroups" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type ListGroupsioSubgroupsServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// CreateGrpsioMailingListBadRequestResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list" endpoint HTTP response +// CreateGroupsioSubgroupBadRequestResponseBody is the type of the +// "mailing-list" service "create-groupsio-subgroup" endpoint HTTP response // body for the "BadRequest" error. -type CreateGrpsioMailingListBadRequestResponseBody struct { +type CreateGroupsioSubgroupBadRequestResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// CreateGrpsioMailingListConflictResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list" endpoint HTTP response -// body for the "Conflict" error. -type CreateGrpsioMailingListConflictResponseBody struct { +// CreateGroupsioSubgroupConflictResponseBody is the type of the "mailing-list" +// service "create-groupsio-subgroup" endpoint HTTP response body for the +// "Conflict" error. +type CreateGroupsioSubgroupConflictResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// CreateGrpsioMailingListInternalServerErrorResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list" endpoint HTTP response +// CreateGroupsioSubgroupInternalServerErrorResponseBody is the type of the +// "mailing-list" service "create-groupsio-subgroup" endpoint HTTP response // body for the "InternalServerError" error. -type CreateGrpsioMailingListInternalServerErrorResponseBody struct { +type CreateGroupsioSubgroupInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// CreateGrpsioMailingListNotFoundResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list" endpoint HTTP response -// body for the "NotFound" error. -type CreateGrpsioMailingListNotFoundResponseBody struct { +// CreateGroupsioSubgroupServiceUnavailableResponseBody is the type of the +// "mailing-list" service "create-groupsio-subgroup" endpoint HTTP response +// body for the "ServiceUnavailable" error. +type CreateGroupsioSubgroupServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// CreateGrpsioMailingListServiceUnavailableResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list" endpoint HTTP response -// body for the "ServiceUnavailable" error. -type CreateGrpsioMailingListServiceUnavailableResponseBody struct { +// GetGroupsioSubgroupInternalServerErrorResponseBody is the type of the +// "mailing-list" service "get-groupsio-subgroup" endpoint HTTP response body +// for the "InternalServerError" error. +type GetGroupsioSubgroupInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioMailingListBadRequestResponseBody is the type of the "mailing-list" -// service "get-grpsio-mailing-list" endpoint HTTP response body for the -// "BadRequest" error. -type GetGrpsioMailingListBadRequestResponseBody struct { +// GetGroupsioSubgroupNotFoundResponseBody is the type of the "mailing-list" +// service "get-groupsio-subgroup" endpoint HTTP response body for the +// "NotFound" error. +type GetGroupsioSubgroupNotFoundResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioMailingListInternalServerErrorResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list" endpoint HTTP response body -// for the "InternalServerError" error. -type GetGrpsioMailingListInternalServerErrorResponseBody struct { +// GetGroupsioSubgroupServiceUnavailableResponseBody is the type of the +// "mailing-list" service "get-groupsio-subgroup" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type GetGroupsioSubgroupServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioMailingListNotFoundResponseBody is the type of the "mailing-list" -// service "get-grpsio-mailing-list" endpoint HTTP response body for the -// "NotFound" error. -type GetGrpsioMailingListNotFoundResponseBody struct { +// UpdateGroupsioSubgroupBadRequestResponseBody is the type of the +// "mailing-list" service "update-groupsio-subgroup" endpoint HTTP response +// body for the "BadRequest" error. +type UpdateGroupsioSubgroupBadRequestResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioMailingListServiceUnavailableResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list" endpoint HTTP response body -// for the "ServiceUnavailable" error. -type GetGrpsioMailingListServiceUnavailableResponseBody struct { +// UpdateGroupsioSubgroupInternalServerErrorResponseBody is the type of the +// "mailing-list" service "update-groupsio-subgroup" endpoint HTTP response +// body for the "InternalServerError" error. +type UpdateGroupsioSubgroupInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListBadRequestResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list" endpoint HTTP response -// body for the "BadRequest" error. -type UpdateGrpsioMailingListBadRequestResponseBody struct { +// UpdateGroupsioSubgroupNotFoundResponseBody is the type of the "mailing-list" +// service "update-groupsio-subgroup" endpoint HTTP response body for the +// "NotFound" error. +type UpdateGroupsioSubgroupNotFoundResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListConflictResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list" endpoint HTTP response -// body for the "Conflict" error. -type UpdateGrpsioMailingListConflictResponseBody struct { +// UpdateGroupsioSubgroupServiceUnavailableResponseBody is the type of the +// "mailing-list" service "update-groupsio-subgroup" endpoint HTTP response +// body for the "ServiceUnavailable" error. +type UpdateGroupsioSubgroupServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListInternalServerErrorResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list" endpoint HTTP response +// DeleteGroupsioSubgroupInternalServerErrorResponseBody is the type of the +// "mailing-list" service "delete-groupsio-subgroup" endpoint HTTP response // body for the "InternalServerError" error. -type UpdateGrpsioMailingListInternalServerErrorResponseBody struct { +type DeleteGroupsioSubgroupInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListNotFoundResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list" endpoint HTTP response -// body for the "NotFound" error. -type UpdateGrpsioMailingListNotFoundResponseBody struct { +// DeleteGroupsioSubgroupNotFoundResponseBody is the type of the "mailing-list" +// service "delete-groupsio-subgroup" endpoint HTTP response body for the +// "NotFound" error. +type DeleteGroupsioSubgroupNotFoundResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListServiceUnavailableResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list" endpoint HTTP response +// DeleteGroupsioSubgroupServiceUnavailableResponseBody is the type of the +// "mailing-list" service "delete-groupsio-subgroup" endpoint HTTP response // body for the "ServiceUnavailable" error. -type UpdateGrpsioMailingListServiceUnavailableResponseBody struct { +type DeleteGroupsioSubgroupServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioMailingListBadRequestResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list" endpoint HTTP response +// GetGroupsioSubgroupCountBadRequestResponseBody is the type of the +// "mailing-list" service "get-groupsio-subgroup-count" endpoint HTTP response // body for the "BadRequest" error. -type DeleteGrpsioMailingListBadRequestResponseBody struct { +type GetGroupsioSubgroupCountBadRequestResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioMailingListConflictResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list" endpoint HTTP response -// body for the "Conflict" error. -type DeleteGrpsioMailingListConflictResponseBody struct { +// GetGroupsioSubgroupCountInternalServerErrorResponseBody is the type of the +// "mailing-list" service "get-groupsio-subgroup-count" endpoint HTTP response +// body for the "InternalServerError" error. +type GetGroupsioSubgroupCountInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioMailingListInternalServerErrorResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list" endpoint HTTP response -// body for the "InternalServerError" error. -type DeleteGrpsioMailingListInternalServerErrorResponseBody struct { +// GetGroupsioSubgroupCountServiceUnavailableResponseBody is the type of the +// "mailing-list" service "get-groupsio-subgroup-count" endpoint HTTP response +// body for the "ServiceUnavailable" error. +type GetGroupsioSubgroupCountServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioMailingListNotFoundResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list" endpoint HTTP response -// body for the "NotFound" error. -type DeleteGrpsioMailingListNotFoundResponseBody struct { +// GetGroupsioSubgroupMemberCountInternalServerErrorResponseBody is the type of +// the "mailing-list" service "get-groupsio-subgroup-member-count" endpoint +// HTTP response body for the "InternalServerError" error. +type GetGroupsioSubgroupMemberCountInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioMailingListServiceUnavailableResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list" endpoint HTTP response -// body for the "ServiceUnavailable" error. -type DeleteGrpsioMailingListServiceUnavailableResponseBody struct { +// GetGroupsioSubgroupMemberCountNotFoundResponseBody is the type of the +// "mailing-list" service "get-groupsio-subgroup-member-count" endpoint HTTP +// response body for the "NotFound" error. +type GetGroupsioSubgroupMemberCountNotFoundResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioMailingListSettingsBadRequestResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list-settings" endpoint HTTP -// response body for the "BadRequest" error. -type GetGrpsioMailingListSettingsBadRequestResponseBody struct { +// GetGroupsioSubgroupMemberCountServiceUnavailableResponseBody is the type of +// the "mailing-list" service "get-groupsio-subgroup-member-count" endpoint +// HTTP response body for the "ServiceUnavailable" error. +type GetGroupsioSubgroupMemberCountServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioMailingListSettingsInternalServerErrorResponseBody is the type of -// the "mailing-list" service "get-grpsio-mailing-list-settings" endpoint HTTP -// response body for the "InternalServerError" error. -type GetGrpsioMailingListSettingsInternalServerErrorResponseBody struct { +// ListGroupsioMembersInternalServerErrorResponseBody is the type of the +// "mailing-list" service "list-groupsio-members" endpoint HTTP response body +// for the "InternalServerError" error. +type ListGroupsioMembersInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioMailingListSettingsNotFoundResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list-settings" endpoint HTTP -// response body for the "NotFound" error. -type GetGrpsioMailingListSettingsNotFoundResponseBody struct { +// ListGroupsioMembersNotFoundResponseBody is the type of the "mailing-list" +// service "list-groupsio-members" endpoint HTTP response body for the +// "NotFound" error. +type ListGroupsioMembersNotFoundResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioMailingListSettingsServiceUnavailableResponseBody is the type of -// the "mailing-list" service "get-grpsio-mailing-list-settings" endpoint HTTP -// response body for the "ServiceUnavailable" error. -type GetGrpsioMailingListSettingsServiceUnavailableResponseBody struct { +// ListGroupsioMembersServiceUnavailableResponseBody is the type of the +// "mailing-list" service "list-groupsio-members" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type ListGroupsioMembersServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListSettingsBadRequestResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list-settings" endpoint HTTP -// response body for the "BadRequest" error. -type UpdateGrpsioMailingListSettingsBadRequestResponseBody struct { +// AddGroupsioMemberBadRequestResponseBody is the type of the "mailing-list" +// service "add-groupsio-member" endpoint HTTP response body for the +// "BadRequest" error. +type AddGroupsioMemberBadRequestResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListSettingsConflictResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list-settings" endpoint HTTP -// response body for the "Conflict" error. -type UpdateGrpsioMailingListSettingsConflictResponseBody struct { +// AddGroupsioMemberConflictResponseBody is the type of the "mailing-list" +// service "add-groupsio-member" endpoint HTTP response body for the "Conflict" +// error. +type AddGroupsioMemberConflictResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListSettingsInternalServerErrorResponseBody is the type -// of the "mailing-list" service "update-grpsio-mailing-list-settings" endpoint -// HTTP response body for the "InternalServerError" error. -type UpdateGrpsioMailingListSettingsInternalServerErrorResponseBody struct { +// AddGroupsioMemberInternalServerErrorResponseBody is the type of the +// "mailing-list" service "add-groupsio-member" endpoint HTTP response body for +// the "InternalServerError" error. +type AddGroupsioMemberInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListSettingsNotFoundResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list-settings" endpoint HTTP -// response body for the "NotFound" error. -type UpdateGrpsioMailingListSettingsNotFoundResponseBody struct { +// AddGroupsioMemberNotFoundResponseBody is the type of the "mailing-list" +// service "add-groupsio-member" endpoint HTTP response body for the "NotFound" +// error. +type AddGroupsioMemberNotFoundResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListSettingsServiceUnavailableResponseBody is the type of -// the "mailing-list" service "update-grpsio-mailing-list-settings" endpoint -// HTTP response body for the "ServiceUnavailable" error. -type UpdateGrpsioMailingListSettingsServiceUnavailableResponseBody struct { +// AddGroupsioMemberServiceUnavailableResponseBody is the type of the +// "mailing-list" service "add-groupsio-member" endpoint HTTP response body for +// the "ServiceUnavailable" error. +type AddGroupsioMemberServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// CreateGrpsioMailingListMemberBadRequestResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list-member" endpoint HTTP -// response body for the "BadRequest" error. -type CreateGrpsioMailingListMemberBadRequestResponseBody struct { +// GetGroupsioMemberInternalServerErrorResponseBody is the type of the +// "mailing-list" service "get-groupsio-member" endpoint HTTP response body for +// the "InternalServerError" error. +type GetGroupsioMemberInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// CreateGrpsioMailingListMemberConflictResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list-member" endpoint HTTP -// response body for the "Conflict" error. -type CreateGrpsioMailingListMemberConflictResponseBody struct { +// GetGroupsioMemberNotFoundResponseBody is the type of the "mailing-list" +// service "get-groupsio-member" endpoint HTTP response body for the "NotFound" +// error. +type GetGroupsioMemberNotFoundResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// CreateGrpsioMailingListMemberInternalServerErrorResponseBody is the type of -// the "mailing-list" service "create-grpsio-mailing-list-member" endpoint HTTP -// response body for the "InternalServerError" error. -type CreateGrpsioMailingListMemberInternalServerErrorResponseBody struct { +// GetGroupsioMemberServiceUnavailableResponseBody is the type of the +// "mailing-list" service "get-groupsio-member" endpoint HTTP response body for +// the "ServiceUnavailable" error. +type GetGroupsioMemberServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// CreateGrpsioMailingListMemberNotFoundResponseBody is the type of the -// "mailing-list" service "create-grpsio-mailing-list-member" endpoint HTTP -// response body for the "NotFound" error. -type CreateGrpsioMailingListMemberNotFoundResponseBody struct { - // Error message - Message string `form:"message" json:"message" xml:"message"` -} - -// CreateGrpsioMailingListMemberServiceUnavailableResponseBody is the type of -// the "mailing-list" service "create-grpsio-mailing-list-member" endpoint HTTP -// response body for the "ServiceUnavailable" error. -type CreateGrpsioMailingListMemberServiceUnavailableResponseBody struct { - // Error message - Message string `form:"message" json:"message" xml:"message"` -} - -// GetGrpsioMailingListMemberBadRequestResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list-member" endpoint HTTP -// response body for the "BadRequest" error. -type GetGrpsioMailingListMemberBadRequestResponseBody struct { - // Error message - Message string `form:"message" json:"message" xml:"message"` -} - -// GetGrpsioMailingListMemberInternalServerErrorResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list-member" endpoint HTTP -// response body for the "InternalServerError" error. -type GetGrpsioMailingListMemberInternalServerErrorResponseBody struct { - // Error message - Message string `form:"message" json:"message" xml:"message"` -} - -// GetGrpsioMailingListMemberNotFoundResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list-member" endpoint HTTP -// response body for the "NotFound" error. -type GetGrpsioMailingListMemberNotFoundResponseBody struct { +// UpdateGroupsioMemberBadRequestResponseBody is the type of the "mailing-list" +// service "update-groupsio-member" endpoint HTTP response body for the +// "BadRequest" error. +type UpdateGroupsioMemberBadRequestResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GetGrpsioMailingListMemberServiceUnavailableResponseBody is the type of the -// "mailing-list" service "get-grpsio-mailing-list-member" endpoint HTTP -// response body for the "ServiceUnavailable" error. -type GetGrpsioMailingListMemberServiceUnavailableResponseBody struct { +// UpdateGroupsioMemberInternalServerErrorResponseBody is the type of the +// "mailing-list" service "update-groupsio-member" endpoint HTTP response body +// for the "InternalServerError" error. +type UpdateGroupsioMemberInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListMemberBadRequestResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list-member" endpoint HTTP -// response body for the "BadRequest" error. -type UpdateGrpsioMailingListMemberBadRequestResponseBody struct { +// UpdateGroupsioMemberNotFoundResponseBody is the type of the "mailing-list" +// service "update-groupsio-member" endpoint HTTP response body for the +// "NotFound" error. +type UpdateGroupsioMemberNotFoundResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListMemberConflictResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list-member" endpoint HTTP -// response body for the "Conflict" error. -type UpdateGrpsioMailingListMemberConflictResponseBody struct { +// UpdateGroupsioMemberServiceUnavailableResponseBody is the type of the +// "mailing-list" service "update-groupsio-member" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type UpdateGroupsioMemberServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListMemberInternalServerErrorResponseBody is the type of -// the "mailing-list" service "update-grpsio-mailing-list-member" endpoint HTTP -// response body for the "InternalServerError" error. -type UpdateGrpsioMailingListMemberInternalServerErrorResponseBody struct { +// DeleteGroupsioMemberInternalServerErrorResponseBody is the type of the +// "mailing-list" service "delete-groupsio-member" endpoint HTTP response body +// for the "InternalServerError" error. +type DeleteGroupsioMemberInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListMemberNotFoundResponseBody is the type of the -// "mailing-list" service "update-grpsio-mailing-list-member" endpoint HTTP -// response body for the "NotFound" error. -type UpdateGrpsioMailingListMemberNotFoundResponseBody struct { +// DeleteGroupsioMemberNotFoundResponseBody is the type of the "mailing-list" +// service "delete-groupsio-member" endpoint HTTP response body for the +// "NotFound" error. +type DeleteGroupsioMemberNotFoundResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UpdateGrpsioMailingListMemberServiceUnavailableResponseBody is the type of -// the "mailing-list" service "update-grpsio-mailing-list-member" endpoint HTTP -// response body for the "ServiceUnavailable" error. -type UpdateGrpsioMailingListMemberServiceUnavailableResponseBody struct { +// DeleteGroupsioMemberServiceUnavailableResponseBody is the type of the +// "mailing-list" service "delete-groupsio-member" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type DeleteGroupsioMemberServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioMailingListMemberBadRequestResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list-member" endpoint HTTP -// response body for the "BadRequest" error. -type DeleteGrpsioMailingListMemberBadRequestResponseBody struct { +// InviteGroupsioMembersBadRequestResponseBody is the type of the +// "mailing-list" service "invite-groupsio-members" endpoint HTTP response body +// for the "BadRequest" error. +type InviteGroupsioMembersBadRequestResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioMailingListMemberConflictResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list-member" endpoint HTTP -// response body for the "Conflict" error. -type DeleteGrpsioMailingListMemberConflictResponseBody struct { +// InviteGroupsioMembersInternalServerErrorResponseBody is the type of the +// "mailing-list" service "invite-groupsio-members" endpoint HTTP response body +// for the "InternalServerError" error. +type InviteGroupsioMembersInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioMailingListMemberInternalServerErrorResponseBody is the type of -// the "mailing-list" service "delete-grpsio-mailing-list-member" endpoint HTTP -// response body for the "InternalServerError" error. -type DeleteGrpsioMailingListMemberInternalServerErrorResponseBody struct { +// InviteGroupsioMembersNotFoundResponseBody is the type of the "mailing-list" +// service "invite-groupsio-members" endpoint HTTP response body for the +// "NotFound" error. +type InviteGroupsioMembersNotFoundResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioMailingListMemberNotFoundResponseBody is the type of the -// "mailing-list" service "delete-grpsio-mailing-list-member" endpoint HTTP -// response body for the "NotFound" error. -type DeleteGrpsioMailingListMemberNotFoundResponseBody struct { +// InviteGroupsioMembersServiceUnavailableResponseBody is the type of the +// "mailing-list" service "invite-groupsio-members" endpoint HTTP response body +// for the "ServiceUnavailable" error. +type InviteGroupsioMembersServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// DeleteGrpsioMailingListMemberServiceUnavailableResponseBody is the type of -// the "mailing-list" service "delete-grpsio-mailing-list-member" endpoint HTTP -// response body for the "ServiceUnavailable" error. -type DeleteGrpsioMailingListMemberServiceUnavailableResponseBody struct { +// CheckGroupsioSubscriberBadRequestResponseBody is the type of the +// "mailing-list" service "check-groupsio-subscriber" endpoint HTTP response +// body for the "BadRequest" error. +type CheckGroupsioSubscriberBadRequestResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GroupsioWebhookBadRequestResponseBody is the type of the "mailing-list" -// service "groupsio-webhook" endpoint HTTP response body for the "BadRequest" -// error. -type GroupsioWebhookBadRequestResponseBody struct { +// CheckGroupsioSubscriberInternalServerErrorResponseBody is the type of the +// "mailing-list" service "check-groupsio-subscriber" endpoint HTTP response +// body for the "InternalServerError" error. +type CheckGroupsioSubscriberInternalServerErrorResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// GroupsioWebhookUnauthorizedResponseBody is the type of the "mailing-list" -// service "groupsio-webhook" endpoint HTTP response body for the -// "Unauthorized" error. -type GroupsioWebhookUnauthorizedResponseBody struct { +// CheckGroupsioSubscriberServiceUnavailableResponseBody is the type of the +// "mailing-list" service "check-groupsio-subscriber" endpoint HTTP response +// body for the "ServiceUnavailable" error. +type CheckGroupsioSubscriberServiceUnavailableResponseBody struct { // Error message Message string `form:"message" json:"message" xml:"message"` } -// UserInfoResponseBody is used to define fields on response body types. -type UserInfoResponseBody struct { - // The full name of the user - Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` - // The email address of the user - Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` - // The username/LFID of the user - Username *string `form:"username,omitempty" json:"username,omitempty" xml:"username,omitempty"` - // The avatar URL of the user - Avatar *string `form:"avatar,omitempty" json:"avatar,omitempty" xml:"avatar,omitempty"` -} - -// GrpsIoServiceWithReadonlyAttributesResponseBody is used to define fields on -// response body types. -type GrpsIoServiceWithReadonlyAttributesResponseBody struct { - // Service UID -- unique identifier for the service - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` +// GroupsioServiceResponseBody is used to define fields on response body types. +type GroupsioServiceResponseBody struct { + // Service ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` // Service type - Type string `form:"type" json:"type" xml:"type"` - // Service domain - Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` + // Service domain + Domain *string `form:"domain,omitempty" json:"domain,omitempty" xml:"domain,omitempty"` + // Email prefix + Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` // Service status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // List of global owner email addresses (required for primary, forbidden for - // shared) - GlobalOwners []string `form:"global_owners,omitempty" json:"global_owners,omitempty" xml:"global_owners,omitempty"` - // Email prefix (required for formation and shared, forbidden for primary) - Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty" xml:"prefix,omitempty"` - // Parent primary service UID (automatically set for shared type services) - ParentServiceUID *string `form:"parent_service_uid,omitempty" json:"parent_service_uid,omitempty" xml:"parent_service_uid,omitempty"` - // Project slug identifier - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // LFXv2 Project UID - ProjectUID string `form:"project_uid" json:"project_uid" xml:"project_uid"` - // Service URL - URL *string `form:"url,omitempty" json:"url,omitempty" xml:"url,omitempty"` - // GroupsIO group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Whether the service is publicly accessible - Public bool `form:"public" json:"public" xml:"public"` - // Project name (read-only) - ProjectName *string `form:"project_name,omitempty" json:"project_name,omitempty" xml:"project_name,omitempty"` - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) - UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// GrpsIoServiceSettingsResponseBody is used to define fields on response body -// types. -type GrpsIoServiceSettingsResponseBody struct { - // Service UID -- unique identifier for the service - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` - // The timestamp when the service was last reviewed in RFC3339 format - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // The user ID who last reviewed this service - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` - // The user ID who last audited the service - LastAuditedBy *string `form:"last_audited_by,omitempty" json:"last_audited_by,omitempty" xml:"last_audited_by,omitempty"` - // The timestamp when the service was last audited - LastAuditedTime *string `form:"last_audited_time,omitempty" json:"last_audited_time,omitempty" xml:"last_audited_time,omitempty"` - // The timestamp when the service was created (read-only) - CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` } -// CommitteeResponseBody is used to define fields on response body types. -type CommitteeResponseBody struct { - // Committee UUID - UID string `form:"uid" json:"uid" xml:"uid"` - // Committee name (read-only, populated by server) - Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` - // Committee member voting statuses that determine which members are synced - AllowedVotingStatuses []string `form:"allowed_voting_statuses,omitempty" json:"allowed_voting_statuses,omitempty" xml:"allowed_voting_statuses,omitempty"` -} - -// GrpsIoMailingListWithReadonlyAttributesResponseBody is used to define fields -// on response body types. -type GrpsIoMailingListWithReadonlyAttributesResponseBody struct { - // Mailing list UID -- unique identifier for the mailing list - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Mailing list group name - GroupName *string `form:"group_name,omitempty" json:"group_name,omitempty" xml:"group_name,omitempty"` - // Mailing list group ID +// GroupsioSubgroupResponseBody is used to define fields on response body types. +type GroupsioSubgroupResponseBody struct { + // Subgroup ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // LFX v2 project UID + ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` + // LFX v2 committee UID + CommitteeUID *string `form:"committee_uid,omitempty" json:"committee_uid,omitempty" xml:"committee_uid,omitempty"` + // GroupsIO group ID GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // Whether the mailing list is publicly accessible - Public bool `form:"public" json:"public" xml:"public"` - // Mailing list type - Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` - // public: Anyone can join. approval_required: Users must request to join and - // be approved. invite_only: Only invited users can join. - AudienceAccess string `form:"audience_access" json:"audience_access" xml:"audience_access"` - // Committees associated with this mailing list (OR logic for access control) - Committees []*CommitteeResponseBody `form:"committees,omitempty" json:"committees,omitempty" xml:"committees,omitempty"` - // Mailing list description (11-500 characters) + // Subgroup name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + // Subgroup description Description *string `form:"description,omitempty" json:"description,omitempty" xml:"description,omitempty"` - // Mailing list title - Title *string `form:"title,omitempty" json:"title,omitempty" xml:"title,omitempty"` - // Subject tag prefix - SubjectTag *string `form:"subject_tag,omitempty" json:"subject_tag,omitempty" xml:"subject_tag,omitempty"` - // Service UUID - ServiceUID *string `form:"service_uid,omitempty" json:"service_uid,omitempty" xml:"service_uid,omitempty"` - // Number of subscribers in this mailing list (read-only, maintained by service) - SubscriberCount *int `form:"subscriber_count,omitempty" json:"subscriber_count,omitempty" xml:"subscriber_count,omitempty"` - // LFXv2 Project UID (inherited from parent service) - ProjectUID *string `form:"project_uid,omitempty" json:"project_uid,omitempty" xml:"project_uid,omitempty"` - // Project name (read-only) - ProjectName *string `form:"project_name,omitempty" json:"project_name,omitempty" xml:"project_name,omitempty"` - // Project slug identifier (read-only) - ProjectSlug *string `form:"project_slug,omitempty" json:"project_slug,omitempty" xml:"project_slug,omitempty"` - // The timestamp when the service was created (read-only) - CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) - UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` -} - -// GrpsIoMailingListSettingsResponseBody is used to define fields on response -// body types. -type GrpsIoMailingListSettingsResponseBody struct { - // Mailing list UID -- unique identifier for the mailing list - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` - // The timestamp when the service was last reviewed in RFC3339 format - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // The user ID who last reviewed this service - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` - // The user ID who last audited the service - LastAuditedBy *string `form:"last_audited_by,omitempty" json:"last_audited_by,omitempty" xml:"last_audited_by,omitempty"` - // The timestamp when the service was last audited - LastAuditedTime *string `form:"last_audited_time,omitempty" json:"last_audited_time,omitempty" xml:"last_audited_time,omitempty"` - // The timestamp when the service was created (read-only) + // Subgroup type + Type *string `form:"type,omitempty" json:"type,omitempty" xml:"type,omitempty"` + // Audience access setting + AudienceAccess *string `form:"audience_access,omitempty" json:"audience_access,omitempty" xml:"audience_access,omitempty"` + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` } -// GrpsIoMemberWithReadonlyAttributesResponseBody is used to define fields on -// response body types. -type GrpsIoMemberWithReadonlyAttributesResponseBody struct { - // Member UID - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Mailing list UID - MailingListUID *string `form:"mailing_list_uid,omitempty" json:"mailing_list_uid,omitempty" xml:"mailing_list_uid,omitempty"` - // Member username - Username *string `form:"username,omitempty" json:"username,omitempty" xml:"username,omitempty"` +// GroupsioMemberResponseBody is used to define fields on response body types. +type GroupsioMemberResponseBody struct { + // Member ID + ID *string `form:"id,omitempty" json:"id,omitempty" xml:"id,omitempty"` + // Subgroup ID + SubgroupID *string `form:"subgroup_id,omitempty" json:"subgroup_id,omitempty" xml:"subgroup_id,omitempty"` + // Member email address + Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` + // Member display name + Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` // Member first name FirstName *string `form:"first_name,omitempty" json:"first_name,omitempty" xml:"first_name,omitempty"` // Member last name LastName *string `form:"last_name,omitempty" json:"last_name,omitempty" xml:"last_name,omitempty"` - // Member email address - Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` - // Member organization - Organization *string `form:"organization,omitempty" json:"organization,omitempty" xml:"organization,omitempty"` - // Member job title - JobTitle *string `form:"job_title,omitempty" json:"job_title,omitempty" xml:"job_title,omitempty"` - // Member type - MemberType string `form:"member_type" json:"member_type" xml:"member_type"` - // Email delivery mode - DeliveryMode string `form:"delivery_mode" json:"delivery_mode" xml:"delivery_mode"` // Moderation status - ModStatus string `form:"mod_status" json:"mod_status" xml:"mod_status"` - // Last reviewed timestamp - LastReviewedAt *string `form:"last_reviewed_at,omitempty" json:"last_reviewed_at,omitempty" xml:"last_reviewed_at,omitempty"` - // Last reviewed by user ID - LastReviewedBy *string `form:"last_reviewed_by,omitempty" json:"last_reviewed_by,omitempty" xml:"last_reviewed_by,omitempty"` + ModStatus *string `form:"mod_status,omitempty" json:"mod_status,omitempty" xml:"mod_status,omitempty"` + // Email delivery mode + DeliveryMode *string `form:"delivery_mode,omitempty" json:"delivery_mode,omitempty" xml:"delivery_mode,omitempty"` // Member status Status *string `form:"status,omitempty" json:"status,omitempty" xml:"status,omitempty"` - // Groups.io member ID - MemberID *int64 `form:"member_id,omitempty" json:"member_id,omitempty" xml:"member_id,omitempty"` - // Groups.io group ID - GroupID *int64 `form:"group_id,omitempty" json:"group_id,omitempty" xml:"group_id,omitempty"` - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string `form:"created_at,omitempty" json:"created_at,omitempty" xml:"created_at,omitempty"` - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string `form:"updated_at,omitempty" json:"updated_at,omitempty" xml:"updated_at,omitempty"` - // Manager users who can edit/modify this resource - Writers []*UserInfoResponseBody `form:"writers,omitempty" json:"writers,omitempty" xml:"writers,omitempty"` - // Auditor users who can audit this resource - Auditors []*UserInfoResponseBody `form:"auditors,omitempty" json:"auditors,omitempty" xml:"auditors,omitempty"` -} - -// UserInfoRequestBody is used to define fields on request body types. -type UserInfoRequestBody struct { - // The full name of the user - Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` - // The email address of the user - Email *string `form:"email,omitempty" json:"email,omitempty" xml:"email,omitempty"` - // The username/LFID of the user - Username *string `form:"username,omitempty" json:"username,omitempty" xml:"username,omitempty"` - // The avatar URL of the user - Avatar *string `form:"avatar,omitempty" json:"avatar,omitempty" xml:"avatar,omitempty"` } -// CommitteeRequestBody is used to define fields on request body types. -type CommitteeRequestBody struct { - // Committee UUID - UID *string `form:"uid,omitempty" json:"uid,omitempty" xml:"uid,omitempty"` - // Committee name (read-only, populated by server) - Name *string `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` - // Committee member voting statuses that determine which members are synced - AllowedVotingStatuses []string `form:"allowed_voting_statuses,omitempty" json:"allowed_voting_statuses,omitempty" xml:"allowed_voting_statuses,omitempty"` -} - -// NewCreateGrpsioServiceResponseBody builds the HTTP response body from the -// result of the "create-grpsio-service" endpoint of the "mailing-list" service. -func NewCreateGrpsioServiceResponseBody(res *mailinglist.GrpsIoServiceFull) *CreateGrpsioServiceResponseBody { - body := &CreateGrpsioServiceResponseBody{ - UID: res.UID, - Type: res.Type, - Domain: res.Domain, - GroupID: res.GroupID, - Status: res.Status, - Prefix: res.Prefix, - ParentServiceUID: res.ParentServiceUID, - ProjectSlug: res.ProjectSlug, - ProjectUID: res.ProjectUID, - URL: res.URL, - GroupName: res.GroupName, - Public: res.Public, - ProjectName: res.ProjectName, - CreatedAt: res.CreatedAt, - UpdatedAt: res.UpdatedAt, - } - if res.GlobalOwners != nil { - body.GlobalOwners = make([]string, len(res.GlobalOwners)) - for i, val := range res.GlobalOwners { - body.GlobalOwners[i] = val - } - } - { - var zero bool - if body.Public == zero { - body.Public = false - } - } - if res.Writers != nil { - body.Writers = make([]*UserInfoResponseBody, len(res.Writers)) - for i, val := range res.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } +// NewListGroupsioServicesResponseBody builds the HTTP response body from the +// result of the "list-groupsio-services" endpoint of the "mailing-list" +// service. +func NewListGroupsioServicesResponseBody(res *mailinglist.GroupsioServiceList) *ListGroupsioServicesResponseBody { + body := &ListGroupsioServicesResponseBody{ + Total: res.Total, } - if res.Auditors != nil { - body.Auditors = make([]*UserInfoResponseBody, len(res.Auditors)) - for i, val := range res.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) + if res.Items != nil { + body.Items = make([]*GroupsioServiceResponseBody, len(res.Items)) + for i, val := range res.Items { + body.Items[i] = marshalMailinglistGroupsioServiceToGroupsioServiceResponseBody(val) } } return body } -// NewGetGrpsioServiceResponseBody builds the HTTP response body from the -// result of the "get-grpsio-service" endpoint of the "mailing-list" service. -func NewGetGrpsioServiceResponseBody(res *mailinglist.GetGrpsioServiceResult) *GetGrpsioServiceResponseBody { - body := &GetGrpsioServiceResponseBody{ - UID: res.Service.UID, - Type: res.Service.Type, - Domain: res.Service.Domain, - GroupID: res.Service.GroupID, - Status: res.Service.Status, - Prefix: res.Service.Prefix, - ParentServiceUID: res.Service.ParentServiceUID, - ProjectSlug: res.Service.ProjectSlug, - ProjectUID: res.Service.ProjectUID, - URL: res.Service.URL, - GroupName: res.Service.GroupName, - Public: res.Service.Public, - ProjectName: res.Service.ProjectName, - CreatedAt: res.Service.CreatedAt, - UpdatedAt: res.Service.UpdatedAt, - } - if res.Service.GlobalOwners != nil { - body.GlobalOwners = make([]string, len(res.Service.GlobalOwners)) - for i, val := range res.Service.GlobalOwners { - body.GlobalOwners[i] = val - } - } - { - var zero bool - if body.Public == zero { - body.Public = false - } - } - if res.Service.Writers != nil { - body.Writers = make([]*UserInfoResponseBody, len(res.Service.Writers)) - for i, val := range res.Service.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } - } - if res.Service.Auditors != nil { - body.Auditors = make([]*UserInfoResponseBody, len(res.Service.Auditors)) - for i, val := range res.Service.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } +// NewCreateGroupsioServiceResponseBody builds the HTTP response body from the +// result of the "create-groupsio-service" endpoint of the "mailing-list" +// service. +func NewCreateGroupsioServiceResponseBody(res *mailinglist.GroupsioService) *CreateGroupsioServiceResponseBody { + body := &CreateGroupsioServiceResponseBody{ + ID: res.ID, + ProjectUID: res.ProjectUID, + Type: res.Type, + GroupID: res.GroupID, + Domain: res.Domain, + Prefix: res.Prefix, + Status: res.Status, + CreatedAt: res.CreatedAt, + UpdatedAt: res.UpdatedAt, } return body } -// NewUpdateGrpsioServiceResponseBody builds the HTTP response body from the -// result of the "update-grpsio-service" endpoint of the "mailing-list" service. -func NewUpdateGrpsioServiceResponseBody(res *mailinglist.GrpsIoServiceWithReadonlyAttributes) *UpdateGrpsioServiceResponseBody { - body := &UpdateGrpsioServiceResponseBody{ - UID: res.UID, - Type: res.Type, - Domain: res.Domain, - GroupID: res.GroupID, - Status: res.Status, - Prefix: res.Prefix, - ParentServiceUID: res.ParentServiceUID, - ProjectSlug: res.ProjectSlug, - ProjectUID: res.ProjectUID, - URL: res.URL, - GroupName: res.GroupName, - Public: res.Public, - ProjectName: res.ProjectName, - CreatedAt: res.CreatedAt, - UpdatedAt: res.UpdatedAt, - } - if res.GlobalOwners != nil { - body.GlobalOwners = make([]string, len(res.GlobalOwners)) - for i, val := range res.GlobalOwners { - body.GlobalOwners[i] = val - } - } - { - var zero bool - if body.Public == zero { - body.Public = false - } - } - if res.Writers != nil { - body.Writers = make([]*UserInfoResponseBody, len(res.Writers)) - for i, val := range res.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } - } - if res.Auditors != nil { - body.Auditors = make([]*UserInfoResponseBody, len(res.Auditors)) - for i, val := range res.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } +// NewGetGroupsioServiceResponseBody builds the HTTP response body from the +// result of the "get-groupsio-service" endpoint of the "mailing-list" service. +func NewGetGroupsioServiceResponseBody(res *mailinglist.GroupsioService) *GetGroupsioServiceResponseBody { + body := &GetGroupsioServiceResponseBody{ + ID: res.ID, + ProjectUID: res.ProjectUID, + Type: res.Type, + GroupID: res.GroupID, + Domain: res.Domain, + Prefix: res.Prefix, + Status: res.Status, + CreatedAt: res.CreatedAt, + UpdatedAt: res.UpdatedAt, } return body } -// NewGetGrpsioServiceSettingsResponseBody builds the HTTP response body from -// the result of the "get-grpsio-service-settings" endpoint of the -// "mailing-list" service. -func NewGetGrpsioServiceSettingsResponseBody(res *mailinglist.GetGrpsioServiceSettingsResult) *GetGrpsioServiceSettingsResponseBody { - body := &GetGrpsioServiceSettingsResponseBody{ - UID: res.ServiceSettings.UID, - LastReviewedAt: res.ServiceSettings.LastReviewedAt, - LastReviewedBy: res.ServiceSettings.LastReviewedBy, - LastAuditedBy: res.ServiceSettings.LastAuditedBy, - LastAuditedTime: res.ServiceSettings.LastAuditedTime, - CreatedAt: res.ServiceSettings.CreatedAt, - UpdatedAt: res.ServiceSettings.UpdatedAt, - } - if res.ServiceSettings.Writers != nil { - body.Writers = make([]*UserInfoResponseBody, len(res.ServiceSettings.Writers)) - for i, val := range res.ServiceSettings.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } - } - if res.ServiceSettings.Auditors != nil { - body.Auditors = make([]*UserInfoResponseBody, len(res.ServiceSettings.Auditors)) - for i, val := range res.ServiceSettings.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } +// NewUpdateGroupsioServiceResponseBody builds the HTTP response body from the +// result of the "update-groupsio-service" endpoint of the "mailing-list" +// service. +func NewUpdateGroupsioServiceResponseBody(res *mailinglist.GroupsioService) *UpdateGroupsioServiceResponseBody { + body := &UpdateGroupsioServiceResponseBody{ + ID: res.ID, + ProjectUID: res.ProjectUID, + Type: res.Type, + GroupID: res.GroupID, + Domain: res.Domain, + Prefix: res.Prefix, + Status: res.Status, + CreatedAt: res.CreatedAt, + UpdatedAt: res.UpdatedAt, } return body } -// NewUpdateGrpsioServiceSettingsResponseBody builds the HTTP response body -// from the result of the "update-grpsio-service-settings" endpoint of the +// NewGetGroupsioServiceProjectsResponseBody builds the HTTP response body from +// the result of the "get-groupsio-service-projects" endpoint of the // "mailing-list" service. -func NewUpdateGrpsioServiceSettingsResponseBody(res *mailinglist.GrpsIoServiceSettings) *UpdateGrpsioServiceSettingsResponseBody { - body := &UpdateGrpsioServiceSettingsResponseBody{ - UID: res.UID, - LastReviewedAt: res.LastReviewedAt, - LastReviewedBy: res.LastReviewedBy, - LastAuditedBy: res.LastAuditedBy, - LastAuditedTime: res.LastAuditedTime, - CreatedAt: res.CreatedAt, - UpdatedAt: res.UpdatedAt, - } - if res.Writers != nil { - body.Writers = make([]*UserInfoResponseBody, len(res.Writers)) - for i, val := range res.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } - } - if res.Auditors != nil { - body.Auditors = make([]*UserInfoResponseBody, len(res.Auditors)) - for i, val := range res.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) +func NewGetGroupsioServiceProjectsResponseBody(res *mailinglist.GroupsioProjectsResponse) *GetGroupsioServiceProjectsResponseBody { + body := &GetGroupsioServiceProjectsResponseBody{} + if res.Projects != nil { + body.Projects = make([]string, len(res.Projects)) + for i, val := range res.Projects { + body.Projects[i] = val } } return body } -// NewCreateGrpsioMailingListResponseBody builds the HTTP response body from -// the result of the "create-grpsio-mailing-list" endpoint of the +// NewFindParentGroupsioServiceResponseBody builds the HTTP response body from +// the result of the "find-parent-groupsio-service" endpoint of the // "mailing-list" service. -func NewCreateGrpsioMailingListResponseBody(res *mailinglist.GrpsIoMailingListFull) *CreateGrpsioMailingListResponseBody { - body := &CreateGrpsioMailingListResponseBody{ - UID: res.UID, - GroupName: res.GroupName, - GroupID: res.GroupID, - Public: res.Public, - Type: res.Type, - AudienceAccess: res.AudienceAccess, - Description: res.Description, - Title: res.Title, - SubjectTag: res.SubjectTag, - ServiceUID: res.ServiceUID, - SubscriberCount: res.SubscriberCount, - ProjectUID: res.ProjectUID, - ProjectName: res.ProjectName, - ProjectSlug: res.ProjectSlug, - CreatedAt: res.CreatedAt, - UpdatedAt: res.UpdatedAt, - } - { - var zero bool - if body.Public == zero { - body.Public = false - } - } - { - var zero string - if body.AudienceAccess == zero { - body.AudienceAccess = "public" - } - } - if res.Committees != nil { - body.Committees = make([]*CommitteeResponseBody, len(res.Committees)) - for i, val := range res.Committees { - body.Committees[i] = marshalMailinglistCommitteeToCommitteeResponseBody(val) - } - } - if res.Writers != nil { - body.Writers = make([]*UserInfoResponseBody, len(res.Writers)) - for i, val := range res.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } - } - if res.Auditors != nil { - body.Auditors = make([]*UserInfoResponseBody, len(res.Auditors)) - for i, val := range res.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } +func NewFindParentGroupsioServiceResponseBody(res *mailinglist.GroupsioService) *FindParentGroupsioServiceResponseBody { + body := &FindParentGroupsioServiceResponseBody{ + ID: res.ID, + ProjectUID: res.ProjectUID, + Type: res.Type, + GroupID: res.GroupID, + Domain: res.Domain, + Prefix: res.Prefix, + Status: res.Status, + CreatedAt: res.CreatedAt, + UpdatedAt: res.UpdatedAt, } return body } -// NewGetGrpsioMailingListResponseBody builds the HTTP response body from the -// result of the "get-grpsio-mailing-list" endpoint of the "mailing-list" +// NewListGroupsioSubgroupsResponseBody builds the HTTP response body from the +// result of the "list-groupsio-subgroups" endpoint of the "mailing-list" // service. -func NewGetGrpsioMailingListResponseBody(res *mailinglist.GetGrpsioMailingListResult) *GetGrpsioMailingListResponseBody { - body := &GetGrpsioMailingListResponseBody{ - UID: res.MailingList.UID, - GroupName: res.MailingList.GroupName, - GroupID: res.MailingList.GroupID, - Public: res.MailingList.Public, - Type: res.MailingList.Type, - AudienceAccess: res.MailingList.AudienceAccess, - Description: res.MailingList.Description, - Title: res.MailingList.Title, - SubjectTag: res.MailingList.SubjectTag, - ServiceUID: res.MailingList.ServiceUID, - SubscriberCount: res.MailingList.SubscriberCount, - ProjectUID: res.MailingList.ProjectUID, - ProjectName: res.MailingList.ProjectName, - ProjectSlug: res.MailingList.ProjectSlug, - CreatedAt: res.MailingList.CreatedAt, - UpdatedAt: res.MailingList.UpdatedAt, - } - { - var zero bool - if body.Public == zero { - body.Public = false - } - } - { - var zero string - if body.AudienceAccess == zero { - body.AudienceAccess = "public" - } +func NewListGroupsioSubgroupsResponseBody(res *mailinglist.GroupsioSubgroupList) *ListGroupsioSubgroupsResponseBody { + body := &ListGroupsioSubgroupsResponseBody{ + Total: res.Total, } - if res.MailingList.Committees != nil { - body.Committees = make([]*CommitteeResponseBody, len(res.MailingList.Committees)) - for i, val := range res.MailingList.Committees { - body.Committees[i] = marshalMailinglistCommitteeToCommitteeResponseBody(val) + if res.Items != nil { + body.Items = make([]*GroupsioSubgroupResponseBody, len(res.Items)) + for i, val := range res.Items { + body.Items[i] = marshalMailinglistGroupsioSubgroupToGroupsioSubgroupResponseBody(val) } } return body } -// NewUpdateGrpsioMailingListResponseBody builds the HTTP response body from -// the result of the "update-grpsio-mailing-list" endpoint of the -// "mailing-list" service. -func NewUpdateGrpsioMailingListResponseBody(res *mailinglist.GrpsIoMailingListWithReadonlyAttributes) *UpdateGrpsioMailingListResponseBody { - body := &UpdateGrpsioMailingListResponseBody{ - UID: res.UID, - GroupName: res.GroupName, - GroupID: res.GroupID, - Public: res.Public, - Type: res.Type, - AudienceAccess: res.AudienceAccess, - Description: res.Description, - Title: res.Title, - SubjectTag: res.SubjectTag, - ServiceUID: res.ServiceUID, - SubscriberCount: res.SubscriberCount, - ProjectUID: res.ProjectUID, - ProjectName: res.ProjectName, - ProjectSlug: res.ProjectSlug, - CreatedAt: res.CreatedAt, - UpdatedAt: res.UpdatedAt, - } - { - var zero bool - if body.Public == zero { - body.Public = false - } - } - { - var zero string - if body.AudienceAccess == zero { - body.AudienceAccess = "public" - } - } - if res.Committees != nil { - body.Committees = make([]*CommitteeResponseBody, len(res.Committees)) - for i, val := range res.Committees { - body.Committees[i] = marshalMailinglistCommitteeToCommitteeResponseBody(val) - } +// NewCreateGroupsioSubgroupResponseBody builds the HTTP response body from the +// result of the "create-groupsio-subgroup" endpoint of the "mailing-list" +// service. +func NewCreateGroupsioSubgroupResponseBody(res *mailinglist.GroupsioSubgroup) *CreateGroupsioSubgroupResponseBody { + body := &CreateGroupsioSubgroupResponseBody{ + ID: res.ID, + ProjectUID: res.ProjectUID, + CommitteeUID: res.CommitteeUID, + GroupID: res.GroupID, + Name: res.Name, + Description: res.Description, + Type: res.Type, + AudienceAccess: res.AudienceAccess, + CreatedAt: res.CreatedAt, + UpdatedAt: res.UpdatedAt, } return body } -// NewGetGrpsioMailingListSettingsResponseBody builds the HTTP response body -// from the result of the "get-grpsio-mailing-list-settings" endpoint of the -// "mailing-list" service. -func NewGetGrpsioMailingListSettingsResponseBody(res *mailinglist.GetGrpsioMailingListSettingsResult) *GetGrpsioMailingListSettingsResponseBody { - body := &GetGrpsioMailingListSettingsResponseBody{ - UID: res.MailingListSettings.UID, - LastReviewedAt: res.MailingListSettings.LastReviewedAt, - LastReviewedBy: res.MailingListSettings.LastReviewedBy, - LastAuditedBy: res.MailingListSettings.LastAuditedBy, - LastAuditedTime: res.MailingListSettings.LastAuditedTime, - CreatedAt: res.MailingListSettings.CreatedAt, - UpdatedAt: res.MailingListSettings.UpdatedAt, - } - if res.MailingListSettings.Writers != nil { - body.Writers = make([]*UserInfoResponseBody, len(res.MailingListSettings.Writers)) - for i, val := range res.MailingListSettings.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } - } - if res.MailingListSettings.Auditors != nil { - body.Auditors = make([]*UserInfoResponseBody, len(res.MailingListSettings.Auditors)) - for i, val := range res.MailingListSettings.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } +// NewGetGroupsioSubgroupResponseBody builds the HTTP response body from the +// result of the "get-groupsio-subgroup" endpoint of the "mailing-list" service. +func NewGetGroupsioSubgroupResponseBody(res *mailinglist.GroupsioSubgroup) *GetGroupsioSubgroupResponseBody { + body := &GetGroupsioSubgroupResponseBody{ + ID: res.ID, + ProjectUID: res.ProjectUID, + CommitteeUID: res.CommitteeUID, + GroupID: res.GroupID, + Name: res.Name, + Description: res.Description, + Type: res.Type, + AudienceAccess: res.AudienceAccess, + CreatedAt: res.CreatedAt, + UpdatedAt: res.UpdatedAt, } return body } -// NewUpdateGrpsioMailingListSettingsResponseBody builds the HTTP response body -// from the result of the "update-grpsio-mailing-list-settings" endpoint of the -// "mailing-list" service. -func NewUpdateGrpsioMailingListSettingsResponseBody(res *mailinglist.GrpsIoMailingListSettings) *UpdateGrpsioMailingListSettingsResponseBody { - body := &UpdateGrpsioMailingListSettingsResponseBody{ - UID: res.UID, - LastReviewedAt: res.LastReviewedAt, - LastReviewedBy: res.LastReviewedBy, - LastAuditedBy: res.LastAuditedBy, - LastAuditedTime: res.LastAuditedTime, - CreatedAt: res.CreatedAt, - UpdatedAt: res.UpdatedAt, - } - if res.Writers != nil { - body.Writers = make([]*UserInfoResponseBody, len(res.Writers)) - for i, val := range res.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } - } - if res.Auditors != nil { - body.Auditors = make([]*UserInfoResponseBody, len(res.Auditors)) - for i, val := range res.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } +// NewUpdateGroupsioSubgroupResponseBody builds the HTTP response body from the +// result of the "update-groupsio-subgroup" endpoint of the "mailing-list" +// service. +func NewUpdateGroupsioSubgroupResponseBody(res *mailinglist.GroupsioSubgroup) *UpdateGroupsioSubgroupResponseBody { + body := &UpdateGroupsioSubgroupResponseBody{ + ID: res.ID, + ProjectUID: res.ProjectUID, + CommitteeUID: res.CommitteeUID, + GroupID: res.GroupID, + Name: res.Name, + Description: res.Description, + Type: res.Type, + AudienceAccess: res.AudienceAccess, + CreatedAt: res.CreatedAt, + UpdatedAt: res.UpdatedAt, } return body } -// NewCreateGrpsioMailingListMemberResponseBody builds the HTTP response body -// from the result of the "create-grpsio-mailing-list-member" endpoint of the +// NewGetGroupsioSubgroupCountResponseBody builds the HTTP response body from +// the result of the "get-groupsio-subgroup-count" endpoint of the // "mailing-list" service. -func NewCreateGrpsioMailingListMemberResponseBody(res *mailinglist.GrpsIoMemberFull) *CreateGrpsioMailingListMemberResponseBody { - body := &CreateGrpsioMailingListMemberResponseBody{ - UID: res.UID, - MailingListUID: res.MailingListUID, - Username: res.Username, - FirstName: res.FirstName, - LastName: res.LastName, - Email: res.Email, - Organization: res.Organization, - JobTitle: res.JobTitle, - MemberType: res.MemberType, - DeliveryMode: res.DeliveryMode, - ModStatus: res.ModStatus, - LastReviewedAt: res.LastReviewedAt, - LastReviewedBy: res.LastReviewedBy, - Status: res.Status, - MemberID: res.MemberID, - GroupID: res.GroupID, - CreatedAt: res.CreatedAt, - UpdatedAt: res.UpdatedAt, - } - if res.Writers != nil { - body.Writers = make([]*UserInfoResponseBody, len(res.Writers)) - for i, val := range res.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } - } - if res.Auditors != nil { - body.Auditors = make([]*UserInfoResponseBody, len(res.Auditors)) - for i, val := range res.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } +func NewGetGroupsioSubgroupCountResponseBody(res *mailinglist.GroupsioCount) *GetGroupsioSubgroupCountResponseBody { + body := &GetGroupsioSubgroupCountResponseBody{ + Count: res.Count, } return body } -// NewGetGrpsioMailingListMemberResponseBody builds the HTTP response body from -// the result of the "get-grpsio-mailing-list-member" endpoint of the +// NewGetGroupsioSubgroupMemberCountResponseBody builds the HTTP response body +// from the result of the "get-groupsio-subgroup-member-count" endpoint of the // "mailing-list" service. -func NewGetGrpsioMailingListMemberResponseBody(res *mailinglist.GetGrpsioMailingListMemberResult) *GetGrpsioMailingListMemberResponseBody { - body := &GetGrpsioMailingListMemberResponseBody{ - UID: res.Member.UID, - MailingListUID: res.Member.MailingListUID, - Username: res.Member.Username, - FirstName: res.Member.FirstName, - LastName: res.Member.LastName, - Email: res.Member.Email, - Organization: res.Member.Organization, - JobTitle: res.Member.JobTitle, - MemberType: res.Member.MemberType, - DeliveryMode: res.Member.DeliveryMode, - ModStatus: res.Member.ModStatus, - LastReviewedAt: res.Member.LastReviewedAt, - LastReviewedBy: res.Member.LastReviewedBy, - Status: res.Member.Status, - MemberID: res.Member.MemberID, - GroupID: res.Member.GroupID, - CreatedAt: res.Member.CreatedAt, - UpdatedAt: res.Member.UpdatedAt, - } - { - var zero string - if body.MemberType == zero { - body.MemberType = "direct" - } - } - { - var zero string - if body.DeliveryMode == zero { - body.DeliveryMode = "normal" - } - } - { - var zero string - if body.ModStatus == zero { - body.ModStatus = "none" - } - } - if res.Member.Writers != nil { - body.Writers = make([]*UserInfoResponseBody, len(res.Member.Writers)) - for i, val := range res.Member.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } - } - if res.Member.Auditors != nil { - body.Auditors = make([]*UserInfoResponseBody, len(res.Member.Auditors)) - for i, val := range res.Member.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } +func NewGetGroupsioSubgroupMemberCountResponseBody(res *mailinglist.GroupsioCount) *GetGroupsioSubgroupMemberCountResponseBody { + body := &GetGroupsioSubgroupMemberCountResponseBody{ + Count: res.Count, } return body } -// NewUpdateGrpsioMailingListMemberResponseBody builds the HTTP response body -// from the result of the "update-grpsio-mailing-list-member" endpoint of the -// "mailing-list" service. -func NewUpdateGrpsioMailingListMemberResponseBody(res *mailinglist.GrpsIoMemberWithReadonlyAttributes) *UpdateGrpsioMailingListMemberResponseBody { - body := &UpdateGrpsioMailingListMemberResponseBody{ - UID: res.UID, - MailingListUID: res.MailingListUID, - Username: res.Username, - FirstName: res.FirstName, - LastName: res.LastName, - Email: res.Email, - Organization: res.Organization, - JobTitle: res.JobTitle, - MemberType: res.MemberType, - DeliveryMode: res.DeliveryMode, - ModStatus: res.ModStatus, - LastReviewedAt: res.LastReviewedAt, - LastReviewedBy: res.LastReviewedBy, - Status: res.Status, - MemberID: res.MemberID, - GroupID: res.GroupID, - CreatedAt: res.CreatedAt, - UpdatedAt: res.UpdatedAt, +// NewListGroupsioMembersResponseBody builds the HTTP response body from the +// result of the "list-groupsio-members" endpoint of the "mailing-list" service. +func NewListGroupsioMembersResponseBody(res *mailinglist.GroupsioMemberList) *ListGroupsioMembersResponseBody { + body := &ListGroupsioMembersResponseBody{ + Total: res.Total, } - { - var zero string - if body.MemberType == zero { - body.MemberType = "direct" + if res.Items != nil { + body.Items = make([]*GroupsioMemberResponseBody, len(res.Items)) + for i, val := range res.Items { + body.Items[i] = marshalMailinglistGroupsioMemberToGroupsioMemberResponseBody(val) } } - { - var zero string - if body.DeliveryMode == zero { - body.DeliveryMode = "normal" - } + return body +} + +// NewAddGroupsioMemberResponseBody builds the HTTP response body from the +// result of the "add-groupsio-member" endpoint of the "mailing-list" service. +func NewAddGroupsioMemberResponseBody(res *mailinglist.GroupsioMember) *AddGroupsioMemberResponseBody { + body := &AddGroupsioMemberResponseBody{ + ID: res.ID, + SubgroupID: res.SubgroupID, + Email: res.Email, + Name: res.Name, + FirstName: res.FirstName, + LastName: res.LastName, + ModStatus: res.ModStatus, + DeliveryMode: res.DeliveryMode, + Status: res.Status, + CreatedAt: res.CreatedAt, + UpdatedAt: res.UpdatedAt, } - { - var zero string - if body.ModStatus == zero { - body.ModStatus = "none" - } + return body +} + +// NewGetGroupsioMemberResponseBody builds the HTTP response body from the +// result of the "get-groupsio-member" endpoint of the "mailing-list" service. +func NewGetGroupsioMemberResponseBody(res *mailinglist.GroupsioMember) *GetGroupsioMemberResponseBody { + body := &GetGroupsioMemberResponseBody{ + ID: res.ID, + SubgroupID: res.SubgroupID, + Email: res.Email, + Name: res.Name, + FirstName: res.FirstName, + LastName: res.LastName, + ModStatus: res.ModStatus, + DeliveryMode: res.DeliveryMode, + Status: res.Status, + CreatedAt: res.CreatedAt, + UpdatedAt: res.UpdatedAt, } - if res.Writers != nil { - body.Writers = make([]*UserInfoResponseBody, len(res.Writers)) - for i, val := range res.Writers { - body.Writers[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } + return body +} + +// NewUpdateGroupsioMemberResponseBody builds the HTTP response body from the +// result of the "update-groupsio-member" endpoint of the "mailing-list" +// service. +func NewUpdateGroupsioMemberResponseBody(res *mailinglist.GroupsioMember) *UpdateGroupsioMemberResponseBody { + body := &UpdateGroupsioMemberResponseBody{ + ID: res.ID, + SubgroupID: res.SubgroupID, + Email: res.Email, + Name: res.Name, + FirstName: res.FirstName, + LastName: res.LastName, + ModStatus: res.ModStatus, + DeliveryMode: res.DeliveryMode, + Status: res.Status, + CreatedAt: res.CreatedAt, + UpdatedAt: res.UpdatedAt, } - if res.Auditors != nil { - body.Auditors = make([]*UserInfoResponseBody, len(res.Auditors)) - for i, val := range res.Auditors { - body.Auditors[i] = marshalMailinglistUserInfoToUserInfoResponseBody(val) - } + return body +} + +// NewCheckGroupsioSubscriberResponseBody builds the HTTP response body from +// the result of the "check-groupsio-subscriber" endpoint of the "mailing-list" +// service. +func NewCheckGroupsioSubscriberResponseBody(res *mailinglist.GroupsioCheckSubscriberResponse) *CheckGroupsioSubscriberResponseBody { + body := &CheckGroupsioSubscriberResponseBody{ + Subscribed: res.Subscribed, } return body } @@ -1940,1527 +1363,1034 @@ func NewReadyzServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailable return body } -// NewCreateGrpsioServiceBadRequestResponseBody builds the HTTP response body -// from the result of the "create-grpsio-service" endpoint of the +// NewListGroupsioServicesBadRequestResponseBody builds the HTTP response body +// from the result of the "list-groupsio-services" endpoint of the // "mailing-list" service. -func NewCreateGrpsioServiceBadRequestResponseBody(res *mailinglist.BadRequestError) *CreateGrpsioServiceBadRequestResponseBody { - body := &CreateGrpsioServiceBadRequestResponseBody{ +func NewListGroupsioServicesBadRequestResponseBody(res *mailinglist.BadRequestError) *ListGroupsioServicesBadRequestResponseBody { + body := &ListGroupsioServicesBadRequestResponseBody{ Message: res.Message, } return body } -// NewCreateGrpsioServiceConflictResponseBody builds the HTTP response body -// from the result of the "create-grpsio-service" endpoint of the -// "mailing-list" service. -func NewCreateGrpsioServiceConflictResponseBody(res *mailinglist.ConflictError) *CreateGrpsioServiceConflictResponseBody { - body := &CreateGrpsioServiceConflictResponseBody{ +// NewListGroupsioServicesInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "list-groupsio-services" endpoint of +// the "mailing-list" service. +func NewListGroupsioServicesInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *ListGroupsioServicesInternalServerErrorResponseBody { + body := &ListGroupsioServicesInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewCreateGrpsioServiceInternalServerErrorResponseBody builds the HTTP -// response body from the result of the "create-grpsio-service" endpoint of the -// "mailing-list" service. -func NewCreateGrpsioServiceInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *CreateGrpsioServiceInternalServerErrorResponseBody { - body := &CreateGrpsioServiceInternalServerErrorResponseBody{ +// NewListGroupsioServicesServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "list-groupsio-services" endpoint of +// the "mailing-list" service. +func NewListGroupsioServicesServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *ListGroupsioServicesServiceUnavailableResponseBody { + body := &ListGroupsioServicesServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewCreateGrpsioServiceNotFoundResponseBody builds the HTTP response body -// from the result of the "create-grpsio-service" endpoint of the +// NewCreateGroupsioServiceBadRequestResponseBody builds the HTTP response body +// from the result of the "create-groupsio-service" endpoint of the // "mailing-list" service. -func NewCreateGrpsioServiceNotFoundResponseBody(res *mailinglist.NotFoundError) *CreateGrpsioServiceNotFoundResponseBody { - body := &CreateGrpsioServiceNotFoundResponseBody{ +func NewCreateGroupsioServiceBadRequestResponseBody(res *mailinglist.BadRequestError) *CreateGroupsioServiceBadRequestResponseBody { + body := &CreateGroupsioServiceBadRequestResponseBody{ Message: res.Message, } return body } -// NewCreateGrpsioServiceServiceUnavailableResponseBody builds the HTTP -// response body from the result of the "create-grpsio-service" endpoint of the +// NewCreateGroupsioServiceConflictResponseBody builds the HTTP response body +// from the result of the "create-groupsio-service" endpoint of the // "mailing-list" service. -func NewCreateGrpsioServiceServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *CreateGrpsioServiceServiceUnavailableResponseBody { - body := &CreateGrpsioServiceServiceUnavailableResponseBody{ +func NewCreateGroupsioServiceConflictResponseBody(res *mailinglist.ConflictError) *CreateGroupsioServiceConflictResponseBody { + body := &CreateGroupsioServiceConflictResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioServiceBadRequestResponseBody builds the HTTP response body from -// the result of the "get-grpsio-service" endpoint of the "mailing-list" -// service. -func NewGetGrpsioServiceBadRequestResponseBody(res *mailinglist.BadRequestError) *GetGrpsioServiceBadRequestResponseBody { - body := &GetGrpsioServiceBadRequestResponseBody{ +// NewCreateGroupsioServiceInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "create-groupsio-service" endpoint of +// the "mailing-list" service. +func NewCreateGroupsioServiceInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *CreateGroupsioServiceInternalServerErrorResponseBody { + body := &CreateGroupsioServiceInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioServiceInternalServerErrorResponseBody builds the HTTP response -// body from the result of the "get-grpsio-service" endpoint of the -// "mailing-list" service. -func NewGetGrpsioServiceInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *GetGrpsioServiceInternalServerErrorResponseBody { - body := &GetGrpsioServiceInternalServerErrorResponseBody{ +// NewCreateGroupsioServiceServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "create-groupsio-service" endpoint of +// the "mailing-list" service. +func NewCreateGroupsioServiceServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *CreateGroupsioServiceServiceUnavailableResponseBody { + body := &CreateGroupsioServiceServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioServiceNotFoundResponseBody builds the HTTP response body from -// the result of the "get-grpsio-service" endpoint of the "mailing-list" -// service. -func NewGetGrpsioServiceNotFoundResponseBody(res *mailinglist.NotFoundError) *GetGrpsioServiceNotFoundResponseBody { - body := &GetGrpsioServiceNotFoundResponseBody{ +// NewGetGroupsioServiceInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "get-groupsio-service" endpoint of the +// "mailing-list" service. +func NewGetGroupsioServiceInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *GetGroupsioServiceInternalServerErrorResponseBody { + body := &GetGroupsioServiceInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioServiceServiceUnavailableResponseBody builds the HTTP response -// body from the result of the "get-grpsio-service" endpoint of the -// "mailing-list" service. -func NewGetGrpsioServiceServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *GetGrpsioServiceServiceUnavailableResponseBody { - body := &GetGrpsioServiceServiceUnavailableResponseBody{ +// NewGetGroupsioServiceNotFoundResponseBody builds the HTTP response body from +// the result of the "get-groupsio-service" endpoint of the "mailing-list" +// service. +func NewGetGroupsioServiceNotFoundResponseBody(res *mailinglist.NotFoundError) *GetGroupsioServiceNotFoundResponseBody { + body := &GetGroupsioServiceNotFoundResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioServiceBadRequestResponseBody builds the HTTP response body -// from the result of the "update-grpsio-service" endpoint of the +// NewGetGroupsioServiceServiceUnavailableResponseBody builds the HTTP response +// body from the result of the "get-groupsio-service" endpoint of the // "mailing-list" service. -func NewUpdateGrpsioServiceBadRequestResponseBody(res *mailinglist.BadRequestError) *UpdateGrpsioServiceBadRequestResponseBody { - body := &UpdateGrpsioServiceBadRequestResponseBody{ +func NewGetGroupsioServiceServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *GetGroupsioServiceServiceUnavailableResponseBody { + body := &GetGroupsioServiceServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioServiceConflictResponseBody builds the HTTP response body -// from the result of the "update-grpsio-service" endpoint of the +// NewUpdateGroupsioServiceBadRequestResponseBody builds the HTTP response body +// from the result of the "update-groupsio-service" endpoint of the // "mailing-list" service. -func NewUpdateGrpsioServiceConflictResponseBody(res *mailinglist.ConflictError) *UpdateGrpsioServiceConflictResponseBody { - body := &UpdateGrpsioServiceConflictResponseBody{ +func NewUpdateGroupsioServiceBadRequestResponseBody(res *mailinglist.BadRequestError) *UpdateGroupsioServiceBadRequestResponseBody { + body := &UpdateGroupsioServiceBadRequestResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioServiceInternalServerErrorResponseBody builds the HTTP -// response body from the result of the "update-grpsio-service" endpoint of the -// "mailing-list" service. -func NewUpdateGrpsioServiceInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *UpdateGrpsioServiceInternalServerErrorResponseBody { - body := &UpdateGrpsioServiceInternalServerErrorResponseBody{ +// NewUpdateGroupsioServiceInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "update-groupsio-service" endpoint of +// the "mailing-list" service. +func NewUpdateGroupsioServiceInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *UpdateGroupsioServiceInternalServerErrorResponseBody { + body := &UpdateGroupsioServiceInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioServiceNotFoundResponseBody builds the HTTP response body -// from the result of the "update-grpsio-service" endpoint of the +// NewUpdateGroupsioServiceNotFoundResponseBody builds the HTTP response body +// from the result of the "update-groupsio-service" endpoint of the // "mailing-list" service. -func NewUpdateGrpsioServiceNotFoundResponseBody(res *mailinglist.NotFoundError) *UpdateGrpsioServiceNotFoundResponseBody { - body := &UpdateGrpsioServiceNotFoundResponseBody{ +func NewUpdateGroupsioServiceNotFoundResponseBody(res *mailinglist.NotFoundError) *UpdateGroupsioServiceNotFoundResponseBody { + body := &UpdateGroupsioServiceNotFoundResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioServiceServiceUnavailableResponseBody builds the HTTP -// response body from the result of the "update-grpsio-service" endpoint of the -// "mailing-list" service. -func NewUpdateGrpsioServiceServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *UpdateGrpsioServiceServiceUnavailableResponseBody { - body := &UpdateGrpsioServiceServiceUnavailableResponseBody{ +// NewUpdateGroupsioServiceServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "update-groupsio-service" endpoint of +// the "mailing-list" service. +func NewUpdateGroupsioServiceServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *UpdateGroupsioServiceServiceUnavailableResponseBody { + body := &UpdateGroupsioServiceServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewDeleteGrpsioServiceBadRequestResponseBody builds the HTTP response body -// from the result of the "delete-grpsio-service" endpoint of the -// "mailing-list" service. -func NewDeleteGrpsioServiceBadRequestResponseBody(res *mailinglist.BadRequestError) *DeleteGrpsioServiceBadRequestResponseBody { - body := &DeleteGrpsioServiceBadRequestResponseBody{ +// NewDeleteGroupsioServiceInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "delete-groupsio-service" endpoint of +// the "mailing-list" service. +func NewDeleteGroupsioServiceInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *DeleteGroupsioServiceInternalServerErrorResponseBody { + body := &DeleteGroupsioServiceInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewDeleteGrpsioServiceConflictResponseBody builds the HTTP response body -// from the result of the "delete-grpsio-service" endpoint of the +// NewDeleteGroupsioServiceNotFoundResponseBody builds the HTTP response body +// from the result of the "delete-groupsio-service" endpoint of the // "mailing-list" service. -func NewDeleteGrpsioServiceConflictResponseBody(res *mailinglist.ConflictError) *DeleteGrpsioServiceConflictResponseBody { - body := &DeleteGrpsioServiceConflictResponseBody{ +func NewDeleteGroupsioServiceNotFoundResponseBody(res *mailinglist.NotFoundError) *DeleteGroupsioServiceNotFoundResponseBody { + body := &DeleteGroupsioServiceNotFoundResponseBody{ Message: res.Message, } return body } -// NewDeleteGrpsioServiceInternalServerErrorResponseBody builds the HTTP -// response body from the result of the "delete-grpsio-service" endpoint of the -// "mailing-list" service. -func NewDeleteGrpsioServiceInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *DeleteGrpsioServiceInternalServerErrorResponseBody { - body := &DeleteGrpsioServiceInternalServerErrorResponseBody{ +// NewDeleteGroupsioServiceServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "delete-groupsio-service" endpoint of +// the "mailing-list" service. +func NewDeleteGroupsioServiceServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *DeleteGroupsioServiceServiceUnavailableResponseBody { + body := &DeleteGroupsioServiceServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewDeleteGrpsioServiceNotFoundResponseBody builds the HTTP response body -// from the result of the "delete-grpsio-service" endpoint of the -// "mailing-list" service. -func NewDeleteGrpsioServiceNotFoundResponseBody(res *mailinglist.NotFoundError) *DeleteGrpsioServiceNotFoundResponseBody { - body := &DeleteGrpsioServiceNotFoundResponseBody{ +// NewGetGroupsioServiceProjectsInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "get-groupsio-service-projects" +// endpoint of the "mailing-list" service. +func NewGetGroupsioServiceProjectsInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *GetGroupsioServiceProjectsInternalServerErrorResponseBody { + body := &GetGroupsioServiceProjectsInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewDeleteGrpsioServiceServiceUnavailableResponseBody builds the HTTP -// response body from the result of the "delete-grpsio-service" endpoint of the -// "mailing-list" service. -func NewDeleteGrpsioServiceServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *DeleteGrpsioServiceServiceUnavailableResponseBody { - body := &DeleteGrpsioServiceServiceUnavailableResponseBody{ +// NewGetGroupsioServiceProjectsServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "get-groupsio-service-projects" +// endpoint of the "mailing-list" service. +func NewGetGroupsioServiceProjectsServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *GetGroupsioServiceProjectsServiceUnavailableResponseBody { + body := &GetGroupsioServiceProjectsServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioServiceSettingsBadRequestResponseBody builds the HTTP response -// body from the result of the "get-grpsio-service-settings" endpoint of the +// NewFindParentGroupsioServiceBadRequestResponseBody builds the HTTP response +// body from the result of the "find-parent-groupsio-service" endpoint of the // "mailing-list" service. -func NewGetGrpsioServiceSettingsBadRequestResponseBody(res *mailinglist.BadRequestError) *GetGrpsioServiceSettingsBadRequestResponseBody { - body := &GetGrpsioServiceSettingsBadRequestResponseBody{ +func NewFindParentGroupsioServiceBadRequestResponseBody(res *mailinglist.BadRequestError) *FindParentGroupsioServiceBadRequestResponseBody { + body := &FindParentGroupsioServiceBadRequestResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioServiceSettingsInternalServerErrorResponseBody builds the HTTP -// response body from the result of the "get-grpsio-service-settings" endpoint +// NewFindParentGroupsioServiceInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "find-parent-groupsio-service" endpoint // of the "mailing-list" service. -func NewGetGrpsioServiceSettingsInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *GetGrpsioServiceSettingsInternalServerErrorResponseBody { - body := &GetGrpsioServiceSettingsInternalServerErrorResponseBody{ +func NewFindParentGroupsioServiceInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *FindParentGroupsioServiceInternalServerErrorResponseBody { + body := &FindParentGroupsioServiceInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioServiceSettingsNotFoundResponseBody builds the HTTP response -// body from the result of the "get-grpsio-service-settings" endpoint of the +// NewFindParentGroupsioServiceNotFoundResponseBody builds the HTTP response +// body from the result of the "find-parent-groupsio-service" endpoint of the // "mailing-list" service. -func NewGetGrpsioServiceSettingsNotFoundResponseBody(res *mailinglist.NotFoundError) *GetGrpsioServiceSettingsNotFoundResponseBody { - body := &GetGrpsioServiceSettingsNotFoundResponseBody{ +func NewFindParentGroupsioServiceNotFoundResponseBody(res *mailinglist.NotFoundError) *FindParentGroupsioServiceNotFoundResponseBody { + body := &FindParentGroupsioServiceNotFoundResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioServiceSettingsServiceUnavailableResponseBody builds the HTTP -// response body from the result of the "get-grpsio-service-settings" endpoint +// NewFindParentGroupsioServiceServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "find-parent-groupsio-service" endpoint // of the "mailing-list" service. -func NewGetGrpsioServiceSettingsServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *GetGrpsioServiceSettingsServiceUnavailableResponseBody { - body := &GetGrpsioServiceSettingsServiceUnavailableResponseBody{ +func NewFindParentGroupsioServiceServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *FindParentGroupsioServiceServiceUnavailableResponseBody { + body := &FindParentGroupsioServiceServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioServiceSettingsBadRequestResponseBody builds the HTTP -// response body from the result of the "update-grpsio-service-settings" -// endpoint of the "mailing-list" service. -func NewUpdateGrpsioServiceSettingsBadRequestResponseBody(res *mailinglist.BadRequestError) *UpdateGrpsioServiceSettingsBadRequestResponseBody { - body := &UpdateGrpsioServiceSettingsBadRequestResponseBody{ +// NewListGroupsioSubgroupsBadRequestResponseBody builds the HTTP response body +// from the result of the "list-groupsio-subgroups" endpoint of the +// "mailing-list" service. +func NewListGroupsioSubgroupsBadRequestResponseBody(res *mailinglist.BadRequestError) *ListGroupsioSubgroupsBadRequestResponseBody { + body := &ListGroupsioSubgroupsBadRequestResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioServiceSettingsConflictResponseBody builds the HTTP response -// body from the result of the "update-grpsio-service-settings" endpoint of the -// "mailing-list" service. -func NewUpdateGrpsioServiceSettingsConflictResponseBody(res *mailinglist.ConflictError) *UpdateGrpsioServiceSettingsConflictResponseBody { - body := &UpdateGrpsioServiceSettingsConflictResponseBody{ +// NewListGroupsioSubgroupsInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "list-groupsio-subgroups" endpoint of +// the "mailing-list" service. +func NewListGroupsioSubgroupsInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *ListGroupsioSubgroupsInternalServerErrorResponseBody { + body := &ListGroupsioSubgroupsInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioServiceSettingsInternalServerErrorResponseBody builds the -// HTTP response body from the result of the "update-grpsio-service-settings" -// endpoint of the "mailing-list" service. -func NewUpdateGrpsioServiceSettingsInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *UpdateGrpsioServiceSettingsInternalServerErrorResponseBody { - body := &UpdateGrpsioServiceSettingsInternalServerErrorResponseBody{ +// NewListGroupsioSubgroupsServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "list-groupsio-subgroups" endpoint of +// the "mailing-list" service. +func NewListGroupsioSubgroupsServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *ListGroupsioSubgroupsServiceUnavailableResponseBody { + body := &ListGroupsioSubgroupsServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioServiceSettingsNotFoundResponseBody builds the HTTP response -// body from the result of the "update-grpsio-service-settings" endpoint of the +// NewCreateGroupsioSubgroupBadRequestResponseBody builds the HTTP response +// body from the result of the "create-groupsio-subgroup" endpoint of the // "mailing-list" service. -func NewUpdateGrpsioServiceSettingsNotFoundResponseBody(res *mailinglist.NotFoundError) *UpdateGrpsioServiceSettingsNotFoundResponseBody { - body := &UpdateGrpsioServiceSettingsNotFoundResponseBody{ +func NewCreateGroupsioSubgroupBadRequestResponseBody(res *mailinglist.BadRequestError) *CreateGroupsioSubgroupBadRequestResponseBody { + body := &CreateGroupsioSubgroupBadRequestResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioServiceSettingsServiceUnavailableResponseBody builds the HTTP -// response body from the result of the "update-grpsio-service-settings" -// endpoint of the "mailing-list" service. -func NewUpdateGrpsioServiceSettingsServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *UpdateGrpsioServiceSettingsServiceUnavailableResponseBody { - body := &UpdateGrpsioServiceSettingsServiceUnavailableResponseBody{ +// NewCreateGroupsioSubgroupConflictResponseBody builds the HTTP response body +// from the result of the "create-groupsio-subgroup" endpoint of the +// "mailing-list" service. +func NewCreateGroupsioSubgroupConflictResponseBody(res *mailinglist.ConflictError) *CreateGroupsioSubgroupConflictResponseBody { + body := &CreateGroupsioSubgroupConflictResponseBody{ Message: res.Message, } return body } -// NewCreateGrpsioMailingListBadRequestResponseBody builds the HTTP response -// body from the result of the "create-grpsio-mailing-list" endpoint of the -// "mailing-list" service. -func NewCreateGrpsioMailingListBadRequestResponseBody(res *mailinglist.BadRequestError) *CreateGrpsioMailingListBadRequestResponseBody { - body := &CreateGrpsioMailingListBadRequestResponseBody{ +// NewCreateGroupsioSubgroupInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "create-groupsio-subgroup" endpoint of +// the "mailing-list" service. +func NewCreateGroupsioSubgroupInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *CreateGroupsioSubgroupInternalServerErrorResponseBody { + body := &CreateGroupsioSubgroupInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewCreateGrpsioMailingListConflictResponseBody builds the HTTP response body -// from the result of the "create-grpsio-mailing-list" endpoint of the -// "mailing-list" service. -func NewCreateGrpsioMailingListConflictResponseBody(res *mailinglist.ConflictError) *CreateGrpsioMailingListConflictResponseBody { - body := &CreateGrpsioMailingListConflictResponseBody{ +// NewCreateGroupsioSubgroupServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "create-groupsio-subgroup" endpoint of +// the "mailing-list" service. +func NewCreateGroupsioSubgroupServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *CreateGroupsioSubgroupServiceUnavailableResponseBody { + body := &CreateGroupsioSubgroupServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewCreateGrpsioMailingListInternalServerErrorResponseBody builds the HTTP -// response body from the result of the "create-grpsio-mailing-list" endpoint -// of the "mailing-list" service. -func NewCreateGrpsioMailingListInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *CreateGrpsioMailingListInternalServerErrorResponseBody { - body := &CreateGrpsioMailingListInternalServerErrorResponseBody{ +// NewGetGroupsioSubgroupInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "get-groupsio-subgroup" endpoint of the +// "mailing-list" service. +func NewGetGroupsioSubgroupInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *GetGroupsioSubgroupInternalServerErrorResponseBody { + body := &GetGroupsioSubgroupInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewCreateGrpsioMailingListNotFoundResponseBody builds the HTTP response body -// from the result of the "create-grpsio-mailing-list" endpoint of the +// NewGetGroupsioSubgroupNotFoundResponseBody builds the HTTP response body +// from the result of the "get-groupsio-subgroup" endpoint of the // "mailing-list" service. -func NewCreateGrpsioMailingListNotFoundResponseBody(res *mailinglist.NotFoundError) *CreateGrpsioMailingListNotFoundResponseBody { - body := &CreateGrpsioMailingListNotFoundResponseBody{ +func NewGetGroupsioSubgroupNotFoundResponseBody(res *mailinglist.NotFoundError) *GetGroupsioSubgroupNotFoundResponseBody { + body := &GetGroupsioSubgroupNotFoundResponseBody{ Message: res.Message, } return body } -// NewCreateGrpsioMailingListServiceUnavailableResponseBody builds the HTTP -// response body from the result of the "create-grpsio-mailing-list" endpoint -// of the "mailing-list" service. -func NewCreateGrpsioMailingListServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *CreateGrpsioMailingListServiceUnavailableResponseBody { - body := &CreateGrpsioMailingListServiceUnavailableResponseBody{ +// NewGetGroupsioSubgroupServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "get-groupsio-subgroup" endpoint of the +// "mailing-list" service. +func NewGetGroupsioSubgroupServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *GetGroupsioSubgroupServiceUnavailableResponseBody { + body := &GetGroupsioSubgroupServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioMailingListBadRequestResponseBody builds the HTTP response body -// from the result of the "get-grpsio-mailing-list" endpoint of the +// NewUpdateGroupsioSubgroupBadRequestResponseBody builds the HTTP response +// body from the result of the "update-groupsio-subgroup" endpoint of the // "mailing-list" service. -func NewGetGrpsioMailingListBadRequestResponseBody(res *mailinglist.BadRequestError) *GetGrpsioMailingListBadRequestResponseBody { - body := &GetGrpsioMailingListBadRequestResponseBody{ +func NewUpdateGroupsioSubgroupBadRequestResponseBody(res *mailinglist.BadRequestError) *UpdateGroupsioSubgroupBadRequestResponseBody { + body := &UpdateGroupsioSubgroupBadRequestResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioMailingListInternalServerErrorResponseBody builds the HTTP -// response body from the result of the "get-grpsio-mailing-list" endpoint of +// NewUpdateGroupsioSubgroupInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "update-groupsio-subgroup" endpoint of // the "mailing-list" service. -func NewGetGrpsioMailingListInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *GetGrpsioMailingListInternalServerErrorResponseBody { - body := &GetGrpsioMailingListInternalServerErrorResponseBody{ +func NewUpdateGroupsioSubgroupInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *UpdateGroupsioSubgroupInternalServerErrorResponseBody { + body := &UpdateGroupsioSubgroupInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioMailingListNotFoundResponseBody builds the HTTP response body -// from the result of the "get-grpsio-mailing-list" endpoint of the +// NewUpdateGroupsioSubgroupNotFoundResponseBody builds the HTTP response body +// from the result of the "update-groupsio-subgroup" endpoint of the // "mailing-list" service. -func NewGetGrpsioMailingListNotFoundResponseBody(res *mailinglist.NotFoundError) *GetGrpsioMailingListNotFoundResponseBody { - body := &GetGrpsioMailingListNotFoundResponseBody{ +func NewUpdateGroupsioSubgroupNotFoundResponseBody(res *mailinglist.NotFoundError) *UpdateGroupsioSubgroupNotFoundResponseBody { + body := &UpdateGroupsioSubgroupNotFoundResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioMailingListServiceUnavailableResponseBody builds the HTTP -// response body from the result of the "get-grpsio-mailing-list" endpoint of +// NewUpdateGroupsioSubgroupServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "update-groupsio-subgroup" endpoint of // the "mailing-list" service. -func NewGetGrpsioMailingListServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *GetGrpsioMailingListServiceUnavailableResponseBody { - body := &GetGrpsioMailingListServiceUnavailableResponseBody{ +func NewUpdateGroupsioSubgroupServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *UpdateGroupsioSubgroupServiceUnavailableResponseBody { + body := &UpdateGroupsioSubgroupServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListBadRequestResponseBody builds the HTTP response -// body from the result of the "update-grpsio-mailing-list" endpoint of the -// "mailing-list" service. -func NewUpdateGrpsioMailingListBadRequestResponseBody(res *mailinglist.BadRequestError) *UpdateGrpsioMailingListBadRequestResponseBody { - body := &UpdateGrpsioMailingListBadRequestResponseBody{ +// NewDeleteGroupsioSubgroupInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "delete-groupsio-subgroup" endpoint of +// the "mailing-list" service. +func NewDeleteGroupsioSubgroupInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *DeleteGroupsioSubgroupInternalServerErrorResponseBody { + body := &DeleteGroupsioSubgroupInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListConflictResponseBody builds the HTTP response body -// from the result of the "update-grpsio-mailing-list" endpoint of the +// NewDeleteGroupsioSubgroupNotFoundResponseBody builds the HTTP response body +// from the result of the "delete-groupsio-subgroup" endpoint of the // "mailing-list" service. -func NewUpdateGrpsioMailingListConflictResponseBody(res *mailinglist.ConflictError) *UpdateGrpsioMailingListConflictResponseBody { - body := &UpdateGrpsioMailingListConflictResponseBody{ +func NewDeleteGroupsioSubgroupNotFoundResponseBody(res *mailinglist.NotFoundError) *DeleteGroupsioSubgroupNotFoundResponseBody { + body := &DeleteGroupsioSubgroupNotFoundResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListInternalServerErrorResponseBody builds the HTTP -// response body from the result of the "update-grpsio-mailing-list" endpoint -// of the "mailing-list" service. -func NewUpdateGrpsioMailingListInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *UpdateGrpsioMailingListInternalServerErrorResponseBody { - body := &UpdateGrpsioMailingListInternalServerErrorResponseBody{ +// NewDeleteGroupsioSubgroupServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "delete-groupsio-subgroup" endpoint of +// the "mailing-list" service. +func NewDeleteGroupsioSubgroupServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *DeleteGroupsioSubgroupServiceUnavailableResponseBody { + body := &DeleteGroupsioSubgroupServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListNotFoundResponseBody builds the HTTP response body -// from the result of the "update-grpsio-mailing-list" endpoint of the +// NewGetGroupsioSubgroupCountBadRequestResponseBody builds the HTTP response +// body from the result of the "get-groupsio-subgroup-count" endpoint of the // "mailing-list" service. -func NewUpdateGrpsioMailingListNotFoundResponseBody(res *mailinglist.NotFoundError) *UpdateGrpsioMailingListNotFoundResponseBody { - body := &UpdateGrpsioMailingListNotFoundResponseBody{ +func NewGetGroupsioSubgroupCountBadRequestResponseBody(res *mailinglist.BadRequestError) *GetGroupsioSubgroupCountBadRequestResponseBody { + body := &GetGroupsioSubgroupCountBadRequestResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListServiceUnavailableResponseBody builds the HTTP -// response body from the result of the "update-grpsio-mailing-list" endpoint +// NewGetGroupsioSubgroupCountInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "get-groupsio-subgroup-count" endpoint // of the "mailing-list" service. -func NewUpdateGrpsioMailingListServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *UpdateGrpsioMailingListServiceUnavailableResponseBody { - body := &UpdateGrpsioMailingListServiceUnavailableResponseBody{ +func NewGetGroupsioSubgroupCountInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *GetGroupsioSubgroupCountInternalServerErrorResponseBody { + body := &GetGroupsioSubgroupCountInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewDeleteGrpsioMailingListBadRequestResponseBody builds the HTTP response -// body from the result of the "delete-grpsio-mailing-list" endpoint of the -// "mailing-list" service. -func NewDeleteGrpsioMailingListBadRequestResponseBody(res *mailinglist.BadRequestError) *DeleteGrpsioMailingListBadRequestResponseBody { - body := &DeleteGrpsioMailingListBadRequestResponseBody{ +// NewGetGroupsioSubgroupCountServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "get-groupsio-subgroup-count" endpoint +// of the "mailing-list" service. +func NewGetGroupsioSubgroupCountServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *GetGroupsioSubgroupCountServiceUnavailableResponseBody { + body := &GetGroupsioSubgroupCountServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewDeleteGrpsioMailingListConflictResponseBody builds the HTTP response body -// from the result of the "delete-grpsio-mailing-list" endpoint of the -// "mailing-list" service. -func NewDeleteGrpsioMailingListConflictResponseBody(res *mailinglist.ConflictError) *DeleteGrpsioMailingListConflictResponseBody { - body := &DeleteGrpsioMailingListConflictResponseBody{ +// NewGetGroupsioSubgroupMemberCountInternalServerErrorResponseBody builds the +// HTTP response body from the result of the +// "get-groupsio-subgroup-member-count" endpoint of the "mailing-list" service. +func NewGetGroupsioSubgroupMemberCountInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *GetGroupsioSubgroupMemberCountInternalServerErrorResponseBody { + body := &GetGroupsioSubgroupMemberCountInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewDeleteGrpsioMailingListInternalServerErrorResponseBody builds the HTTP -// response body from the result of the "delete-grpsio-mailing-list" endpoint -// of the "mailing-list" service. -func NewDeleteGrpsioMailingListInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *DeleteGrpsioMailingListInternalServerErrorResponseBody { - body := &DeleteGrpsioMailingListInternalServerErrorResponseBody{ +// NewGetGroupsioSubgroupMemberCountNotFoundResponseBody builds the HTTP +// response body from the result of the "get-groupsio-subgroup-member-count" +// endpoint of the "mailing-list" service. +func NewGetGroupsioSubgroupMemberCountNotFoundResponseBody(res *mailinglist.NotFoundError) *GetGroupsioSubgroupMemberCountNotFoundResponseBody { + body := &GetGroupsioSubgroupMemberCountNotFoundResponseBody{ Message: res.Message, } return body } -// NewDeleteGrpsioMailingListNotFoundResponseBody builds the HTTP response body -// from the result of the "delete-grpsio-mailing-list" endpoint of the -// "mailing-list" service. -func NewDeleteGrpsioMailingListNotFoundResponseBody(res *mailinglist.NotFoundError) *DeleteGrpsioMailingListNotFoundResponseBody { - body := &DeleteGrpsioMailingListNotFoundResponseBody{ +// NewGetGroupsioSubgroupMemberCountServiceUnavailableResponseBody builds the +// HTTP response body from the result of the +// "get-groupsio-subgroup-member-count" endpoint of the "mailing-list" service. +func NewGetGroupsioSubgroupMemberCountServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *GetGroupsioSubgroupMemberCountServiceUnavailableResponseBody { + body := &GetGroupsioSubgroupMemberCountServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewDeleteGrpsioMailingListServiceUnavailableResponseBody builds the HTTP -// response body from the result of the "delete-grpsio-mailing-list" endpoint -// of the "mailing-list" service. -func NewDeleteGrpsioMailingListServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *DeleteGrpsioMailingListServiceUnavailableResponseBody { - body := &DeleteGrpsioMailingListServiceUnavailableResponseBody{ +// NewListGroupsioMembersInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "list-groupsio-members" endpoint of the +// "mailing-list" service. +func NewListGroupsioMembersInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *ListGroupsioMembersInternalServerErrorResponseBody { + body := &ListGroupsioMembersInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioMailingListSettingsBadRequestResponseBody builds the HTTP -// response body from the result of the "get-grpsio-mailing-list-settings" -// endpoint of the "mailing-list" service. -func NewGetGrpsioMailingListSettingsBadRequestResponseBody(res *mailinglist.BadRequestError) *GetGrpsioMailingListSettingsBadRequestResponseBody { - body := &GetGrpsioMailingListSettingsBadRequestResponseBody{ +// NewListGroupsioMembersNotFoundResponseBody builds the HTTP response body +// from the result of the "list-groupsio-members" endpoint of the +// "mailing-list" service. +func NewListGroupsioMembersNotFoundResponseBody(res *mailinglist.NotFoundError) *ListGroupsioMembersNotFoundResponseBody { + body := &ListGroupsioMembersNotFoundResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioMailingListSettingsInternalServerErrorResponseBody builds the -// HTTP response body from the result of the "get-grpsio-mailing-list-settings" -// endpoint of the "mailing-list" service. -func NewGetGrpsioMailingListSettingsInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *GetGrpsioMailingListSettingsInternalServerErrorResponseBody { - body := &GetGrpsioMailingListSettingsInternalServerErrorResponseBody{ +// NewListGroupsioMembersServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "list-groupsio-members" endpoint of the +// "mailing-list" service. +func NewListGroupsioMembersServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *ListGroupsioMembersServiceUnavailableResponseBody { + body := &ListGroupsioMembersServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioMailingListSettingsNotFoundResponseBody builds the HTTP response -// body from the result of the "get-grpsio-mailing-list-settings" endpoint of -// the "mailing-list" service. -func NewGetGrpsioMailingListSettingsNotFoundResponseBody(res *mailinglist.NotFoundError) *GetGrpsioMailingListSettingsNotFoundResponseBody { - body := &GetGrpsioMailingListSettingsNotFoundResponseBody{ +// NewAddGroupsioMemberBadRequestResponseBody builds the HTTP response body +// from the result of the "add-groupsio-member" endpoint of the "mailing-list" +// service. +func NewAddGroupsioMemberBadRequestResponseBody(res *mailinglist.BadRequestError) *AddGroupsioMemberBadRequestResponseBody { + body := &AddGroupsioMemberBadRequestResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioMailingListSettingsServiceUnavailableResponseBody builds the -// HTTP response body from the result of the "get-grpsio-mailing-list-settings" -// endpoint of the "mailing-list" service. -func NewGetGrpsioMailingListSettingsServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *GetGrpsioMailingListSettingsServiceUnavailableResponseBody { - body := &GetGrpsioMailingListSettingsServiceUnavailableResponseBody{ +// NewAddGroupsioMemberConflictResponseBody builds the HTTP response body from +// the result of the "add-groupsio-member" endpoint of the "mailing-list" +// service. +func NewAddGroupsioMemberConflictResponseBody(res *mailinglist.ConflictError) *AddGroupsioMemberConflictResponseBody { + body := &AddGroupsioMemberConflictResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListSettingsBadRequestResponseBody builds the HTTP -// response body from the result of the "update-grpsio-mailing-list-settings" -// endpoint of the "mailing-list" service. -func NewUpdateGrpsioMailingListSettingsBadRequestResponseBody(res *mailinglist.BadRequestError) *UpdateGrpsioMailingListSettingsBadRequestResponseBody { - body := &UpdateGrpsioMailingListSettingsBadRequestResponseBody{ +// NewAddGroupsioMemberInternalServerErrorResponseBody builds the HTTP response +// body from the result of the "add-groupsio-member" endpoint of the +// "mailing-list" service. +func NewAddGroupsioMemberInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *AddGroupsioMemberInternalServerErrorResponseBody { + body := &AddGroupsioMemberInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListSettingsConflictResponseBody builds the HTTP -// response body from the result of the "update-grpsio-mailing-list-settings" -// endpoint of the "mailing-list" service. -func NewUpdateGrpsioMailingListSettingsConflictResponseBody(res *mailinglist.ConflictError) *UpdateGrpsioMailingListSettingsConflictResponseBody { - body := &UpdateGrpsioMailingListSettingsConflictResponseBody{ +// NewAddGroupsioMemberNotFoundResponseBody builds the HTTP response body from +// the result of the "add-groupsio-member" endpoint of the "mailing-list" +// service. +func NewAddGroupsioMemberNotFoundResponseBody(res *mailinglist.NotFoundError) *AddGroupsioMemberNotFoundResponseBody { + body := &AddGroupsioMemberNotFoundResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListSettingsInternalServerErrorResponseBody builds the -// HTTP response body from the result of the -// "update-grpsio-mailing-list-settings" endpoint of the "mailing-list" service. -func NewUpdateGrpsioMailingListSettingsInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *UpdateGrpsioMailingListSettingsInternalServerErrorResponseBody { - body := &UpdateGrpsioMailingListSettingsInternalServerErrorResponseBody{ +// NewAddGroupsioMemberServiceUnavailableResponseBody builds the HTTP response +// body from the result of the "add-groupsio-member" endpoint of the +// "mailing-list" service. +func NewAddGroupsioMemberServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *AddGroupsioMemberServiceUnavailableResponseBody { + body := &AddGroupsioMemberServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListSettingsNotFoundResponseBody builds the HTTP -// response body from the result of the "update-grpsio-mailing-list-settings" -// endpoint of the "mailing-list" service. -func NewUpdateGrpsioMailingListSettingsNotFoundResponseBody(res *mailinglist.NotFoundError) *UpdateGrpsioMailingListSettingsNotFoundResponseBody { - body := &UpdateGrpsioMailingListSettingsNotFoundResponseBody{ +// NewGetGroupsioMemberInternalServerErrorResponseBody builds the HTTP response +// body from the result of the "get-groupsio-member" endpoint of the +// "mailing-list" service. +func NewGetGroupsioMemberInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *GetGroupsioMemberInternalServerErrorResponseBody { + body := &GetGroupsioMemberInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListSettingsServiceUnavailableResponseBody builds the -// HTTP response body from the result of the -// "update-grpsio-mailing-list-settings" endpoint of the "mailing-list" service. -func NewUpdateGrpsioMailingListSettingsServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *UpdateGrpsioMailingListSettingsServiceUnavailableResponseBody { - body := &UpdateGrpsioMailingListSettingsServiceUnavailableResponseBody{ +// NewGetGroupsioMemberNotFoundResponseBody builds the HTTP response body from +// the result of the "get-groupsio-member" endpoint of the "mailing-list" +// service. +func NewGetGroupsioMemberNotFoundResponseBody(res *mailinglist.NotFoundError) *GetGroupsioMemberNotFoundResponseBody { + body := &GetGroupsioMemberNotFoundResponseBody{ Message: res.Message, } return body } -// NewCreateGrpsioMailingListMemberBadRequestResponseBody builds the HTTP -// response body from the result of the "create-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -func NewCreateGrpsioMailingListMemberBadRequestResponseBody(res *mailinglist.BadRequestError) *CreateGrpsioMailingListMemberBadRequestResponseBody { - body := &CreateGrpsioMailingListMemberBadRequestResponseBody{ - Message: res.Message, - } - return body -} - -// NewCreateGrpsioMailingListMemberConflictResponseBody builds the HTTP -// response body from the result of the "create-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -func NewCreateGrpsioMailingListMemberConflictResponseBody(res *mailinglist.ConflictError) *CreateGrpsioMailingListMemberConflictResponseBody { - body := &CreateGrpsioMailingListMemberConflictResponseBody{ +// NewGetGroupsioMemberServiceUnavailableResponseBody builds the HTTP response +// body from the result of the "get-groupsio-member" endpoint of the +// "mailing-list" service. +func NewGetGroupsioMemberServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *GetGroupsioMemberServiceUnavailableResponseBody { + body := &GetGroupsioMemberServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewCreateGrpsioMailingListMemberInternalServerErrorResponseBody builds the -// HTTP response body from the result of the -// "create-grpsio-mailing-list-member" endpoint of the "mailing-list" service. -func NewCreateGrpsioMailingListMemberInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *CreateGrpsioMailingListMemberInternalServerErrorResponseBody { - body := &CreateGrpsioMailingListMemberInternalServerErrorResponseBody{ +// NewUpdateGroupsioMemberBadRequestResponseBody builds the HTTP response body +// from the result of the "update-groupsio-member" endpoint of the +// "mailing-list" service. +func NewUpdateGroupsioMemberBadRequestResponseBody(res *mailinglist.BadRequestError) *UpdateGroupsioMemberBadRequestResponseBody { + body := &UpdateGroupsioMemberBadRequestResponseBody{ Message: res.Message, } return body } -// NewCreateGrpsioMailingListMemberNotFoundResponseBody builds the HTTP -// response body from the result of the "create-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -func NewCreateGrpsioMailingListMemberNotFoundResponseBody(res *mailinglist.NotFoundError) *CreateGrpsioMailingListMemberNotFoundResponseBody { - body := &CreateGrpsioMailingListMemberNotFoundResponseBody{ +// NewUpdateGroupsioMemberInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "update-groupsio-member" endpoint of +// the "mailing-list" service. +func NewUpdateGroupsioMemberInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *UpdateGroupsioMemberInternalServerErrorResponseBody { + body := &UpdateGroupsioMemberInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewCreateGrpsioMailingListMemberServiceUnavailableResponseBody builds the -// HTTP response body from the result of the -// "create-grpsio-mailing-list-member" endpoint of the "mailing-list" service. -func NewCreateGrpsioMailingListMemberServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *CreateGrpsioMailingListMemberServiceUnavailableResponseBody { - body := &CreateGrpsioMailingListMemberServiceUnavailableResponseBody{ +// NewUpdateGroupsioMemberNotFoundResponseBody builds the HTTP response body +// from the result of the "update-groupsio-member" endpoint of the +// "mailing-list" service. +func NewUpdateGroupsioMemberNotFoundResponseBody(res *mailinglist.NotFoundError) *UpdateGroupsioMemberNotFoundResponseBody { + body := &UpdateGroupsioMemberNotFoundResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioMailingListMemberBadRequestResponseBody builds the HTTP response -// body from the result of the "get-grpsio-mailing-list-member" endpoint of the -// "mailing-list" service. -func NewGetGrpsioMailingListMemberBadRequestResponseBody(res *mailinglist.BadRequestError) *GetGrpsioMailingListMemberBadRequestResponseBody { - body := &GetGrpsioMailingListMemberBadRequestResponseBody{ +// NewUpdateGroupsioMemberServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "update-groupsio-member" endpoint of +// the "mailing-list" service. +func NewUpdateGroupsioMemberServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *UpdateGroupsioMemberServiceUnavailableResponseBody { + body := &UpdateGroupsioMemberServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioMailingListMemberInternalServerErrorResponseBody builds the HTTP -// response body from the result of the "get-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -func NewGetGrpsioMailingListMemberInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *GetGrpsioMailingListMemberInternalServerErrorResponseBody { - body := &GetGrpsioMailingListMemberInternalServerErrorResponseBody{ +// NewDeleteGroupsioMemberInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "delete-groupsio-member" endpoint of +// the "mailing-list" service. +func NewDeleteGroupsioMemberInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *DeleteGroupsioMemberInternalServerErrorResponseBody { + body := &DeleteGroupsioMemberInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioMailingListMemberNotFoundResponseBody builds the HTTP response -// body from the result of the "get-grpsio-mailing-list-member" endpoint of the +// NewDeleteGroupsioMemberNotFoundResponseBody builds the HTTP response body +// from the result of the "delete-groupsio-member" endpoint of the // "mailing-list" service. -func NewGetGrpsioMailingListMemberNotFoundResponseBody(res *mailinglist.NotFoundError) *GetGrpsioMailingListMemberNotFoundResponseBody { - body := &GetGrpsioMailingListMemberNotFoundResponseBody{ +func NewDeleteGroupsioMemberNotFoundResponseBody(res *mailinglist.NotFoundError) *DeleteGroupsioMemberNotFoundResponseBody { + body := &DeleteGroupsioMemberNotFoundResponseBody{ Message: res.Message, } return body } -// NewGetGrpsioMailingListMemberServiceUnavailableResponseBody builds the HTTP -// response body from the result of the "get-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -func NewGetGrpsioMailingListMemberServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *GetGrpsioMailingListMemberServiceUnavailableResponseBody { - body := &GetGrpsioMailingListMemberServiceUnavailableResponseBody{ +// NewDeleteGroupsioMemberServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "delete-groupsio-member" endpoint of +// the "mailing-list" service. +func NewDeleteGroupsioMemberServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *DeleteGroupsioMemberServiceUnavailableResponseBody { + body := &DeleteGroupsioMemberServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListMemberBadRequestResponseBody builds the HTTP -// response body from the result of the "update-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -func NewUpdateGrpsioMailingListMemberBadRequestResponseBody(res *mailinglist.BadRequestError) *UpdateGrpsioMailingListMemberBadRequestResponseBody { - body := &UpdateGrpsioMailingListMemberBadRequestResponseBody{ +// NewInviteGroupsioMembersBadRequestResponseBody builds the HTTP response body +// from the result of the "invite-groupsio-members" endpoint of the +// "mailing-list" service. +func NewInviteGroupsioMembersBadRequestResponseBody(res *mailinglist.BadRequestError) *InviteGroupsioMembersBadRequestResponseBody { + body := &InviteGroupsioMembersBadRequestResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListMemberConflictResponseBody builds the HTTP -// response body from the result of the "update-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -func NewUpdateGrpsioMailingListMemberConflictResponseBody(res *mailinglist.ConflictError) *UpdateGrpsioMailingListMemberConflictResponseBody { - body := &UpdateGrpsioMailingListMemberConflictResponseBody{ +// NewInviteGroupsioMembersInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "invite-groupsio-members" endpoint of +// the "mailing-list" service. +func NewInviteGroupsioMembersInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *InviteGroupsioMembersInternalServerErrorResponseBody { + body := &InviteGroupsioMembersInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListMemberInternalServerErrorResponseBody builds the -// HTTP response body from the result of the -// "update-grpsio-mailing-list-member" endpoint of the "mailing-list" service. -func NewUpdateGrpsioMailingListMemberInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *UpdateGrpsioMailingListMemberInternalServerErrorResponseBody { - body := &UpdateGrpsioMailingListMemberInternalServerErrorResponseBody{ +// NewInviteGroupsioMembersNotFoundResponseBody builds the HTTP response body +// from the result of the "invite-groupsio-members" endpoint of the +// "mailing-list" service. +func NewInviteGroupsioMembersNotFoundResponseBody(res *mailinglist.NotFoundError) *InviteGroupsioMembersNotFoundResponseBody { + body := &InviteGroupsioMembersNotFoundResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListMemberNotFoundResponseBody builds the HTTP -// response body from the result of the "update-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -func NewUpdateGrpsioMailingListMemberNotFoundResponseBody(res *mailinglist.NotFoundError) *UpdateGrpsioMailingListMemberNotFoundResponseBody { - body := &UpdateGrpsioMailingListMemberNotFoundResponseBody{ +// NewInviteGroupsioMembersServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "invite-groupsio-members" endpoint of +// the "mailing-list" service. +func NewInviteGroupsioMembersServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *InviteGroupsioMembersServiceUnavailableResponseBody { + body := &InviteGroupsioMembersServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewUpdateGrpsioMailingListMemberServiceUnavailableResponseBody builds the -// HTTP response body from the result of the -// "update-grpsio-mailing-list-member" endpoint of the "mailing-list" service. -func NewUpdateGrpsioMailingListMemberServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *UpdateGrpsioMailingListMemberServiceUnavailableResponseBody { - body := &UpdateGrpsioMailingListMemberServiceUnavailableResponseBody{ +// NewCheckGroupsioSubscriberBadRequestResponseBody builds the HTTP response +// body from the result of the "check-groupsio-subscriber" endpoint of the +// "mailing-list" service. +func NewCheckGroupsioSubscriberBadRequestResponseBody(res *mailinglist.BadRequestError) *CheckGroupsioSubscriberBadRequestResponseBody { + body := &CheckGroupsioSubscriberBadRequestResponseBody{ Message: res.Message, } return body } -// NewDeleteGrpsioMailingListMemberBadRequestResponseBody builds the HTTP -// response body from the result of the "delete-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -func NewDeleteGrpsioMailingListMemberBadRequestResponseBody(res *mailinglist.BadRequestError) *DeleteGrpsioMailingListMemberBadRequestResponseBody { - body := &DeleteGrpsioMailingListMemberBadRequestResponseBody{ +// NewCheckGroupsioSubscriberInternalServerErrorResponseBody builds the HTTP +// response body from the result of the "check-groupsio-subscriber" endpoint of +// the "mailing-list" service. +func NewCheckGroupsioSubscriberInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *CheckGroupsioSubscriberInternalServerErrorResponseBody { + body := &CheckGroupsioSubscriberInternalServerErrorResponseBody{ Message: res.Message, } return body } -// NewDeleteGrpsioMailingListMemberConflictResponseBody builds the HTTP -// response body from the result of the "delete-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -func NewDeleteGrpsioMailingListMemberConflictResponseBody(res *mailinglist.ConflictError) *DeleteGrpsioMailingListMemberConflictResponseBody { - body := &DeleteGrpsioMailingListMemberConflictResponseBody{ +// NewCheckGroupsioSubscriberServiceUnavailableResponseBody builds the HTTP +// response body from the result of the "check-groupsio-subscriber" endpoint of +// the "mailing-list" service. +func NewCheckGroupsioSubscriberServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *CheckGroupsioSubscriberServiceUnavailableResponseBody { + body := &CheckGroupsioSubscriberServiceUnavailableResponseBody{ Message: res.Message, } return body } -// NewDeleteGrpsioMailingListMemberInternalServerErrorResponseBody builds the -// HTTP response body from the result of the -// "delete-grpsio-mailing-list-member" endpoint of the "mailing-list" service. -func NewDeleteGrpsioMailingListMemberInternalServerErrorResponseBody(res *mailinglist.InternalServerError) *DeleteGrpsioMailingListMemberInternalServerErrorResponseBody { - body := &DeleteGrpsioMailingListMemberInternalServerErrorResponseBody{ - Message: res.Message, - } - return body -} +// NewListGroupsioServicesPayload builds a mailing-list service +// list-groupsio-services endpoint payload. +func NewListGroupsioServicesPayload(projectUID *string, bearerToken *string) *mailinglist.ListGroupsioServicesPayload { + v := &mailinglist.ListGroupsioServicesPayload{} + v.ProjectUID = projectUID + v.BearerToken = bearerToken -// NewDeleteGrpsioMailingListMemberNotFoundResponseBody builds the HTTP -// response body from the result of the "delete-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -func NewDeleteGrpsioMailingListMemberNotFoundResponseBody(res *mailinglist.NotFoundError) *DeleteGrpsioMailingListMemberNotFoundResponseBody { - body := &DeleteGrpsioMailingListMemberNotFoundResponseBody{ - Message: res.Message, - } - return body + return v } -// NewDeleteGrpsioMailingListMemberServiceUnavailableResponseBody builds the -// HTTP response body from the result of the -// "delete-grpsio-mailing-list-member" endpoint of the "mailing-list" service. -func NewDeleteGrpsioMailingListMemberServiceUnavailableResponseBody(res *mailinglist.ServiceUnavailableError) *DeleteGrpsioMailingListMemberServiceUnavailableResponseBody { - body := &DeleteGrpsioMailingListMemberServiceUnavailableResponseBody{ - Message: res.Message, +// NewCreateGroupsioServicePayload builds a mailing-list service +// create-groupsio-service endpoint payload. +func NewCreateGroupsioServicePayload(body *CreateGroupsioServiceRequestBody, bearerToken *string) *mailinglist.CreateGroupsioServicePayload { + v := &mailinglist.CreateGroupsioServicePayload{ + ProjectUID: body.ProjectUID, + Type: body.Type, + GroupID: body.GroupID, + Domain: body.Domain, + Prefix: body.Prefix, + Status: body.Status, } - return body -} + v.BearerToken = bearerToken -// NewGroupsioWebhookBadRequestResponseBody builds the HTTP response body from -// the result of the "groupsio-webhook" endpoint of the "mailing-list" service. -func NewGroupsioWebhookBadRequestResponseBody(res *mailinglist.BadRequestError) *GroupsioWebhookBadRequestResponseBody { - body := &GroupsioWebhookBadRequestResponseBody{ - Message: res.Message, - } - return body + return v } -// NewGroupsioWebhookUnauthorizedResponseBody builds the HTTP response body -// from the result of the "groupsio-webhook" endpoint of the "mailing-list" -// service. -func NewGroupsioWebhookUnauthorizedResponseBody(res *mailinglist.UnauthorizedError) *GroupsioWebhookUnauthorizedResponseBody { - body := &GroupsioWebhookUnauthorizedResponseBody{ - Message: res.Message, - } - return body +// NewGetGroupsioServicePayload builds a mailing-list service +// get-groupsio-service endpoint payload. +func NewGetGroupsioServicePayload(serviceID string, bearerToken *string) *mailinglist.GetGroupsioServicePayload { + v := &mailinglist.GetGroupsioServicePayload{} + v.ServiceID = serviceID + v.BearerToken = bearerToken + + return v } -// NewCreateGrpsioServicePayload builds a mailing-list service -// create-grpsio-service endpoint payload. -func NewCreateGrpsioServicePayload(body *CreateGrpsioServiceRequestBody, version string, bearerToken *string) *mailinglist.CreateGrpsioServicePayload { - v := &mailinglist.CreateGrpsioServicePayload{ - Type: *body.Type, - Domain: body.Domain, - GroupID: body.GroupID, - Status: body.Status, - Prefix: body.Prefix, - ParentServiceUID: body.ParentServiceUID, - ProjectSlug: body.ProjectSlug, - ProjectUID: *body.ProjectUID, - URL: body.URL, - GroupName: body.GroupName, - } - if body.Public != nil { - v.Public = *body.Public +// NewUpdateGroupsioServicePayload builds a mailing-list service +// update-groupsio-service endpoint payload. +func NewUpdateGroupsioServicePayload(body *UpdateGroupsioServiceRequestBody, serviceID string, bearerToken *string) *mailinglist.UpdateGroupsioServicePayload { + v := &mailinglist.UpdateGroupsioServicePayload{ + ProjectUID: body.ProjectUID, + Type: body.Type, + GroupID: body.GroupID, + Domain: body.Domain, + Prefix: body.Prefix, + Status: body.Status, } - if body.GlobalOwners != nil { - v.GlobalOwners = make([]string, len(body.GlobalOwners)) - for i, val := range body.GlobalOwners { - v.GlobalOwners[i] = val - } - } - if body.Public == nil { - v.Public = false - } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoRequestBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoRequestBodyToMailinglistUserInfo(val) - } - } - v.Version = version + v.ServiceID = serviceID v.BearerToken = bearerToken return v } -// NewGetGrpsioServicePayload builds a mailing-list service get-grpsio-service -// endpoint payload. -func NewGetGrpsioServicePayload(uid string, version *string, bearerToken *string) *mailinglist.GetGrpsioServicePayload { - v := &mailinglist.GetGrpsioServicePayload{} - v.UID = &uid - v.Version = version +// NewDeleteGroupsioServicePayload builds a mailing-list service +// delete-groupsio-service endpoint payload. +func NewDeleteGroupsioServicePayload(serviceID string, bearerToken *string) *mailinglist.DeleteGroupsioServicePayload { + v := &mailinglist.DeleteGroupsioServicePayload{} + v.ServiceID = serviceID v.BearerToken = bearerToken return v } -// NewUpdateGrpsioServicePayload builds a mailing-list service -// update-grpsio-service endpoint payload. -func NewUpdateGrpsioServicePayload(body *UpdateGrpsioServiceRequestBody, uid string, version string, bearerToken *string, ifMatch *string) *mailinglist.UpdateGrpsioServicePayload { - v := &mailinglist.UpdateGrpsioServicePayload{ - Type: *body.Type, - Domain: body.Domain, - GroupID: body.GroupID, - Status: body.Status, - Prefix: body.Prefix, - ParentServiceUID: body.ParentServiceUID, - ProjectSlug: body.ProjectSlug, - ProjectUID: *body.ProjectUID, - URL: body.URL, - GroupName: body.GroupName, - } - if body.Public != nil { - v.Public = *body.Public - } - if body.GlobalOwners != nil { - v.GlobalOwners = make([]string, len(body.GlobalOwners)) - for i, val := range body.GlobalOwners { - v.GlobalOwners[i] = val - } - } - if body.Public == nil { - v.Public = false - } - v.UID = &uid - v.Version = version +// NewGetGroupsioServiceProjectsPayload builds a mailing-list service +// get-groupsio-service-projects endpoint payload. +func NewGetGroupsioServiceProjectsPayload(bearerToken *string) *mailinglist.GetGroupsioServiceProjectsPayload { + v := &mailinglist.GetGroupsioServiceProjectsPayload{} v.BearerToken = bearerToken - v.IfMatch = ifMatch return v } -// NewDeleteGrpsioServicePayload builds a mailing-list service -// delete-grpsio-service endpoint payload. -func NewDeleteGrpsioServicePayload(uid string, version *string, bearerToken *string, ifMatch *string) *mailinglist.DeleteGrpsioServicePayload { - v := &mailinglist.DeleteGrpsioServicePayload{} - v.UID = &uid - v.Version = version +// NewFindParentGroupsioServicePayload builds a mailing-list service +// find-parent-groupsio-service endpoint payload. +func NewFindParentGroupsioServicePayload(projectUID string, bearerToken *string) *mailinglist.FindParentGroupsioServicePayload { + v := &mailinglist.FindParentGroupsioServicePayload{} + v.ProjectUID = projectUID v.BearerToken = bearerToken - v.IfMatch = ifMatch return v } -// NewGetGrpsioServiceSettingsPayload builds a mailing-list service -// get-grpsio-service-settings endpoint payload. -func NewGetGrpsioServiceSettingsPayload(uid string, version *string, bearerToken *string) *mailinglist.GetGrpsioServiceSettingsPayload { - v := &mailinglist.GetGrpsioServiceSettingsPayload{} - v.UID = &uid - v.Version = version +// NewListGroupsioSubgroupsPayload builds a mailing-list service +// list-groupsio-subgroups endpoint payload. +func NewListGroupsioSubgroupsPayload(projectUID *string, committeeUID *string, bearerToken *string) *mailinglist.ListGroupsioSubgroupsPayload { + v := &mailinglist.ListGroupsioSubgroupsPayload{} + v.ProjectUID = projectUID + v.CommitteeUID = committeeUID v.BearerToken = bearerToken return v } -// NewUpdateGrpsioServiceSettingsPayload builds a mailing-list service -// update-grpsio-service-settings endpoint payload. -func NewUpdateGrpsioServiceSettingsPayload(body *UpdateGrpsioServiceSettingsRequestBody, uid string, version string, bearerToken *string, ifMatch *string) *mailinglist.UpdateGrpsioServiceSettingsPayload { - v := &mailinglist.UpdateGrpsioServiceSettingsPayload{} - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoRequestBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoRequestBodyToMailinglistUserInfo(val) - } +// NewCreateGroupsioSubgroupPayload builds a mailing-list service +// create-groupsio-subgroup endpoint payload. +func NewCreateGroupsioSubgroupPayload(body *CreateGroupsioSubgroupRequestBody, bearerToken *string) *mailinglist.CreateGroupsioSubgroupPayload { + v := &mailinglist.CreateGroupsioSubgroupPayload{ + ProjectUID: body.ProjectUID, + CommitteeUID: body.CommitteeUID, + GroupID: body.GroupID, + Name: body.Name, + Description: body.Description, + Type: body.Type, + AudienceAccess: body.AudienceAccess, } - v.UID = uid - v.Version = version v.BearerToken = bearerToken - v.IfMatch = ifMatch return v } -// NewCreateGrpsioMailingListPayload builds a mailing-list service -// create-grpsio-mailing-list endpoint payload. -func NewCreateGrpsioMailingListPayload(body *CreateGrpsioMailingListRequestBody, version string, bearerToken *string) *mailinglist.CreateGrpsioMailingListPayload { - v := &mailinglist.CreateGrpsioMailingListPayload{ - GroupName: *body.GroupName, - GroupID: body.GroupID, - Public: *body.Public, - Type: *body.Type, - Description: *body.Description, - Title: *body.Title, - SubjectTag: body.SubjectTag, - ServiceUID: *body.ServiceUID, - SubscriberCount: body.SubscriberCount, - } - if body.AudienceAccess != nil { - v.AudienceAccess = *body.AudienceAccess - } - if body.AudienceAccess == nil { - v.AudienceAccess = "public" - } - if body.Committees != nil { - v.Committees = make([]*mailinglist.Committee, len(body.Committees)) - for i, val := range body.Committees { - v.Committees[i] = unmarshalCommitteeRequestBodyToMailinglistCommittee(val) - } - } - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoRequestBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoRequestBodyToMailinglistUserInfo(val) - } - } - v.Version = version +// NewGetGroupsioSubgroupPayload builds a mailing-list service +// get-groupsio-subgroup endpoint payload. +func NewGetGroupsioSubgroupPayload(subgroupID string, bearerToken *string) *mailinglist.GetGroupsioSubgroupPayload { + v := &mailinglist.GetGroupsioSubgroupPayload{} + v.SubgroupID = subgroupID v.BearerToken = bearerToken return v } -// NewGetGrpsioMailingListPayload builds a mailing-list service -// get-grpsio-mailing-list endpoint payload. -func NewGetGrpsioMailingListPayload(uid string, version string, bearerToken string) *mailinglist.GetGrpsioMailingListPayload { - v := &mailinglist.GetGrpsioMailingListPayload{} - v.UID = &uid - v.Version = version +// NewUpdateGroupsioSubgroupPayload builds a mailing-list service +// update-groupsio-subgroup endpoint payload. +func NewUpdateGroupsioSubgroupPayload(body *UpdateGroupsioSubgroupRequestBody, subgroupID string, bearerToken *string) *mailinglist.UpdateGroupsioSubgroupPayload { + v := &mailinglist.UpdateGroupsioSubgroupPayload{ + ProjectUID: body.ProjectUID, + CommitteeUID: body.CommitteeUID, + GroupID: body.GroupID, + Name: body.Name, + Description: body.Description, + Type: body.Type, + AudienceAccess: body.AudienceAccess, + } + v.SubgroupID = subgroupID v.BearerToken = bearerToken return v } -// NewUpdateGrpsioMailingListPayload builds a mailing-list service -// update-grpsio-mailing-list endpoint payload. -func NewUpdateGrpsioMailingListPayload(body *UpdateGrpsioMailingListRequestBody, uid string, version string, bearerToken *string, ifMatch *string) *mailinglist.UpdateGrpsioMailingListPayload { - v := &mailinglist.UpdateGrpsioMailingListPayload{ - GroupName: *body.GroupName, - GroupID: body.GroupID, - Public: *body.Public, - Type: *body.Type, - Description: *body.Description, - Title: *body.Title, - SubjectTag: body.SubjectTag, - ServiceUID: *body.ServiceUID, - SubscriberCount: body.SubscriberCount, - } - if body.AudienceAccess != nil { - v.AudienceAccess = *body.AudienceAccess - } - if body.AudienceAccess == nil { - v.AudienceAccess = "public" - } - if body.Committees != nil { - v.Committees = make([]*mailinglist.Committee, len(body.Committees)) - for i, val := range body.Committees { - v.Committees[i] = unmarshalCommitteeRequestBodyToMailinglistCommittee(val) - } - } - v.UID = &uid - v.Version = version +// NewDeleteGroupsioSubgroupPayload builds a mailing-list service +// delete-groupsio-subgroup endpoint payload. +func NewDeleteGroupsioSubgroupPayload(subgroupID string, bearerToken *string) *mailinglist.DeleteGroupsioSubgroupPayload { + v := &mailinglist.DeleteGroupsioSubgroupPayload{} + v.SubgroupID = subgroupID v.BearerToken = bearerToken - v.IfMatch = ifMatch return v } -// NewDeleteGrpsioMailingListPayload builds a mailing-list service -// delete-grpsio-mailing-list endpoint payload. -func NewDeleteGrpsioMailingListPayload(uid string, version *string, bearerToken *string, ifMatch *string) *mailinglist.DeleteGrpsioMailingListPayload { - v := &mailinglist.DeleteGrpsioMailingListPayload{} - v.UID = &uid - v.Version = version +// NewGetGroupsioSubgroupCountPayload builds a mailing-list service +// get-groupsio-subgroup-count endpoint payload. +func NewGetGroupsioSubgroupCountPayload(projectUID string, bearerToken *string) *mailinglist.GetGroupsioSubgroupCountPayload { + v := &mailinglist.GetGroupsioSubgroupCountPayload{} + v.ProjectUID = projectUID v.BearerToken = bearerToken - v.IfMatch = ifMatch return v } -// NewGetGrpsioMailingListSettingsPayload builds a mailing-list service -// get-grpsio-mailing-list-settings endpoint payload. -func NewGetGrpsioMailingListSettingsPayload(uid string, version *string, bearerToken *string) *mailinglist.GetGrpsioMailingListSettingsPayload { - v := &mailinglist.GetGrpsioMailingListSettingsPayload{} - v.UID = &uid - v.Version = version +// NewGetGroupsioSubgroupMemberCountPayload builds a mailing-list service +// get-groupsio-subgroup-member-count endpoint payload. +func NewGetGroupsioSubgroupMemberCountPayload(subgroupID string, bearerToken *string) *mailinglist.GetGroupsioSubgroupMemberCountPayload { + v := &mailinglist.GetGroupsioSubgroupMemberCountPayload{} + v.SubgroupID = subgroupID v.BearerToken = bearerToken return v } -// NewUpdateGrpsioMailingListSettingsPayload builds a mailing-list service -// update-grpsio-mailing-list-settings endpoint payload. -func NewUpdateGrpsioMailingListSettingsPayload(body *UpdateGrpsioMailingListSettingsRequestBody, uid string, version string, bearerToken *string, ifMatch *string) *mailinglist.UpdateGrpsioMailingListSettingsPayload { - v := &mailinglist.UpdateGrpsioMailingListSettingsPayload{} - if body.Writers != nil { - v.Writers = make([]*mailinglist.UserInfo, len(body.Writers)) - for i, val := range body.Writers { - v.Writers[i] = unmarshalUserInfoRequestBodyToMailinglistUserInfo(val) - } - } - if body.Auditors != nil { - v.Auditors = make([]*mailinglist.UserInfo, len(body.Auditors)) - for i, val := range body.Auditors { - v.Auditors[i] = unmarshalUserInfoRequestBodyToMailinglistUserInfo(val) - } - } - v.UID = uid - v.Version = version +// NewListGroupsioMembersPayload builds a mailing-list service +// list-groupsio-members endpoint payload. +func NewListGroupsioMembersPayload(subgroupID string, bearerToken *string) *mailinglist.ListGroupsioMembersPayload { + v := &mailinglist.ListGroupsioMembersPayload{} + v.SubgroupID = subgroupID v.BearerToken = bearerToken - v.IfMatch = ifMatch return v } -// NewCreateGrpsioMailingListMemberPayload builds a mailing-list service -// create-grpsio-mailing-list-member endpoint payload. -func NewCreateGrpsioMailingListMemberPayload(body *CreateGrpsioMailingListMemberRequestBody, uid string, version string, bearerToken *string) *mailinglist.CreateGrpsioMailingListMemberPayload { - v := &mailinglist.CreateGrpsioMailingListMemberPayload{ - Username: body.Username, - FirstName: body.FirstName, - LastName: body.LastName, - Email: *body.Email, - Organization: body.Organization, - JobTitle: body.JobTitle, - LastReviewedAt: body.LastReviewedAt, - LastReviewedBy: body.LastReviewedBy, +// NewAddGroupsioMemberPayload builds a mailing-list service +// add-groupsio-member endpoint payload. +func NewAddGroupsioMemberPayload(body *AddGroupsioMemberRequestBody, subgroupID string, bearerToken *string) *mailinglist.AddGroupsioMemberPayload { + v := &mailinglist.AddGroupsioMemberPayload{ + Email: body.Email, + Name: body.Name, + ModStatus: body.ModStatus, + DeliveryMode: body.DeliveryMode, } - if body.MemberType != nil { - v.MemberType = *body.MemberType - } - if body.DeliveryMode != nil { - v.DeliveryMode = *body.DeliveryMode - } - if body.ModStatus != nil { - v.ModStatus = *body.ModStatus - } - if body.MemberType == nil { - v.MemberType = "direct" - } - if body.DeliveryMode == nil { - v.DeliveryMode = "normal" - } - if body.ModStatus == nil { - v.ModStatus = "none" - } - v.UID = uid - v.Version = version + v.SubgroupID = subgroupID v.BearerToken = bearerToken return v } -// NewGetGrpsioMailingListMemberPayload builds a mailing-list service -// get-grpsio-mailing-list-member endpoint payload. -func NewGetGrpsioMailingListMemberPayload(uid string, memberUID string, version string, bearerToken string) *mailinglist.GetGrpsioMailingListMemberPayload { - v := &mailinglist.GetGrpsioMailingListMemberPayload{} - v.UID = uid - v.MemberUID = memberUID - v.Version = version +// NewGetGroupsioMemberPayload builds a mailing-list service +// get-groupsio-member endpoint payload. +func NewGetGroupsioMemberPayload(subgroupID string, memberID string, bearerToken *string) *mailinglist.GetGroupsioMemberPayload { + v := &mailinglist.GetGroupsioMemberPayload{} + v.SubgroupID = subgroupID + v.MemberID = memberID v.BearerToken = bearerToken return v } -// NewUpdateGrpsioMailingListMemberPayload builds a mailing-list service -// update-grpsio-mailing-list-member endpoint payload. -func NewUpdateGrpsioMailingListMemberPayload(body *UpdateGrpsioMailingListMemberRequestBody, uid string, memberUID string, version string, bearerToken string, ifMatch string) *mailinglist.UpdateGrpsioMailingListMemberPayload { - v := &mailinglist.UpdateGrpsioMailingListMemberPayload{ - Username: body.Username, - FirstName: body.FirstName, - LastName: body.LastName, - Organization: body.Organization, - JobTitle: body.JobTitle, +// NewUpdateGroupsioMemberPayload builds a mailing-list service +// update-groupsio-member endpoint payload. +func NewUpdateGroupsioMemberPayload(body *UpdateGroupsioMemberRequestBody, subgroupID string, memberID string, bearerToken *string) *mailinglist.UpdateGroupsioMemberPayload { + v := &mailinglist.UpdateGroupsioMemberPayload{ + Email: body.Email, + Name: body.Name, + ModStatus: body.ModStatus, + DeliveryMode: body.DeliveryMode, } - if body.DeliveryMode != nil { - v.DeliveryMode = *body.DeliveryMode - } - if body.ModStatus != nil { - v.ModStatus = *body.ModStatus - } - if body.DeliveryMode == nil { - v.DeliveryMode = "normal" - } - if body.ModStatus == nil { - v.ModStatus = "none" - } - v.UID = uid - v.MemberUID = memberUID - v.Version = version + v.SubgroupID = subgroupID + v.MemberID = memberID v.BearerToken = bearerToken - v.IfMatch = ifMatch return v } -// NewDeleteGrpsioMailingListMemberPayload builds a mailing-list service -// delete-grpsio-mailing-list-member endpoint payload. -func NewDeleteGrpsioMailingListMemberPayload(uid string, memberUID string, version string, bearerToken string, ifMatch string) *mailinglist.DeleteGrpsioMailingListMemberPayload { - v := &mailinglist.DeleteGrpsioMailingListMemberPayload{} - v.UID = uid - v.MemberUID = memberUID - v.Version = version +// NewDeleteGroupsioMemberPayload builds a mailing-list service +// delete-groupsio-member endpoint payload. +func NewDeleteGroupsioMemberPayload(subgroupID string, memberID string, bearerToken *string) *mailinglist.DeleteGroupsioMemberPayload { + v := &mailinglist.DeleteGroupsioMemberPayload{} + v.SubgroupID = subgroupID + v.MemberID = memberID v.BearerToken = bearerToken - v.IfMatch = ifMatch return v } -// NewGroupsioWebhookPayload builds a mailing-list service groupsio-webhook -// endpoint payload. -func NewGroupsioWebhookPayload(body *GroupsioWebhookRequestBody, signature string) *mailinglist.GroupsioWebhookPayload { - v := &mailinglist.GroupsioWebhookPayload{ - Action: *body.Action, - Group: body.Group, - MemberInfo: body.MemberInfo, - Extra: body.Extra, - ExtraID: body.ExtraID, +// NewInviteGroupsioMembersPayload builds a mailing-list service +// invite-groupsio-members endpoint payload. +func NewInviteGroupsioMembersPayload(body *InviteGroupsioMembersRequestBody, subgroupID string, bearerToken *string) *mailinglist.InviteGroupsioMembersPayload { + v := &mailinglist.InviteGroupsioMembersPayload{} + v.Emails = make([]string, len(body.Emails)) + for i, val := range body.Emails { + v.Emails[i] = val } - v.Signature = signature + v.SubgroupID = subgroupID + v.BearerToken = bearerToken return v } -// ValidateCreateGrpsioServiceRequestBody runs the validations defined on -// Create-Grpsio-ServiceRequestBody -func ValidateCreateGrpsioServiceRequestBody(body *CreateGrpsioServiceRequestBody) (err error) { - if body.Type == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("type", "body")) +// NewCheckGroupsioSubscriberPayload builds a mailing-list service +// check-groupsio-subscriber endpoint payload. +func NewCheckGroupsioSubscriberPayload(body *CheckGroupsioSubscriberRequestBody, bearerToken *string) *mailinglist.CheckGroupsioSubscriberPayload { + v := &mailinglist.CheckGroupsioSubscriberPayload{ + Email: *body.Email, + SubgroupID: *body.SubgroupID, } - if body.ProjectUID == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("project_uid", "body")) - } - if body.Type != nil { - if !(*body.Type == "primary" || *body.Type == "formation" || *body.Type == "shared") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", *body.Type, []any{"primary", "formation", "shared"})) - } - } - for _, e := range body.GlobalOwners { - err = goa.MergeErrors(err, goa.ValidateFormat("body.global_owners[*]", e, goa.FormatEmail)) - } - if body.ParentServiceUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.parent_service_uid", *body.ParentServiceUID, goa.FormatUUID)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_slug", *body.ProjectSlug, goa.FormatRegexp)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.project_slug", *body.ProjectSlug, "^[a-z][a-z0-9_\\-]*[a-z0-9]$")) - } - if body.ProjectUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) - } - if body.URL != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.url", *body.URL, goa.FormatURI)) - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - return + v.BearerToken = bearerToken + + return v } -// ValidateUpdateGrpsioServiceRequestBody runs the validations defined on -// Update-Grpsio-ServiceRequestBody -func ValidateUpdateGrpsioServiceRequestBody(body *UpdateGrpsioServiceRequestBody) (err error) { - if body.Type == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("type", "body")) - } - if body.ProjectUID == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("project_uid", "body")) +// ValidateCreateGroupsioServiceRequestBody runs the validations defined on +// Create-Groupsio-ServiceRequestBody +func ValidateCreateGroupsioServiceRequestBody(body *CreateGroupsioServiceRequestBody) (err error) { + if body.ProjectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) } if body.Type != nil { if !(*body.Type == "primary" || *body.Type == "formation" || *body.Type == "shared") { err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", *body.Type, []any{"primary", "formation", "shared"})) } } - for _, e := range body.GlobalOwners { - err = goa.MergeErrors(err, goa.ValidateFormat("body.global_owners[*]", e, goa.FormatEmail)) - } - if body.ParentServiceUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.parent_service_uid", *body.ParentServiceUID, goa.FormatUUID)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_slug", *body.ProjectSlug, goa.FormatRegexp)) - } - if body.ProjectSlug != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.project_slug", *body.ProjectSlug, "^[a-z][a-z0-9_\\-]*[a-z0-9]$")) - } - if body.ProjectUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) - } - if body.URL != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.url", *body.URL, goa.FormatURI)) - } - return -} - -// ValidateUpdateGrpsioServiceSettingsRequestBody runs the validations defined -// on Update-Grpsio-Service-SettingsRequestBody -func ValidateUpdateGrpsioServiceSettingsRequestBody(body *UpdateGrpsioServiceSettingsRequestBody) (err error) { - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } return } -// ValidateCreateGrpsioMailingListRequestBody runs the validations defined on -// Create-Grpsio-Mailing-ListRequestBody -func ValidateCreateGrpsioMailingListRequestBody(body *CreateGrpsioMailingListRequestBody) (err error) { - if body.GroupName == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("group_name", "body")) - } - if body.Public == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("public", "body")) - } - if body.Type == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("type", "body")) - } - if body.Description == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("description", "body")) - } - if body.Title == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("title", "body")) - } - if body.ServiceUID == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("service_uid", "body")) - } - if body.GroupName != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.group_name", *body.GroupName, "^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$")) - } - if body.GroupName != nil { - if utf8.RuneCountInString(*body.GroupName) < 3 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", *body.GroupName, utf8.RuneCountInString(*body.GroupName), 3, true)) - } - } - if body.GroupName != nil { - if utf8.RuneCountInString(*body.GroupName) > 34 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", *body.GroupName, utf8.RuneCountInString(*body.GroupName), 34, false)) - } - } - if body.GroupID != nil { - if *body.GroupID < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.group_id", *body.GroupID, 0, true)) - } +// ValidateUpdateGroupsioServiceRequestBody runs the validations defined on +// Update-Groupsio-ServiceRequestBody +func ValidateUpdateGroupsioServiceRequestBody(body *UpdateGroupsioServiceRequestBody) (err error) { + if body.ProjectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) } if body.Type != nil { - if !(*body.Type == "announcement" || *body.Type == "discussion_moderated" || *body.Type == "discussion_open") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", *body.Type, []any{"announcement", "discussion_moderated", "discussion_open"})) - } - } - if body.AudienceAccess != nil { - if !(*body.AudienceAccess == "public" || *body.AudienceAccess == "approval_required" || *body.AudienceAccess == "invite_only") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.audience_access", *body.AudienceAccess, []any{"public", "approval_required", "invite_only"})) - } - } - for _, e := range body.Committees { - if e != nil { - if err2 := ValidateCommitteeRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if body.Description != nil { - if utf8.RuneCountInString(*body.Description) < 11 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", *body.Description, utf8.RuneCountInString(*body.Description), 11, true)) - } - } - if body.Description != nil { - if utf8.RuneCountInString(*body.Description) > 500 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", *body.Description, utf8.RuneCountInString(*body.Description), 500, false)) - } - } - if body.Title != nil { - if utf8.RuneCountInString(*body.Title) < 5 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", *body.Title, utf8.RuneCountInString(*body.Title), 5, true)) - } - } - if body.Title != nil { - if utf8.RuneCountInString(*body.Title) > 100 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", *body.Title, utf8.RuneCountInString(*body.Title), 100, false)) - } - } - if body.SubjectTag != nil { - if utf8.RuneCountInString(*body.SubjectTag) > 50 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.subject_tag", *body.SubjectTag, utf8.RuneCountInString(*body.SubjectTag), 50, false)) - } - } - if body.ServiceUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.service_uid", *body.ServiceUID, goa.FormatUUID)) - } - if body.SubscriberCount != nil { - if *body.SubscriberCount < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.subscriber_count", *body.SubscriberCount, 0, true)) - } - } - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } + if !(*body.Type == "primary" || *body.Type == "formation" || *body.Type == "shared") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", *body.Type, []any{"primary", "formation", "shared"})) } } return } -// ValidateUpdateGrpsioMailingListRequestBody runs the validations defined on -// Update-Grpsio-Mailing-ListRequestBody -func ValidateUpdateGrpsioMailingListRequestBody(body *UpdateGrpsioMailingListRequestBody) (err error) { - if body.GroupName == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("group_name", "body")) - } - if body.Public == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("public", "body")) - } - if body.Type == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("type", "body")) - } - if body.Description == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("description", "body")) - } - if body.Title == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("title", "body")) - } - if body.ServiceUID == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("service_uid", "body")) - } - if body.GroupName != nil { - err = goa.MergeErrors(err, goa.ValidatePattern("body.group_name", *body.GroupName, "^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$")) - } - if body.GroupName != nil { - if utf8.RuneCountInString(*body.GroupName) < 3 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", *body.GroupName, utf8.RuneCountInString(*body.GroupName), 3, true)) - } - } - if body.GroupName != nil { - if utf8.RuneCountInString(*body.GroupName) > 34 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.group_name", *body.GroupName, utf8.RuneCountInString(*body.GroupName), 34, false)) - } - } - if body.GroupID != nil { - if *body.GroupID < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.group_id", *body.GroupID, 0, true)) - } - } - if body.Type != nil { - if !(*body.Type == "announcement" || *body.Type == "discussion_moderated" || *body.Type == "discussion_open") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.type", *body.Type, []any{"announcement", "discussion_moderated", "discussion_open"})) - } - } - if body.AudienceAccess != nil { - if !(*body.AudienceAccess == "public" || *body.AudienceAccess == "approval_required" || *body.AudienceAccess == "invite_only") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.audience_access", *body.AudienceAccess, []any{"public", "approval_required", "invite_only"})) - } - } - for _, e := range body.Committees { - if e != nil { - if err2 := ValidateCommitteeRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } - } - if body.Description != nil { - if utf8.RuneCountInString(*body.Description) < 11 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", *body.Description, utf8.RuneCountInString(*body.Description), 11, true)) - } - } - if body.Description != nil { - if utf8.RuneCountInString(*body.Description) > 500 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.description", *body.Description, utf8.RuneCountInString(*body.Description), 500, false)) - } - } - if body.Title != nil { - if utf8.RuneCountInString(*body.Title) < 5 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", *body.Title, utf8.RuneCountInString(*body.Title), 5, true)) - } - } - if body.Title != nil { - if utf8.RuneCountInString(*body.Title) > 100 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.title", *body.Title, utf8.RuneCountInString(*body.Title), 100, false)) - } - } - if body.SubjectTag != nil { - if utf8.RuneCountInString(*body.SubjectTag) > 50 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.subject_tag", *body.SubjectTag, utf8.RuneCountInString(*body.SubjectTag), 50, false)) - } +// ValidateCreateGroupsioSubgroupRequestBody runs the validations defined on +// Create-Groupsio-SubgroupRequestBody +func ValidateCreateGroupsioSubgroupRequestBody(body *CreateGroupsioSubgroupRequestBody) (err error) { + if body.ProjectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) } - if body.ServiceUID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.service_uid", *body.ServiceUID, goa.FormatUUID)) - } - if body.SubscriberCount != nil { - if *body.SubscriberCount < 0 { - err = goa.MergeErrors(err, goa.InvalidRangeError("body.subscriber_count", *body.SubscriberCount, 0, true)) - } + if body.CommitteeUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.committee_uid", *body.CommitteeUID, goa.FormatUUID)) } return } -// ValidateUpdateGrpsioMailingListSettingsRequestBody runs the validations -// defined on Update-Grpsio-Mailing-List-SettingsRequestBody -func ValidateUpdateGrpsioMailingListSettingsRequestBody(body *UpdateGrpsioMailingListSettingsRequestBody) (err error) { - for _, e := range body.Writers { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } +// ValidateUpdateGroupsioSubgroupRequestBody runs the validations defined on +// Update-Groupsio-SubgroupRequestBody +func ValidateUpdateGroupsioSubgroupRequestBody(body *UpdateGroupsioSubgroupRequestBody) (err error) { + if body.ProjectUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.project_uid", *body.ProjectUID, goa.FormatUUID)) } - for _, e := range body.Auditors { - if e != nil { - if err2 := ValidateUserInfoRequestBody(e); err2 != nil { - err = goa.MergeErrors(err, err2) - } - } + if body.CommitteeUID != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.committee_uid", *body.CommitteeUID, goa.FormatUUID)) } return } -// ValidateCreateGrpsioMailingListMemberRequestBody runs the validations -// defined on Create-Grpsio-Mailing-List-MemberRequestBody -func ValidateCreateGrpsioMailingListMemberRequestBody(body *CreateGrpsioMailingListMemberRequestBody) (err error) { - if body.Email == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("email", "body")) - } - if body.Username != nil { - if utf8.RuneCountInString(*body.Username) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.username", *body.Username, utf8.RuneCountInString(*body.Username), 255, false)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 1, true)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 255, false)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 1, true)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 255, false)) - } - } +// ValidateAddGroupsioMemberRequestBody runs the validations defined on +// Add-Groupsio-MemberRequestBody +func ValidateAddGroupsioMemberRequestBody(body *AddGroupsioMemberRequestBody) (err error) { if body.Email != nil { err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) } - if body.Organization != nil { - if utf8.RuneCountInString(*body.Organization) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.organization", *body.Organization, utf8.RuneCountInString(*body.Organization), 255, false)) - } - } - if body.JobTitle != nil { - if utf8.RuneCountInString(*body.JobTitle) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.job_title", *body.JobTitle, utf8.RuneCountInString(*body.JobTitle), 255, false)) - } - } - if body.MemberType != nil { - if !(*body.MemberType == "committee" || *body.MemberType == "direct") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.member_type", *body.MemberType, []any{"committee", "direct"})) + if body.ModStatus != nil { + if !(*body.ModStatus == "none" || *body.ModStatus == "moderator" || *body.ModStatus == "owner") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.mod_status", *body.ModStatus, []any{"none", "moderator", "owner"})) } } if body.DeliveryMode != nil { @@ -3468,107 +2398,48 @@ func ValidateCreateGrpsioMailingListMemberRequestBody(body *CreateGrpsioMailingL err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.delivery_mode", *body.DeliveryMode, []any{"normal", "digest", "none"})) } } - if body.ModStatus != nil { - if !(*body.ModStatus == "none" || *body.ModStatus == "moderator" || *body.ModStatus == "owner") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.mod_status", *body.ModStatus, []any{"none", "moderator", "owner"})) - } - } - if body.LastReviewedAt != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.last_reviewed_at", *body.LastReviewedAt, goa.FormatDateTime)) - } return } -// ValidateUpdateGrpsioMailingListMemberRequestBody runs the validations -// defined on Update-Grpsio-Mailing-List-MemberRequestBody -func ValidateUpdateGrpsioMailingListMemberRequestBody(body *UpdateGrpsioMailingListMemberRequestBody) (err error) { - if body.Username != nil { - if utf8.RuneCountInString(*body.Username) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.username", *body.Username, utf8.RuneCountInString(*body.Username), 255, false)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 1, true)) - } - } - if body.FirstName != nil { - if utf8.RuneCountInString(*body.FirstName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.first_name", *body.FirstName, utf8.RuneCountInString(*body.FirstName), 255, false)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) < 1 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 1, true)) - } - } - if body.LastName != nil { - if utf8.RuneCountInString(*body.LastName) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.last_name", *body.LastName, utf8.RuneCountInString(*body.LastName), 255, false)) - } - } - if body.Organization != nil { - if utf8.RuneCountInString(*body.Organization) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.organization", *body.Organization, utf8.RuneCountInString(*body.Organization), 255, false)) - } - } - if body.JobTitle != nil { - if utf8.RuneCountInString(*body.JobTitle) > 255 { - err = goa.MergeErrors(err, goa.InvalidLengthError("body.job_title", *body.JobTitle, utf8.RuneCountInString(*body.JobTitle), 255, false)) - } - } - if body.DeliveryMode != nil { - if !(*body.DeliveryMode == "normal" || *body.DeliveryMode == "digest" || *body.DeliveryMode == "none") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.delivery_mode", *body.DeliveryMode, []any{"normal", "digest", "none"})) - } +// ValidateUpdateGroupsioMemberRequestBody runs the validations defined on +// Update-Groupsio-MemberRequestBody +func ValidateUpdateGroupsioMemberRequestBody(body *UpdateGroupsioMemberRequestBody) (err error) { + if body.Email != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) } if body.ModStatus != nil { if !(*body.ModStatus == "none" || *body.ModStatus == "moderator" || *body.ModStatus == "owner") { err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.mod_status", *body.ModStatus, []any{"none", "moderator", "owner"})) } } - return -} - -// ValidateGroupsioWebhookRequestBody runs the validations defined on -// Groupsio-WebhookRequestBody -func ValidateGroupsioWebhookRequestBody(body *GroupsioWebhookRequestBody) (err error) { - if body.Action == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("action", "body")) - } - if body.Action != nil { - if !(*body.Action == "created_subgroup" || *body.Action == "deleted_subgroup" || *body.Action == "added_member" || *body.Action == "removed_member" || *body.Action == "ban_members") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.action", *body.Action, []any{"created_subgroup", "deleted_subgroup", "added_member", "removed_member", "ban_members"})) + if body.DeliveryMode != nil { + if !(*body.DeliveryMode == "normal" || *body.DeliveryMode == "digest" || *body.DeliveryMode == "none") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.delivery_mode", *body.DeliveryMode, []any{"normal", "digest", "none"})) } } return } -// ValidateUserInfoRequestBody runs the validations defined on -// UserInfoRequestBody -func ValidateUserInfoRequestBody(body *UserInfoRequestBody) (err error) { - if body.Email != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) - } - if body.Avatar != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.avatar", *body.Avatar, goa.FormatURI)) +// ValidateInviteGroupsioMembersRequestBody runs the validations defined on +// Invite-Groupsio-MembersRequestBody +func ValidateInviteGroupsioMembersRequestBody(body *InviteGroupsioMembersRequestBody) (err error) { + if body.Emails == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("emails", "body")) } return } -// ValidateCommitteeRequestBody runs the validations defined on -// CommitteeRequestBody -func ValidateCommitteeRequestBody(body *CommitteeRequestBody) (err error) { - if body.UID == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("uid", "body")) +// ValidateCheckGroupsioSubscriberRequestBody runs the validations defined on +// Check-Groupsio-SubscriberRequestBody +func ValidateCheckGroupsioSubscriberRequestBody(body *CheckGroupsioSubscriberRequestBody) (err error) { + if body.Email == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("email", "body")) } - if body.UID != nil { - err = goa.MergeErrors(err, goa.ValidateFormat("body.uid", *body.UID, goa.FormatUUID)) + if body.SubgroupID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("subgroup_id", "body")) } - for _, e := range body.AllowedVotingStatuses { - if !(e == "Voting Rep" || e == "Alternate Voting Rep" || e == "Observer" || e == "Emeritus" || e == "None") { - err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.allowed_voting_statuses[*]", e, []any{"Voting Rep", "Alternate Voting Rep", "Observer", "Emeritus", "None"})) - } + if body.Email != nil { + err = goa.MergeErrors(err, goa.ValidateFormat("body.email", *body.Email, goa.FormatEmail)) } return } diff --git a/gen/http/openapi.json b/gen/http/openapi.json index 7a443b4..4e8768c 100644 --- a/gen/http/openapi.json +++ b/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Mailing List Service","description":"Service for managing mailing lists in LFX","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/groupsio/mailing-lists":{"post":{"tags":["mailing-list"],"summary":"create-grpsio-mailing-list mailing-list","description":"Create GroupsIO mailing list/subgroup with comprehensive validation","operationId":"mailing-list#create-grpsio-mailing-list","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Grpsio-Mailing-ListRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListCreateGrpsioMailingListRequestBody","required":["group_name","public","type","description","title","service_uid"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/GrpsIoMailingListFull"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/mailing-lists/{uid}":{"get":{"tags":["mailing-list"],"summary":"get-grpsio-mailing-list mailing-list","description":"Get GroupsIO mailing list details by UID","operationId":"mailing-list#get-grpsio-mailing-list","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":true,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MailingListGetGrpsioMailingListResponseBody"},"headers":{"ETag":{"description":"ETag header value","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-grpsio-mailing-list mailing-list","description":"Update GroupsIO mailing list","operationId":"mailing-list#update-grpsio-mailing-list","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","required":false,"type":"string"},{"name":"Update-Grpsio-Mailing-ListRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListUpdateGrpsioMailingListRequestBody","required":["group_name","public","type","description","title","service_uid"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GrpsIoMailingListWithReadonlyAttributes"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"delete":{"tags":["mailing-list"],"summary":"delete-grpsio-mailing-list mailing-list","description":"Delete GroupsIO mailing list","operationId":"mailing-list#delete-grpsio-mailing-list","parameters":[{"name":"v","in":"query","description":"Version of the API","required":false,"type":"string","enum":["1"]},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/mailing-lists/{uid}/members":{"post":{"tags":["mailing-list"],"summary":"create-grpsio-mailing-list-member mailing-list","description":"Create a new member for a GroupsIO mailing list","operationId":"mailing-list#create-grpsio-mailing-list-member","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"uid","in":"path","description":"Mailing list UID","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Grpsio-Mailing-List-MemberRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListCreateGrpsioMailingListMemberRequestBody","required":["email"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/GrpsIoMemberFull","required":["uid","mailing_list_uid","first_name","last_name","email","member_type","delivery_mode","mod_status","status","created_at","updated_at"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/mailing-lists/{uid}/members/{member_uid}":{"get":{"tags":["mailing-list"],"summary":"get-grpsio-mailing-list-member mailing-list","description":"Get a member of a GroupsIO mailing list by UID","operationId":"mailing-list#get-grpsio-mailing-list-member","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"type":"string","format":"uuid"},{"name":"member_uid","in":"path","description":"Member UID -- unique identifier for the member","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":true,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MailingListGetGrpsioMailingListMemberResponseBody"},"headers":{"ETag":{"description":"ETag header value","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-grpsio-mailing-list-member mailing-list","description":"Update a member of a GroupsIO mailing list","operationId":"mailing-list#update-grpsio-mailing-list-member","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"type":"string","format":"uuid"},{"name":"member_uid","in":"path","description":"Member UID -- unique identifier for the member","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":true,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","required":true,"type":"string"},{"name":"Update-Grpsio-Mailing-List-MemberRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListUpdateGrpsioMailingListMemberRequestBody"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GrpsIoMemberWithReadonlyAttributes"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"delete":{"tags":["mailing-list"],"summary":"delete-grpsio-mailing-list-member mailing-list","description":"Delete a member from a GroupsIO mailing list","operationId":"mailing-list#delete-grpsio-mailing-list-member","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"type":"string","format":"uuid"},{"name":"member_uid","in":"path","description":"Member UID -- unique identifier for the member","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":true,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","required":true,"type":"string"}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/mailing-lists/{uid}/settings":{"get":{"tags":["mailing-list"],"summary":"get-grpsio-mailing-list-settings mailing-list","description":"Get GroupsIO mailing list settings (writers and auditors)","operationId":"mailing-list#get-grpsio-mailing-list-settings","parameters":[{"name":"v","in":"query","description":"Version of the API","required":false,"type":"string","enum":["1"]},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MailingListGetGrpsioMailingListSettingsResponseBody"},"headers":{"ETag":{"description":"ETag header value","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-grpsio-mailing-list-settings mailing-list","description":"Update GroupsIO mailing list settings (writers and auditors)","operationId":"mailing-list#update-grpsio-mailing-list-settings","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","required":false,"type":"string"},{"name":"Update-Grpsio-Mailing-List-SettingsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListUpdateGrpsioMailingListSettingsRequestBody"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GrpsIoMailingListSettings"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/services":{"post":{"tags":["mailing-list"],"summary":"create-grpsio-service mailing-list","description":"Create GroupsIO service with type-specific validation rules","operationId":"mailing-list#create-grpsio-service","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Grpsio-ServiceRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListCreateGrpsioServiceRequestBody","required":["type","project_uid"]}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/GrpsIoServiceFull","required":["type","project_uid"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/services/{uid}":{"get":{"tags":["mailing-list"],"summary":"get-grpsio-service mailing-list","description":"Get groupsIO service details by ID","operationId":"mailing-list#get-grpsio-service","parameters":[{"name":"v","in":"query","description":"Version of the API","required":false,"type":"string","enum":["1"]},{"name":"uid","in":"path","description":"Service UID -- unique identifier for the service","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MailingListGetGrpsioServiceResponseBody","required":["type","project_uid"]},"headers":{"ETag":{"description":"ETag header value","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-grpsio-service mailing-list","description":"Update GroupsIO service","operationId":"mailing-list#update-grpsio-service","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"uid","in":"path","description":"Service UID -- unique identifier for the service","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","required":false,"type":"string"},{"name":"Update-Grpsio-ServiceRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListUpdateGrpsioServiceRequestBody","required":["type","project_uid"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GrpsIoServiceWithReadonlyAttributes","required":["type","project_uid"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"delete":{"tags":["mailing-list"],"summary":"delete-grpsio-service mailing-list","description":"Delete GroupsIO service","operationId":"mailing-list#delete-grpsio-service","parameters":[{"name":"v","in":"query","description":"Version of the API","required":false,"type":"string","enum":["1"]},{"name":"uid","in":"path","description":"Service UID -- unique identifier for the service","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/services/{uid}/settings":{"get":{"tags":["mailing-list"],"summary":"get-grpsio-service-settings mailing-list","description":"Get GroupsIO service settings (writers and auditors)","operationId":"mailing-list#get-grpsio-service-settings","parameters":[{"name":"v","in":"query","description":"Version of the API","required":false,"type":"string","enum":["1"]},{"name":"uid","in":"path","description":"Service UID -- unique identifier for the service","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/MailingListGetGrpsioServiceSettingsResponseBody"},"headers":{"ETag":{"description":"ETag header value","type":"string"}}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-grpsio-service-settings mailing-list","description":"Update GroupsIO service settings (writers and auditors)","operationId":"mailing-list#update-grpsio-service-settings","parameters":[{"name":"v","in":"query","description":"Version of the API","required":true,"type":"string","enum":["1"]},{"name":"uid","in":"path","description":"Service UID -- unique identifier for the service","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","required":false,"type":"string"},{"name":"Update-Grpsio-Service-SettingsRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListUpdateGrpsioServiceSettingsRequestBody"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GrpsIoServiceSettings"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/livez":{"get":{"tags":["mailing-list"],"summary":"livez mailing-list","description":"Check if the service is alive.","operationId":"mailing-list#livez","produces":["text/plain"],"responses":{"200":{"description":"OK response.","schema":{"type":"string","format":"byte"}}},"schemes":["http"]}},"/openapi.json":{"get":{"tags":["mailing-list"],"summary":"Download gen/http/openapi3.json","operationId":"mailing-list#/openapi.json","responses":{"200":{"description":"File downloaded","schema":{"type":"file"}}},"schemes":["http"]}},"/readyz":{"get":{"tags":["mailing-list"],"summary":"readyz mailing-list","description":"Check if the service is able to take inbound requests.","operationId":"mailing-list#readyz","produces":["text/plain"],"responses":{"200":{"description":"OK response.","schema":{"type":"string","format":"byte"}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"]}},"/webhooks/groupsio":{"post":{"tags":["mailing-list"],"summary":"groupsio-webhook mailing-list","description":"Handle GroupsIO webhook events for subgroup and member changes","operationId":"mailing-list#groupsio-webhook","parameters":[{"name":"x-groupsio-signature","in":"header","description":"HMAC-SHA1 base64 signature for verification","required":true,"type":"string"},{"name":"Groupsio-WebhookRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/GroupsioWebhookPayload","required":["action"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"401":{"description":"Unauthorized response.","schema":{"$ref":"#/definitions/UnauthorizedError","required":["message"]}}},"schemes":["http"]}}},"definitions":{"BadRequestError":{"title":"BadRequestError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"description":"Bad request - Invalid type, missing required fields, or validation failures","example":{"message":"The request was invalid."},"required":["message"]},"Committee":{"title":"Committee","type":"object","properties":{"allowed_voting_statuses":{"type":"array","items":{"type":"string","example":"Alternate Voting Rep","enum":["Voting Rep","Alternate Voting Rep","Observer","Emeritus","None"]},"description":"Committee member voting statuses that determine which members are synced","example":["Voting Rep","Alternate Voting Rep"]},"name":{"type":"string","description":"Committee name (read-only, populated by server)","example":"Blanditiis libero rerum quasi."},"uid":{"type":"string","description":"Committee UUID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"}},"description":"Committee associated with a mailing list","example":{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Voluptatem itaque deleniti possimus distinctio magnam.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},"required":["uid"]},"ConflictError":{"title":"ConflictError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The resource already exists."}},"description":"Conflict","example":{"message":"The resource already exists."},"required":["message"]},"GroupsioWebhookPayload":{"title":"GroupsioWebhookPayload","type":"object","properties":{"action":{"type":"string","description":"The type of webhook event","example":"created_subgroup","enum":["created_subgroup","deleted_subgroup","added_member","removed_member","ban_members"]},"extra":{"type":"string","description":"Extra data field (subgroup suffix)","example":"Inventore in."},"extra_id":{"type":"integer","description":"Extra ID field (subgroup ID for deletion)","example":9053037415002072244,"format":"int64"},"group":{"description":"Contains subgroup data from Groups.io","example":"Eligendi esse vel aut dolor repellendus tempore."},"member_info":{"description":"Contains member data from Groups.io","example":"Commodi placeat aliquam molestiae voluptas neque velit."}},"example":{"action":"created_subgroup","extra":"Et aut iste quaerat sit porro molestias.","extra_id":8238369577073719666,"group":"Deleniti magnam.","member_info":"Aliquid distinctio mollitia."},"required":["action"]},"GrpsIoMailingListFull":{"title":"GrpsIoMailingListFull","type":"object","properties":{"audience_access":{"type":"string","description":"public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.","default":"public","example":"public","enum":["public","approval_required","invite_only"]},"auditors":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]},"committees":{"type":"array","items":{"$ref":"#/definitions/Committee"},"description":"Committees associated with this mailing list (OR logic for access control)","example":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"description":{"type":"string","description":"Mailing list description (11-500 characters)","example":"Technical steering committee discussions","minLength":11,"maxLength":500},"group_id":{"type":"integer","description":"Mailing list group ID","example":12345,"format":"int64","minimum":0},"group_name":{"type":"string","description":"Mailing list group name","example":"technical-steering-committee","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$","minLength":3,"maxLength":34},"project_name":{"type":"string","description":"Project name (read-only)","example":"Cloud Native Computing Foundation"},"project_slug":{"type":"string","description":"Project slug identifier (read-only)","example":"cncf","format":"regexp","pattern":"^[a-z][a-z0-9_\\-]*[a-z0-9]$"},"project_uid":{"type":"string","description":"LFXv2 Project UID (inherited from parent service)","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"public":{"type":"boolean","description":"Whether the mailing list is publicly accessible","default":false,"example":false},"service_uid":{"type":"string","description":"Service UUID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"subject_tag":{"type":"string","description":"Subject tag prefix","example":"[TSC]","maxLength":50},"subscriber_count":{"type":"integer","description":"Number of subscribers in this mailing list (read-only, maintained by service)","example":42,"format":"int64","minimum":0},"title":{"type":"string","description":"Mailing list title","example":"Technical Steering Committee","minLength":5,"maxLength":100},"type":{"type":"string","description":"Mailing list type","example":"discussion_moderated","enum":["announcement","discussion_moderated","discussion_open"]},"uid":{"type":"string","description":"Mailing list UID -- unique identifier for the mailing list","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"},"writers":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}},"example":{"audience_access":"public","auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"committees":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"created_at":"2023-01-15T10:30:00Z","description":"Technical steering committee discussions","group_id":12345,"group_name":"technical-steering-committee","project_name":"Cloud Native Computing Foundation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":false,"service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","subject_tag":"[TSC]","subscriber_count":42,"title":"Technical Steering Committee","type":"discussion_moderated","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}},"GrpsIoMailingListSettings":{"title":"GrpsIoMailingListSettings","type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"last_audited_by":{"type":"string","description":"The user ID who last audited the service","example":"user_id_12345"},"last_audited_time":{"type":"string","description":"The timestamp when the service was last audited","example":"2023-05-10T09:15:00Z","format":"date-time"},"last_reviewed_at":{"type":"string","description":"The timestamp when the service was last reviewed in RFC3339 format","example":"2025-08-04T09:00:00Z","format":"date-time"},"last_reviewed_by":{"type":"string","description":"The user ID who last reviewed this service","example":"user_id_12345"},"uid":{"type":"string","description":"Mailing list UID -- unique identifier for the mailing list","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"},"writers":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}},"description":"A representation of GroupsIO mailing list settings for user management.","example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","last_audited_by":"user_id_12345","last_audited_time":"2023-05-10T09:15:00Z","last_reviewed_at":"2025-08-04T09:00:00Z","last_reviewed_by":"user_id_12345","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}},"GrpsIoMailingListWithReadonlyAttributes":{"title":"GrpsIoMailingListWithReadonlyAttributes","type":"object","properties":{"audience_access":{"type":"string","description":"public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.","default":"public","example":"public","enum":["public","approval_required","invite_only"]},"committees":{"type":"array","items":{"$ref":"#/definitions/Committee"},"description":"Committees associated with this mailing list (OR logic for access control)","example":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"description":{"type":"string","description":"Mailing list description (11-500 characters)","example":"Technical steering committee discussions","minLength":11,"maxLength":500},"group_id":{"type":"integer","description":"Mailing list group ID","example":12345,"format":"int64","minimum":0},"group_name":{"type":"string","description":"Mailing list group name","example":"technical-steering-committee","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$","minLength":3,"maxLength":34},"project_name":{"type":"string","description":"Project name (read-only)","example":"Cloud Native Computing Foundation"},"project_slug":{"type":"string","description":"Project slug identifier (read-only)","example":"cncf","format":"regexp","pattern":"^[a-z][a-z0-9_\\-]*[a-z0-9]$"},"project_uid":{"type":"string","description":"LFXv2 Project UID (inherited from parent service)","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"public":{"type":"boolean","description":"Whether the mailing list is publicly accessible","default":false,"example":false},"service_uid":{"type":"string","description":"Service UUID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"subject_tag":{"type":"string","description":"Subject tag prefix","example":"[TSC]","maxLength":50},"subscriber_count":{"type":"integer","description":"Number of subscribers in this mailing list (read-only, maintained by service)","example":42,"format":"int64","minimum":0},"title":{"type":"string","description":"Mailing list title","example":"Technical Steering Committee","minLength":5,"maxLength":100},"type":{"type":"string","description":"Mailing list type","example":"discussion_moderated","enum":["announcement","discussion_moderated","discussion_open"]},"uid":{"type":"string","description":"Mailing list UID -- unique identifier for the mailing list","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"}},"description":"A representation of GroupsIO mailing lists with readonly attributes.","example":{"audience_access":"public","committees":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"created_at":"2023-01-15T10:30:00Z","description":"Technical steering committee discussions","group_id":12345,"group_name":"technical-steering-committee","project_name":"Cloud Native Computing Foundation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":false,"service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","subject_tag":"[TSC]","subscriber_count":42,"title":"Technical Steering Committee","type":"discussion_moderated","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z"}},"GrpsIoMemberFull":{"title":"GrpsIoMemberFull","type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"delivery_mode":{"type":"string","description":"Email delivery mode","default":"normal","example":"normal","enum":["normal","digest","none"]},"email":{"type":"string","description":"Member email address","example":"john.doe@example.com","format":"email"},"first_name":{"type":"string","description":"Member first name","example":"John","minLength":1,"maxLength":255},"group_id":{"type":"integer","description":"Groups.io group ID","example":67890,"format":"int64"},"job_title":{"type":"string","description":"Member job title","example":"Software Engineer","maxLength":255},"last_name":{"type":"string","description":"Member last name","example":"Doe","minLength":1,"maxLength":255},"last_reviewed_at":{"type":"string","description":"Last reviewed timestamp","example":"2023-01-15T14:30:00Z","format":"date-time"},"last_reviewed_by":{"type":"string","description":"Last reviewed by user ID","example":"admin@example.com"},"mailing_list_uid":{"type":"string","description":"Mailing list UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"member_id":{"type":"integer","description":"Groups.io member ID","example":12345,"format":"int64"},"member_type":{"type":"string","description":"Member type","default":"direct","example":"direct","enum":["committee","direct"]},"mod_status":{"type":"string","description":"Moderation status","default":"none","example":"none","enum":["none","moderator","owner"]},"organization":{"type":"string","description":"Member organization","example":"Example Corp","maxLength":255},"status":{"type":"string","description":"Member status","example":"pending"},"uid":{"type":"string","description":"Member UID","example":"f47ac10b-58cc-4372-a567-0e02b2c3d479","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"},"username":{"type":"string","description":"Member username","example":"jdoe","maxLength":255},"writers":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}},"example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","delivery_mode":"normal","email":"john.doe@example.com","first_name":"John","group_id":67890,"job_title":"Software Engineer","last_name":"Doe","last_reviewed_at":"2023-01-15T14:30:00Z","last_reviewed_by":"admin@example.com","mailing_list_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","member_id":12345,"member_type":"direct","mod_status":"owner","organization":"Example Corp","status":"pending","uid":"f47ac10b-58cc-4372-a567-0e02b2c3d479","updated_at":"2023-06-20T14:45:30Z","username":"jdoe","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]},"required":["uid","mailing_list_uid","first_name","last_name","email","member_type","delivery_mode","mod_status","status","created_at","updated_at"]},"GrpsIoMemberWithReadonlyAttributes":{"title":"GrpsIoMemberWithReadonlyAttributes","type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"delivery_mode":{"type":"string","description":"Email delivery mode","default":"normal","example":"normal","enum":["normal","digest","none"]},"email":{"type":"string","description":"Member email address","example":"john.doe@example.com","format":"email"},"first_name":{"type":"string","description":"Member first name","example":"John","minLength":1,"maxLength":255},"group_id":{"type":"integer","description":"Groups.io group ID","example":67890,"format":"int64"},"job_title":{"type":"string","description":"Member job title","example":"Software Engineer","maxLength":255},"last_name":{"type":"string","description":"Member last name","example":"Doe","minLength":1,"maxLength":255},"last_reviewed_at":{"type":"string","description":"Last reviewed timestamp","example":"2023-01-15T14:30:00Z","format":"date-time"},"last_reviewed_by":{"type":"string","description":"Last reviewed by user ID","example":"admin@example.com"},"mailing_list_uid":{"type":"string","description":"Mailing list UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"member_id":{"type":"integer","description":"Groups.io member ID","example":12345,"format":"int64"},"member_type":{"type":"string","description":"Member type","default":"direct","example":"direct","enum":["committee","direct"]},"mod_status":{"type":"string","description":"Moderation status","default":"none","example":"none","enum":["none","moderator","owner"]},"organization":{"type":"string","description":"Member organization","example":"Example Corp","maxLength":255},"status":{"type":"string","description":"Member status","example":"pending"},"uid":{"type":"string","description":"Member UID","example":"f47ac10b-58cc-4372-a567-0e02b2c3d479","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"},"username":{"type":"string","description":"Member username","example":"jdoe","maxLength":255},"writers":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}},"description":"A representation of GroupsIO mailing list members with readonly attributes.","example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","delivery_mode":"none","email":"john.doe@example.com","first_name":"John","group_id":67890,"job_title":"Software Engineer","last_name":"Doe","last_reviewed_at":"2023-01-15T14:30:00Z","last_reviewed_by":"admin@example.com","mailing_list_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","member_id":12345,"member_type":"direct","mod_status":"moderator","organization":"Example Corp","status":"pending","uid":"f47ac10b-58cc-4372-a567-0e02b2c3d479","updated_at":"2023-06-20T14:45:30Z","username":"jdoe","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}},"GrpsIoServiceFull":{"title":"GrpsIoServiceFull","type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"domain":{"type":"string","description":"Service domain","example":"lists.project.org"},"global_owners":{"type":"array","items":{"type":"string","example":"nettie@schoenharvey.org","format":"email"},"description":"List of global owner email addresses (required for primary, forbidden for shared)","example":["admin@example.com"]},"group_id":{"type":"integer","description":"GroupsIO group ID","example":12345,"format":"int64"},"group_name":{"type":"string","description":"GroupsIO group name","example":"project-name"},"parent_service_uid":{"type":"string","description":"Parent primary service UID (automatically set for shared type services)","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"prefix":{"type":"string","description":"Email prefix (required for formation and shared, forbidden for primary)","example":"formation"},"project_name":{"type":"string","description":"Project name (read-only)","example":"Cloud Native Computing Foundation"},"project_slug":{"type":"string","description":"Project slug identifier","example":"cncf","format":"regexp","pattern":"^[a-z][a-z0-9_\\-]*[a-z0-9]$"},"project_uid":{"type":"string","description":"LFXv2 Project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"public":{"type":"boolean","description":"Whether the service is publicly accessible","default":false,"example":true},"status":{"type":"string","description":"Service status","example":"created"},"type":{"type":"string","description":"Service type","example":"primary","enum":["primary","formation","shared"]},"uid":{"type":"string","description":"Service UID -- unique identifier for the service","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"},"url":{"type":"string","description":"Service URL","example":"https://lists.project.org","format":"uri"},"writers":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}},"example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","domain":"lists.project.org","global_owners":["admin@example.com"],"group_id":12345,"group_name":"project-name","parent_service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","prefix":"formation","project_name":"Cloud Native Computing Foundation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":true,"status":"created","type":"primary","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","url":"https://lists.project.org","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]},"required":["type","project_uid"]},"GrpsIoServiceSettings":{"title":"GrpsIoServiceSettings","type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"last_audited_by":{"type":"string","description":"The user ID who last audited the service","example":"user_id_12345"},"last_audited_time":{"type":"string","description":"The timestamp when the service was last audited","example":"2023-05-10T09:15:00Z","format":"date-time"},"last_reviewed_at":{"type":"string","description":"The timestamp when the service was last reviewed in RFC3339 format","example":"2025-08-04T09:00:00Z","format":"date-time"},"last_reviewed_by":{"type":"string","description":"The user ID who last reviewed this service","example":"user_id_12345"},"uid":{"type":"string","description":"Service UID -- unique identifier for the service","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"},"writers":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}},"description":"A representation of GroupsIO service settings for user management.","example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","last_audited_by":"user_id_12345","last_audited_time":"2023-05-10T09:15:00Z","last_reviewed_at":"2025-08-04T09:00:00Z","last_reviewed_by":"user_id_12345","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}},"GrpsIoServiceWithReadonlyAttributes":{"title":"GrpsIoServiceWithReadonlyAttributes","type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"domain":{"type":"string","description":"Service domain","example":"lists.project.org"},"global_owners":{"type":"array","items":{"type":"string","example":"suzanne@dooley.com","format":"email"},"description":"List of global owner email addresses (required for primary, forbidden for shared)","example":["admin@example.com"]},"group_id":{"type":"integer","description":"GroupsIO group ID","example":12345,"format":"int64"},"group_name":{"type":"string","description":"GroupsIO group name","example":"project-name"},"parent_service_uid":{"type":"string","description":"Parent primary service UID (automatically set for shared type services)","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"prefix":{"type":"string","description":"Email prefix (required for formation and shared, forbidden for primary)","example":"formation"},"project_name":{"type":"string","description":"Project name (read-only)","example":"Cloud Native Computing Foundation"},"project_slug":{"type":"string","description":"Project slug identifier","example":"cncf","format":"regexp","pattern":"^[a-z][a-z0-9_\\-]*[a-z0-9]$"},"project_uid":{"type":"string","description":"LFXv2 Project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"public":{"type":"boolean","description":"Whether the service is publicly accessible","default":false,"example":true},"status":{"type":"string","description":"Service status","example":"created"},"type":{"type":"string","description":"Service type","example":"primary","enum":["primary","formation","shared"]},"uid":{"type":"string","description":"Service UID -- unique identifier for the service","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"},"url":{"type":"string","description":"Service URL","example":"https://lists.project.org","format":"uri"},"writers":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}},"description":"A representation of GroupsIO services with readonly attributes.","example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","domain":"lists.project.org","global_owners":["admin@example.com"],"group_id":12345,"group_name":"project-name","parent_service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","prefix":"formation","project_name":"Cloud Native Computing Foundation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":true,"status":"created","type":"primary","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","url":"https://lists.project.org","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]},"required":["type","project_uid"]},"InternalServerError":{"title":"InternalServerError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"description":"Internal server error","example":{"message":"An internal server error occurred."},"required":["message"]},"MailingListCreateGrpsioMailingListMemberRequestBody":{"title":"MailingListCreateGrpsioMailingListMemberRequestBody","type":"object","properties":{"delivery_mode":{"type":"string","description":"Email delivery mode","default":"normal","example":"none","enum":["normal","digest","none"]},"email":{"type":"string","description":"Member email address","example":"john.doe@example.com","format":"email"},"first_name":{"type":"string","description":"Member first name","example":"John","minLength":1,"maxLength":255},"job_title":{"type":"string","description":"Member job title","example":"Software Engineer","maxLength":255},"last_name":{"type":"string","description":"Member last name","example":"Doe","minLength":1,"maxLength":255},"last_reviewed_at":{"type":"string","description":"Last reviewed timestamp","example":"2023-01-15T14:30:00Z","format":"date-time"},"last_reviewed_by":{"type":"string","description":"Last reviewed by user ID","example":"admin@example.com"},"member_type":{"type":"string","description":"Member type","default":"direct","example":"committee","enum":["committee","direct"]},"mod_status":{"type":"string","description":"Moderation status","default":"none","example":"none","enum":["none","moderator","owner"]},"organization":{"type":"string","description":"Member organization","example":"Example Corp","maxLength":255},"username":{"type":"string","description":"Member username","example":"jdoe","maxLength":255}},"example":{"delivery_mode":"none","email":"john.doe@example.com","first_name":"John","job_title":"Software Engineer","last_name":"Doe","last_reviewed_at":"2023-01-15T14:30:00Z","last_reviewed_by":"admin@example.com","member_type":"direct","mod_status":"moderator","organization":"Example Corp","username":"jdoe"},"required":["email"]},"MailingListCreateGrpsioMailingListRequestBody":{"title":"MailingListCreateGrpsioMailingListRequestBody","type":"object","properties":{"audience_access":{"type":"string","description":"public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.","default":"public","example":"public","enum":["public","approval_required","invite_only"]},"auditors":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]},"committees":{"type":"array","items":{"$ref":"#/definitions/Committee"},"description":"Committees associated with this mailing list (OR logic for access control)","example":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}]},"description":{"type":"string","description":"Mailing list description (11-500 characters)","example":"Technical steering committee discussions","minLength":11,"maxLength":500},"group_id":{"type":"integer","description":"Mailing list group ID","example":12345,"format":"int64","minimum":0},"group_name":{"type":"string","description":"Mailing list group name","example":"technical-steering-committee","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$","minLength":3,"maxLength":34},"public":{"type":"boolean","description":"Whether the mailing list is publicly accessible","default":false,"example":false},"service_uid":{"type":"string","description":"Service UUID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"subject_tag":{"type":"string","description":"Subject tag prefix","example":"[TSC]","maxLength":50},"subscriber_count":{"type":"integer","description":"Number of subscribers in this mailing list (read-only, maintained by service)","example":42,"format":"int64","minimum":0},"title":{"type":"string","description":"Mailing list title","example":"Technical Steering Committee","minLength":5,"maxLength":100},"type":{"type":"string","description":"Mailing list type","example":"discussion_moderated","enum":["announcement","discussion_moderated","discussion_open"]},"writers":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]}},"example":{"audience_access":"public","auditors":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}],"committees":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"description":"Technical steering committee discussions","group_id":12345,"group_name":"technical-steering-committee","public":false,"service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","subject_tag":"[TSC]","subscriber_count":42,"title":"Technical Steering Committee","type":"discussion_moderated","writers":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]},"required":["group_name","public","type","description","title","service_uid"]},"MailingListCreateGrpsioServiceRequestBody":{"title":"MailingListCreateGrpsioServiceRequestBody","type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]},"domain":{"type":"string","description":"Service domain","example":"lists.project.org"},"global_owners":{"type":"array","items":{"type":"string","example":"nat@hickle.biz","format":"email"},"description":"List of global owner email addresses (required for primary, forbidden for shared)","example":["admin@example.com"]},"group_id":{"type":"integer","description":"GroupsIO group ID","example":12345,"format":"int64"},"group_name":{"type":"string","description":"GroupsIO group name","example":"project-name"},"parent_service_uid":{"type":"string","description":"Parent primary service UID (automatically set for shared type services)","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"prefix":{"type":"string","description":"Email prefix (required for formation and shared, forbidden for primary)","example":"formation"},"project_slug":{"type":"string","description":"Project slug identifier","example":"cncf","format":"regexp","pattern":"^[a-z][a-z0-9_\\-]*[a-z0-9]$"},"project_uid":{"type":"string","description":"LFXv2 Project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"public":{"type":"boolean","description":"Whether the service is publicly accessible","default":false,"example":true},"status":{"type":"string","description":"Service status","example":"created"},"type":{"type":"string","description":"Service type","example":"primary","enum":["primary","formation","shared"]},"url":{"type":"string","description":"Service URL","example":"https://lists.project.org","format":"uri"},"writers":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]}},"example":{"auditors":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}],"domain":"lists.project.org","global_owners":["admin@example.com"],"group_id":12345,"group_name":"project-name","parent_service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","prefix":"formation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":true,"status":"created","type":"primary","url":"https://lists.project.org","writers":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]},"required":["type","project_uid"]},"MailingListGetGrpsioMailingListMemberResponseBody":{"title":"MailingListGetGrpsioMailingListMemberResponseBody","$ref":"#/definitions/GrpsIoMemberWithReadonlyAttributes"},"MailingListGetGrpsioMailingListResponseBody":{"title":"MailingListGetGrpsioMailingListResponseBody","$ref":"#/definitions/GrpsIoMailingListWithReadonlyAttributes"},"MailingListGetGrpsioMailingListSettingsResponseBody":{"title":"MailingListGetGrpsioMailingListSettingsResponseBody","$ref":"#/definitions/GrpsIoMailingListSettings"},"MailingListGetGrpsioServiceResponseBody":{"title":"MailingListGetGrpsioServiceResponseBody","$ref":"#/definitions/GrpsIoServiceWithReadonlyAttributes"},"MailingListGetGrpsioServiceSettingsResponseBody":{"title":"MailingListGetGrpsioServiceSettingsResponseBody","$ref":"#/definitions/GrpsIoServiceSettings"},"MailingListUpdateGrpsioMailingListMemberRequestBody":{"title":"MailingListUpdateGrpsioMailingListMemberRequestBody","type":"object","properties":{"delivery_mode":{"type":"string","description":"Email delivery mode","default":"normal","example":"normal","enum":["normal","digest","none"]},"first_name":{"type":"string","description":"Member first name","example":"John","minLength":1,"maxLength":255},"job_title":{"type":"string","description":"Member job title","example":"Software Engineer","maxLength":255},"last_name":{"type":"string","description":"Member last name","example":"Doe","minLength":1,"maxLength":255},"mod_status":{"type":"string","description":"Moderation status","default":"none","example":"owner","enum":["none","moderator","owner"]},"organization":{"type":"string","description":"Member organization","example":"Example Corp","maxLength":255},"username":{"type":"string","description":"Member username","example":"jdoe","maxLength":255}},"example":{"delivery_mode":"digest","first_name":"John","job_title":"Software Engineer","last_name":"Doe","mod_status":"moderator","organization":"Example Corp","username":"jdoe"}},"MailingListUpdateGrpsioMailingListRequestBody":{"title":"MailingListUpdateGrpsioMailingListRequestBody","type":"object","properties":{"audience_access":{"type":"string","description":"public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.","default":"public","example":"public","enum":["public","approval_required","invite_only"]},"committees":{"type":"array","items":{"$ref":"#/definitions/Committee"},"description":"Committees associated with this mailing list (OR logic for access control)","example":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}]},"description":{"type":"string","description":"Mailing list description (11-500 characters)","example":"Technical steering committee discussions","minLength":11,"maxLength":500},"group_id":{"type":"integer","description":"Mailing list group ID","example":12345,"format":"int64","minimum":0},"group_name":{"type":"string","description":"Mailing list group name","example":"technical-steering-committee","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$","minLength":3,"maxLength":34},"public":{"type":"boolean","description":"Whether the mailing list is publicly accessible","default":false,"example":false},"service_uid":{"type":"string","description":"Service UUID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"subject_tag":{"type":"string","description":"Subject tag prefix","example":"[TSC]","maxLength":50},"subscriber_count":{"type":"integer","description":"Number of subscribers in this mailing list (read-only, maintained by service)","example":42,"format":"int64","minimum":0},"title":{"type":"string","description":"Mailing list title","example":"Technical Steering Committee","minLength":5,"maxLength":100},"type":{"type":"string","description":"Mailing list type","example":"discussion_moderated","enum":["announcement","discussion_moderated","discussion_open"]}},"example":{"audience_access":"public","committees":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"description":"Technical steering committee discussions","group_id":12345,"group_name":"technical-steering-committee","public":false,"service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","subject_tag":"[TSC]","subscriber_count":42,"title":"Technical Steering Committee","type":"discussion_moderated"},"required":["group_name","public","type","description","title","service_uid"]},"MailingListUpdateGrpsioMailingListSettingsRequestBody":{"title":"MailingListUpdateGrpsioMailingListSettingsRequestBody","type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]},"writers":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]}},"example":{"auditors":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}],"writers":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]}},"MailingListUpdateGrpsioServiceRequestBody":{"title":"MailingListUpdateGrpsioServiceRequestBody","type":"object","properties":{"domain":{"type":"string","description":"Service domain","example":"lists.project.org"},"global_owners":{"type":"array","items":{"type":"string","example":"korey@ryan.net","format":"email"},"description":"List of global owner email addresses (required for primary, forbidden for shared)","example":["admin@example.com"]},"group_id":{"type":"integer","description":"GroupsIO group ID","example":12345,"format":"int64"},"group_name":{"type":"string","description":"GroupsIO group name","example":"project-name"},"parent_service_uid":{"type":"string","description":"Parent primary service UID (automatically set for shared type services)","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"prefix":{"type":"string","description":"Email prefix (required for formation and shared, forbidden for primary)","example":"formation"},"project_slug":{"type":"string","description":"Project slug identifier","example":"cncf","format":"regexp","pattern":"^[a-z][a-z0-9_\\-]*[a-z0-9]$"},"project_uid":{"type":"string","description":"LFXv2 Project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"public":{"type":"boolean","description":"Whether the service is publicly accessible","default":false,"example":true},"status":{"type":"string","description":"Service status","example":"created"},"type":{"type":"string","description":"Service type","example":"primary","enum":["primary","formation","shared"]},"url":{"type":"string","description":"Service URL","example":"https://lists.project.org","format":"uri"}},"example":{"domain":"lists.project.org","global_owners":["admin@example.com"],"group_id":12345,"group_name":"project-name","parent_service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","prefix":"formation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":true,"status":"created","type":"primary","url":"https://lists.project.org"},"required":["type","project_uid"]},"MailingListUpdateGrpsioServiceSettingsRequestBody":{"title":"MailingListUpdateGrpsioServiceSettingsRequestBody","type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]},"writers":{"type":"array","items":{"$ref":"#/definitions/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]}},"example":{"auditors":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}],"writers":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]}},"NotFoundError":{"title":"NotFoundError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The resource was not found."}},"description":"Resource not found","example":{"message":"The resource was not found."},"required":["message"]},"ServiceUnavailableError":{"title":"ServiceUnavailableError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"description":"Service unavailable","example":{"message":"The service is unavailable."},"required":["message"]},"UnauthorizedError":{"title":"UnauthorizedError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"Unauthorized access."}},"description":"Invalid webhook signature","example":{"message":"Unauthorized access."},"required":["message"]},"UserInfo":{"title":"UserInfo","type":"object","properties":{"avatar":{"type":"string","description":"The avatar URL of the user","example":"http://feest.org/elody.zulauf","format":"uri"},"email":{"type":"string","description":"The email address of the user","example":"tom@raynor.com","format":"email"},"name":{"type":"string","description":"The full name of the user","example":"Hic tempore non atque ut quis ut."},"username":{"type":"string","description":"The username/LFID of the user","example":"Sint neque et."}},"description":"User information including profile details.","example":{"avatar":"http://schultz.org/josue","email":"velma@rathtowne.biz","name":"Consequatur molestias omnis aliquid aspernatur.","username":"Voluptate dignissimos expedita quis sapiente asperiores magnam."}}},"securityDefinitions":{"jwt_header_Authorization":{"type":"apiKey","description":"Heimdall authorization","name":"Authorization","in":"header"}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"Mailing List Service","description":"Service for proxying GroupsIO operations to the ITX API","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/groupsio/checksubscriber":{"post":{"tags":["mailing-list"],"summary":"check-groupsio-subscriber mailing-list","description":"Check if an email address is subscribed to a GroupsIO subgroup","operationId":"mailing-list#check-groupsio-subscriber","parameters":[{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Check-Groupsio-SubscriberRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListCheckGroupsioSubscriberRequestBody","required":["email","subgroup_id"]}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GroupsioCheckSubscriberResponse","required":["subscribed"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/services":{"get":{"tags":["mailing-list"],"summary":"list-groupsio-services mailing-list","description":"List GroupsIO services, optionally filtered by project UID","operationId":"mailing-list#list-groupsio-services","parameters":[{"name":"project_uid","in":"query","description":"LFX v2 project UID filter","required":false,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GroupsioServiceList"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["mailing-list"],"summary":"create-groupsio-service mailing-list","description":"Create a GroupsIO service","operationId":"mailing-list#create-groupsio-service","parameters":[{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Groupsio-ServiceRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListCreateGroupsioServiceRequestBody"}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/GroupsioService"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/services/_projects":{"get":{"tags":["mailing-list"],"summary":"get-groupsio-service-projects mailing-list","description":"Get projects that have GroupsIO services","operationId":"mailing-list#get-groupsio-service-projects","parameters":[{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GroupsioProjectsResponse"}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/services/find_parent":{"get":{"tags":["mailing-list"],"summary":"find-parent-groupsio-service mailing-list","description":"Find the parent GroupsIO service for a project","operationId":"mailing-list#find-parent-groupsio-service","parameters":[{"name":"project_uid","in":"query","description":"LFX v2 project UID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GroupsioService"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/services/{service_id}":{"get":{"tags":["mailing-list"],"summary":"get-groupsio-service mailing-list","description":"Get a GroupsIO service by ID","operationId":"mailing-list#get-groupsio-service","parameters":[{"name":"service_id","in":"path","description":"Service ID","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GroupsioService"}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-groupsio-service mailing-list","description":"Update a GroupsIO service","operationId":"mailing-list#update-groupsio-service","parameters":[{"name":"service_id","in":"path","description":"Service ID","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Update-Groupsio-ServiceRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListUpdateGroupsioServiceRequestBody"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GroupsioService"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"delete":{"tags":["mailing-list"],"summary":"delete-groupsio-service mailing-list","description":"Delete a GroupsIO service","operationId":"mailing-list#delete-groupsio-service","parameters":[{"name":"service_id","in":"path","description":"Service ID","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/subgroups":{"get":{"tags":["mailing-list"],"summary":"list-groupsio-subgroups mailing-list","description":"List GroupsIO subgroups, optionally filtered by project UID and/or committee UID","operationId":"mailing-list#list-groupsio-subgroups","parameters":[{"name":"project_uid","in":"query","description":"LFX v2 project UID filter","required":false,"type":"string","format":"uuid"},{"name":"committee_uid","in":"query","description":"LFX v2 committee UID filter","required":false,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GroupsioSubgroupList"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["mailing-list"],"summary":"create-groupsio-subgroup mailing-list","description":"Create a GroupsIO subgroup","operationId":"mailing-list#create-groupsio-subgroup","parameters":[{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Create-Groupsio-SubgroupRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListCreateGroupsioSubgroupRequestBody"}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/GroupsioSubgroup"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/subgroups/count":{"get":{"tags":["mailing-list"],"summary":"get-groupsio-subgroup-count mailing-list","description":"Get count of GroupsIO subgroups for a project","operationId":"mailing-list#get-groupsio-subgroup-count","parameters":[{"name":"project_uid","in":"query","description":"LFX v2 project UID","required":true,"type":"string","format":"uuid"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GroupsioCount","required":["count"]}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/subgroups/{subgroup_id}":{"get":{"tags":["mailing-list"],"summary":"get-groupsio-subgroup mailing-list","description":"Get a GroupsIO subgroup by ID","operationId":"mailing-list#get-groupsio-subgroup","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GroupsioSubgroup"}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-groupsio-subgroup mailing-list","description":"Update a GroupsIO subgroup","operationId":"mailing-list#update-groupsio-subgroup","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Update-Groupsio-SubgroupRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListUpdateGroupsioSubgroupRequestBody"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GroupsioSubgroup"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"delete":{"tags":["mailing-list"],"summary":"delete-groupsio-subgroup mailing-list","description":"Delete a GroupsIO subgroup","operationId":"mailing-list#delete-groupsio-subgroup","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/subgroups/{subgroup_id}/invitemembers":{"post":{"tags":["mailing-list"],"summary":"invite-groupsio-members mailing-list","description":"Invite members to a GroupsIO subgroup by email","operationId":"mailing-list#invite-groupsio-members","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Invite-Groupsio-MembersRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListInviteGroupsioMembersRequestBody","required":["emails"]}}],"responses":{"204":{"description":"No Content response."},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/subgroups/{subgroup_id}/member_count":{"get":{"tags":["mailing-list"],"summary":"get-groupsio-subgroup-member-count mailing-list","description":"Get count of members in a GroupsIO subgroup","operationId":"mailing-list#get-groupsio-subgroup-member-count","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GroupsioCount","required":["count"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/subgroups/{subgroup_id}/members":{"get":{"tags":["mailing-list"],"summary":"list-groupsio-members mailing-list","description":"List members of a GroupsIO subgroup","operationId":"mailing-list#list-groupsio-members","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GroupsioMemberList"}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["mailing-list"],"summary":"add-groupsio-member mailing-list","description":"Add a member to a GroupsIO subgroup","operationId":"mailing-list#add-groupsio-member","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Add-Groupsio-MemberRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListAddGroupsioMemberRequestBody"}}],"responses":{"201":{"description":"Created response.","schema":{"$ref":"#/definitions/GroupsioMember"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"409":{"description":"Conflict response.","schema":{"$ref":"#/definitions/ConflictError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/subgroups/{subgroup_id}/members/{member_id}":{"get":{"tags":["mailing-list"],"summary":"get-groupsio-member mailing-list","description":"Get a member of a GroupsIO subgroup by ID","operationId":"mailing-list#get-groupsio-member","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"type":"string"},{"name":"member_id","in":"path","description":"Member ID","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GroupsioMember"}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-groupsio-member mailing-list","description":"Update a member of a GroupsIO subgroup","operationId":"mailing-list#update-groupsio-member","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"type":"string"},{"name":"member_id","in":"path","description":"Member ID","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"},{"name":"Update-Groupsio-MemberRequestBody","in":"body","required":true,"schema":{"$ref":"#/definitions/MailingListUpdateGroupsioMemberRequestBody"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/GroupsioMember"}},"400":{"description":"Bad Request response.","schema":{"$ref":"#/definitions/BadRequestError","required":["message"]}},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]},"delete":{"tags":["mailing-list"],"summary":"delete-groupsio-member mailing-list","description":"Delete a member from a GroupsIO subgroup","operationId":"mailing-list#delete-groupsio-member","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"type":"string"},{"name":"member_id","in":"path","description":"Member ID","required":true,"type":"string"},{"name":"Authorization","in":"header","description":"JWT token issued by Heimdall","required":false,"type":"string"}],"responses":{"204":{"description":"No Content response."},"404":{"description":"Not Found response.","schema":{"$ref":"#/definitions/NotFoundError","required":["message"]}},"500":{"description":"Internal Server Error response.","schema":{"$ref":"#/definitions/InternalServerError","required":["message"]}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"],"security":[{"jwt_header_Authorization":[]}]}},"/livez":{"get":{"tags":["mailing-list"],"summary":"livez mailing-list","description":"Check if the service is alive.","operationId":"mailing-list#livez","produces":["text/plain"],"responses":{"200":{"description":"OK response.","schema":{"type":"string","format":"byte"}}},"schemes":["http"]}},"/openapi.json":{"get":{"tags":["mailing-list"],"summary":"Download gen/http/openapi3.json","operationId":"mailing-list#/openapi.json","responses":{"200":{"description":"File downloaded","schema":{"type":"file"}}},"schemes":["http"]}},"/readyz":{"get":{"tags":["mailing-list"],"summary":"readyz mailing-list","description":"Check if the service is able to take inbound requests.","operationId":"mailing-list#readyz","produces":["text/plain"],"responses":{"200":{"description":"OK response.","schema":{"type":"string","format":"byte"}},"503":{"description":"Service Unavailable response.","schema":{"$ref":"#/definitions/ServiceUnavailableError","required":["message"]}}},"schemes":["http"]}}},"definitions":{"BadRequestError":{"title":"BadRequestError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"description":"Bad request","example":{"message":"The request was invalid."},"required":["message"]},"ConflictError":{"title":"ConflictError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The resource already exists."}},"description":"Conflict","example":{"message":"The resource already exists."},"required":["message"]},"GroupsioCheckSubscriberResponse":{"title":"GroupsioCheckSubscriberResponse","type":"object","properties":{"subscribed":{"type":"boolean","description":"Whether the email is subscribed","example":true}},"example":{"subscribed":true},"required":["subscribed"]},"GroupsioCount":{"title":"GroupsioCount","type":"object","properties":{"count":{"type":"integer","description":"Count value","example":6800691645898679136,"format":"int64"}},"example":{"count":763626204865788569},"required":["count"]},"GroupsioMember":{"title":"GroupsioMember","type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","example":"Est est et."},"delivery_mode":{"type":"string","description":"Email delivery mode","example":"Sunt ipsum et in ipsa sed."},"email":{"type":"string","description":"Member email address","example":"sunny@hoeger.org","format":"email"},"first_name":{"type":"string","description":"Member first name","example":"Nulla quas repellat."},"id":{"type":"string","description":"Member ID","example":"Alias aut delectus ut omnis."},"last_name":{"type":"string","description":"Member last name","example":"Sunt et qui rerum."},"mod_status":{"type":"string","description":"Moderation status","example":"Dolor accusantium ipsam cumque."},"name":{"type":"string","description":"Member display name","example":"Dolorum fugit similique saepe fugiat."},"status":{"type":"string","description":"Member status","example":"Voluptas optio eveniet maxime."},"subgroup_id":{"type":"string","description":"Subgroup ID","example":"Ut iste velit repudiandae dolores non quas."},"updated_at":{"type":"string","description":"Last update timestamp","example":"Voluptatem debitis."}},"description":"A member of a GroupsIO subgroup","example":{"created_at":"Voluptatem reprehenderit voluptatibus voluptatem.","delivery_mode":"Repudiandae expedita est.","email":"manley@mckenzie.org","first_name":"Veritatis ut repudiandae sed.","id":"Esse enim.","last_name":"Dolore sapiente sit et sunt vitae quos.","mod_status":"Voluptas iure alias sequi.","name":"Est architecto ea magnam quisquam doloremque autem.","status":"Officia et dignissimos ut voluptatibus fuga id.","subgroup_id":"Voluptatibus explicabo.","updated_at":"Commodi in porro."}},"GroupsioMemberList":{"title":"GroupsioMemberList","type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/definitions/GroupsioMember"},"description":"List of members","example":[{"created_at":"Eos sint ea provident.","delivery_mode":"Quae dolores.","email":"casey@littel.org","first_name":"Cumque sed eveniet reprehenderit.","id":"Ullam exercitationem quisquam nostrum nihil culpa.","last_name":"Ut atque voluptatibus.","mod_status":"Aut tempora.","name":"Qui et voluptates in perspiciatis non.","status":"Natus nesciunt omnis et.","subgroup_id":"Et repellat voluptates reiciendis.","updated_at":"Reiciendis consequatur laborum quidem voluptatum et voluptatibus."},{"created_at":"Eos sint ea provident.","delivery_mode":"Quae dolores.","email":"casey@littel.org","first_name":"Cumque sed eveniet reprehenderit.","id":"Ullam exercitationem quisquam nostrum nihil culpa.","last_name":"Ut atque voluptatibus.","mod_status":"Aut tempora.","name":"Qui et voluptates in perspiciatis non.","status":"Natus nesciunt omnis et.","subgroup_id":"Et repellat voluptates reiciendis.","updated_at":"Reiciendis consequatur laborum quidem voluptatum et voluptatibus."},{"created_at":"Eos sint ea provident.","delivery_mode":"Quae dolores.","email":"casey@littel.org","first_name":"Cumque sed eveniet reprehenderit.","id":"Ullam exercitationem quisquam nostrum nihil culpa.","last_name":"Ut atque voluptatibus.","mod_status":"Aut tempora.","name":"Qui et voluptates in perspiciatis non.","status":"Natus nesciunt omnis et.","subgroup_id":"Et repellat voluptates reiciendis.","updated_at":"Reiciendis consequatur laborum quidem voluptatum et voluptatibus."},{"created_at":"Eos sint ea provident.","delivery_mode":"Quae dolores.","email":"casey@littel.org","first_name":"Cumque sed eveniet reprehenderit.","id":"Ullam exercitationem quisquam nostrum nihil culpa.","last_name":"Ut atque voluptatibus.","mod_status":"Aut tempora.","name":"Qui et voluptates in perspiciatis non.","status":"Natus nesciunt omnis et.","subgroup_id":"Et repellat voluptates reiciendis.","updated_at":"Reiciendis consequatur laborum quidem voluptatum et voluptatibus."}]},"total":{"type":"integer","description":"Total count","example":6152661567749295680,"format":"int64"}},"example":{"items":[{"created_at":"Eos sint ea provident.","delivery_mode":"Quae dolores.","email":"casey@littel.org","first_name":"Cumque sed eveniet reprehenderit.","id":"Ullam exercitationem quisquam nostrum nihil culpa.","last_name":"Ut atque voluptatibus.","mod_status":"Aut tempora.","name":"Qui et voluptates in perspiciatis non.","status":"Natus nesciunt omnis et.","subgroup_id":"Et repellat voluptates reiciendis.","updated_at":"Reiciendis consequatur laborum quidem voluptatum et voluptatibus."},{"created_at":"Eos sint ea provident.","delivery_mode":"Quae dolores.","email":"casey@littel.org","first_name":"Cumque sed eveniet reprehenderit.","id":"Ullam exercitationem quisquam nostrum nihil culpa.","last_name":"Ut atque voluptatibus.","mod_status":"Aut tempora.","name":"Qui et voluptates in perspiciatis non.","status":"Natus nesciunt omnis et.","subgroup_id":"Et repellat voluptates reiciendis.","updated_at":"Reiciendis consequatur laborum quidem voluptatum et voluptatibus."},{"created_at":"Eos sint ea provident.","delivery_mode":"Quae dolores.","email":"casey@littel.org","first_name":"Cumque sed eveniet reprehenderit.","id":"Ullam exercitationem quisquam nostrum nihil culpa.","last_name":"Ut atque voluptatibus.","mod_status":"Aut tempora.","name":"Qui et voluptates in perspiciatis non.","status":"Natus nesciunt omnis et.","subgroup_id":"Et repellat voluptates reiciendis.","updated_at":"Reiciendis consequatur laborum quidem voluptatum et voluptatibus."}],"total":6755547264242539697}},"GroupsioProjectsResponse":{"title":"GroupsioProjectsResponse","type":"object","properties":{"projects":{"type":"array","items":{"type":"string","example":"Et omnis harum eveniet molestias."},"description":"List of project identifiers","example":["Aperiam ut quia praesentium ut.","Molestiae rerum vero exercitationem eum."]}},"example":{"projects":["Blanditiis laborum magni aut qui.","Similique quibusdam."]}},"GroupsioService":{"title":"GroupsioService","type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","example":"Omnis quidem iste deserunt voluptas."},"domain":{"type":"string","description":"Service domain","example":"Corporis qui."},"group_id":{"type":"integer","description":"GroupsIO group ID","example":2523950269387679955,"format":"int64"},"id":{"type":"string","description":"Service ID","example":"Ab est."},"prefix":{"type":"string","description":"Email prefix","example":"In enim et ut."},"project_uid":{"type":"string","description":"LFX v2 project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"status":{"type":"string","description":"Service status","example":"Nobis ea ipsum optio."},"type":{"type":"string","description":"Service type","example":"primary"},"updated_at":{"type":"string","description":"Last update timestamp","example":"Ea aut ea."}},"description":"A GroupsIO service managed via ITX","example":{"created_at":"Et et quae ad debitis veniam.","domain":"Enim tenetur provident occaecati molestiae.","group_id":2296828321887294762,"id":"Rem praesentium aut quisquam veniam explicabo.","prefix":"Blanditiis sequi molestias est sunt nihil mollitia.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Aliquid hic facere non corporis.","type":"primary","updated_at":"Delectus expedita voluptas occaecati."}},"GroupsioServiceList":{"title":"GroupsioServiceList","type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/definitions/GroupsioService"},"description":"List of services","example":[{"created_at":"Voluptatem et ipsum eum.","domain":"Temporibus enim recusandae ipsam.","group_id":7850125793634127719,"id":"Provident quo quia ea debitis numquam.","prefix":"Perspiciatis nihil at.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Quasi consequatur.","type":"primary","updated_at":"Ipsam qui vel provident."},{"created_at":"Voluptatem et ipsum eum.","domain":"Temporibus enim recusandae ipsam.","group_id":7850125793634127719,"id":"Provident quo quia ea debitis numquam.","prefix":"Perspiciatis nihil at.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Quasi consequatur.","type":"primary","updated_at":"Ipsam qui vel provident."}]},"total":{"type":"integer","description":"Total count","example":4846887354177833450,"format":"int64"}},"example":{"items":[{"created_at":"Voluptatem et ipsum eum.","domain":"Temporibus enim recusandae ipsam.","group_id":7850125793634127719,"id":"Provident quo quia ea debitis numquam.","prefix":"Perspiciatis nihil at.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Quasi consequatur.","type":"primary","updated_at":"Ipsam qui vel provident."},{"created_at":"Voluptatem et ipsum eum.","domain":"Temporibus enim recusandae ipsam.","group_id":7850125793634127719,"id":"Provident quo quia ea debitis numquam.","prefix":"Perspiciatis nihil at.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Quasi consequatur.","type":"primary","updated_at":"Ipsam qui vel provident."}],"total":1724364197153247090}},"GroupsioSubgroup":{"title":"GroupsioSubgroup","type":"object","properties":{"audience_access":{"type":"string","description":"Audience access setting","example":"Non aut sit sit nesciunt quibusdam."},"committee_uid":{"type":"string","description":"LFX v2 committee UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"created_at":{"type":"string","description":"Creation timestamp","example":"Et molestias."},"description":{"type":"string","description":"Subgroup description","example":"Rerum voluptatem distinctio perferendis rerum consequuntur provident."},"group_id":{"type":"integer","description":"GroupsIO group ID","example":5150237287792658257,"format":"int64"},"id":{"type":"string","description":"Subgroup ID","example":"Quis repellendus voluptatem hic necessitatibus."},"name":{"type":"string","description":"Subgroup name","example":"Rerum ut a."},"project_uid":{"type":"string","description":"LFX v2 project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"type":{"type":"string","description":"Subgroup type","example":"Occaecati quia enim expedita soluta alias ex."},"updated_at":{"type":"string","description":"Last update timestamp","example":"Optio nobis mollitia consequuntur ullam."}},"description":"A GroupsIO subgroup (mailing list) managed via ITX","example":{"audience_access":"Quis quis ab.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"In inventore.","description":"Impedit minus.","group_id":9138520030546806571,"id":"Ratione ullam delectus vel a.","name":"Reiciendis et ea possimus sint.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Id et velit recusandae recusandae expedita quisquam.","updated_at":"Officia temporibus voluptate nihil excepturi."}},"GroupsioSubgroupList":{"title":"GroupsioSubgroupList","type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/definitions/GroupsioSubgroup"},"description":"List of subgroups","example":[{"audience_access":"Dolores sed officiis nihil ex.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Dolores recusandae amet blanditiis omnis qui optio.","description":"Voluptatibus autem expedita cumque magnam et.","group_id":3581801669178659495,"id":"Debitis eaque sed aut sequi veniam.","name":"Aut adipisci veritatis.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Natus et.","updated_at":"Saepe nihil quaerat exercitationem vero."},{"audience_access":"Dolores sed officiis nihil ex.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Dolores recusandae amet blanditiis omnis qui optio.","description":"Voluptatibus autem expedita cumque magnam et.","group_id":3581801669178659495,"id":"Debitis eaque sed aut sequi veniam.","name":"Aut adipisci veritatis.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Natus et.","updated_at":"Saepe nihil quaerat exercitationem vero."}]},"total":{"type":"integer","description":"Total count","example":392522118725679720,"format":"int64"}},"example":{"items":[{"audience_access":"Dolores sed officiis nihil ex.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Dolores recusandae amet blanditiis omnis qui optio.","description":"Voluptatibus autem expedita cumque magnam et.","group_id":3581801669178659495,"id":"Debitis eaque sed aut sequi veniam.","name":"Aut adipisci veritatis.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Natus et.","updated_at":"Saepe nihil quaerat exercitationem vero."},{"audience_access":"Dolores sed officiis nihil ex.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Dolores recusandae amet blanditiis omnis qui optio.","description":"Voluptatibus autem expedita cumque magnam et.","group_id":3581801669178659495,"id":"Debitis eaque sed aut sequi veniam.","name":"Aut adipisci veritatis.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Natus et.","updated_at":"Saepe nihil quaerat exercitationem vero."}],"total":2594653333279322015}},"InternalServerError":{"title":"InternalServerError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"description":"Internal server error","example":{"message":"An internal server error occurred."},"required":["message"]},"MailingListAddGroupsioMemberRequestBody":{"title":"MailingListAddGroupsioMemberRequestBody","type":"object","properties":{"delivery_mode":{"type":"string","description":"Email delivery mode","example":"normal","enum":["normal","digest","none"]},"email":{"type":"string","description":"Member email address","example":"lee.rodriguez@emard.com","format":"email"},"mod_status":{"type":"string","description":"Moderation status","example":"none","enum":["none","moderator","owner"]},"name":{"type":"string","description":"Member display name","example":"Consequatur molestiae laborum nihil."}},"example":{"delivery_mode":"normal","email":"nick@eichmann.com","mod_status":"moderator","name":"Nobis dolores et."}},"MailingListCheckGroupsioSubscriberRequestBody":{"title":"MailingListCheckGroupsioSubscriberRequestBody","type":"object","properties":{"email":{"type":"string","description":"Email address to check","example":"celestino@sawayn.biz","format":"email"},"subgroup_id":{"type":"string","description":"Subgroup ID","example":"Deserunt ab illum rem tenetur."}},"example":{"email":"lucious.witting@carter.com","subgroup_id":"Sint aut aliquid."},"required":["email","subgroup_id"]},"MailingListCreateGroupsioServiceRequestBody":{"title":"MailingListCreateGroupsioServiceRequestBody","type":"object","properties":{"domain":{"type":"string","description":"Service domain","example":"Qui iure deserunt."},"group_id":{"type":"integer","description":"GroupsIO group ID","example":1965789746219430351,"format":"int64"},"prefix":{"type":"string","description":"Email prefix","example":"Et repellendus non sed doloremque voluptatibus."},"project_uid":{"type":"string","description":"LFX v2 project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"status":{"type":"string","description":"Service status","example":"Autem ut dolorem nihil nesciunt quidem corporis."},"type":{"type":"string","description":"Service type","example":"primary","enum":["primary","formation","shared"]}},"example":{"domain":"Adipisci qui deleniti dolores ab.","group_id":1512003203849078727,"prefix":"Sed dignissimos quam tempora odit.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Libero temporibus distinctio et.","type":"primary"}},"MailingListCreateGroupsioSubgroupRequestBody":{"title":"MailingListCreateGroupsioSubgroupRequestBody","type":"object","properties":{"audience_access":{"type":"string","description":"Audience access setting","example":"Deserunt voluptatem deserunt optio eius omnis est."},"committee_uid":{"type":"string","description":"LFX v2 committee UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"description":{"type":"string","description":"Subgroup description","example":"Nisi illum et omnis omnis."},"group_id":{"type":"integer","description":"GroupsIO group ID","example":8362702504098453980,"format":"int64"},"name":{"type":"string","description":"Subgroup name","example":"Quisquam distinctio nesciunt consequatur maxime molestiae."},"project_uid":{"type":"string","description":"LFX v2 project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"type":{"type":"string","description":"Subgroup type","example":"Dolor odio incidunt expedita quia enim."}},"example":{"audience_access":"Qui fugiat voluptates.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","description":"Libero consectetur nisi doloribus numquam rerum et.","group_id":6109535771415932356,"name":"Veritatis mollitia et doloribus.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Aspernatur est."}},"MailingListInviteGroupsioMembersRequestBody":{"title":"MailingListInviteGroupsioMembersRequestBody","type":"object","properties":{"emails":{"type":"array","items":{"type":"string","example":"Ut amet unde eaque ut veniam."},"description":"Email addresses to invite","example":["Et qui quisquam vel illo velit.","Corrupti quia sit nemo sunt.","Quasi aliquam est ullam cumque.","Magnam libero minima."]}},"example":{"emails":["Aspernatur rerum odit qui et consequatur.","Dolores facere."]},"required":["emails"]},"MailingListUpdateGroupsioMemberRequestBody":{"title":"MailingListUpdateGroupsioMemberRequestBody","type":"object","properties":{"delivery_mode":{"type":"string","description":"Email delivery mode","example":"normal","enum":["normal","digest","none"]},"email":{"type":"string","description":"Member email address","example":"ocie@streichbergnaum.biz","format":"email"},"mod_status":{"type":"string","description":"Moderation status","example":"moderator","enum":["none","moderator","owner"]},"name":{"type":"string","description":"Member display name","example":"Voluptatem atque architecto qui eius."}},"example":{"delivery_mode":"none","email":"brionna.baumbach@hageneskerluke.org","mod_status":"owner","name":"Voluptatem illum qui."}},"MailingListUpdateGroupsioServiceRequestBody":{"title":"MailingListUpdateGroupsioServiceRequestBody","type":"object","properties":{"domain":{"type":"string","description":"Service domain","example":"Explicabo non quibusdam ut facilis."},"group_id":{"type":"integer","description":"GroupsIO group ID","example":8907944369201721908,"format":"int64"},"prefix":{"type":"string","description":"Email prefix","example":"Blanditiis quisquam quia voluptatem molestiae qui qui."},"project_uid":{"type":"string","description":"LFX v2 project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"status":{"type":"string","description":"Service status","example":"Minus est molestiae repudiandae odit."},"type":{"type":"string","description":"Service type","example":"primary","enum":["primary","formation","shared"]}},"example":{"domain":"Soluta ut nesciunt dolores tempora.","group_id":3063062694427452684,"prefix":"Qui deleniti alias natus quo.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Perspiciatis laudantium accusantium eum voluptatem.","type":"primary"}},"MailingListUpdateGroupsioSubgroupRequestBody":{"title":"MailingListUpdateGroupsioSubgroupRequestBody","type":"object","properties":{"audience_access":{"type":"string","description":"Audience access setting","example":"Laudantium exercitationem iusto laborum nihil."},"committee_uid":{"type":"string","description":"LFX v2 committee UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"description":{"type":"string","description":"Subgroup description","example":"Sint animi sint error qui odit."},"group_id":{"type":"integer","description":"GroupsIO group ID","example":1334617563062168445,"format":"int64"},"name":{"type":"string","description":"Subgroup name","example":"Ad enim."},"project_uid":{"type":"string","description":"LFX v2 project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"type":{"type":"string","description":"Subgroup type","example":"Sed et praesentium et eius fugiat id."}},"example":{"audience_access":"Voluptatem qui sapiente tempora quasi.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","description":"Id velit quibusdam.","group_id":385295109757726360,"name":"Adipisci ab enim sint quos.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Est ut maxime error velit."}},"NotFoundError":{"title":"NotFoundError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The resource was not found."}},"description":"Service not found","example":{"message":"The resource was not found."},"required":["message"]},"ServiceUnavailableError":{"title":"ServiceUnavailableError","type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"description":"Service unavailable","example":{"message":"The service is unavailable."},"required":["message"]}},"securityDefinitions":{"jwt_header_Authorization":{"type":"apiKey","description":"Heimdall authorization","name":"Authorization","in":"header"}}} \ No newline at end of file diff --git a/gen/http/openapi.yaml b/gen/http/openapi.yaml index 72fe319..6102450 100644 --- a/gen/http/openapi.yaml +++ b/gen/http/openapi.yaml @@ -1,7 +1,7 @@ swagger: "2.0" info: title: Mailing List Service - description: Service for managing mailing lists in LFX + description: Service for proxying GroupsIO operations to the ITX API version: 0.0.1 host: localhost:80 consumes: @@ -13,61 +13,40 @@ produces: - application/xml - application/gob paths: - /groupsio/mailing-lists: + /groupsio/checksubscriber: post: tags: - mailing-list - summary: create-grpsio-mailing-list mailing-list - description: Create GroupsIO mailing list/subgroup with comprehensive validation - operationId: mailing-list#create-grpsio-mailing-list + summary: check-groupsio-subscriber mailing-list + description: Check if an email address is subscribed to a GroupsIO subgroup + operationId: mailing-list#check-groupsio-subscriber parameters: - - name: v - in: query - description: Version of the API - required: true - type: string - enum: - - "1" - name: Authorization in: header description: JWT token issued by Heimdall required: false type: string - - name: Create-Grpsio-Mailing-ListRequestBody + - name: Check-Groupsio-SubscriberRequestBody in: body required: true schema: - $ref: '#/definitions/MailingListCreateGrpsioMailingListRequestBody' + $ref: '#/definitions/MailingListCheckGroupsioSubscriberRequestBody' required: - - group_name - - public - - type - - description - - title - - service_uid + - email + - subgroup_id responses: - "201": - description: Created response. + "200": + description: OK response. schema: - $ref: '#/definitions/GrpsIoMailingListFull' + $ref: '#/definitions/GroupsioCheckSubscriberResponse' + required: + - subscribed "400": description: Bad Request response. schema: $ref: '#/definitions/BadRequestError' required: - message - "404": - description: Not Found response. - schema: - $ref: '#/definitions/NotFoundError' - required: - - message - "409": - description: Conflict response. - schema: - $ref: '#/definitions/ConflictError' - required: - - message "500": description: Internal Server Error response. schema: @@ -84,53 +63,36 @@ paths: - http security: - jwt_header_Authorization: [] - /groupsio/mailing-lists/{uid}: + /groupsio/services: get: tags: - mailing-list - summary: get-grpsio-mailing-list mailing-list - description: Get GroupsIO mailing list details by UID - operationId: mailing-list#get-grpsio-mailing-list + summary: list-groupsio-services mailing-list + description: List GroupsIO services, optionally filtered by project UID + operationId: mailing-list#list-groupsio-services parameters: - - name: v + - name: project_uid in: query - description: Version of the API - required: true - type: string - enum: - - "1" - - name: uid - in: path - description: Mailing list UID -- unique identifier for the mailing list - required: true + description: LFX v2 project UID filter + required: false type: string format: uuid - name: Authorization in: header description: JWT token issued by Heimdall - required: true + required: false type: string responses: "200": description: OK response. schema: - $ref: '#/definitions/MailingListGetGrpsioMailingListResponseBody' - headers: - ETag: - description: ETag header value - type: string + $ref: '#/definitions/GroupsioServiceList' "400": description: Bad Request response. schema: $ref: '#/definitions/BadRequestError' required: - message - "404": - description: Not Found response. - schema: - $ref: '#/definitions/NotFoundError' - required: - - message "500": description: Internal Server Error response. schema: @@ -147,65 +109,34 @@ paths: - http security: - jwt_header_Authorization: [] - put: + post: tags: - mailing-list - summary: update-grpsio-mailing-list mailing-list - description: Update GroupsIO mailing list - operationId: mailing-list#update-grpsio-mailing-list + summary: create-groupsio-service mailing-list + description: Create a GroupsIO service + operationId: mailing-list#create-groupsio-service parameters: - - name: v - in: query - description: Version of the API - required: true - type: string - enum: - - "1" - - name: uid - in: path - description: Mailing list UID -- unique identifier for the mailing list - required: true - type: string - format: uuid - name: Authorization in: header description: JWT token issued by Heimdall required: false type: string - - name: If-Match - in: header - description: If-Match header value for conditional requests - required: false - type: string - - name: Update-Grpsio-Mailing-ListRequestBody + - name: Create-Groupsio-ServiceRequestBody in: body required: true schema: - $ref: '#/definitions/MailingListUpdateGrpsioMailingListRequestBody' - required: - - group_name - - public - - type - - description - - title - - service_uid + $ref: '#/definitions/MailingListCreateGroupsioServiceRequestBody' responses: - "200": - description: OK response. + "201": + description: Created response. schema: - $ref: '#/definitions/GrpsIoMailingListWithReadonlyAttributes' + $ref: '#/definitions/GroupsioService' "400": description: Bad Request response. schema: $ref: '#/definitions/BadRequestError' required: - message - "404": - description: Not Found response. - schema: - $ref: '#/definitions/NotFoundError' - required: - - message "409": description: Conflict response. schema: @@ -228,57 +159,69 @@ paths: - http security: - jwt_header_Authorization: [] - delete: + /groupsio/services/_projects: + get: tags: - mailing-list - summary: delete-grpsio-mailing-list mailing-list - description: Delete GroupsIO mailing list - operationId: mailing-list#delete-grpsio-mailing-list + summary: get-groupsio-service-projects mailing-list + description: Get projects that have GroupsIO services + operationId: mailing-list#get-groupsio-service-projects parameters: - - name: v - in: query - description: Version of the API + - name: Authorization + in: header + description: JWT token issued by Heimdall required: false type: string - enum: - - "1" - - name: uid + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/GroupsioProjectsResponse' + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ServiceUnavailableError' + required: + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + /groupsio/services/{service_id}: + get: + tags: + - mailing-list + summary: get-groupsio-service mailing-list + description: Get a GroupsIO service by ID + operationId: mailing-list#get-groupsio-service + parameters: + - name: service_id in: path - description: Mailing list UID -- unique identifier for the mailing list + description: Service ID required: true type: string - format: uuid - name: Authorization in: header description: JWT token issued by Heimdall required: false type: string - - name: If-Match - in: header - description: If-Match header value for conditional requests - required: false - type: string responses: - "204": - description: No Content response. - "400": - description: Bad Request response. + "200": + description: OK response. schema: - $ref: '#/definitions/BadRequestError' - required: - - message + $ref: '#/definitions/GroupsioService' "404": description: Not Found response. schema: $ref: '#/definitions/NotFoundError' required: - message - "409": - description: Conflict response. - schema: - $ref: '#/definitions/ConflictError' - required: - - message "500": description: Internal Server Error response. schema: @@ -295,24 +238,16 @@ paths: - http security: - jwt_header_Authorization: [] - /groupsio/mailing-lists/{uid}/members: - post: + put: tags: - mailing-list - summary: create-grpsio-mailing-list-member mailing-list - description: Create a new member for a GroupsIO mailing list - operationId: mailing-list#create-grpsio-mailing-list-member + summary: update-groupsio-service mailing-list + description: Update a GroupsIO service + operationId: mailing-list#update-groupsio-service parameters: - - name: v - in: query - description: Version of the API - required: true - type: string - enum: - - "1" - - name: uid + - name: service_id in: path - description: Mailing list UID + description: Service ID required: true type: string - name: Authorization @@ -320,30 +255,16 @@ paths: description: JWT token issued by Heimdall required: false type: string - - name: Create-Grpsio-Mailing-List-MemberRequestBody + - name: Update-Groupsio-ServiceRequestBody in: body required: true schema: - $ref: '#/definitions/MailingListCreateGrpsioMailingListMemberRequestBody' - required: - - email + $ref: '#/definitions/MailingListUpdateGroupsioServiceRequestBody' responses: - "201": - description: Created response. + "200": + description: OK response. schema: - $ref: '#/definitions/GrpsIoMemberFull' - required: - - uid - - mailing_list_uid - - first_name - - last_name - - email - - member_type - - delivery_mode - - mod_status - - status - - created_at - - updated_at + $ref: '#/definitions/GroupsioService' "400": description: Bad Request response. schema: @@ -356,10 +277,46 @@ paths: $ref: '#/definitions/NotFoundError' required: - message - "409": - description: Conflict response. + "500": + description: Internal Server Error response. schema: - $ref: '#/definitions/ConflictError' + $ref: '#/definitions/InternalServerError' + required: + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ServiceUnavailableError' + required: + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + delete: + tags: + - mailing-list + summary: delete-groupsio-service mailing-list + description: Delete a GroupsIO service + operationId: mailing-list#delete-groupsio-service + parameters: + - name: service_id + in: path + description: Service ID + required: true + type: string + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + responses: + "204": + description: No Content response. + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' required: - message "500": @@ -378,47 +335,30 @@ paths: - http security: - jwt_header_Authorization: [] - /groupsio/mailing-lists/{uid}/members/{member_uid}: + /groupsio/services/find_parent: get: tags: - mailing-list - summary: get-grpsio-mailing-list-member mailing-list - description: Get a member of a GroupsIO mailing list by UID - operationId: mailing-list#get-grpsio-mailing-list-member + summary: find-parent-groupsio-service mailing-list + description: Find the parent GroupsIO service for a project + operationId: mailing-list#find-parent-groupsio-service parameters: - - name: v + - name: project_uid in: query - description: Version of the API - required: true - type: string - enum: - - "1" - - name: uid - in: path - description: Mailing list UID -- unique identifier for the mailing list - required: true - type: string - format: uuid - - name: member_uid - in: path - description: Member UID -- unique identifier for the member + description: LFX v2 project UID required: true type: string format: uuid - name: Authorization in: header description: JWT token issued by Heimdall - required: true + required: false type: string responses: "200": description: OK response. schema: - $ref: '#/definitions/MailingListGetGrpsioMailingListMemberResponseBody' - headers: - ETag: - description: ETag header value - type: string + $ref: '#/definitions/GroupsioService' "400": description: Bad Request response. schema: @@ -447,70 +387,42 @@ paths: - http security: - jwt_header_Authorization: [] - put: + /groupsio/subgroups: + get: tags: - mailing-list - summary: update-grpsio-mailing-list-member mailing-list - description: Update a member of a GroupsIO mailing list - operationId: mailing-list#update-grpsio-mailing-list-member + summary: list-groupsio-subgroups mailing-list + description: List GroupsIO subgroups, optionally filtered by project UID and/or committee UID + operationId: mailing-list#list-groupsio-subgroups parameters: - - name: v + - name: project_uid in: query - description: Version of the API - required: true - type: string - enum: - - "1" - - name: uid - in: path - description: Mailing list UID -- unique identifier for the mailing list - required: true + description: LFX v2 project UID filter + required: false type: string format: uuid - - name: member_uid - in: path - description: Member UID -- unique identifier for the member - required: true + - name: committee_uid + in: query + description: LFX v2 committee UID filter + required: false type: string format: uuid - name: Authorization in: header description: JWT token issued by Heimdall - required: true - type: string - - name: If-Match - in: header - description: If-Match header value for conditional requests - required: true + required: false type: string - - name: Update-Grpsio-Mailing-List-MemberRequestBody - in: body - required: true - schema: - $ref: '#/definitions/MailingListUpdateGrpsioMailingListMemberRequestBody' responses: "200": description: OK response. schema: - $ref: '#/definitions/GrpsIoMemberWithReadonlyAttributes' + $ref: '#/definitions/GroupsioSubgroupList' "400": description: Bad Request response. schema: $ref: '#/definitions/BadRequestError' required: - message - "404": - description: Not Found response. - schema: - $ref: '#/definitions/NotFoundError' - required: - - message - "409": - description: Conflict response. - schema: - $ref: '#/definitions/ConflictError' - required: - - message "500": description: Internal Server Error response. schema: @@ -527,57 +439,34 @@ paths: - http security: - jwt_header_Authorization: [] - delete: + post: tags: - mailing-list - summary: delete-grpsio-mailing-list-member mailing-list - description: Delete a member from a GroupsIO mailing list - operationId: mailing-list#delete-grpsio-mailing-list-member + summary: create-groupsio-subgroup mailing-list + description: Create a GroupsIO subgroup + operationId: mailing-list#create-groupsio-subgroup parameters: - - name: v - in: query - description: Version of the API - required: true - type: string - enum: - - "1" - - name: uid - in: path - description: Mailing list UID -- unique identifier for the mailing list - required: true - type: string - format: uuid - - name: member_uid - in: path - description: Member UID -- unique identifier for the member - required: true - type: string - format: uuid - name: Authorization in: header description: JWT token issued by Heimdall - required: true + required: false type: string - - name: If-Match - in: header - description: If-Match header value for conditional requests + - name: Create-Groupsio-SubgroupRequestBody + in: body required: true - type: string + schema: + $ref: '#/definitions/MailingListCreateGroupsioSubgroupRequestBody' responses: - "204": - description: No Content response. + "201": + description: Created response. + schema: + $ref: '#/definitions/GroupsioSubgroup' "400": description: Bad Request response. schema: $ref: '#/definitions/BadRequestError' required: - message - "404": - description: Not Found response. - schema: - $ref: '#/definitions/NotFoundError' - required: - - message "409": description: Conflict response. schema: @@ -600,27 +489,19 @@ paths: - http security: - jwt_header_Authorization: [] - /groupsio/mailing-lists/{uid}/settings: + /groupsio/subgroups/{subgroup_id}: get: tags: - mailing-list - summary: get-grpsio-mailing-list-settings mailing-list - description: Get GroupsIO mailing list settings (writers and auditors) - operationId: mailing-list#get-grpsio-mailing-list-settings + summary: get-groupsio-subgroup mailing-list + description: Get a GroupsIO subgroup by ID + operationId: mailing-list#get-groupsio-subgroup parameters: - - name: v - in: query - description: Version of the API - required: false - type: string - enum: - - "1" - - name: uid + - name: subgroup_id in: path - description: Mailing list UID -- unique identifier for the mailing list + description: Subgroup ID required: true type: string - format: uuid - name: Authorization in: header description: JWT token issued by Heimdall @@ -630,17 +511,7 @@ paths: "200": description: OK response. schema: - $ref: '#/definitions/MailingListGetGrpsioMailingListSettingsResponseBody' - headers: - ETag: - description: ETag header value - type: string - "400": - description: Bad Request response. - schema: - $ref: '#/definitions/BadRequestError' - required: - - message + $ref: '#/definitions/GroupsioSubgroup' "404": description: Not Found response. schema: @@ -666,43 +537,30 @@ paths: put: tags: - mailing-list - summary: update-grpsio-mailing-list-settings mailing-list - description: Update GroupsIO mailing list settings (writers and auditors) - operationId: mailing-list#update-grpsio-mailing-list-settings + summary: update-groupsio-subgroup mailing-list + description: Update a GroupsIO subgroup + operationId: mailing-list#update-groupsio-subgroup parameters: - - name: v - in: query - description: Version of the API - required: true - type: string - enum: - - "1" - - name: uid + - name: subgroup_id in: path - description: Mailing list UID -- unique identifier for the mailing list + description: Subgroup ID required: true type: string - format: uuid - name: Authorization in: header description: JWT token issued by Heimdall required: false type: string - - name: If-Match - in: header - description: If-Match header value for conditional requests - required: false - type: string - - name: Update-Grpsio-Mailing-List-SettingsRequestBody + - name: Update-Groupsio-SubgroupRequestBody in: body required: true schema: - $ref: '#/definitions/MailingListUpdateGrpsioMailingListSettingsRequestBody' + $ref: '#/definitions/MailingListUpdateGroupsioSubgroupRequestBody' responses: "200": description: OK response. schema: - $ref: '#/definitions/GrpsIoMailingListSettings' + $ref: '#/definitions/GroupsioSubgroup' "400": description: Bad Request response. schema: @@ -715,12 +573,6 @@ paths: $ref: '#/definitions/NotFoundError' required: - message - "409": - description: Conflict response. - schema: - $ref: '#/definitions/ConflictError' - required: - - message "500": description: Internal Server Error response. schema: @@ -737,42 +589,76 @@ paths: - http security: - jwt_header_Authorization: [] - /groupsio/services: - post: + delete: tags: - mailing-list - summary: create-grpsio-service mailing-list - description: Create GroupsIO service with type-specific validation rules - operationId: mailing-list#create-grpsio-service + summary: delete-groupsio-subgroup mailing-list + description: Delete a GroupsIO subgroup + operationId: mailing-list#delete-groupsio-subgroup parameters: - - name: v - in: query - description: Version of the API + - name: subgroup_id + in: path + description: Subgroup ID required: true type: string - enum: - - "1" - name: Authorization in: header description: JWT token issued by Heimdall required: false type: string - - name: Create-Grpsio-ServiceRequestBody - in: body - required: true - schema: - $ref: '#/definitions/MailingListCreateGrpsioServiceRequestBody' - required: - - type - - project_uid responses: - "201": - description: Created response. + "204": + description: No Content response. + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - message + "503": + description: Service Unavailable response. schema: - $ref: '#/definitions/GrpsIoServiceFull' + $ref: '#/definitions/ServiceUnavailableError' required: - - type - - project_uid + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + /groupsio/subgroups/{subgroup_id}/invitemembers: + post: + tags: + - mailing-list + summary: invite-groupsio-members mailing-list + description: Invite members to a GroupsIO subgroup by email + operationId: mailing-list#invite-groupsio-members + parameters: + - name: subgroup_id + in: path + description: Subgroup ID + required: true + type: string + - name: Authorization + in: header + description: JWT token issued by Heimdall + required: false + type: string + - name: Invite-Groupsio-MembersRequestBody + in: body + required: true + schema: + $ref: '#/definitions/MailingListInviteGroupsioMembersRequestBody' + required: + - emails + responses: + "204": + description: No Content response. "400": description: Bad Request response. schema: @@ -785,12 +671,6 @@ paths: $ref: '#/definitions/NotFoundError' required: - message - "409": - description: Conflict response. - schema: - $ref: '#/definitions/ConflictError' - required: - - message "500": description: Internal Server Error response. schema: @@ -807,27 +687,19 @@ paths: - http security: - jwt_header_Authorization: [] - /groupsio/services/{uid}: + /groupsio/subgroups/{subgroup_id}/member_count: get: tags: - mailing-list - summary: get-grpsio-service mailing-list - description: Get groupsIO service details by ID - operationId: mailing-list#get-grpsio-service + summary: get-groupsio-subgroup-member-count mailing-list + description: Get count of members in a GroupsIO subgroup + operationId: mailing-list#get-groupsio-subgroup-member-count parameters: - - name: v - in: query - description: Version of the API - required: false - type: string - enum: - - "1" - - name: uid + - name: subgroup_id in: path - description: Service UID -- unique identifier for the service + description: Subgroup ID required: true type: string - format: uuid - name: Authorization in: header description: JWT token issued by Heimdall @@ -837,20 +709,9 @@ paths: "200": description: OK response. schema: - $ref: '#/definitions/MailingListGetGrpsioServiceResponseBody' - required: - - type - - project_uid - headers: - ETag: - description: ETag header value - type: string - "400": - description: Bad Request response. - schema: - $ref: '#/definitions/BadRequestError' + $ref: '#/definitions/GroupsioCount' required: - - message + - count "404": description: Not Found response. schema: @@ -873,52 +734,78 @@ paths: - http security: - jwt_header_Authorization: [] - put: + /groupsio/subgroups/{subgroup_id}/members: + get: tags: - mailing-list - summary: update-grpsio-service mailing-list - description: Update GroupsIO service - operationId: mailing-list#update-grpsio-service + summary: list-groupsio-members mailing-list + description: List members of a GroupsIO subgroup + operationId: mailing-list#list-groupsio-members parameters: - - name: v - in: query - description: Version of the API - required: true - type: string - enum: - - "1" - - name: uid + - name: subgroup_id in: path - description: Service UID -- unique identifier for the service + description: Subgroup ID required: true type: string - format: uuid - name: Authorization in: header description: JWT token issued by Heimdall required: false type: string - - name: If-Match + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/GroupsioMemberList' + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ServiceUnavailableError' + required: + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + post: + tags: + - mailing-list + summary: add-groupsio-member mailing-list + description: Add a member to a GroupsIO subgroup + operationId: mailing-list#add-groupsio-member + parameters: + - name: subgroup_id + in: path + description: Subgroup ID + required: true + type: string + - name: Authorization in: header - description: If-Match header value for conditional requests + description: JWT token issued by Heimdall required: false type: string - - name: Update-Grpsio-ServiceRequestBody + - name: Add-Groupsio-MemberRequestBody in: body required: true schema: - $ref: '#/definitions/MailingListUpdateGrpsioServiceRequestBody' - required: - - type - - project_uid + $ref: '#/definitions/MailingListAddGroupsioMemberRequestBody' responses: - "200": - description: OK response. + "201": + description: Created response. schema: - $ref: '#/definitions/GrpsIoServiceWithReadonlyAttributes' - required: - - type - - project_uid + $ref: '#/definitions/GroupsioMember' "400": description: Bad Request response. schema: @@ -953,57 +840,40 @@ paths: - http security: - jwt_header_Authorization: [] - delete: + /groupsio/subgroups/{subgroup_id}/members/{member_id}: + get: tags: - mailing-list - summary: delete-grpsio-service mailing-list - description: Delete GroupsIO service - operationId: mailing-list#delete-grpsio-service + summary: get-groupsio-member mailing-list + description: Get a member of a GroupsIO subgroup by ID + operationId: mailing-list#get-groupsio-member parameters: - - name: v - in: query - description: Version of the API - required: false + - name: subgroup_id + in: path + description: Subgroup ID + required: true type: string - enum: - - "1" - - name: uid + - name: member_id in: path - description: Service UID -- unique identifier for the service + description: Member ID required: true type: string - format: uuid - name: Authorization in: header description: JWT token issued by Heimdall required: false type: string - - name: If-Match - in: header - description: If-Match header value for conditional requests - required: false - type: string responses: - "204": - description: No Content response. - "400": - description: Bad Request response. + "200": + description: OK response. schema: - $ref: '#/definitions/BadRequestError' - required: - - message + $ref: '#/definitions/GroupsioMember' "404": description: Not Found response. schema: $ref: '#/definitions/NotFoundError' required: - message - "409": - description: Conflict response. - schema: - $ref: '#/definitions/ConflictError' - required: - - message "500": description: Internal Server Error response. schema: @@ -1020,41 +890,38 @@ paths: - http security: - jwt_header_Authorization: [] - /groupsio/services/{uid}/settings: - get: + put: tags: - mailing-list - summary: get-grpsio-service-settings mailing-list - description: Get GroupsIO service settings (writers and auditors) - operationId: mailing-list#get-grpsio-service-settings + summary: update-groupsio-member mailing-list + description: Update a member of a GroupsIO subgroup + operationId: mailing-list#update-groupsio-member parameters: - - name: v - in: query - description: Version of the API - required: false + - name: subgroup_id + in: path + description: Subgroup ID + required: true type: string - enum: - - "1" - - name: uid + - name: member_id in: path - description: Service UID -- unique identifier for the service + description: Member ID required: true type: string - format: uuid - name: Authorization in: header description: JWT token issued by Heimdall required: false type: string + - name: Update-Groupsio-MemberRequestBody + in: body + required: true + schema: + $ref: '#/definitions/MailingListUpdateGroupsioMemberRequestBody' responses: "200": description: OK response. schema: - $ref: '#/definitions/MailingListGetGrpsioServiceSettingsResponseBody' - headers: - ETag: - description: ETag header value - type: string + $ref: '#/definitions/GroupsioMember' "400": description: Bad Request response. schema: @@ -1083,64 +950,85 @@ paths: - http security: - jwt_header_Authorization: [] - put: + delete: tags: - mailing-list - summary: update-grpsio-service-settings mailing-list - description: Update GroupsIO service settings (writers and auditors) - operationId: mailing-list#update-grpsio-service-settings + summary: delete-groupsio-member mailing-list + description: Delete a member from a GroupsIO subgroup + operationId: mailing-list#delete-groupsio-member parameters: - - name: v - in: query - description: Version of the API + - name: subgroup_id + in: path + description: Subgroup ID required: true type: string - enum: - - "1" - - name: uid + - name: member_id in: path - description: Service UID -- unique identifier for the service + description: Member ID required: true type: string - format: uuid - name: Authorization in: header description: JWT token issued by Heimdall required: false type: string - - name: If-Match + responses: + "204": + description: No Content response. + "404": + description: Not Found response. + schema: + $ref: '#/definitions/NotFoundError' + required: + - message + "500": + description: Internal Server Error response. + schema: + $ref: '#/definitions/InternalServerError' + required: + - message + "503": + description: Service Unavailable response. + schema: + $ref: '#/definitions/ServiceUnavailableError' + required: + - message + schemes: + - http + security: + - jwt_header_Authorization: [] + /groupsio/subgroups/count: + get: + tags: + - mailing-list + summary: get-groupsio-subgroup-count mailing-list + description: Get count of GroupsIO subgroups for a project + operationId: mailing-list#get-groupsio-subgroup-count + parameters: + - name: project_uid + in: query + description: LFX v2 project UID + required: true + type: string + format: uuid + - name: Authorization in: header - description: If-Match header value for conditional requests + description: JWT token issued by Heimdall required: false type: string - - name: Update-Grpsio-Service-SettingsRequestBody - in: body - required: true - schema: - $ref: '#/definitions/MailingListUpdateGrpsioServiceSettingsRequestBody' responses: "200": description: OK response. schema: - $ref: '#/definitions/GrpsIoServiceSettings' + $ref: '#/definitions/GroupsioCount' + required: + - count "400": description: Bad Request response. schema: $ref: '#/definitions/BadRequestError' required: - message - "404": - description: Not Found response. - schema: - $ref: '#/definitions/NotFoundError' - required: - - message - "409": - description: Conflict response. - schema: - $ref: '#/definitions/ConflictError' - required: - - message "500": description: Internal Server Error response. schema: @@ -1210,43 +1098,6 @@ paths: - message schemes: - http - /webhooks/groupsio: - post: - tags: - - mailing-list - summary: groupsio-webhook mailing-list - description: Handle GroupsIO webhook events for subgroup and member changes - operationId: mailing-list#groupsio-webhook - parameters: - - name: x-groupsio-signature - in: header - description: HMAC-SHA1 base64 signature for verification - required: true - type: string - - name: Groupsio-WebhookRequestBody - in: body - required: true - schema: - $ref: '#/definitions/GroupsioWebhookPayload' - required: - - action - responses: - "204": - description: No Content response. - "400": - description: Bad Request response. - schema: - $ref: '#/definitions/BadRequestError' - required: - - message - "401": - description: Unauthorized response. - schema: - $ref: '#/definitions/UnauthorizedError' - required: - - message - schemes: - - http definitions: BadRequestError: title: BadRequestError @@ -1256,48 +1107,11 @@ definitions: type: string description: Error message example: The request was invalid. - description: Bad request - Invalid type, missing required fields, or validation failures + description: Bad request example: message: The request was invalid. required: - message - Committee: - title: Committee - type: object - properties: - allowed_voting_statuses: - type: array - items: - type: string - example: Alternate Voting Rep - enum: - - Voting Rep - - Alternate Voting Rep - - Observer - - Emeritus - - None - description: Committee member voting statuses that determine which members are synced - example: - - Voting Rep - - Alternate Voting Rep - name: - type: string - description: Committee name (read-only, populated by server) - example: Blanditiis libero rerum quasi. - uid: - type: string - description: Committee UUID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - description: Committee associated with a mailing list - example: - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Voluptatem itaque deleniti possimus distinctio magnam. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - required: - - uid ConflictError: title: ConflictError type: object @@ -1311,1375 +1125,429 @@ definitions: message: The resource already exists. required: - message - GroupsioWebhookPayload: - title: GroupsioWebhookPayload + GroupsioCheckSubscriberResponse: + title: GroupsioCheckSubscriberResponse type: object properties: - action: - type: string - description: The type of webhook event - example: created_subgroup - enum: - - created_subgroup - - deleted_subgroup - - added_member - - removed_member - - ban_members - extra: - type: string - description: Extra data field (subgroup suffix) - example: Inventore in. - extra_id: + subscribed: + type: boolean + description: Whether the email is subscribed + example: true + example: + subscribed: true + required: + - subscribed + GroupsioCount: + title: GroupsioCount + type: object + properties: + count: type: integer - description: Extra ID field (subgroup ID for deletion) - example: 9053037415002072244 + description: Count value + example: 6800691645898679136 format: int64 - group: - description: Contains subgroup data from Groups.io - example: Eligendi esse vel aut dolor repellendus tempore. - member_info: - description: Contains member data from Groups.io - example: Commodi placeat aliquam molestiae voluptas neque velit. example: - action: created_subgroup - extra: Et aut iste quaerat sit porro molestias. - extra_id: 8238369577073719666 - group: Deleniti magnam. - member_info: Aliquid distinctio mollitia. + count: 763626204865788569 required: - - action - GrpsIoMailingListFull: - title: GrpsIoMailingListFull + - count + GroupsioMember: + title: GroupsioMember type: object properties: - audience_access: - type: string - description: 'public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.' - default: public - example: public - enum: - - public - - approval_required - - invite_only - auditors: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - committees: - type: array - items: - $ref: '#/definitions/Committee' - description: Committees associated with this mailing list (OR logic for access control) - example: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee created_at: type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time - description: - type: string - description: Mailing list description (11-500 characters) - example: Technical steering committee discussions - minLength: 11 - maxLength: 500 - group_id: - type: integer - description: Mailing list group ID - example: 12345 - format: int64 - minimum: 0 - group_name: - type: string - description: Mailing list group name - example: technical-steering-committee - pattern: ^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$ - minLength: 3 - maxLength: 34 - project_name: - type: string - description: Project name (read-only) - example: Cloud Native Computing Foundation - project_slug: - type: string - description: Project slug identifier (read-only) - example: cncf - format: regexp - pattern: ^[a-z][a-z0-9_\-]*[a-z0-9]$ - project_uid: - type: string - description: LFXv2 Project UID (inherited from parent service) - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - public: - type: boolean - description: Whether the mailing list is publicly accessible - default: false - example: false - service_uid: - type: string - description: Service UUID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - subject_tag: - type: string - description: Subject tag prefix - example: '[TSC]' - maxLength: 50 - subscriber_count: - type: integer - description: Number of subscribers in this mailing list (read-only, maintained by service) - example: 42 - format: int64 - minimum: 0 - title: - type: string - description: Mailing list title - example: Technical Steering Committee - minLength: 5 - maxLength: 100 - type: - type: string - description: Mailing list type - example: discussion_moderated - enum: - - announcement - - discussion_moderated - - discussion_open - uid: - type: string - description: Mailing list UID -- unique identifier for the mailing list - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - updated_at: - type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - writers: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - example: - audience_access: public - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - committees: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - created_at: "2023-01-15T10:30:00Z" - description: Technical steering committee discussions - group_id: 12345 - group_name: technical-steering-committee - project_name: Cloud Native Computing Foundation - project_slug: cncf - project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: false - service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - subject_tag: '[TSC]' - subscriber_count: 42 - title: Technical Steering Committee - type: discussion_moderated - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - GrpsIoMailingListSettings: - title: GrpsIoMailingListSettings - type: object - properties: - auditors: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: - type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time - last_audited_by: - type: string - description: The user ID who last audited the service - example: user_id_12345 - last_audited_time: - type: string - description: The timestamp when the service was last audited - example: "2023-05-10T09:15:00Z" - format: date-time - last_reviewed_at: - type: string - description: The timestamp when the service was last reviewed in RFC3339 format - example: "2025-08-04T09:00:00Z" - format: date-time - last_reviewed_by: - type: string - description: The user ID who last reviewed this service - example: user_id_12345 - uid: - type: string - description: Mailing list UID -- unique identifier for the mailing list - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - updated_at: - type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - writers: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - description: A representation of GroupsIO mailing list settings for user management. - example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - last_audited_by: user_id_12345 - last_audited_time: "2023-05-10T09:15:00Z" - last_reviewed_at: "2025-08-04T09:00:00Z" - last_reviewed_by: user_id_12345 - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - GrpsIoMailingListWithReadonlyAttributes: - title: GrpsIoMailingListWithReadonlyAttributes - type: object - properties: - audience_access: - type: string - description: 'public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.' - default: public - example: public - enum: - - public - - approval_required - - invite_only - committees: - type: array - items: - $ref: '#/definitions/Committee' - description: Committees associated with this mailing list (OR logic for access control) - example: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - created_at: - type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time - description: - type: string - description: Mailing list description (11-500 characters) - example: Technical steering committee discussions - minLength: 11 - maxLength: 500 - group_id: - type: integer - description: Mailing list group ID - example: 12345 - format: int64 - minimum: 0 - group_name: - type: string - description: Mailing list group name - example: technical-steering-committee - pattern: ^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$ - minLength: 3 - maxLength: 34 - project_name: - type: string - description: Project name (read-only) - example: Cloud Native Computing Foundation - project_slug: - type: string - description: Project slug identifier (read-only) - example: cncf - format: regexp - pattern: ^[a-z][a-z0-9_\-]*[a-z0-9]$ - project_uid: - type: string - description: LFXv2 Project UID (inherited from parent service) - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - public: - type: boolean - description: Whether the mailing list is publicly accessible - default: false - example: false - service_uid: - type: string - description: Service UUID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - subject_tag: - type: string - description: Subject tag prefix - example: '[TSC]' - maxLength: 50 - subscriber_count: - type: integer - description: Number of subscribers in this mailing list (read-only, maintained by service) - example: 42 - format: int64 - minimum: 0 - title: - type: string - description: Mailing list title - example: Technical Steering Committee - minLength: 5 - maxLength: 100 - type: - type: string - description: Mailing list type - example: discussion_moderated - enum: - - announcement - - discussion_moderated - - discussion_open - uid: - type: string - description: Mailing list UID -- unique identifier for the mailing list - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - updated_at: - type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - description: A representation of GroupsIO mailing lists with readonly attributes. - example: - audience_access: public - committees: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - created_at: "2023-01-15T10:30:00Z" - description: Technical steering committee discussions - group_id: 12345 - group_name: technical-steering-committee - project_name: Cloud Native Computing Foundation - project_slug: cncf - project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: false - service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - subject_tag: '[TSC]' - subscriber_count: 42 - title: Technical Steering Committee - type: discussion_moderated - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - GrpsIoMemberFull: - title: GrpsIoMemberFull - type: object - properties: - auditors: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: - type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time - delivery_mode: + description: Creation timestamp + example: Est est et. + delivery_mode: type: string description: Email delivery mode - default: normal - example: normal - enum: - - normal - - digest - - none + example: Sunt ipsum et in ipsa sed. email: type: string description: Member email address - example: john.doe@example.com + example: sunny@hoeger.org format: email first_name: type: string description: Member first name - example: John - minLength: 1 - maxLength: 255 - group_id: - type: integer - description: Groups.io group ID - example: 67890 - format: int64 - job_title: + example: Nulla quas repellat. + id: type: string - description: Member job title - example: Software Engineer - maxLength: 255 + description: Member ID + example: Alias aut delectus ut omnis. last_name: type: string description: Member last name - example: Doe - minLength: 1 - maxLength: 255 - last_reviewed_at: - type: string - description: Last reviewed timestamp - example: "2023-01-15T14:30:00Z" - format: date-time - last_reviewed_by: - type: string - description: Last reviewed by user ID - example: admin@example.com - mailing_list_uid: - type: string - description: Mailing list UID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - member_id: - type: integer - description: Groups.io member ID - example: 12345 - format: int64 - member_type: - type: string - description: Member type - default: direct - example: direct - enum: - - committee - - direct + example: Sunt et qui rerum. mod_status: type: string description: Moderation status - default: none - example: none - enum: - - none - - moderator - - owner - organization: + example: Dolor accusantium ipsam cumque. + name: type: string - description: Member organization - example: Example Corp - maxLength: 255 + description: Member display name + example: Dolorum fugit similique saepe fugiat. status: type: string description: Member status - example: pending - uid: + example: Voluptas optio eveniet maxime. + subgroup_id: type: string - description: Member UID - example: f47ac10b-58cc-4372-a567-0e02b2c3d479 - format: uuid + description: Subgroup ID + example: Ut iste velit repudiandae dolores non quas. updated_at: type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - username: - type: string - description: Member username - example: jdoe - maxLength: 255 - writers: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. + description: Last update timestamp + example: Voluptatem debitis. + description: A member of a GroupsIO subgroup example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - delivery_mode: normal - email: john.doe@example.com - first_name: John - group_id: 67890 - job_title: Software Engineer - last_name: Doe - last_reviewed_at: "2023-01-15T14:30:00Z" - last_reviewed_by: admin@example.com - mailing_list_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - member_id: 12345 - member_type: direct - mod_status: owner - organization: Example Corp - status: pending - uid: f47ac10b-58cc-4372-a567-0e02b2c3d479 - updated_at: "2023-06-20T14:45:30Z" - username: jdoe - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - required: - - uid - - mailing_list_uid - - first_name - - last_name - - email - - member_type - - delivery_mode - - mod_status - - status - - created_at - - updated_at - GrpsIoMemberWithReadonlyAttributes: - title: GrpsIoMemberWithReadonlyAttributes + created_at: Voluptatem reprehenderit voluptatibus voluptatem. + delivery_mode: Repudiandae expedita est. + email: manley@mckenzie.org + first_name: Veritatis ut repudiandae sed. + id: Esse enim. + last_name: Dolore sapiente sit et sunt vitae quos. + mod_status: Voluptas iure alias sequi. + name: Est architecto ea magnam quisquam doloremque autem. + status: Officia et dignissimos ut voluptatibus fuga id. + subgroup_id: Voluptatibus explicabo. + updated_at: Commodi in porro. + GroupsioMemberList: + title: GroupsioMemberList type: object properties: - auditors: + items: type: array items: - $ref: '#/definitions/UserInfo' - description: Auditor users who can audit this resource + $ref: '#/definitions/GroupsioMember' + description: List of members example: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: - type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time - delivery_mode: - type: string - description: Email delivery mode - default: normal - example: normal - enum: - - normal - - digest - - none - email: - type: string - description: Member email address - example: john.doe@example.com - format: email - first_name: - type: string - description: Member first name - example: John - minLength: 1 - maxLength: 255 - group_id: + - created_at: Eos sint ea provident. + delivery_mode: Quae dolores. + email: casey@littel.org + first_name: Cumque sed eveniet reprehenderit. + id: Ullam exercitationem quisquam nostrum nihil culpa. + last_name: Ut atque voluptatibus. + mod_status: Aut tempora. + name: Qui et voluptates in perspiciatis non. + status: Natus nesciunt omnis et. + subgroup_id: Et repellat voluptates reiciendis. + updated_at: Reiciendis consequatur laborum quidem voluptatum et voluptatibus. + - created_at: Eos sint ea provident. + delivery_mode: Quae dolores. + email: casey@littel.org + first_name: Cumque sed eveniet reprehenderit. + id: Ullam exercitationem quisquam nostrum nihil culpa. + last_name: Ut atque voluptatibus. + mod_status: Aut tempora. + name: Qui et voluptates in perspiciatis non. + status: Natus nesciunt omnis et. + subgroup_id: Et repellat voluptates reiciendis. + updated_at: Reiciendis consequatur laborum quidem voluptatum et voluptatibus. + - created_at: Eos sint ea provident. + delivery_mode: Quae dolores. + email: casey@littel.org + first_name: Cumque sed eveniet reprehenderit. + id: Ullam exercitationem quisquam nostrum nihil culpa. + last_name: Ut atque voluptatibus. + mod_status: Aut tempora. + name: Qui et voluptates in perspiciatis non. + status: Natus nesciunt omnis et. + subgroup_id: Et repellat voluptates reiciendis. + updated_at: Reiciendis consequatur laborum quidem voluptatum et voluptatibus. + - created_at: Eos sint ea provident. + delivery_mode: Quae dolores. + email: casey@littel.org + first_name: Cumque sed eveniet reprehenderit. + id: Ullam exercitationem quisquam nostrum nihil culpa. + last_name: Ut atque voluptatibus. + mod_status: Aut tempora. + name: Qui et voluptates in perspiciatis non. + status: Natus nesciunt omnis et. + subgroup_id: Et repellat voluptates reiciendis. + updated_at: Reiciendis consequatur laborum quidem voluptatum et voluptatibus. + total: type: integer - description: Groups.io group ID - example: 67890 + description: Total count + example: 6152661567749295680 format: int64 - job_title: - type: string - description: Member job title - example: Software Engineer - maxLength: 255 - last_name: - type: string - description: Member last name - example: Doe - minLength: 1 - maxLength: 255 - last_reviewed_at: - type: string - description: Last reviewed timestamp - example: "2023-01-15T14:30:00Z" - format: date-time - last_reviewed_by: - type: string - description: Last reviewed by user ID - example: admin@example.com - mailing_list_uid: - type: string - description: Mailing list UID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - member_id: - type: integer - description: Groups.io member ID - example: 12345 - format: int64 - member_type: - type: string - description: Member type - default: direct - example: direct - enum: - - committee - - direct - mod_status: - type: string - description: Moderation status - default: none - example: none - enum: - - none - - moderator - - owner - organization: - type: string - description: Member organization - example: Example Corp - maxLength: 255 - status: - type: string - description: Member status - example: pending - uid: - type: string - description: Member UID - example: f47ac10b-58cc-4372-a567-0e02b2c3d479 - format: uuid - updated_at: - type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - username: - type: string - description: Member username - example: jdoe - maxLength: 255 - writers: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - description: A representation of GroupsIO mailing list members with readonly attributes. example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - delivery_mode: none - email: john.doe@example.com - first_name: John - group_id: 67890 - job_title: Software Engineer - last_name: Doe - last_reviewed_at: "2023-01-15T14:30:00Z" - last_reviewed_by: admin@example.com - mailing_list_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - member_id: 12345 - member_type: direct - mod_status: moderator - organization: Example Corp - status: pending - uid: f47ac10b-58cc-4372-a567-0e02b2c3d479 - updated_at: "2023-06-20T14:45:30Z" - username: jdoe - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - GrpsIoServiceFull: - title: GrpsIoServiceFull + items: + - created_at: Eos sint ea provident. + delivery_mode: Quae dolores. + email: casey@littel.org + first_name: Cumque sed eveniet reprehenderit. + id: Ullam exercitationem quisquam nostrum nihil culpa. + last_name: Ut atque voluptatibus. + mod_status: Aut tempora. + name: Qui et voluptates in perspiciatis non. + status: Natus nesciunt omnis et. + subgroup_id: Et repellat voluptates reiciendis. + updated_at: Reiciendis consequatur laborum quidem voluptatum et voluptatibus. + - created_at: Eos sint ea provident. + delivery_mode: Quae dolores. + email: casey@littel.org + first_name: Cumque sed eveniet reprehenderit. + id: Ullam exercitationem quisquam nostrum nihil culpa. + last_name: Ut atque voluptatibus. + mod_status: Aut tempora. + name: Qui et voluptates in perspiciatis non. + status: Natus nesciunt omnis et. + subgroup_id: Et repellat voluptates reiciendis. + updated_at: Reiciendis consequatur laborum quidem voluptatum et voluptatibus. + - created_at: Eos sint ea provident. + delivery_mode: Quae dolores. + email: casey@littel.org + first_name: Cumque sed eveniet reprehenderit. + id: Ullam exercitationem quisquam nostrum nihil culpa. + last_name: Ut atque voluptatibus. + mod_status: Aut tempora. + name: Qui et voluptates in perspiciatis non. + status: Natus nesciunt omnis et. + subgroup_id: Et repellat voluptates reiciendis. + updated_at: Reiciendis consequatur laborum quidem voluptatum et voluptatibus. + total: 6755547264242539697 + GroupsioProjectsResponse: + title: GroupsioProjectsResponse type: object properties: - auditors: + projects: type: array items: - $ref: '#/definitions/UserInfo' - description: Auditor users who can audit this resource + type: string + example: Et omnis harum eveniet molestias. + description: List of project identifiers example: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. + - Aperiam ut quia praesentium ut. + - Molestiae rerum vero exercitationem eum. + example: + projects: + - Blanditiis laborum magni aut qui. + - Similique quibusdam. + GroupsioService: + title: GroupsioService + type: object + properties: created_at: type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time + description: Creation timestamp + example: Omnis quidem iste deserunt voluptas. domain: type: string description: Service domain - example: lists.project.org - global_owners: - type: array - items: - type: string - example: nettie@schoenharvey.org - format: email - description: List of global owner email addresses (required for primary, forbidden for shared) - example: - - admin@example.com + example: Corporis qui. group_id: type: integer description: GroupsIO group ID - example: 12345 + example: 2523950269387679955 format: int64 - group_name: - type: string - description: GroupsIO group name - example: project-name - parent_service_uid: + id: type: string - description: Parent primary service UID (automatically set for shared type services) - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid + description: Service ID + example: Ab est. prefix: type: string - description: Email prefix (required for formation and shared, forbidden for primary) - example: formation - project_name: - type: string - description: Project name (read-only) - example: Cloud Native Computing Foundation - project_slug: - type: string - description: Project slug identifier - example: cncf - format: regexp - pattern: ^[a-z][a-z0-9_\-]*[a-z0-9]$ + description: Email prefix + example: In enim et ut. project_uid: type: string - description: LFXv2 Project UID + description: LFX v2 project UID example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee format: uuid - public: - type: boolean - description: Whether the service is publicly accessible - default: false - example: true status: type: string description: Service status - example: created + example: Nobis ea ipsum optio. type: type: string description: Service type example: primary - enum: - - primary - - formation - - shared - uid: - type: string - description: Service UID -- unique identifier for the service - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid updated_at: type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - url: - type: string - description: Service URL - example: https://lists.project.org - format: uri - writers: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. + description: Last update timestamp + example: Ea aut ea. + description: A GroupsIO service managed via ITX example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - domain: lists.project.org - global_owners: - - admin@example.com - group_id: 12345 - group_name: project-name - parent_service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - prefix: formation - project_name: Cloud Native Computing Foundation - project_slug: cncf + created_at: Et et quae ad debitis veniam. + domain: Enim tenetur provident occaecati molestiae. + group_id: 2296828321887294762 + id: Rem praesentium aut quisquam veniam explicabo. + prefix: Blanditiis sequi molestias est sunt nihil mollitia. project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: true - status: created + status: Aliquid hic facere non corporis. type: primary - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - url: https://lists.project.org - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - required: - - type - - project_uid - GrpsIoServiceSettings: - title: GrpsIoServiceSettings + updated_at: Delectus expedita voluptas occaecati. + GroupsioServiceList: + title: GroupsioServiceList type: object properties: - auditors: + items: type: array items: - $ref: '#/definitions/UserInfo' - description: Auditor users who can audit this resource + $ref: '#/definitions/GroupsioService' + description: List of services example: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: - type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time - last_audited_by: - type: string - description: The user ID who last audited the service - example: user_id_12345 - last_audited_time: - type: string - description: The timestamp when the service was last audited - example: "2023-05-10T09:15:00Z" - format: date-time - last_reviewed_at: - type: string - description: The timestamp when the service was last reviewed in RFC3339 format - example: "2025-08-04T09:00:00Z" - format: date-time - last_reviewed_by: + - created_at: Voluptatem et ipsum eum. + domain: Temporibus enim recusandae ipsam. + group_id: 7850125793634127719 + id: Provident quo quia ea debitis numquam. + prefix: Perspiciatis nihil at. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Quasi consequatur. + type: primary + updated_at: Ipsam qui vel provident. + - created_at: Voluptatem et ipsum eum. + domain: Temporibus enim recusandae ipsam. + group_id: 7850125793634127719 + id: Provident quo quia ea debitis numquam. + prefix: Perspiciatis nihil at. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Quasi consequatur. + type: primary + updated_at: Ipsam qui vel provident. + total: + type: integer + description: Total count + example: 4846887354177833450 + format: int64 + example: + items: + - created_at: Voluptatem et ipsum eum. + domain: Temporibus enim recusandae ipsam. + group_id: 7850125793634127719 + id: Provident quo quia ea debitis numquam. + prefix: Perspiciatis nihil at. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Quasi consequatur. + type: primary + updated_at: Ipsam qui vel provident. + - created_at: Voluptatem et ipsum eum. + domain: Temporibus enim recusandae ipsam. + group_id: 7850125793634127719 + id: Provident quo quia ea debitis numquam. + prefix: Perspiciatis nihil at. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Quasi consequatur. + type: primary + updated_at: Ipsam qui vel provident. + total: 1724364197153247090 + GroupsioSubgroup: + title: GroupsioSubgroup + type: object + properties: + audience_access: type: string - description: The user ID who last reviewed this service - example: user_id_12345 - uid: + description: Audience access setting + example: Non aut sit sit nesciunt quibusdam. + committee_uid: type: string - description: Service UID -- unique identifier for the service + description: LFX v2 committee UID example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee format: uuid - updated_at: - type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - writers: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - description: A representation of GroupsIO service settings for user management. - example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - last_audited_by: user_id_12345 - last_audited_time: "2023-05-10T09:15:00Z" - last_reviewed_at: "2025-08-04T09:00:00Z" - last_reviewed_by: user_id_12345 - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - GrpsIoServiceWithReadonlyAttributes: - title: GrpsIoServiceWithReadonlyAttributes - type: object - properties: - auditors: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. created_at: type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time - domain: + description: Creation timestamp + example: Et molestias. + description: type: string - description: Service domain - example: lists.project.org - global_owners: - type: array - items: - type: string - example: suzanne@dooley.com - format: email - description: List of global owner email addresses (required for primary, forbidden for shared) - example: - - admin@example.com + description: Subgroup description + example: Rerum voluptatem distinctio perferendis rerum consequuntur provident. group_id: type: integer description: GroupsIO group ID - example: 12345 + example: 5150237287792658257 format: int64 - group_name: - type: string - description: GroupsIO group name - example: project-name - parent_service_uid: + id: type: string - description: Parent primary service UID (automatically set for shared type services) - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - prefix: - type: string - description: Email prefix (required for formation and shared, forbidden for primary) - example: formation - project_name: - type: string - description: Project name (read-only) - example: Cloud Native Computing Foundation - project_slug: + description: Subgroup ID + example: Quis repellendus voluptatem hic necessitatibus. + name: type: string - description: Project slug identifier - example: cncf - format: regexp - pattern: ^[a-z][a-z0-9_\-]*[a-z0-9]$ + description: Subgroup name + example: Rerum ut a. project_uid: type: string - description: LFXv2 Project UID + description: LFX v2 project UID example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee format: uuid - public: - type: boolean - description: Whether the service is publicly accessible - default: false - example: true - status: - type: string - description: Service status - example: created type: type: string - description: Service type - example: primary - enum: - - primary - - formation - - shared - uid: - type: string - description: Service UID -- unique identifier for the service - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid + description: Subgroup type + example: Occaecati quia enim expedita soluta alias ex. updated_at: type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - url: - type: string - description: Service URL - example: https://lists.project.org - format: uri - writers: + description: Last update timestamp + example: Optio nobis mollitia consequuntur ullam. + description: A GroupsIO subgroup (mailing list) managed via ITX + example: + audience_access: Quis quis ab. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: In inventore. + description: Impedit minus. + group_id: 9138520030546806571 + id: Ratione ullam delectus vel a. + name: Reiciendis et ea possimus sint. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Id et velit recusandae recusandae expedita quisquam. + updated_at: Officia temporibus voluptate nihil excepturi. + GroupsioSubgroupList: + title: GroupsioSubgroupList + type: object + properties: + items: type: array items: - $ref: '#/definitions/UserInfo' - description: Manager users who can edit/modify this resource + $ref: '#/definitions/GroupsioSubgroup' + description: List of subgroups example: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - description: A representation of GroupsIO services with readonly attributes. + - audience_access: Dolores sed officiis nihil ex. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Dolores recusandae amet blanditiis omnis qui optio. + description: Voluptatibus autem expedita cumque magnam et. + group_id: 3581801669178659495 + id: Debitis eaque sed aut sequi veniam. + name: Aut adipisci veritatis. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Natus et. + updated_at: Saepe nihil quaerat exercitationem vero. + - audience_access: Dolores sed officiis nihil ex. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Dolores recusandae amet blanditiis omnis qui optio. + description: Voluptatibus autem expedita cumque magnam et. + group_id: 3581801669178659495 + id: Debitis eaque sed aut sequi veniam. + name: Aut adipisci veritatis. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Natus et. + updated_at: Saepe nihil quaerat exercitationem vero. + total: + type: integer + description: Total count + example: 392522118725679720 + format: int64 example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - domain: lists.project.org - global_owners: - - admin@example.com - group_id: 12345 - group_name: project-name - parent_service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - prefix: formation - project_name: Cloud Native Computing Foundation - project_slug: cncf - project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: true - status: created - type: primary - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - url: https://lists.project.org - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - required: - - type - - project_uid + items: + - audience_access: Dolores sed officiis nihil ex. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Dolores recusandae amet blanditiis omnis qui optio. + description: Voluptatibus autem expedita cumque magnam et. + group_id: 3581801669178659495 + id: Debitis eaque sed aut sequi veniam. + name: Aut adipisci veritatis. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Natus et. + updated_at: Saepe nihil quaerat exercitationem vero. + - audience_access: Dolores sed officiis nihil ex. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Dolores recusandae amet blanditiis omnis qui optio. + description: Voluptatibus autem expedita cumque magnam et. + group_id: 3581801669178659495 + id: Debitis eaque sed aut sequi veniam. + name: Aut adipisci veritatis. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Natus et. + updated_at: Saepe nihil quaerat exercitationem vero. + total: 2594653333279322015 InternalServerError: title: InternalServerError type: object @@ -2693,15 +1561,14 @@ definitions: message: An internal server error occurred. required: - message - MailingListCreateGrpsioMailingListMemberRequestBody: - title: MailingListCreateGrpsioMailingListMemberRequestBody + MailingListAddGroupsioMemberRequestBody: + title: MailingListAddGroupsioMemberRequestBody type: object properties: delivery_mode: type: string description: Email delivery mode - default: normal - example: none + example: normal enum: - normal - digest @@ -2709,338 +1576,70 @@ definitions: email: type: string description: Member email address - example: john.doe@example.com + example: lee.rodriguez@emard.com format: email - first_name: - type: string - description: Member first name - example: John - minLength: 1 - maxLength: 255 - job_title: - type: string - description: Member job title - example: Software Engineer - maxLength: 255 - last_name: - type: string - description: Member last name - example: Doe - minLength: 1 - maxLength: 255 - last_reviewed_at: - type: string - description: Last reviewed timestamp - example: "2023-01-15T14:30:00Z" - format: date-time - last_reviewed_by: - type: string - description: Last reviewed by user ID - example: admin@example.com - member_type: - type: string - description: Member type - default: direct - example: committee - enum: - - committee - - direct mod_status: type: string description: Moderation status - default: none example: none enum: - none - moderator - owner - organization: - type: string - description: Member organization - example: Example Corp - maxLength: 255 - username: + name: type: string - description: Member username - example: jdoe - maxLength: 255 + description: Member display name + example: Consequatur molestiae laborum nihil. example: - delivery_mode: none - email: john.doe@example.com - first_name: John - job_title: Software Engineer - last_name: Doe - last_reviewed_at: "2023-01-15T14:30:00Z" - last_reviewed_by: admin@example.com - member_type: direct + delivery_mode: normal + email: nick@eichmann.com mod_status: moderator - organization: Example Corp - username: jdoe - required: - - email - MailingListCreateGrpsioMailingListRequestBody: - title: MailingListCreateGrpsioMailingListRequestBody + name: Nobis dolores et. + MailingListCheckGroupsioSubscriberRequestBody: + title: MailingListCheckGroupsioSubscriberRequestBody type: object properties: - audience_access: - type: string - description: 'public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.' - default: public - example: public - enum: - - public - - approval_required - - invite_only - auditors: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - committees: - type: array - items: - $ref: '#/definitions/Committee' - description: Committees associated with this mailing list (OR logic for access control) - example: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - description: - type: string - description: Mailing list description (11-500 characters) - example: Technical steering committee discussions - minLength: 11 - maxLength: 500 - group_id: - type: integer - description: Mailing list group ID - example: 12345 - format: int64 - minimum: 0 - group_name: - type: string - description: Mailing list group name - example: technical-steering-committee - pattern: ^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$ - minLength: 3 - maxLength: 34 - public: - type: boolean - description: Whether the mailing list is publicly accessible - default: false - example: false - service_uid: - type: string - description: Service UUID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - subject_tag: - type: string - description: Subject tag prefix - example: '[TSC]' - maxLength: 50 - subscriber_count: - type: integer - description: Number of subscribers in this mailing list (read-only, maintained by service) - example: 42 - format: int64 - minimum: 0 - title: + email: type: string - description: Mailing list title - example: Technical Steering Committee - minLength: 5 - maxLength: 100 - type: + description: Email address to check + example: celestino@sawayn.biz + format: email + subgroup_id: type: string - description: Mailing list type - example: discussion_moderated - enum: - - announcement - - discussion_moderated - - discussion_open - writers: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + description: Subgroup ID + example: Deserunt ab illum rem tenetur. example: - audience_access: public - auditors: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - committees: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - description: Technical steering committee discussions - group_id: 12345 - group_name: technical-steering-committee - public: false - service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - subject_tag: '[TSC]' - subscriber_count: 42 - title: Technical Steering Committee - type: discussion_moderated - writers: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + email: lucious.witting@carter.com + subgroup_id: Sint aut aliquid. required: - - group_name - - public - - type - - description - - title - - service_uid - MailingListCreateGrpsioServiceRequestBody: - title: MailingListCreateGrpsioServiceRequestBody + - email + - subgroup_id + MailingListCreateGroupsioServiceRequestBody: + title: MailingListCreateGroupsioServiceRequestBody type: object properties: - auditors: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. domain: type: string description: Service domain - example: lists.project.org - global_owners: - type: array - items: - type: string - example: nat@hickle.biz - format: email - description: List of global owner email addresses (required for primary, forbidden for shared) - example: - - admin@example.com + example: Qui iure deserunt. group_id: type: integer description: GroupsIO group ID - example: 12345 + example: 1965789746219430351 format: int64 - group_name: - type: string - description: GroupsIO group name - example: project-name - parent_service_uid: - type: string - description: Parent primary service UID (automatically set for shared type services) - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid prefix: type: string - description: Email prefix (required for formation and shared, forbidden for primary) - example: formation - project_slug: - type: string - description: Project slug identifier - example: cncf - format: regexp - pattern: ^[a-z][a-z0-9_\-]*[a-z0-9]$ + description: Email prefix + example: Et repellendus non sed doloremque voluptatibus. project_uid: type: string - description: LFXv2 Project UID + description: LFX v2 project UID example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee format: uuid - public: - type: boolean - description: Whether the service is publicly accessible - default: false - example: true status: type: string description: Service status - example: created + example: Autem ut dolorem nihil nesciunt quidem corporis. type: type: string description: Service type @@ -3049,389 +1648,137 @@ definitions: - primary - formation - shared - url: + example: + domain: Adipisci qui deleniti dolores ab. + group_id: 1512003203849078727 + prefix: Sed dignissimos quam tempora odit. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Libero temporibus distinctio et. + type: primary + MailingListCreateGroupsioSubgroupRequestBody: + title: MailingListCreateGroupsioSubgroupRequestBody + type: object + properties: + audience_access: + type: string + description: Audience access setting + example: Deserunt voluptatem deserunt optio eius omnis est. + committee_uid: type: string - description: Service URL - example: https://lists.project.org - format: uri - writers: + description: LFX v2 committee UID + example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + format: uuid + description: + type: string + description: Subgroup description + example: Nisi illum et omnis omnis. + group_id: + type: integer + description: GroupsIO group ID + example: 8362702504098453980 + format: int64 + name: + type: string + description: Subgroup name + example: Quisquam distinctio nesciunt consequatur maxime molestiae. + project_uid: + type: string + description: LFX v2 project UID + example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + format: uuid + type: + type: string + description: Subgroup type + example: Dolor odio incidunt expedita quia enim. + example: + audience_access: Qui fugiat voluptates. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + description: Libero consectetur nisi doloribus numquam rerum et. + group_id: 6109535771415932356 + name: Veritatis mollitia et doloribus. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Aspernatur est. + MailingListInviteGroupsioMembersRequestBody: + title: MailingListInviteGroupsioMembersRequestBody + type: object + properties: + emails: type: array items: - $ref: '#/definitions/UserInfo' - description: Manager users who can edit/modify this resource + type: string + example: Ut amet unde eaque ut veniam. + description: Email addresses to invite example: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + - Et qui quisquam vel illo velit. + - Corrupti quia sit nemo sunt. + - Quasi aliquam est ullam cumque. + - Magnam libero minima. example: - auditors: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - domain: lists.project.org - global_owners: - - admin@example.com - group_id: 12345 - group_name: project-name - parent_service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - prefix: formation - project_slug: cncf - project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: true - status: created - type: primary - url: https://lists.project.org - writers: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + emails: + - Aspernatur rerum odit qui et consequatur. + - Dolores facere. required: - - type - - project_uid - MailingListGetGrpsioMailingListMemberResponseBody: - title: MailingListGetGrpsioMailingListMemberResponseBody - $ref: '#/definitions/GrpsIoMemberWithReadonlyAttributes' - MailingListGetGrpsioMailingListResponseBody: - title: MailingListGetGrpsioMailingListResponseBody - $ref: '#/definitions/GrpsIoMailingListWithReadonlyAttributes' - MailingListGetGrpsioMailingListSettingsResponseBody: - title: MailingListGetGrpsioMailingListSettingsResponseBody - $ref: '#/definitions/GrpsIoMailingListSettings' - MailingListGetGrpsioServiceResponseBody: - title: MailingListGetGrpsioServiceResponseBody - $ref: '#/definitions/GrpsIoServiceWithReadonlyAttributes' - MailingListGetGrpsioServiceSettingsResponseBody: - title: MailingListGetGrpsioServiceSettingsResponseBody - $ref: '#/definitions/GrpsIoServiceSettings' - MailingListUpdateGrpsioMailingListMemberRequestBody: - title: MailingListUpdateGrpsioMailingListMemberRequestBody + - emails + MailingListUpdateGroupsioMemberRequestBody: + title: MailingListUpdateGroupsioMemberRequestBody type: object properties: delivery_mode: type: string description: Email delivery mode - default: normal example: normal enum: - normal - digest - none - first_name: - type: string - description: Member first name - example: John - minLength: 1 - maxLength: 255 - job_title: - type: string - description: Member job title - example: Software Engineer - maxLength: 255 - last_name: + email: type: string - description: Member last name - example: Doe - minLength: 1 - maxLength: 255 + description: Member email address + example: ocie@streichbergnaum.biz + format: email mod_status: type: string description: Moderation status - default: none - example: owner + example: moderator enum: - none - moderator - owner - organization: - type: string - description: Member organization - example: Example Corp - maxLength: 255 - username: - type: string - description: Member username - example: jdoe - maxLength: 255 - example: - delivery_mode: digest - first_name: John - job_title: Software Engineer - last_name: Doe - mod_status: moderator - organization: Example Corp - username: jdoe - MailingListUpdateGrpsioMailingListRequestBody: - title: MailingListUpdateGrpsioMailingListRequestBody - type: object - properties: - audience_access: - type: string - description: 'public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.' - default: public - example: public - enum: - - public - - approval_required - - invite_only - committees: - type: array - items: - $ref: '#/definitions/Committee' - description: Committees associated with this mailing list (OR logic for access control) - example: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - description: - type: string - description: Mailing list description (11-500 characters) - example: Technical steering committee discussions - minLength: 11 - maxLength: 500 - group_id: - type: integer - description: Mailing list group ID - example: 12345 - format: int64 - minimum: 0 - group_name: - type: string - description: Mailing list group name - example: technical-steering-committee - pattern: ^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$ - minLength: 3 - maxLength: 34 - public: - type: boolean - description: Whether the mailing list is publicly accessible - default: false - example: false - service_uid: - type: string - description: Service UUID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - subject_tag: - type: string - description: Subject tag prefix - example: '[TSC]' - maxLength: 50 - subscriber_count: - type: integer - description: Number of subscribers in this mailing list (read-only, maintained by service) - example: 42 - format: int64 - minimum: 0 - title: - type: string - description: Mailing list title - example: Technical Steering Committee - minLength: 5 - maxLength: 100 - type: + name: type: string - description: Mailing list type - example: discussion_moderated - enum: - - announcement - - discussion_moderated - - discussion_open - example: - audience_access: public - committees: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - description: Technical steering committee discussions - group_id: 12345 - group_name: technical-steering-committee - public: false - service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - subject_tag: '[TSC]' - subscriber_count: 42 - title: Technical Steering Committee - type: discussion_moderated - required: - - group_name - - public - - type - - description - - title - - service_uid - MailingListUpdateGrpsioMailingListSettingsRequestBody: - title: MailingListUpdateGrpsioMailingListSettingsRequestBody - type: object - properties: - auditors: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - writers: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + description: Member display name + example: Voluptatem atque architecto qui eius. example: - auditors: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - writers: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - MailingListUpdateGrpsioServiceRequestBody: - title: MailingListUpdateGrpsioServiceRequestBody + delivery_mode: none + email: brionna.baumbach@hageneskerluke.org + mod_status: owner + name: Voluptatem illum qui. + MailingListUpdateGroupsioServiceRequestBody: + title: MailingListUpdateGroupsioServiceRequestBody type: object properties: domain: type: string description: Service domain - example: lists.project.org - global_owners: - type: array - items: - type: string - example: korey@ryan.net - format: email - description: List of global owner email addresses (required for primary, forbidden for shared) - example: - - admin@example.com + example: Explicabo non quibusdam ut facilis. group_id: type: integer description: GroupsIO group ID - example: 12345 + example: 8907944369201721908 format: int64 - group_name: - type: string - description: GroupsIO group name - example: project-name - parent_service_uid: - type: string - description: Parent primary service UID (automatically set for shared type services) - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid prefix: type: string - description: Email prefix (required for formation and shared, forbidden for primary) - example: formation - project_slug: - type: string - description: Project slug identifier - example: cncf - format: regexp - pattern: ^[a-z][a-z0-9_\-]*[a-z0-9]$ + description: Email prefix + example: Blanditiis quisquam quia voluptatem molestiae qui qui. project_uid: type: string - description: LFXv2 Project UID + description: LFX v2 project UID example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee format: uuid - public: - type: boolean - description: Whether the service is publicly accessible - default: false - example: true status: type: string description: Service status - example: created + example: Minus est molestiae repudiandae odit. type: type: string description: Service type @@ -3440,95 +1787,56 @@ definitions: - primary - formation - shared - url: - type: string - description: Service URL - example: https://lists.project.org - format: uri example: - domain: lists.project.org - global_owners: - - admin@example.com - group_id: 12345 - group_name: project-name - parent_service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - prefix: formation - project_slug: cncf + domain: Soluta ut nesciunt dolores tempora. + group_id: 3063062694427452684 + prefix: Qui deleniti alias natus quo. project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: true - status: created + status: Perspiciatis laudantium accusantium eum voluptatem. type: primary - url: https://lists.project.org - required: - - type - - project_uid - MailingListUpdateGrpsioServiceSettingsRequestBody: - title: MailingListUpdateGrpsioServiceSettingsRequestBody + MailingListUpdateGroupsioSubgroupRequestBody: + title: MailingListUpdateGroupsioSubgroupRequestBody type: object properties: - auditors: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - writers: - type: array - items: - $ref: '#/definitions/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + audience_access: + type: string + description: Audience access setting + example: Laudantium exercitationem iusto laborum nihil. + committee_uid: + type: string + description: LFX v2 committee UID + example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + format: uuid + description: + type: string + description: Subgroup description + example: Sint animi sint error qui odit. + group_id: + type: integer + description: GroupsIO group ID + example: 1334617563062168445 + format: int64 + name: + type: string + description: Subgroup name + example: Ad enim. + project_uid: + type: string + description: LFX v2 project UID + example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + format: uuid + type: + type: string + description: Subgroup type + example: Sed et praesentium et eius fugiat id. example: - auditors: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - writers: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + audience_access: Voluptatem qui sapiente tempora quasi. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + description: Id velit quibusdam. + group_id: 385295109757726360 + name: Adipisci ab enim sint quos. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Est ut maxime error velit. NotFoundError: title: NotFoundError type: object @@ -3537,7 +1845,7 @@ definitions: type: string description: Error message example: The resource was not found. - description: Resource not found + description: Service not found example: message: The resource was not found. required: @@ -3555,47 +1863,6 @@ definitions: message: The service is unavailable. required: - message - UnauthorizedError: - title: UnauthorizedError - type: object - properties: - message: - type: string - description: Error message - example: Unauthorized access. - description: Invalid webhook signature - example: - message: Unauthorized access. - required: - - message - UserInfo: - title: UserInfo - type: object - properties: - avatar: - type: string - description: The avatar URL of the user - example: http://feest.org/elody.zulauf - format: uri - email: - type: string - description: The email address of the user - example: tom@raynor.com - format: email - name: - type: string - description: The full name of the user - example: Hic tempore non atque ut quis ut. - username: - type: string - description: The username/LFID of the user - example: Sint neque et. - description: User information including profile details. - example: - avatar: http://schultz.org/josue - email: velma@rathtowne.biz - name: Consequatur molestias omnis aliquid aspernatur. - username: Voluptate dignissimos expedita quis sapiente asperiores magnam. securityDefinitions: jwt_header_Authorization: type: apiKey diff --git a/gen/http/openapi3.json b/gen/http/openapi3.json index d9c5ed6..a812be6 100644 --- a/gen/http/openapi3.json +++ b/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Mailing List Service","description":"Service for managing mailing lists in LFX","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for mailing-list"}],"paths":{"/groupsio/mailing-lists":{"post":{"tags":["mailing-list"],"summary":"create-grpsio-mailing-list mailing-list","description":"Create GroupsIO mailing list/subgroup with comprehensive validation","operationId":"mailing-list#create-grpsio-mailing-list","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGrpsioMailingListRequestBody"},"example":{"audience_access":"public","auditors":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}],"committees":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"description":"Technical steering committee discussions","group_id":12345,"group_name":"technical-steering-committee","public":false,"service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","subject_tag":"[TSC]","subscriber_count":42,"title":"Technical Steering Committee","type":"discussion_moderated","writers":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]}}}},"responses":{"201":{"description":"Created response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrpsIoMailingListFull"},"example":{"audience_access":"public","auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"committees":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"created_at":"2023-01-15T10:30:00Z","description":"Technical steering committee discussions","group_id":12345,"group_name":"technical-steering-committee","project_name":"Cloud Native Computing Foundation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":false,"service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","subject_tag":"[TSC]","subscriber_count":42,"title":"Technical Steering Committee","type":"discussion_moderated","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}}}},"400":{"description":"BadRequest: Bad request - Invalid data, missing required fields, or validation failures","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Parent service not found or committee not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"409":{"description":"Conflict: Mailing list with same name already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"message":"The resource already exists."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/mailing-lists/{uid}":{"delete":{"tags":["mailing-list"],"summary":"delete-grpsio-mailing-list mailing-list","description":"Delete GroupsIO mailing list","operationId":"mailing-list#delete-grpsio-mailing-list","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"schema":{"type":"string","description":"Mailing list UID -- unique identifier for the mailing list","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header value for conditional requests","example":"123"},"example":"123"}],"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Mailing list not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"409":{"description":"Conflict: Conflict - ETag mismatch or deletion not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"message":"The resource already exists."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["mailing-list"],"summary":"get-grpsio-mailing-list mailing-list","description":"Get GroupsIO mailing list details by UID","operationId":"mailing-list#get-grpsio-mailing-list","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"schema":{"type":"string","description":"Mailing list UID -- unique identifier for the mailing list","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value","schema":{"type":"string","description":"ETag header value","example":"123"},"example":"123"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrpsIoMailingListWithReadonlyAttributes"},"example":{"audience_access":"public","committees":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"created_at":"2023-01-15T10:30:00Z","description":"Technical steering committee discussions","group_id":12345,"group_name":"technical-steering-committee","project_name":"Cloud Native Computing Foundation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":false,"service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","subject_tag":"[TSC]","subscriber_count":42,"title":"Technical Steering Committee","type":"discussion_moderated","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z"}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Mailing list not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-grpsio-mailing-list mailing-list","description":"Update GroupsIO mailing list","operationId":"mailing-list#update-grpsio-mailing-list","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"schema":{"type":"string","description":"Mailing list UID -- unique identifier for the mailing list","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header value for conditional requests","example":"123"},"example":"123"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGrpsioMailingListRequestBody"},"example":{"audience_access":"public","committees":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"description":"Technical steering committee discussions","group_id":12345,"group_name":"technical-steering-committee","public":false,"service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","subject_tag":"[TSC]","subscriber_count":42,"title":"Technical Steering Committee","type":"discussion_moderated"}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrpsIoMailingListWithReadonlyAttributes"},"example":{"audience_access":"public","committees":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Qui consequuntur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"created_at":"2023-01-15T10:30:00Z","description":"Technical steering committee discussions","group_id":12345,"group_name":"technical-steering-committee","project_name":"Cloud Native Computing Foundation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":false,"service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","subject_tag":"[TSC]","subscriber_count":42,"title":"Technical Steering Committee","type":"discussion_moderated","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z"}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Mailing list not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"409":{"description":"Conflict: Conflict - ETag mismatch or validation failure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"message":"The resource already exists."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/mailing-lists/{uid}/members":{"post":{"tags":["mailing-list"],"summary":"create-grpsio-mailing-list-member mailing-list","description":"Create a new member for a GroupsIO mailing list","operationId":"mailing-list#create-grpsio-mailing-list-member","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"uid","in":"path","description":"Mailing list UID","required":true,"schema":{"type":"string","description":"Mailing list UID","example":"f47ac10b-58cc-4372-a567-0e02b2c3d479"},"example":"f47ac10b-58cc-4372-a567-0e02b2c3d479"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGrpsioMailingListMemberRequestBody"},"example":{"delivery_mode":"none","email":"john.doe@example.com","first_name":"John","job_title":"Software Engineer","last_name":"Doe","last_reviewed_at":"2023-01-15T14:30:00Z","last_reviewed_by":"admin@example.com","member_type":"direct","mod_status":"moderator","organization":"Example Corp","username":"jdoe"}}}},"responses":{"201":{"description":"Created response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrpsIoMemberFull"},"example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","delivery_mode":"digest","email":"john.doe@example.com","first_name":"John","group_id":67890,"job_title":"Software Engineer","last_name":"Doe","last_reviewed_at":"2023-01-15T14:30:00Z","last_reviewed_by":"admin@example.com","mailing_list_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","member_id":12345,"member_type":"direct","mod_status":"none","organization":"Example Corp","status":"pending","uid":"f47ac10b-58cc-4372-a567-0e02b2c3d479","updated_at":"2023-06-20T14:45:30Z","username":"jdoe","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Mailing list not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"409":{"description":"Conflict: Member already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"message":"The resource already exists."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/mailing-lists/{uid}/members/{member_uid}":{"delete":{"tags":["mailing-list"],"summary":"delete-grpsio-mailing-list-member mailing-list","description":"Delete a member from a GroupsIO mailing list","operationId":"mailing-list#delete-grpsio-mailing-list-member","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"schema":{"type":"string","description":"Mailing list UID -- unique identifier for the mailing list","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"name":"member_uid","in":"path","description":"Member UID -- unique identifier for the member","required":true,"schema":{"type":"string","description":"Member UID -- unique identifier for the member","example":"f47ac10b-58cc-4372-a567-0e02b2c3d479","format":"uuid"},"example":"f47ac10b-58cc-4372-a567-0e02b2c3d479"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"If-Match header value for conditional requests","example":"123"},"example":"123"}],"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request - Cannot remove sole owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Member not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"409":{"description":"Conflict: Conflict - ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"message":"The resource already exists."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["mailing-list"],"summary":"get-grpsio-mailing-list-member mailing-list","description":"Get a member of a GroupsIO mailing list by UID","operationId":"mailing-list#get-grpsio-mailing-list-member","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"schema":{"type":"string","description":"Mailing list UID -- unique identifier for the mailing list","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"name":"member_uid","in":"path","description":"Member UID -- unique identifier for the member","required":true,"schema":{"type":"string","description":"Member UID -- unique identifier for the member","example":"f47ac10b-58cc-4372-a567-0e02b2c3d479","format":"uuid"},"example":"f47ac10b-58cc-4372-a567-0e02b2c3d479"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value","schema":{"type":"string","description":"ETag header value","example":"123"},"example":"123"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrpsIoMemberWithReadonlyAttributes"},"example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","delivery_mode":"none","email":"john.doe@example.com","first_name":"John","group_id":67890,"job_title":"Software Engineer","last_name":"Doe","last_reviewed_at":"2023-01-15T14:30:00Z","last_reviewed_by":"admin@example.com","mailing_list_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","member_id":12345,"member_type":"direct","mod_status":"owner","organization":"Example Corp","status":"pending","uid":"f47ac10b-58cc-4372-a567-0e02b2c3d479","updated_at":"2023-06-20T14:45:30Z","username":"jdoe","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Member not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-grpsio-mailing-list-member mailing-list","description":"Update a member of a GroupsIO mailing list","operationId":"mailing-list#update-grpsio-mailing-list-member","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"schema":{"type":"string","description":"Mailing list UID -- unique identifier for the mailing list","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"name":"member_uid","in":"path","description":"Member UID -- unique identifier for the member","required":true,"schema":{"type":"string","description":"Member UID -- unique identifier for the member","example":"f47ac10b-58cc-4372-a567-0e02b2c3d479","format":"uuid"},"example":"f47ac10b-58cc-4372-a567-0e02b2c3d479"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"If-Match header value for conditional requests","example":"123"},"example":"123"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGrpsioMailingListMemberRequestBody"},"example":{"delivery_mode":"none","first_name":"John","job_title":"Software Engineer","last_name":"Doe","mod_status":"none","organization":"Example Corp","username":"jdoe"}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrpsIoMemberWithReadonlyAttributes"},"example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","delivery_mode":"normal","email":"john.doe@example.com","first_name":"John","group_id":67890,"job_title":"Software Engineer","last_name":"Doe","last_reviewed_at":"2023-01-15T14:30:00Z","last_reviewed_by":"admin@example.com","mailing_list_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","member_id":12345,"member_type":"committee","mod_status":"moderator","organization":"Example Corp","status":"pending","uid":"f47ac10b-58cc-4372-a567-0e02b2c3d479","updated_at":"2023-06-20T14:45:30Z","username":"jdoe","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}}}},"400":{"description":"BadRequest: Bad request - Invalid data or immutable field modification","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Member not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"409":{"description":"Conflict: Conflict - ETag mismatch or validation failure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"message":"The resource already exists."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/mailing-lists/{uid}/settings":{"get":{"tags":["mailing-list"],"summary":"get-grpsio-mailing-list-settings mailing-list","description":"Get GroupsIO mailing list settings (writers and auditors)","operationId":"mailing-list#get-grpsio-mailing-list-settings","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"schema":{"type":"string","description":"Mailing list UID -- unique identifier for the mailing list","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value","schema":{"type":"string","description":"ETag header value","example":"123"},"example":"123"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrpsIoServiceSettings"},"example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","last_audited_by":"user_id_12345","last_audited_time":"2023-05-10T09:15:00Z","last_reviewed_at":"2025-08-04T09:00:00Z","last_reviewed_by":"user_id_12345","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Mailing list settings not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-grpsio-mailing-list-settings mailing-list","description":"Update GroupsIO mailing list settings (writers and auditors)","operationId":"mailing-list#update-grpsio-mailing-list-settings","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"uid","in":"path","description":"Mailing list UID -- unique identifier for the mailing list","required":true,"schema":{"type":"string","description":"Mailing list UID -- unique identifier for the mailing list","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header value for conditional requests","example":"123"},"example":"123"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGrpsioServiceSettingsRequestBody"},"example":{"auditors":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}],"writers":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrpsIoMailingListSettings"},"example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","last_audited_by":"user_id_12345","last_audited_time":"2023-05-10T09:15:00Z","last_reviewed_at":"2025-08-04T09:00:00Z","last_reviewed_by":"user_id_12345","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Mailing list settings not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"409":{"description":"Conflict: Conflict - ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"message":"The resource already exists."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/services":{"post":{"tags":["mailing-list"],"summary":"create-grpsio-service mailing-list","description":"Create GroupsIO service with type-specific validation rules","operationId":"mailing-list#create-grpsio-service","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGrpsioServiceRequestBody"},"example":{"auditors":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}],"domain":"lists.project.org","global_owners":["admin@example.com"],"group_id":12345,"group_name":"project-name","parent_service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","prefix":"formation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":true,"status":"created","type":"primary","url":"https://lists.project.org","writers":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]}}}},"responses":{"201":{"description":"Created response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrpsIoServiceFull"},"example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","domain":"lists.project.org","global_owners":["admin@example.com"],"group_id":12345,"group_name":"project-name","parent_service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","prefix":"formation","project_name":"Cloud Native Computing Foundation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":true,"status":"created","type":"primary","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","url":"https://lists.project.org","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}}}},"400":{"description":"BadRequest: Bad request - Invalid type, missing required fields, or validation failures","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"message":"The resource already exists."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/services/{uid}":{"delete":{"tags":["mailing-list"],"summary":"delete-grpsio-service mailing-list","description":"Delete GroupsIO service","operationId":"mailing-list#delete-grpsio-service","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"uid","in":"path","description":"Service UID -- unique identifier for the service","required":true,"schema":{"type":"string","description":"Service UID -- unique identifier for the service","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header value for conditional requests","example":"123"},"example":"123"}],"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"message":"The resource already exists."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["mailing-list"],"summary":"get-grpsio-service mailing-list","description":"Get groupsIO service details by ID","operationId":"mailing-list#get-grpsio-service","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"uid","in":"path","description":"Service UID -- unique identifier for the service","required":true,"schema":{"type":"string","description":"Service UID -- unique identifier for the service","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value","schema":{"type":"string","description":"ETag header value","example":"123"},"example":"123"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrpsIoServiceWithReadonlyAttributes"},"example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","domain":"lists.project.org","global_owners":["admin@example.com"],"group_id":12345,"group_name":"project-name","parent_service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","prefix":"formation","project_name":"Cloud Native Computing Foundation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":true,"status":"created","type":"primary","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","url":"https://lists.project.org","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-grpsio-service mailing-list","description":"Update GroupsIO service","operationId":"mailing-list#update-grpsio-service","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"uid","in":"path","description":"Service UID -- unique identifier for the service","required":true,"schema":{"type":"string","description":"Service UID -- unique identifier for the service","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header value for conditional requests","example":"123"},"example":"123"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGrpsioServiceRequestBody"},"example":{"domain":"lists.project.org","global_owners":["admin@example.com"],"group_id":12345,"group_name":"project-name","parent_service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","prefix":"formation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":true,"status":"created","type":"primary","url":"https://lists.project.org"}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrpsIoServiceWithReadonlyAttributes"},"example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","domain":"lists.project.org","global_owners":["admin@example.com"],"group_id":12345,"group_name":"project-name","parent_service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","prefix":"formation","project_name":"Cloud Native Computing Foundation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":true,"status":"created","type":"primary","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","url":"https://lists.project.org","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"message":"The resource already exists."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/services/{uid}/settings":{"get":{"tags":["mailing-list"],"summary":"get-grpsio-service-settings mailing-list","description":"Get GroupsIO service settings (writers and auditors)","operationId":"mailing-list#get-grpsio-service-settings","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"uid","in":"path","description":"Service UID -- unique identifier for the service","required":true,"schema":{"type":"string","description":"Service UID -- unique identifier for the service","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"responses":{"200":{"description":"OK response.","headers":{"ETag":{"description":"ETag header value","schema":{"type":"string","description":"ETag header value","example":"123"},"example":"123"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrpsIoServiceSettings"},"example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","last_audited_by":"user_id_12345","last_audited_time":"2023-05-10T09:15:00Z","last_reviewed_at":"2025-08-04T09:00:00Z","last_reviewed_by":"user_id_12345","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Service settings not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-grpsio-service-settings mailing-list","description":"Update GroupsIO service settings (writers and auditors)","operationId":"mailing-list#update-grpsio-service-settings","parameters":[{"name":"v","in":"query","description":"Version of the API","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Version of the API","example":"1","enum":["1"]},"example":"1"},{"name":"uid","in":"path","description":"Service UID -- unique identifier for the service","required":true,"schema":{"type":"string","description":"Service UID -- unique identifier for the service","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"name":"If-Match","in":"header","description":"If-Match header value for conditional requests","allowEmptyValue":true,"schema":{"type":"string","description":"If-Match header value for conditional requests","example":"123"},"example":"123"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGrpsioServiceSettingsRequestBody"},"example":{"auditors":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}],"writers":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrpsIoServiceSettings"},"example":{"auditors":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}],"created_at":"2023-01-15T10:30:00Z","last_audited_by":"user_id_12345","last_audited_time":"2023-05-10T09:15:00Z","last_reviewed_at":"2025-08-04T09:00:00Z","last_reviewed_by":"user_id_12345","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","writers":[{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."},{"avatar":"http://leuschke.com/pietro","email":"pierre.wolf@stroman.info","name":"Temporibus assumenda error.","username":"Ducimus ex et."}]}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Service settings not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"409":{"description":"Conflict: Conflict - ETag mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"message":"The resource already exists."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/livez":{"get":{"tags":["mailing-list"],"summary":"livez mailing-list","description":"Check if the service is alive.","operationId":"mailing-list#livez","responses":{"200":{"description":"OK response.","content":{"text/plain":{"schema":{"type":"string","example":"OK","format":"binary"},"example":"OK"}}}}}},"/openapi.json":{"get":{"tags":["mailing-list"],"summary":"Download gen/http/openapi3.json","operationId":"mailing-list#/openapi.json","responses":{"200":{"description":"File downloaded"}}}},"/readyz":{"get":{"tags":["mailing-list"],"summary":"readyz mailing-list","description":"Check if the service is able to take inbound requests.","operationId":"mailing-list#readyz","responses":{"200":{"description":"OK response.","content":{"text/plain":{"schema":{"type":"string","example":"OK","format":"binary"},"example":"OK"}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}}}},"/webhooks/groupsio":{"post":{"tags":["mailing-list"],"summary":"groupsio-webhook mailing-list","description":"Handle GroupsIO webhook events for subgroup and member changes","operationId":"mailing-list#groupsio-webhook","parameters":[{"name":"x-groupsio-signature","in":"header","description":"HMAC-SHA1 base64 signature for verification","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"HMAC-SHA1 base64 signature for verification","example":"Eveniet reprehenderit unde ut atque voluptatibus rem."},"example":"Tempora nihil."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioWebhookPayload2"},"example":{"action":"created_subgroup","extra":"Sint rerum laboriosam consequatur ut explicabo saepe.","extra_id":500672409584855543,"group":"Aut consequatur nihil repellat velit qui.","member_info":"Totam cumque totam alias."}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Invalid webhook payload or signature","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"401":{"description":"Unauthorized: Invalid webhook signature","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"},"example":{"message":"Unauthorized access."}}}}}}}},"components":{"schemas":{"BadRequestError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"example":{"message":"The request was invalid."},"required":["message"]},"Committee":{"type":"object","properties":{"allowed_voting_statuses":{"type":"array","items":{"type":"string","example":"Voting Rep","enum":["Voting Rep","Alternate Voting Rep","Observer","Emeritus","None"]},"description":"Committee member voting statuses that determine which members are synced","example":["Voting Rep","Alternate Voting Rep"]},"name":{"type":"string","description":"Committee name (read-only, populated by server)","example":"Tempore est et sit et maxime."},"uid":{"type":"string","description":"Committee UUID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"}},"description":"Committee associated with a mailing list","example":{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Ex aspernatur.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},"required":["uid"]},"ConflictError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The resource already exists."}},"example":{"message":"The resource already exists."},"required":["message"]},"CreateGrpsioMailingListMemberRequestBody":{"type":"object","properties":{"delivery_mode":{"type":"string","description":"Email delivery mode","default":"normal","example":"digest","enum":["normal","digest","none"]},"email":{"type":"string","description":"Member email address","example":"john.doe@example.com","format":"email"},"first_name":{"type":"string","description":"Member first name","example":"John","minLength":1,"maxLength":255},"job_title":{"type":"string","description":"Member job title","example":"Software Engineer","maxLength":255},"last_name":{"type":"string","description":"Member last name","example":"Doe","minLength":1,"maxLength":255},"last_reviewed_at":{"type":"string","description":"Last reviewed timestamp","example":"2023-01-15T14:30:00Z","format":"date-time"},"last_reviewed_by":{"type":"string","description":"Last reviewed by user ID","example":"admin@example.com"},"member_type":{"type":"string","description":"Member type","default":"direct","example":"committee","enum":["committee","direct"]},"mod_status":{"type":"string","description":"Moderation status","default":"none","example":"owner","enum":["none","moderator","owner"]},"organization":{"type":"string","description":"Member organization","example":"Example Corp","maxLength":255},"username":{"type":"string","description":"Member username","example":"jdoe","maxLength":255}},"example":{"delivery_mode":"none","email":"john.doe@example.com","first_name":"John","job_title":"Software Engineer","last_name":"Doe","last_reviewed_at":"2023-01-15T14:30:00Z","last_reviewed_by":"admin@example.com","member_type":"committee","mod_status":"none","organization":"Example Corp","username":"jdoe"},"required":["email"]},"CreateGrpsioMailingListRequestBody":{"type":"object","properties":{"audience_access":{"type":"string","description":"public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.","default":"public","example":"public","enum":["public","approval_required","invite_only"]},"auditors":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]},"committees":{"type":"array","items":{"$ref":"#/components/schemas/Committee"},"description":"Committees associated with this mailing list (OR logic for access control)","example":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}]},"description":{"type":"string","description":"Mailing list description (11-500 characters)","example":"Technical steering committee discussions","minLength":11,"maxLength":500},"group_id":{"type":"integer","description":"Mailing list group ID","example":12345,"format":"int64","minimum":0},"group_name":{"type":"string","description":"Mailing list group name","example":"technical-steering-committee","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$","minLength":3,"maxLength":34},"public":{"type":"boolean","description":"Whether the mailing list is publicly accessible","default":false,"example":false},"service_uid":{"type":"string","description":"Service UUID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"subject_tag":{"type":"string","description":"Subject tag prefix","example":"[TSC]","maxLength":50},"subscriber_count":{"type":"integer","description":"Number of subscribers in this mailing list (read-only, maintained by service)","example":42,"format":"int64","minimum":0},"title":{"type":"string","description":"Mailing list title","example":"Technical Steering Committee","minLength":5,"maxLength":100},"type":{"type":"string","description":"Mailing list type","example":"discussion_moderated","enum":["announcement","discussion_moderated","discussion_open"]},"writers":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]}},"example":{"audience_access":"public","auditors":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}],"committees":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"description":"Technical steering committee discussions","group_id":12345,"group_name":"technical-steering-committee","public":false,"service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","subject_tag":"[TSC]","subscriber_count":42,"title":"Technical Steering Committee","type":"discussion_moderated","writers":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]},"required":["group_name","public","type","description","title","service_uid"]},"CreateGrpsioServiceRequestBody":{"type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]},"domain":{"type":"string","description":"Service domain","example":"lists.project.org"},"global_owners":{"type":"array","items":{"type":"string","example":"carli_johns@sipes.com","format":"email"},"description":"List of global owner email addresses (required for primary, forbidden for shared)","example":["admin@example.com"]},"group_id":{"type":"integer","description":"GroupsIO group ID","example":12345,"format":"int64"},"group_name":{"type":"string","description":"GroupsIO group name","example":"project-name"},"parent_service_uid":{"type":"string","description":"Parent primary service UID (automatically set for shared type services)","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"prefix":{"type":"string","description":"Email prefix (required for formation and shared, forbidden for primary)","example":"formation"},"project_slug":{"type":"string","description":"Project slug identifier","example":"cncf","format":"regexp","pattern":"^[a-z][a-z0-9_\\-]*[a-z0-9]$"},"project_uid":{"type":"string","description":"LFXv2 Project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"public":{"type":"boolean","description":"Whether the service is publicly accessible","default":false,"example":true},"status":{"type":"string","description":"Service status","example":"created"},"type":{"type":"string","description":"Service type","example":"primary","enum":["primary","formation","shared"]},"url":{"type":"string","description":"Service URL","example":"https://lists.project.org","format":"uri"},"writers":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]}},"example":{"auditors":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}],"domain":"lists.project.org","global_owners":["admin@example.com"],"group_id":12345,"group_name":"project-name","parent_service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","prefix":"formation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":true,"status":"created","type":"primary","url":"https://lists.project.org","writers":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]},"required":["type","project_uid"]},"GroupsioWebhookPayload":{"type":"object","properties":{"action":{"type":"string","description":"The type of webhook event","example":"created_subgroup","enum":["created_subgroup","deleted_subgroup","added_member","removed_member","ban_members"]},"extra":{"type":"string","description":"Extra data field (subgroup suffix)","example":"Quia fuga."},"extra_id":{"type":"integer","description":"Extra ID field (subgroup ID for deletion)","example":5008031110446800685,"format":"int64"},"group":{"description":"Contains subgroup data from Groups.io","example":"Eligendi facilis sit deserunt reiciendis."},"member_info":{"description":"Contains member data from Groups.io","example":"Accusamus et perspiciatis."},"signature":{"type":"string","description":"HMAC-SHA1 base64 signature for verification","example":"Repellendus sint libero quibusdam nulla cupiditate."}},"description":"Webhook event payload from Groups.io","example":{"action":"created_subgroup","extra":"Praesentium molestiae consequatur impedit esse mollitia voluptatem.","extra_id":5572662436592214257,"group":"Consectetur repudiandae eaque adipisci optio vel hic.","member_info":"Modi libero quas rem.","signature":"Aut et enim ut laudantium rerum at."},"required":["action","signature"]},"GroupsioWebhookPayload2":{"type":"object","properties":{"action":{"type":"string","description":"The type of webhook event","example":"created_subgroup","enum":["created_subgroup","deleted_subgroup","added_member","removed_member","ban_members"]},"extra":{"type":"string","description":"Extra data field (subgroup suffix)","example":"Reiciendis voluptatibus illum ut et."},"extra_id":{"type":"integer","description":"Extra ID field (subgroup ID for deletion)","example":5719676566333912791,"format":"int64"},"group":{"description":"Contains subgroup data from Groups.io","example":"Vero qui est nostrum sit."},"member_info":{"description":"Contains member data from Groups.io","example":"Officiis dignissimos."}},"example":{"action":"created_subgroup","extra":"Et voluptates in perspiciatis non repudiandae.","extra_id":3235636813397717479,"group":"Et ipsam iste dignissimos vel.","member_info":"A quam enim debitis veniam."},"required":["action"]},"GrpsIoMailingListFull":{"type":"object","properties":{"audience_access":{"type":"string","description":"public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.","default":"public","example":"public","enum":["public","approval_required","invite_only"]},"auditors":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]},"committees":{"type":"array","items":{"$ref":"#/components/schemas/Committee"},"description":"Committees associated with this mailing list (OR logic for access control)","example":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Quas animi qui.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Quas animi qui.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Quas animi qui.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"description":{"type":"string","description":"Mailing list description (11-500 characters)","example":"Technical steering committee discussions","minLength":11,"maxLength":500},"group_id":{"type":"integer","description":"Mailing list group ID","example":12345,"format":"int64","minimum":0},"group_name":{"type":"string","description":"Mailing list group name","example":"technical-steering-committee","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$","minLength":3,"maxLength":34},"project_name":{"type":"string","description":"Project name (read-only)","example":"Cloud Native Computing Foundation"},"project_slug":{"type":"string","description":"Project slug identifier (read-only)","example":"cncf","format":"regexp","pattern":"^[a-z][a-z0-9_\\-]*[a-z0-9]$"},"project_uid":{"type":"string","description":"LFXv2 Project UID (inherited from parent service)","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"public":{"type":"boolean","description":"Whether the mailing list is publicly accessible","default":false,"example":false},"service_uid":{"type":"string","description":"Service UUID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"subject_tag":{"type":"string","description":"Subject tag prefix","example":"[TSC]","maxLength":50},"subscriber_count":{"type":"integer","description":"Number of subscribers in this mailing list (read-only, maintained by service)","example":42,"format":"int64","minimum":0},"title":{"type":"string","description":"Mailing list title","example":"Technical Steering Committee","minLength":5,"maxLength":100},"type":{"type":"string","description":"Mailing list type","example":"discussion_moderated","enum":["announcement","discussion_moderated","discussion_open"]},"uid":{"type":"string","description":"Mailing list UID -- unique identifier for the mailing list","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"},"writers":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]}},"description":"A complete representation of GroupsIO mailing lists with all attributes including access control and audit trail.","example":{"audience_access":"public","auditors":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}],"committees":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Quas animi qui.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Quas animi qui.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Quas animi qui.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Quas animi qui.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"created_at":"2023-01-15T10:30:00Z","description":"Technical steering committee discussions","group_id":12345,"group_name":"technical-steering-committee","project_name":"Cloud Native Computing Foundation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":false,"service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","subject_tag":"[TSC]","subscriber_count":42,"title":"Technical Steering Committee","type":"discussion_moderated","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","writers":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]}},"GrpsIoMailingListSettings":{"type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"last_audited_by":{"type":"string","description":"The user ID who last audited the service","example":"user_id_12345"},"last_audited_time":{"type":"string","description":"The timestamp when the service was last audited","example":"2023-05-10T09:15:00Z","format":"date-time"},"last_reviewed_at":{"type":"string","description":"The timestamp when the service was last reviewed in RFC3339 format","example":"2025-08-04T09:00:00Z","format":"date-time"},"last_reviewed_by":{"type":"string","description":"The user ID who last reviewed this service","example":"user_id_12345"},"uid":{"type":"string","description":"Mailing list UID -- unique identifier for the mailing list","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"},"writers":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]}},"description":"A representation of GroupsIO mailing list settings for user management.","example":{"auditors":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}],"created_at":"2023-01-15T10:30:00Z","last_audited_by":"user_id_12345","last_audited_time":"2023-05-10T09:15:00Z","last_reviewed_at":"2025-08-04T09:00:00Z","last_reviewed_by":"user_id_12345","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","writers":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]}},"GrpsIoMailingListWithReadonlyAttributes":{"type":"object","properties":{"audience_access":{"type":"string","description":"public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.","default":"public","example":"public","enum":["public","approval_required","invite_only"]},"committees":{"type":"array","items":{"$ref":"#/components/schemas/Committee"},"description":"Committees associated with this mailing list (OR logic for access control)","example":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Quas animi qui.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Quas animi qui.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Quas animi qui.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Quas animi qui.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"description":{"type":"string","description":"Mailing list description (11-500 characters)","example":"Technical steering committee discussions","minLength":11,"maxLength":500},"group_id":{"type":"integer","description":"Mailing list group ID","example":12345,"format":"int64","minimum":0},"group_name":{"type":"string","description":"Mailing list group name","example":"technical-steering-committee","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$","minLength":3,"maxLength":34},"project_name":{"type":"string","description":"Project name (read-only)","example":"Cloud Native Computing Foundation"},"project_slug":{"type":"string","description":"Project slug identifier (read-only)","example":"cncf","format":"regexp","pattern":"^[a-z][a-z0-9_\\-]*[a-z0-9]$"},"project_uid":{"type":"string","description":"LFXv2 Project UID (inherited from parent service)","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"public":{"type":"boolean","description":"Whether the mailing list is publicly accessible","default":false,"example":false},"service_uid":{"type":"string","description":"Service UUID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"subject_tag":{"type":"string","description":"Subject tag prefix","example":"[TSC]","maxLength":50},"subscriber_count":{"type":"integer","description":"Number of subscribers in this mailing list (read-only, maintained by service)","example":42,"format":"int64","minimum":0},"title":{"type":"string","description":"Mailing list title","example":"Technical Steering Committee","minLength":5,"maxLength":100},"type":{"type":"string","description":"Mailing list type","example":"discussion_moderated","enum":["announcement","discussion_moderated","discussion_open"]},"uid":{"type":"string","description":"Mailing list UID -- unique identifier for the mailing list","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"}},"description":"A representation of GroupsIO mailing lists with readonly attributes.","example":{"audience_access":"public","committees":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Quas animi qui.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Quas animi qui.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"created_at":"2023-01-15T10:30:00Z","description":"Technical steering committee discussions","group_id":12345,"group_name":"technical-steering-committee","project_name":"Cloud Native Computing Foundation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":false,"service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","subject_tag":"[TSC]","subscriber_count":42,"title":"Technical Steering Committee","type":"discussion_moderated","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z"}},"GrpsIoMemberFull":{"type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"delivery_mode":{"type":"string","description":"Email delivery mode","default":"normal","example":"digest","enum":["normal","digest","none"]},"email":{"type":"string","description":"Member email address","example":"john.doe@example.com","format":"email"},"first_name":{"type":"string","description":"Member first name","example":"John","minLength":1,"maxLength":255},"group_id":{"type":"integer","description":"Groups.io group ID","example":67890,"format":"int64"},"job_title":{"type":"string","description":"Member job title","example":"Software Engineer","maxLength":255},"last_name":{"type":"string","description":"Member last name","example":"Doe","minLength":1,"maxLength":255},"last_reviewed_at":{"type":"string","description":"Last reviewed timestamp","example":"2023-01-15T14:30:00Z","format":"date-time"},"last_reviewed_by":{"type":"string","description":"Last reviewed by user ID","example":"admin@example.com"},"mailing_list_uid":{"type":"string","description":"Mailing list UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"member_id":{"type":"integer","description":"Groups.io member ID","example":12345,"format":"int64"},"member_type":{"type":"string","description":"Member type","default":"direct","example":"direct","enum":["committee","direct"]},"mod_status":{"type":"string","description":"Moderation status","default":"none","example":"owner","enum":["none","moderator","owner"]},"organization":{"type":"string","description":"Member organization","example":"Example Corp","maxLength":255},"status":{"type":"string","description":"Member status","example":"pending"},"uid":{"type":"string","description":"Member UID","example":"f47ac10b-58cc-4372-a567-0e02b2c3d479","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"},"username":{"type":"string","description":"Member username","example":"jdoe","maxLength":255},"writers":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]}},"description":"A complete representation of a GroupsIO mailing list member with all attributes.","example":{"auditors":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}],"created_at":"2023-01-15T10:30:00Z","delivery_mode":"none","email":"john.doe@example.com","first_name":"John","group_id":67890,"job_title":"Software Engineer","last_name":"Doe","last_reviewed_at":"2023-01-15T14:30:00Z","last_reviewed_by":"admin@example.com","mailing_list_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","member_id":12345,"member_type":"committee","mod_status":"owner","organization":"Example Corp","status":"pending","uid":"f47ac10b-58cc-4372-a567-0e02b2c3d479","updated_at":"2023-06-20T14:45:30Z","username":"jdoe","writers":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]},"required":["uid","mailing_list_uid","first_name","last_name","email","member_type","delivery_mode","mod_status","status","created_at","updated_at"]},"GrpsIoMemberWithReadonlyAttributes":{"type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"delivery_mode":{"type":"string","description":"Email delivery mode","default":"normal","example":"normal","enum":["normal","digest","none"]},"email":{"type":"string","description":"Member email address","example":"john.doe@example.com","format":"email"},"first_name":{"type":"string","description":"Member first name","example":"John","minLength":1,"maxLength":255},"group_id":{"type":"integer","description":"Groups.io group ID","example":67890,"format":"int64"},"job_title":{"type":"string","description":"Member job title","example":"Software Engineer","maxLength":255},"last_name":{"type":"string","description":"Member last name","example":"Doe","minLength":1,"maxLength":255},"last_reviewed_at":{"type":"string","description":"Last reviewed timestamp","example":"2023-01-15T14:30:00Z","format":"date-time"},"last_reviewed_by":{"type":"string","description":"Last reviewed by user ID","example":"admin@example.com"},"mailing_list_uid":{"type":"string","description":"Mailing list UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"member_id":{"type":"integer","description":"Groups.io member ID","example":12345,"format":"int64"},"member_type":{"type":"string","description":"Member type","default":"direct","example":"committee","enum":["committee","direct"]},"mod_status":{"type":"string","description":"Moderation status","default":"none","example":"none","enum":["none","moderator","owner"]},"organization":{"type":"string","description":"Member organization","example":"Example Corp","maxLength":255},"status":{"type":"string","description":"Member status","example":"pending"},"uid":{"type":"string","description":"Member UID","example":"f47ac10b-58cc-4372-a567-0e02b2c3d479","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"},"username":{"type":"string","description":"Member username","example":"jdoe","maxLength":255},"writers":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]}},"description":"A representation of GroupsIO mailing list members with readonly attributes.","example":{"auditors":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}],"created_at":"2023-01-15T10:30:00Z","delivery_mode":"none","email":"john.doe@example.com","first_name":"John","group_id":67890,"job_title":"Software Engineer","last_name":"Doe","last_reviewed_at":"2023-01-15T14:30:00Z","last_reviewed_by":"admin@example.com","mailing_list_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","member_id":12345,"member_type":"committee","mod_status":"owner","organization":"Example Corp","status":"pending","uid":"f47ac10b-58cc-4372-a567-0e02b2c3d479","updated_at":"2023-06-20T14:45:30Z","username":"jdoe","writers":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]}},"GrpsIoServiceFull":{"type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"domain":{"type":"string","description":"Service domain","example":"lists.project.org"},"global_owners":{"type":"array","items":{"type":"string","example":"elmo_kautzer@muellerkoepp.org","format":"email"},"description":"List of global owner email addresses (required for primary, forbidden for shared)","example":["admin@example.com"]},"group_id":{"type":"integer","description":"GroupsIO group ID","example":12345,"format":"int64"},"group_name":{"type":"string","description":"GroupsIO group name","example":"project-name"},"parent_service_uid":{"type":"string","description":"Parent primary service UID (automatically set for shared type services)","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"prefix":{"type":"string","description":"Email prefix (required for formation and shared, forbidden for primary)","example":"formation"},"project_name":{"type":"string","description":"Project name (read-only)","example":"Cloud Native Computing Foundation"},"project_slug":{"type":"string","description":"Project slug identifier","example":"cncf","format":"regexp","pattern":"^[a-z][a-z0-9_\\-]*[a-z0-9]$"},"project_uid":{"type":"string","description":"LFXv2 Project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"public":{"type":"boolean","description":"Whether the service is publicly accessible","default":false,"example":true},"status":{"type":"string","description":"Service status","example":"created"},"type":{"type":"string","description":"Service type","example":"primary","enum":["primary","formation","shared"]},"uid":{"type":"string","description":"Service UID -- unique identifier for the service","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"},"url":{"type":"string","description":"Service URL","example":"https://lists.project.org","format":"uri"},"writers":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]}},"description":"A complete representation of GroupsIO services with all attributes including access control and audit trail.","example":{"auditors":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}],"created_at":"2023-01-15T10:30:00Z","domain":"lists.project.org","global_owners":["admin@example.com"],"group_id":12345,"group_name":"project-name","parent_service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","prefix":"formation","project_name":"Cloud Native Computing Foundation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":true,"status":"created","type":"primary","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","url":"https://lists.project.org","writers":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]},"required":["type","project_uid"]},"GrpsIoServiceSettings":{"type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"last_audited_by":{"type":"string","description":"The user ID who last audited the service","example":"user_id_12345"},"last_audited_time":{"type":"string","description":"The timestamp when the service was last audited","example":"2023-05-10T09:15:00Z","format":"date-time"},"last_reviewed_at":{"type":"string","description":"The timestamp when the service was last reviewed in RFC3339 format","example":"2025-08-04T09:00:00Z","format":"date-time"},"last_reviewed_by":{"type":"string","description":"The user ID who last reviewed this service","example":"user_id_12345"},"uid":{"type":"string","description":"Service UID -- unique identifier for the service","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"},"writers":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]}},"description":"A representation of GroupsIO service settings for user management.","example":{"auditors":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}],"created_at":"2023-01-15T10:30:00Z","last_audited_by":"user_id_12345","last_audited_time":"2023-05-10T09:15:00Z","last_reviewed_at":"2025-08-04T09:00:00Z","last_reviewed_by":"user_id_12345","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","writers":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]}},"GrpsIoServiceWithReadonlyAttributes":{"type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]},"created_at":{"type":"string","description":"The timestamp when the service was created (read-only)","example":"2023-01-15T10:30:00Z","format":"date-time"},"domain":{"type":"string","description":"Service domain","example":"lists.project.org"},"global_owners":{"type":"array","items":{"type":"string","example":"otho.witting@trantowschimmel.biz","format":"email"},"description":"List of global owner email addresses (required for primary, forbidden for shared)","example":["admin@example.com"]},"group_id":{"type":"integer","description":"GroupsIO group ID","example":12345,"format":"int64"},"group_name":{"type":"string","description":"GroupsIO group name","example":"project-name"},"parent_service_uid":{"type":"string","description":"Parent primary service UID (automatically set for shared type services)","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"prefix":{"type":"string","description":"Email prefix (required for formation and shared, forbidden for primary)","example":"formation"},"project_name":{"type":"string","description":"Project name (read-only)","example":"Cloud Native Computing Foundation"},"project_slug":{"type":"string","description":"Project slug identifier","example":"cncf","format":"regexp","pattern":"^[a-z][a-z0-9_\\-]*[a-z0-9]$"},"project_uid":{"type":"string","description":"LFXv2 Project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"public":{"type":"boolean","description":"Whether the service is publicly accessible","default":false,"example":true},"status":{"type":"string","description":"Service status","example":"created"},"type":{"type":"string","description":"Service type","example":"primary","enum":["primary","formation","shared"]},"uid":{"type":"string","description":"Service UID -- unique identifier for the service","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"updated_at":{"type":"string","description":"The timestamp when the service was last updated (read-only)","example":"2023-06-20T14:45:30Z","format":"date-time"},"url":{"type":"string","description":"Service URL","example":"https://lists.project.org","format":"uri"},"writers":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]}},"description":"A representation of GroupsIO services with readonly attributes.","example":{"auditors":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}],"created_at":"2023-01-15T10:30:00Z","domain":"lists.project.org","global_owners":["admin@example.com"],"group_id":12345,"group_name":"project-name","parent_service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","prefix":"formation","project_name":"Cloud Native Computing Foundation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":true,"status":"created","type":"primary","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","updated_at":"2023-06-20T14:45:30Z","url":"https://lists.project.org","writers":[{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."},{"avatar":"http://legrosboyle.net/adolph_deckow","email":"noe@ebertbartoletti.biz","name":"Et neque dolor deserunt.","username":"Sapiente quo eveniet iusto sit aperiam neque."}]},"required":["type","project_uid"]},"InternalServerError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"example":{"message":"An internal server error occurred."},"required":["message"]},"NotFoundError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The resource was not found."}},"example":{"message":"The resource was not found."},"required":["message"]},"ServiceUnavailableError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"example":{"message":"The service is unavailable."},"required":["message"]},"UnauthorizedError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"Unauthorized access."}},"example":{"message":"Unauthorized access."},"required":["message"]},"UpdateGrpsioMailingListMemberRequestBody":{"type":"object","properties":{"delivery_mode":{"type":"string","description":"Email delivery mode","default":"normal","example":"none","enum":["normal","digest","none"]},"first_name":{"type":"string","description":"Member first name","example":"John","minLength":1,"maxLength":255},"job_title":{"type":"string","description":"Member job title","example":"Software Engineer","maxLength":255},"last_name":{"type":"string","description":"Member last name","example":"Doe","minLength":1,"maxLength":255},"mod_status":{"type":"string","description":"Moderation status","default":"none","example":"none","enum":["none","moderator","owner"]},"organization":{"type":"string","description":"Member organization","example":"Example Corp","maxLength":255},"username":{"type":"string","description":"Member username","example":"jdoe","maxLength":255}},"example":{"delivery_mode":"digest","first_name":"John","job_title":"Software Engineer","last_name":"Doe","mod_status":"moderator","organization":"Example Corp","username":"jdoe"}},"UpdateGrpsioMailingListRequestBody":{"type":"object","properties":{"audience_access":{"type":"string","description":"public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.","default":"public","example":"public","enum":["public","approval_required","invite_only"]},"committees":{"type":"array","items":{"$ref":"#/components/schemas/Committee"},"description":"Committees associated with this mailing list (OR logic for access control)","example":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}]},"description":{"type":"string","description":"Mailing list description (11-500 characters)","example":"Technical steering committee discussions","minLength":11,"maxLength":500},"group_id":{"type":"integer","description":"Mailing list group ID","example":12345,"format":"int64","minimum":0},"group_name":{"type":"string","description":"Mailing list group name","example":"technical-steering-committee","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$","minLength":3,"maxLength":34},"public":{"type":"boolean","description":"Whether the mailing list is publicly accessible","default":false,"example":false},"service_uid":{"type":"string","description":"Service UUID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"subject_tag":{"type":"string","description":"Subject tag prefix","example":"[TSC]","maxLength":50},"subscriber_count":{"type":"integer","description":"Number of subscribers in this mailing list (read-only, maintained by service)","example":42,"format":"int64","minimum":0},"title":{"type":"string","description":"Mailing list title","example":"Technical Steering Committee","minLength":5,"maxLength":100},"type":{"type":"string","description":"Mailing list type","example":"discussion_moderated","enum":["announcement","discussion_moderated","discussion_open"]}},"example":{"audience_access":"public","committees":[{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"},{"allowed_voting_statuses":["Voting Rep","Alternate Voting Rep"],"name":"Aliquid aliquid.","uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee"}],"description":"Technical steering committee discussions","group_id":12345,"group_name":"technical-steering-committee","public":false,"service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","subject_tag":"[TSC]","subscriber_count":42,"title":"Technical Steering Committee","type":"discussion_moderated"},"required":["group_name","public","type","description","title","service_uid"]},"UpdateGrpsioServiceRequestBody":{"type":"object","properties":{"domain":{"type":"string","description":"Service domain","example":"lists.project.org"},"global_owners":{"type":"array","items":{"type":"string","example":"khalid_fisher@kassulke.com","format":"email"},"description":"List of global owner email addresses (required for primary, forbidden for shared)","example":["admin@example.com"]},"group_id":{"type":"integer","description":"GroupsIO group ID","example":12345,"format":"int64"},"group_name":{"type":"string","description":"GroupsIO group name","example":"project-name"},"parent_service_uid":{"type":"string","description":"Parent primary service UID (automatically set for shared type services)","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"prefix":{"type":"string","description":"Email prefix (required for formation and shared, forbidden for primary)","example":"formation"},"project_slug":{"type":"string","description":"Project slug identifier","example":"cncf","format":"regexp","pattern":"^[a-z][a-z0-9_\\-]*[a-z0-9]$"},"project_uid":{"type":"string","description":"LFXv2 Project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"public":{"type":"boolean","description":"Whether the service is publicly accessible","default":false,"example":true},"status":{"type":"string","description":"Service status","example":"created"},"type":{"type":"string","description":"Service type","example":"primary","enum":["primary","formation","shared"]},"url":{"type":"string","description":"Service URL","example":"https://lists.project.org","format":"uri"}},"example":{"domain":"lists.project.org","global_owners":["admin@example.com"],"group_id":12345,"group_name":"project-name","parent_service_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","prefix":"formation","project_slug":"cncf","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","public":true,"status":"created","type":"primary","url":"https://lists.project.org"},"required":["type","project_uid"]},"UpdateGrpsioServiceSettingsRequestBody":{"type":"object","properties":{"auditors":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Auditor users who can audit this resource","example":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]},"writers":{"type":"array","items":{"$ref":"#/components/schemas/UserInfo"},"description":"Manager users who can edit/modify this resource","example":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]}},"example":{"auditors":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}],"writers":[{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."},{"avatar":"http://goldner.info/perry","email":"ernie.lueilwitz@altenwerth.info","name":"Eos non id at perspiciatis.","username":"Eaque quis possimus velit quasi quis occaecati."}]}},"UserInfo":{"type":"object","properties":{"avatar":{"type":"string","description":"The avatar URL of the user","example":"http://hilpert.name/brooklyn.thompson","format":"uri"},"email":{"type":"string","description":"The email address of the user","example":"beth.renner@ratke.biz","format":"email"},"name":{"type":"string","description":"The full name of the user","example":"Odit omnis eaque similique consectetur."},"username":{"type":"string","description":"The username/LFID of the user","example":"Ullam consequatur."}},"description":"User information including profile details.","example":{"avatar":"http://pacocha.name/karina.pollich","email":"carlo_pollich@gottlieb.org","name":"Magnam nisi.","username":"Cumque aut ipsum."}}},"securitySchemes":{"jwt_header_Authorization":{"type":"http","description":"Heimdall authorization","scheme":"bearer"}}},"tags":[{"name":"mailing-list","description":"The mailing list service manages mailing lists and services"}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Mailing List Service","description":"Service for proxying GroupsIO operations to the ITX API","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for mailing-list"}],"paths":{"/groupsio/checksubscriber":{"post":{"tags":["mailing-list"],"summary":"check-groupsio-subscriber mailing-list","description":"Check if an email address is subscribed to a GroupsIO subgroup","operationId":"mailing-list#check-groupsio-subscriber","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioCheckSubscriberRequest"},"example":{"email":"murl.o'keefe@gleason.biz","subgroup_id":"Voluptates animi totam."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioCheckSubscriberResponse"},"example":{"subscribed":true}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/services":{"get":{"tags":["mailing-list"],"summary":"list-groupsio-services mailing-list","description":"List GroupsIO services, optionally filtered by project UID","operationId":"mailing-list#list-groupsio-services","parameters":[{"name":"project_uid","in":"query","description":"LFX v2 project UID filter","allowEmptyValue":true,"schema":{"type":"string","description":"LFX v2 project UID filter","example":"bb7110a4-abb8-42c0-b362-6e80d8e3537e","format":"uuid"},"example":"894cde6c-df0d-4ac7-a4f6-b04158c4b9c4"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioServiceList"},"example":{"items":[{"created_at":"Voluptatem et ipsum eum.","domain":"Temporibus enim recusandae ipsam.","group_id":7850125793634127719,"id":"Provident quo quia ea debitis numquam.","prefix":"Perspiciatis nihil at.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Quasi consequatur.","type":"primary","updated_at":"Ipsam qui vel provident."},{"created_at":"Voluptatem et ipsum eum.","domain":"Temporibus enim recusandae ipsam.","group_id":7850125793634127719,"id":"Provident quo quia ea debitis numquam.","prefix":"Perspiciatis nihil at.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Quasi consequatur.","type":"primary","updated_at":"Ipsam qui vel provident."}],"total":8445256372544043781}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["mailing-list"],"summary":"create-groupsio-service mailing-list","description":"Create a GroupsIO service","operationId":"mailing-list#create-groupsio-service","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioServiceRequest"},"example":{"domain":"Molestias amet aut molestiae sequi quisquam.","group_id":7943249514373272995,"prefix":"Non fuga a est et.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Eum officiis voluptates.","type":"primary"}}}},"responses":{"201":{"description":"Created response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioService"},"example":{"created_at":"Architecto et accusantium rem dolores.","domain":"Et voluptatem consequatur tempora.","group_id":7669006925329481744,"id":"Qui itaque quaerat aut non vel temporibus.","prefix":"Saepe pariatur.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Enim quasi veritatis veritatis doloremque.","type":"primary","updated_at":"Nam non harum voluptatum."}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"message":"The resource already exists."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/services/_projects":{"get":{"tags":["mailing-list"],"summary":"get-groupsio-service-projects mailing-list","description":"Get projects that have GroupsIO services","operationId":"mailing-list#get-groupsio-service-projects","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioProjectsResponse"},"example":{"projects":["Aliquid distinctio mollitia.","Et aut iste quaerat sit porro molestias."]}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/services/find_parent":{"get":{"tags":["mailing-list"],"summary":"find-parent-groupsio-service mailing-list","description":"Find the parent GroupsIO service for a project","operationId":"mailing-list#find-parent-groupsio-service","parameters":[{"name":"project_uid","in":"query","description":"LFX v2 project UID","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"LFX v2 project UID","example":"37d64732-1a79-471c-95a3-7536daebfc37","format":"uuid"},"example":"cc066c58-6895-4611-bf3b-3c8917a87161"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioService"},"example":{"created_at":"Voluptate sit natus dolorem laudantium.","domain":"Consequatur voluptatem quae dolore qui quas ipsa.","group_id":6701442498276098951,"id":"Quae ut atque accusantium vero.","prefix":"Doloribus consequatur quibusdam error aliquam.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Perferendis itaque accusantium nesciunt.","type":"primary","updated_at":"Similique esse in aut explicabo."}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Parent service not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/services/{service_id}":{"delete":{"tags":["mailing-list"],"summary":"delete-groupsio-service mailing-list","description":"Delete a GroupsIO service","operationId":"mailing-list#delete-groupsio-service","parameters":[{"name":"service_id","in":"path","description":"Service ID","required":true,"schema":{"type":"string","description":"Service ID","example":"Similique est consequuntur quod occaecati ipsa nam."},"example":"Voluptate quia assumenda nisi."}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Service not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["mailing-list"],"summary":"get-groupsio-service mailing-list","description":"Get a GroupsIO service by ID","operationId":"mailing-list#get-groupsio-service","parameters":[{"name":"service_id","in":"path","description":"Service ID","required":true,"schema":{"type":"string","description":"Service ID","example":"Maxime unde laudantium."},"example":"Voluptatibus porro totam assumenda eum."}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioService"},"example":{"created_at":"Voluptatem itaque deleniti possimus distinctio magnam.","domain":"Aliquam consequatur.","group_id":305646027091442090,"id":"Facilis iure sed.","prefix":"Perferendis eum ut blanditiis.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Rerum quasi molestias.","type":"primary","updated_at":"Et suscipit aliquid ut ipsam suscipit numquam."}}}},"404":{"description":"NotFound: Service not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-groupsio-service mailing-list","description":"Update a GroupsIO service","operationId":"mailing-list#update-groupsio-service","parameters":[{"name":"service_id","in":"path","description":"Service ID","required":true,"schema":{"type":"string","description":"Service ID","example":"Est ex ut."},"example":"Ducimus odio magni quisquam sequi voluptatem quisquam."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGroupsioServiceRequestBody"},"example":{"domain":"Velit molestias molestiae fuga.","group_id":1590455940722915203,"prefix":"Ea nisi sapiente minus qui aut.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Aliquid dicta non.","type":"primary"}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioService"},"example":{"created_at":"Aut dolor repellendus tempore hic commodi.","domain":"Voluptas dolorem inventore ducimus expedita dolore.","group_id":7060729682158494453,"id":"Ea error unde.","prefix":"Est cum vero quibusdam nam molestias.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Est necessitatibus eligendi esse.","type":"primary","updated_at":"Aliquam molestiae voluptas neque velit."}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Service not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/subgroups":{"get":{"tags":["mailing-list"],"summary":"list-groupsio-subgroups mailing-list","description":"List GroupsIO subgroups, optionally filtered by project UID and/or committee UID","operationId":"mailing-list#list-groupsio-subgroups","parameters":[{"name":"project_uid","in":"query","description":"LFX v2 project UID filter","allowEmptyValue":true,"schema":{"type":"string","description":"LFX v2 project UID filter","example":"c6cb35e3-c184-43e4-b43c-aee8aec8ef25","format":"uuid"},"example":"d4bee84c-8ba6-4dc6-a277-b89c24ea2cc0"},{"name":"committee_uid","in":"query","description":"LFX v2 committee UID filter","allowEmptyValue":true,"schema":{"type":"string","description":"LFX v2 committee UID filter","example":"a0810552-13e5-4d31-9bb9-84221f9a48f8","format":"uuid"},"example":"d1f3c3db-f6c4-4f4c-8892-1aa29d2f63e0"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioSubgroupList"},"example":{"items":[{"audience_access":"Dolores sed officiis nihil ex.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Dolores recusandae amet blanditiis omnis qui optio.","description":"Voluptatibus autem expedita cumque magnam et.","group_id":3581801669178659495,"id":"Debitis eaque sed aut sequi veniam.","name":"Aut adipisci veritatis.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Natus et.","updated_at":"Saepe nihil quaerat exercitationem vero."},{"audience_access":"Dolores sed officiis nihil ex.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Dolores recusandae amet blanditiis omnis qui optio.","description":"Voluptatibus autem expedita cumque magnam et.","group_id":3581801669178659495,"id":"Debitis eaque sed aut sequi veniam.","name":"Aut adipisci veritatis.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Natus et.","updated_at":"Saepe nihil quaerat exercitationem vero."},{"audience_access":"Dolores sed officiis nihil ex.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Dolores recusandae amet blanditiis omnis qui optio.","description":"Voluptatibus autem expedita cumque magnam et.","group_id":3581801669178659495,"id":"Debitis eaque sed aut sequi veniam.","name":"Aut adipisci veritatis.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Natus et.","updated_at":"Saepe nihil quaerat exercitationem vero."},{"audience_access":"Dolores sed officiis nihil ex.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Dolores recusandae amet blanditiis omnis qui optio.","description":"Voluptatibus autem expedita cumque magnam et.","group_id":3581801669178659495,"id":"Debitis eaque sed aut sequi veniam.","name":"Aut adipisci veritatis.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Natus et.","updated_at":"Saepe nihil quaerat exercitationem vero."}],"total":6812148234041603013}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["mailing-list"],"summary":"create-groupsio-subgroup mailing-list","description":"Create a GroupsIO subgroup","operationId":"mailing-list#create-groupsio-subgroup","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioSubgroupRequest"},"example":{"audience_access":"Aut iure.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","description":"Voluptates mollitia et pariatur modi.","group_id":3213952105058865418,"name":"Non quo debitis animi itaque.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Atque vero asperiores iusto reiciendis sit asperiores."}}}},"responses":{"201":{"description":"Created response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioSubgroup"},"example":{"audience_access":"Quaerat et non sed velit eum rerum.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Pariatur quam quo quasi natus totam.","description":"Tenetur provident expedita.","group_id":7735175026921310208,"id":"Sed rerum voluptas est unde et ipsa.","name":"Vitae vel modi cum.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Aut id sed.","updated_at":"Sunt exercitationem amet animi dolore facilis ad."}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"409":{"description":"Conflict: Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"message":"The resource already exists."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/subgroups/count":{"get":{"tags":["mailing-list"],"summary":"get-groupsio-subgroup-count mailing-list","description":"Get count of GroupsIO subgroups for a project","operationId":"mailing-list#get-groupsio-subgroup-count","parameters":[{"name":"project_uid","in":"query","description":"LFX v2 project UID","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"LFX v2 project UID","example":"1fd03ad1-4d67-4597-b0e2-51feab065472","format":"uuid"},"example":"85e38c2b-ce4d-4eef-a098-bfc8a22bcd45"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioCount"},"example":{"count":4933079255212787388}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/subgroups/{subgroup_id}":{"delete":{"tags":["mailing-list"],"summary":"delete-groupsio-subgroup mailing-list","description":"Delete a GroupsIO subgroup","operationId":"mailing-list#delete-groupsio-subgroup","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"schema":{"type":"string","description":"Subgroup ID","example":"Sunt cupiditate."},"example":"Exercitationem aut repellendus sit suscipit placeat voluptates."}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Subgroup not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["mailing-list"],"summary":"get-groupsio-subgroup mailing-list","description":"Get a GroupsIO subgroup by ID","operationId":"mailing-list#get-groupsio-subgroup","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"schema":{"type":"string","description":"Subgroup ID","example":"Quisquam sit."},"example":"Ea et dolorum et qui rerum."}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioSubgroup"},"example":{"audience_access":"Similique voluptatibus quia id fugit.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Cupiditate tempore est et sit et.","description":"Itaque delectus expedita.","group_id":2881373843474038323,"id":"Ad voluptatibus voluptatem commodi qui dolores voluptas.","name":"Voluptate expedita recusandae ducimus sed quis sunt.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Eos laboriosam eaque aliquam exercitationem sint.","updated_at":"Asperiores rerum ex aspernatur."}}}},"404":{"description":"NotFound: Subgroup not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-groupsio-subgroup mailing-list","description":"Update a GroupsIO subgroup","operationId":"mailing-list#update-groupsio-subgroup","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"schema":{"type":"string","description":"Subgroup ID","example":"Est aut praesentium cupiditate."},"example":"Soluta ipsam quibusdam."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGroupsioSubgroupRequestBody"},"example":{"audience_access":"Quas tenetur eligendi facilis.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","description":"Qui inventore voluptatibus quas at suscipit.","group_id":3362199226119147318,"name":"Blanditiis consequatur molestiae odio quis enim et.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Tenetur voluptatum sed optio incidunt."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioSubgroup"},"example":{"audience_access":"Praesentium molestiae consequatur impedit esse mollitia voluptatem.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Impedit aut et enim ut.","description":"Consectetur repudiandae eaque adipisci optio vel hic.","group_id":5008031110446800685,"id":"Et perspiciatis id quia fuga.","name":"Repellendus sint libero quibusdam nulla cupiditate.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Modi libero quas rem.","updated_at":"Rerum at enim adipisci expedita et."}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Subgroup not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/subgroups/{subgroup_id}/invitemembers":{"post":{"tags":["mailing-list"],"summary":"invite-groupsio-members mailing-list","description":"Invite members to a GroupsIO subgroup by email","operationId":"mailing-list#invite-groupsio-members","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"schema":{"type":"string","description":"Subgroup ID","example":"Sequi vero."},"example":"Incidunt suscipit."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioInviteMembersRequest"},"example":{"emails":["Quo nihil quia blanditiis unde.","Qui commodi totam.","Voluptatem excepturi nam debitis quisquam voluptas velit.","Quibusdam voluptatum soluta sapiente error ut."]}}}},"responses":{"204":{"description":"No Content response."},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Subgroup not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/subgroups/{subgroup_id}/member_count":{"get":{"tags":["mailing-list"],"summary":"get-groupsio-subgroup-member-count mailing-list","description":"Get count of members in a GroupsIO subgroup","operationId":"mailing-list#get-groupsio-subgroup-member-count","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"schema":{"type":"string","description":"Subgroup ID","example":"Qui ut."},"example":"Ea omnis aliquam est saepe."}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioCount"},"example":{"count":7013755550356993833}}}},"404":{"description":"NotFound: Subgroup not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/subgroups/{subgroup_id}/members":{"get":{"tags":["mailing-list"],"summary":"list-groupsio-members mailing-list","description":"List members of a GroupsIO subgroup","operationId":"mailing-list#list-groupsio-members","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"schema":{"type":"string","description":"Subgroup ID","example":"Aut similique."},"example":"Quae eaque est facere."}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioMemberList"},"example":{"items":[{"created_at":"Eos sint ea provident.","delivery_mode":"Quae dolores.","email":"casey@littel.org","first_name":"Cumque sed eveniet reprehenderit.","id":"Ullam exercitationem quisquam nostrum nihil culpa.","last_name":"Ut atque voluptatibus.","mod_status":"Aut tempora.","name":"Qui et voluptates in perspiciatis non.","status":"Natus nesciunt omnis et.","subgroup_id":"Et repellat voluptates reiciendis.","updated_at":"Reiciendis consequatur laborum quidem voluptatum et voluptatibus."},{"created_at":"Eos sint ea provident.","delivery_mode":"Quae dolores.","email":"casey@littel.org","first_name":"Cumque sed eveniet reprehenderit.","id":"Ullam exercitationem quisquam nostrum nihil culpa.","last_name":"Ut atque voluptatibus.","mod_status":"Aut tempora.","name":"Qui et voluptates in perspiciatis non.","status":"Natus nesciunt omnis et.","subgroup_id":"Et repellat voluptates reiciendis.","updated_at":"Reiciendis consequatur laborum quidem voluptatum et voluptatibus."},{"created_at":"Eos sint ea provident.","delivery_mode":"Quae dolores.","email":"casey@littel.org","first_name":"Cumque sed eveniet reprehenderit.","id":"Ullam exercitationem quisquam nostrum nihil culpa.","last_name":"Ut atque voluptatibus.","mod_status":"Aut tempora.","name":"Qui et voluptates in perspiciatis non.","status":"Natus nesciunt omnis et.","subgroup_id":"Et repellat voluptates reiciendis.","updated_at":"Reiciendis consequatur laborum quidem voluptatum et voluptatibus."}],"total":5232052381345216570}}}},"404":{"description":"NotFound: Subgroup not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["mailing-list"],"summary":"add-groupsio-member mailing-list","description":"Add a member to a GroupsIO subgroup","operationId":"mailing-list#add-groupsio-member","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"schema":{"type":"string","description":"Subgroup ID","example":"Ab error nihil amet."},"example":"Velit quasi reprehenderit impedit cum."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddGroupsioMemberRequestBody"},"example":{"delivery_mode":"digest","email":"talia@runolfsdottir.org","mod_status":"moderator","name":"Quidem illum aliquam ut."}}}},"responses":{"201":{"description":"Created response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioMember"},"example":{"created_at":"Fugit aut non eos.","delivery_mode":"Nulla consequatur ipsam iusto sed voluptate.","email":"syble@beer.net","first_name":"Autem tempora exercitationem iusto aut et.","id":"Non qui suscipit sit voluptas minima.","last_name":"Ducimus deserunt vitae at quia.","mod_status":"Alias voluptas illum ipsum.","name":"Dolores in minima autem excepturi.","status":"Inventore soluta aut suscipit non.","subgroup_id":"Totam repellat ut esse aut earum architecto.","updated_at":"Id quis et quibusdam et."}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Subgroup not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"409":{"description":"Conflict: Member already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictError"},"example":{"message":"The resource already exists."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/groupsio/subgroups/{subgroup_id}/members/{member_id}":{"delete":{"tags":["mailing-list"],"summary":"delete-groupsio-member mailing-list","description":"Delete a member from a GroupsIO subgroup","operationId":"mailing-list#delete-groupsio-member","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"schema":{"type":"string","description":"Subgroup ID","example":"Eius optio dolores voluptates id magnam."},"example":"Sint blanditiis natus deserunt veritatis molestiae."},{"name":"member_id","in":"path","description":"Member ID","required":true,"schema":{"type":"string","description":"Member ID","example":"At eius."},"example":"Id vel rem a omnis amet laboriosam."}],"responses":{"204":{"description":"No Content response."},"404":{"description":"NotFound: Member not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"get":{"tags":["mailing-list"],"summary":"get-groupsio-member mailing-list","description":"Get a member of a GroupsIO subgroup by ID","operationId":"mailing-list#get-groupsio-member","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"schema":{"type":"string","description":"Subgroup ID","example":"Id quam eligendi necessitatibus optio velit."},"example":"Qui voluptas."},{"name":"member_id","in":"path","description":"Member ID","required":true,"schema":{"type":"string","description":"Member ID","example":"Temporibus eius enim magni et."},"example":"Qui eveniet ex."}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioMember"},"example":{"created_at":"Quas excepturi maxime.","delivery_mode":"Id ut sed nihil suscipit laudantium.","email":"celine@smitham.biz","first_name":"Et quia facere deleniti.","id":"Enim fugiat.","last_name":"Tenetur illum alias.","mod_status":"In nostrum id ut.","name":"Ab qui tempore beatae atque ab repudiandae.","status":"Doloremque consequatur quo illo voluptatem ipsam.","subgroup_id":"Sequi ut assumenda omnis iusto.","updated_at":"Corrupti aut."}}}},"404":{"description":"NotFound: Member not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]},"put":{"tags":["mailing-list"],"summary":"update-groupsio-member mailing-list","description":"Update a member of a GroupsIO subgroup","operationId":"mailing-list#update-groupsio-member","parameters":[{"name":"subgroup_id","in":"path","description":"Subgroup ID","required":true,"schema":{"type":"string","description":"Subgroup ID","example":"Sed facilis sit aut rerum."},"example":"Ullam doloribus ab vitae illum harum."},{"name":"member_id","in":"path","description":"Member ID","required":true,"schema":{"type":"string","description":"Member ID","example":"Animi velit."},"example":"Sunt quidem distinctio cumque facilis rem."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddGroupsioMemberRequestBody"},"example":{"delivery_mode":"normal","email":"katelynn_rempel@bruen.info","mod_status":"none","name":"Inventore suscipit eveniet ipsum aut et."}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupsioMember"},"example":{"created_at":"A soluta.","delivery_mode":"Veritatis ea aut eos recusandae architecto.","email":"daphnee.schinner@raynor.net","first_name":"Velit consequatur magni et dolorem quasi.","id":"Commodi ut similique provident saepe rerum saepe.","last_name":"Laudantium numquam sint.","mod_status":"Ea et.","name":"Accusamus labore nobis cum.","status":"Quisquam consequuntur tenetur eius assumenda.","subgroup_id":"Qui rerum.","updated_at":"Quis aspernatur."}}}},"400":{"description":"BadRequest: Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"},"example":{"message":"The request was invalid."}}}},"404":{"description":"NotFound: Member not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"},"example":{"message":"The resource was not found."}}}},"500":{"description":"InternalServerError: Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"example":{"message":"An internal server error occurred."}}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}},"security":[{"jwt_header_Authorization":[]}]}},"/livez":{"get":{"tags":["mailing-list"],"summary":"livez mailing-list","description":"Check if the service is alive.","operationId":"mailing-list#livez","responses":{"200":{"description":"OK response.","content":{"text/plain":{"schema":{"type":"string","example":"OK","format":"binary"},"example":"OK"}}}}}},"/openapi.json":{"get":{"tags":["mailing-list"],"summary":"Download gen/http/openapi3.json","operationId":"mailing-list#/openapi.json","responses":{"200":{"description":"File downloaded"}}}},"/readyz":{"get":{"tags":["mailing-list"],"summary":"readyz mailing-list","description":"Check if the service is able to take inbound requests.","operationId":"mailing-list#readyz","responses":{"200":{"description":"OK response.","content":{"text/plain":{"schema":{"type":"string","example":"OK","format":"binary"},"example":"OK"}}},"503":{"description":"ServiceUnavailable: Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUnavailableError"},"example":{"message":"The service is unavailable."}}}}}}}},"components":{"schemas":{"AddGroupsioMemberRequestBody":{"type":"object","properties":{"delivery_mode":{"type":"string","description":"Email delivery mode","example":"digest","enum":["normal","digest","none"]},"email":{"type":"string","description":"Member email address","example":"hillard@ferrylabadie.com","format":"email"},"mod_status":{"type":"string","description":"Moderation status","example":"owner","enum":["none","moderator","owner"]},"name":{"type":"string","description":"Member display name","example":"Dolorem cumque."}},"example":{"delivery_mode":"none","email":"hayden@stark.biz","mod_status":"owner","name":"Aut praesentium quasi nobis et suscipit blanditiis."}},"BadRequestError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The request was invalid."}},"example":{"message":"The request was invalid."},"required":["message"]},"ConflictError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The resource already exists."}},"example":{"message":"The resource already exists."},"required":["message"]},"GroupsioCheckSubscriberRequest":{"type":"object","properties":{"email":{"type":"string","description":"Email address to check","example":"helmer@friesen.net","format":"email"},"subgroup_id":{"type":"string","description":"Subgroup ID","example":"Quia nam sed."}},"description":"Request body for checking if an email is subscribed","example":{"email":"jovan_baumbach@effertz.net","subgroup_id":"Velit id eligendi est perspiciatis consequatur voluptas."},"required":["email","subgroup_id"]},"GroupsioCheckSubscriberResponse":{"type":"object","properties":{"subscribed":{"type":"boolean","description":"Whether the email is subscribed","example":false}},"description":"Response for check subscriber request","example":{"subscribed":true},"required":["subscribed"]},"GroupsioCount":{"type":"object","properties":{"count":{"type":"integer","description":"Count value","example":3518554859839344188,"format":"int64"}},"description":"Count response","example":{"count":1791894229755420167},"required":["count"]},"GroupsioInviteMembersRequest":{"type":"object","properties":{"emails":{"type":"array","items":{"type":"string","example":"Voluptas voluptatum occaecati iste ipsam."},"description":"Email addresses to invite","example":["Iusto et debitis minus porro doloremque.","Autem dolores.","Dolorem earum deserunt facilis.","Quae corporis ut sit dolore commodi."]}},"description":"Request body for inviting members to a GroupsIO subgroup","example":{"emails":["Maxime saepe ut aliquid repudiandae aut architecto.","Repellendus nostrum repellat harum aut.","Optio consequatur."]},"required":["emails"]},"GroupsioMember":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","example":"Et voluptates commodi cupiditate asperiores asperiores."},"delivery_mode":{"type":"string","description":"Email delivery mode","example":"Dolore omnis corrupti magni adipisci quia omnis."},"email":{"type":"string","description":"Member email address","example":"brennon@boyer.info","format":"email"},"first_name":{"type":"string","description":"Member first name","example":"Inventore quis."},"id":{"type":"string","description":"Member ID","example":"Aut quas."},"last_name":{"type":"string","description":"Member last name","example":"Velit qui."},"mod_status":{"type":"string","description":"Moderation status","example":"Neque dignissimos minus maiores voluptates est libero."},"name":{"type":"string","description":"Member display name","example":"Aut accusantium sint."},"status":{"type":"string","description":"Member status","example":"Magni illo minus."},"subgroup_id":{"type":"string","description":"Subgroup ID","example":"A fugit temporibus incidunt quia ut."},"updated_at":{"type":"string","description":"Last update timestamp","example":"Tempora delectus cumque est."}},"description":"A member of a GroupsIO subgroup","example":{"created_at":"Perspiciatis ipsam debitis natus qui voluptatem eum.","delivery_mode":"Non soluta.","email":"ramona@bogandibbert.name","first_name":"Alias fugit quod velit ab.","id":"Possimus possimus vel quos eum.","last_name":"Ea omnis dolores et recusandae adipisci quos.","mod_status":"Ut neque.","name":"Ab voluptas error placeat explicabo facere saepe.","status":"Illum quia ea et deleniti maiores.","subgroup_id":"Eum adipisci hic.","updated_at":"Consequatur fugiat a dolorem sed."}},"GroupsioMemberList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/GroupsioMember"},"description":"List of members","example":[{"created_at":"Explicabo saepe hic exercitationem.","delivery_mode":"Sequi hic.","email":"natalia@harris.biz","first_name":"Ut et quia est non rem.","id":"Ad dolorem sit molestias aliquam sit.","last_name":"Aut est facere laborum enim.","mod_status":"Quidem impedit voluptas quia rem.","name":"Ad voluptatum magni fugit similique cumque.","status":"Non atque ut quis ut tempore placeat.","subgroup_id":"Sint praesentium.","updated_at":"Et rem."},{"created_at":"Explicabo saepe hic exercitationem.","delivery_mode":"Sequi hic.","email":"natalia@harris.biz","first_name":"Ut et quia est non rem.","id":"Ad dolorem sit molestias aliquam sit.","last_name":"Aut est facere laborum enim.","mod_status":"Quidem impedit voluptas quia rem.","name":"Ad voluptatum magni fugit similique cumque.","status":"Non atque ut quis ut tempore placeat.","subgroup_id":"Sint praesentium.","updated_at":"Et rem."}]},"total":{"type":"integer","description":"Total count","example":4598526804261273058,"format":"int64"}},"description":"List of GroupsIO members","example":{"items":[{"created_at":"Explicabo saepe hic exercitationem.","delivery_mode":"Sequi hic.","email":"natalia@harris.biz","first_name":"Ut et quia est non rem.","id":"Ad dolorem sit molestias aliquam sit.","last_name":"Aut est facere laborum enim.","mod_status":"Quidem impedit voluptas quia rem.","name":"Ad voluptatum magni fugit similique cumque.","status":"Non atque ut quis ut tempore placeat.","subgroup_id":"Sint praesentium.","updated_at":"Et rem."},{"created_at":"Explicabo saepe hic exercitationem.","delivery_mode":"Sequi hic.","email":"natalia@harris.biz","first_name":"Ut et quia est non rem.","id":"Ad dolorem sit molestias aliquam sit.","last_name":"Aut est facere laborum enim.","mod_status":"Quidem impedit voluptas quia rem.","name":"Ad voluptatum magni fugit similique cumque.","status":"Non atque ut quis ut tempore placeat.","subgroup_id":"Sint praesentium.","updated_at":"Et rem."},{"created_at":"Explicabo saepe hic exercitationem.","delivery_mode":"Sequi hic.","email":"natalia@harris.biz","first_name":"Ut et quia est non rem.","id":"Ad dolorem sit molestias aliquam sit.","last_name":"Aut est facere laborum enim.","mod_status":"Quidem impedit voluptas quia rem.","name":"Ad voluptatum magni fugit similique cumque.","status":"Non atque ut quis ut tempore placeat.","subgroup_id":"Sint praesentium.","updated_at":"Et rem."}],"total":9046657099964780568}},"GroupsioMemberRequest":{"type":"object","properties":{"delivery_mode":{"type":"string","description":"Email delivery mode","example":"digest","enum":["normal","digest","none"]},"email":{"type":"string","description":"Member email address","example":"cyrus_becker@breitenberghaley.com","format":"email"},"mod_status":{"type":"string","description":"Moderation status","example":"moderator","enum":["none","moderator","owner"]},"name":{"type":"string","description":"Member display name","example":"Placeat alias qui non labore."}},"description":"Request body for adding or updating a GroupsIO member","example":{"delivery_mode":"normal","email":"conrad.cassin@borer.com","mod_status":"none","name":"Adipisci quaerat molestiae voluptas itaque porro facere."}},"GroupsioProjectsResponse":{"type":"object","properties":{"projects":{"type":"array","items":{"type":"string","example":"Error cupiditate ut velit culpa delectus dignissimos."},"description":"List of project identifiers","example":["Sunt ut error architecto ea.","Voluptas vitae quae debitis voluptas molestias."]}},"description":"Projects that have GroupsIO services","example":{"projects":["Perferendis ullam.","Perspiciatis aspernatur minima aperiam corporis."]}},"GroupsioService":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","example":"Ratione autem fugit optio."},"domain":{"type":"string","description":"Service domain","example":"Qui natus ducimus similique fugiat."},"group_id":{"type":"integer","description":"GroupsIO group ID","example":3792419788170046764,"format":"int64"},"id":{"type":"string","description":"Service ID","example":"Ea laborum maiores."},"prefix":{"type":"string","description":"Email prefix","example":"Qui culpa neque est."},"project_uid":{"type":"string","description":"LFX v2 project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"status":{"type":"string","description":"Service status","example":"Qui tempore id quisquam illum."},"type":{"type":"string","description":"Service type","example":"primary"},"updated_at":{"type":"string","description":"Last update timestamp","example":"Sequi voluptatem."}},"description":"A GroupsIO service managed via ITX","example":{"created_at":"Iusto explicabo nihil.","domain":"Aliquam voluptatem quia et praesentium quo assumenda.","group_id":5494386556706073005,"id":"Nam facere deleniti doloribus.","prefix":"Consequatur repudiandae ipsam hic.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Laboriosam repellendus ut.","type":"primary","updated_at":"Possimus labore consequatur sunt voluptatibus beatae."}},"GroupsioServiceList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/GroupsioService"},"description":"List of services","example":[{"created_at":"Sunt laborum ut id laboriosam aut aut.","domain":"Sapiente quo eveniet iusto sit aperiam neque.","group_id":123850831553145715,"id":"Aliquam esse odit.","prefix":"Minima hic repellendus consequatur adipisci.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Laudantium qui.","type":"primary","updated_at":"Sequi vero sit."},{"created_at":"Sunt laborum ut id laboriosam aut aut.","domain":"Sapiente quo eveniet iusto sit aperiam neque.","group_id":123850831553145715,"id":"Aliquam esse odit.","prefix":"Minima hic repellendus consequatur adipisci.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Laudantium qui.","type":"primary","updated_at":"Sequi vero sit."},{"created_at":"Sunt laborum ut id laboriosam aut aut.","domain":"Sapiente quo eveniet iusto sit aperiam neque.","group_id":123850831553145715,"id":"Aliquam esse odit.","prefix":"Minima hic repellendus consequatur adipisci.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Laudantium qui.","type":"primary","updated_at":"Sequi vero sit."}]},"total":{"type":"integer","description":"Total count","example":6020740068368848590,"format":"int64"}},"description":"List of GroupsIO services","example":{"items":[{"created_at":"Sunt laborum ut id laboriosam aut aut.","domain":"Sapiente quo eveniet iusto sit aperiam neque.","group_id":123850831553145715,"id":"Aliquam esse odit.","prefix":"Minima hic repellendus consequatur adipisci.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Laudantium qui.","type":"primary","updated_at":"Sequi vero sit."},{"created_at":"Sunt laborum ut id laboriosam aut aut.","domain":"Sapiente quo eveniet iusto sit aperiam neque.","group_id":123850831553145715,"id":"Aliquam esse odit.","prefix":"Minima hic repellendus consequatur adipisci.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Laudantium qui.","type":"primary","updated_at":"Sequi vero sit."},{"created_at":"Sunt laborum ut id laboriosam aut aut.","domain":"Sapiente quo eveniet iusto sit aperiam neque.","group_id":123850831553145715,"id":"Aliquam esse odit.","prefix":"Minima hic repellendus consequatur adipisci.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Laudantium qui.","type":"primary","updated_at":"Sequi vero sit."},{"created_at":"Sunt laborum ut id laboriosam aut aut.","domain":"Sapiente quo eveniet iusto sit aperiam neque.","group_id":123850831553145715,"id":"Aliquam esse odit.","prefix":"Minima hic repellendus consequatur adipisci.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Laudantium qui.","type":"primary","updated_at":"Sequi vero sit."}],"total":8640143773663907109}},"GroupsioServiceRequest":{"type":"object","properties":{"domain":{"type":"string","description":"Service domain","example":"Commodi et."},"group_id":{"type":"integer","description":"GroupsIO group ID","example":898470202368092092,"format":"int64"},"prefix":{"type":"string","description":"Email prefix","example":"Qui maxime ad."},"project_uid":{"type":"string","description":"LFX v2 project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"status":{"type":"string","description":"Service status","example":"Soluta sed laborum maiores ipsa."},"type":{"type":"string","description":"Service type","example":"primary","enum":["primary","formation","shared"]}},"description":"Request body for creating or updating a GroupsIO service","example":{"domain":"Amet qui eligendi.","group_id":1558129810533357763,"prefix":"Magni provident laborum voluptatem.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Iusto recusandae.","type":"primary"}},"GroupsioSubgroup":{"type":"object","properties":{"audience_access":{"type":"string","description":"Audience access setting","example":"Et sint laudantium officiis."},"committee_uid":{"type":"string","description":"LFX v2 committee UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"created_at":{"type":"string","description":"Creation timestamp","example":"Est laborum animi cum molestiae harum dicta."},"description":{"type":"string","description":"Subgroup description","example":"Ad numquam porro enim in."},"group_id":{"type":"integer","description":"GroupsIO group ID","example":2112860877170077964,"format":"int64"},"id":{"type":"string","description":"Subgroup ID","example":"Possimus ut ullam aliquid ad commodi."},"name":{"type":"string","description":"Subgroup name","example":"Quisquam repudiandae hic excepturi est."},"project_uid":{"type":"string","description":"LFX v2 project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"type":{"type":"string","description":"Subgroup type","example":"Animi assumenda incidunt ut dolores dolores."},"updated_at":{"type":"string","description":"Last update timestamp","example":"Possimus esse id recusandae cum praesentium itaque."}},"description":"A GroupsIO subgroup (mailing list) managed via ITX","example":{"audience_access":"Qui nostrum aut sit.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Iste ut odit nisi.","description":"Voluptatem unde saepe reiciendis nesciunt eos necessitatibus.","group_id":8636928196036809970,"id":"Et ut et et ut unde.","name":"Ut dolorum velit quisquam similique assumenda.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Laudantium voluptas aliquid labore et nobis ratione.","updated_at":"Consectetur a similique aspernatur velit omnis."}},"GroupsioSubgroupList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/GroupsioSubgroup"},"description":"List of subgroups","example":[{"audience_access":"Est dolore.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Et et qui dolorum.","description":"Vero amet.","group_id":346864561935847351,"id":"Omnis sed earum rerum corporis quae quo.","name":"Minus aspernatur.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Asperiores suscipit quia.","updated_at":"Alias amet aspernatur ut nihil."},{"audience_access":"Est dolore.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Et et qui dolorum.","description":"Vero amet.","group_id":346864561935847351,"id":"Omnis sed earum rerum corporis quae quo.","name":"Minus aspernatur.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Asperiores suscipit quia.","updated_at":"Alias amet aspernatur ut nihil."}]},"total":{"type":"integer","description":"Total count","example":4059489510681644250,"format":"int64"}},"description":"List of GroupsIO subgroups","example":{"items":[{"audience_access":"Est dolore.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Et et qui dolorum.","description":"Vero amet.","group_id":346864561935847351,"id":"Omnis sed earum rerum corporis quae quo.","name":"Minus aspernatur.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Asperiores suscipit quia.","updated_at":"Alias amet aspernatur ut nihil."},{"audience_access":"Est dolore.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Et et qui dolorum.","description":"Vero amet.","group_id":346864561935847351,"id":"Omnis sed earum rerum corporis quae quo.","name":"Minus aspernatur.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Asperiores suscipit quia.","updated_at":"Alias amet aspernatur ut nihil."},{"audience_access":"Est dolore.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Et et qui dolorum.","description":"Vero amet.","group_id":346864561935847351,"id":"Omnis sed earum rerum corporis quae quo.","name":"Minus aspernatur.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Asperiores suscipit quia.","updated_at":"Alias amet aspernatur ut nihil."},{"audience_access":"Est dolore.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","created_at":"Et et qui dolorum.","description":"Vero amet.","group_id":346864561935847351,"id":"Omnis sed earum rerum corporis quae quo.","name":"Minus aspernatur.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Asperiores suscipit quia.","updated_at":"Alias amet aspernatur ut nihil."}],"total":710387942492653757}},"GroupsioSubgroupRequest":{"type":"object","properties":{"audience_access":{"type":"string","description":"Audience access setting","example":"Et quia architecto molestiae assumenda."},"committee_uid":{"type":"string","description":"LFX v2 committee UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"description":{"type":"string","description":"Subgroup description","example":"Provident sit commodi autem incidunt enim."},"group_id":{"type":"integer","description":"GroupsIO group ID","example":5743989652491219245,"format":"int64"},"name":{"type":"string","description":"Subgroup name","example":"Reiciendis quisquam quisquam autem quisquam qui impedit."},"project_uid":{"type":"string","description":"LFX v2 project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"type":{"type":"string","description":"Subgroup type","example":"Quia aliquid rerum numquam."}},"description":"Request body for creating or updating a GroupsIO subgroup","example":{"audience_access":"Earum in et provident et nulla facilis.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","description":"Voluptas sed sapiente autem.","group_id":7498979218594870713,"name":"Autem quo voluptatum ut laboriosam qui voluptatibus.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Est laboriosam non."}},"InternalServerError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"An internal server error occurred."}},"example":{"message":"An internal server error occurred."},"required":["message"]},"NotFoundError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The resource was not found."}},"example":{"message":"The resource was not found."},"required":["message"]},"ServiceUnavailableError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"The service is unavailable."}},"example":{"message":"The service is unavailable."},"required":["message"]},"UnauthorizedError":{"type":"object","properties":{"message":{"type":"string","description":"Error message","example":"Unauthorized access."}},"example":{"message":"Unauthorized access."},"required":["message"]},"UpdateGroupsioServiceRequestBody":{"type":"object","properties":{"domain":{"type":"string","description":"Service domain","example":"Vel illum accusantium voluptatem voluptates et ex."},"group_id":{"type":"integer","description":"GroupsIO group ID","example":7845011941832544071,"format":"int64"},"prefix":{"type":"string","description":"Email prefix","example":"Omnis atque maxime nam dolorum."},"project_uid":{"type":"string","description":"LFX v2 project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"status":{"type":"string","description":"Service status","example":"Odit delectus."},"type":{"type":"string","description":"Service type","example":"primary","enum":["primary","formation","shared"]}},"example":{"domain":"Exercitationem laboriosam ipsum.","group_id":3468863149838709088,"prefix":"Eos error qui.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","status":"Qui nihil.","type":"primary"}},"UpdateGroupsioSubgroupRequestBody":{"type":"object","properties":{"audience_access":{"type":"string","description":"Audience access setting","example":"Ducimus sed eveniet sed quos et alias."},"committee_uid":{"type":"string","description":"LFX v2 committee UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"description":{"type":"string","description":"Subgroup description","example":"Vitae ducimus."},"group_id":{"type":"integer","description":"GroupsIO group ID","example":3156761412527126577,"format":"int64"},"name":{"type":"string","description":"Subgroup name","example":"Qui ex nihil quasi occaecati magni."},"project_uid":{"type":"string","description":"LFX v2 project UID","example":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","format":"uuid"},"type":{"type":"string","description":"Subgroup type","example":"A perspiciatis rerum enim incidunt repellat."}},"example":{"audience_access":"Consectetur ducimus corrupti aut itaque.","committee_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","description":"Reiciendis quis eaque delectus voluptas aperiam.","group_id":228014870381650192,"name":"Corporis eum molestiae.","project_uid":"7cad5a8d-19d0-41a4-81a6-043453daf9ee","type":"Iure aut sunt."}}},"securitySchemes":{"jwt_header_Authorization":{"type":"http","description":"Heimdall authorization","scheme":"bearer"}}},"tags":[{"name":"mailing-list","description":"The mailing list service proxies GroupsIO operations to the ITX API"}]} \ No newline at end of file diff --git a/gen/http/openapi3.yaml b/gen/http/openapi3.yaml index 8d83c79..6af719b 100644 --- a/gen/http/openapi3.yaml +++ b/gen/http/openapi3.yaml @@ -1,189 +1,45 @@ openapi: 3.0.3 info: title: Mailing List Service - description: Service for managing mailing lists in LFX + description: Service for proxying GroupsIO operations to the ITX API version: 0.0.1 servers: - url: http://localhost:80 description: Default server for mailing-list paths: - /groupsio/mailing-lists: + /groupsio/checksubscriber: post: tags: - mailing-list - summary: create-grpsio-mailing-list mailing-list - description: Create GroupsIO mailing list/subgroup with comprehensive validation - operationId: mailing-list#create-grpsio-mailing-list - parameters: - - name: v - in: query - description: Version of the API - allowEmptyValue: true - required: true - schema: - type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" + summary: check-groupsio-subscriber mailing-list + description: Check if an email address is subscribed to a GroupsIO subgroup + operationId: mailing-list#check-groupsio-subscriber requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CreateGrpsioMailingListRequestBody' + $ref: '#/components/schemas/GroupsioCheckSubscriberRequest' example: - audience_access: public - auditors: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - committees: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - description: Technical steering committee discussions - group_id: 12345 - group_name: technical-steering-committee - public: false - service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - subject_tag: '[TSC]' - subscriber_count: 42 - title: Technical Steering Committee - type: discussion_moderated - writers: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + email: murl.o'keefe@gleason.biz + subgroup_id: Voluptates animi totam. responses: - "201": - description: Created response. + "200": + description: OK response. content: application/json: schema: - $ref: '#/components/schemas/GrpsIoMailingListFull' + $ref: '#/components/schemas/GroupsioCheckSubscriberResponse' example: - audience_access: public - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - committees: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - created_at: "2023-01-15T10:30:00Z" - description: Technical steering committee discussions - group_id: 12345 - group_name: technical-steering-committee - project_name: Cloud Native Computing Foundation - project_slug: cncf - project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: false - service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - subject_tag: '[TSC]' - subscriber_count: 42 - title: Technical Steering Committee - type: discussion_moderated - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. + subscribed: true "400": - description: 'BadRequest: Bad request - Invalid data, missing required fields, or validation failures' + description: 'BadRequest: Bad request' content: application/json: schema: $ref: '#/components/schemas/BadRequestError' example: message: The request was invalid. - "404": - description: 'NotFound: Parent service not found or committee not found' - content: - application/json: - schema: - $ref: '#/components/schemas/NotFoundError' - example: - message: The resource was not found. - "409": - description: 'Conflict: Mailing list with same name already exists' - content: - application/json: - schema: - $ref: '#/components/schemas/ConflictError' - example: - message: The resource already exists. "500": description: 'InternalServerError: Internal server error' content: @@ -202,47 +58,52 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /groupsio/mailing-lists/{uid}: - delete: + /groupsio/services: + get: tags: - mailing-list - summary: delete-grpsio-mailing-list mailing-list - description: Delete GroupsIO mailing list - operationId: mailing-list#delete-grpsio-mailing-list + summary: list-groupsio-services mailing-list + description: List GroupsIO services, optionally filtered by project UID + operationId: mailing-list#list-groupsio-services parameters: - - name: v + - name: project_uid in: query - description: Version of the API + description: LFX v2 project UID filter allowEmptyValue: true schema: type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" - - name: uid - in: path - description: Mailing list UID -- unique identifier for the mailing list - required: true - schema: - type: string - description: Mailing list UID -- unique identifier for the mailing list - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + description: LFX v2 project UID filter + example: bb7110a4-abb8-42c0-b362-6e80d8e3537e format: uuid - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - name: If-Match - in: header - description: If-Match header value for conditional requests - allowEmptyValue: true - schema: - type: string - description: If-Match header value for conditional requests - example: "123" - example: "123" + example: 894cde6c-df0d-4ac7-a4f6-b04158c4b9c4 responses: - "204": - description: No Content response. + "200": + description: OK response. + content: + application/json: + schema: + $ref: '#/components/schemas/GroupsioServiceList' + example: + items: + - created_at: Voluptatem et ipsum eum. + domain: Temporibus enim recusandae ipsam. + group_id: 7850125793634127719 + id: Provident quo quia ea debitis numquam. + prefix: Perspiciatis nihil at. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Quasi consequatur. + type: primary + updated_at: Ipsam qui vel provident. + - created_at: Voluptatem et ipsum eum. + domain: Temporibus enim recusandae ipsam. + group_id: 7850125793634127719 + id: Provident quo quia ea debitis numquam. + prefix: Perspiciatis nihil at. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Quasi consequatur. + type: primary + updated_at: Ipsam qui vel provident. + total: 8445256372544043781 "400": description: 'BadRequest: Bad request' content: @@ -251,16 +112,70 @@ paths: $ref: '#/components/schemas/BadRequestError' example: message: The request was invalid. - "404": - description: 'NotFound: Mailing list not found' + "500": + description: 'InternalServerError: Internal server error' content: application/json: schema: - $ref: '#/components/schemas/NotFoundError' + $ref: '#/components/schemas/InternalServerError' example: - message: The resource was not found. + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ServiceUnavailableError' + example: + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + post: + tags: + - mailing-list + summary: create-groupsio-service mailing-list + description: Create a GroupsIO service + operationId: mailing-list#create-groupsio-service + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GroupsioServiceRequest' + example: + domain: Molestias amet aut molestiae sequi quisquam. + group_id: 7943249514373272995 + prefix: Non fuga a est et. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Eum officiis voluptates. + type: primary + responses: + "201": + description: Created response. + content: + application/json: + schema: + $ref: '#/components/schemas/GroupsioService' + example: + created_at: Architecto et accusantium rem dolores. + domain: Et voluptatem consequatur tempora. + group_id: 7669006925329481744 + id: Qui itaque quaerat aut non vel temporibus. + prefix: Saepe pariatur. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Enim quasi veritatis veritatis doloremque. + type: primary + updated_at: Nam non harum voluptatum. + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + message: The request was invalid. "409": - description: 'Conflict: Conflict - ETag mismatch or deletion not allowed' + description: 'Conflict: Conflict' content: application/json: schema: @@ -285,98 +200,64 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] + /groupsio/services/_projects: get: tags: - mailing-list - summary: get-grpsio-mailing-list mailing-list - description: Get GroupsIO mailing list details by UID - operationId: mailing-list#get-grpsio-mailing-list - parameters: - - name: v - in: query - description: Version of the API - allowEmptyValue: true - required: true - schema: - type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" - - name: uid - in: path - description: Mailing list UID -- unique identifier for the mailing list - required: true - schema: - type: string - description: Mailing list UID -- unique identifier for the mailing list - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + summary: get-groupsio-service-projects mailing-list + description: Get projects that have GroupsIO services + operationId: mailing-list#get-groupsio-service-projects responses: "200": description: OK response. - headers: - ETag: - description: ETag header value + content: + application/json: schema: - type: string - description: ETag header value - example: "123" - example: "123" + $ref: '#/components/schemas/GroupsioProjectsResponse' + example: + projects: + - Aliquid distinctio mollitia. + - Et aut iste quaerat sit porro molestias. + "500": + description: 'InternalServerError: Internal server error' content: application/json: schema: - $ref: '#/components/schemas/GrpsIoMailingListWithReadonlyAttributes' + $ref: '#/components/schemas/InternalServerError' example: - audience_access: public - committees: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - created_at: "2023-01-15T10:30:00Z" - description: Technical steering committee discussions - group_id: 12345 - group_name: technical-steering-committee - project_name: Cloud Native Computing Foundation - project_slug: cncf - project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: false - service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - subject_tag: '[TSC]' - subscriber_count: 42 - title: Technical Steering Committee - type: discussion_moderated - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - "400": - description: 'BadRequest: Bad request' + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' content: application/json: schema: - $ref: '#/components/schemas/BadRequestError' + $ref: '#/components/schemas/ServiceUnavailableError' example: - message: The request was invalid. + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + /groupsio/services/{service_id}: + delete: + tags: + - mailing-list + summary: delete-groupsio-service mailing-list + description: Delete a GroupsIO service + operationId: mailing-list#delete-groupsio-service + parameters: + - name: service_id + in: path + description: Service ID + required: true + schema: + type: string + description: Service ID + example: Similique est consequuntur quod occaecati ipsa nam. + example: Voluptate quia assumenda nisi. + responses: + "204": + description: No Content response. "404": - description: 'NotFound: Mailing list not found' + description: 'NotFound: Service not found' content: application/json: schema: @@ -401,151 +282,47 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - put: + get: tags: - mailing-list - summary: update-grpsio-mailing-list mailing-list - description: Update GroupsIO mailing list - operationId: mailing-list#update-grpsio-mailing-list + summary: get-groupsio-service mailing-list + description: Get a GroupsIO service by ID + operationId: mailing-list#get-groupsio-service parameters: - - name: v - in: query - description: Version of the API - allowEmptyValue: true - required: true - schema: - type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" - - name: uid + - name: service_id in: path - description: Mailing list UID -- unique identifier for the mailing list + description: Service ID required: true schema: type: string - description: Mailing list UID -- unique identifier for the mailing list - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - name: If-Match - in: header - description: If-Match header value for conditional requests - allowEmptyValue: true - schema: - type: string - description: If-Match header value for conditional requests - example: "123" - example: "123" - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateGrpsioMailingListRequestBody' - example: - audience_access: public - committees: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - description: Technical steering committee discussions - group_id: 12345 - group_name: technical-steering-committee - public: false - service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - subject_tag: '[TSC]' - subscriber_count: 42 - title: Technical Steering Committee - type: discussion_moderated + description: Service ID + example: Maxime unde laudantium. + example: Voluptatibus porro totam assumenda eum. responses: "200": description: OK response. content: application/json: schema: - $ref: '#/components/schemas/GrpsIoMailingListWithReadonlyAttributes' + $ref: '#/components/schemas/GroupsioService' example: - audience_access: public - committees: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Qui consequuntur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - created_at: "2023-01-15T10:30:00Z" - description: Technical steering committee discussions - group_id: 12345 - group_name: technical-steering-committee - project_name: Cloud Native Computing Foundation - project_slug: cncf + created_at: Voluptatem itaque deleniti possimus distinctio magnam. + domain: Aliquam consequatur. + group_id: 305646027091442090 + id: Facilis iure sed. + prefix: Perferendis eum ut blanditiis. project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: false - service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - subject_tag: '[TSC]' - subscriber_count: 42 - title: Technical Steering Committee - type: discussion_moderated - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - "400": - description: 'BadRequest: Bad request' - content: - application/json: - schema: - $ref: '#/components/schemas/BadRequestError' - example: - message: The request was invalid. + status: Rerum quasi molestias. + type: primary + updated_at: Et suscipit aliquid ut ipsam suscipit numquam. "404": - description: 'NotFound: Mailing list not found' + description: 'NotFound: Service not found' content: application/json: schema: $ref: '#/components/schemas/NotFoundError' example: message: The resource was not found. - "409": - description: 'Conflict: Conflict - ETag mismatch or validation failure' - content: - application/json: - schema: - $ref: '#/components/schemas/ConflictError' - example: - message: The resource already exists. "500": description: 'InternalServerError: Internal server error' content: @@ -564,113 +341,52 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /groupsio/mailing-lists/{uid}/members: - post: + put: tags: - mailing-list - summary: create-grpsio-mailing-list-member mailing-list - description: Create a new member for a GroupsIO mailing list - operationId: mailing-list#create-grpsio-mailing-list-member + summary: update-groupsio-service mailing-list + description: Update a GroupsIO service + operationId: mailing-list#update-groupsio-service parameters: - - name: v - in: query - description: Version of the API - allowEmptyValue: true - required: true - schema: - type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" - - name: uid + - name: service_id in: path - description: Mailing list UID + description: Service ID required: true schema: type: string - description: Mailing list UID - example: f47ac10b-58cc-4372-a567-0e02b2c3d479 - example: f47ac10b-58cc-4372-a567-0e02b2c3d479 + description: Service ID + example: Est ex ut. + example: Ducimus odio magni quisquam sequi voluptatem quisquam. requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CreateGrpsioMailingListMemberRequestBody' + $ref: '#/components/schemas/UpdateGroupsioServiceRequestBody' example: - delivery_mode: none - email: john.doe@example.com - first_name: John - job_title: Software Engineer - last_name: Doe - last_reviewed_at: "2023-01-15T14:30:00Z" - last_reviewed_by: admin@example.com - member_type: direct - mod_status: moderator - organization: Example Corp - username: jdoe + domain: Velit molestias molestiae fuga. + group_id: 1590455940722915203 + prefix: Ea nisi sapiente minus qui aut. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Aliquid dicta non. + type: primary responses: - "201": - description: Created response. + "200": + description: OK response. content: application/json: schema: - $ref: '#/components/schemas/GrpsIoMemberFull' + $ref: '#/components/schemas/GroupsioService' example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - delivery_mode: digest - email: john.doe@example.com - first_name: John - group_id: 67890 - job_title: Software Engineer - last_name: Doe - last_reviewed_at: "2023-01-15T14:30:00Z" - last_reviewed_by: admin@example.com - mailing_list_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - member_id: 12345 - member_type: direct - mod_status: none - organization: Example Corp - status: pending - uid: f47ac10b-58cc-4372-a567-0e02b2c3d479 - updated_at: "2023-06-20T14:45:30Z" - username: jdoe - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. + created_at: Aut dolor repellendus tempore hic commodi. + domain: Voluptas dolorem inventore ducimus expedita dolore. + group_id: 7060729682158494453 + id: Ea error unde. + prefix: Est cum vero quibusdam nam molestias. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Est necessitatibus eligendi esse. + type: primary + updated_at: Aliquam molestiae voluptas neque velit. "400": description: 'BadRequest: Bad request' content: @@ -680,21 +396,13 @@ paths: example: message: The request was invalid. "404": - description: 'NotFound: Mailing list not found' + description: 'NotFound: Service not found' content: application/json: schema: $ref: '#/components/schemas/NotFoundError' example: message: The resource was not found. - "409": - description: 'Conflict: Member already exists' - content: - application/json: - schema: - $ref: '#/components/schemas/ConflictError' - example: - message: The resource already exists. "500": description: 'InternalServerError: Internal server error' content: @@ -713,61 +421,44 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /groupsio/mailing-lists/{uid}/members/{member_uid}: - delete: + /groupsio/services/find_parent: + get: tags: - mailing-list - summary: delete-grpsio-mailing-list-member mailing-list - description: Delete a member from a GroupsIO mailing list - operationId: mailing-list#delete-grpsio-mailing-list-member + summary: find-parent-groupsio-service mailing-list + description: Find the parent GroupsIO service for a project + operationId: mailing-list#find-parent-groupsio-service parameters: - - name: v + - name: project_uid in: query - description: Version of the API + description: LFX v2 project UID allowEmptyValue: true required: true schema: type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" - - name: uid - in: path - description: Mailing list UID -- unique identifier for the mailing list - required: true - schema: - type: string - description: Mailing list UID -- unique identifier for the mailing list - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - name: member_uid - in: path - description: Member UID -- unique identifier for the member - required: true - schema: - type: string - description: Member UID -- unique identifier for the member - example: f47ac10b-58cc-4372-a567-0e02b2c3d479 + description: LFX v2 project UID + example: 37d64732-1a79-471c-95a3-7536daebfc37 format: uuid - example: f47ac10b-58cc-4372-a567-0e02b2c3d479 - - name: If-Match - in: header - description: If-Match header value for conditional requests - allowEmptyValue: true - required: true - schema: - type: string - description: If-Match header value for conditional requests - example: "123" - example: "123" + example: cc066c58-6895-4611-bf3b-3c8917a87161 responses: - "204": - description: No Content response. + "200": + description: OK response. + content: + application/json: + schema: + $ref: '#/components/schemas/GroupsioService' + example: + created_at: Voluptate sit natus dolorem laudantium. + domain: Consequatur voluptatem quae dolore qui quas ipsa. + group_id: 6701442498276098951 + id: Quae ut atque accusantium vero. + prefix: Doloribus consequatur quibusdam error aliquam. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Perferendis itaque accusantium nesciunt. + type: primary + updated_at: Similique esse in aut explicabo. "400": - description: 'BadRequest: Bad request - Cannot remove sole owner' + description: 'BadRequest: Bad request' content: application/json: schema: @@ -775,21 +466,13 @@ paths: example: message: The request was invalid. "404": - description: 'NotFound: Member not found' + description: 'NotFound: Parent service not found' content: application/json: schema: $ref: '#/components/schemas/NotFoundError' example: message: The resource was not found. - "409": - description: 'Conflict: Conflict - ETag mismatch' - content: - application/json: - schema: - $ref: '#/components/schemas/ConflictError' - example: - message: The resource already exists. "500": description: 'InternalServerError: Internal server error' content: @@ -808,105 +491,84 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] + /groupsio/subgroups: get: tags: - mailing-list - summary: get-grpsio-mailing-list-member mailing-list - description: Get a member of a GroupsIO mailing list by UID - operationId: mailing-list#get-grpsio-mailing-list-member + summary: list-groupsio-subgroups mailing-list + description: List GroupsIO subgroups, optionally filtered by project UID and/or committee UID + operationId: mailing-list#list-groupsio-subgroups parameters: - - name: v + - name: project_uid in: query - description: Version of the API + description: LFX v2 project UID filter allowEmptyValue: true - required: true schema: type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" - - name: uid - in: path - description: Mailing list UID -- unique identifier for the mailing list - required: true - schema: - type: string - description: Mailing list UID -- unique identifier for the mailing list - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + description: LFX v2 project UID filter + example: c6cb35e3-c184-43e4-b43c-aee8aec8ef25 format: uuid - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - name: member_uid - in: path - description: Member UID -- unique identifier for the member - required: true + example: d4bee84c-8ba6-4dc6-a277-b89c24ea2cc0 + - name: committee_uid + in: query + description: LFX v2 committee UID filter + allowEmptyValue: true schema: type: string - description: Member UID -- unique identifier for the member - example: f47ac10b-58cc-4372-a567-0e02b2c3d479 + description: LFX v2 committee UID filter + example: a0810552-13e5-4d31-9bb9-84221f9a48f8 format: uuid - example: f47ac10b-58cc-4372-a567-0e02b2c3d479 + example: d1f3c3db-f6c4-4f4c-8892-1aa29d2f63e0 responses: "200": description: OK response. - headers: - ETag: - description: ETag header value - schema: - type: string - description: ETag header value - example: "123" - example: "123" content: application/json: schema: - $ref: '#/components/schemas/GrpsIoMemberWithReadonlyAttributes' - example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - delivery_mode: none - email: john.doe@example.com - first_name: John - group_id: 67890 - job_title: Software Engineer - last_name: Doe - last_reviewed_at: "2023-01-15T14:30:00Z" - last_reviewed_by: admin@example.com - mailing_list_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - member_id: 12345 - member_type: direct - mod_status: owner - organization: Example Corp - status: pending - uid: f47ac10b-58cc-4372-a567-0e02b2c3d479 - updated_at: "2023-06-20T14:45:30Z" - username: jdoe - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. + $ref: '#/components/schemas/GroupsioSubgroupList' + example: + items: + - audience_access: Dolores sed officiis nihil ex. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Dolores recusandae amet blanditiis omnis qui optio. + description: Voluptatibus autem expedita cumque magnam et. + group_id: 3581801669178659495 + id: Debitis eaque sed aut sequi veniam. + name: Aut adipisci veritatis. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Natus et. + updated_at: Saepe nihil quaerat exercitationem vero. + - audience_access: Dolores sed officiis nihil ex. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Dolores recusandae amet blanditiis omnis qui optio. + description: Voluptatibus autem expedita cumque magnam et. + group_id: 3581801669178659495 + id: Debitis eaque sed aut sequi veniam. + name: Aut adipisci veritatis. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Natus et. + updated_at: Saepe nihil quaerat exercitationem vero. + - audience_access: Dolores sed officiis nihil ex. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Dolores recusandae amet blanditiis omnis qui optio. + description: Voluptatibus autem expedita cumque magnam et. + group_id: 3581801669178659495 + id: Debitis eaque sed aut sequi veniam. + name: Aut adipisci veritatis. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Natus et. + updated_at: Saepe nihil quaerat exercitationem vero. + - audience_access: Dolores sed officiis nihil ex. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Dolores recusandae amet blanditiis omnis qui optio. + description: Voluptatibus autem expedita cumque magnam et. + group_id: 3581801669178659495 + id: Debitis eaque sed aut sequi veniam. + name: Aut adipisci veritatis. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Natus et. + updated_at: Saepe nihil quaerat exercitationem vero. + total: 6812148234041603013 "400": description: 'BadRequest: Bad request' content: @@ -915,14 +577,6 @@ paths: $ref: '#/components/schemas/BadRequestError' example: message: The request was invalid. - "404": - description: 'NotFound: Member not found' - content: - application/json: - schema: - $ref: '#/components/schemas/NotFoundError' - example: - message: The resource was not found. "500": description: 'InternalServerError: Internal server error' content: @@ -941,135 +595,54 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - put: + post: tags: - mailing-list - summary: update-grpsio-mailing-list-member mailing-list - description: Update a member of a GroupsIO mailing list - operationId: mailing-list#update-grpsio-mailing-list-member - parameters: - - name: v - in: query - description: Version of the API - allowEmptyValue: true - required: true - schema: - type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" - - name: uid - in: path - description: Mailing list UID -- unique identifier for the mailing list - required: true - schema: - type: string - description: Mailing list UID -- unique identifier for the mailing list - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - name: member_uid - in: path - description: Member UID -- unique identifier for the member - required: true - schema: - type: string - description: Member UID -- unique identifier for the member - example: f47ac10b-58cc-4372-a567-0e02b2c3d479 - format: uuid - example: f47ac10b-58cc-4372-a567-0e02b2c3d479 - - name: If-Match - in: header - description: If-Match header value for conditional requests - allowEmptyValue: true - required: true - schema: - type: string - description: If-Match header value for conditional requests - example: "123" - example: "123" + summary: create-groupsio-subgroup mailing-list + description: Create a GroupsIO subgroup + operationId: mailing-list#create-groupsio-subgroup requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/UpdateGrpsioMailingListMemberRequestBody' + $ref: '#/components/schemas/GroupsioSubgroupRequest' example: - delivery_mode: none - first_name: John - job_title: Software Engineer - last_name: Doe - mod_status: none - organization: Example Corp - username: jdoe + audience_access: Aut iure. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + description: Voluptates mollitia et pariatur modi. + group_id: 3213952105058865418 + name: Non quo debitis animi itaque. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Atque vero asperiores iusto reiciendis sit asperiores. responses: - "200": - description: OK response. + "201": + description: Created response. content: application/json: schema: - $ref: '#/components/schemas/GrpsIoMemberWithReadonlyAttributes' + $ref: '#/components/schemas/GroupsioSubgroup' example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - delivery_mode: normal - email: john.doe@example.com - first_name: John - group_id: 67890 - job_title: Software Engineer - last_name: Doe - last_reviewed_at: "2023-01-15T14:30:00Z" - last_reviewed_by: admin@example.com - mailing_list_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - member_id: 12345 - member_type: committee - mod_status: moderator - organization: Example Corp - status: pending - uid: f47ac10b-58cc-4372-a567-0e02b2c3d479 - updated_at: "2023-06-20T14:45:30Z" - username: jdoe - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. + audience_access: Quaerat et non sed velit eum rerum. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Pariatur quam quo quasi natus totam. + description: Tenetur provident expedita. + group_id: 7735175026921310208 + id: Sed rerum voluptas est unde et ipsa. + name: Vitae vel modi cum. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Aut id sed. + updated_at: Sunt exercitationem amet animi dolore facilis ad. "400": - description: 'BadRequest: Bad request - Invalid data or immutable field modification' + description: 'BadRequest: Bad request' content: application/json: schema: $ref: '#/components/schemas/BadRequestError' example: message: The request was invalid. - "404": - description: 'NotFound: Member not found' - content: - application/json: - schema: - $ref: '#/components/schemas/NotFoundError' - example: - message: The resource was not found. "409": - description: 'Conflict: Conflict - ETag mismatch or validation failure' + description: 'Conflict: Conflict' content: application/json: schema: @@ -1094,90 +667,88 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /groupsio/mailing-lists/{uid}/settings: - get: + /groupsio/subgroups/{subgroup_id}: + delete: tags: - mailing-list - summary: get-grpsio-mailing-list-settings mailing-list - description: Get GroupsIO mailing list settings (writers and auditors) - operationId: mailing-list#get-grpsio-mailing-list-settings + summary: delete-groupsio-subgroup mailing-list + description: Delete a GroupsIO subgroup + operationId: mailing-list#delete-groupsio-subgroup parameters: - - name: v - in: query - description: Version of the API - allowEmptyValue: true - schema: - type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" - - name: uid + - name: subgroup_id in: path - description: Mailing list UID -- unique identifier for the mailing list + description: Subgroup ID required: true schema: type: string - description: Mailing list UID -- unique identifier for the mailing list - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + description: Subgroup ID + example: Sunt cupiditate. + example: Exercitationem aut repellendus sit suscipit placeat voluptates. responses: - "200": - description: OK response. - headers: - ETag: - description: ETag header value + "204": + description: No Content response. + "404": + description: 'NotFound: Subgroup not found' + content: + application/json: schema: - type: string - description: ETag header value - example: "123" - example: "123" + $ref: '#/components/schemas/NotFoundError' + example: + message: The resource was not found. + "500": + description: 'InternalServerError: Internal server error' content: application/json: schema: - $ref: '#/components/schemas/GrpsIoServiceSettings' + $ref: '#/components/schemas/InternalServerError' example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - last_audited_by: user_id_12345 - last_audited_time: "2023-05-10T09:15:00Z" - last_reviewed_at: "2025-08-04T09:00:00Z" - last_reviewed_by: user_id_12345 - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - "400": - description: 'BadRequest: Bad request' + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' content: application/json: schema: - $ref: '#/components/schemas/BadRequestError' + $ref: '#/components/schemas/ServiceUnavailableError' example: - message: The request was invalid. + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + get: + tags: + - mailing-list + summary: get-groupsio-subgroup mailing-list + description: Get a GroupsIO subgroup by ID + operationId: mailing-list#get-groupsio-subgroup + parameters: + - name: subgroup_id + in: path + description: Subgroup ID + required: true + schema: + type: string + description: Subgroup ID + example: Quisquam sit. + example: Ea et dolorum et qui rerum. + responses: + "200": + description: OK response. + content: + application/json: + schema: + $ref: '#/components/schemas/GroupsioSubgroup' + example: + audience_access: Similique voluptatibus quia id fugit. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Cupiditate tempore est et sit et. + description: Itaque delectus expedita. + group_id: 2881373843474038323 + id: Ad voluptatibus voluptatem commodi qui dolores voluptas. + name: Voluptate expedita recusandae ducimus sed quis sunt. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Eos laboriosam eaque aliquam exercitationem sint. + updated_at: Asperiores rerum ex aspernatur. "404": - description: 'NotFound: Mailing list settings not found' + description: 'NotFound: Subgroup not found' content: application/json: schema: @@ -1205,119 +776,51 @@ paths: put: tags: - mailing-list - summary: update-grpsio-mailing-list-settings mailing-list - description: Update GroupsIO mailing list settings (writers and auditors) - operationId: mailing-list#update-grpsio-mailing-list-settings + summary: update-groupsio-subgroup mailing-list + description: Update a GroupsIO subgroup + operationId: mailing-list#update-groupsio-subgroup parameters: - - name: v - in: query - description: Version of the API - allowEmptyValue: true - required: true - schema: - type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" - - name: uid + - name: subgroup_id in: path - description: Mailing list UID -- unique identifier for the mailing list + description: Subgroup ID required: true schema: type: string - description: Mailing list UID -- unique identifier for the mailing list - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - name: If-Match - in: header - description: If-Match header value for conditional requests - allowEmptyValue: true - schema: - type: string - description: If-Match header value for conditional requests - example: "123" - example: "123" + description: Subgroup ID + example: Est aut praesentium cupiditate. + example: Soluta ipsam quibusdam. requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/UpdateGrpsioServiceSettingsRequestBody' + $ref: '#/components/schemas/UpdateGroupsioSubgroupRequestBody' example: - auditors: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - writers: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + audience_access: Quas tenetur eligendi facilis. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + description: Qui inventore voluptatibus quas at suscipit. + group_id: 3362199226119147318 + name: Blanditiis consequatur molestiae odio quis enim et. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Tenetur voluptatum sed optio incidunt. responses: "200": description: OK response. content: application/json: schema: - $ref: '#/components/schemas/GrpsIoMailingListSettings' + $ref: '#/components/schemas/GroupsioSubgroup' example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - last_audited_by: user_id_12345 - last_audited_time: "2023-05-10T09:15:00Z" - last_reviewed_at: "2025-08-04T09:00:00Z" - last_reviewed_by: user_id_12345 - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. + audience_access: Praesentium molestiae consequatur impedit esse mollitia voluptatem. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Impedit aut et enim ut. + description: Consectetur repudiandae eaque adipisci optio vel hic. + group_id: 5008031110446800685 + id: Et perspiciatis id quia fuga. + name: Repellendus sint libero quibusdam nulla cupiditate. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Modi libero quas rem. + updated_at: Rerum at enim adipisci expedita et. "400": description: 'BadRequest: Bad request' content: @@ -1327,21 +830,13 @@ paths: example: message: The request was invalid. "404": - description: 'NotFound: Mailing list settings not found' + description: 'NotFound: Subgroup not found' content: application/json: schema: $ref: '#/components/schemas/NotFoundError' example: message: The resource was not found. - "409": - description: 'Conflict: Conflict - ETag mismatch' - content: - application/json: - schema: - $ref: '#/components/schemas/ConflictError' - example: - message: The resource already exists. "500": description: 'InternalServerError: Internal server error' content: @@ -1360,117 +855,40 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /groupsio/services: + /groupsio/subgroups/{subgroup_id}/invitemembers: post: tags: - mailing-list - summary: create-grpsio-service mailing-list - description: Create GroupsIO service with type-specific validation rules - operationId: mailing-list#create-grpsio-service + summary: invite-groupsio-members mailing-list + description: Invite members to a GroupsIO subgroup by email + operationId: mailing-list#invite-groupsio-members parameters: - - name: v - in: query - description: Version of the API - allowEmptyValue: true + - name: subgroup_id + in: path + description: Subgroup ID required: true schema: type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" + description: Subgroup ID + example: Sequi vero. + example: Incidunt suscipit. requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CreateGrpsioServiceRequestBody' + $ref: '#/components/schemas/GroupsioInviteMembersRequest' example: - auditors: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - domain: lists.project.org - global_owners: - - admin@example.com - group_id: 12345 - group_name: project-name - parent_service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - prefix: formation - project_slug: cncf - project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: true - status: created - type: primary - url: https://lists.project.org - writers: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + emails: + - Quo nihil quia blanditiis unde. + - Qui commodi totam. + - Voluptatem excepturi nam debitis quisquam voluptas velit. + - Quibusdam voluptatum soluta sapiente error ut. responses: - "201": - description: Created response. - content: - application/json: - schema: - $ref: '#/components/schemas/GrpsIoServiceFull' - example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - domain: lists.project.org - global_owners: - - admin@example.com - group_id: 12345 - group_name: project-name - parent_service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - prefix: formation - project_name: Cloud Native Computing Foundation - project_slug: cncf - project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: true - status: created - type: primary - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - url: https://lists.project.org - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. + "204": + description: No Content response. "400": - description: 'BadRequest: Bad request - Invalid type, missing required fields, or validation failures' + description: 'BadRequest: Bad request' content: application/json: schema: @@ -1478,21 +896,13 @@ paths: example: message: The request was invalid. "404": - description: 'NotFound: Resource not found' + description: 'NotFound: Subgroup not found' content: application/json: schema: $ref: '#/components/schemas/NotFoundError' example: message: The resource was not found. - "409": - description: 'Conflict: Conflict' - content: - application/json: - schema: - $ref: '#/components/schemas/ConflictError' - example: - message: The resource already exists. "500": description: 'InternalServerError: Internal server error' content: @@ -1511,71 +921,40 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /groupsio/services/{uid}: - delete: + /groupsio/subgroups/{subgroup_id}/member_count: + get: tags: - mailing-list - summary: delete-grpsio-service mailing-list - description: Delete GroupsIO service - operationId: mailing-list#delete-grpsio-service + summary: get-groupsio-subgroup-member-count mailing-list + description: Get count of members in a GroupsIO subgroup + operationId: mailing-list#get-groupsio-subgroup-member-count parameters: - - name: v - in: query - description: Version of the API - allowEmptyValue: true - schema: - type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" - - name: uid + - name: subgroup_id in: path - description: Service UID -- unique identifier for the service + description: Subgroup ID required: true schema: type: string - description: Service UID -- unique identifier for the service - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - name: If-Match - in: header - description: If-Match header value for conditional requests - allowEmptyValue: true - schema: - type: string - description: If-Match header value for conditional requests - example: "123" - example: "123" + description: Subgroup ID + example: Qui ut. + example: Ea omnis aliquam est saepe. responses: - "204": - description: No Content response. - "400": - description: 'BadRequest: Bad request' + "200": + description: OK response. content: application/json: schema: - $ref: '#/components/schemas/BadRequestError' + $ref: '#/components/schemas/GroupsioCount' example: - message: The request was invalid. + count: 7013755550356993833 "404": - description: 'NotFound: Resource not found' + description: 'NotFound: Subgroup not found' content: application/json: schema: $ref: '#/components/schemas/NotFoundError' example: message: The resource was not found. - "409": - description: 'Conflict: Conflict' - content: - application/json: - schema: - $ref: '#/components/schemas/ConflictError' - example: - message: The resource already exists. "500": description: 'InternalServerError: Internal server error' content: @@ -1594,99 +973,68 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] + /groupsio/subgroups/{subgroup_id}/members: get: tags: - mailing-list - summary: get-grpsio-service mailing-list - description: Get groupsIO service details by ID - operationId: mailing-list#get-grpsio-service + summary: list-groupsio-members mailing-list + description: List members of a GroupsIO subgroup + operationId: mailing-list#list-groupsio-members parameters: - - name: v - in: query - description: Version of the API - allowEmptyValue: true - schema: - type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" - - name: uid + - name: subgroup_id in: path - description: Service UID -- unique identifier for the service + description: Subgroup ID required: true schema: type: string - description: Service UID -- unique identifier for the service - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + description: Subgroup ID + example: Aut similique. + example: Quae eaque est facere. responses: "200": description: OK response. - headers: - ETag: - description: ETag header value - schema: - type: string - description: ETag header value - example: "123" - example: "123" - content: - application/json: - schema: - $ref: '#/components/schemas/GrpsIoServiceWithReadonlyAttributes' - example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - domain: lists.project.org - global_owners: - - admin@example.com - group_id: 12345 - group_name: project-name - parent_service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - prefix: formation - project_name: Cloud Native Computing Foundation - project_slug: cncf - project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: true - status: created - type: primary - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - url: https://lists.project.org - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - "400": - description: 'BadRequest: Bad request' content: application/json: schema: - $ref: '#/components/schemas/BadRequestError' - example: - message: The request was invalid. + $ref: '#/components/schemas/GroupsioMemberList' + example: + items: + - created_at: Eos sint ea provident. + delivery_mode: Quae dolores. + email: casey@littel.org + first_name: Cumque sed eveniet reprehenderit. + id: Ullam exercitationem quisquam nostrum nihil culpa. + last_name: Ut atque voluptatibus. + mod_status: Aut tempora. + name: Qui et voluptates in perspiciatis non. + status: Natus nesciunt omnis et. + subgroup_id: Et repellat voluptates reiciendis. + updated_at: Reiciendis consequatur laborum quidem voluptatum et voluptatibus. + - created_at: Eos sint ea provident. + delivery_mode: Quae dolores. + email: casey@littel.org + first_name: Cumque sed eveniet reprehenderit. + id: Ullam exercitationem quisquam nostrum nihil culpa. + last_name: Ut atque voluptatibus. + mod_status: Aut tempora. + name: Qui et voluptates in perspiciatis non. + status: Natus nesciunt omnis et. + subgroup_id: Et repellat voluptates reiciendis. + updated_at: Reiciendis consequatur laborum quidem voluptatum et voluptatibus. + - created_at: Eos sint ea provident. + delivery_mode: Quae dolores. + email: casey@littel.org + first_name: Cumque sed eveniet reprehenderit. + id: Ullam exercitationem quisquam nostrum nihil culpa. + last_name: Ut atque voluptatibus. + mod_status: Aut tempora. + name: Qui et voluptates in perspiciatis non. + status: Natus nesciunt omnis et. + subgroup_id: Et repellat voluptates reiciendis. + updated_at: Reiciendis consequatur laborum quidem voluptatum et voluptatibus. + total: 5232052381345216570 "404": - description: 'NotFound: Resource not found' + description: 'NotFound: Subgroup not found' content: application/json: schema: @@ -1711,111 +1059,52 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - put: + post: tags: - mailing-list - summary: update-grpsio-service mailing-list - description: Update GroupsIO service - operationId: mailing-list#update-grpsio-service + summary: add-groupsio-member mailing-list + description: Add a member to a GroupsIO subgroup + operationId: mailing-list#add-groupsio-member parameters: - - name: v - in: query - description: Version of the API - allowEmptyValue: true - required: true - schema: - type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" - - name: uid + - name: subgroup_id in: path - description: Service UID -- unique identifier for the service + description: Subgroup ID required: true schema: type: string - description: Service UID -- unique identifier for the service - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - name: If-Match - in: header - description: If-Match header value for conditional requests - allowEmptyValue: true - schema: - type: string - description: If-Match header value for conditional requests - example: "123" - example: "123" + description: Subgroup ID + example: Ab error nihil amet. + example: Velit quasi reprehenderit impedit cum. requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/UpdateGrpsioServiceRequestBody' + $ref: '#/components/schemas/AddGroupsioMemberRequestBody' example: - domain: lists.project.org - global_owners: - - admin@example.com - group_id: 12345 - group_name: project-name - parent_service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - prefix: formation - project_slug: cncf - project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: true - status: created - type: primary - url: https://lists.project.org + delivery_mode: digest + email: talia@runolfsdottir.org + mod_status: moderator + name: Quidem illum aliquam ut. responses: - "200": - description: OK response. + "201": + description: Created response. content: application/json: schema: - $ref: '#/components/schemas/GrpsIoServiceWithReadonlyAttributes' + $ref: '#/components/schemas/GroupsioMember' example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - domain: lists.project.org - global_owners: - - admin@example.com - group_id: 12345 - group_name: project-name - parent_service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - prefix: formation - project_name: Cloud Native Computing Foundation - project_slug: cncf - project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: true - status: created - type: primary - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - url: https://lists.project.org - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. + created_at: Fugit aut non eos. + delivery_mode: Nulla consequatur ipsam iusto sed voluptate. + email: syble@beer.net + first_name: Autem tempora exercitationem iusto aut et. + id: Non qui suscipit sit voluptas minima. + last_name: Ducimus deserunt vitae at quia. + mod_status: Alias voluptas illum ipsum. + name: Dolores in minima autem excepturi. + status: Inventore soluta aut suscipit non. + subgroup_id: Totam repellat ut esse aut earum architecto. + updated_at: Id quis et quibusdam et. "400": description: 'BadRequest: Bad request' content: @@ -1825,7 +1114,7 @@ paths: example: message: The request was invalid. "404": - description: 'NotFound: Resource not found' + description: 'NotFound: Subgroup not found' content: application/json: schema: @@ -1833,7 +1122,7 @@ paths: example: message: The resource was not found. "409": - description: 'Conflict: Conflict' + description: 'Conflict: Member already exists' content: application/json: schema: @@ -1858,98 +1147,37 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - /groupsio/services/{uid}/settings: - get: + /groupsio/subgroups/{subgroup_id}/members/{member_id}: + delete: tags: - mailing-list - summary: get-grpsio-service-settings mailing-list - description: Get GroupsIO service settings (writers and auditors) - operationId: mailing-list#get-grpsio-service-settings + summary: delete-groupsio-member mailing-list + description: Delete a member from a GroupsIO subgroup + operationId: mailing-list#delete-groupsio-member parameters: - - name: v - in: query - description: Version of the API - allowEmptyValue: true + - name: subgroup_id + in: path + description: Subgroup ID + required: true schema: type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" - - name: uid + description: Subgroup ID + example: Eius optio dolores voluptates id magnam. + example: Sint blanditiis natus deserunt veritatis molestiae. + - name: member_id in: path - description: Service UID -- unique identifier for the service + description: Member ID required: true schema: type: string - description: Service UID -- unique identifier for the service - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + description: Member ID + example: At eius. + example: Id vel rem a omnis amet laboriosam. responses: - "200": - description: OK response. - headers: - ETag: - description: ETag header value - schema: - type: string - description: ETag header value - example: "123" - example: "123" - content: - application/json: - schema: - $ref: '#/components/schemas/GrpsIoServiceSettings' - example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - last_audited_by: user_id_12345 - last_audited_time: "2023-05-10T09:15:00Z" - last_reviewed_at: "2025-08-04T09:00:00Z" - last_reviewed_by: user_id_12345 - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - "400": - description: 'BadRequest: Bad request' - content: - application/json: - schema: - $ref: '#/components/schemas/BadRequestError' - example: - message: The request was invalid. + "204": + description: No Content response. "404": - description: 'NotFound: Service settings not found' + description: 'NotFound: Member not found' content: application/json: schema: @@ -1974,146 +1202,201 @@ paths: message: The service is unavailable. security: - jwt_header_Authorization: [] - put: + get: tags: - mailing-list - summary: update-grpsio-service-settings mailing-list - description: Update GroupsIO service settings (writers and auditors) - operationId: mailing-list#update-grpsio-service-settings + summary: get-groupsio-member mailing-list + description: Get a member of a GroupsIO subgroup by ID + operationId: mailing-list#get-groupsio-member parameters: - - name: v - in: query - description: Version of the API - allowEmptyValue: true + - name: subgroup_id + in: path + description: Subgroup ID required: true schema: type: string - description: Version of the API - example: "1" - enum: - - "1" - example: "1" - - name: uid + description: Subgroup ID + example: Id quam eligendi necessitatibus optio velit. + example: Qui voluptas. + - name: member_id in: path - description: Service UID -- unique identifier for the service + description: Member ID required: true schema: type: string - description: Service UID -- unique identifier for the service - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - name: If-Match - in: header - description: If-Match header value for conditional requests - allowEmptyValue: true - schema: - type: string - description: If-Match header value for conditional requests - example: "123" - example: "123" - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateGrpsioServiceSettingsRequestBody' - example: - auditors: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - writers: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + description: Member ID + example: Temporibus eius enim magni et. + example: Qui eveniet ex. responses: "200": description: OK response. content: application/json: schema: - $ref: '#/components/schemas/GrpsIoServiceSettings' + $ref: '#/components/schemas/GroupsioMember' example: - auditors: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - created_at: "2023-01-15T10:30:00Z" - last_audited_by: user_id_12345 - last_audited_time: "2023-05-10T09:15:00Z" - last_reviewed_at: "2025-08-04T09:00:00Z" - last_reviewed_by: user_id_12345 - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - writers: - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - - avatar: http://leuschke.com/pietro - email: pierre.wolf@stroman.info - name: Temporibus assumenda error. - username: Ducimus ex et. - "400": - description: 'BadRequest: Bad request' + created_at: Quas excepturi maxime. + delivery_mode: Id ut sed nihil suscipit laudantium. + email: celine@smitham.biz + first_name: Et quia facere deleniti. + id: Enim fugiat. + last_name: Tenetur illum alias. + mod_status: In nostrum id ut. + name: Ab qui tempore beatae atque ab repudiandae. + status: Doloremque consequatur quo illo voluptatem ipsam. + subgroup_id: Sequi ut assumenda omnis iusto. + updated_at: Corrupti aut. + "404": + description: 'NotFound: Member not found' content: application/json: schema: - $ref: '#/components/schemas/BadRequestError' + $ref: '#/components/schemas/NotFoundError' + example: + message: The resource was not found. + "500": + description: 'InternalServerError: Internal server error' + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerError' + example: + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ServiceUnavailableError' + example: + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + put: + tags: + - mailing-list + summary: update-groupsio-member mailing-list + description: Update a member of a GroupsIO subgroup + operationId: mailing-list#update-groupsio-member + parameters: + - name: subgroup_id + in: path + description: Subgroup ID + required: true + schema: + type: string + description: Subgroup ID + example: Sed facilis sit aut rerum. + example: Ullam doloribus ab vitae illum harum. + - name: member_id + in: path + description: Member ID + required: true + schema: + type: string + description: Member ID + example: Animi velit. + example: Sunt quidem distinctio cumque facilis rem. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AddGroupsioMemberRequestBody' + example: + delivery_mode: normal + email: katelynn_rempel@bruen.info + mod_status: none + name: Inventore suscipit eveniet ipsum aut et. + responses: + "200": + description: OK response. + content: + application/json: + schema: + $ref: '#/components/schemas/GroupsioMember' + example: + created_at: A soluta. + delivery_mode: Veritatis ea aut eos recusandae architecto. + email: daphnee.schinner@raynor.net + first_name: Velit consequatur magni et dolorem quasi. + id: Commodi ut similique provident saepe rerum saepe. + last_name: Laudantium numquam sint. + mod_status: Ea et. + name: Accusamus labore nobis cum. + status: Quisquam consequuntur tenetur eius assumenda. + subgroup_id: Qui rerum. + updated_at: Quis aspernatur. + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' example: message: The request was invalid. "404": - description: 'NotFound: Service settings not found' + description: 'NotFound: Member not found' content: application/json: schema: $ref: '#/components/schemas/NotFoundError' example: message: The resource was not found. - "409": - description: 'Conflict: Conflict - ETag mismatch' + "500": + description: 'InternalServerError: Internal server error' content: application/json: schema: - $ref: '#/components/schemas/ConflictError' + $ref: '#/components/schemas/InternalServerError' example: - message: The resource already exists. + message: An internal server error occurred. + "503": + description: 'ServiceUnavailable: Service unavailable' + content: + application/json: + schema: + $ref: '#/components/schemas/ServiceUnavailableError' + example: + message: The service is unavailable. + security: + - jwt_header_Authorization: [] + /groupsio/subgroups/count: + get: + tags: + - mailing-list + summary: get-groupsio-subgroup-count mailing-list + description: Get count of GroupsIO subgroups for a project + operationId: mailing-list#get-groupsio-subgroup-count + parameters: + - name: project_uid + in: query + description: LFX v2 project UID + allowEmptyValue: true + required: true + schema: + type: string + description: LFX v2 project UID + example: 1fd03ad1-4d67-4597-b0e2-51feab065472 + format: uuid + example: 85e38c2b-ce4d-4eef-a098-bfc8a22bcd45 + responses: + "200": + description: OK response. + content: + application/json: + schema: + $ref: '#/components/schemas/GroupsioCount' + example: + count: 4933079255212787388 + "400": + description: 'BadRequest: Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestError' + example: + message: The request was invalid. "500": description: 'InternalServerError: Internal server error' content: @@ -2183,122 +1466,14 @@ paths: $ref: '#/components/schemas/ServiceUnavailableError' example: message: The service is unavailable. - /webhooks/groupsio: - post: - tags: - - mailing-list - summary: groupsio-webhook mailing-list - description: Handle GroupsIO webhook events for subgroup and member changes - operationId: mailing-list#groupsio-webhook - parameters: - - name: x-groupsio-signature - in: header - description: HMAC-SHA1 base64 signature for verification - allowEmptyValue: true - required: true - schema: - type: string - description: HMAC-SHA1 base64 signature for verification - example: Eveniet reprehenderit unde ut atque voluptatibus rem. - example: Tempora nihil. - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/GroupsioWebhookPayload2' - example: - action: created_subgroup - extra: Sint rerum laboriosam consequatur ut explicabo saepe. - extra_id: 500672409584855543 - group: Aut consequatur nihil repellat velit qui. - member_info: Totam cumque totam alias. - responses: - "204": - description: No Content response. - "400": - description: 'BadRequest: Invalid webhook payload or signature' - content: - application/json: - schema: - $ref: '#/components/schemas/BadRequestError' - example: - message: The request was invalid. - "401": - description: 'Unauthorized: Invalid webhook signature' - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedError' - example: - message: Unauthorized access. components: schemas: - BadRequestError: - type: object - properties: - message: - type: string - description: Error message - example: The request was invalid. - example: - message: The request was invalid. - required: - - message - Committee: - type: object - properties: - allowed_voting_statuses: - type: array - items: - type: string - example: Voting Rep - enum: - - Voting Rep - - Alternate Voting Rep - - Observer - - Emeritus - - None - description: Committee member voting statuses that determine which members are synced - example: - - Voting Rep - - Alternate Voting Rep - name: - type: string - description: Committee name (read-only, populated by server) - example: Tempore est et sit et maxime. - uid: - type: string - description: Committee UUID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - description: Committee associated with a mailing list - example: - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Ex aspernatur. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - required: - - uid - ConflictError: - type: object - properties: - message: - type: string - description: Error message - example: The resource already exists. - example: - message: The resource already exists. - required: - - message - CreateGrpsioMailingListMemberRequestBody: + AddGroupsioMemberRequestBody: type: object properties: delivery_mode: type: string description: Email delivery mode - default: normal example: digest enum: - normal @@ -2307,1179 +1482,254 @@ components: email: type: string description: Member email address - example: john.doe@example.com + example: hillard@ferrylabadie.com format: email - first_name: - type: string - description: Member first name - example: John - minLength: 1 - maxLength: 255 - job_title: - type: string - description: Member job title - example: Software Engineer - maxLength: 255 - last_name: - type: string - description: Member last name - example: Doe - minLength: 1 - maxLength: 255 - last_reviewed_at: - type: string - description: Last reviewed timestamp - example: "2023-01-15T14:30:00Z" - format: date-time - last_reviewed_by: - type: string - description: Last reviewed by user ID - example: admin@example.com - member_type: - type: string - description: Member type - default: direct - example: committee - enum: - - committee - - direct mod_status: type: string description: Moderation status - default: none example: owner enum: - none - moderator - owner - organization: - type: string - description: Member organization - example: Example Corp - maxLength: 255 - username: + name: type: string - description: Member username - example: jdoe - maxLength: 255 + description: Member display name + example: Dolorem cumque. example: delivery_mode: none - email: john.doe@example.com - first_name: John - job_title: Software Engineer - last_name: Doe - last_reviewed_at: "2023-01-15T14:30:00Z" - last_reviewed_by: admin@example.com - member_type: committee - mod_status: none - organization: Example Corp - username: jdoe - required: - - email - CreateGrpsioMailingListRequestBody: + email: hayden@stark.biz + mod_status: owner + name: Aut praesentium quasi nobis et suscipit blanditiis. + BadRequestError: type: object properties: - audience_access: - type: string - description: 'public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.' - default: public - example: public - enum: - - public - - approval_required - - invite_only - auditors: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - committees: - type: array - items: - $ref: '#/components/schemas/Committee' - description: Committees associated with this mailing list (OR logic for access control) - example: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - description: - type: string - description: Mailing list description (11-500 characters) - example: Technical steering committee discussions - minLength: 11 - maxLength: 500 - group_id: - type: integer - description: Mailing list group ID - example: 12345 - format: int64 - minimum: 0 - group_name: - type: string - description: Mailing list group name - example: technical-steering-committee - pattern: ^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$ - minLength: 3 - maxLength: 34 - public: - type: boolean - description: Whether the mailing list is publicly accessible - default: false - example: false - service_uid: - type: string - description: Service UUID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - subject_tag: - type: string - description: Subject tag prefix - example: '[TSC]' - maxLength: 50 - subscriber_count: - type: integer - description: Number of subscribers in this mailing list (read-only, maintained by service) - example: 42 - format: int64 - minimum: 0 - title: - type: string - description: Mailing list title - example: Technical Steering Committee - minLength: 5 - maxLength: 100 - type: + message: type: string - description: Mailing list type - example: discussion_moderated - enum: - - announcement - - discussion_moderated - - discussion_open - writers: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + description: Error message + example: The request was invalid. example: - audience_access: public - auditors: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - committees: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - description: Technical steering committee discussions - group_id: 12345 - group_name: technical-steering-committee - public: false - service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - subject_tag: '[TSC]' - subscriber_count: 42 - title: Technical Steering Committee - type: discussion_moderated - writers: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + message: The request was invalid. required: - - group_name - - public - - type - - description - - title - - service_uid - CreateGrpsioServiceRequestBody: + - message + ConflictError: type: object properties: - auditors: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - domain: - type: string - description: Service domain - example: lists.project.org - global_owners: - type: array - items: - type: string - example: carli_johns@sipes.com - format: email - description: List of global owner email addresses (required for primary, forbidden for shared) - example: - - admin@example.com - group_id: - type: integer - description: GroupsIO group ID - example: 12345 - format: int64 - group_name: - type: string - description: GroupsIO group name - example: project-name - parent_service_uid: - type: string - description: Parent primary service UID (automatically set for shared type services) - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - prefix: - type: string - description: Email prefix (required for formation and shared, forbidden for primary) - example: formation - project_slug: - type: string - description: Project slug identifier - example: cncf - format: regexp - pattern: ^[a-z][a-z0-9_\-]*[a-z0-9]$ - project_uid: - type: string - description: LFXv2 Project UID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - public: - type: boolean - description: Whether the service is publicly accessible - default: false - example: true - status: - type: string - description: Service status - example: created - type: - type: string - description: Service type - example: primary - enum: - - primary - - formation - - shared - url: + message: type: string - description: Service URL - example: https://lists.project.org - format: uri - writers: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + description: Error message + example: The resource already exists. example: - auditors: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - domain: lists.project.org - global_owners: - - admin@example.com - group_id: 12345 - group_name: project-name - parent_service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - prefix: formation - project_slug: cncf - project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: true - status: created - type: primary - url: https://lists.project.org - writers: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. + message: The resource already exists. required: - - type - - project_uid - GroupsioWebhookPayload: + - message + GroupsioCheckSubscriberRequest: type: object properties: - action: - type: string - description: The type of webhook event - example: created_subgroup - enum: - - created_subgroup - - deleted_subgroup - - added_member - - removed_member - - ban_members - extra: + email: type: string - description: Extra data field (subgroup suffix) - example: Quia fuga. - extra_id: - type: integer - description: Extra ID field (subgroup ID for deletion) - example: 5008031110446800685 - format: int64 - group: - description: Contains subgroup data from Groups.io - example: Eligendi facilis sit deserunt reiciendis. - member_info: - description: Contains member data from Groups.io - example: Accusamus et perspiciatis. - signature: + description: Email address to check + example: helmer@friesen.net + format: email + subgroup_id: type: string - description: HMAC-SHA1 base64 signature for verification - example: Repellendus sint libero quibusdam nulla cupiditate. - description: Webhook event payload from Groups.io + description: Subgroup ID + example: Quia nam sed. + description: Request body for checking if an email is subscribed example: - action: created_subgroup - extra: Praesentium molestiae consequatur impedit esse mollitia voluptatem. - extra_id: 5572662436592214257 - group: Consectetur repudiandae eaque adipisci optio vel hic. - member_info: Modi libero quas rem. - signature: Aut et enim ut laudantium rerum at. + email: jovan_baumbach@effertz.net + subgroup_id: Velit id eligendi est perspiciatis consequatur voluptas. required: - - action - - signature - GroupsioWebhookPayload2: + - email + - subgroup_id + GroupsioCheckSubscriberResponse: type: object properties: - action: - type: string - description: The type of webhook event - example: created_subgroup - enum: - - created_subgroup - - deleted_subgroup - - added_member - - removed_member - - ban_members - extra: - type: string - description: Extra data field (subgroup suffix) - example: Reiciendis voluptatibus illum ut et. - extra_id: - type: integer - description: Extra ID field (subgroup ID for deletion) - example: 5719676566333912791 - format: int64 - group: - description: Contains subgroup data from Groups.io - example: Vero qui est nostrum sit. - member_info: - description: Contains member data from Groups.io - example: Officiis dignissimos. + subscribed: + type: boolean + description: Whether the email is subscribed + example: false + description: Response for check subscriber request example: - action: created_subgroup - extra: Et voluptates in perspiciatis non repudiandae. - extra_id: 3235636813397717479 - group: Et ipsam iste dignissimos vel. - member_info: A quam enim debitis veniam. + subscribed: true required: - - action - GrpsIoMailingListFull: + - subscribed + GroupsioCount: type: object properties: - audience_access: - type: string - description: 'public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.' - default: public - example: public - enum: - - public - - approval_required - - invite_only - auditors: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - committees: - type: array - items: - $ref: '#/components/schemas/Committee' - description: Committees associated with this mailing list (OR logic for access control) - example: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Quas animi qui. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Quas animi qui. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Quas animi qui. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - created_at: - type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time - description: - type: string - description: Mailing list description (11-500 characters) - example: Technical steering committee discussions - minLength: 11 - maxLength: 500 - group_id: + count: type: integer - description: Mailing list group ID - example: 12345 + description: Count value + example: 3518554859839344188 format: int64 - minimum: 0 - group_name: - type: string - description: Mailing list group name - example: technical-steering-committee - pattern: ^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$ - minLength: 3 - maxLength: 34 - project_name: - type: string - description: Project name (read-only) - example: Cloud Native Computing Foundation - project_slug: - type: string - description: Project slug identifier (read-only) - example: cncf - format: regexp - pattern: ^[a-z][a-z0-9_\-]*[a-z0-9]$ - project_uid: - type: string - description: LFXv2 Project UID (inherited from parent service) - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - public: - type: boolean - description: Whether the mailing list is publicly accessible - default: false - example: false - service_uid: - type: string - description: Service UUID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - subject_tag: - type: string - description: Subject tag prefix - example: '[TSC]' - maxLength: 50 - subscriber_count: - type: integer - description: Number of subscribers in this mailing list (read-only, maintained by service) - example: 42 - format: int64 - minimum: 0 - title: - type: string - description: Mailing list title - example: Technical Steering Committee - minLength: 5 - maxLength: 100 - type: - type: string - description: Mailing list type - example: discussion_moderated - enum: - - announcement - - discussion_moderated - - discussion_open - uid: - type: string - description: Mailing list UID -- unique identifier for the mailing list - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - updated_at: - type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - writers: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - description: A complete representation of GroupsIO mailing lists with all attributes including access control and audit trail. + description: Count response example: - audience_access: public - auditors: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - committees: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Quas animi qui. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Quas animi qui. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Quas animi qui. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Quas animi qui. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - created_at: "2023-01-15T10:30:00Z" - description: Technical steering committee discussions - group_id: 12345 - group_name: technical-steering-committee - project_name: Cloud Native Computing Foundation - project_slug: cncf - project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: false - service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - subject_tag: '[TSC]' - subscriber_count: 42 - title: Technical Steering Committee - type: discussion_moderated - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - writers: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - GrpsIoMailingListSettings: + count: 1791894229755420167 + required: + - count + GroupsioInviteMembersRequest: type: object properties: - auditors: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - created_at: - type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time - last_audited_by: - type: string - description: The user ID who last audited the service - example: user_id_12345 - last_audited_time: - type: string - description: The timestamp when the service was last audited - example: "2023-05-10T09:15:00Z" - format: date-time - last_reviewed_at: - type: string - description: The timestamp when the service was last reviewed in RFC3339 format - example: "2025-08-04T09:00:00Z" - format: date-time - last_reviewed_by: - type: string - description: The user ID who last reviewed this service - example: user_id_12345 - uid: - type: string - description: Mailing list UID -- unique identifier for the mailing list - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - updated_at: - type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - writers: + emails: type: array items: - $ref: '#/components/schemas/UserInfo' - description: Manager users who can edit/modify this resource + type: string + example: Voluptas voluptatum occaecati iste ipsam. + description: Email addresses to invite example: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - description: A representation of GroupsIO mailing list settings for user management. + - Iusto et debitis minus porro doloremque. + - Autem dolores. + - Dolorem earum deserunt facilis. + - Quae corporis ut sit dolore commodi. + description: Request body for inviting members to a GroupsIO subgroup example: - auditors: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - created_at: "2023-01-15T10:30:00Z" - last_audited_by: user_id_12345 - last_audited_time: "2023-05-10T09:15:00Z" - last_reviewed_at: "2025-08-04T09:00:00Z" - last_reviewed_by: user_id_12345 - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - writers: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - GrpsIoMailingListWithReadonlyAttributes: - type: object - properties: - audience_access: - type: string - description: 'public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.' - default: public - example: public - enum: - - public - - approval_required - - invite_only - committees: - type: array - items: - $ref: '#/components/schemas/Committee' - description: Committees associated with this mailing list (OR logic for access control) - example: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Quas animi qui. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Quas animi qui. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Quas animi qui. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Quas animi qui. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - created_at: - type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time - description: - type: string - description: Mailing list description (11-500 characters) - example: Technical steering committee discussions - minLength: 11 - maxLength: 500 - group_id: - type: integer - description: Mailing list group ID - example: 12345 - format: int64 - minimum: 0 - group_name: - type: string - description: Mailing list group name - example: technical-steering-committee - pattern: ^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$ - minLength: 3 - maxLength: 34 - project_name: - type: string - description: Project name (read-only) - example: Cloud Native Computing Foundation - project_slug: - type: string - description: Project slug identifier (read-only) - example: cncf - format: regexp - pattern: ^[a-z][a-z0-9_\-]*[a-z0-9]$ - project_uid: - type: string - description: LFXv2 Project UID (inherited from parent service) - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - public: - type: boolean - description: Whether the mailing list is publicly accessible - default: false - example: false - service_uid: - type: string - description: Service UUID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - subject_tag: - type: string - description: Subject tag prefix - example: '[TSC]' - maxLength: 50 - subscriber_count: - type: integer - description: Number of subscribers in this mailing list (read-only, maintained by service) - example: 42 - format: int64 - minimum: 0 - title: - type: string - description: Mailing list title - example: Technical Steering Committee - minLength: 5 - maxLength: 100 - type: - type: string - description: Mailing list type - example: discussion_moderated - enum: - - announcement - - discussion_moderated - - discussion_open - uid: - type: string - description: Mailing list UID -- unique identifier for the mailing list - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - updated_at: - type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - description: A representation of GroupsIO mailing lists with readonly attributes. - example: - audience_access: public - committees: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Quas animi qui. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Quas animi qui. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - created_at: "2023-01-15T10:30:00Z" - description: Technical steering committee discussions - group_id: 12345 - group_name: technical-steering-committee - project_name: Cloud Native Computing Foundation - project_slug: cncf - project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: false - service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - subject_tag: '[TSC]' - subscriber_count: 42 - title: Technical Steering Committee - type: discussion_moderated - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - GrpsIoMemberFull: + emails: + - Maxime saepe ut aliquid repudiandae aut architecto. + - Repellendus nostrum repellat harum aut. + - Optio consequatur. + required: + - emails + GroupsioMember: type: object properties: - auditors: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. created_at: type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time + description: Creation timestamp + example: Et voluptates commodi cupiditate asperiores asperiores. delivery_mode: type: string description: Email delivery mode - default: normal - example: digest - enum: - - normal - - digest - - none + example: Dolore omnis corrupti magni adipisci quia omnis. email: type: string description: Member email address - example: john.doe@example.com + example: brennon@boyer.info format: email first_name: type: string description: Member first name - example: John - minLength: 1 - maxLength: 255 - group_id: - type: integer - description: Groups.io group ID - example: 67890 - format: int64 - job_title: + example: Inventore quis. + id: type: string - description: Member job title - example: Software Engineer - maxLength: 255 + description: Member ID + example: Aut quas. last_name: type: string description: Member last name - example: Doe - minLength: 1 - maxLength: 255 - last_reviewed_at: - type: string - description: Last reviewed timestamp - example: "2023-01-15T14:30:00Z" - format: date-time - last_reviewed_by: - type: string - description: Last reviewed by user ID - example: admin@example.com - mailing_list_uid: - type: string - description: Mailing list UID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - member_id: - type: integer - description: Groups.io member ID - example: 12345 - format: int64 - member_type: - type: string - description: Member type - default: direct - example: direct - enum: - - committee - - direct + example: Velit qui. mod_status: type: string description: Moderation status - default: none - example: owner - enum: - - none - - moderator - - owner - organization: + example: Neque dignissimos minus maiores voluptates est libero. + name: type: string - description: Member organization - example: Example Corp - maxLength: 255 + description: Member display name + example: Aut accusantium sint. status: type: string description: Member status - example: pending - uid: + example: Magni illo minus. + subgroup_id: type: string - description: Member UID - example: f47ac10b-58cc-4372-a567-0e02b2c3d479 - format: uuid + description: Subgroup ID + example: A fugit temporibus incidunt quia ut. updated_at: type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - username: - type: string - description: Member username - example: jdoe - maxLength: 255 - writers: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - description: A complete representation of a GroupsIO mailing list member with all attributes. + description: Last update timestamp + example: Tempora delectus cumque est. + description: A member of a GroupsIO subgroup example: - auditors: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - created_at: "2023-01-15T10:30:00Z" - delivery_mode: none - email: john.doe@example.com - first_name: John - group_id: 67890 - job_title: Software Engineer - last_name: Doe - last_reviewed_at: "2023-01-15T14:30:00Z" - last_reviewed_by: admin@example.com - mailing_list_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - member_id: 12345 - member_type: committee - mod_status: owner - organization: Example Corp - status: pending - uid: f47ac10b-58cc-4372-a567-0e02b2c3d479 - updated_at: "2023-06-20T14:45:30Z" - username: jdoe - writers: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - required: - - uid - - mailing_list_uid - - first_name - - last_name - - email - - member_type - - delivery_mode - - mod_status - - status - - created_at - - updated_at - GrpsIoMemberWithReadonlyAttributes: + created_at: Perspiciatis ipsam debitis natus qui voluptatem eum. + delivery_mode: Non soluta. + email: ramona@bogandibbert.name + first_name: Alias fugit quod velit ab. + id: Possimus possimus vel quos eum. + last_name: Ea omnis dolores et recusandae adipisci quos. + mod_status: Ut neque. + name: Ab voluptas error placeat explicabo facere saepe. + status: Illum quia ea et deleniti maiores. + subgroup_id: Eum adipisci hic. + updated_at: Consequatur fugiat a dolorem sed. + GroupsioMemberList: type: object properties: - auditors: + items: type: array items: - $ref: '#/components/schemas/UserInfo' - description: Auditor users who can audit this resource + $ref: '#/components/schemas/GroupsioMember' + description: List of members example: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - created_at: - type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time + - created_at: Explicabo saepe hic exercitationem. + delivery_mode: Sequi hic. + email: natalia@harris.biz + first_name: Ut et quia est non rem. + id: Ad dolorem sit molestias aliquam sit. + last_name: Aut est facere laborum enim. + mod_status: Quidem impedit voluptas quia rem. + name: Ad voluptatum magni fugit similique cumque. + status: Non atque ut quis ut tempore placeat. + subgroup_id: Sint praesentium. + updated_at: Et rem. + - created_at: Explicabo saepe hic exercitationem. + delivery_mode: Sequi hic. + email: natalia@harris.biz + first_name: Ut et quia est non rem. + id: Ad dolorem sit molestias aliquam sit. + last_name: Aut est facere laborum enim. + mod_status: Quidem impedit voluptas quia rem. + name: Ad voluptatum magni fugit similique cumque. + status: Non atque ut quis ut tempore placeat. + subgroup_id: Sint praesentium. + updated_at: Et rem. + total: + type: integer + description: Total count + example: 4598526804261273058 + format: int64 + description: List of GroupsIO members + example: + items: + - created_at: Explicabo saepe hic exercitationem. + delivery_mode: Sequi hic. + email: natalia@harris.biz + first_name: Ut et quia est non rem. + id: Ad dolorem sit molestias aliquam sit. + last_name: Aut est facere laborum enim. + mod_status: Quidem impedit voluptas quia rem. + name: Ad voluptatum magni fugit similique cumque. + status: Non atque ut quis ut tempore placeat. + subgroup_id: Sint praesentium. + updated_at: Et rem. + - created_at: Explicabo saepe hic exercitationem. + delivery_mode: Sequi hic. + email: natalia@harris.biz + first_name: Ut et quia est non rem. + id: Ad dolorem sit molestias aliquam sit. + last_name: Aut est facere laborum enim. + mod_status: Quidem impedit voluptas quia rem. + name: Ad voluptatum magni fugit similique cumque. + status: Non atque ut quis ut tempore placeat. + subgroup_id: Sint praesentium. + updated_at: Et rem. + - created_at: Explicabo saepe hic exercitationem. + delivery_mode: Sequi hic. + email: natalia@harris.biz + first_name: Ut et quia est non rem. + id: Ad dolorem sit molestias aliquam sit. + last_name: Aut est facere laborum enim. + mod_status: Quidem impedit voluptas quia rem. + name: Ad voluptatum magni fugit similique cumque. + status: Non atque ut quis ut tempore placeat. + subgroup_id: Sint praesentium. + updated_at: Et rem. + total: 9046657099964780568 + GroupsioMemberRequest: + type: object + properties: delivery_mode: type: string description: Email delivery mode - default: normal - example: normal + example: digest enum: - normal - digest @@ -3487,520 +1737,201 @@ components: email: type: string description: Member email address - example: john.doe@example.com + example: cyrus_becker@breitenberghaley.com format: email - first_name: - type: string - description: Member first name - example: John - minLength: 1 - maxLength: 255 - group_id: - type: integer - description: Groups.io group ID - example: 67890 - format: int64 - job_title: - type: string - description: Member job title - example: Software Engineer - maxLength: 255 - last_name: - type: string - description: Member last name - example: Doe - minLength: 1 - maxLength: 255 - last_reviewed_at: - type: string - description: Last reviewed timestamp - example: "2023-01-15T14:30:00Z" - format: date-time - last_reviewed_by: - type: string - description: Last reviewed by user ID - example: admin@example.com - mailing_list_uid: - type: string - description: Mailing list UID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - member_id: - type: integer - description: Groups.io member ID - example: 12345 - format: int64 - member_type: - type: string - description: Member type - default: direct - example: committee - enum: - - committee - - direct mod_status: type: string description: Moderation status - default: none - example: none + example: moderator enum: - none - moderator - owner - organization: - type: string - description: Member organization - example: Example Corp - maxLength: 255 - status: - type: string - description: Member status - example: pending - uid: - type: string - description: Member UID - example: f47ac10b-58cc-4372-a567-0e02b2c3d479 - format: uuid - updated_at: - type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - username: + name: type: string - description: Member username - example: jdoe - maxLength: 255 - writers: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - description: A representation of GroupsIO mailing list members with readonly attributes. + description: Member display name + example: Placeat alias qui non labore. + description: Request body for adding or updating a GroupsIO member example: - auditors: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - created_at: "2023-01-15T10:30:00Z" - delivery_mode: none - email: john.doe@example.com - first_name: John - group_id: 67890 - job_title: Software Engineer - last_name: Doe - last_reviewed_at: "2023-01-15T14:30:00Z" - last_reviewed_by: admin@example.com - mailing_list_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - member_id: 12345 - member_type: committee - mod_status: owner - organization: Example Corp - status: pending - uid: f47ac10b-58cc-4372-a567-0e02b2c3d479 - updated_at: "2023-06-20T14:45:30Z" - username: jdoe - writers: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - GrpsIoServiceFull: + delivery_mode: normal + email: conrad.cassin@borer.com + mod_status: none + name: Adipisci quaerat molestiae voluptas itaque porro facere. + GroupsioProjectsResponse: type: object properties: - auditors: + projects: type: array items: - $ref: '#/components/schemas/UserInfo' - description: Auditor users who can audit this resource + type: string + example: Error cupiditate ut velit culpa delectus dignissimos. + description: List of project identifiers example: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. + - Sunt ut error architecto ea. + - Voluptas vitae quae debitis voluptas molestias. + description: Projects that have GroupsIO services + example: + projects: + - Perferendis ullam. + - Perspiciatis aspernatur minima aperiam corporis. + GroupsioService: + type: object + properties: created_at: type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time + description: Creation timestamp + example: Ratione autem fugit optio. domain: type: string description: Service domain - example: lists.project.org - global_owners: - type: array - items: - type: string - example: elmo_kautzer@muellerkoepp.org - format: email - description: List of global owner email addresses (required for primary, forbidden for shared) - example: - - admin@example.com + example: Qui natus ducimus similique fugiat. group_id: type: integer description: GroupsIO group ID - example: 12345 + example: 3792419788170046764 format: int64 - group_name: - type: string - description: GroupsIO group name - example: project-name - parent_service_uid: + id: type: string - description: Parent primary service UID (automatically set for shared type services) - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid + description: Service ID + example: Ea laborum maiores. prefix: type: string - description: Email prefix (required for formation and shared, forbidden for primary) - example: formation - project_name: - type: string - description: Project name (read-only) - example: Cloud Native Computing Foundation - project_slug: - type: string - description: Project slug identifier - example: cncf - format: regexp - pattern: ^[a-z][a-z0-9_\-]*[a-z0-9]$ + description: Email prefix + example: Qui culpa neque est. project_uid: type: string - description: LFXv2 Project UID + description: LFX v2 project UID example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee format: uuid - public: - type: boolean - description: Whether the service is publicly accessible - default: false - example: true status: type: string description: Service status - example: created + example: Qui tempore id quisquam illum. type: type: string description: Service type example: primary - enum: - - primary - - formation - - shared - uid: - type: string - description: Service UID -- unique identifier for the service - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid updated_at: type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - url: - type: string - description: Service URL - example: https://lists.project.org - format: uri - writers: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - description: A complete representation of GroupsIO services with all attributes including access control and audit trail. + description: Last update timestamp + example: Sequi voluptatem. + description: A GroupsIO service managed via ITX example: - auditors: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - created_at: "2023-01-15T10:30:00Z" - domain: lists.project.org - global_owners: - - admin@example.com - group_id: 12345 - group_name: project-name - parent_service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - prefix: formation - project_name: Cloud Native Computing Foundation - project_slug: cncf + created_at: Iusto explicabo nihil. + domain: Aliquam voluptatem quia et praesentium quo assumenda. + group_id: 5494386556706073005 + id: Nam facere deleniti doloribus. + prefix: Consequatur repudiandae ipsam hic. project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: true - status: created + status: Laboriosam repellendus ut. type: primary - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - url: https://lists.project.org - writers: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - required: - - type - - project_uid - GrpsIoServiceSettings: + updated_at: Possimus labore consequatur sunt voluptatibus beatae. + GroupsioServiceList: type: object properties: - auditors: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - created_at: - type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time - last_audited_by: - type: string - description: The user ID who last audited the service - example: user_id_12345 - last_audited_time: - type: string - description: The timestamp when the service was last audited - example: "2023-05-10T09:15:00Z" - format: date-time - last_reviewed_at: - type: string - description: The timestamp when the service was last reviewed in RFC3339 format - example: "2025-08-04T09:00:00Z" - format: date-time - last_reviewed_by: - type: string - description: The user ID who last reviewed this service - example: user_id_12345 - uid: - type: string - description: Service UID -- unique identifier for the service - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - updated_at: - type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - writers: + items: type: array items: - $ref: '#/components/schemas/UserInfo' - description: Manager users who can edit/modify this resource + $ref: '#/components/schemas/GroupsioService' + description: List of services example: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - description: A representation of GroupsIO service settings for user management. + - created_at: Sunt laborum ut id laboriosam aut aut. + domain: Sapiente quo eveniet iusto sit aperiam neque. + group_id: 123850831553145715 + id: Aliquam esse odit. + prefix: Minima hic repellendus consequatur adipisci. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Laudantium qui. + type: primary + updated_at: Sequi vero sit. + - created_at: Sunt laborum ut id laboriosam aut aut. + domain: Sapiente quo eveniet iusto sit aperiam neque. + group_id: 123850831553145715 + id: Aliquam esse odit. + prefix: Minima hic repellendus consequatur adipisci. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Laudantium qui. + type: primary + updated_at: Sequi vero sit. + - created_at: Sunt laborum ut id laboriosam aut aut. + domain: Sapiente quo eveniet iusto sit aperiam neque. + group_id: 123850831553145715 + id: Aliquam esse odit. + prefix: Minima hic repellendus consequatur adipisci. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Laudantium qui. + type: primary + updated_at: Sequi vero sit. + total: + type: integer + description: Total count + example: 6020740068368848590 + format: int64 + description: List of GroupsIO services example: - auditors: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - created_at: "2023-01-15T10:30:00Z" - last_audited_by: user_id_12345 - last_audited_time: "2023-05-10T09:15:00Z" - last_reviewed_at: "2025-08-04T09:00:00Z" - last_reviewed_by: user_id_12345 - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - writers: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - GrpsIoServiceWithReadonlyAttributes: + items: + - created_at: Sunt laborum ut id laboriosam aut aut. + domain: Sapiente quo eveniet iusto sit aperiam neque. + group_id: 123850831553145715 + id: Aliquam esse odit. + prefix: Minima hic repellendus consequatur adipisci. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Laudantium qui. + type: primary + updated_at: Sequi vero sit. + - created_at: Sunt laborum ut id laboriosam aut aut. + domain: Sapiente quo eveniet iusto sit aperiam neque. + group_id: 123850831553145715 + id: Aliquam esse odit. + prefix: Minima hic repellendus consequatur adipisci. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Laudantium qui. + type: primary + updated_at: Sequi vero sit. + - created_at: Sunt laborum ut id laboriosam aut aut. + domain: Sapiente quo eveniet iusto sit aperiam neque. + group_id: 123850831553145715 + id: Aliquam esse odit. + prefix: Minima hic repellendus consequatur adipisci. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Laudantium qui. + type: primary + updated_at: Sequi vero sit. + - created_at: Sunt laborum ut id laboriosam aut aut. + domain: Sapiente quo eveniet iusto sit aperiam neque. + group_id: 123850831553145715 + id: Aliquam esse odit. + prefix: Minima hic repellendus consequatur adipisci. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Laudantium qui. + type: primary + updated_at: Sequi vero sit. + total: 8640143773663907109 + GroupsioServiceRequest: type: object properties: - auditors: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - created_at: - type: string - description: The timestamp when the service was created (read-only) - example: "2023-01-15T10:30:00Z" - format: date-time domain: type: string description: Service domain - example: lists.project.org - global_owners: - type: array - items: - type: string - example: otho.witting@trantowschimmel.biz - format: email - description: List of global owner email addresses (required for primary, forbidden for shared) - example: - - admin@example.com + example: Commodi et. group_id: type: integer description: GroupsIO group ID - example: 12345 + example: 898470202368092092 format: int64 - group_name: - type: string - description: GroupsIO group name - example: project-name - parent_service_uid: - type: string - description: Parent primary service UID (automatically set for shared type services) - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid prefix: type: string - description: Email prefix (required for formation and shared, forbidden for primary) - example: formation - project_name: - type: string - description: Project name (read-only) - example: Cloud Native Computing Foundation - project_slug: - type: string - description: Project slug identifier - example: cncf - format: regexp - pattern: ^[a-z][a-z0-9_\-]*[a-z0-9]$ + description: Email prefix + example: Qui maxime ad. project_uid: type: string - description: LFXv2 Project UID + description: LFX v2 project UID example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee format: uuid - public: - type: boolean - description: Whether the service is publicly accessible - default: false - example: true status: type: string description: Service status - example: created + example: Soluta sed laborum maiores ipsa. type: type: string description: Service type @@ -4009,95 +1940,193 @@ components: - primary - formation - shared - uid: + description: Request body for creating or updating a GroupsIO service + example: + domain: Amet qui eligendi. + group_id: 1558129810533357763 + prefix: Magni provident laborum voluptatem. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + status: Iusto recusandae. + type: primary + GroupsioSubgroup: + type: object + properties: + audience_access: + type: string + description: Audience access setting + example: Et sint laudantium officiis. + committee_uid: type: string - description: Service UID -- unique identifier for the service + description: LFX v2 committee UID example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee format: uuid - updated_at: + created_at: + type: string + description: Creation timestamp + example: Est laborum animi cum molestiae harum dicta. + description: + type: string + description: Subgroup description + example: Ad numquam porro enim in. + group_id: + type: integer + description: GroupsIO group ID + example: 2112860877170077964 + format: int64 + id: + type: string + description: Subgroup ID + example: Possimus ut ullam aliquid ad commodi. + name: + type: string + description: Subgroup name + example: Quisquam repudiandae hic excepturi est. + project_uid: + type: string + description: LFX v2 project UID + example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + format: uuid + type: type: string - description: The timestamp when the service was last updated (read-only) - example: "2023-06-20T14:45:30Z" - format: date-time - url: + description: Subgroup type + example: Animi assumenda incidunt ut dolores dolores. + updated_at: type: string - description: Service URL - example: https://lists.project.org - format: uri - writers: + description: Last update timestamp + example: Possimus esse id recusandae cum praesentium itaque. + description: A GroupsIO subgroup (mailing list) managed via ITX + example: + audience_access: Qui nostrum aut sit. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Iste ut odit nisi. + description: Voluptatem unde saepe reiciendis nesciunt eos necessitatibus. + group_id: 8636928196036809970 + id: Et ut et et ut unde. + name: Ut dolorum velit quisquam similique assumenda. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Laudantium voluptas aliquid labore et nobis ratione. + updated_at: Consectetur a similique aspernatur velit omnis. + GroupsioSubgroupList: + type: object + properties: + items: type: array items: - $ref: '#/components/schemas/UserInfo' - description: Manager users who can edit/modify this resource + $ref: '#/components/schemas/GroupsioSubgroup' + description: List of subgroups example: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - description: A representation of GroupsIO services with readonly attributes. + - audience_access: Est dolore. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Et et qui dolorum. + description: Vero amet. + group_id: 346864561935847351 + id: Omnis sed earum rerum corporis quae quo. + name: Minus aspernatur. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Asperiores suscipit quia. + updated_at: Alias amet aspernatur ut nihil. + - audience_access: Est dolore. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Et et qui dolorum. + description: Vero amet. + group_id: 346864561935847351 + id: Omnis sed earum rerum corporis quae quo. + name: Minus aspernatur. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Asperiores suscipit quia. + updated_at: Alias amet aspernatur ut nihil. + total: + type: integer + description: Total count + example: 4059489510681644250 + format: int64 + description: List of GroupsIO subgroups + example: + items: + - audience_access: Est dolore. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Et et qui dolorum. + description: Vero amet. + group_id: 346864561935847351 + id: Omnis sed earum rerum corporis quae quo. + name: Minus aspernatur. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Asperiores suscipit quia. + updated_at: Alias amet aspernatur ut nihil. + - audience_access: Est dolore. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Et et qui dolorum. + description: Vero amet. + group_id: 346864561935847351 + id: Omnis sed earum rerum corporis quae quo. + name: Minus aspernatur. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Asperiores suscipit quia. + updated_at: Alias amet aspernatur ut nihil. + - audience_access: Est dolore. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Et et qui dolorum. + description: Vero amet. + group_id: 346864561935847351 + id: Omnis sed earum rerum corporis quae quo. + name: Minus aspernatur. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Asperiores suscipit quia. + updated_at: Alias amet aspernatur ut nihil. + - audience_access: Est dolore. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + created_at: Et et qui dolorum. + description: Vero amet. + group_id: 346864561935847351 + id: Omnis sed earum rerum corporis quae quo. + name: Minus aspernatur. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Asperiores suscipit quia. + updated_at: Alias amet aspernatur ut nihil. + total: 710387942492653757 + GroupsioSubgroupRequest: + type: object + properties: + audience_access: + type: string + description: Audience access setting + example: Et quia architecto molestiae assumenda. + committee_uid: + type: string + description: LFX v2 committee UID + example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + format: uuid + description: + type: string + description: Subgroup description + example: Provident sit commodi autem incidunt enim. + group_id: + type: integer + description: GroupsIO group ID + example: 5743989652491219245 + format: int64 + name: + type: string + description: Subgroup name + example: Reiciendis quisquam quisquam autem quisquam qui impedit. + project_uid: + type: string + description: LFX v2 project UID + example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + format: uuid + type: + type: string + description: Subgroup type + example: Quia aliquid rerum numquam. + description: Request body for creating or updating a GroupsIO subgroup example: - auditors: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - created_at: "2023-01-15T10:30:00Z" - domain: lists.project.org - global_owners: - - admin@example.com - group_id: 12345 - group_name: project-name - parent_service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - prefix: formation - project_name: Cloud Native Computing Foundation - project_slug: cncf + audience_access: Earum in et provident et nulla facilis. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + description: Voluptas sed sapiente autem. + group_id: 7498979218594870713 + name: Autem quo voluptatum ut laboriosam qui voluptatibus. project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: true - status: created - type: primary - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - updated_at: "2023-06-20T14:45:30Z" - url: https://lists.project.org - writers: - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - - avatar: http://legrosboyle.net/adolph_deckow - email: noe@ebertbartoletti.biz - name: Et neque dolor deserunt. - username: Sapiente quo eveniet iusto sit aperiam neque. - required: - - type - - project_uid + type: Est laboriosam non. InternalServerError: type: object properties: @@ -4142,227 +2171,31 @@ components: message: Unauthorized access. required: - message - UpdateGrpsioMailingListMemberRequestBody: - type: object - properties: - delivery_mode: - type: string - description: Email delivery mode - default: normal - example: none - enum: - - normal - - digest - - none - first_name: - type: string - description: Member first name - example: John - minLength: 1 - maxLength: 255 - job_title: - type: string - description: Member job title - example: Software Engineer - maxLength: 255 - last_name: - type: string - description: Member last name - example: Doe - minLength: 1 - maxLength: 255 - mod_status: - type: string - description: Moderation status - default: none - example: none - enum: - - none - - moderator - - owner - organization: - type: string - description: Member organization - example: Example Corp - maxLength: 255 - username: - type: string - description: Member username - example: jdoe - maxLength: 255 - example: - delivery_mode: digest - first_name: John - job_title: Software Engineer - last_name: Doe - mod_status: moderator - organization: Example Corp - username: jdoe - UpdateGrpsioMailingListRequestBody: - type: object - properties: - audience_access: - type: string - description: 'public: Anyone can join. approval_required: Users must request to join and be approved. invite_only: Only invited users can join.' - default: public - example: public - enum: - - public - - approval_required - - invite_only - committees: - type: array - items: - $ref: '#/components/schemas/Committee' - description: Committees associated with this mailing list (OR logic for access control) - example: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - description: - type: string - description: Mailing list description (11-500 characters) - example: Technical steering committee discussions - minLength: 11 - maxLength: 500 - group_id: - type: integer - description: Mailing list group ID - example: 12345 - format: int64 - minimum: 0 - group_name: - type: string - description: Mailing list group name - example: technical-steering-committee - pattern: ^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$ - minLength: 3 - maxLength: 34 - public: - type: boolean - description: Whether the mailing list is publicly accessible - default: false - example: false - service_uid: - type: string - description: Service UUID - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid - subject_tag: - type: string - description: Subject tag prefix - example: '[TSC]' - maxLength: 50 - subscriber_count: - type: integer - description: Number of subscribers in this mailing list (read-only, maintained by service) - example: 42 - format: int64 - minimum: 0 - title: - type: string - description: Mailing list title - example: Technical Steering Committee - minLength: 5 - maxLength: 100 - type: - type: string - description: Mailing list type - example: discussion_moderated - enum: - - announcement - - discussion_moderated - - discussion_open - example: - audience_access: public - committees: - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - - allowed_voting_statuses: - - Voting Rep - - Alternate Voting Rep - name: Aliquid aliquid. - uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - description: Technical steering committee discussions - group_id: 12345 - group_name: technical-steering-committee - public: false - service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - subject_tag: '[TSC]' - subscriber_count: 42 - title: Technical Steering Committee - type: discussion_moderated - required: - - group_name - - public - - type - - description - - title - - service_uid - UpdateGrpsioServiceRequestBody: + UpdateGroupsioServiceRequestBody: type: object properties: domain: type: string description: Service domain - example: lists.project.org - global_owners: - type: array - items: - type: string - example: khalid_fisher@kassulke.com - format: email - description: List of global owner email addresses (required for primary, forbidden for shared) - example: - - admin@example.com + example: Vel illum accusantium voluptatem voluptates et ex. group_id: type: integer description: GroupsIO group ID - example: 12345 + example: 7845011941832544071 format: int64 - group_name: - type: string - description: GroupsIO group name - example: project-name - parent_service_uid: - type: string - description: Parent primary service UID (automatically set for shared type services) - example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - format: uuid prefix: type: string - description: Email prefix (required for formation and shared, forbidden for primary) - example: formation - project_slug: - type: string - description: Project slug identifier - example: cncf - format: regexp - pattern: ^[a-z][a-z0-9_\-]*[a-z0-9]$ + description: Email prefix + example: Omnis atque maxime nam dolorum. project_uid: type: string - description: LFXv2 Project UID + description: LFX v2 project UID example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee format: uuid - public: - type: boolean - description: Whether the service is publicly accessible - default: false - example: true status: type: string description: Service status - example: created + example: Odit delectus. type: type: string description: Service type @@ -4371,125 +2204,55 @@ components: - primary - formation - shared - url: - type: string - description: Service URL - example: https://lists.project.org - format: uri example: - domain: lists.project.org - global_owners: - - admin@example.com - group_id: 12345 - group_name: project-name - parent_service_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - prefix: formation - project_slug: cncf + domain: Exercitationem laboriosam ipsum. + group_id: 3468863149838709088 + prefix: Eos error qui. project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee - public: true - status: created + status: Qui nihil. type: primary - url: https://lists.project.org - required: - - type - - project_uid - UpdateGrpsioServiceSettingsRequestBody: - type: object - properties: - auditors: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Auditor users who can audit this resource - example: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - writers: - type: array - items: - $ref: '#/components/schemas/UserInfo' - description: Manager users who can edit/modify this resource - example: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - example: - auditors: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - writers: - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - - avatar: http://goldner.info/perry - email: ernie.lueilwitz@altenwerth.info - name: Eos non id at perspiciatis. - username: Eaque quis possimus velit quasi quis occaecati. - UserInfo: + UpdateGroupsioSubgroupRequestBody: type: object properties: - avatar: + audience_access: type: string - description: The avatar URL of the user - example: http://hilpert.name/brooklyn.thompson - format: uri - email: + description: Audience access setting + example: Ducimus sed eveniet sed quos et alias. + committee_uid: type: string - description: The email address of the user - example: beth.renner@ratke.biz - format: email + description: LFX v2 committee UID + example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + format: uuid + description: + type: string + description: Subgroup description + example: Vitae ducimus. + group_id: + type: integer + description: GroupsIO group ID + example: 3156761412527126577 + format: int64 name: type: string - description: The full name of the user - example: Odit omnis eaque similique consectetur. - username: + description: Subgroup name + example: Qui ex nihil quasi occaecati magni. + project_uid: + type: string + description: LFX v2 project UID + example: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + format: uuid + type: type: string - description: The username/LFID of the user - example: Ullam consequatur. - description: User information including profile details. + description: Subgroup type + example: A perspiciatis rerum enim incidunt repellat. example: - avatar: http://pacocha.name/karina.pollich - email: carlo_pollich@gottlieb.org - name: Magnam nisi. - username: Cumque aut ipsum. + audience_access: Consectetur ducimus corrupti aut itaque. + committee_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + description: Reiciendis quis eaque delectus voluptas aperiam. + group_id: 228014870381650192 + name: Corporis eum molestiae. + project_uid: 7cad5a8d-19d0-41a4-81a6-043453daf9ee + type: Iure aut sunt. securitySchemes: jwt_header_Authorization: type: http @@ -4497,4 +2260,4 @@ components: scheme: bearer tags: - name: mailing-list - description: The mailing list service manages mailing lists and services + description: The mailing list service proxies GroupsIO operations to the ITX API diff --git a/gen/mailing_list/client.go b/gen/mailing_list/client.go index 7534299..db443d8 100644 --- a/gen/mailing_list/client.go +++ b/gen/mailing_list/client.go @@ -16,49 +16,57 @@ import ( // Client is the "mailing-list" service client. type Client struct { - LivezEndpoint goa.Endpoint - ReadyzEndpoint goa.Endpoint - CreateGrpsioServiceEndpoint goa.Endpoint - GetGrpsioServiceEndpoint goa.Endpoint - UpdateGrpsioServiceEndpoint goa.Endpoint - DeleteGrpsioServiceEndpoint goa.Endpoint - GetGrpsioServiceSettingsEndpoint goa.Endpoint - UpdateGrpsioServiceSettingsEndpoint goa.Endpoint - CreateGrpsioMailingListEndpoint goa.Endpoint - GetGrpsioMailingListEndpoint goa.Endpoint - UpdateGrpsioMailingListEndpoint goa.Endpoint - DeleteGrpsioMailingListEndpoint goa.Endpoint - GetGrpsioMailingListSettingsEndpoint goa.Endpoint - UpdateGrpsioMailingListSettingsEndpoint goa.Endpoint - CreateGrpsioMailingListMemberEndpoint goa.Endpoint - GetGrpsioMailingListMemberEndpoint goa.Endpoint - UpdateGrpsioMailingListMemberEndpoint goa.Endpoint - DeleteGrpsioMailingListMemberEndpoint goa.Endpoint - GroupsioWebhookEndpoint goa.Endpoint + LivezEndpoint goa.Endpoint + ReadyzEndpoint goa.Endpoint + ListGroupsioServicesEndpoint goa.Endpoint + CreateGroupsioServiceEndpoint goa.Endpoint + GetGroupsioServiceEndpoint goa.Endpoint + UpdateGroupsioServiceEndpoint goa.Endpoint + DeleteGroupsioServiceEndpoint goa.Endpoint + GetGroupsioServiceProjectsEndpoint goa.Endpoint + FindParentGroupsioServiceEndpoint goa.Endpoint + ListGroupsioSubgroupsEndpoint goa.Endpoint + CreateGroupsioSubgroupEndpoint goa.Endpoint + GetGroupsioSubgroupEndpoint goa.Endpoint + UpdateGroupsioSubgroupEndpoint goa.Endpoint + DeleteGroupsioSubgroupEndpoint goa.Endpoint + GetGroupsioSubgroupCountEndpoint goa.Endpoint + GetGroupsioSubgroupMemberCountEndpoint goa.Endpoint + ListGroupsioMembersEndpoint goa.Endpoint + AddGroupsioMemberEndpoint goa.Endpoint + GetGroupsioMemberEndpoint goa.Endpoint + UpdateGroupsioMemberEndpoint goa.Endpoint + DeleteGroupsioMemberEndpoint goa.Endpoint + InviteGroupsioMembersEndpoint goa.Endpoint + CheckGroupsioSubscriberEndpoint goa.Endpoint } // NewClient initializes a "mailing-list" service client given the endpoints. -func NewClient(livez, readyz, createGrpsioService, getGrpsioService, updateGrpsioService, deleteGrpsioService, getGrpsioServiceSettings, updateGrpsioServiceSettings, createGrpsioMailingList, getGrpsioMailingList, updateGrpsioMailingList, deleteGrpsioMailingList, getGrpsioMailingListSettings, updateGrpsioMailingListSettings, createGrpsioMailingListMember, getGrpsioMailingListMember, updateGrpsioMailingListMember, deleteGrpsioMailingListMember, groupsioWebhook goa.Endpoint) *Client { +func NewClient(livez, readyz, listGroupsioServices, createGroupsioService, getGroupsioService, updateGroupsioService, deleteGroupsioService, getGroupsioServiceProjects, findParentGroupsioService, listGroupsioSubgroups, createGroupsioSubgroup, getGroupsioSubgroup, updateGroupsioSubgroup, deleteGroupsioSubgroup, getGroupsioSubgroupCount, getGroupsioSubgroupMemberCount, listGroupsioMembers, addGroupsioMember, getGroupsioMember, updateGroupsioMember, deleteGroupsioMember, inviteGroupsioMembers, checkGroupsioSubscriber goa.Endpoint) *Client { return &Client{ - LivezEndpoint: livez, - ReadyzEndpoint: readyz, - CreateGrpsioServiceEndpoint: createGrpsioService, - GetGrpsioServiceEndpoint: getGrpsioService, - UpdateGrpsioServiceEndpoint: updateGrpsioService, - DeleteGrpsioServiceEndpoint: deleteGrpsioService, - GetGrpsioServiceSettingsEndpoint: getGrpsioServiceSettings, - UpdateGrpsioServiceSettingsEndpoint: updateGrpsioServiceSettings, - CreateGrpsioMailingListEndpoint: createGrpsioMailingList, - GetGrpsioMailingListEndpoint: getGrpsioMailingList, - UpdateGrpsioMailingListEndpoint: updateGrpsioMailingList, - DeleteGrpsioMailingListEndpoint: deleteGrpsioMailingList, - GetGrpsioMailingListSettingsEndpoint: getGrpsioMailingListSettings, - UpdateGrpsioMailingListSettingsEndpoint: updateGrpsioMailingListSettings, - CreateGrpsioMailingListMemberEndpoint: createGrpsioMailingListMember, - GetGrpsioMailingListMemberEndpoint: getGrpsioMailingListMember, - UpdateGrpsioMailingListMemberEndpoint: updateGrpsioMailingListMember, - DeleteGrpsioMailingListMemberEndpoint: deleteGrpsioMailingListMember, - GroupsioWebhookEndpoint: groupsioWebhook, + LivezEndpoint: livez, + ReadyzEndpoint: readyz, + ListGroupsioServicesEndpoint: listGroupsioServices, + CreateGroupsioServiceEndpoint: createGroupsioService, + GetGroupsioServiceEndpoint: getGroupsioService, + UpdateGroupsioServiceEndpoint: updateGroupsioService, + DeleteGroupsioServiceEndpoint: deleteGroupsioService, + GetGroupsioServiceProjectsEndpoint: getGroupsioServiceProjects, + FindParentGroupsioServiceEndpoint: findParentGroupsioService, + ListGroupsioSubgroupsEndpoint: listGroupsioSubgroups, + CreateGroupsioSubgroupEndpoint: createGroupsioSubgroup, + GetGroupsioSubgroupEndpoint: getGroupsioSubgroup, + UpdateGroupsioSubgroupEndpoint: updateGroupsioSubgroup, + DeleteGroupsioSubgroupEndpoint: deleteGroupsioSubgroup, + GetGroupsioSubgroupCountEndpoint: getGroupsioSubgroupCount, + GetGroupsioSubgroupMemberCountEndpoint: getGroupsioSubgroupMemberCount, + ListGroupsioMembersEndpoint: listGroupsioMembers, + AddGroupsioMemberEndpoint: addGroupsioMember, + GetGroupsioMemberEndpoint: getGroupsioMember, + UpdateGroupsioMemberEndpoint: updateGroupsioMember, + DeleteGroupsioMemberEndpoint: deleteGroupsioMember, + InviteGroupsioMembersEndpoint: inviteGroupsioMembers, + CheckGroupsioSubscriberEndpoint: checkGroupsioSubscriber, } } @@ -85,284 +93,330 @@ func (c *Client) Readyz(ctx context.Context) (res []byte, err error) { return ires.([]byte), nil } -// CreateGrpsioService calls the "create-grpsio-service" endpoint of the +// ListGroupsioServices calls the "list-groupsio-services" endpoint of the // "mailing-list" service. -// CreateGrpsioService may return the following errors: -// - "BadRequest" (type *BadRequestError): Bad request - Invalid type, missing required fields, or validation failures -// - "NotFound" (type *NotFoundError): Resource not found -// - "Conflict" (type *ConflictError): Conflict +// ListGroupsioServices may return the following errors: +// - "BadRequest" (type *BadRequestError): Bad request // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) CreateGrpsioService(ctx context.Context, p *CreateGrpsioServicePayload) (res *GrpsIoServiceFull, err error) { +func (c *Client) ListGroupsioServices(ctx context.Context, p *ListGroupsioServicesPayload) (res *GroupsioServiceList, err error) { var ires any - ires, err = c.CreateGrpsioServiceEndpoint(ctx, p) + ires, err = c.ListGroupsioServicesEndpoint(ctx, p) if err != nil { return } - return ires.(*GrpsIoServiceFull), nil + return ires.(*GroupsioServiceList), nil } -// GetGrpsioService calls the "get-grpsio-service" endpoint of the +// CreateGroupsioService calls the "create-groupsio-service" endpoint of the // "mailing-list" service. -// GetGrpsioService may return the following errors: +// CreateGroupsioService may return the following errors: // - "BadRequest" (type *BadRequestError): Bad request -// - "NotFound" (type *NotFoundError): Resource not found +// - "Conflict" (type *ConflictError): Conflict // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) GetGrpsioService(ctx context.Context, p *GetGrpsioServicePayload) (res *GetGrpsioServiceResult, err error) { +func (c *Client) CreateGroupsioService(ctx context.Context, p *CreateGroupsioServicePayload) (res *GroupsioService, err error) { var ires any - ires, err = c.GetGrpsioServiceEndpoint(ctx, p) + ires, err = c.CreateGroupsioServiceEndpoint(ctx, p) if err != nil { return } - return ires.(*GetGrpsioServiceResult), nil + return ires.(*GroupsioService), nil } -// UpdateGrpsioService calls the "update-grpsio-service" endpoint of the +// GetGroupsioService calls the "get-groupsio-service" endpoint of the // "mailing-list" service. -// UpdateGrpsioService may return the following errors: -// - "BadRequest" (type *BadRequestError): Bad request -// - "NotFound" (type *NotFoundError): Resource not found -// - "Conflict" (type *ConflictError): Conflict +// GetGroupsioService may return the following errors: +// - "NotFound" (type *NotFoundError): Service not found // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) UpdateGrpsioService(ctx context.Context, p *UpdateGrpsioServicePayload) (res *GrpsIoServiceWithReadonlyAttributes, err error) { +func (c *Client) GetGroupsioService(ctx context.Context, p *GetGroupsioServicePayload) (res *GroupsioService, err error) { var ires any - ires, err = c.UpdateGrpsioServiceEndpoint(ctx, p) + ires, err = c.GetGroupsioServiceEndpoint(ctx, p) if err != nil { return } - return ires.(*GrpsIoServiceWithReadonlyAttributes), nil + return ires.(*GroupsioService), nil } -// DeleteGrpsioService calls the "delete-grpsio-service" endpoint of the +// UpdateGroupsioService calls the "update-groupsio-service" endpoint of the // "mailing-list" service. -// DeleteGrpsioService may return the following errors: +// UpdateGroupsioService may return the following errors: // - "BadRequest" (type *BadRequestError): Bad request -// - "NotFound" (type *NotFoundError): Resource not found -// - "Conflict" (type *ConflictError): Conflict +// - "NotFound" (type *NotFoundError): Service not found +// - "InternalServerError" (type *InternalServerError): Internal server error +// - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable +// - error: internal error +func (c *Client) UpdateGroupsioService(ctx context.Context, p *UpdateGroupsioServicePayload) (res *GroupsioService, err error) { + var ires any + ires, err = c.UpdateGroupsioServiceEndpoint(ctx, p) + if err != nil { + return + } + return ires.(*GroupsioService), nil +} + +// DeleteGroupsioService calls the "delete-groupsio-service" endpoint of the +// "mailing-list" service. +// DeleteGroupsioService may return the following errors: +// - "NotFound" (type *NotFoundError): Service not found // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) DeleteGrpsioService(ctx context.Context, p *DeleteGrpsioServicePayload) (err error) { - _, err = c.DeleteGrpsioServiceEndpoint(ctx, p) +func (c *Client) DeleteGroupsioService(ctx context.Context, p *DeleteGroupsioServicePayload) (err error) { + _, err = c.DeleteGroupsioServiceEndpoint(ctx, p) return } -// GetGrpsioServiceSettings calls the "get-grpsio-service-settings" endpoint of -// the "mailing-list" service. -// GetGrpsioServiceSettings may return the following errors: -// - "BadRequest" (type *BadRequestError): Bad request -// - "NotFound" (type *NotFoundError): Service settings not found +// GetGroupsioServiceProjects calls the "get-groupsio-service-projects" +// endpoint of the "mailing-list" service. +// GetGroupsioServiceProjects may return the following errors: // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) GetGrpsioServiceSettings(ctx context.Context, p *GetGrpsioServiceSettingsPayload) (res *GetGrpsioServiceSettingsResult, err error) { +func (c *Client) GetGroupsioServiceProjects(ctx context.Context, p *GetGroupsioServiceProjectsPayload) (res *GroupsioProjectsResponse, err error) { var ires any - ires, err = c.GetGrpsioServiceSettingsEndpoint(ctx, p) + ires, err = c.GetGroupsioServiceProjectsEndpoint(ctx, p) if err != nil { return } - return ires.(*GetGrpsioServiceSettingsResult), nil + return ires.(*GroupsioProjectsResponse), nil } -// UpdateGrpsioServiceSettings calls the "update-grpsio-service-settings" -// endpoint of the "mailing-list" service. -// UpdateGrpsioServiceSettings may return the following errors: +// FindParentGroupsioService calls the "find-parent-groupsio-service" endpoint +// of the "mailing-list" service. +// FindParentGroupsioService may return the following errors: +// - "NotFound" (type *NotFoundError): Parent service not found // - "BadRequest" (type *BadRequestError): Bad request -// - "NotFound" (type *NotFoundError): Service settings not found -// - "Conflict" (type *ConflictError): Conflict - ETag mismatch // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) UpdateGrpsioServiceSettings(ctx context.Context, p *UpdateGrpsioServiceSettingsPayload) (res *GrpsIoServiceSettings, err error) { +func (c *Client) FindParentGroupsioService(ctx context.Context, p *FindParentGroupsioServicePayload) (res *GroupsioService, err error) { var ires any - ires, err = c.UpdateGrpsioServiceSettingsEndpoint(ctx, p) + ires, err = c.FindParentGroupsioServiceEndpoint(ctx, p) if err != nil { return } - return ires.(*GrpsIoServiceSettings), nil + return ires.(*GroupsioService), nil } -// CreateGrpsioMailingList calls the "create-grpsio-mailing-list" endpoint of -// the "mailing-list" service. -// CreateGrpsioMailingList may return the following errors: -// - "BadRequest" (type *BadRequestError): Bad request - Invalid data, missing required fields, or validation failures -// - "NotFound" (type *NotFoundError): Parent service not found or committee not found -// - "Conflict" (type *ConflictError): Mailing list with same name already exists +// ListGroupsioSubgroups calls the "list-groupsio-subgroups" endpoint of the +// "mailing-list" service. +// ListGroupsioSubgroups may return the following errors: +// - "BadRequest" (type *BadRequestError): Bad request // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) CreateGrpsioMailingList(ctx context.Context, p *CreateGrpsioMailingListPayload) (res *GrpsIoMailingListFull, err error) { +func (c *Client) ListGroupsioSubgroups(ctx context.Context, p *ListGroupsioSubgroupsPayload) (res *GroupsioSubgroupList, err error) { var ires any - ires, err = c.CreateGrpsioMailingListEndpoint(ctx, p) + ires, err = c.ListGroupsioSubgroupsEndpoint(ctx, p) if err != nil { return } - return ires.(*GrpsIoMailingListFull), nil + return ires.(*GroupsioSubgroupList), nil } -// GetGrpsioMailingList calls the "get-grpsio-mailing-list" endpoint of the +// CreateGroupsioSubgroup calls the "create-groupsio-subgroup" endpoint of the // "mailing-list" service. -// GetGrpsioMailingList may return the following errors: +// CreateGroupsioSubgroup may return the following errors: // - "BadRequest" (type *BadRequestError): Bad request -// - "NotFound" (type *NotFoundError): Mailing list not found +// - "Conflict" (type *ConflictError): Conflict // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) GetGrpsioMailingList(ctx context.Context, p *GetGrpsioMailingListPayload) (res *GetGrpsioMailingListResult, err error) { +func (c *Client) CreateGroupsioSubgroup(ctx context.Context, p *CreateGroupsioSubgroupPayload) (res *GroupsioSubgroup, err error) { var ires any - ires, err = c.GetGrpsioMailingListEndpoint(ctx, p) + ires, err = c.CreateGroupsioSubgroupEndpoint(ctx, p) if err != nil { return } - return ires.(*GetGrpsioMailingListResult), nil + return ires.(*GroupsioSubgroup), nil } -// UpdateGrpsioMailingList calls the "update-grpsio-mailing-list" endpoint of -// the "mailing-list" service. -// UpdateGrpsioMailingList may return the following errors: -// - "BadRequest" (type *BadRequestError): Bad request -// - "NotFound" (type *NotFoundError): Mailing list not found -// - "Conflict" (type *ConflictError): Conflict - ETag mismatch or validation failure +// GetGroupsioSubgroup calls the "get-groupsio-subgroup" endpoint of the +// "mailing-list" service. +// GetGroupsioSubgroup may return the following errors: +// - "NotFound" (type *NotFoundError): Subgroup not found // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) UpdateGrpsioMailingList(ctx context.Context, p *UpdateGrpsioMailingListPayload) (res *GrpsIoMailingListWithReadonlyAttributes, err error) { +func (c *Client) GetGroupsioSubgroup(ctx context.Context, p *GetGroupsioSubgroupPayload) (res *GroupsioSubgroup, err error) { var ires any - ires, err = c.UpdateGrpsioMailingListEndpoint(ctx, p) + ires, err = c.GetGroupsioSubgroupEndpoint(ctx, p) if err != nil { return } - return ires.(*GrpsIoMailingListWithReadonlyAttributes), nil + return ires.(*GroupsioSubgroup), nil } -// DeleteGrpsioMailingList calls the "delete-grpsio-mailing-list" endpoint of -// the "mailing-list" service. -// DeleteGrpsioMailingList may return the following errors: +// UpdateGroupsioSubgroup calls the "update-groupsio-subgroup" endpoint of the +// "mailing-list" service. +// UpdateGroupsioSubgroup may return the following errors: // - "BadRequest" (type *BadRequestError): Bad request -// - "NotFound" (type *NotFoundError): Mailing list not found -// - "Conflict" (type *ConflictError): Conflict - ETag mismatch or deletion not allowed +// - "NotFound" (type *NotFoundError): Subgroup not found +// - "InternalServerError" (type *InternalServerError): Internal server error +// - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable +// - error: internal error +func (c *Client) UpdateGroupsioSubgroup(ctx context.Context, p *UpdateGroupsioSubgroupPayload) (res *GroupsioSubgroup, err error) { + var ires any + ires, err = c.UpdateGroupsioSubgroupEndpoint(ctx, p) + if err != nil { + return + } + return ires.(*GroupsioSubgroup), nil +} + +// DeleteGroupsioSubgroup calls the "delete-groupsio-subgroup" endpoint of the +// "mailing-list" service. +// DeleteGroupsioSubgroup may return the following errors: +// - "NotFound" (type *NotFoundError): Subgroup not found // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) DeleteGrpsioMailingList(ctx context.Context, p *DeleteGrpsioMailingListPayload) (err error) { - _, err = c.DeleteGrpsioMailingListEndpoint(ctx, p) +func (c *Client) DeleteGroupsioSubgroup(ctx context.Context, p *DeleteGroupsioSubgroupPayload) (err error) { + _, err = c.DeleteGroupsioSubgroupEndpoint(ctx, p) return } -// GetGrpsioMailingListSettings calls the "get-grpsio-mailing-list-settings" -// endpoint of the "mailing-list" service. -// GetGrpsioMailingListSettings may return the following errors: +// GetGroupsioSubgroupCount calls the "get-groupsio-subgroup-count" endpoint of +// the "mailing-list" service. +// GetGroupsioSubgroupCount may return the following errors: // - "BadRequest" (type *BadRequestError): Bad request -// - "NotFound" (type *NotFoundError): Mailing list settings not found // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) GetGrpsioMailingListSettings(ctx context.Context, p *GetGrpsioMailingListSettingsPayload) (res *GetGrpsioMailingListSettingsResult, err error) { +func (c *Client) GetGroupsioSubgroupCount(ctx context.Context, p *GetGroupsioSubgroupCountPayload) (res *GroupsioCount, err error) { var ires any - ires, err = c.GetGrpsioMailingListSettingsEndpoint(ctx, p) + ires, err = c.GetGroupsioSubgroupCountEndpoint(ctx, p) if err != nil { return } - return ires.(*GetGrpsioMailingListSettingsResult), nil + return ires.(*GroupsioCount), nil } -// UpdateGrpsioMailingListSettings calls the -// "update-grpsio-mailing-list-settings" endpoint of the "mailing-list" service. -// UpdateGrpsioMailingListSettings may return the following errors: -// - "BadRequest" (type *BadRequestError): Bad request -// - "NotFound" (type *NotFoundError): Mailing list settings not found -// - "Conflict" (type *ConflictError): Conflict - ETag mismatch +// GetGroupsioSubgroupMemberCount calls the +// "get-groupsio-subgroup-member-count" endpoint of the "mailing-list" service. +// GetGroupsioSubgroupMemberCount may return the following errors: +// - "NotFound" (type *NotFoundError): Subgroup not found // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) UpdateGrpsioMailingListSettings(ctx context.Context, p *UpdateGrpsioMailingListSettingsPayload) (res *GrpsIoMailingListSettings, err error) { +func (c *Client) GetGroupsioSubgroupMemberCount(ctx context.Context, p *GetGroupsioSubgroupMemberCountPayload) (res *GroupsioCount, err error) { var ires any - ires, err = c.UpdateGrpsioMailingListSettingsEndpoint(ctx, p) + ires, err = c.GetGroupsioSubgroupMemberCountEndpoint(ctx, p) if err != nil { return } - return ires.(*GrpsIoMailingListSettings), nil + return ires.(*GroupsioCount), nil } -// CreateGrpsioMailingListMember calls the "create-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -// CreateGrpsioMailingListMember may return the following errors: +// ListGroupsioMembers calls the "list-groupsio-members" endpoint of the +// "mailing-list" service. +// ListGroupsioMembers may return the following errors: +// - "NotFound" (type *NotFoundError): Subgroup not found +// - "InternalServerError" (type *InternalServerError): Internal server error +// - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable +// - error: internal error +func (c *Client) ListGroupsioMembers(ctx context.Context, p *ListGroupsioMembersPayload) (res *GroupsioMemberList, err error) { + var ires any + ires, err = c.ListGroupsioMembersEndpoint(ctx, p) + if err != nil { + return + } + return ires.(*GroupsioMemberList), nil +} + +// AddGroupsioMember calls the "add-groupsio-member" endpoint of the +// "mailing-list" service. +// AddGroupsioMember may return the following errors: // - "BadRequest" (type *BadRequestError): Bad request -// - "NotFound" (type *NotFoundError): Mailing list not found +// - "NotFound" (type *NotFoundError): Subgroup not found // - "Conflict" (type *ConflictError): Member already exists // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) CreateGrpsioMailingListMember(ctx context.Context, p *CreateGrpsioMailingListMemberPayload) (res *GrpsIoMemberFull, err error) { +func (c *Client) AddGroupsioMember(ctx context.Context, p *AddGroupsioMemberPayload) (res *GroupsioMember, err error) { var ires any - ires, err = c.CreateGrpsioMailingListMemberEndpoint(ctx, p) + ires, err = c.AddGroupsioMemberEndpoint(ctx, p) if err != nil { return } - return ires.(*GrpsIoMemberFull), nil + return ires.(*GroupsioMember), nil } -// GetGrpsioMailingListMember calls the "get-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -// GetGrpsioMailingListMember may return the following errors: -// - "BadRequest" (type *BadRequestError): Bad request +// GetGroupsioMember calls the "get-groupsio-member" endpoint of the +// "mailing-list" service. +// GetGroupsioMember may return the following errors: // - "NotFound" (type *NotFoundError): Member not found // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) GetGrpsioMailingListMember(ctx context.Context, p *GetGrpsioMailingListMemberPayload) (res *GetGrpsioMailingListMemberResult, err error) { +func (c *Client) GetGroupsioMember(ctx context.Context, p *GetGroupsioMemberPayload) (res *GroupsioMember, err error) { var ires any - ires, err = c.GetGrpsioMailingListMemberEndpoint(ctx, p) + ires, err = c.GetGroupsioMemberEndpoint(ctx, p) if err != nil { return } - return ires.(*GetGrpsioMailingListMemberResult), nil + return ires.(*GroupsioMember), nil } -// UpdateGrpsioMailingListMember calls the "update-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -// UpdateGrpsioMailingListMember may return the following errors: -// - "BadRequest" (type *BadRequestError): Bad request - Invalid data or immutable field modification +// UpdateGroupsioMember calls the "update-groupsio-member" endpoint of the +// "mailing-list" service. +// UpdateGroupsioMember may return the following errors: +// - "BadRequest" (type *BadRequestError): Bad request // - "NotFound" (type *NotFoundError): Member not found -// - "Conflict" (type *ConflictError): Conflict - ETag mismatch or validation failure // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) UpdateGrpsioMailingListMember(ctx context.Context, p *UpdateGrpsioMailingListMemberPayload) (res *GrpsIoMemberWithReadonlyAttributes, err error) { +func (c *Client) UpdateGroupsioMember(ctx context.Context, p *UpdateGroupsioMemberPayload) (res *GroupsioMember, err error) { var ires any - ires, err = c.UpdateGrpsioMailingListMemberEndpoint(ctx, p) + ires, err = c.UpdateGroupsioMemberEndpoint(ctx, p) if err != nil { return } - return ires.(*GrpsIoMemberWithReadonlyAttributes), nil + return ires.(*GroupsioMember), nil } -// DeleteGrpsioMailingListMember calls the "delete-grpsio-mailing-list-member" -// endpoint of the "mailing-list" service. -// DeleteGrpsioMailingListMember may return the following errors: -// - "BadRequest" (type *BadRequestError): Bad request - Cannot remove sole owner +// DeleteGroupsioMember calls the "delete-groupsio-member" endpoint of the +// "mailing-list" service. +// DeleteGroupsioMember may return the following errors: // - "NotFound" (type *NotFoundError): Member not found -// - "Conflict" (type *ConflictError): Conflict - ETag mismatch // - "InternalServerError" (type *InternalServerError): Internal server error // - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) DeleteGrpsioMailingListMember(ctx context.Context, p *DeleteGrpsioMailingListMemberPayload) (err error) { - _, err = c.DeleteGrpsioMailingListMemberEndpoint(ctx, p) +func (c *Client) DeleteGroupsioMember(ctx context.Context, p *DeleteGroupsioMemberPayload) (err error) { + _, err = c.DeleteGroupsioMemberEndpoint(ctx, p) return } -// GroupsioWebhook calls the "groupsio-webhook" endpoint of the "mailing-list" -// service. -// GroupsioWebhook may return the following errors: -// - "BadRequest" (type *BadRequestError): Invalid webhook payload or signature -// - "Unauthorized" (type *UnauthorizedError): Invalid webhook signature +// InviteGroupsioMembers calls the "invite-groupsio-members" endpoint of the +// "mailing-list" service. +// InviteGroupsioMembers may return the following errors: +// - "BadRequest" (type *BadRequestError): Bad request +// - "NotFound" (type *NotFoundError): Subgroup not found +// - "InternalServerError" (type *InternalServerError): Internal server error +// - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable // - error: internal error -func (c *Client) GroupsioWebhook(ctx context.Context, p *GroupsioWebhookPayload) (err error) { - _, err = c.GroupsioWebhookEndpoint(ctx, p) +func (c *Client) InviteGroupsioMembers(ctx context.Context, p *InviteGroupsioMembersPayload) (err error) { + _, err = c.InviteGroupsioMembersEndpoint(ctx, p) return } + +// CheckGroupsioSubscriber calls the "check-groupsio-subscriber" endpoint of +// the "mailing-list" service. +// CheckGroupsioSubscriber may return the following errors: +// - "BadRequest" (type *BadRequestError): Bad request +// - "InternalServerError" (type *InternalServerError): Internal server error +// - "ServiceUnavailable" (type *ServiceUnavailableError): Service unavailable +// - error: internal error +func (c *Client) CheckGroupsioSubscriber(ctx context.Context, p *CheckGroupsioSubscriberPayload) (res *GroupsioCheckSubscriberResponse, err error) { + var ires any + ires, err = c.CheckGroupsioSubscriberEndpoint(ctx, p) + if err != nil { + return + } + return ires.(*GroupsioCheckSubscriberResponse), nil +} diff --git a/gen/mailing_list/endpoints.go b/gen/mailing_list/endpoints.go index ad8da7b..8cc241e 100644 --- a/gen/mailing_list/endpoints.go +++ b/gen/mailing_list/endpoints.go @@ -17,25 +17,29 @@ import ( // Endpoints wraps the "mailing-list" service endpoints. type Endpoints struct { - Livez goa.Endpoint - Readyz goa.Endpoint - CreateGrpsioService goa.Endpoint - GetGrpsioService goa.Endpoint - UpdateGrpsioService goa.Endpoint - DeleteGrpsioService goa.Endpoint - GetGrpsioServiceSettings goa.Endpoint - UpdateGrpsioServiceSettings goa.Endpoint - CreateGrpsioMailingList goa.Endpoint - GetGrpsioMailingList goa.Endpoint - UpdateGrpsioMailingList goa.Endpoint - DeleteGrpsioMailingList goa.Endpoint - GetGrpsioMailingListSettings goa.Endpoint - UpdateGrpsioMailingListSettings goa.Endpoint - CreateGrpsioMailingListMember goa.Endpoint - GetGrpsioMailingListMember goa.Endpoint - UpdateGrpsioMailingListMember goa.Endpoint - DeleteGrpsioMailingListMember goa.Endpoint - GroupsioWebhook goa.Endpoint + Livez goa.Endpoint + Readyz goa.Endpoint + ListGroupsioServices goa.Endpoint + CreateGroupsioService goa.Endpoint + GetGroupsioService goa.Endpoint + UpdateGroupsioService goa.Endpoint + DeleteGroupsioService goa.Endpoint + GetGroupsioServiceProjects goa.Endpoint + FindParentGroupsioService goa.Endpoint + ListGroupsioSubgroups goa.Endpoint + CreateGroupsioSubgroup goa.Endpoint + GetGroupsioSubgroup goa.Endpoint + UpdateGroupsioSubgroup goa.Endpoint + DeleteGroupsioSubgroup goa.Endpoint + GetGroupsioSubgroupCount goa.Endpoint + GetGroupsioSubgroupMemberCount goa.Endpoint + ListGroupsioMembers goa.Endpoint + AddGroupsioMember goa.Endpoint + GetGroupsioMember goa.Endpoint + UpdateGroupsioMember goa.Endpoint + DeleteGroupsioMember goa.Endpoint + InviteGroupsioMembers goa.Endpoint + CheckGroupsioSubscriber goa.Endpoint } // NewEndpoints wraps the methods of the "mailing-list" service with endpoints. @@ -43,25 +47,29 @@ func NewEndpoints(s Service) *Endpoints { // Casting service to Auther interface a := s.(Auther) return &Endpoints{ - Livez: NewLivezEndpoint(s), - Readyz: NewReadyzEndpoint(s), - CreateGrpsioService: NewCreateGrpsioServiceEndpoint(s, a.JWTAuth), - GetGrpsioService: NewGetGrpsioServiceEndpoint(s, a.JWTAuth), - UpdateGrpsioService: NewUpdateGrpsioServiceEndpoint(s, a.JWTAuth), - DeleteGrpsioService: NewDeleteGrpsioServiceEndpoint(s, a.JWTAuth), - GetGrpsioServiceSettings: NewGetGrpsioServiceSettingsEndpoint(s, a.JWTAuth), - UpdateGrpsioServiceSettings: NewUpdateGrpsioServiceSettingsEndpoint(s, a.JWTAuth), - CreateGrpsioMailingList: NewCreateGrpsioMailingListEndpoint(s, a.JWTAuth), - GetGrpsioMailingList: NewGetGrpsioMailingListEndpoint(s, a.JWTAuth), - UpdateGrpsioMailingList: NewUpdateGrpsioMailingListEndpoint(s, a.JWTAuth), - DeleteGrpsioMailingList: NewDeleteGrpsioMailingListEndpoint(s, a.JWTAuth), - GetGrpsioMailingListSettings: NewGetGrpsioMailingListSettingsEndpoint(s, a.JWTAuth), - UpdateGrpsioMailingListSettings: NewUpdateGrpsioMailingListSettingsEndpoint(s, a.JWTAuth), - CreateGrpsioMailingListMember: NewCreateGrpsioMailingListMemberEndpoint(s, a.JWTAuth), - GetGrpsioMailingListMember: NewGetGrpsioMailingListMemberEndpoint(s, a.JWTAuth), - UpdateGrpsioMailingListMember: NewUpdateGrpsioMailingListMemberEndpoint(s, a.JWTAuth), - DeleteGrpsioMailingListMember: NewDeleteGrpsioMailingListMemberEndpoint(s, a.JWTAuth), - GroupsioWebhook: NewGroupsioWebhookEndpoint(s), + Livez: NewLivezEndpoint(s), + Readyz: NewReadyzEndpoint(s), + ListGroupsioServices: NewListGroupsioServicesEndpoint(s, a.JWTAuth), + CreateGroupsioService: NewCreateGroupsioServiceEndpoint(s, a.JWTAuth), + GetGroupsioService: NewGetGroupsioServiceEndpoint(s, a.JWTAuth), + UpdateGroupsioService: NewUpdateGroupsioServiceEndpoint(s, a.JWTAuth), + DeleteGroupsioService: NewDeleteGroupsioServiceEndpoint(s, a.JWTAuth), + GetGroupsioServiceProjects: NewGetGroupsioServiceProjectsEndpoint(s, a.JWTAuth), + FindParentGroupsioService: NewFindParentGroupsioServiceEndpoint(s, a.JWTAuth), + ListGroupsioSubgroups: NewListGroupsioSubgroupsEndpoint(s, a.JWTAuth), + CreateGroupsioSubgroup: NewCreateGroupsioSubgroupEndpoint(s, a.JWTAuth), + GetGroupsioSubgroup: NewGetGroupsioSubgroupEndpoint(s, a.JWTAuth), + UpdateGroupsioSubgroup: NewUpdateGroupsioSubgroupEndpoint(s, a.JWTAuth), + DeleteGroupsioSubgroup: NewDeleteGroupsioSubgroupEndpoint(s, a.JWTAuth), + GetGroupsioSubgroupCount: NewGetGroupsioSubgroupCountEndpoint(s, a.JWTAuth), + GetGroupsioSubgroupMemberCount: NewGetGroupsioSubgroupMemberCountEndpoint(s, a.JWTAuth), + ListGroupsioMembers: NewListGroupsioMembersEndpoint(s, a.JWTAuth), + AddGroupsioMember: NewAddGroupsioMemberEndpoint(s, a.JWTAuth), + GetGroupsioMember: NewGetGroupsioMemberEndpoint(s, a.JWTAuth), + UpdateGroupsioMember: NewUpdateGroupsioMemberEndpoint(s, a.JWTAuth), + DeleteGroupsioMember: NewDeleteGroupsioMemberEndpoint(s, a.JWTAuth), + InviteGroupsioMembers: NewInviteGroupsioMembersEndpoint(s, a.JWTAuth), + CheckGroupsioSubscriber: NewCheckGroupsioSubscriberEndpoint(s, a.JWTAuth), } } @@ -69,23 +77,27 @@ func NewEndpoints(s Service) *Endpoints { func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) { e.Livez = m(e.Livez) e.Readyz = m(e.Readyz) - e.CreateGrpsioService = m(e.CreateGrpsioService) - e.GetGrpsioService = m(e.GetGrpsioService) - e.UpdateGrpsioService = m(e.UpdateGrpsioService) - e.DeleteGrpsioService = m(e.DeleteGrpsioService) - e.GetGrpsioServiceSettings = m(e.GetGrpsioServiceSettings) - e.UpdateGrpsioServiceSettings = m(e.UpdateGrpsioServiceSettings) - e.CreateGrpsioMailingList = m(e.CreateGrpsioMailingList) - e.GetGrpsioMailingList = m(e.GetGrpsioMailingList) - e.UpdateGrpsioMailingList = m(e.UpdateGrpsioMailingList) - e.DeleteGrpsioMailingList = m(e.DeleteGrpsioMailingList) - e.GetGrpsioMailingListSettings = m(e.GetGrpsioMailingListSettings) - e.UpdateGrpsioMailingListSettings = m(e.UpdateGrpsioMailingListSettings) - e.CreateGrpsioMailingListMember = m(e.CreateGrpsioMailingListMember) - e.GetGrpsioMailingListMember = m(e.GetGrpsioMailingListMember) - e.UpdateGrpsioMailingListMember = m(e.UpdateGrpsioMailingListMember) - e.DeleteGrpsioMailingListMember = m(e.DeleteGrpsioMailingListMember) - e.GroupsioWebhook = m(e.GroupsioWebhook) + e.ListGroupsioServices = m(e.ListGroupsioServices) + e.CreateGroupsioService = m(e.CreateGroupsioService) + e.GetGroupsioService = m(e.GetGroupsioService) + e.UpdateGroupsioService = m(e.UpdateGroupsioService) + e.DeleteGroupsioService = m(e.DeleteGroupsioService) + e.GetGroupsioServiceProjects = m(e.GetGroupsioServiceProjects) + e.FindParentGroupsioService = m(e.FindParentGroupsioService) + e.ListGroupsioSubgroups = m(e.ListGroupsioSubgroups) + e.CreateGroupsioSubgroup = m(e.CreateGroupsioSubgroup) + e.GetGroupsioSubgroup = m(e.GetGroupsioSubgroup) + e.UpdateGroupsioSubgroup = m(e.UpdateGroupsioSubgroup) + e.DeleteGroupsioSubgroup = m(e.DeleteGroupsioSubgroup) + e.GetGroupsioSubgroupCount = m(e.GetGroupsioSubgroupCount) + e.GetGroupsioSubgroupMemberCount = m(e.GetGroupsioSubgroupMemberCount) + e.ListGroupsioMembers = m(e.ListGroupsioMembers) + e.AddGroupsioMember = m(e.AddGroupsioMember) + e.GetGroupsioMember = m(e.GetGroupsioMember) + e.UpdateGroupsioMember = m(e.UpdateGroupsioMember) + e.DeleteGroupsioMember = m(e.DeleteGroupsioMember) + e.InviteGroupsioMembers = m(e.InviteGroupsioMembers) + e.CheckGroupsioSubscriber = m(e.CheckGroupsioSubscriber) } // NewLivezEndpoint returns an endpoint function that calls the method "livez" @@ -104,11 +116,11 @@ func NewReadyzEndpoint(s Service) goa.Endpoint { } } -// NewCreateGrpsioServiceEndpoint returns an endpoint function that calls the -// method "create-grpsio-service" of service "mailing-list". -func NewCreateGrpsioServiceEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewListGroupsioServicesEndpoint returns an endpoint function that calls the +// method "list-groupsio-services" of service "mailing-list". +func NewListGroupsioServicesEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*CreateGrpsioServicePayload) + p := req.(*ListGroupsioServicesPayload) var err error sc := security.JWTScheme{ Name: "jwt", @@ -123,15 +135,15 @@ func NewCreateGrpsioServiceEndpoint(s Service, authJWTFn security.AuthJWTFunc) g if err != nil { return nil, err } - return s.CreateGrpsioService(ctx, p) + return s.ListGroupsioServices(ctx, p) } } -// NewGetGrpsioServiceEndpoint returns an endpoint function that calls the -// method "get-grpsio-service" of service "mailing-list". -func NewGetGrpsioServiceEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewCreateGroupsioServiceEndpoint returns an endpoint function that calls the +// method "create-groupsio-service" of service "mailing-list". +func NewCreateGroupsioServiceEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*GetGrpsioServicePayload) + p := req.(*CreateGroupsioServicePayload) var err error sc := security.JWTScheme{ Name: "jwt", @@ -146,15 +158,15 @@ func NewGetGrpsioServiceEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa. if err != nil { return nil, err } - return s.GetGrpsioService(ctx, p) + return s.CreateGroupsioService(ctx, p) } } -// NewUpdateGrpsioServiceEndpoint returns an endpoint function that calls the -// method "update-grpsio-service" of service "mailing-list". -func NewUpdateGrpsioServiceEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewGetGroupsioServiceEndpoint returns an endpoint function that calls the +// method "get-groupsio-service" of service "mailing-list". +func NewGetGroupsioServiceEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*UpdateGrpsioServicePayload) + p := req.(*GetGroupsioServicePayload) var err error sc := security.JWTScheme{ Name: "jwt", @@ -169,15 +181,15 @@ func NewUpdateGrpsioServiceEndpoint(s Service, authJWTFn security.AuthJWTFunc) g if err != nil { return nil, err } - return s.UpdateGrpsioService(ctx, p) + return s.GetGroupsioService(ctx, p) } } -// NewDeleteGrpsioServiceEndpoint returns an endpoint function that calls the -// method "delete-grpsio-service" of service "mailing-list". -func NewDeleteGrpsioServiceEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewUpdateGroupsioServiceEndpoint returns an endpoint function that calls the +// method "update-groupsio-service" of service "mailing-list". +func NewUpdateGroupsioServiceEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*DeleteGrpsioServicePayload) + p := req.(*UpdateGroupsioServicePayload) var err error sc := security.JWTScheme{ Name: "jwt", @@ -192,15 +204,15 @@ func NewDeleteGrpsioServiceEndpoint(s Service, authJWTFn security.AuthJWTFunc) g if err != nil { return nil, err } - return nil, s.DeleteGrpsioService(ctx, p) + return s.UpdateGroupsioService(ctx, p) } } -// NewGetGrpsioServiceSettingsEndpoint returns an endpoint function that calls -// the method "get-grpsio-service-settings" of service "mailing-list". -func NewGetGrpsioServiceSettingsEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewDeleteGroupsioServiceEndpoint returns an endpoint function that calls the +// method "delete-groupsio-service" of service "mailing-list". +func NewDeleteGroupsioServiceEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*GetGrpsioServiceSettingsPayload) + p := req.(*DeleteGroupsioServicePayload) var err error sc := security.JWTScheme{ Name: "jwt", @@ -215,15 +227,15 @@ func NewGetGrpsioServiceSettingsEndpoint(s Service, authJWTFn security.AuthJWTFu if err != nil { return nil, err } - return s.GetGrpsioServiceSettings(ctx, p) + return nil, s.DeleteGroupsioService(ctx, p) } } -// NewUpdateGrpsioServiceSettingsEndpoint returns an endpoint function that -// calls the method "update-grpsio-service-settings" of service "mailing-list". -func NewUpdateGrpsioServiceSettingsEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewGetGroupsioServiceProjectsEndpoint returns an endpoint function that +// calls the method "get-groupsio-service-projects" of service "mailing-list". +func NewGetGroupsioServiceProjectsEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*UpdateGrpsioServiceSettingsPayload) + p := req.(*GetGroupsioServiceProjectsPayload) var err error sc := security.JWTScheme{ Name: "jwt", @@ -238,15 +250,15 @@ func NewUpdateGrpsioServiceSettingsEndpoint(s Service, authJWTFn security.AuthJW if err != nil { return nil, err } - return s.UpdateGrpsioServiceSettings(ctx, p) + return s.GetGroupsioServiceProjects(ctx, p) } } -// NewCreateGrpsioMailingListEndpoint returns an endpoint function that calls -// the method "create-grpsio-mailing-list" of service "mailing-list". -func NewCreateGrpsioMailingListEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewFindParentGroupsioServiceEndpoint returns an endpoint function that calls +// the method "find-parent-groupsio-service" of service "mailing-list". +func NewFindParentGroupsioServiceEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*CreateGrpsioMailingListPayload) + p := req.(*FindParentGroupsioServicePayload) var err error sc := security.JWTScheme{ Name: "jwt", @@ -261,34 +273,38 @@ func NewCreateGrpsioMailingListEndpoint(s Service, authJWTFn security.AuthJWTFun if err != nil { return nil, err } - return s.CreateGrpsioMailingList(ctx, p) + return s.FindParentGroupsioService(ctx, p) } } -// NewGetGrpsioMailingListEndpoint returns an endpoint function that calls the -// method "get-grpsio-mailing-list" of service "mailing-list". -func NewGetGrpsioMailingListEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewListGroupsioSubgroupsEndpoint returns an endpoint function that calls the +// method "list-groupsio-subgroups" of service "mailing-list". +func NewListGroupsioSubgroupsEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*GetGrpsioMailingListPayload) + p := req.(*ListGroupsioSubgroupsPayload) var err error sc := security.JWTScheme{ Name: "jwt", Scopes: []string{}, RequiredScopes: []string{}, } - ctx, err = authJWTFn(ctx, p.BearerToken, &sc) + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) if err != nil { return nil, err } - return s.GetGrpsioMailingList(ctx, p) + return s.ListGroupsioSubgroups(ctx, p) } } -// NewUpdateGrpsioMailingListEndpoint returns an endpoint function that calls -// the method "update-grpsio-mailing-list" of service "mailing-list". -func NewUpdateGrpsioMailingListEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewCreateGroupsioSubgroupEndpoint returns an endpoint function that calls +// the method "create-groupsio-subgroup" of service "mailing-list". +func NewCreateGroupsioSubgroupEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*UpdateGrpsioMailingListPayload) + p := req.(*CreateGroupsioSubgroupPayload) var err error sc := security.JWTScheme{ Name: "jwt", @@ -303,15 +319,15 @@ func NewUpdateGrpsioMailingListEndpoint(s Service, authJWTFn security.AuthJWTFun if err != nil { return nil, err } - return s.UpdateGrpsioMailingList(ctx, p) + return s.CreateGroupsioSubgroup(ctx, p) } } -// NewDeleteGrpsioMailingListEndpoint returns an endpoint function that calls -// the method "delete-grpsio-mailing-list" of service "mailing-list". -func NewDeleteGrpsioMailingListEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewGetGroupsioSubgroupEndpoint returns an endpoint function that calls the +// method "get-groupsio-subgroup" of service "mailing-list". +func NewGetGroupsioSubgroupEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*DeleteGrpsioMailingListPayload) + p := req.(*GetGroupsioSubgroupPayload) var err error sc := security.JWTScheme{ Name: "jwt", @@ -326,16 +342,15 @@ func NewDeleteGrpsioMailingListEndpoint(s Service, authJWTFn security.AuthJWTFun if err != nil { return nil, err } - return nil, s.DeleteGrpsioMailingList(ctx, p) + return s.GetGroupsioSubgroup(ctx, p) } } -// NewGetGrpsioMailingListSettingsEndpoint returns an endpoint function that -// calls the method "get-grpsio-mailing-list-settings" of service -// "mailing-list". -func NewGetGrpsioMailingListSettingsEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewUpdateGroupsioSubgroupEndpoint returns an endpoint function that calls +// the method "update-groupsio-subgroup" of service "mailing-list". +func NewUpdateGroupsioSubgroupEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*GetGrpsioMailingListSettingsPayload) + p := req.(*UpdateGroupsioSubgroupPayload) var err error sc := security.JWTScheme{ Name: "jwt", @@ -350,16 +365,38 @@ func NewGetGrpsioMailingListSettingsEndpoint(s Service, authJWTFn security.AuthJ if err != nil { return nil, err } - return s.GetGrpsioMailingListSettings(ctx, p) + return s.UpdateGroupsioSubgroup(ctx, p) } } -// NewUpdateGrpsioMailingListSettingsEndpoint returns an endpoint function that -// calls the method "update-grpsio-mailing-list-settings" of service -// "mailing-list". -func NewUpdateGrpsioMailingListSettingsEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewDeleteGroupsioSubgroupEndpoint returns an endpoint function that calls +// the method "delete-groupsio-subgroup" of service "mailing-list". +func NewDeleteGroupsioSubgroupEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*DeleteGroupsioSubgroupPayload) + var err error + sc := security.JWTScheme{ + Name: "jwt", + Scopes: []string{}, + RequiredScopes: []string{}, + } + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) + if err != nil { + return nil, err + } + return nil, s.DeleteGroupsioSubgroup(ctx, p) + } +} + +// NewGetGroupsioSubgroupCountEndpoint returns an endpoint function that calls +// the method "get-groupsio-subgroup-count" of service "mailing-list". +func NewGetGroupsioSubgroupCountEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*UpdateGrpsioMailingListSettingsPayload) + p := req.(*GetGroupsioSubgroupCountPayload) var err error sc := security.JWTScheme{ Name: "jwt", @@ -374,16 +411,16 @@ func NewUpdateGrpsioMailingListSettingsEndpoint(s Service, authJWTFn security.Au if err != nil { return nil, err } - return s.UpdateGrpsioMailingListSettings(ctx, p) + return s.GetGroupsioSubgroupCount(ctx, p) } } -// NewCreateGrpsioMailingListMemberEndpoint returns an endpoint function that -// calls the method "create-grpsio-mailing-list-member" of service +// NewGetGroupsioSubgroupMemberCountEndpoint returns an endpoint function that +// calls the method "get-groupsio-subgroup-member-count" of service // "mailing-list". -func NewCreateGrpsioMailingListMemberEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +func NewGetGroupsioSubgroupMemberCountEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*CreateGrpsioMailingListMemberPayload) + p := req.(*GetGroupsioSubgroupMemberCountPayload) var err error sc := security.JWTScheme{ Name: "jwt", @@ -398,74 +435,167 @@ func NewCreateGrpsioMailingListMemberEndpoint(s Service, authJWTFn security.Auth if err != nil { return nil, err } - return s.CreateGrpsioMailingListMember(ctx, p) + return s.GetGroupsioSubgroupMemberCount(ctx, p) } } -// NewGetGrpsioMailingListMemberEndpoint returns an endpoint function that -// calls the method "get-grpsio-mailing-list-member" of service "mailing-list". -func NewGetGrpsioMailingListMemberEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewListGroupsioMembersEndpoint returns an endpoint function that calls the +// method "list-groupsio-members" of service "mailing-list". +func NewListGroupsioMembersEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*GetGrpsioMailingListMemberPayload) + p := req.(*ListGroupsioMembersPayload) var err error sc := security.JWTScheme{ Name: "jwt", Scopes: []string{}, RequiredScopes: []string{}, } - ctx, err = authJWTFn(ctx, p.BearerToken, &sc) + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) if err != nil { return nil, err } - return s.GetGrpsioMailingListMember(ctx, p) + return s.ListGroupsioMembers(ctx, p) } } -// NewUpdateGrpsioMailingListMemberEndpoint returns an endpoint function that -// calls the method "update-grpsio-mailing-list-member" of service -// "mailing-list". -func NewUpdateGrpsioMailingListMemberEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewAddGroupsioMemberEndpoint returns an endpoint function that calls the +// method "add-groupsio-member" of service "mailing-list". +func NewAddGroupsioMemberEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*UpdateGrpsioMailingListMemberPayload) + p := req.(*AddGroupsioMemberPayload) var err error sc := security.JWTScheme{ Name: "jwt", Scopes: []string{}, RequiredScopes: []string{}, } - ctx, err = authJWTFn(ctx, p.BearerToken, &sc) + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) if err != nil { return nil, err } - return s.UpdateGrpsioMailingListMember(ctx, p) + return s.AddGroupsioMember(ctx, p) } } -// NewDeleteGrpsioMailingListMemberEndpoint returns an endpoint function that -// calls the method "delete-grpsio-mailing-list-member" of service -// "mailing-list". -func NewDeleteGrpsioMailingListMemberEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { +// NewGetGroupsioMemberEndpoint returns an endpoint function that calls the +// method "get-groupsio-member" of service "mailing-list". +func NewGetGroupsioMemberEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*GetGroupsioMemberPayload) + var err error + sc := security.JWTScheme{ + Name: "jwt", + Scopes: []string{}, + RequiredScopes: []string{}, + } + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) + if err != nil { + return nil, err + } + return s.GetGroupsioMember(ctx, p) + } +} + +// NewUpdateGroupsioMemberEndpoint returns an endpoint function that calls the +// method "update-groupsio-member" of service "mailing-list". +func NewUpdateGroupsioMemberEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*DeleteGrpsioMailingListMemberPayload) + p := req.(*UpdateGroupsioMemberPayload) var err error sc := security.JWTScheme{ Name: "jwt", Scopes: []string{}, RequiredScopes: []string{}, } - ctx, err = authJWTFn(ctx, p.BearerToken, &sc) + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) + if err != nil { + return nil, err + } + return s.UpdateGroupsioMember(ctx, p) + } +} + +// NewDeleteGroupsioMemberEndpoint returns an endpoint function that calls the +// method "delete-groupsio-member" of service "mailing-list". +func NewDeleteGroupsioMemberEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*DeleteGroupsioMemberPayload) + var err error + sc := security.JWTScheme{ + Name: "jwt", + Scopes: []string{}, + RequiredScopes: []string{}, + } + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) if err != nil { return nil, err } - return nil, s.DeleteGrpsioMailingListMember(ctx, p) + return nil, s.DeleteGroupsioMember(ctx, p) } } -// NewGroupsioWebhookEndpoint returns an endpoint function that calls the -// method "groupsio-webhook" of service "mailing-list". -func NewGroupsioWebhookEndpoint(s Service) goa.Endpoint { +// NewInviteGroupsioMembersEndpoint returns an endpoint function that calls the +// method "invite-groupsio-members" of service "mailing-list". +func NewInviteGroupsioMembersEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { return func(ctx context.Context, req any) (any, error) { - p := req.(*GroupsioWebhookPayload) - return nil, s.GroupsioWebhook(ctx, p) + p := req.(*InviteGroupsioMembersPayload) + var err error + sc := security.JWTScheme{ + Name: "jwt", + Scopes: []string{}, + RequiredScopes: []string{}, + } + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) + if err != nil { + return nil, err + } + return nil, s.InviteGroupsioMembers(ctx, p) + } +} + +// NewCheckGroupsioSubscriberEndpoint returns an endpoint function that calls +// the method "check-groupsio-subscriber" of service "mailing-list". +func NewCheckGroupsioSubscriberEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*CheckGroupsioSubscriberPayload) + var err error + sc := security.JWTScheme{ + Name: "jwt", + Scopes: []string{}, + RequiredScopes: []string{}, + } + var token string + if p.BearerToken != nil { + token = *p.BearerToken + } + ctx, err = authJWTFn(ctx, token, &sc) + if err != nil { + return nil, err + } + return s.CheckGroupsioSubscriber(ctx, p) } } diff --git a/gen/mailing_list/service.go b/gen/mailing_list/service.go index 712ca4b..593aae2 100644 --- a/gen/mailing_list/service.go +++ b/gen/mailing_list/service.go @@ -14,46 +14,55 @@ import ( "goa.design/goa/v3/security" ) -// The mailing list service manages mailing lists and services +// The mailing list service proxies GroupsIO operations to the ITX API type Service interface { // Check if the service is alive. Livez(context.Context) (res []byte, err error) // Check if the service is able to take inbound requests. Readyz(context.Context) (res []byte, err error) - // Create GroupsIO service with type-specific validation rules - CreateGrpsioService(context.Context, *CreateGrpsioServicePayload) (res *GrpsIoServiceFull, err error) - // Get groupsIO service details by ID - GetGrpsioService(context.Context, *GetGrpsioServicePayload) (res *GetGrpsioServiceResult, err error) - // Update GroupsIO service - UpdateGrpsioService(context.Context, *UpdateGrpsioServicePayload) (res *GrpsIoServiceWithReadonlyAttributes, err error) - // Delete GroupsIO service - DeleteGrpsioService(context.Context, *DeleteGrpsioServicePayload) (err error) - // Get GroupsIO service settings (writers and auditors) - GetGrpsioServiceSettings(context.Context, *GetGrpsioServiceSettingsPayload) (res *GetGrpsioServiceSettingsResult, err error) - // Update GroupsIO service settings (writers and auditors) - UpdateGrpsioServiceSettings(context.Context, *UpdateGrpsioServiceSettingsPayload) (res *GrpsIoServiceSettings, err error) - // Create GroupsIO mailing list/subgroup with comprehensive validation - CreateGrpsioMailingList(context.Context, *CreateGrpsioMailingListPayload) (res *GrpsIoMailingListFull, err error) - // Get GroupsIO mailing list details by UID - GetGrpsioMailingList(context.Context, *GetGrpsioMailingListPayload) (res *GetGrpsioMailingListResult, err error) - // Update GroupsIO mailing list - UpdateGrpsioMailingList(context.Context, *UpdateGrpsioMailingListPayload) (res *GrpsIoMailingListWithReadonlyAttributes, err error) - // Delete GroupsIO mailing list - DeleteGrpsioMailingList(context.Context, *DeleteGrpsioMailingListPayload) (err error) - // Get GroupsIO mailing list settings (writers and auditors) - GetGrpsioMailingListSettings(context.Context, *GetGrpsioMailingListSettingsPayload) (res *GetGrpsioMailingListSettingsResult, err error) - // Update GroupsIO mailing list settings (writers and auditors) - UpdateGrpsioMailingListSettings(context.Context, *UpdateGrpsioMailingListSettingsPayload) (res *GrpsIoMailingListSettings, err error) - // Create a new member for a GroupsIO mailing list - CreateGrpsioMailingListMember(context.Context, *CreateGrpsioMailingListMemberPayload) (res *GrpsIoMemberFull, err error) - // Get a member of a GroupsIO mailing list by UID - GetGrpsioMailingListMember(context.Context, *GetGrpsioMailingListMemberPayload) (res *GetGrpsioMailingListMemberResult, err error) - // Update a member of a GroupsIO mailing list - UpdateGrpsioMailingListMember(context.Context, *UpdateGrpsioMailingListMemberPayload) (res *GrpsIoMemberWithReadonlyAttributes, err error) - // Delete a member from a GroupsIO mailing list - DeleteGrpsioMailingListMember(context.Context, *DeleteGrpsioMailingListMemberPayload) (err error) - // Handle GroupsIO webhook events for subgroup and member changes - GroupsioWebhook(context.Context, *GroupsioWebhookPayload) (err error) + // List GroupsIO services, optionally filtered by project UID + ListGroupsioServices(context.Context, *ListGroupsioServicesPayload) (res *GroupsioServiceList, err error) + // Create a GroupsIO service + CreateGroupsioService(context.Context, *CreateGroupsioServicePayload) (res *GroupsioService, err error) + // Get a GroupsIO service by ID + GetGroupsioService(context.Context, *GetGroupsioServicePayload) (res *GroupsioService, err error) + // Update a GroupsIO service + UpdateGroupsioService(context.Context, *UpdateGroupsioServicePayload) (res *GroupsioService, err error) + // Delete a GroupsIO service + DeleteGroupsioService(context.Context, *DeleteGroupsioServicePayload) (err error) + // Get projects that have GroupsIO services + GetGroupsioServiceProjects(context.Context, *GetGroupsioServiceProjectsPayload) (res *GroupsioProjectsResponse, err error) + // Find the parent GroupsIO service for a project + FindParentGroupsioService(context.Context, *FindParentGroupsioServicePayload) (res *GroupsioService, err error) + // List GroupsIO subgroups, optionally filtered by project UID and/or committee + // UID + ListGroupsioSubgroups(context.Context, *ListGroupsioSubgroupsPayload) (res *GroupsioSubgroupList, err error) + // Create a GroupsIO subgroup + CreateGroupsioSubgroup(context.Context, *CreateGroupsioSubgroupPayload) (res *GroupsioSubgroup, err error) + // Get a GroupsIO subgroup by ID + GetGroupsioSubgroup(context.Context, *GetGroupsioSubgroupPayload) (res *GroupsioSubgroup, err error) + // Update a GroupsIO subgroup + UpdateGroupsioSubgroup(context.Context, *UpdateGroupsioSubgroupPayload) (res *GroupsioSubgroup, err error) + // Delete a GroupsIO subgroup + DeleteGroupsioSubgroup(context.Context, *DeleteGroupsioSubgroupPayload) (err error) + // Get count of GroupsIO subgroups for a project + GetGroupsioSubgroupCount(context.Context, *GetGroupsioSubgroupCountPayload) (res *GroupsioCount, err error) + // Get count of members in a GroupsIO subgroup + GetGroupsioSubgroupMemberCount(context.Context, *GetGroupsioSubgroupMemberCountPayload) (res *GroupsioCount, err error) + // List members of a GroupsIO subgroup + ListGroupsioMembers(context.Context, *ListGroupsioMembersPayload) (res *GroupsioMemberList, err error) + // Add a member to a GroupsIO subgroup + AddGroupsioMember(context.Context, *AddGroupsioMemberPayload) (res *GroupsioMember, err error) + // Get a member of a GroupsIO subgroup by ID + GetGroupsioMember(context.Context, *GetGroupsioMemberPayload) (res *GroupsioMember, err error) + // Update a member of a GroupsIO subgroup + UpdateGroupsioMember(context.Context, *UpdateGroupsioMemberPayload) (res *GroupsioMember, err error) + // Delete a member from a GroupsIO subgroup + DeleteGroupsioMember(context.Context, *DeleteGroupsioMemberPayload) (err error) + // Invite members to a GroupsIO subgroup by email + InviteGroupsioMembers(context.Context, *InviteGroupsioMembersPayload) (err error) + // Check if an email address is subscribed to a GroupsIO subgroup + CheckGroupsioSubscriber(context.Context, *CheckGroupsioSubscriberPayload) (res *GroupsioCheckSubscriberResponse, err error) } // Auther defines the authorization functions to be implemented by the service. @@ -76,733 +85,392 @@ const ServiceName = "mailing-list" // MethodNames lists the service method names as defined in the design. These // are the same values that are set in the endpoint request contexts under the // MethodKey key. -var MethodNames = [19]string{"livez", "readyz", "create-grpsio-service", "get-grpsio-service", "update-grpsio-service", "delete-grpsio-service", "get-grpsio-service-settings", "update-grpsio-service-settings", "create-grpsio-mailing-list", "get-grpsio-mailing-list", "update-grpsio-mailing-list", "delete-grpsio-mailing-list", "get-grpsio-mailing-list-settings", "update-grpsio-mailing-list-settings", "create-grpsio-mailing-list-member", "get-grpsio-mailing-list-member", "update-grpsio-mailing-list-member", "delete-grpsio-mailing-list-member", "groupsio-webhook"} +var MethodNames = [23]string{"livez", "readyz", "list-groupsio-services", "create-groupsio-service", "get-groupsio-service", "update-groupsio-service", "delete-groupsio-service", "get-groupsio-service-projects", "find-parent-groupsio-service", "list-groupsio-subgroups", "create-groupsio-subgroup", "get-groupsio-subgroup", "update-groupsio-subgroup", "delete-groupsio-subgroup", "get-groupsio-subgroup-count", "get-groupsio-subgroup-member-count", "list-groupsio-members", "add-groupsio-member", "get-groupsio-member", "update-groupsio-member", "delete-groupsio-member", "invite-groupsio-members", "check-groupsio-subscriber"} -// Committee associated with a mailing list -type Committee struct { - // Committee UUID - UID string - // Committee name (read-only, populated by server) - Name *string - // Committee member voting statuses that determine which members are synced - AllowedVotingStatuses []string -} - -// CreateGrpsioMailingListMemberPayload is the payload type of the mailing-list -// service create-grpsio-mailing-list-member method. -type CreateGrpsioMailingListMemberPayload struct { +// AddGroupsioMemberPayload is the payload type of the mailing-list service +// add-groupsio-member method. +type AddGroupsioMemberPayload struct { // JWT token issued by Heimdall BearerToken *string - // Version of the API - Version string - // Mailing list UID - UID string - // Member username - Username *string - // Member first name - FirstName *string - // Member last name - LastName *string + // Subgroup ID + SubgroupID string // Member email address - Email string - // Member organization - Organization *string - // Member job title - JobTitle *string - // Member type - MemberType string - // Email delivery mode - DeliveryMode string + Email *string + // Member display name + Name *string // Moderation status - ModStatus string - // Last reviewed timestamp - LastReviewedAt *string - // Last reviewed by user ID - LastReviewedBy *string + ModStatus *string + // Email delivery mode + DeliveryMode *string } -// CreateGrpsioMailingListPayload is the payload type of the mailing-list -// service create-grpsio-mailing-list method. -type CreateGrpsioMailingListPayload struct { +// CheckGroupsioSubscriberPayload is the payload type of the mailing-list +// service check-groupsio-subscriber method. +type CheckGroupsioSubscriberPayload struct { // JWT token issued by Heimdall BearerToken *string - // Version of the API - Version string - // Mailing list group name - GroupName string - // Mailing list group ID - GroupID *int64 - // Whether the mailing list is publicly accessible - Public bool - // Mailing list type - Type string - // public: Anyone can join. approval_required: Users must request to join and - // be approved. invite_only: Only invited users can join. - AudienceAccess string - // Committees associated with this mailing list (OR logic for access control) - Committees []*Committee - // Mailing list description (11-500 characters) - Description string - // Mailing list title - Title string - // Subject tag prefix - SubjectTag *string - // Service UUID - ServiceUID string - // Number of subscribers in this mailing list (read-only, maintained by service) - SubscriberCount *int - // Manager users who can edit/modify this resource - Writers []*UserInfo - // Auditor users who can audit this resource - Auditors []*UserInfo -} - -// CreateGrpsioServicePayload is the payload type of the mailing-list service -// create-grpsio-service method. -type CreateGrpsioServicePayload struct { + // Email address to check + Email string + // Subgroup ID + SubgroupID string +} + +// CreateGroupsioServicePayload is the payload type of the mailing-list service +// create-groupsio-service method. +type CreateGroupsioServicePayload struct { // JWT token issued by Heimdall BearerToken *string - // Version of the API - Version string + // LFX v2 project UID + ProjectUID *string // Service type - Type string - // Service domain - Domain *string + Type *string // GroupsIO group ID GroupID *int64 + // Service domain + Domain *string + // Email prefix + Prefix *string // Service status Status *string - // List of global owner email addresses (required for primary, forbidden for - // shared) - GlobalOwners []string - // Email prefix (required for formation and shared, forbidden for primary) - Prefix *string - // Parent primary service UID (automatically set for shared type services) - ParentServiceUID *string - // Project slug identifier - ProjectSlug *string - // LFXv2 Project UID - ProjectUID string - // Service URL - URL *string - // GroupsIO group name - GroupName *string - // Whether the service is publicly accessible - Public bool - // Manager users who can edit/modify this resource - Writers []*UserInfo - // Auditor users who can audit this resource - Auditors []*UserInfo -} - -// DeleteGrpsioMailingListMemberPayload is the payload type of the mailing-list -// service delete-grpsio-mailing-list-member method. -type DeleteGrpsioMailingListMemberPayload struct { +} + +// CreateGroupsioSubgroupPayload is the payload type of the mailing-list +// service create-groupsio-subgroup method. +type CreateGroupsioSubgroupPayload struct { // JWT token issued by Heimdall - BearerToken string - // Version of the API - Version string - // If-Match header value for conditional requests - IfMatch string - // Mailing list UID -- unique identifier for the mailing list - UID string - // Member UID -- unique identifier for the member - MemberUID string -} - -// DeleteGrpsioMailingListPayload is the payload type of the mailing-list -// service delete-grpsio-mailing-list method. -type DeleteGrpsioMailingListPayload struct { + BearerToken *string + // LFX v2 project UID + ProjectUID *string + // LFX v2 committee UID + CommitteeUID *string + // GroupsIO group ID + GroupID *int64 + // Subgroup name + Name *string + // Subgroup description + Description *string + // Subgroup type + Type *string + // Audience access setting + AudienceAccess *string +} + +// DeleteGroupsioMemberPayload is the payload type of the mailing-list service +// delete-groupsio-member method. +type DeleteGroupsioMemberPayload struct { // JWT token issued by Heimdall BearerToken *string - // Version of the API - Version *string - // If-Match header value for conditional requests - IfMatch *string - // Mailing list UID -- unique identifier for the mailing list - UID *string + // Subgroup ID + SubgroupID string + // Member ID + MemberID string } -// DeleteGrpsioServicePayload is the payload type of the mailing-list service -// delete-grpsio-service method. -type DeleteGrpsioServicePayload struct { +// DeleteGroupsioServicePayload is the payload type of the mailing-list service +// delete-groupsio-service method. +type DeleteGroupsioServicePayload struct { // JWT token issued by Heimdall BearerToken *string - // Version of the API - Version *string - // If-Match header value for conditional requests - IfMatch *string - // Service UID -- unique identifier for the service - UID *string + // Service ID + ServiceID string } -// GetGrpsioMailingListMemberPayload is the payload type of the mailing-list -// service get-grpsio-mailing-list-member method. -type GetGrpsioMailingListMemberPayload struct { +// DeleteGroupsioSubgroupPayload is the payload type of the mailing-list +// service delete-groupsio-subgroup method. +type DeleteGroupsioSubgroupPayload struct { // JWT token issued by Heimdall - BearerToken string - // Version of the API - Version string - // Mailing list UID -- unique identifier for the mailing list - UID string - // Member UID -- unique identifier for the member - MemberUID string -} - -// GetGrpsioMailingListMemberResult is the result type of the mailing-list -// service get-grpsio-mailing-list-member method. -type GetGrpsioMailingListMemberResult struct { - Member *GrpsIoMemberWithReadonlyAttributes - // ETag header value - Etag *string -} - -// GetGrpsioMailingListPayload is the payload type of the mailing-list service -// get-grpsio-mailing-list method. -type GetGrpsioMailingListPayload struct { + BearerToken *string + // Subgroup ID + SubgroupID string +} + +// FindParentGroupsioServicePayload is the payload type of the mailing-list +// service find-parent-groupsio-service method. +type FindParentGroupsioServicePayload struct { // JWT token issued by Heimdall - BearerToken string - // Version of the API - Version string - // Mailing list UID -- unique identifier for the mailing list - UID *string + BearerToken *string + // LFX v2 project UID + ProjectUID string } -// GetGrpsioMailingListResult is the result type of the mailing-list service -// get-grpsio-mailing-list method. -type GetGrpsioMailingListResult struct { - MailingList *GrpsIoMailingListWithReadonlyAttributes - // ETag header value - Etag *string +// GetGroupsioMemberPayload is the payload type of the mailing-list service +// get-groupsio-member method. +type GetGroupsioMemberPayload struct { + // JWT token issued by Heimdall + BearerToken *string + // Subgroup ID + SubgroupID string + // Member ID + MemberID string } -// GetGrpsioMailingListSettingsPayload is the payload type of the mailing-list -// service get-grpsio-mailing-list-settings method. -type GetGrpsioMailingListSettingsPayload struct { +// GetGroupsioServicePayload is the payload type of the mailing-list service +// get-groupsio-service method. +type GetGroupsioServicePayload struct { // JWT token issued by Heimdall BearerToken *string - // Version of the API - Version *string - // Mailing list UID -- unique identifier for the mailing list - UID *string + // Service ID + ServiceID string } -// GetGrpsioMailingListSettingsResult is the result type of the mailing-list -// service get-grpsio-mailing-list-settings method. -type GetGrpsioMailingListSettingsResult struct { - MailingListSettings *GrpsIoMailingListSettings - // ETag header value - Etag *string +// GetGroupsioServiceProjectsPayload is the payload type of the mailing-list +// service get-groupsio-service-projects method. +type GetGroupsioServiceProjectsPayload struct { + // JWT token issued by Heimdall + BearerToken *string } -// GetGrpsioServicePayload is the payload type of the mailing-list service -// get-grpsio-service method. -type GetGrpsioServicePayload struct { +// GetGroupsioSubgroupCountPayload is the payload type of the mailing-list +// service get-groupsio-subgroup-count method. +type GetGroupsioSubgroupCountPayload struct { // JWT token issued by Heimdall BearerToken *string - // Version of the API - Version *string - // Service UID -- unique identifier for the service - UID *string + // LFX v2 project UID + ProjectUID string } -// GetGrpsioServiceResult is the result type of the mailing-list service -// get-grpsio-service method. -type GetGrpsioServiceResult struct { - Service *GrpsIoServiceWithReadonlyAttributes - // ETag header value - Etag *string - // Version of the API - Version string +// GetGroupsioSubgroupMemberCountPayload is the payload type of the +// mailing-list service get-groupsio-subgroup-member-count method. +type GetGroupsioSubgroupMemberCountPayload struct { + // JWT token issued by Heimdall + BearerToken *string + // Subgroup ID + SubgroupID string } -// GetGrpsioServiceSettingsPayload is the payload type of the mailing-list -// service get-grpsio-service-settings method. -type GetGrpsioServiceSettingsPayload struct { +// GetGroupsioSubgroupPayload is the payload type of the mailing-list service +// get-groupsio-subgroup method. +type GetGroupsioSubgroupPayload struct { // JWT token issued by Heimdall BearerToken *string - // Version of the API - Version *string - // Service UID -- unique identifier for the service - UID *string -} - -// GetGrpsioServiceSettingsResult is the result type of the mailing-list -// service get-grpsio-service-settings method. -type GetGrpsioServiceSettingsResult struct { - ServiceSettings *GrpsIoServiceSettings - // ETag header value - Etag *string -} - -// GroupsioWebhookPayload is the payload type of the mailing-list service -// groupsio-webhook method. -type GroupsioWebhookPayload struct { - // The type of webhook event - Action string - // Contains subgroup data from Groups.io - Group any - // Contains member data from Groups.io - MemberInfo any - // Extra data field (subgroup suffix) - Extra *string - // Extra ID field (subgroup ID for deletion) - ExtraID *int - // HMAC-SHA1 base64 signature for verification - Signature string -} - -// GrpsIoMailingListFull is the result type of the mailing-list service -// create-grpsio-mailing-list method. -type GrpsIoMailingListFull struct { - // Mailing list UID -- unique identifier for the mailing list - UID *string - // Mailing list group name - GroupName *string - // Mailing list group ID - GroupID *int64 - // Whether the mailing list is publicly accessible - Public bool - // Mailing list type - Type *string - // public: Anyone can join. approval_required: Users must request to join and - // be approved. invite_only: Only invited users can join. - AudienceAccess string - // Committees associated with this mailing list (OR logic for access control) - Committees []*Committee - // Mailing list description (11-500 characters) - Description *string - // Mailing list title - Title *string - // Subject tag prefix - SubjectTag *string - // Service UUID - ServiceUID *string - // Number of subscribers in this mailing list (read-only, maintained by service) - SubscriberCount *int - // LFXv2 Project UID (inherited from parent service) - ProjectUID *string - // Manager users who can edit/modify this resource - Writers []*UserInfo - // Auditor users who can audit this resource - Auditors []*UserInfo - // Project name (read-only) - ProjectName *string - // Project slug identifier (read-only) - ProjectSlug *string - // The timestamp when the service was created (read-only) - CreatedAt *string - // The timestamp when the service was last updated (read-only) - UpdatedAt *string + // Subgroup ID + SubgroupID string } -// GrpsIoMailingListSettings is the result type of the mailing-list service -// update-grpsio-mailing-list-settings method. -type GrpsIoMailingListSettings struct { - // Mailing list UID -- unique identifier for the mailing list - UID *string - // Manager users who can edit/modify this resource - Writers []*UserInfo - // Auditor users who can audit this resource - Auditors []*UserInfo - // The timestamp when the service was last reviewed in RFC3339 format - LastReviewedAt *string - // The user ID who last reviewed this service - LastReviewedBy *string - // The user ID who last audited the service - LastAuditedBy *string - // The timestamp when the service was last audited - LastAuditedTime *string - // The timestamp when the service was created (read-only) - CreatedAt *string - // The timestamp when the service was last updated (read-only) - UpdatedAt *string +// GroupsioCheckSubscriberResponse is the result type of the mailing-list +// service check-groupsio-subscriber method. +type GroupsioCheckSubscriberResponse struct { + // Whether the email is subscribed + Subscribed bool } -// GrpsIoMailingListWithReadonlyAttributes is the result type of the -// mailing-list service update-grpsio-mailing-list method. -type GrpsIoMailingListWithReadonlyAttributes struct { - // Mailing list UID -- unique identifier for the mailing list - UID *string - // Mailing list group name - GroupName *string - // Mailing list group ID - GroupID *int64 - // Whether the mailing list is publicly accessible - Public bool - // Mailing list type - Type *string - // public: Anyone can join. approval_required: Users must request to join and - // be approved. invite_only: Only invited users can join. - AudienceAccess string - // Committees associated with this mailing list (OR logic for access control) - Committees []*Committee - // Mailing list description (11-500 characters) - Description *string - // Mailing list title - Title *string - // Subject tag prefix - SubjectTag *string - // Service UUID - ServiceUID *string - // Number of subscribers in this mailing list (read-only, maintained by service) - SubscriberCount *int - // LFXv2 Project UID (inherited from parent service) - ProjectUID *string - // Project name (read-only) - ProjectName *string - // Project slug identifier (read-only) - ProjectSlug *string - // The timestamp when the service was created (read-only) - CreatedAt *string - // The timestamp when the service was last updated (read-only) - UpdatedAt *string +// GroupsioCount is the result type of the mailing-list service +// get-groupsio-subgroup-count method. +type GroupsioCount struct { + // Count value + Count int } -// GrpsIoMemberFull is the result type of the mailing-list service -// create-grpsio-mailing-list-member method. -type GrpsIoMemberFull struct { - // Member UID - UID string - // Mailing list UID - MailingListUID string - // Member username - Username *string - // Member first name - FirstName string - // Member last name - LastName string +// GroupsioMember is the result type of the mailing-list service +// add-groupsio-member method. +type GroupsioMember struct { + // Member ID + ID *string + // Subgroup ID + SubgroupID *string // Member email address - Email string - // Member organization - Organization *string - // Member job title - JobTitle *string - // Member type - MemberType string - // Email delivery mode - DeliveryMode string - // Moderation status - ModStatus string - // Last reviewed timestamp - LastReviewedAt *string - // Last reviewed by user ID - LastReviewedBy *string - // Member status - Status string - // Groups.io member ID - MemberID *int64 - // Groups.io group ID - GroupID *int64 - // The timestamp when the service was created (read-only) - CreatedAt string - // The timestamp when the service was last updated (read-only) - UpdatedAt string - // Manager users who can edit/modify this resource - Writers []*UserInfo - // Auditor users who can audit this resource - Auditors []*UserInfo -} - -// GrpsIoMemberWithReadonlyAttributes is the result type of the mailing-list -// service update-grpsio-mailing-list-member method. -type GrpsIoMemberWithReadonlyAttributes struct { - // Member UID - UID *string - // Mailing list UID - MailingListUID *string - // Member username - Username *string + Email *string + // Member display name + Name *string // Member first name FirstName *string // Member last name LastName *string - // Member email address - Email *string - // Member organization - Organization *string - // Member job title - JobTitle *string - // Member type - MemberType string - // Email delivery mode - DeliveryMode string // Moderation status - ModStatus string - // Last reviewed timestamp - LastReviewedAt *string - // Last reviewed by user ID - LastReviewedBy *string + ModStatus *string + // Email delivery mode + DeliveryMode *string // Member status Status *string - // Groups.io member ID - MemberID *int64 - // Groups.io group ID - GroupID *int64 - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string - // Manager users who can edit/modify this resource - Writers []*UserInfo - // Auditor users who can audit this resource - Auditors []*UserInfo } -// GrpsIoServiceFull is the result type of the mailing-list service -// create-grpsio-service method. -type GrpsIoServiceFull struct { - // Service UID -- unique identifier for the service - UID *string +// GroupsioMemberList is the result type of the mailing-list service +// list-groupsio-members method. +type GroupsioMemberList struct { + // List of members + Items []*GroupsioMember + // Total count + Total *int +} + +// GroupsioProjectsResponse is the result type of the mailing-list service +// get-groupsio-service-projects method. +type GroupsioProjectsResponse struct { + // List of project identifiers + Projects []string +} + +// GroupsioService is the result type of the mailing-list service +// create-groupsio-service method. +type GroupsioService struct { + // Service ID + ID *string + // LFX v2 project UID + ProjectUID *string // Service type - Type string - // Service domain - Domain *string + Type *string // GroupsIO group ID GroupID *int64 + // Service domain + Domain *string + // Email prefix + Prefix *string // Service status Status *string - // List of global owner email addresses (required for primary, forbidden for - // shared) - GlobalOwners []string - // Email prefix (required for formation and shared, forbidden for primary) - Prefix *string - // Parent primary service UID (automatically set for shared type services) - ParentServiceUID *string - // Project slug identifier - ProjectSlug *string - // LFXv2 Project UID - ProjectUID string - // Service URL - URL *string - // GroupsIO group name - GroupName *string - // Whether the service is publicly accessible - Public bool - // Project name (read-only) - ProjectName *string - // The timestamp when the service was created (read-only) - CreatedAt *string - // The timestamp when the service was last updated (read-only) - UpdatedAt *string - // Manager users who can edit/modify this resource - Writers []*UserInfo - // Auditor users who can audit this resource - Auditors []*UserInfo -} - -// GrpsIoServiceSettings is the result type of the mailing-list service -// update-grpsio-service-settings method. -type GrpsIoServiceSettings struct { - // Service UID -- unique identifier for the service - UID *string - // Manager users who can edit/modify this resource - Writers []*UserInfo - // Auditor users who can audit this resource - Auditors []*UserInfo - // The timestamp when the service was last reviewed in RFC3339 format - LastReviewedAt *string - // The user ID who last reviewed this service - LastReviewedBy *string - // The user ID who last audited the service - LastAuditedBy *string - // The timestamp when the service was last audited - LastAuditedTime *string - // The timestamp when the service was created (read-only) + // Creation timestamp CreatedAt *string - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string } -// GrpsIoServiceWithReadonlyAttributes is the result type of the mailing-list -// service update-grpsio-service method. -type GrpsIoServiceWithReadonlyAttributes struct { - // Service UID -- unique identifier for the service - UID *string - // Service type - Type string - // Service domain - Domain *string +// GroupsioServiceList is the result type of the mailing-list service +// list-groupsio-services method. +type GroupsioServiceList struct { + // List of services + Items []*GroupsioService + // Total count + Total *int +} + +// GroupsioSubgroup is the result type of the mailing-list service +// create-groupsio-subgroup method. +type GroupsioSubgroup struct { + // Subgroup ID + ID *string + // LFX v2 project UID + ProjectUID *string + // LFX v2 committee UID + CommitteeUID *string // GroupsIO group ID GroupID *int64 - // Service status - Status *string - // List of global owner email addresses (required for primary, forbidden for - // shared) - GlobalOwners []string - // Email prefix (required for formation and shared, forbidden for primary) - Prefix *string - // Parent primary service UID (automatically set for shared type services) - ParentServiceUID *string - // Project slug identifier - ProjectSlug *string - // LFXv2 Project UID - ProjectUID string - // Service URL - URL *string - // GroupsIO group name - GroupName *string - // Whether the service is publicly accessible - Public bool - // Project name (read-only) - ProjectName *string - // The timestamp when the service was created (read-only) + // Subgroup name + Name *string + // Subgroup description + Description *string + // Subgroup type + Type *string + // Audience access setting + AudienceAccess *string + // Creation timestamp CreatedAt *string - // The timestamp when the service was last updated (read-only) + // Last update timestamp UpdatedAt *string - // Manager users who can edit/modify this resource - Writers []*UserInfo - // Auditor users who can audit this resource - Auditors []*UserInfo } -// UpdateGrpsioMailingListMemberPayload is the payload type of the mailing-list -// service update-grpsio-mailing-list-member method. -type UpdateGrpsioMailingListMemberPayload struct { +// GroupsioSubgroupList is the result type of the mailing-list service +// list-groupsio-subgroups method. +type GroupsioSubgroupList struct { + // List of subgroups + Items []*GroupsioSubgroup + // Total count + Total *int +} + +// InviteGroupsioMembersPayload is the payload type of the mailing-list service +// invite-groupsio-members method. +type InviteGroupsioMembersPayload struct { // JWT token issued by Heimdall - BearerToken string - // Version of the API - Version string - // If-Match header value for conditional requests - IfMatch string - // Mailing list UID -- unique identifier for the mailing list - UID string - // Member UID -- unique identifier for the member - MemberUID string - // Member username - Username *string - // Member first name - FirstName *string - // Member last name - LastName *string - // Member organization - Organization *string - // Member job title - JobTitle *string - // Email delivery mode - DeliveryMode string - // Moderation status - ModStatus string + BearerToken *string + // Subgroup ID + SubgroupID string + // Email addresses to invite + Emails []string } -// UpdateGrpsioMailingListPayload is the payload type of the mailing-list -// service update-grpsio-mailing-list method. -type UpdateGrpsioMailingListPayload struct { +// ListGroupsioMembersPayload is the payload type of the mailing-list service +// list-groupsio-members method. +type ListGroupsioMembersPayload struct { // JWT token issued by Heimdall BearerToken *string - // Version of the API - Version string - // If-Match header value for conditional requests - IfMatch *string - // Mailing list UID -- unique identifier for the mailing list - UID *string - // Mailing list group name - GroupName string - // Mailing list group ID - GroupID *int64 - // Whether the mailing list is publicly accessible - Public bool - // Mailing list type - Type string - // public: Anyone can join. approval_required: Users must request to join and - // be approved. invite_only: Only invited users can join. - AudienceAccess string - // Committees associated with this mailing list (OR logic for access control) - Committees []*Committee - // Mailing list description (11-500 characters) - Description string - // Mailing list title - Title string - // Subject tag prefix - SubjectTag *string - // Service UUID - ServiceUID string - // Number of subscribers in this mailing list (read-only, maintained by service) - SubscriberCount *int -} - -// UpdateGrpsioMailingListSettingsPayload is the payload type of the -// mailing-list service update-grpsio-mailing-list-settings method. -type UpdateGrpsioMailingListSettingsPayload struct { + // Subgroup ID + SubgroupID string +} + +// ListGroupsioServicesPayload is the payload type of the mailing-list service +// list-groupsio-services method. +type ListGroupsioServicesPayload struct { // JWT token issued by Heimdall BearerToken *string - // Version of the API - Version string - // If-Match header value for conditional requests - IfMatch *string - // Mailing list UID -- unique identifier for the mailing list - UID string - // Manager users who can edit/modify this resource - Writers []*UserInfo - // Auditor users who can audit this resource - Auditors []*UserInfo -} - -// UpdateGrpsioServicePayload is the payload type of the mailing-list service -// update-grpsio-service method. -type UpdateGrpsioServicePayload struct { + // LFX v2 project UID filter + ProjectUID *string +} + +// ListGroupsioSubgroupsPayload is the payload type of the mailing-list service +// list-groupsio-subgroups method. +type ListGroupsioSubgroupsPayload struct { // JWT token issued by Heimdall BearerToken *string - // Version of the API - Version string - // If-Match header value for conditional requests - IfMatch *string - // Service UID -- unique identifier for the service - UID *string + // LFX v2 project UID filter + ProjectUID *string + // LFX v2 committee UID filter + CommitteeUID *string +} + +// UpdateGroupsioMemberPayload is the payload type of the mailing-list service +// update-groupsio-member method. +type UpdateGroupsioMemberPayload struct { + // JWT token issued by Heimdall + BearerToken *string + // Subgroup ID + SubgroupID string + // Member ID + MemberID string + // Member email address + Email *string + // Member display name + Name *string + // Moderation status + ModStatus *string + // Email delivery mode + DeliveryMode *string +} + +// UpdateGroupsioServicePayload is the payload type of the mailing-list service +// update-groupsio-service method. +type UpdateGroupsioServicePayload struct { + // JWT token issued by Heimdall + BearerToken *string + // Service ID + ServiceID string + // LFX v2 project UID + ProjectUID *string // Service type - Type string - // Service domain - Domain *string + Type *string // GroupsIO group ID GroupID *int64 + // Service domain + Domain *string + // Email prefix + Prefix *string // Service status Status *string - // List of global owner email addresses (required for primary, forbidden for - // shared) - GlobalOwners []string - // Email prefix (required for formation and shared, forbidden for primary) - Prefix *string - // Parent primary service UID (automatically set for shared type services) - ParentServiceUID *string - // Project slug identifier - ProjectSlug *string - // LFXv2 Project UID - ProjectUID string - // Service URL - URL *string - // GroupsIO group name - GroupName *string - // Whether the service is publicly accessible - Public bool } -// UpdateGrpsioServiceSettingsPayload is the payload type of the mailing-list -// service update-grpsio-service-settings method. -type UpdateGrpsioServiceSettingsPayload struct { +// UpdateGroupsioSubgroupPayload is the payload type of the mailing-list +// service update-groupsio-subgroup method. +type UpdateGroupsioSubgroupPayload struct { // JWT token issued by Heimdall BearerToken *string - // Version of the API - Version string - // If-Match header value for conditional requests - IfMatch *string - // Service UID -- unique identifier for the service - UID string - // Manager users who can edit/modify this resource - Writers []*UserInfo - // Auditor users who can audit this resource - Auditors []*UserInfo -} - -// User information including profile details. -type UserInfo struct { - // The full name of the user + // Subgroup ID + SubgroupID string + // LFX v2 project UID + ProjectUID *string + // LFX v2 committee UID + CommitteeUID *string + // GroupsIO group ID + GroupID *int64 + // Subgroup name Name *string - // The email address of the user - Email *string - // The username/LFID of the user - Username *string - // The avatar URL of the user - Avatar *string + // Subgroup description + Description *string + // Subgroup type + Type *string + // Audience access setting + AudienceAccess *string } type BadRequestError struct { @@ -830,11 +498,6 @@ type ServiceUnavailableError struct { Message string } -type UnauthorizedError struct { - // Error message - Message string -} - // Error returns an error description. func (e *BadRequestError) Error() string { return "" @@ -919,20 +582,3 @@ func (e *ServiceUnavailableError) ErrorName() string { func (e *ServiceUnavailableError) GoaErrorName() string { return "ServiceUnavailable" } - -// Error returns an error description. -func (e *UnauthorizedError) Error() string { - return "" -} - -// ErrorName returns "unauthorized-error". -// -// Deprecated: Use GoaErrorName - https://github.com/goadesign/goa/issues/3105 -func (e *UnauthorizedError) ErrorName() string { - return e.GoaErrorName() -} - -// GoaErrorName returns "unauthorized-error". -func (e *UnauthorizedError) GoaErrorName() string { - return "Unauthorized" -} diff --git a/go.mod b/go.mod index f105cf2..af99a8e 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,8 @@ module github.com/linuxfoundation/lfx-v2-mailing-list-service go 1.24.0 require ( + github.com/auth0/go-auth0 v1.33.0 github.com/auth0/go-jwt-middleware/v2 v2.3.0 - github.com/golang-jwt/jwt/v5 v5.3.0 - github.com/google/go-querystring v1.1.0 github.com/google/uuid v1.6.0 github.com/nats-io/nats.go v1.31.0 github.com/remychantenay/slog-otel v1.3.4 @@ -28,29 +27,39 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.40.0 goa.design/clue v1.2.2 goa.design/goa/v3 v3.21.5 + golang.org/x/oauth2 v0.34.0 golang.org/x/sync v0.19.0 - golang.org/x/text v0.33.0 ) require ( + github.com/PuerkitoBio/rehttp v1.4.0 // indirect github.com/aws/smithy-go v1.22.5 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-chi/chi/v5 v5.2.2 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/gohugoio/hashstructure v0.5.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect github.com/klauspost/compress v1.17.0 // indirect + github.com/lestrrat-go/blackmagic v1.0.3 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.6 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/jwx/v2 v2.1.6 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d // indirect github.com/nats-io/nkeys v0.4.15 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/objx v0.5.2 // indirect + github.com/segmentio/asm v1.2.0 // indirect + go.devnw.com/structs v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect go.opentelemetry.io/otel/metric v1.40.0 // indirect @@ -61,6 +70,7 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect golang.org/x/tools v0.40.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect diff --git a/go.sum b/go.sum index c3a365f..60ddc1a 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,27 @@ +github.com/PuerkitoBio/rehttp v1.4.0 h1:rIN7A2s+O9fmHUM1vUcInvlHj9Ysql4hE+Y0wcl/xk8= +github.com/PuerkitoBio/rehttp v1.4.0/go.mod h1:LUwKPoDbDIA2RL5wYZCNsQ90cx4OJ4AWBmq6KzWZL1s= +github.com/auth0/go-auth0 v1.33.0 h1:7qx0UCA6Tn2udnEVA35xzKsseh/R9559f+nnGcUI0Ss= +github.com/auth0/go-auth0 v1.33.0/go.mod h1:32sQB1uAn+99fJo6N819EniKq8h785p0ag0lMWhiTaE= github.com/auth0/go-jwt-middleware/v2 v2.3.0 h1:4QREj6cS3d8dS05bEm443jhnqQF97FX9sMBeWqnNRzE= github.com/auth0/go-jwt-middleware/v2 v2.3.0/go.mod h1:dL4ObBs1/dj4/W4cYxd8rqAdDGXYyd5rqbpMIxcbVrU= github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aybabtme/iocontrol v0.0.0-20150809002002-ad15bcfc95a0 h1:0NmehRCgyk5rljDQLKUO+cRJCnduDyn11+zGZIc9Z48= +github.com/aybabtme/iocontrol v0.0.0-20150809002002-ad15bcfc95a0/go.mod h1:6L7zgvqo0idzI7IO8de6ZC051AfXb5ipkIJ7bIA2tGA= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 h1:MGKhKyiYrvMDZsmLR/+RGffQSXwEkXgfLSA08qDn9AI= github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598/go.mod h1:0FpDmbrt36utu8jEmeU05dPC9AB5tsLYVVi+ZHfyuwI= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= @@ -19,29 +31,40 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gohugoio/hashstructure v0.5.0 h1:G2fjSBU36RdwEJBWJ+919ERvOVqAg9tfcYp47K9swqg= github.com/gohugoio/hashstructure v0.5.0/go.mod h1:Ser0TniXuu/eauYmrwM4o64EBvySxNzITEOLlm4igec= -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lestrrat-go/blackmagic v1.0.3 h1:94HXkVLxkZO9vJI/w2u1T0DAoprShFd13xtnSINtDWs= +github.com/lestrrat-go/blackmagic v1.0.3/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= +github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVfecA= +github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d h1:Zj+PHjnhRYWBK6RqCDBcAhLXoi3TzC27Zad/Vn+gnVQ= github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d/go.mod h1:WZy8Q5coAB1zhY9AOBJP0O6J4BuDfbupUDavKY+I3+s= github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b h1:3E44bLeN8uKYdfQqVQycPnaVviZdBLbizFhU49mtbe4= @@ -58,10 +81,16 @@ github.com/remychantenay/slog-otel v1.3.4 h1:xoM41ayLff2U8zlK5PH31XwD7Lk3W9wKfl4 github.com/remychantenay/slog-otel v1.3.4/go.mod h1:ZkazuFMICKGDrO0r1njxKRdjTt/YcXKn6v2+0q/b0+U= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.devnw.com/structs v1.0.0 h1:FFkBoBOkapCdxFEIkpOZRmMOMr9b9hxjKTD3bJYl9lk= +go.devnw.com/structs v1.0.0/go.mod h1:wHBkdQpNeazdQHszJ2sxwVEpd8zGTEsKkeywDLGbrmg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= @@ -112,19 +141,26 @@ golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= @@ -138,7 +174,10 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/dnaeon/go-vcr.v3 v3.2.0 h1:Rltp0Vf+Aq0u4rQXgmXgtgoRDStTnFN83cWgSGSoRzM= +gopkg.in/dnaeon/go-vcr.v3 v3.2.0/go.mod h1:2IMOnnlx9I6u9x+YBsM3tAMx6AlOxnJ0pWxQAzZ79Ag= gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/domain/errors.go b/internal/domain/errors.go new file mode 100644 index 0000000..c11711c --- /dev/null +++ b/internal/domain/errors.go @@ -0,0 +1,65 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package domain + +import "errors" + +// ErrorType represents the semantic category of an error +type ErrorType int + +const ( + ErrorTypeValidation ErrorType = iota // Input validation errors (400 Bad Request) + ErrorTypeNotFound // Resource not found errors (404 Not Found) + ErrorTypeConflict // Resource conflict errors (409 Conflict) + ErrorTypeInternal // Internal server errors (500 Internal Server Error) + ErrorTypeUnavailable // Service unavailable errors (503 Service Unavailable) +) + +// DomainError represents an error with semantic type information +type DomainError struct { + Type ErrorType + Message string + Err error // underlying error for wrapping +} + +func (e *DomainError) Error() string { + if e.Err != nil { + return e.Message + ": " + e.Err.Error() + } + return e.Message +} + +func (e *DomainError) Unwrap() error { + return e.Err +} + +// GetErrorType returns the semantic type of an error +func GetErrorType(err error) ErrorType { + var domainErr *DomainError + if errors.As(err, &domainErr) { + return domainErr.Type + } + return ErrorTypeInternal // default fallback +} + +// Error constructors for different types +func NewValidationError(message string, err ...error) *DomainError { + return &DomainError{Type: ErrorTypeValidation, Message: message, Err: errors.Join(err...)} +} + +func NewNotFoundError(message string, err ...error) *DomainError { + return &DomainError{Type: ErrorTypeNotFound, Message: message, Err: errors.Join(err...)} +} + +func NewConflictError(message string, err ...error) *DomainError { + return &DomainError{Type: ErrorTypeConflict, Message: message, Err: errors.Join(err...)} +} + +func NewInternalError(message string, err ...error) *DomainError { + return &DomainError{Type: ErrorTypeInternal, Message: message, Err: errors.Join(err...)} +} + +func NewUnavailableError(message string, err ...error) *DomainError { + return &DomainError{Type: ErrorTypeUnavailable, Message: message, Err: errors.Join(err...)} +} diff --git a/internal/domain/errors_test.go b/internal/domain/errors_test.go new file mode 100644 index 0000000..b51f165 --- /dev/null +++ b/internal/domain/errors_test.go @@ -0,0 +1,113 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package domain + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDomainError_ErrorMessage(t *testing.T) { + tests := []struct { + name string + err *DomainError + expected string + }{ + { + name: "message only", + err: &DomainError{Type: ErrorTypeValidation, Message: "bad input"}, + expected: "bad input", + }, + { + name: "message with cause", + err: &DomainError{Type: ErrorTypeInternal, Message: "failed", Err: errors.New("underlying")}, + expected: "failed: underlying", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, tt.err.Error()) + }) + } +} + +func TestDomainError_Unwrap(t *testing.T) { + cause := errors.New("root cause") + err := &DomainError{Type: ErrorTypeInternal, Message: "wrapped", Err: cause} + + assert.Equal(t, cause, err.Unwrap()) + assert.True(t, errors.Is(err, cause)) +} + +func TestDomainError_ErrorsAs(t *testing.T) { + orig := NewNotFoundError("not found") + wrapped := fmt.Errorf("outer: %w", orig) + + var domErr *DomainError + require.True(t, errors.As(wrapped, &domErr)) + assert.Equal(t, ErrorTypeNotFound, domErr.Type) + assert.Equal(t, "not found", domErr.Message) +} + +func TestNewValidationError(t *testing.T) { + err := NewValidationError("invalid input") + assert.Equal(t, ErrorTypeValidation, err.Type) + assert.Equal(t, "invalid input", err.Message) + assert.Nil(t, err.Err) +} + +func TestNewValidationError_WithCause(t *testing.T) { + cause := errors.New("cause") + err := NewValidationError("invalid input", cause) + assert.Equal(t, "invalid input: cause", err.Error()) +} + +func TestNewNotFoundError(t *testing.T) { + err := NewNotFoundError("resource not found") + assert.Equal(t, ErrorTypeNotFound, err.Type) + assert.Equal(t, "resource not found", err.Message) +} + +func TestNewConflictError(t *testing.T) { + err := NewConflictError("already exists") + assert.Equal(t, ErrorTypeConflict, err.Type) + assert.Equal(t, "already exists", err.Message) +} + +func TestNewInternalError(t *testing.T) { + err := NewInternalError("internal failure") + assert.Equal(t, ErrorTypeInternal, err.Type) + assert.Equal(t, "internal failure", err.Message) +} + +func TestNewUnavailableError(t *testing.T) { + err := NewUnavailableError("service down") + assert.Equal(t, ErrorTypeUnavailable, err.Type) + assert.Equal(t, "service down", err.Message) +} + +func TestGetErrorType(t *testing.T) { + tests := []struct { + name string + err error + expected ErrorType + }{ + {"validation", NewValidationError("v"), ErrorTypeValidation}, + {"not found", NewNotFoundError("nf"), ErrorTypeNotFound}, + {"conflict", NewConflictError("c"), ErrorTypeConflict}, + {"internal", NewInternalError("i"), ErrorTypeInternal}, + {"unavailable", NewUnavailableError("u"), ErrorTypeUnavailable}, + {"non-domain error defaults to internal", errors.New("plain"), ErrorTypeInternal}, + {"wrapped domain error", fmt.Errorf("wrap: %w", NewNotFoundError("x")), ErrorTypeNotFound}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, GetErrorType(tt.err)) + }) + } +} diff --git a/internal/domain/id_mapper.go b/internal/domain/id_mapper.go new file mode 100644 index 0000000..0a3df12 --- /dev/null +++ b/internal/domain/id_mapper.go @@ -0,0 +1,21 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package domain + +import "context" + +// IDMapper defines the interface for mapping between LFX v1 and v2 identifiers +type IDMapper interface { + // MapProjectV2ToV1 maps a v2 project UID to v1 project SFID + MapProjectV2ToV1(ctx context.Context, v2UID string) (string, error) + + // MapProjectV1ToV2 maps a v1 project SFID to v2 project UID + MapProjectV1ToV2(ctx context.Context, v1SFID string) (string, error) + + // MapCommitteeV2ToV1 maps a v2 committee UID to v1 committee SFID + MapCommitteeV2ToV1(ctx context.Context, v2UID string) (string, error) + + // MapCommitteeV1ToV2 maps a v1 committee SFID to v2 committee UID + MapCommitteeV1ToV2(ctx context.Context, v1SFID string) (string, error) +} diff --git a/internal/domain/itx_proxy.go b/internal/domain/itx_proxy.go new file mode 100644 index 0000000..42136b9 --- /dev/null +++ b/internal/domain/itx_proxy.go @@ -0,0 +1,89 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package domain + +import ( + "context" + + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/models" +) + +// GroupsioServiceClient defines the interface for ITX GroupsIO service operations. +type GroupsioServiceClient interface { + // ListServices lists GroupsIO services, optionally filtered by project_id (v1 SFID) + ListServices(ctx context.Context, projectID string) (*models.GroupsioServiceListResponse, error) + + // CreateService creates a new GroupsIO service + CreateService(ctx context.Context, req *models.GroupsioServiceRequest) (*models.GroupsioService, error) + + // GetService retrieves a GroupsIO service by ID + GetService(ctx context.Context, serviceID string) (*models.GroupsioService, error) + + // UpdateService updates a GroupsIO service + UpdateService(ctx context.Context, serviceID string, req *models.GroupsioServiceRequest) (*models.GroupsioService, error) + + // DeleteService deletes a GroupsIO service + DeleteService(ctx context.Context, serviceID string) error + + // GetProjects returns projects that have GroupsIO services + GetProjects(ctx context.Context) (*models.GroupsioServiceProjectsResponse, error) + + // FindParentService finds the parent service for a project (v1 SFID) + FindParentService(ctx context.Context, projectID string) (*models.GroupsioService, error) +} + +// GroupsioSubgroupClient defines the interface for ITX GroupsIO subgroup operations. +type GroupsioSubgroupClient interface { + // ListSubgroups lists subgroups, optionally filtered by project_id and/or committee_id (v1 SFIDs) + ListSubgroups(ctx context.Context, projectID, committeeID string) (*models.GroupsioSubgroupListResponse, error) + + // CreateSubgroup creates a new subgroup + CreateSubgroup(ctx context.Context, req *models.GroupsioSubgroupRequest) (*models.GroupsioSubgroup, error) + + // GetSubgroup retrieves a subgroup by ID + GetSubgroup(ctx context.Context, subgroupID string) (*models.GroupsioSubgroup, error) + + // UpdateSubgroup updates a subgroup + UpdateSubgroup(ctx context.Context, subgroupID string, req *models.GroupsioSubgroupRequest) (*models.GroupsioSubgroup, error) + + // DeleteSubgroup deletes a subgroup + DeleteSubgroup(ctx context.Context, subgroupID string) error + + // GetSubgroupCount returns the count of subgroups for a project (v1 SFID) + GetSubgroupCount(ctx context.Context, projectID string) (*models.GroupsioSubgroupCountResponse, error) + + // GetMemberCount returns the count of members in a subgroup + GetMemberCount(ctx context.Context, subgroupID string) (*models.GroupsioMemberCountResponse, error) +} + +// GroupsioMemberClient defines the interface for ITX GroupsIO member operations. +type GroupsioMemberClient interface { + // ListMembers lists members of a subgroup + ListMembers(ctx context.Context, subgroupID string) (*models.GroupsioMemberListResponse, error) + + // AddMember adds a member to a subgroup + AddMember(ctx context.Context, subgroupID string, req *models.GroupsioMemberRequest) (*models.GroupsioMember, error) + + // GetMember retrieves a member by ID + GetMember(ctx context.Context, subgroupID, memberID string) (*models.GroupsioMember, error) + + // UpdateMember updates a member + UpdateMember(ctx context.Context, subgroupID, memberID string, req *models.GroupsioMemberRequest) (*models.GroupsioMember, error) + + // DeleteMember removes a member from a subgroup + DeleteMember(ctx context.Context, subgroupID, memberID string) error + + // InviteMembers sends invitations to multiple email addresses + InviteMembers(ctx context.Context, subgroupID string, req *models.GroupsioInviteMembersRequest) error + + // CheckSubscriber checks if an email is subscribed to a subgroup + CheckSubscriber(ctx context.Context, req *models.GroupsioCheckSubscriberRequest) (*models.GroupsioCheckSubscriberResponse, error) +} + +// ITXGroupsioClient combines all GroupsIO client interfaces. +type ITXGroupsioClient interface { + GroupsioServiceClient + GroupsioSubgroupClient + GroupsioMemberClient +} diff --git a/internal/domain/models/itx_groupsio.go b/internal/domain/models/itx_groupsio.go new file mode 100644 index 0000000..20a6f01 --- /dev/null +++ b/internal/domain/models/itx_groupsio.go @@ -0,0 +1,130 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +// Package models contains domain model types for ITX GroupsIO integration. +package models + +// GroupsioService represents a GroupsIO service in the ITX API. +type GroupsioService struct { + ID string `json:"id,omitempty"` + ProjectID string `json:"project_id,omitempty"` // v1 SFID in ITX, v2 UUID in our API + Type string `json:"type,omitempty"` + GroupID int64 `json:"group_id,omitempty"` + Domain string `json:"domain,omitempty"` + Prefix string `json:"prefix,omitempty"` + Status string `json:"status,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` +} + +// GroupsioServiceRequest represents a create/update request for a GroupsIO service. +type GroupsioServiceRequest struct { + ProjectID string `json:"project_id,omitempty"` // v1 SFID + Type string `json:"type,omitempty"` + GroupID int64 `json:"group_id,omitempty"` + Domain string `json:"domain,omitempty"` + Prefix string `json:"prefix,omitempty"` + Status string `json:"status,omitempty"` +} + +// GroupsioServiceListResponse represents a list of GroupsIO services. +type GroupsioServiceListResponse struct { + Items []*GroupsioService `json:"items,omitempty"` + Total int `json:"total,omitempty"` +} + +// GroupsioServiceProjectsResponse represents a list of projects with services. +type GroupsioServiceProjectsResponse struct { + Projects []string `json:"projects,omitempty"` +} + +// GroupsioSubgroup represents a GroupsIO subgroup (mailing list) in the ITX API. +type GroupsioSubgroup struct { + ID string `json:"id,omitempty"` + ProjectID string `json:"project_id,omitempty"` // v1 SFID in ITX, v2 UUID in our API + CommitteeID string `json:"committee_id,omitempty"` // v1 SFID in ITX, v2 UUID in our API + GroupID int64 `json:"group_id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Type string `json:"type,omitempty"` + AudienceAccess string `json:"audience_access,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` +} + +// GroupsioSubgroupRequest represents a create/update request for a GroupsIO subgroup. +type GroupsioSubgroupRequest struct { + ProjectID string `json:"project_id,omitempty"` // v1 SFID + CommitteeID string `json:"committee_id,omitempty"` // v1 SFID + GroupID int64 `json:"group_id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Type string `json:"type,omitempty"` + AudienceAccess string `json:"audience_access,omitempty"` +} + +// GroupsioSubgroupListResponse represents a list of GroupsIO subgroups. +type GroupsioSubgroupListResponse struct { + Items []*GroupsioSubgroup `json:"items,omitempty"` + Total int `json:"total,omitempty"` +} + +// GroupsioSubgroupCountResponse represents the count of subgroups. +type GroupsioSubgroupCountResponse struct { + Count int `json:"count"` +} + +// GroupsioMemberCountResponse represents the count of members in a subgroup. +type GroupsioMemberCountResponse struct { + Count int `json:"count"` +} + +// GroupsioMember represents a member of a GroupsIO subgroup. +type GroupsioMember struct { + ID string `json:"id,omitempty"` + SubgroupID string `json:"subgroup_id,omitempty"` + Email string `json:"email,omitempty"` + Name string `json:"name,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + ModStatus string `json:"mod_status,omitempty"` + DeliveryMode string `json:"delivery_mode,omitempty"` + Status string `json:"status,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` +} + +// GroupsioMemberRequest represents a create/update request for a GroupsIO member. +type GroupsioMemberRequest struct { + Email string `json:"email,omitempty"` + Name string `json:"name,omitempty"` + ModStatus string `json:"mod_status,omitempty"` + DeliveryMode string `json:"delivery_mode,omitempty"` +} + +// GroupsioMemberListResponse represents a list of GroupsIO members. +type GroupsioMemberListResponse struct { + Items []*GroupsioMember `json:"items,omitempty"` + Total int `json:"total,omitempty"` +} + +// GroupsioInviteMembersRequest represents an invite members request. +type GroupsioInviteMembersRequest struct { + Emails []string `json:"emails,omitempty"` +} + +// GroupsioCheckSubscriberRequest represents a check subscriber request. +type GroupsioCheckSubscriberRequest struct { + Email string `json:"email,omitempty"` + SubgroupID string `json:"subgroup_id,omitempty"` +} + +// GroupsioCheckSubscriberResponse represents a check subscriber response. +type GroupsioCheckSubscriberResponse struct { + Subscribed bool `json:"subscribed"` +} + +// GroupsioFindParentResponse represents the find parent service response. +type GroupsioFindParentResponse struct { + Service *GroupsioService `json:"service,omitempty"` +} diff --git a/internal/infrastructure/groupsio/client.go b/internal/infrastructure/groupsio/client.go deleted file mode 100644 index 8d7a2cb..0000000 --- a/internal/infrastructure/groupsio/client.go +++ /dev/null @@ -1,565 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package groupsio - -import ( - "context" - "encoding/json" - "fmt" - "io" - "log/slog" - "net/http" - "net/url" - "strconv" - "strings" - "sync" - "time" - - "github.com/golang-jwt/jwt/v5" - "github.com/google/go-querystring/query" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/httpclient" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/redaction" -) - -// groupsioBasicAuthRoundTripper implements automatic BasicAuth injection (production pattern) -type groupsioBasicAuthRoundTripper struct { - client *Client -} - -// RoundTrip ensures authentication before non-login requests to groups.io -// and adds authorization header to those requests (production pattern) -func (rt *groupsioBasicAuthRoundTripper) RoundTrip(req *http.Request, next func(*http.Request) (*http.Response, error)) (*http.Response, error) { - // Skip auth for login requests to avoid infinite recursion - if strings.Contains(req.URL.Path, "/v1/login") { - return next(req) - } - - // Get cached token for this domain - rt.client.cacheMu.RLock() - if cache, exists := rt.client.cache[req.Host]; exists { - cache.mu.RLock() - if time.Now().Before(cache.expiry) && cache.token != "" { - // Use cached token with BasicAuth (production pattern: req.SetBasicAuth) - req.SetBasicAuth(cache.token, "") - cache.mu.RUnlock() - rt.client.cacheMu.RUnlock() - - slog.DebugContext(req.Context(), "RoundTripper: using cached Groups.io token", - "host", req.Host, "path", req.URL.Path) - return next(req) - } - cache.mu.RUnlock() - } - rt.client.cacheMu.RUnlock() - - // No valid cached token - authenticate first (production pattern) - if err := rt.client.WithDomain(req.Context(), req.Host); err != nil { - return nil, fmt.Errorf("authentication failed in RoundTripper: %w", err) - } - - // Now get the token and set BasicAuth - rt.client.cacheMu.RLock() - if cache, exists := rt.client.cache[req.Host]; exists { - cache.mu.RLock() - if cache.token != "" { - req.SetBasicAuth(cache.token, "") - slog.DebugContext(req.Context(), "RoundTripper: using fresh Groups.io token", - "host", req.Host, "path", req.URL.Path) - } - cache.mu.RUnlock() - } - rt.client.cacheMu.RUnlock() - - return next(req) -} - -// tokenCache holds cached authentication tokens per domain -type tokenCache struct { - token string - expiry time.Time - mu sync.RWMutex -} - -// Client handles all Groups.io API operations with smart token caching -// ClientInterface defines the contract for GroupsIO API operations -type ClientInterface interface { - CreateGroup(ctx context.Context, domain string, options GroupCreateOptions) (*GroupObject, error) - DeleteGroup(ctx context.Context, domain string, groupID uint64) error - CreateSubgroup(ctx context.Context, domain string, parentGroupID uint64, options SubgroupCreateOptions) (*SubgroupObject, error) - DeleteSubgroup(ctx context.Context, domain string, subgroupID uint64) error - GetGroup(ctx context.Context, domain string, groupID uint64) (*GroupObject, error) - DirectAdd(ctx context.Context, domain string, groupID uint64, emails []string, subgroupIDs []uint64) (*DirectAddResultsObject, error) - UpdateMember(ctx context.Context, domain string, memberID uint64, updates MemberUpdateOptions) error - UpdateGroup(ctx context.Context, domain string, groupID uint64, updates GroupUpdateOptions) error - UpdateSubgroup(ctx context.Context, domain string, subgroupID uint64, updates SubgroupUpdateOptions) error - RemoveMember(ctx context.Context, domain string, groupID uint64, memberID uint64) error - IsReady(ctx context.Context) error -} - -type Client struct { - config Config - httpClient *httpclient.Client - cache map[string]*tokenCache // domain -> token - cacheMu sync.RWMutex -} - -// NewClient creates a new GroupsIO client with the given configuration -func NewClient(cfg Config) (*Client, error) { - if cfg.MockMode { - return nil, nil // Return nil for mock mode - orchestrator will handle this - } - - // Validate required configuration - if cfg.Email == "" || cfg.Password == "" { - return nil, fmt.Errorf("email and password are required for Groups.io client") - } - - if cfg.BaseURL == "" { - cfg.BaseURL = "https://api.groups.io" - } - - // Use reusable httpclient from pkg - httpConfig := httpclient.Config{ - Timeout: cfg.Timeout, - MaxRetries: cfg.MaxRetries, - RetryDelay: cfg.RetryDelay, - RetryBackoff: true, - MaxDelay: 30 * time.Second, // Cap exponential backoff at 30s - } - - client := &Client{ - config: cfg, - httpClient: httpclient.NewClient(httpConfig), - cache: make(map[string]*tokenCache), - } - - // Register BasicAuth RoundTripper for automatic token injection (production pattern) - authRoundTripper := &groupsioBasicAuthRoundTripper{client: client} - client.httpClient.AddRoundTripper(authRoundTripper) - - slog.InfoContext(context.Background(), "Groups.io client initialized with RoundTripper auth pattern") - - return client, nil -} - -// CreateGroup creates a main group in Groups.io -func (c *Client) CreateGroup(ctx context.Context, domain string, options GroupCreateOptions) (*GroupObject, error) { - slog.InfoContext(ctx, "creating group in Groups.io", - "domain", domain, "group_name", options.GroupName) - - data, err := query.Values(options) - if err != nil { - return nil, fmt.Errorf("failed to encode options: %w", err) - } - - var response GroupObject - err = c.makeRequest(ctx, domain, http.MethodPost, "/creategroup", data, &response) - if err != nil { - return nil, err - } - - slog.InfoContext(ctx, "group created successfully in Groups.io", - "group_id", response.ID, "domain", domain) - - return &response, nil -} - -// DeleteGroup removes a group from Groups.io -func (c *Client) DeleteGroup(ctx context.Context, domain string, groupID uint64) error { - slog.InfoContext(ctx, "deleting group from Groups.io", - "domain", domain, "group_id", groupID) - - data := url.Values{ - "group_id": {strconv.FormatUint(groupID, 10)}, - } - - return c.makeRequest(ctx, domain, http.MethodPost, "/deletegroup", data, nil) -} - -// CreateSubgroup creates a subgroup (mailing list) in Groups.io -func (c *Client) CreateSubgroup(ctx context.Context, domain string, parentGroupID uint64, options SubgroupCreateOptions) (*SubgroupObject, error) { - slog.InfoContext(ctx, "creating subgroup in Groups.io", - "domain", domain, "parent_group_id", parentGroupID, "subgroup_name", options.GroupName) - - // Set the parent group ID in options if not already set (for backwards compatibility) - if options.ParentGroupID == 0 { - options.ParentGroupID = parentGroupID - } - - data, err := query.Values(options) - if err != nil { - return nil, fmt.Errorf("failed to encode options: %w", err) - } - - var response SubgroupObject - err = c.makeRequest(ctx, domain, http.MethodPost, "/createsubgroup", data, &response) - if err != nil { - return nil, err - } - - slog.InfoContext(ctx, "subgroup created successfully in Groups.io", - "subgroup_id", response.ID, "parent_group_id", parentGroupID) - - return &response, nil -} - -// DeleteSubgroup removes a subgroup from Groups.io -func (c *Client) DeleteSubgroup(ctx context.Context, domain string, subgroupID uint64) error { - slog.InfoContext(ctx, "deleting subgroup from Groups.io", - "domain", domain, "subgroup_id", subgroupID) - - data := url.Values{ - "subgroup_id": {strconv.FormatUint(subgroupID, 10)}, - } - - return c.makeRequest(ctx, domain, http.MethodPost, "/deletesubgroup", data, nil) -} - -// GetGroup retrieves group details from Groups.io (works for both main groups and subgroups) -func (c *Client) GetGroup(ctx context.Context, domain string, groupID uint64) (*GroupObject, error) { - slog.InfoContext(ctx, "getting group from Groups.io", - "domain", domain, "group_id", groupID) - - data := url.Values{ - "group_id": {strconv.FormatUint(groupID, 10)}, - } - - var response GroupObject - err := c.makeRequest(ctx, domain, http.MethodGet, "/getgroup", data, &response) - if err != nil { - return nil, err - } - - slog.InfoContext(ctx, "group retrieved successfully from Groups.io", - "group_id", response.ID, - "subscriber_count", response.SubsCount) - - return &response, nil -} - -// DirectAdd adds one or more members to a group or subgroup using the direct_add endpoint -// emails: slice of email addresses to add (comma-separated in API call) -// subgroupIDs: optional slice of subgroup IDs, one per email (comma-separated in API call) -// Returns the full DirectAddResultsObject with all added members and any errors -func (c *Client) DirectAdd(ctx context.Context, domain string, groupID uint64, emails []string, subgroupIDs []uint64) (*DirectAddResultsObject, error) { - if len(emails) == 0 { - return nil, fmt.Errorf("at least one email is required") - } - - // Convert emails to comma-separated string - emailsStr := strings.Join(emails, ",") - - // Redact emails for logging - redactedEmails := make([]string, len(emails)) - for i, email := range emails { - redactedEmails[i] = redaction.RedactEmail(email) - } - - // Convert subgroup IDs to comma-separated string (if provided) - var subgroupIDsStr string - if len(subgroupIDs) > 0 { - ids := make([]string, len(subgroupIDs)) - for i, id := range subgroupIDs { - ids[i] = strconv.FormatUint(id, 10) - } - subgroupIDsStr = strings.Join(ids, ",") - } - - slog.InfoContext(ctx, "adding members to Groups.io via direct_add", - "domain", domain, - "group_id", groupID, - "email_count", len(emails), - "emails", redactedEmails, - "subgroup_ids", subgroupIDsStr) - - data := url.Values{ - "group_id": {strconv.FormatUint(groupID, 10)}, - "emails": {emailsStr}, - } - - // Only add subgroup IDs if provided (for adding to subgroups rather than main group) - if subgroupIDsStr != "" { - data.Set("subgroupids", subgroupIDsStr) - } - - var result DirectAddResultsObject - err := c.makeRequest(ctx, domain, http.MethodPost, "/directadd", data, &result) - if err != nil { - return nil, err - } - - slog.InfoContext(ctx, "direct_add completed", - "domain", domain, - "group_id", groupID, - "total_emails", result.TotalEmails, - "added_count", len(result.AddedMembers), - "error_count", len(result.Errors)) - - return &result, nil -} - -// UpdateMember updates member properties (e.g., mod_status) -func (c *Client) UpdateMember(ctx context.Context, domain string, memberID uint64, updates MemberUpdateOptions) error { - slog.InfoContext(ctx, "updating member in Groups.io", - "domain", domain, "member_id", memberID) - - data, err := query.Values(updates) - if err != nil { - return fmt.Errorf("failed to encode updates: %w", err) - } - data.Set("member_id", strconv.FormatUint(memberID, 10)) - - err = c.makeRequest(ctx, domain, http.MethodPost, "/updatemember", data, nil) - if err != nil { - return err - } - - slog.InfoContext(ctx, "member updated successfully in Groups.io", - "member_id", memberID) - - return nil -} - -// UpdateGroup updates a Groups.io group with the provided options -func (c *Client) UpdateGroup(ctx context.Context, domain string, groupID uint64, updates GroupUpdateOptions) error { - slog.InfoContext(ctx, "updating group in Groups.io", - "domain", domain, "group_id", groupID) - - data, err := query.Values(updates) - if err != nil { - return fmt.Errorf("failed to encode updates: %w", err) - } - data.Set("group_id", strconv.FormatUint(groupID, 10)) - - err = c.makeRequest(ctx, domain, http.MethodPost, "/updategroup", data, nil) - if err != nil { - return err - } - - slog.InfoContext(ctx, "group updated successfully in Groups.io", - "group_id", groupID) - - return nil -} - -// UpdateSubgroup updates a Groups.io subgroup with the provided options -func (c *Client) UpdateSubgroup(ctx context.Context, domain string, subgroupID uint64, updates SubgroupUpdateOptions) error { - slog.InfoContext(ctx, "updating subgroup in Groups.io", - "domain", domain, "subgroup_id", subgroupID) - - data, err := query.Values(updates) - if err != nil { - return fmt.Errorf("failed to encode updates: %w", err) - } - data.Set("subgroup_id", strconv.FormatUint(subgroupID, 10)) - - err = c.makeRequest(ctx, domain, http.MethodPost, "/updatesubgroup", data, nil) - if err != nil { - return err - } - - slog.InfoContext(ctx, "subgroup updated successfully in Groups.io", - "subgroup_id", subgroupID) - - return nil -} - -// RemoveMember removes a single member from a subgroup -func (c *Client) RemoveMember(ctx context.Context, domain string, groupID uint64, memberID uint64) error { - slog.InfoContext(ctx, "removing member from Groups.io", - "domain", domain, "member_id", memberID) - - data := url.Values{ - "group_id": {strconv.FormatUint(groupID, 10)}, - "member_info_id": {strconv.FormatUint(memberID, 10)}, - } - - err := c.makeRequest(ctx, domain, http.MethodPost, "/removemember", data, nil) - if err != nil { - return err - } - - slog.InfoContext(ctx, "member removed successfully from Groups.io", - "member_id", memberID) - - return nil -} - -// makeRequest centralizes all API calls with authentication and error handling -func (c *Client) makeRequest(ctx context.Context, domain string, method string, path string, data url.Values, result interface{}) error { - // Get or refresh token for domain - token, err := c.getOrRefreshToken(ctx, domain) - if err != nil { - return fmt.Errorf("authentication failed: %w", err) - } - - // Build request URL - reqURL := c.config.BaseURL + "/v1" + path - - var body io.Reader - headers := map[string]string{} - - if method == "POST" && data != nil { - // Add cached token to POST data (Groups.io pattern) - data.Set("csrf", token) - body = strings.NewReader(data.Encode()) - headers["Content-Type"] = "application/x-www-form-urlencoded" - } else if method == "GET" && data != nil { - reqURL += "?" + data.Encode() - } - - // Set domain header for vhost auth (critical for multi-tenant) - headers["Host"] = domain - - // NOTE: BasicAuth is now handled automatically by RoundTripper (production pattern) - // No manual Authorization header needed - RoundTripper calls req.SetBasicAuth(token, "") - - // Make request using httpclient (with retry logic + RoundTripper auth) - resp, err := c.httpClient.Request(ctx, method, reqURL, body, headers) - if err != nil { - return MapHTTPError(ctx, err) - } - - // Parse response - if result != nil { - if err := json.Unmarshal(resp.Body, result); err != nil { - return fmt.Errorf("failed to parse response: %w", err) - } - } - - return nil -} - -// WithDomain ensures authentication for a domain (production pattern) -func (c *Client) WithDomain(ctx context.Context, domain string) error { - // Check if auth token is already cached - c.cacheMu.RLock() - if cache, exists := c.cache[domain]; exists { - cache.mu.RLock() - if time.Now().Before(cache.expiry) { - cache.mu.RUnlock() - c.cacheMu.RUnlock() - slog.DebugContext(ctx, "using cached Groups.io login token", "domain", domain) - return nil - } - cache.mu.RUnlock() - } - c.cacheMu.RUnlock() - - // Need to get new token - _, err := c.getToken(ctx, domain) - if err != nil { - slog.ErrorContext(ctx, "failed to authenticate with Groups.io", "error", err, "domain", domain) - return fmt.Errorf("failed to authenticate request: %w", err) - } - return nil -} - -// getOrRefreshToken implements smart token caching with JWT expiry -func (c *Client) getOrRefreshToken(ctx context.Context, domain string) (string, error) { - c.cacheMu.RLock() - if cache, exists := c.cache[domain]; exists { - cache.mu.RLock() - if time.Now().Before(cache.expiry) { - token := cache.token - cache.mu.RUnlock() - c.cacheMu.RUnlock() - return token, nil - } - cache.mu.RUnlock() - } - c.cacheMu.RUnlock() - - // Need to authenticate - use simplified getToken - return c.getToken(ctx, domain) -} - -// getToken authenticates a user and returns a login token (production pattern) -func (c *Client) getToken(ctx context.Context, domain string) (string, error) { - // Login endpoint with timeout (prevents hanging on invalid domains) - loginCtx, cancel := context.WithTimeout(ctx, c.config.Timeout) - defer cancel() - - data := url.Values{ - "email": {c.config.Email}, - "password": {c.config.Password}, - "token": {"true"}, - } - - // Set domain header for vhost auth (critical for multi-tenant) - headers := map[string]string{ - "Host": domain, - } - - // Use GET with query parameters (production pattern) - loginURL := c.config.BaseURL + "/v1/login?" + data.Encode() - resp, err := c.httpClient.Request(loginCtx, http.MethodGet, loginURL, nil, headers) - if err != nil { - return "", fmt.Errorf("login request failed: %w", MapHTTPError(loginCtx, err)) - } - - var loginResp LoginObject - if err := json.Unmarshal(resp.Body, &loginResp); err != nil { - return "", fmt.Errorf("login response parse failed: %w", err) - } - - token := loginResp.Token - if token == "" { - return "", fmt.Errorf("no token in login response") - } - - // Parse JWT for expiry (production pattern) - expiry := c.parseTokenExpiry(token) - - // Cache token for this domain - c.cacheMu.Lock() - if c.cache[domain] == nil { - c.cache[domain] = &tokenCache{} - } - cache := c.cache[domain] - c.cacheMu.Unlock() - - cache.mu.Lock() - cache.token = token - cache.expiry = expiry - cache.mu.Unlock() - - slog.InfoContext(ctx, "Groups.io authentication successful", - "domain", domain, "expires_at", expiry.Format(time.RFC3339)) - - return token, nil -} - -// parseTokenExpiry extracts expiry from JWT (reused from go-groupsio) -func (c *Client) parseTokenExpiry(token string) time.Time { - parser := jwt.Parser{} - claims := jwt.MapClaims{} - - _, _, err := parser.ParseUnverified(token, &claims) - if err != nil { - slog.Warn("failed to parse JWT token", "error", err) - return time.Now().Add(10 * time.Minute) // Default TTL - } - - exp, err := claims.GetExpirationTime() - if err != nil || exp == nil { - slog.Warn("no expiry in JWT token", "error", err) - return time.Now().Add(10 * time.Minute) // Default TTL - } - - // Cache until 1 minute before expiry - return exp.Time.Add(-1 * time.Minute) -} - -// IsReady checks if Groups.io API is accessible -func (c *Client) IsReady(ctx context.Context) error { - resp, err := c.httpClient.Request(ctx, "GET", c.config.BaseURL, nil, nil) - if err != nil { - return fmt.Errorf("groups.io API unreachable: %w", MapHTTPError(ctx, err)) - } - if resp.StatusCode >= 400 { - return fmt.Errorf("groups.io API unhealthy (status: %d)", resp.StatusCode) - } - return nil -} diff --git a/internal/infrastructure/groupsio/config.go b/internal/infrastructure/groupsio/config.go deleted file mode 100644 index fb3fe53..0000000 --- a/internal/infrastructure/groupsio/config.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package groupsio - -import ( - "os" - "strconv" - "time" -) - -// Config holds the configuration for the GroupsIO client -type Config struct { - // BaseURL is the Groups.io API base URL - BaseURL string - - // Email is the Groups.io account email for authentication - Email string - - // Password is the Groups.io account password for authentication - Password string - - // Timeout is the HTTP client timeout for requests - Timeout time.Duration - - // MaxRetries is the maximum number of retry attempts for failed requests - MaxRetries int - - // RetryDelay is the delay between retry attempts - RetryDelay time.Duration - - // MockMode disables real Groups.io API calls (for testing) - MockMode bool -} - -// DefaultConfig returns a Config with sensible defaults -func DefaultConfig() Config { - return Config{ - BaseURL: "https://api.groups.io", - Timeout: 30 * time.Second, - MaxRetries: 3, - RetryDelay: 1 * time.Second, - MockMode: false, - } -} - -// NewConfigFromEnv creates a Config from environment variables -func NewConfigFromEnv() Config { - config := DefaultConfig() - - if baseURL := os.Getenv("GROUPSIO_BASE_URL"); baseURL != "" { - config.BaseURL = baseURL - } - - if email := os.Getenv("GROUPSIO_EMAIL"); email != "" { - config.Email = email - } - - if password := os.Getenv("GROUPSIO_PASSWORD"); password != "" { - config.Password = password - } - - if timeoutStr := os.Getenv("GROUPSIO_TIMEOUT"); timeoutStr != "" { - if timeout, err := time.ParseDuration(timeoutStr); err == nil { - config.Timeout = timeout - } - } - - if retriesStr := os.Getenv("GROUPSIO_MAX_RETRIES"); retriesStr != "" { - if retries, err := strconv.Atoi(retriesStr); err == nil { - config.MaxRetries = retries - } - } - - if delayStr := os.Getenv("GROUPSIO_RETRY_DELAY"); delayStr != "" { - if delay, err := time.ParseDuration(delayStr); err == nil { - config.RetryDelay = delay - } - } - - // Check for mock mode - if mockMode := os.Getenv("GROUPSIO_SOURCE"); mockMode == "mock" { - config.MockMode = true - } - - return config -} diff --git a/internal/infrastructure/groupsio/errors.go b/internal/infrastructure/groupsio/errors.go deleted file mode 100644 index 0bb3388..0000000 --- a/internal/infrastructure/groupsio/errors.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package groupsio - -import ( - "context" - "fmt" - "log/slog" - "net/http" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/httpclient" -) - -// MapHTTPError maps httpclient errors to domain errors with proper context logging -func MapHTTPError(ctx context.Context, err error) error { - if err == nil { - return nil - } - - // Check if it's a retryable error from httpclient - if retryableErr, ok := err.(*httpclient.RetryableError); ok { - slog.WarnContext(ctx, "Groups.io HTTP error occurred", - "status_code", retryableErr.StatusCode, - "message", retryableErr.Message, - ) - - switch retryableErr.StatusCode { - case http.StatusNotFound: - return errors.NewNotFound("resource not found in Groups.io", err) - case http.StatusConflict: - return errors.NewConflict("resource already exists in Groups.io", err) - case http.StatusUnauthorized: - return errors.NewUnauthorized("Groups.io authentication failed", err) - case http.StatusForbidden: - return errors.NewValidation("Groups.io access denied", err) - case http.StatusTooManyRequests: - return errors.NewServiceUnavailable("Groups.io rate limited", err) - case http.StatusBadRequest: - return errors.NewValidation(fmt.Sprintf("Groups.io validation error: %s", retryableErr.Message), err) - case http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout: - return errors.NewServiceUnavailable("Groups.io service unavailable", err) - default: - slog.ErrorContext(ctx, "Unexpected Groups.io HTTP status code", - "status_code", retryableErr.StatusCode, - "message", retryableErr.Message, - ) - return errors.NewUnexpected("Groups.io API error", err) - } - } - - // Handle other error types (network, timeout, etc.) - slog.ErrorContext(ctx, "Groups.io request failed with non-HTTP error", - "error", err.Error(), - ) - return errors.NewUnexpected("Groups.io request failed", err) -} - -// WrapGroupsIOError wraps a Groups.io API error response with proper context logging -func WrapGroupsIOError(ctx context.Context, errObj *ErrorObject) error { - if errObj == nil { - return nil - } - - slog.WarnContext(ctx, "Groups.io API error response", - "error_type", errObj.Type, - "message", errObj.Message, - "code", errObj.Code, - ) - - switch errObj.Type { - case "validation_error": - return errors.NewValidation(errObj.Message) - case "not_found": - return errors.NewNotFound(errObj.Message) - case "conflict": - return errors.NewConflict(errObj.Message) - case "unauthorized": - return errors.NewUnauthorized(errObj.Message) - case "forbidden": - return errors.NewValidation(errObj.Message) // Access denied, not auth failure - case "rate_limited": - return errors.NewServiceUnavailable(errObj.Message) - default: - slog.ErrorContext(ctx, "Unknown Groups.io error type", - "error_type", errObj.Type, - "message", errObj.Message, - ) - return errors.NewUnexpected(errObj.Message) - } -} diff --git a/internal/infrastructure/groupsio/grpsio_webhook_validator.go b/internal/infrastructure/groupsio/grpsio_webhook_validator.go deleted file mode 100644 index fcac6ff..0000000 --- a/internal/infrastructure/groupsio/grpsio_webhook_validator.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package groupsio - -import ( - "crypto/hmac" - "crypto/sha1" - "encoding/base64" - "fmt" - "log/slog" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" -) - -// GrpsIOWebhookValidator handles validation of GroupsIO webhook signatures -type GrpsIOWebhookValidator struct { - secret string -} - -// NewGrpsIOWebhookValidator creates a new GroupsIO webhook validator -func NewGrpsIOWebhookValidator(secret string) port.GrpsIOWebhookValidator { - return &GrpsIOWebhookValidator{secret: secret} -} - -// ValidateSignature validates the GroupsIO HMAC-SHA1 signature -func (v *GrpsIOWebhookValidator) ValidateSignature(body []byte, signature string) error { - if v.secret == "" { - return fmt.Errorf("webhook secret not configured") - } - - if signature == "" { - return fmt.Errorf("missing webhook signature") - } - - // Calculate HMAC-SHA1 with base64 encoding (GroupsIO algorithm) - mac := hmac.New(sha1.New, []byte(v.secret)) - mac.Write(body) - expectedSignature := base64.StdEncoding.EncodeToString(mac.Sum(nil)) - - // Constant-time comparison to prevent timing attacks - if !hmac.Equal([]byte(signature), []byte(expectedSignature)) { - slog.Error("invalid webhook signature") - return fmt.Errorf("invalid webhook signature") - } - - return nil -} - -// IsValidEvent checks if the event type is supported by GroupsIO -func (v *GrpsIOWebhookValidator) IsValidEvent(eventType string) bool { - validEvents := map[string]bool{ - constants.SubGroupCreatedEvent: true, - constants.SubGroupDeletedEvent: true, - constants.SubGroupMemberAddedEvent: true, - constants.SubGroupMemberRemovedEvent: true, - constants.SubGroupMemberBannedEvent: true, - } - return validEvents[eventType] -} diff --git a/internal/infrastructure/groupsio/models.go b/internal/infrastructure/groupsio/models.go deleted file mode 100644 index 67f90c6..0000000 --- a/internal/infrastructure/groupsio/models.go +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package groupsio - -import "time" - -// GroupObject represents a Groups.io group (expanded to match production structure) -type GroupObject struct { - ID uint64 `json:"id"` - Object string `json:"object"` - Created string `json:"created"` - Updated string `json:"updated"` - Title string `json:"title"` - Name string `json:"name"` - Alias string `json:"alias"` - Desc string `json:"desc"` - PlainDesc string `json:"plain_desc"` - SubjectTag string `json:"subject_tag"` - Footer string `json:"footer"` - Website string `json:"website"` - Announce bool `json:"announce"` - Restricted bool `json:"restricted"` - Moderated bool `json:"moderated"` - Privacy string `json:"privacy"` - OrgID uint64 `json:"org_id"` - OrgDomain string `json:"org_domain"` - SubsCount uint64 `json:"subs_count"` - GroupURL string `json:"group_url"` -} - -// SubgroupObject represents a Groups.io subgroup (mailing list) -type SubgroupObject struct { - ID uint64 `json:"id"` - GroupID uint64 `json:"group_id"` - Name string `json:"name"` - Description string `json:"description"` - Public bool `json:"public"` - Restricted bool `json:"restricted"` // If true, users must request to join and be approved - InviteOnly bool `json:"invite_only"` // If true, only invited users can join - Type string `json:"type"` // announcement, discussion_moderated, discussion_open - SubsCount uint64 `json:"subs_count"` // Subscriber count from Groups.io - CreatedAt string `json:"created"` - UpdatedAt string `json:"updated"` -} - -// MemberObject represents a Groups.io member (simplified from go-groupsio) -type MemberObject struct { - ID uint64 `json:"id"` - GroupID uint64 `json:"group_id"` - SubgroupID uint64 `json:"subgroup_id,omitempty"` - Email string `json:"email"` - Name string `json:"name"` - FirstName string `json:"first_name,omitempty"` - LastName string `json:"last_name,omitempty"` - Username string `json:"username,omitempty"` - Status string `json:"status"` // normal, pending, bouncing, etc. - ModStatus string `json:"mod_status"` // none, moderator, owner - DeliveryMode string `json:"delivery"` // individual, digest, no_email - JoinedAt string `json:"joined"` - UpdatedAt string `json:"updated"` -} - -// DirectAddResultsObject represents the response from the /directadd endpoint -type DirectAddResultsObject struct { - Object string `json:"object"` - TotalEmails uint64 `json:"total_emails"` - Errors []AddErrorObject `json:"errors"` - AddedMembers []MemberInfoObject `json:"added_members"` -} - -// MemberInfoObject represents detailed member information from Groups.io -type MemberInfoObject struct { - ID uint64 `json:"id"` - Object string `json:"object"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` - UserID uint64 `json:"user_id"` - GroupID uint64 `json:"group_id"` - GroupName string `json:"group_name"` - Status string `json:"status"` - PostStatus string `json:"post_status"` - EmailDelivery string `json:"email_delivery"` - MessageSelection string `json:"message_selection"` - AutoFollowReplies bool `json:"auto_follow_replies"` - MaxAttachmentSize string `json:"max_attachment_size"` - ApprovedPosts uint64 `json:"approved_posts"` - ModStatus string `json:"mod_status"` - PendingMsgNotify string `json:"pending_msg_notify"` - PendingSubNotify string `json:"pending_sub_notify"` - SubNotify string `json:"sub_notify"` - StorageNotify string `json:"storage_notify"` - SubGroupNotify string `json:"sub_group_notify"` - MessageReportNotify string `json:"message_report_notify"` - ModPermissions string `json:"mod_permissions"` - OwnerMsgNotify string `json:"owner_msg_notify"` - Email string `json:"email"` - UserStatus string `json:"user_status"` - UserName string `json:"user_name"` - Timezone string `json:"timezone"` - FullName string `json:"full_name"` -} - -// MemberInfoListObject represents a paginated list of member information -type MemberInfoListObject struct { - Object string `json:"object"` - TotalCount uint64 `json:"total_count"` - StartItem uint64 `json:"start_item"` - EndItem uint64 `json:"end_item"` - HasMore bool `json:"has_more"` - NextPageToken uint64 `json:"next_page_token"` - Data []MemberInfoObject `json:"data"` -} - -// AddErrorObject represents an error when adding a member via direct_add -type AddErrorObject struct { - Object string `json:"object"` - Email string `json:"email"` - Status string `json:"status"` - GroupID uint64 `json:"group_id"` -} - -// LoginObject represents the Groups.io login response -type LoginObject struct { - Token string `json:"token"` - User interface{} `json:"user,omitempty"` // Can be object or array, we don't need to parse it - UserID uint64 `json:"user_id,omitempty"` - Email string `json:"email,omitempty"` - ExpiresAt string `json:"expires_at,omitempty"` -} - -// ErrorObject represents a Groups.io API error response -type ErrorObject struct { - Type string `json:"type"` - Message string `json:"message"` - Code int `json:"code,omitempty"` -} - -// GroupCreateOptions represents options for creating a group (matches production go-groupsio) -type GroupCreateOptions struct { - GroupName string `url:"group_name"` - Desc string `url:"desc"` - Privacy string `url:"privacy"` - SubGroupAccess string `url:"sub_group_access"` - - // Creator subscription options (from production) - EmailDelivery string `url:"email_delivery,omitempty"` - MessageSelection string `url:"message_selection,omitempty"` - AutoFollowReplies bool `url:"auto_follow_replies,omitempty"` -} - -// SubgroupCreateOptions represents options for creating a subgroup (matches production go-groupsio) -type SubgroupCreateOptions struct { - // Subgroup options (production field names) - ParentGroupID uint64 `url:"group_id,omitempty"` // Parent group ID - ParentGroupName string `url:"group_name,omitempty"` // Parent group name - GroupName string `url:"sub_group_name"` // REQUIRED by Groups.io API: must be "sub_group_name" not "subgroup_name" per API spec - Desc string `url:"desc"` // REQUIRED by Groups.io API: must be "desc" not "description" per API spec - Privacy string `url:"privacy,omitempty"` // Privacy setting (optional - may inherit from parent) - Restricted *bool `url:"restricted,omitempty"` // If true, users must request to join and be approved - InviteOnly *bool `url:"invite_only,omitempty"` // If true, only invited users can join - - // Creator subscription options (from production) - EmailDelivery string `url:"email_delivery,omitempty"` - MessageSelection string `url:"message_selection,omitempty"` - AutoFollowReplies bool `url:"auto_follow_replies,omitempty"` - MaxAttachmentSize string `url:"max_attachment_size,omitempty"` -} - -// MemberUpdateOptions represents options for updating a member -type MemberUpdateOptions struct { - ModStatus string `url:"mod_status,omitempty"` // none, moderator, owner - DeliveryMode string `url:"delivery,omitempty"` // individual, digest, no_email - Status string `url:"status,omitempty"` // normal, pending, bouncing - FullName string `url:"full_name,omitempty"` -} - -// GroupUpdateOptions represents options for updating a Groups.io group/service -type GroupUpdateOptions struct { - GlobalOwners []string `url:"global_owners,omitempty"` - Announce *bool `url:"announce,omitempty"` - ReplyTo string `url:"reply_to,omitempty"` - MembersVisible string `url:"members_visible,omitempty"` - CalendarAccess string `url:"calendar_access,omitempty"` - FilesAccess string `url:"files_access,omitempty"` - DatabaseAccess string `url:"database_access,omitempty"` - WikiAccess string `url:"wiki_access,omitempty"` - PhotosAccess string `url:"photos_access,omitempty"` - MemberDirectoryAccess string `url:"member_directory_access,omitempty"` - PollsAccess string `url:"polls_access,omitempty"` - ChatAccess string `url:"chat_access,omitempty"` - HandleAttachments string `url:"handle_attachments,omitempty"` -} - -// SubgroupUpdateOptions represents options for updating a Groups.io subgroup/mailing list -type SubgroupUpdateOptions struct { - Title string `url:"title,omitempty"` - Description string `url:"description,omitempty"` - SubjectTag string `url:"subject_tag,omitempty"` - Restricted *bool `url:"restricted,omitempty"` // If true, users must request to join and be approved - InviteOnly *bool `url:"invite_only,omitempty"` // If true, only invited users can join -} - -// TokenCache represents a cached authentication token -type TokenCache struct { - Token string - ExpiresAt time.Time -} diff --git a/internal/infrastructure/idmapper/nats_mapper.go b/internal/infrastructure/idmapper/nats_mapper.go new file mode 100644 index 0000000..70ad1d8 --- /dev/null +++ b/internal/infrastructure/idmapper/nats_mapper.go @@ -0,0 +1,157 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package idmapper + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain" + "github.com/nats-io/nats.go" +) + +const ( + // lookupSubject is the NATS subject for v1-sync-helper lookup + lookupSubject = "lfx.lookup_v1_mapping" + + // defaultTimeout is the default request timeout + defaultTimeout = 5 * time.Second +) + +// Config holds the configuration for the NATS-based ID mapper +type Config struct { + URL string + Timeout time.Duration +} + +// NATSMapper implements IDMapper using NATS messaging to the v1-sync-helper service +type NATSMapper struct { + conn *nats.Conn + timeout time.Duration +} + +// NewNATSMapper creates a new NATS-based ID mapper +func NewNATSMapper(cfg Config) (*NATSMapper, error) { + if cfg.URL == "" { + return nil, fmt.Errorf("NATS URL is required") + } + + timeout := cfg.Timeout + if timeout == 0 { + timeout = defaultTimeout + } + + // Connect to NATS server + conn, err := nats.Connect(cfg.URL) + if err != nil { + return nil, fmt.Errorf("failed to connect to NATS: %w", err) + } + + return &NATSMapper{ + conn: conn, + timeout: timeout, + }, nil +} + +// Close closes the NATS connection +func (m *NATSMapper) Close() { + if m.conn != nil { + m.conn.Close() + } +} + +// MapProjectV2ToV1 maps a v2 project UID to v1 project SFID +func (m *NATSMapper) MapProjectV2ToV1(ctx context.Context, v2UID string) (string, error) { + if v2UID == "" { + return "", domain.NewValidationError("v2 project UID is required") + } + + key := fmt.Sprintf("project.uid.%s", v2UID) + return m.lookup(ctx, key) +} + +// MapProjectV1ToV2 maps a v1 project SFID to v2 project UID +func (m *NATSMapper) MapProjectV1ToV2(ctx context.Context, v1SFID string) (string, error) { + if v1SFID == "" { + return "", domain.NewValidationError("v1 project SFID is required") + } + + key := fmt.Sprintf("project.sfid.%s", v1SFID) + return m.lookup(ctx, key) +} + +// MapCommitteeV2ToV1 maps a v2 committee UID to v1 committee SFID +// The NATS response format is {project_sfid}:{committee_sfid}, but we only return the committee SFID +func (m *NATSMapper) MapCommitteeV2ToV1(ctx context.Context, v2UID string) (string, error) { + if v2UID == "" { + return "", domain.NewValidationError("v2 committee UID is required") + } + + key := fmt.Sprintf("committee.uid.%s", v2UID) + response, err := m.lookup(ctx, key) + if err != nil { + return "", err + } + + return parseCommitteeResponse(response) +} + +// parseCommitteeResponse parses the v1-sync-helper response for committee lookups. +// Responses may be plain SFIDs or compound "projectSFID:committeeSFID" — only the +// committee SFID is returned. +func parseCommitteeResponse(response string) (string, error) { + parts := strings.Split(response, ":") + if len(parts) == 1 { + return response, nil + } + + if len(parts) != 2 { + return "", domain.NewUnavailableError(fmt.Sprintf("unexpected committee mapping format: %s", response)) + } + + committeeSFID := parts[1] + if committeeSFID == "" { + return "", domain.NewUnavailableError("committee SFID is empty in mapping response") + } + + return committeeSFID, nil +} + +// MapCommitteeV1ToV2 maps a v1 committee SFID to v2 committee UID +func (m *NATSMapper) MapCommitteeV1ToV2(ctx context.Context, v1SFID string) (string, error) { + if v1SFID == "" { + return "", domain.NewValidationError("v1 committee SFID is required") + } + + key := fmt.Sprintf("committee.sfid.%s", v1SFID) + return m.lookup(ctx, key) +} + +// lookup performs the NATS request/reply lookup +func (m *NATSMapper) lookup(ctx context.Context, key string) (string, error) { + msg, err := m.conn.RequestWithContext(ctx, lookupSubject, []byte(key)) + if err != nil { + if err == context.DeadlineExceeded || err == nats.ErrTimeout { + return "", domain.NewUnavailableError("v1-sync-helper lookup timed out", err) + } + return "", domain.NewUnavailableError("failed to lookup ID mapping", err) + } + + response := string(msg.Data) + + // Check for error response + if strings.HasPrefix(response, "error: ") { + errMsg := strings.TrimPrefix(response, "error: ") + return "", domain.NewUnavailableError(fmt.Sprintf("v1-sync-helper error: %s", errMsg)) + } + + // Empty response means not found + if response == "" { + return "", domain.NewValidationError(fmt.Sprintf("invalid ID: mapping not found for %s", key)) + } + + return response, nil +} diff --git a/internal/infrastructure/idmapper/nats_mapper_test.go b/internal/infrastructure/idmapper/nats_mapper_test.go new file mode 100644 index 0000000..ecc0633 --- /dev/null +++ b/internal/infrastructure/idmapper/nats_mapper_test.go @@ -0,0 +1,143 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package idmapper + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewNATSMapper_EmptyURL(t *testing.T) { + _, err := NewNATSMapper(Config{URL: ""}) + require.Error(t, err) + assert.Contains(t, err.Error(), "NATS URL is required") +} + +func TestNewNATSMapper_UnreachableURL(t *testing.T) { + _, err := NewNATSMapper(Config{ + URL: "nats://localhost:14222", // port unlikely to be in use + Timeout: 100 * time.Millisecond, + }) + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to connect to NATS") +} + +// The following tests use an internal NATSMapper instance with a nil conn to +// exercise input validation that occurs before any NATS call is made. + +func TestNATSMapper_MapProjectV2ToV1_EmptyUID(t *testing.T) { + m := &NATSMapper{conn: nil, timeout: 5 * time.Second} + + _, err := m.MapProjectV2ToV1(context.Background(), "") + require.Error(t, err) + + var domErr *domain.DomainError + require.True(t, errors.As(err, &domErr)) + assert.Equal(t, domain.ErrorTypeValidation, domErr.Type) + assert.Contains(t, domErr.Message, "v2 project UID is required") +} + +func TestNATSMapper_MapProjectV1ToV2_EmptySFID(t *testing.T) { + m := &NATSMapper{conn: nil, timeout: 5 * time.Second} + + _, err := m.MapProjectV1ToV2(context.Background(), "") + require.Error(t, err) + + var domErr *domain.DomainError + require.True(t, errors.As(err, &domErr)) + assert.Equal(t, domain.ErrorTypeValidation, domErr.Type) + assert.Contains(t, domErr.Message, "v1 project SFID is required") +} + +func TestNATSMapper_MapCommitteeV2ToV1_EmptyUID(t *testing.T) { + m := &NATSMapper{conn: nil, timeout: 5 * time.Second} + + _, err := m.MapCommitteeV2ToV1(context.Background(), "") + require.Error(t, err) + + var domErr *domain.DomainError + require.True(t, errors.As(err, &domErr)) + assert.Equal(t, domain.ErrorTypeValidation, domErr.Type) + assert.Contains(t, domErr.Message, "v2 committee UID is required") +} + +func TestNATSMapper_MapCommitteeV1ToV2_EmptySFID(t *testing.T) { + m := &NATSMapper{conn: nil, timeout: 5 * time.Second} + + _, err := m.MapCommitteeV1ToV2(context.Background(), "") + require.Error(t, err) + + var domErr *domain.DomainError + require.True(t, errors.As(err, &domErr)) + assert.Equal(t, domain.ErrorTypeValidation, domErr.Type) + assert.Contains(t, domErr.Message, "v1 committee SFID is required") +} + +// TestNATSMapper_CommitteeResponseParsing tests the compound response parsing +// logic in MapCommitteeV2ToV1 by directly calling the parseCommitteeResponse helper. +func TestNATSMapper_parseCommitteeResponse(t *testing.T) { + tests := []struct { + name string + response string + expectErr bool + errType domain.ErrorType + expectedSFID string + }{ + { + name: "compound format returns committee SFID", + response: "proj-sfid-001:comm-sfid-002", + expectedSFID: "comm-sfid-002", + }, + { + name: "plain response (no colon) returned as-is", + response: "comm-sfid-only", + expectedSFID: "comm-sfid-only", + }, + { + name: "too many colons is an error", + response: "a:b:c", + expectErr: true, + errType: domain.ErrorTypeUnavailable, + }, + { + name: "empty committee part is an error", + response: "proj-sfid-001:", + expectErr: true, + errType: domain.ErrorTypeUnavailable, + }, + { + name: "UUID-style committee SFID", + response: "a0000000000001:b0000000000002", + expectedSFID: "b0000000000002", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := parseCommitteeResponse(tt.response) + if tt.expectErr { + require.Error(t, err) + var domErr *domain.DomainError + require.True(t, errors.As(err, &domErr)) + assert.Equal(t, tt.errType, domErr.Type) + } else { + require.NoError(t, err) + assert.Equal(t, tt.expectedSFID, result) + } + }) + } +} + +func TestNATSMapper_DefaultTimeout(t *testing.T) { + // Verify that zero timeout uses the default + m := &NATSMapper{conn: nil, timeout: 0} + // A zero timeout would be unusual but shouldn't panic at construction + assert.Equal(t, time.Duration(0), m.timeout) +} diff --git a/internal/infrastructure/idmapper/noop_mapper.go b/internal/infrastructure/idmapper/noop_mapper.go new file mode 100644 index 0000000..4454e8e --- /dev/null +++ b/internal/infrastructure/idmapper/noop_mapper.go @@ -0,0 +1,37 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package idmapper + +import ( + "context" +) + +// NoOpMapper is a no-op ID mapper that returns the input ID unchanged. +// This is useful for local development when the NATS mapping service is not available. +type NoOpMapper struct{} + +// NewNoOpMapper creates a new no-op ID mapper +func NewNoOpMapper() *NoOpMapper { + return &NoOpMapper{} +} + +// MapProjectV2ToV1 returns the input ID unchanged +func (m *NoOpMapper) MapProjectV2ToV1(_ context.Context, v2UID string) (string, error) { + return v2UID, nil +} + +// MapProjectV1ToV2 returns the input ID unchanged +func (m *NoOpMapper) MapProjectV1ToV2(_ context.Context, v1SFID string) (string, error) { + return v1SFID, nil +} + +// MapCommitteeV2ToV1 returns the input ID unchanged +func (m *NoOpMapper) MapCommitteeV2ToV1(_ context.Context, v2UID string) (string, error) { + return v2UID, nil +} + +// MapCommitteeV1ToV2 returns the input ID unchanged +func (m *NoOpMapper) MapCommitteeV1ToV2(_ context.Context, v1SFID string) (string, error) { + return v1SFID, nil +} diff --git a/internal/infrastructure/idmapper/noop_mapper_test.go b/internal/infrastructure/idmapper/noop_mapper_test.go new file mode 100644 index 0000000..6194b33 --- /dev/null +++ b/internal/infrastructure/idmapper/noop_mapper_test.go @@ -0,0 +1,51 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package idmapper + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNoOpMapper_PassThrough(t *testing.T) { + m := NewNoOpMapper() + ctx := context.Background() + + tests := []struct { + name string + fn func(string) (string, error) + id string + }{ + {"MapProjectV2ToV1", func(id string) (string, error) { return m.MapProjectV2ToV1(ctx, id) }, "proj-uuid-123"}, + {"MapProjectV1ToV2", func(id string) (string, error) { return m.MapProjectV1ToV2(ctx, id) }, "a0000000000001"}, + {"MapCommitteeV2ToV1", func(id string) (string, error) { return m.MapCommitteeV2ToV1(ctx, id) }, "comm-uuid-456"}, + {"MapCommitteeV1ToV2", func(id string) (string, error) { return m.MapCommitteeV1ToV2(ctx, id) }, "b0000000000002"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := tt.fn(tt.id) + require.NoError(t, err) + assert.Equal(t, tt.id, result, "NoOpMapper should return input unchanged") + }) + } +} + +func TestNoOpMapper_EmptyInput(t *testing.T) { + m := NewNoOpMapper() + ctx := context.Background() + + result, err := m.MapProjectV2ToV1(ctx, "") + require.NoError(t, err) + assert.Equal(t, "", result) +} + +func TestNoOpMapper_ImplementsInterface(t *testing.T) { + // Verifies the NoOpMapper satisfies the domain.IDMapper interface at compile time + // via the type assertion in providers.go — this test documents the contract + m := NewNoOpMapper() + assert.NotNil(t, m) +} diff --git a/internal/infrastructure/mock/error_simulation_test.go b/internal/infrastructure/mock/error_simulation_test.go deleted file mode 100644 index f230157..0000000 --- a/internal/infrastructure/mock/error_simulation_test.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package mock - -import ( - "context" - "errors" - "testing" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - pkgerrors "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestErrorSimulation(t *testing.T) { - ctx := context.Background() - - t.Run("Service error simulation", func(t *testing.T) { - repo := NewMockRepository() - - // Configure error for specific service - serviceUID := "test-service-uid" - expectedErr := pkgerrors.NewNotFound("simulated service not found") - repo.SetErrorForService(serviceUID, expectedErr) - - // Try to get the service - should return configured error - _, _, err := repo.GetGrpsIOService(ctx, serviceUID) - require.Error(t, err) - assert.True(t, errors.Is(err, expectedErr)) - }) - - t.Run("Mailing list error simulation", func(t *testing.T) { - repo := NewMockRepository() - - // Configure error for specific mailing list - mailingListUID := "test-mailinglist-uid" - expectedErr := pkgerrors.NewConflict("simulated mailing list conflict") - repo.SetErrorForMailingList(mailingListUID, expectedErr) - - // Try to get the mailing list - should return configured error - _, _, err := repo.GetGrpsIOMailingList(ctx, mailingListUID) - require.Error(t, err) - assert.True(t, errors.Is(err, expectedErr)) - }) - - t.Run("Member error simulation", func(t *testing.T) { - repo := NewMockRepository() - - // Configure error for specific member - memberUID := "test-member-uid" - expectedErr := pkgerrors.NewUnauthorized("simulated member unauthorized") - repo.SetErrorForMember(memberUID, expectedErr) - - // Try to get the member - should return configured error - _, _, err := repo.GetGrpsIOMember(ctx, memberUID) - require.Error(t, err) - assert.True(t, errors.Is(err, expectedErr)) - }) - - t.Run("Operation error simulation", func(t *testing.T) { - repo := NewMockRepository() - - // Configure error for specific operation - expectedErr := pkgerrors.NewServiceUnavailable("simulated service unavailable") - repo.SetErrorForOperation("CreateGrpsIOMailingList", expectedErr) - - // Try to create a mailing list - should return configured error - mailingList := &model.GrpsIOMailingList{ - UID: "test-create-uid", - GroupName: "test-list", - Type: "discussion_open", - } - _, _, err := repo.CreateGrpsIOMailingList(ctx, mailingList) - require.Error(t, err) - assert.True(t, errors.Is(err, expectedErr)) - }) - - t.Run("Global error simulation", func(t *testing.T) { - repo := NewMockRepository() - - // Configure global error for all operations - expectedErr := pkgerrors.NewUnexpected("simulated global error") - repo.SetGlobalError(expectedErr) - - // Try any operation - should return configured global error - _, _, err := repo.GetGrpsIOService(ctx, "any-service") - require.Error(t, err) - assert.True(t, errors.Is(err, expectedErr)) - - // Try another operation - should also return global error - _, _, err = repo.GetGrpsIOMailingList(ctx, "any-mailinglist") - require.Error(t, err) - assert.True(t, errors.Is(err, expectedErr)) - }) - - t.Run("Clear error simulation", func(t *testing.T) { - repo := NewMockRepository() - - // Configure some errors - repo.SetErrorForService("test-service", pkgerrors.NewNotFound("test error")) - repo.SetGlobalError(pkgerrors.NewUnexpected("global error")) - - // Clear all error simulation - repo.ClearErrorSimulation() - - // Operations should work normally now (return NotFound for non-existent resources) - _, _, err := repo.GetGrpsIOService(ctx, "non-existent-service") - require.Error(t, err) - - // Should be NotFound error from normal logic, not our simulated error - var notFoundErr pkgerrors.NotFound - assert.True(t, errors.As(err, ¬FoundErr)) - assert.Contains(t, err.Error(), "service with UID non-existent-service not found") - }) - - t.Run("Error priority - global takes precedence", func(t *testing.T) { - repo := NewMockRepository() - - // Set both specific and global errors - serviceUID := "test-service" - specificErr := pkgerrors.NewNotFound("specific error") - globalErr := pkgerrors.NewUnexpected("global error") - - repo.SetErrorForService(serviceUID, specificErr) - repo.SetGlobalError(globalErr) - - // Global error should take precedence - _, _, err := repo.GetGrpsIOService(ctx, serviceUID) - require.Error(t, err) - assert.True(t, errors.Is(err, globalErr)) - assert.False(t, errors.Is(err, specificErr)) - }) - - t.Run("Error priority - operation over resource", func(t *testing.T) { - repo := NewMockRepository() - - // Clear any existing error simulation first - repo.ClearErrorSimulation() - - // Set both operation and resource-specific errors - serviceUID := "test-service" - resourceErr := pkgerrors.NewNotFound("resource error") - operationErr := pkgerrors.NewConflict("operation error") - - repo.SetErrorForService(serviceUID, resourceErr) - repo.SetErrorForOperation("GetGrpsIOService", operationErr) - - // Operation error should take precedence - _, _, err := repo.GetGrpsIOService(ctx, serviceUID) - require.Error(t, err) - assert.True(t, errors.Is(err, operationErr)) - assert.False(t, errors.Is(err, resourceErr)) - }) -} diff --git a/internal/infrastructure/mock/grpsio.go b/internal/infrastructure/mock/grpsio.go deleted file mode 100644 index 5eb1ad4..0000000 --- a/internal/infrastructure/mock/grpsio.go +++ /dev/null @@ -1,2271 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package mock - -import ( - "context" - "fmt" - "log/slog" - "slices" - "strings" - "sync" - "time" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/groupsio" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" -) - -// Global mock repository instance to share data between all repositories -var ( - globalMockRepo *MockRepository - globalMockRepoOnce = &sync.Once{} -) - -// ErrorSimulationConfig configures error simulation for testing scenarios -type ErrorSimulationConfig struct { - Enabled bool `json:"enabled"` - ServiceErrors map[string]error `json:"-"` // service_uid -> error to return - MailingListErrors map[string]error `json:"-"` // mailing_list_uid -> error to return - MemberErrors map[string]error `json:"-"` // member_uid -> error to return - GlobalError error `json:"-"` // error for all operations - OperationErrors map[string]error `json:"-"` // operation_name -> error -} - -// MockRepository provides a mock implementation of all repository interfaces for testing -type MockRepository struct { - services map[string]*model.GrpsIOService - serviceRevisions map[string]uint64 - serviceIndexKeys map[string]*model.GrpsIOService // indexKey -> service - settings map[string]*model.GrpsIOServiceSettings // serviceUID -> settings - settingsRevisions map[string]uint64 // serviceUID -> revision - mailingLists map[string]*model.GrpsIOMailingList - mailingListRevisions map[string]uint64 - mailingListIndexKeys map[string]*model.GrpsIOMailingList // indexKey -> mailingList - mailingListSettings map[string]*model.GrpsIOMailingListSettings // mailingListUID -> settings - mailingListSettingsRevisions map[string]uint64 // mailingListUID -> revision - members map[string]*model.GrpsIOMember // UID -> member - memberRevisions map[string]uint64 // UID -> revision - memberIndexKeys map[string]*model.GrpsIOMember // indexKey -> member - projectSlugs map[string]string // projectUID -> slug - projectNames map[string]string // projectUID -> name - projectParents map[string]string // projectUID -> parentProjectUID - committeeNames map[string]string // committeeUID -> name - errorSimulation ErrorSimulationConfig // Error simulation configuration - errorSimulationMu sync.RWMutex // Protect concurrent access to error config - mu sync.RWMutex // Protect concurrent access to maps -} - -// NewMockRepository creates a new mock repository with sample data -func NewMockRepository() *MockRepository { - - globalMockRepoOnce.Do(func() { - now := time.Now() - - mock := &MockRepository{ - services: make(map[string]*model.GrpsIOService), - serviceRevisions: make(map[string]uint64), - serviceIndexKeys: make(map[string]*model.GrpsIOService), - settings: make(map[string]*model.GrpsIOServiceSettings), - settingsRevisions: make(map[string]uint64), - mailingLists: make(map[string]*model.GrpsIOMailingList), - mailingListRevisions: make(map[string]uint64), - mailingListIndexKeys: make(map[string]*model.GrpsIOMailingList), - mailingListSettings: make(map[string]*model.GrpsIOMailingListSettings), - mailingListSettingsRevisions: make(map[string]uint64), - members: make(map[string]*model.GrpsIOMember), - memberRevisions: make(map[string]uint64), - memberIndexKeys: make(map[string]*model.GrpsIOMember), - projectSlugs: make(map[string]string), - projectNames: make(map[string]string), - projectParents: make(map[string]string), - committeeNames: make(map[string]string), - errorSimulation: ErrorSimulationConfig{ - Enabled: false, - ServiceErrors: make(map[string]error), - MailingListErrors: make(map[string]error), - MemberErrors: make(map[string]error), - GlobalError: nil, - OperationErrors: make(map[string]error), - }, - } - - // Add sample project data for testing - mock.projectSlugs = map[string]string{ - "550e8400-e29b-41d4-a716-446655440001": "primary-project", - "550e8400-e29b-41d4-a716-446655440002": "formation-project", - "550e8400-e29b-41d4-a716-446655440003": "shared-project", - "550e8400-e29b-41d4-a716-446655440004": "error-project", - "550e8400-e29b-41d4-a716-446655440005": "get-test-project", - "66666666-6666-6666-6666-666666666666": "delete-test-project", - "7cad5a8d-19d0-41a4-81a6-043453daf9ee": "sample-project", - } - - mock.projectNames = map[string]string{ - "550e8400-e29b-41d4-a716-446655440001": "Primary Test Project", - "550e8400-e29b-41d4-a716-446655440002": "Formation Test Project", - "550e8400-e29b-41d4-a716-446655440003": "Shared Test Project", - "550e8400-e29b-41d4-a716-446655440004": "Error Test Project", - "550e8400-e29b-41d4-a716-446655440005": "Get Test Project", - "66666666-6666-6666-6666-666666666666": "Delete Test Project", - "7cad5a8d-19d0-41a4-81a6-043453daf9ee": "Cloud Native Computing Foundation", - } - - // Add sample committee data for testing - mock.committeeNames = map[string]string{ - "committee-1": "Technical Oversight Committee", - "committee-2": "Security Committee", - "committee-3": "Architecture Committee", - "committee-4": "Marketing Committee", - "committee-5": "Governance Committee", - } - - // Add sample data for testing - sampleServices := []*model.GrpsIOService{ - { - Type: "primary", - UID: "550e8400-e29b-41d4-a716-446655440001", - Domain: "lists.testproject.org", - GroupID: func() *int64 { id := int64(12345); return &id }(), - Status: "created", - GlobalOwners: []string{"admin@testproject.org"}, - Prefix: "", - ProjectSlug: "test-project", - ProjectUID: "7cad5a8d-19d0-41a4-81a6-043453daf9ee", - URL: "https://lists.testproject.org", - GroupName: "test-project", - Public: true, - CreatedAt: now.Add(-24 * time.Hour), - UpdatedAt: now, - }, - { - Type: "formation", - UID: "550e8400-e29b-41d4-a716-446655440002", - Domain: "lists.formation.testproject.org", - GroupID: func() *int64 { id := int64(12346); return &id }(), - Status: "created", - GlobalOwners: []string{"formation@testproject.org"}, - Prefix: "formation", - ProjectSlug: "test-project", - ProjectUID: "7cad5a8d-19d0-41a4-81a6-043453daf9ee", - URL: "https://lists.formation.testproject.org", - GroupName: "test-project-formation", - Public: false, - CreatedAt: now.Add(-12 * time.Hour), - UpdatedAt: now, - }, - { - Type: "primary", - UID: "550e8400-e29b-41d4-a716-446655440003", - Domain: "lists.example.org", - GroupID: func() *int64 { id := int64(12347); return &id }(), - Status: "pending", - GlobalOwners: []string{"owner@example.org", "admin@example.org"}, - Prefix: "", - ProjectSlug: "example-project", - ProjectUID: "8dbc6b9e-20e1-42b5-92b7-154564eaf0ff", - URL: "https://lists.example.org", - GroupName: "example-project", - Public: true, - CreatedAt: now.Add(-6 * time.Hour), - UpdatedAt: now.Add(-1 * time.Hour), - }, - } - - // Store services by ID and build indices - for _, service := range sampleServices { - mock.services[service.UID] = service - mock.serviceRevisions[service.UID] = 1 - mock.serviceIndexKeys[service.BuildIndexKey(context.Background())] = service - } - - // Add sample settings for services - sampleSettings := []*model.GrpsIOServiceSettings{ - { - UID: "550e8400-e29b-41d4-a716-446655440001", - Writers: []model.UserInfo{ - {Name: stringPtr("Test Admin"), Email: stringPtr("admin@testproject.org"), Username: stringPtr("testadmin"), Avatar: stringPtr("https://example.com/avatar1.png")}, - }, - Auditors: []model.UserInfo{ - {Name: stringPtr("Test Auditor"), Email: stringPtr("auditor@testproject.org"), Username: stringPtr("testauditor"), Avatar: stringPtr("https://example.com/avatar2.png")}, - }, - CreatedAt: now.Add(-24 * time.Hour), - UpdatedAt: now, - }, - { - UID: "550e8400-e29b-41d4-a716-446655440002", - Writers: []model.UserInfo{ - {Name: stringPtr("Formation Admin"), Email: stringPtr("formation@testproject.org"), Username: stringPtr("formationadmin"), Avatar: stringPtr("https://example.com/avatar3.png")}, - }, - Auditors: []model.UserInfo{}, - CreatedAt: now.Add(-12 * time.Hour), - UpdatedAt: now, - }, - { - UID: "550e8400-e29b-41d4-a716-446655440003", - Writers: []model.UserInfo{ - {Name: stringPtr("Owner User"), Email: stringPtr("owner@example.org"), Username: stringPtr("owner"), Avatar: stringPtr("https://example.com/avatar4.png")}, - {Name: stringPtr("Admin User"), Email: stringPtr("admin@example.org"), Username: stringPtr("admin"), Avatar: stringPtr("https://example.com/avatar5.png")}, - }, - Auditors: []model.UserInfo{}, - CreatedAt: now.Add(-6 * time.Hour), - UpdatedAt: now.Add(-1 * time.Hour), - }, - } - - // Store settings by service UID - for _, settings := range sampleSettings { - mock.settings[settings.UID] = settings - mock.settingsRevisions[settings.UID] = 1 - } - - // Add project mappings - using consistent naming - mock.projectSlugs["7cad5a8d-19d0-41a4-81a6-043453daf9ee"] = "sample-project" - mock.projectNames["7cad5a8d-19d0-41a4-81a6-043453daf9ee"] = "Cloud Native Computing Foundation" - mock.projectSlugs["8dbc6b9e-20e1-42b5-92b7-154564eaf0ff"] = "example-project" - mock.projectNames["8dbc6b9e-20e1-42b5-92b7-154564eaf0ff"] = "Example Project" - - // Add sample mailing list data - sampleMailingLists := []*model.GrpsIOMailingList{ - { - UID: "mailing-list-1", - GroupName: "dev", - Public: true, - Type: "discussion_open", - Committees: []model.Committee{ - {UID: "committee-1", Name: "Technical Advisory Committee", AllowedVotingStatuses: []string{"Voting Rep", "Observer"}}, - }, - Description: "Development discussions and technical matters for the project", - Title: "Development List", - SubjectTag: "[DEV]", - ServiceUID: "550e8400-e29b-41d4-a716-446655440001", - ProjectUID: "7cad5a8d-19d0-41a4-81a6-043453daf9ee", - CreatedAt: now.Add(-18 * time.Hour), - UpdatedAt: now.Add(-2 * time.Hour), - }, - { - UID: "mailing-list-2", - GroupName: "announce", - Public: true, - Type: "announcement", - Description: "Official announcements and project news for all stakeholders", - Title: "Announcements", - SubjectTag: "[ANNOUNCE]", - ServiceUID: "550e8400-e29b-41d4-a716-446655440001", - ProjectUID: "7cad5a8d-19d0-41a4-81a6-043453daf9ee", - CreatedAt: now.Add(-12 * time.Hour), - UpdatedAt: now.Add(-1 * time.Hour), - }, - { - UID: "mailing-list-3", - GroupName: "formation-security", - Public: false, - Type: "discussion_moderated", - Committees: []model.Committee{ - {UID: "committee-2", Name: "Security Committee", AllowedVotingStatuses: []string{"Voting Rep"}}, - }, - Description: "Private security discussions for committee members only", - Title: "Formation Security List", - SubjectTag: "[SECURITY]", - ServiceUID: "550e8400-e29b-41d4-a716-446655440002", - ProjectUID: "7cad5a8d-19d0-41a4-81a6-043453daf9ee", - CreatedAt: now.Add(-6 * time.Hour), - UpdatedAt: now, - }, - } - - // Store mailing lists by UID and build indices - for _, mailingList := range sampleMailingLists { - mock.mailingLists[mailingList.UID] = mailingList - mock.mailingListRevisions[mailingList.UID] = 1 - mock.mailingListIndexKeys[mailingList.BuildIndexKey(context.Background())] = mailingList - } - - globalMockRepo = mock - }) - - return globalMockRepo -} - -// MockGrpsIOServiceRepository implements GrpsIOServiceRepository interface -type MockGrpsIOServiceRepository struct { - mock *MockRepository -} - -// Verify interface compliance at compile time -var _ port.GrpsIOServiceRepository = (*MockGrpsIOServiceRepository)(nil) - -// MockGrpsIOMailingListRepository implements GrpsIOMailingListRepository interface -type MockGrpsIOMailingListRepository struct { - mock *MockRepository -} - -// Verify interface compliance at compile time -var _ port.GrpsIOMailingListRepository = (*MockGrpsIOMailingListRepository)(nil) - -// ================== MockGrpsIOMailingListRepository implementation ================== - -// CreateGrpsIOMailingList delegates to MockRepository -func (w *MockGrpsIOMailingListRepository) CreateGrpsIOMailingList(ctx context.Context, mailingList *model.GrpsIOMailingList) (*model.GrpsIOMailingList, uint64, error) { - return w.mock.CreateGrpsIOMailingList(ctx, mailingList) -} - -// UpdateGrpsIOMailingList delegates to MockRepository -func (w *MockGrpsIOMailingListRepository) UpdateGrpsIOMailingList(ctx context.Context, uid string, mailingList *model.GrpsIOMailingList, expectedRevision uint64) (*model.GrpsIOMailingList, uint64, error) { - return w.mock.UpdateGrpsIOMailingListWithRevision(ctx, uid, mailingList, expectedRevision) -} - -// DeleteGrpsIOMailingList delegates to MockRepository -func (w *MockGrpsIOMailingListRepository) DeleteGrpsIOMailingList(ctx context.Context, uid string, expectedRevision uint64, mailingList *model.GrpsIOMailingList) error { - return w.mock.DeleteGrpsIOMailingListWithRevision(ctx, uid, expectedRevision) -} - -// CreateSecondaryIndices delegates to MockRepository -func (w *MockGrpsIOMailingListRepository) CreateSecondaryIndices(ctx context.Context, mailingList *model.GrpsIOMailingList) ([]string, error) { - return w.mock.CreateSecondaryIndices(ctx, mailingList) -} - -// UniqueMailingListGroupName delegates to MockRepository -func (w *MockGrpsIOMailingListRepository) UniqueMailingListGroupName(ctx context.Context, mailingList *model.GrpsIOMailingList) (string, error) { - return w.mock.UniqueMailingListGroupName(ctx, mailingList) -} - -// GetKeyRevision delegates to MockRepository -func (w *MockGrpsIOMailingListRepository) GetKeyRevision(ctx context.Context, key string) (uint64, error) { - slog.DebugContext(ctx, "mock get key revision", "key", key) - return 1, nil -} - -// Delete removes a key with the given revision -func (w *MockGrpsIOMailingListRepository) Delete(ctx context.Context, key string, revision uint64) error { - slog.DebugContext(ctx, "mock delete key", "key", key, "revision", revision) - return nil -} - -// CreateGrpsIOMailingListSettings delegates to MockRepository -func (w *MockGrpsIOMailingListRepository) CreateGrpsIOMailingListSettings(ctx context.Context, settings *model.GrpsIOMailingListSettings) (*model.GrpsIOMailingListSettings, uint64, error) { - return w.mock.CreateGrpsIOMailingListSettings(ctx, settings) -} - -// UpdateGrpsIOMailingListSettings delegates to MockRepository -func (w *MockGrpsIOMailingListRepository) UpdateGrpsIOMailingListSettings(ctx context.Context, settings *model.GrpsIOMailingListSettings, expectedRevision uint64) (*model.GrpsIOMailingListSettings, uint64, error) { - return w.mock.UpdateGrpsIOMailingListSettings(ctx, settings, expectedRevision) -} - -// MockGrpsIOMemberRepository implements GrpsIOMemberRepository interface -type MockGrpsIOMemberRepository struct { - mock *MockRepository -} - -// Verify interface compliance at compile time -var _ port.GrpsIOMemberRepository = (*MockGrpsIOMemberRepository)(nil) - -// ================== MockGrpsIOMemberRepository implementation ================== - -// UniqueMember validates member email is unique within mailing list -func (w *MockGrpsIOMemberRepository) UniqueMember(ctx context.Context, member *model.GrpsIOMember) (string, error) { - constraintKey := fmt.Sprintf("lookup:member:constraint:%s:%s", member.MailingListUID, member.Email) - slog.DebugContext(ctx, "mock: validating unique member", "constraint_key", constraintKey) - return constraintKey, nil -} - -// CreateGrpsIOMember creates a new member and returns it with revision -func (w *MockGrpsIOMemberRepository) CreateGrpsIOMember(ctx context.Context, member *model.GrpsIOMember) (*model.GrpsIOMember, uint64, error) { - slog.DebugContext(ctx, "mock member: creating member", "member_uid", member.UID, "email", member.Email) - - w.mock.mu.Lock() - defer w.mock.mu.Unlock() - - // Check if member already exists - if _, exists := w.mock.members[member.UID]; exists { - return nil, 0, errors.NewConflict(fmt.Sprintf("member with ID %s already exists", member.UID)) - } - - // Set created/updated timestamps - now := time.Now() - member.CreatedAt = now - member.UpdatedAt = now - - // Store member copy to avoid external modifications - memberCopy := *member - - w.mock.members[member.UID] = &memberCopy - w.mock.memberRevisions[member.UID] = 1 - w.mock.memberIndexKeys[member.BuildIndexKey(ctx)] = &memberCopy - - // Return member copy - resultCopy := memberCopy - - return &resultCopy, 1, nil -} - -// UpdateGrpsIOMember updates an existing member with optimistic concurrency control -func (w *MockGrpsIOMemberRepository) UpdateGrpsIOMember(ctx context.Context, uid string, member *model.GrpsIOMember, expectedRevision uint64) (*model.GrpsIOMember, uint64, error) { - slog.DebugContext(ctx, "mock member: updating member", "member_uid", uid) - - w.mock.mu.Lock() - defer w.mock.mu.Unlock() - - // Check if member exists - existing, exists := w.mock.members[uid] - if !exists { - return nil, 0, errors.NewNotFound(fmt.Sprintf("member with UID %s not found", uid)) - } - - // Check revision for optimistic concurrency control - currentRevision := w.mock.memberRevisions[uid] - if expectedRevision != currentRevision { - return nil, 0, errors.NewConflict(fmt.Sprintf("revision mismatch: expected %d, got %d", expectedRevision, currentRevision)) - } - - // Update member while preserving immutable fields - memberCopy := *member - memberCopy.UID = existing.UID // Preserve UID - memberCopy.Email = existing.Email // Preserve email (immutable) - memberCopy.MailingListUID = existing.MailingListUID // Preserve mailing list UID (immutable) - memberCopy.CreatedAt = existing.CreatedAt // Preserve created timestamp - memberCopy.UpdatedAt = time.Now() // Update timestamp - - // Store updated member and increment revision - w.mock.members[uid] = &memberCopy - newRevision := currentRevision + 1 - w.mock.memberRevisions[uid] = newRevision - - // Update index if email or mailing list changed (though they shouldn't for immutable fields) - if w.mock.memberIndexKeys != nil { - w.mock.memberIndexKeys[memberCopy.BuildIndexKey(ctx)] = &memberCopy - } - - // Return member copy - resultCopy := memberCopy - - return &resultCopy, newRevision, nil -} - -// DeleteGrpsIOMember deletes a member with optimistic concurrency control -func (w *MockGrpsIOMemberRepository) DeleteGrpsIOMember(ctx context.Context, uid string, expectedRevision uint64, member *model.GrpsIOMember) error { - slog.DebugContext(ctx, "mock member: deleting member", "member_uid", uid) - - w.mock.mu.Lock() - defer w.mock.mu.Unlock() - - // Check if member exists - existing, exists := w.mock.members[uid] - if !exists { - return errors.NewNotFound(fmt.Sprintf("member with UID %s not found", uid)) - } - - // Check revision for optimistic concurrency control - currentRevision := w.mock.memberRevisions[uid] - if expectedRevision != currentRevision { - return errors.NewConflict(fmt.Sprintf("revision mismatch: expected %d, got %d", expectedRevision, currentRevision)) - } - - // Remove from index - if w.mock.memberIndexKeys != nil { - delete(w.mock.memberIndexKeys, existing.BuildIndexKey(ctx)) - } - - // Delete member and revision - delete(w.mock.members, uid) - delete(w.mock.memberRevisions, uid) - - return nil -} - -// CreateMemberSecondaryIndices creates lookup indices for Groups.io IDs -func (w *MockGrpsIOMemberRepository) CreateMemberSecondaryIndices(ctx context.Context, member *model.GrpsIOMember) ([]string, error) { - slog.DebugContext(ctx, "mock member: creating secondary indices", "member_uid", member.UID) - - // Mock implementation - return mock keys for testing - var keys []string - - if member.MemberID != nil { - keys = append(keys, fmt.Sprintf("lookup/groupsio-member-memberid/%d/%s", *member.MemberID, member.UID)) - } - - if member.GroupID != nil { - keys = append(keys, fmt.Sprintf("lookup/groupsio-member-groupid/%d/%s", *member.GroupID, member.UID)) - } - - return keys, nil -} - -// GetKeyRevision retrieves the revision for a given key -func (w *MockGrpsIOMemberRepository) GetKeyRevision(ctx context.Context, key string) (uint64, error) { - slog.DebugContext(ctx, "mock member: get key revision", "key", key) - return 1, nil -} - -// Delete removes a key with the given revision -func (w *MockGrpsIOMemberRepository) Delete(ctx context.Context, key string, revision uint64) error { - slog.DebugContext(ctx, "mock member: delete key", "key", key, "revision", revision) - return nil -} - -// MockGrpsIOWriter combines both service and mailing list writers -type MockGrpsIOWriter struct { - mock *MockRepository - serviceRepository *MockGrpsIOServiceRepository - mailingListRepository *MockGrpsIOMailingListRepository - memberRepository *MockGrpsIOMemberRepository -} - -// ================== MockGrpsIOWriter implementation (delegates to sub-writers) ================== - -// Service writer methods -func (w *MockGrpsIOWriter) CreateGrpsIOService(ctx context.Context, service *model.GrpsIOService, settings *model.GrpsIOServiceSettings) (*model.GrpsIOService, *model.GrpsIOServiceSettings, uint64, error) { - return w.serviceRepository.CreateGrpsIOService(ctx, service, settings) -} - -func (w *MockGrpsIOWriter) UpdateGrpsIOService(ctx context.Context, uid string, service *model.GrpsIOService, expectedRevision uint64) (*model.GrpsIOService, uint64, error) { - return w.serviceRepository.UpdateGrpsIOService(ctx, uid, service, expectedRevision) -} - -func (w *MockGrpsIOWriter) DeleteGrpsIOService(ctx context.Context, uid string, expectedRevision uint64, service *model.GrpsIOService) error { - return w.serviceRepository.DeleteGrpsIOService(ctx, uid, expectedRevision, service) -} - -func (w *MockGrpsIOWriter) UniqueProjectType(ctx context.Context, service *model.GrpsIOService) (string, error) { - return w.serviceRepository.UniqueProjectType(ctx, service) -} - -func (w *MockGrpsIOWriter) UniqueProjectPrefix(ctx context.Context, service *model.GrpsIOService) (string, error) { - return w.serviceRepository.UniqueProjectPrefix(ctx, service) -} - -func (w *MockGrpsIOWriter) UniqueProjectGroupID(ctx context.Context, service *model.GrpsIOService) (string, error) { - return w.serviceRepository.UniqueProjectGroupID(ctx, service) -} - -func (w *MockGrpsIOWriter) CreateGrpsIOServiceSettings(ctx context.Context, settings *model.GrpsIOServiceSettings) (*model.GrpsIOServiceSettings, uint64, error) { - return w.serviceRepository.CreateGrpsIOServiceSettings(ctx, settings) -} - -func (w *MockGrpsIOWriter) UpdateGrpsIOServiceSettings(ctx context.Context, settings *model.GrpsIOServiceSettings, expectedRevision uint64) (*model.GrpsIOServiceSettings, uint64, error) { - return w.serviceRepository.UpdateGrpsIOServiceSettings(ctx, settings, expectedRevision) -} - -// Mailing list writer methods -func (w *MockGrpsIOWriter) CreateGrpsIOMailingList(ctx context.Context, mailingList *model.GrpsIOMailingList) (*model.GrpsIOMailingList, uint64, error) { - return w.mailingListRepository.CreateGrpsIOMailingList(ctx, mailingList) -} - -func (w *MockGrpsIOWriter) UpdateGrpsIOMailingList(ctx context.Context, uid string, mailingList *model.GrpsIOMailingList, expectedRevision uint64) (*model.GrpsIOMailingList, uint64, error) { - return w.mailingListRepository.UpdateGrpsIOMailingList(ctx, uid, mailingList, expectedRevision) -} - -func (w *MockGrpsIOWriter) DeleteGrpsIOMailingList(ctx context.Context, uid string, expectedRevision uint64, mailingList *model.GrpsIOMailingList) error { - return w.mailingListRepository.DeleteGrpsIOMailingList(ctx, uid, expectedRevision, mailingList) -} - -func (w *MockGrpsIOWriter) CreateSecondaryIndices(ctx context.Context, mailingList *model.GrpsIOMailingList) ([]string, error) { - return w.mailingListRepository.CreateSecondaryIndices(ctx, mailingList) -} - -func (w *MockGrpsIOWriter) UniqueMailingListGroupName(ctx context.Context, mailingList *model.GrpsIOMailingList) (string, error) { - return w.mailingListRepository.UniqueMailingListGroupName(ctx, mailingList) -} - -func (w *MockGrpsIOWriter) CreateGrpsIOMailingListSettings(ctx context.Context, settings *model.GrpsIOMailingListSettings) (*model.GrpsIOMailingListSettings, uint64, error) { - return w.mailingListRepository.CreateGrpsIOMailingListSettings(ctx, settings) -} - -func (w *MockGrpsIOWriter) UpdateGrpsIOMailingListSettings(ctx context.Context, settings *model.GrpsIOMailingListSettings, expectedRevision uint64) (*model.GrpsIOMailingListSettings, uint64, error) { - return w.mailingListRepository.UpdateGrpsIOMailingListSettings(ctx, settings, expectedRevision) -} - -func (w *MockGrpsIOWriter) GetKeyRevision(ctx context.Context, key string) (uint64, error) { - return w.serviceRepository.GetKeyRevision(ctx, key) -} - -// For cleanup operations -func (w *MockGrpsIOWriter) Delete(ctx context.Context, key string, revision uint64) error { - return w.serviceRepository.Delete(ctx, key, revision) -} - -// Member operations - delegate to member writer -func (w *MockGrpsIOWriter) UniqueMember(ctx context.Context, member *model.GrpsIOMember) (string, error) { - return w.memberRepository.UniqueMember(ctx, member) -} - -func (w *MockGrpsIOWriter) CreateGrpsIOMember(ctx context.Context, member *model.GrpsIOMember) (*model.GrpsIOMember, uint64, error) { - return w.memberRepository.CreateGrpsIOMember(ctx, member) -} - -func (w *MockGrpsIOWriter) UpdateGrpsIOMember(ctx context.Context, uid string, member *model.GrpsIOMember, expectedRevision uint64) (*model.GrpsIOMember, uint64, error) { - return w.memberRepository.UpdateGrpsIOMember(ctx, uid, member, expectedRevision) -} - -func (w *MockGrpsIOWriter) DeleteGrpsIOMember(ctx context.Context, uid string, expectedRevision uint64, member *model.GrpsIOMember) error { - return w.memberRepository.DeleteGrpsIOMember(ctx, uid, expectedRevision, member) -} - -func (w *MockGrpsIOWriter) CreateMemberSecondaryIndices(ctx context.Context, member *model.GrpsIOMember) ([]string, error) { - return w.memberRepository.CreateMemberSecondaryIndices(ctx, member) -} - -// MockEntityAttributeReader implements EntityAttributeReader interface -type MockEntityAttributeReader struct { - mock *MockRepository -} - -// ProjectName returns the project name for a given UID -func (r *MockEntityAttributeReader) ProjectName(ctx context.Context, uid string) (string, error) { - slog.DebugContext(ctx, "mock entity attribute reader: getting project name", "uid", uid) - - r.mock.mu.RLock() - defer r.mock.mu.RUnlock() - - name, exists := r.mock.projectNames[uid] - if !exists { - return "", errors.NewNotFound(fmt.Sprintf("project with UID %s not found", uid)) - } - - return name, nil -} - -// ProjectParentUID returns the project parent UID for a given UID -func (r *MockEntityAttributeReader) ProjectParentUID(ctx context.Context, uid string) (string, error) { - slog.DebugContext(ctx, "mock entity attribute reader: getting project parent UID", "uid", uid) - - r.mock.mu.RLock() - defer r.mock.mu.RUnlock() - - parentUID, exists := r.mock.projectParents[uid] - if !exists || parentUID == "" { - return "", errors.NewNotFound(fmt.Sprintf("project parent UID not found for UID %s", uid)) - } - - return parentUID, nil -} - -// ProjectSlug returns the project slug for a given UID -func (r *MockEntityAttributeReader) ProjectSlug(ctx context.Context, uid string) (string, error) { - slog.DebugContext(ctx, "mock entity attribute reader: getting project slug", "uid", uid) - - r.mock.mu.RLock() - defer r.mock.mu.RUnlock() - - slug, exists := r.mock.projectSlugs[uid] - if !exists { - return "", errors.NewNotFound(fmt.Sprintf("project with UID %s not found", uid)) - } - - return slug, nil -} - -// CommitteeName returns the committee name for a given UID -func (r *MockEntityAttributeReader) CommitteeName(ctx context.Context, uid string) (string, error) { - slog.DebugContext(ctx, "mock entity attribute reader: getting committee name", "uid", uid) - - r.mock.mu.RLock() - defer r.mock.mu.RUnlock() - - name, exists := r.mock.committeeNames[uid] - if !exists { - return "", errors.NewNotFound(fmt.Sprintf("committee with UID %s not found", uid)) - } - - return name, nil -} - -// ListMembers returns an empty list for testing (can be customized per test) -func (r *MockEntityAttributeReader) ListMembers(ctx context.Context, committeeUID string) ([]model.CommitteeMember, error) { - slog.DebugContext(ctx, "mock entity attribute reader: listing committee members", "committee_uid", committeeUID) - // Return empty list by default - tests can customize this behavior - return []model.CommitteeMember{}, nil -} - -// Error configuration methods for testing - -// SetErrorForService configures the mock to return an error for a specific service UID -func (m *MockRepository) SetErrorForService(serviceUID string, err error) { - m.errorSimulationMu.Lock() - defer m.errorSimulationMu.Unlock() - - m.errorSimulation.Enabled = true - m.errorSimulation.ServiceErrors[serviceUID] = err -} - -// SetErrorForMailingList configures the mock to return an error for a specific mailing list UID -func (m *MockRepository) SetErrorForMailingList(mailingListUID string, err error) { - m.errorSimulationMu.Lock() - defer m.errorSimulationMu.Unlock() - - m.errorSimulation.Enabled = true - m.errorSimulation.MailingListErrors[mailingListUID] = err -} - -// SetErrorForMember configures the mock to return an error for a specific member UID -func (m *MockRepository) SetErrorForMember(memberUID string, err error) { - m.errorSimulationMu.Lock() - defer m.errorSimulationMu.Unlock() - - m.errorSimulation.Enabled = true - m.errorSimulation.MemberErrors[memberUID] = err -} - -// SetErrorForOperation configures the mock to return an error for a specific operation -func (m *MockRepository) SetErrorForOperation(operation string, err error) { - m.errorSimulationMu.Lock() - defer m.errorSimulationMu.Unlock() - - m.errorSimulation.Enabled = true - m.errorSimulation.OperationErrors[operation] = err -} - -// SetGlobalError configures the mock to return an error for all operations -func (m *MockRepository) SetGlobalError(err error) { - m.errorSimulationMu.Lock() - defer m.errorSimulationMu.Unlock() - - m.errorSimulation.Enabled = true - m.errorSimulation.GlobalError = err -} - -// ClearErrorSimulation disables all error simulation -func (m *MockRepository) ClearErrorSimulation() { - m.errorSimulationMu.Lock() - defer m.errorSimulationMu.Unlock() - - m.errorSimulation.Enabled = false - m.errorSimulation.ServiceErrors = make(map[string]error) - m.errorSimulation.MailingListErrors = make(map[string]error) - m.errorSimulation.MemberErrors = make(map[string]error) - m.errorSimulation.OperationErrors = make(map[string]error) - m.errorSimulation.GlobalError = nil -} - -// checkErrorSimulation checks if an error should be returned based on configuration -func (m *MockRepository) checkErrorSimulation(operation, resourceUID string) error { - m.errorSimulationMu.RLock() - defer m.errorSimulationMu.RUnlock() - - if !m.errorSimulation.Enabled { - return nil - } - - // Check global error first - if m.errorSimulation.GlobalError != nil { - return m.errorSimulation.GlobalError - } - - // Check operation-specific error - if err, exists := m.errorSimulation.OperationErrors[operation]; exists { - return err - } - - // Check resource-specific errors based on operation type - if strings.Contains(operation, "Service") { - if err, exists := m.errorSimulation.ServiceErrors[resourceUID]; exists { - return err - } - } else if strings.Contains(operation, "MailingList") { - if err, exists := m.errorSimulation.MailingListErrors[resourceUID]; exists { - return err - } - } else if strings.Contains(operation, "Member") { - if err, exists := m.errorSimulation.MemberErrors[resourceUID]; exists { - return err - } - } - - return nil -} - -// GetGrpsIOService retrieves a single service by ID and returns ETag revision -func (m *MockRepository) GetGrpsIOService(ctx context.Context, uid string) (*model.GrpsIOService, uint64, error) { - slog.DebugContext(ctx, "mock service: getting service", "service_uid", uid) - - // Check error simulation first - if err := m.checkErrorSimulation("GetGrpsIOService", uid); err != nil { - return nil, 0, err - } - - m.mu.RLock() - defer m.mu.RUnlock() - - service, exists := m.services[uid] - if !exists { - return nil, 0, errors.NewNotFound(fmt.Sprintf("service with UID %s not found", uid)) - } - - // Return a deep copy of the service to avoid data races - serviceCopy := *service - serviceCopy.GlobalOwners = make([]string, len(service.GlobalOwners)) - copy(serviceCopy.GlobalOwners, service.GlobalOwners) - revision := m.serviceRevisions[uid] - return &serviceCopy, revision, nil -} - -// ================== MockGrpsIOServiceWriter implementation ================== - -// CreateGrpsIOService creates a new service and its settings in the mock storage -func (w *MockGrpsIOServiceRepository) CreateGrpsIOService(ctx context.Context, service *model.GrpsIOService, settings *model.GrpsIOServiceSettings) (*model.GrpsIOService, *model.GrpsIOServiceSettings, uint64, error) { - slog.DebugContext(ctx, "mock service: creating service", "service_id", service.UID) - - w.mock.mu.Lock() - defer w.mock.mu.Unlock() - - // Check if service already exists - if _, exists := w.mock.services[service.UID]; exists { - return nil, nil, 0, errors.NewConflict(fmt.Sprintf("service with ID %s already exists", service.UID)) - } - - // Set created/updated timestamps - now := time.Now() - service.CreatedAt = now - service.UpdatedAt = now - - // Store service copy to avoid external modifications - serviceCopy := *service - serviceCopy.GlobalOwners = make([]string, len(service.GlobalOwners)) - copy(serviceCopy.GlobalOwners, service.GlobalOwners) - - w.mock.services[service.UID] = &serviceCopy - w.mock.serviceRevisions[service.UID] = 1 - w.mock.serviceIndexKeys[service.BuildIndexKey(ctx)] = &serviceCopy - - // Create settings if provided - var resultSettingsCopy *model.GrpsIOServiceSettings - if settings != nil { - settings.CreatedAt = now - settings.UpdatedAt = now - settingsCopy := &model.GrpsIOServiceSettings{ - UID: settings.UID, - Writers: append([]model.UserInfo(nil), settings.Writers...), - Auditors: append([]model.UserInfo(nil), settings.Auditors...), - CreatedAt: settings.CreatedAt, - UpdatedAt: settings.UpdatedAt, - } - w.mock.settings[settings.UID] = settingsCopy - w.mock.settingsRevisions[settings.UID] = 1 - - // Create result copy for return - resultSettingsCopy = &model.GrpsIOServiceSettings{ - UID: settingsCopy.UID, - Writers: append([]model.UserInfo(nil), settingsCopy.Writers...), - Auditors: append([]model.UserInfo(nil), settingsCopy.Auditors...), - CreatedAt: settingsCopy.CreatedAt, - UpdatedAt: settingsCopy.UpdatedAt, - } - } - - // Return service copy - resultCopy := serviceCopy - resultCopy.GlobalOwners = make([]string, len(serviceCopy.GlobalOwners)) - copy(resultCopy.GlobalOwners, serviceCopy.GlobalOwners) - - return &resultCopy, resultSettingsCopy, 1, nil -} - -// UpdateGrpsIOService updates an existing service with revision checking -func (w *MockGrpsIOServiceRepository) UpdateGrpsIOService(ctx context.Context, uid string, service *model.GrpsIOService, expectedRevision uint64) (*model.GrpsIOService, uint64, error) { - slog.DebugContext(ctx, "mock service: updating service", "service_uid", uid, "expected_revision", expectedRevision) - - w.mock.mu.Lock() - defer w.mock.mu.Unlock() - - // Check if service exists - existingService, exists := w.mock.services[uid] - if !exists { - return nil, 0, errors.NewNotFound(fmt.Sprintf("service with UID %s not found", uid)) - } - - // Check revision - currentRevision := w.mock.serviceRevisions[uid] - if currentRevision != expectedRevision { - return nil, 0, errors.NewConflict(fmt.Sprintf("revision mismatch: expected %d, got %d", expectedRevision, currentRevision)) - } - - // Preserve created timestamp, update updated timestamp - service.CreatedAt = existingService.CreatedAt - service.UpdatedAt = time.Now() - service.UID = uid // Ensure ID matches - - // Store service copy - serviceCopy := *service - serviceCopy.GlobalOwners = make([]string, len(service.GlobalOwners)) - copy(serviceCopy.GlobalOwners, service.GlobalOwners) - - // Remove old index key and add new one - oldIndexKey := existingService.BuildIndexKey(ctx) - delete(w.mock.serviceIndexKeys, oldIndexKey) - - w.mock.services[uid] = &serviceCopy - newRevision := currentRevision + 1 - w.mock.serviceRevisions[uid] = newRevision - w.mock.serviceIndexKeys[service.BuildIndexKey(ctx)] = &serviceCopy - - // Return service copy - resultCopy := serviceCopy - resultCopy.GlobalOwners = make([]string, len(serviceCopy.GlobalOwners)) - copy(resultCopy.GlobalOwners, serviceCopy.GlobalOwners) - - return &resultCopy, newRevision, nil -} - -// DeleteGrpsIOService deletes a service with revision checking -func (w *MockGrpsIOServiceRepository) DeleteGrpsIOService(ctx context.Context, uid string, expectedRevision uint64, service *model.GrpsIOService) error { - slog.DebugContext(ctx, "mock service: deleting service", "service_uid", uid, "expected_revision", expectedRevision) - - w.mock.mu.Lock() - defer w.mock.mu.Unlock() - - // Check if service exists - if _, exists := w.mock.services[uid]; !exists { - return errors.NewNotFound(fmt.Sprintf("service with UID %s not found", uid)) - } - - // Check revision - currentRevision := w.mock.serviceRevisions[uid] - if currentRevision != expectedRevision { - return errors.NewConflict(fmt.Sprintf("revision mismatch: expected %d, got %d", expectedRevision, currentRevision)) - } - - // Use passed service for index key (same as original pattern) - indexKey := service.BuildIndexKey(ctx) - - // Delete service and its indices - delete(w.mock.services, uid) - delete(w.mock.serviceRevisions, uid) - delete(w.mock.serviceIndexKeys, indexKey) - - return nil -} - -// UniqueProjectType validates that only one primary service exists per project (mock implementation) -func (w *MockGrpsIOServiceRepository) UniqueProjectType(ctx context.Context, service *model.GrpsIOService) (string, error) { - slog.DebugContext(ctx, "mock constraint validation: unique project type", "project_uid", service.ProjectUID, "type", service.Type) - - // Mock implementation - always allows constraint creation - constraintKey := fmt.Sprintf("mock_constraint_%s_%s", service.ProjectUID, service.Type) - return constraintKey, nil -} - -// UniqueProjectPrefix validates that the prefix is unique within the project for formation services (mock implementation) -func (w *MockGrpsIOServiceRepository) UniqueProjectPrefix(ctx context.Context, service *model.GrpsIOService) (string, error) { - slog.DebugContext(ctx, "mock constraint validation: unique project prefix", "project_uid", service.ProjectUID, "prefix", service.Prefix) - - // Mock implementation - always allows constraint creation - constraintKey := fmt.Sprintf("mock_constraint_%s_%s_%s", service.ProjectUID, service.Type, service.Prefix) - return constraintKey, nil -} - -// UniqueProjectGroupID validates that the group_id is unique within the project for shared services (mock implementation) -func (w *MockGrpsIOServiceRepository) UniqueProjectGroupID(ctx context.Context, service *model.GrpsIOService) (string, error) { - slog.DebugContext(ctx, "mock constraint validation: unique project group_id", "project_uid", service.ProjectUID, "group_id", service.GroupID) - - // Mock implementation - always allows constraint creation - constraintKey := fmt.Sprintf("mock_constraint_%s_%s_%d", service.ProjectUID, service.Type, service.GroupID) - return constraintKey, nil -} - -// GetKeyRevision retrieves the revision for a given key (used for cleanup operations) -func (w *MockGrpsIOServiceRepository) GetKeyRevision(ctx context.Context, key string) (uint64, error) { - slog.DebugContext(ctx, "mock get key revision", "key", key) - return 1, nil -} - -// Delete removes a key with the given revision (used for cleanup and rollback) -func (w *MockGrpsIOServiceRepository) Delete(ctx context.Context, key string, revision uint64) error { - slog.DebugContext(ctx, "mock delete key", "key", key, "revision", revision) - return nil -} - -// CreateGrpsIOServiceSettings delegates to MockRepository -func (w *MockGrpsIOServiceRepository) CreateGrpsIOServiceSettings(ctx context.Context, settings *model.GrpsIOServiceSettings) (*model.GrpsIOServiceSettings, uint64, error) { - return w.mock.CreateGrpsIOServiceSettings(ctx, settings) -} - -// UpdateGrpsIOServiceSettings delegates to MockRepository -func (w *MockGrpsIOServiceRepository) UpdateGrpsIOServiceSettings(ctx context.Context, settings *model.GrpsIOServiceSettings, expectedRevision uint64) (*model.GrpsIOServiceSettings, uint64, error) { - return w.mock.UpdateGrpsIOServiceSettings(ctx, settings, expectedRevision) -} - -// GetRevision retrieves only the revision for a given UID (reader interface) -func (m *MockRepository) GetRevision(ctx context.Context, uid string) (uint64, error) { - slog.DebugContext(ctx, "mock get service revision", "service_uid", uid) - - m.mu.RLock() - defer m.mu.RUnlock() - - if rev, exists := m.serviceRevisions[uid]; exists { - return rev, nil - } - - return 0, errors.NewNotFound("service not found") -} - -// GetGrpsIOServiceSettings retrieves service settings by UID and returns ETag revision -func (m *MockRepository) GetGrpsIOServiceSettings(ctx context.Context, uid string) (*model.GrpsIOServiceSettings, uint64, error) { - slog.DebugContext(ctx, "mock service: getting service settings", "service_uid", uid) - - // Check error simulation first - if err := m.checkErrorSimulation("GetGrpsIOServiceSettings", uid); err != nil { - return nil, 0, err - } - - m.mu.RLock() - defer m.mu.RUnlock() - - settings, exists := m.settings[uid] - if !exists { - return nil, 0, errors.NewNotFound(fmt.Sprintf("service settings with UID %s not found", uid)) - } - - // Return a deep copy of the settings to avoid data races - settingsCopy := *settings - settingsCopy.Writers = make([]model.UserInfo, len(settings.Writers)) - copy(settingsCopy.Writers, settings.Writers) - settingsCopy.Auditors = make([]model.UserInfo, len(settings.Auditors)) - copy(settingsCopy.Auditors, settings.Auditors) - - rev := m.settingsRevisions[uid] - - slog.DebugContext(ctx, "mock service: service settings retrieved", - "service_uid", uid, - "revision", rev, - "writers_count", len(settingsCopy.Writers), - "auditors_count", len(settingsCopy.Auditors)) - - return &settingsCopy, rev, nil -} - -// GetSettingsRevision retrieves only the revision for service settings -func (m *MockRepository) GetSettingsRevision(ctx context.Context, uid string) (uint64, error) { - slog.DebugContext(ctx, "mock get service settings revision", "service_uid", uid) - - m.mu.RLock() - defer m.mu.RUnlock() - - if rev, exists := m.settingsRevisions[uid]; exists { - return rev, nil - } - - return 0, errors.NewNotFound("service settings not found") -} - -// CreateGrpsIOServiceSettings creates new service settings in the mock repository -func (m *MockRepository) CreateGrpsIOServiceSettings(ctx context.Context, settings *model.GrpsIOServiceSettings) (*model.GrpsIOServiceSettings, uint64, error) { - slog.DebugContext(ctx, "mock service: creating service settings", - "service_uid", settings.UID) - - // Check error simulation first - if err := m.checkErrorSimulation("CreateGrpsIOServiceSettings", settings.UID); err != nil { - return nil, 0, err - } - - m.mu.Lock() - defer m.mu.Unlock() - - // Check if settings already exist - if _, exists := m.settings[settings.UID]; exists { - return nil, 0, errors.NewConflict(fmt.Sprintf("service settings with UID %s already exists", settings.UID)) - } - - // Set timestamps - now := time.Now().UTC() - settings.CreatedAt = now - settings.UpdatedAt = now - - // Store settings with initial revision 1 - settingsCopy := &model.GrpsIOServiceSettings{ - UID: settings.UID, - Writers: append([]model.UserInfo(nil), settings.Writers...), - Auditors: append([]model.UserInfo(nil), settings.Auditors...), - CreatedAt: settings.CreatedAt, - UpdatedAt: settings.UpdatedAt, - } - m.settings[settings.UID] = settingsCopy - m.settingsRevisions[settings.UID] = 1 - - slog.DebugContext(ctx, "mock service: service settings created", - "service_uid", settings.UID, - "revision", 1) - - return settingsCopy, 1, nil -} - -func (m *MockRepository) UpdateGrpsIOServiceSettings(ctx context.Context, settings *model.GrpsIOServiceSettings, expectedRevision uint64) (*model.GrpsIOServiceSettings, uint64, error) { - slog.DebugContext(ctx, "mock service: updating service settings", - "service_uid", settings.UID, - "expected_revision", expectedRevision) - - // Check error simulation first - if err := m.checkErrorSimulation("UpdateGrpsIOServiceSettings", settings.UID); err != nil { - return nil, 0, err - } - - m.mu.Lock() - defer m.mu.Unlock() - - // Check if settings exist - _, exists := m.settings[settings.UID] - if !exists { - return nil, 0, errors.NewNotFound(fmt.Sprintf("service settings with UID %s not found", settings.UID)) - } - - // Check revision match - currentRevision := m.settingsRevisions[settings.UID] - if currentRevision != expectedRevision { - slog.WarnContext(ctx, "revision mismatch on settings update", - "service_uid", settings.UID, - "expected_revision", expectedRevision, - "current_revision", currentRevision) - return nil, 0, errors.NewConflict("revision mismatch") - } - - // Update timestamp - settings.UpdatedAt = time.Now() - - // Store updated settings and increment revision - m.settings[settings.UID] = settings - newRevision := currentRevision + 1 - m.settingsRevisions[settings.UID] = newRevision - - slog.DebugContext(ctx, "mock service: service settings updated", - "service_uid", settings.UID, - "new_revision", newRevision, - "writers_count", len(settings.Writers), - "auditors_count", len(settings.Auditors)) - - return settings, newRevision, nil -} - -// GetServicesByGroupID retrieves all services for a given GroupsIO parent group ID -// Returns empty slice if no services found (not an error) -func (m *MockRepository) GetServicesByGroupID(ctx context.Context, groupID uint64) ([]*model.GrpsIOService, error) { - slog.DebugContext(ctx, "mock service: getting services by group_id", "group_id", groupID) - - // Check error simulation first - if err := m.checkErrorSimulation("GetServicesByGroupID", fmt.Sprintf("%d", groupID)); err != nil { - return nil, err - } - - m.mu.RLock() - defer m.mu.RUnlock() - - var services []*model.GrpsIOService - - // Iterate through all services to find matches - for _, service := range m.services { - if service.GroupID != nil && uint64(*service.GroupID) == groupID { - // Return deep copy to avoid data races - serviceCopy := *service - serviceCopy.GlobalOwners = make([]string, len(service.GlobalOwners)) - copy(serviceCopy.GlobalOwners, service.GlobalOwners) - services = append(services, &serviceCopy) - } - } - - slog.DebugContext(ctx, "mock service: services retrieved by group_id", - "group_id", groupID, - "count", len(services)) - - return services, nil -} - -// GetServicesByProjectUID retrieves all services for a given project UID -// Returns empty slice if no services found (not an error) -func (m *MockRepository) GetServicesByProjectUID(ctx context.Context, projectUID string) ([]*model.GrpsIOService, error) { - slog.DebugContext(ctx, "mock service: getting services by project_uid", "project_uid", projectUID) - - // Check error simulation first - if err := m.checkErrorSimulation("GetServicesByProjectUID", projectUID); err != nil { - return nil, err - } - - m.mu.RLock() - defer m.mu.RUnlock() - - var services []*model.GrpsIOService - - // Iterate through all services to find matches - for _, service := range m.services { - if service.ProjectUID == projectUID { - // Return deep copy to avoid data races - serviceCopy := *service - serviceCopy.GlobalOwners = make([]string, len(service.GlobalOwners)) - copy(serviceCopy.GlobalOwners, service.GlobalOwners) - services = append(services, &serviceCopy) - } - } - - slog.DebugContext(ctx, "mock service: services retrieved by project_uid", - "project_uid", projectUID, - "count", len(services)) - - return services, nil -} - -// MockGrpsIOReaderWriter combines reader and writer functionality -type MockGrpsIOReaderWriter struct { - port.GrpsIOReader - port.GrpsIOWriter -} - -// IsReady checks if the service is ready (always returns nil for mocks) -func (m *MockGrpsIOReaderWriter) IsReady(ctx context.Context) error { - slog.DebugContext(ctx, "mock storage ready check: always ready") - return nil -} - -// Helper functions - -// NewMockGrpsIOServiceReader creates a mock grpsio service reader -func NewMockGrpsIOServiceReader(mock *MockRepository) port.GrpsIOServiceReader { - return mock -} - -// NewMockGrpsIOServiceWriter creates a mock grpsio service writer -func NewMockGrpsIOServiceWriter(mock *MockRepository) port.GrpsIOServiceWriter { - return &MockGrpsIOServiceRepository{mock: mock} -} - -// NewMockGrpsIOMailingListWriter creates a mock grpsio mailing list writer -func NewMockGrpsIOMailingListWriter(mock *MockRepository) port.GrpsIOMailingListWriter { - return &MockGrpsIOMailingListRepository{mock: mock} -} - -// NewMockGrpsIOMemberWriter creates a mock grpsio member writer -func NewMockGrpsIOMemberWriter(mock *MockRepository) port.GrpsIOMemberWriter { - return &MockGrpsIOMemberRepository{mock: mock} -} - -// NewMockGrpsIOServiceRepository creates a mock grpsio service repository -func NewMockGrpsIOServiceRepository(mock *MockRepository) port.GrpsIOServiceRepository { - return &MockGrpsIOServiceRepository{mock: mock} -} - -// NewMockGrpsIOMailingListRepository creates a mock grpsio mailing list repository -func NewMockGrpsIOMailingListRepository(mock *MockRepository) port.GrpsIOMailingListRepository { - return &MockGrpsIOMailingListRepository{mock: mock} -} - -// NewMockGrpsIOMemberRepository creates a mock grpsio member repository -func NewMockGrpsIOMemberRepository(mock *MockRepository) port.GrpsIOMemberRepository { - return &MockGrpsIOMemberRepository{mock: mock} -} - -// NewMockGrpsIOReader creates a mock grpsio reader -func NewMockGrpsIOReader(mock *MockRepository) port.GrpsIOReader { - return mock -} - -// NewMockGrpsIOWriter creates a mock grpsio writer -func NewMockGrpsIOWriter(mock *MockRepository) port.GrpsIOWriter { - return &MockGrpsIOWriter{ - mock: mock, - serviceRepository: &MockGrpsIOServiceRepository{mock: mock}, - mailingListRepository: &MockGrpsIOMailingListRepository{mock: mock}, - memberRepository: &MockGrpsIOMemberRepository{mock: mock}, - } -} - -// NewMockGrpsIOReaderWriter creates a mock grpsio reader writer -func NewMockGrpsIOReaderWriter(mock *MockRepository) port.GrpsIOReaderWriter { - return &MockGrpsIOReaderWriter{ - GrpsIOReader: mock, - GrpsIOWriter: &MockGrpsIOWriter{ - mock: mock, - serviceRepository: &MockGrpsIOServiceRepository{mock: mock}, - mailingListRepository: &MockGrpsIOMailingListRepository{mock: mock}, - memberRepository: &MockGrpsIOMemberRepository{mock: mock}, - }, - } -} - -// NewMockGrpsIOMemberReader creates a mock grpsio member reader -func NewMockGrpsIOMemberReader(mock *MockRepository) port.GrpsIOMemberReader { - return mock -} - -// NewMockEntityAttributeReader creates a mock entity attribute reader -func NewMockEntityAttributeReader(mock *MockRepository) port.EntityAttributeReader { - return &MockEntityAttributeReader{mock: mock} -} - -// Utility functions for mock data manipulation - -// AddService adds a service to the mock data (useful for testing) -func (m *MockRepository) AddService(service *model.GrpsIOService) { - m.mu.Lock() - defer m.mu.Unlock() - - m.services[service.UID] = service - m.serviceRevisions[service.UID] = 1 -} - -// AddProject adds both project slug and name mappings (useful for testing) -func (m *MockRepository) AddProject(uid, slug, name string) { - m.mu.Lock() - defer m.mu.Unlock() - - m.projectSlugs[uid] = slug - m.projectNames[uid] = name -} - -// SetProjectParent sets the parent project UID for a given project (useful for testing) -func (m *MockRepository) SetProjectParent(projectUID, parentProjectUID string) { - m.mu.Lock() - defer m.mu.Unlock() - - m.projectParents[projectUID] = parentProjectUID -} - -// AddCommittee adds committee name mapping (useful for testing) -func (m *MockRepository) AddCommittee(uid, name string) { - m.mu.Lock() - defer m.mu.Unlock() - - m.committeeNames[uid] = name -} - -// ClearAll clears all mock data (useful for testing) -func (m *MockRepository) ClearAll() { - m.mu.Lock() - defer m.mu.Unlock() - - m.services = make(map[string]*model.GrpsIOService) - m.serviceRevisions = make(map[string]uint64) - m.serviceIndexKeys = make(map[string]*model.GrpsIOService) - m.mailingLists = make(map[string]*model.GrpsIOMailingList) - m.mailingListRevisions = make(map[string]uint64) - m.mailingListIndexKeys = make(map[string]*model.GrpsIOMailingList) - m.members = make(map[string]*model.GrpsIOMember) - m.memberRevisions = make(map[string]uint64) - m.memberIndexKeys = make(map[string]*model.GrpsIOMember) - m.projectSlugs = make(map[string]string) - m.projectNames = make(map[string]string) - m.projectParents = make(map[string]string) - m.committeeNames = make(map[string]string) -} - -// ==================== MOCK MESSAGE PUBLISHER ==================== - -// MockGrpsIOMessagePublisher implements MessagePublisher interface for testing -type MockGrpsIOMessagePublisher struct{} - -// Indexer simulates publishing an indexer message -func (p *MockGrpsIOMessagePublisher) Indexer(ctx context.Context, subject string, message any) error { - slog.InfoContext(ctx, "mock publisher: indexer message published", - "subject", subject, - "message_type", "indexer", - ) - return nil -} - -// Access simulates publishing an access control message -func (p *MockGrpsIOMessagePublisher) Access(ctx context.Context, subject string, message any) error { - slog.InfoContext(ctx, "mock publisher: access message published", - "subject", subject, - "message_type", "access", - ) - return nil -} - -// Internal simulates publishing an internal service event -func (p *MockGrpsIOMessagePublisher) Internal(ctx context.Context, subject string, message any) error { - slog.InfoContext(ctx, "mock publisher: internal event published", - "subject", subject, - "message_type", "internal", - ) - return nil -} - -// NewMockGrpsIOMessagePublisher creates a mock message publisher -func NewMockGrpsIOMessagePublisher() port.MessagePublisher { - return &MockGrpsIOMessagePublisher{} -} - -// MockGroupsIOClient provides a mock implementation of the GroupsIO HTTP client -type MockGroupsIOClient struct { - CallLog []string // Track method calls for testing -} - -// Verify MockGroupsIOClient implements ClientInterface -var _ groupsio.ClientInterface = (*MockGroupsIOClient)(nil) - -// NewMockGroupsIOClient creates a new mock GroupsIO client -func NewMockGroupsIOClient() *MockGroupsIOClient { - return &MockGroupsIOClient{ - CallLog: make([]string, 0), - } -} - -// CreateGroup mocks the Groups.io group creation API -func (m *MockGroupsIOClient) CreateGroup(ctx context.Context, domain string, options groupsio.GroupCreateOptions) (*groupsio.GroupObject, error) { - m.CallLog = append(m.CallLog, fmt.Sprintf("CreateGroup(domain=%s, group_name=%s)", domain, options.GroupName)) - - // Return mock result - return &groupsio.GroupObject{ - ID: 12345, // Mock group ID - }, nil -} - -// UpdateGroup mocks the Groups.io group update API -func (m *MockGroupsIOClient) UpdateGroup(ctx context.Context, domain string, groupID uint64, updates groupsio.GroupUpdateOptions) error { - m.CallLog = append(m.CallLog, fmt.Sprintf("UpdateGroup(domain=%s, group_id=%d, owners=%v)", domain, groupID, updates.GlobalOwners)) - - slog.InfoContext(ctx, "[MOCK] Groups.io group update simulated", - "domain", domain, "group_id", groupID, "global_owners", updates.GlobalOwners) - - return nil -} - -// UpdateMember mocks the Groups.io member update API -func (m *MockGroupsIOClient) UpdateMember(ctx context.Context, domain string, memberID uint64, updates groupsio.MemberUpdateOptions) error { - m.CallLog = append(m.CallLog, fmt.Sprintf("UpdateMember(domain=%s, member_id=%d, mod_status=%s)", domain, memberID, updates.ModStatus)) - - slog.InfoContext(ctx, "[MOCK] Groups.io member update simulated", - "domain", domain, "member_id", memberID, "mod_status", updates.ModStatus, "delivery", updates.DeliveryMode) - - return nil -} - -// UpdateSubgroup mocks the Groups.io subgroup update API -func (m *MockGroupsIOClient) UpdateSubgroup(ctx context.Context, domain string, subgroupID uint64, updates groupsio.SubgroupUpdateOptions) error { - m.CallLog = append(m.CallLog, fmt.Sprintf("UpdateSubgroup(domain=%s, subgroup_id=%d, title=%s)", domain, subgroupID, updates.Title)) - - slog.InfoContext(ctx, "[MOCK] Groups.io subgroup update simulated", - "domain", domain, "subgroup_id", subgroupID, "title", updates.Title, "description", updates.Description) - - return nil -} - -// DeleteGroup mocks the Groups.io group deletion API -func (m *MockGroupsIOClient) DeleteGroup(ctx context.Context, domain string, groupID uint64) error { - m.CallLog = append(m.CallLog, fmt.Sprintf("DeleteGroup(domain=%s, group_id=%d)", domain, groupID)) - - slog.InfoContext(ctx, "[MOCK] Groups.io group deletion simulated", - "domain", domain, "group_id", groupID) - - return nil -} - -// CreateSubgroup mocks the Groups.io subgroup creation API -func (m *MockGroupsIOClient) CreateSubgroup(ctx context.Context, domain string, parentGroupID uint64, options groupsio.SubgroupCreateOptions) (*groupsio.SubgroupObject, error) { - m.CallLog = append(m.CallLog, fmt.Sprintf("CreateSubgroup(domain=%s, parent_id=%d, name=%s)", domain, parentGroupID, options.GroupName)) - - // Return mock result - return &groupsio.SubgroupObject{ - ID: 67890, // Mock subgroup ID - }, nil -} - -// DeleteSubgroup mocks the Groups.io subgroup deletion API -func (m *MockGroupsIOClient) DeleteSubgroup(ctx context.Context, domain string, subgroupID uint64) error { - m.CallLog = append(m.CallLog, fmt.Sprintf("DeleteSubgroup(domain=%s, subgroup_id=%d)", domain, subgroupID)) - - slog.InfoContext(ctx, "[MOCK] Groups.io subgroup deletion simulated", - "domain", domain, "subgroup_id", subgroupID) - - return nil -} - -// GetGroup mocks the Groups.io group retrieval API (works for both main groups and subgroups) -// Returns an error to trigger NATS fallback for subscriber count, since mock mode -// cannot return accurate subscriber counts without access to the storage layer. -func (m *MockGroupsIOClient) GetGroup(ctx context.Context, domain string, groupID uint64) (*groupsio.GroupObject, error) { - m.CallLog = append(m.CallLog, fmt.Sprintf("GetGroup(domain=%s, group_id=%d)", domain, groupID)) - - slog.InfoContext(ctx, "[MOCK] Groups.io GetGroup returning error to trigger NATS fallback for subscriber count", - "domain", domain, "group_id", groupID) - - // Return error to trigger NATS fallback for subscriber count - // The mock cannot return accurate subscriber counts without access to storage - return nil, fmt.Errorf("mock mode: GetGroup not supported, use NATS fallback for subscriber count") -} - -// AddMember mocks the Groups.io member addition API -func (m *MockGroupsIOClient) DirectAdd(ctx context.Context, domain string, groupID uint64, emails []string, subgroupIDs []uint64) (*groupsio.DirectAddResultsObject, error) { - emailsStr := "" - if len(emails) > 0 { - emailsStr = emails[0] // Just log first email for brevity - if len(emails) > 1 { - emailsStr += fmt.Sprintf(" (+%d more)", len(emails)-1) - } - } - - subgroupIDsStr := "" - if len(subgroupIDs) > 0 { - subgroupIDsStr = fmt.Sprintf("%v", subgroupIDs) - } - - m.CallLog = append(m.CallLog, fmt.Sprintf("DirectAdd(domain=%s, group_id=%d, subgroup_ids=%s, emails=%s)", domain, groupID, subgroupIDsStr, emailsStr)) - - // Build mock result with all requested emails - addedMembers := make([]groupsio.MemberInfoObject, len(emails)) - for i, email := range emails { - addedMembers[i] = groupsio.MemberInfoObject{ - ID: uint64(11111 + i), // Mock member IDs - GroupID: groupID, - Email: email, - FullName: "Mock User", - Status: "confirmed", - } - } - - return &groupsio.DirectAddResultsObject{ - Object: "direct_add_results", - TotalEmails: uint64(len(emails)), - AddedMembers: addedMembers, - Errors: []groupsio.AddErrorObject{}, // No errors in mock - }, nil -} - -// RemoveMember mocks the Groups.io member removal API -func (m *MockGroupsIOClient) RemoveMember(ctx context.Context, domain string, groupID uint64, memberID uint64) error { - m.CallLog = append(m.CallLog, fmt.Sprintf("RemoveMember(domain=%s, group_id=%d, member_id=%d)", domain, groupID, memberID)) - - slog.InfoContext(ctx, "[MOCK] Groups.io member removal simulated", - "domain", domain, "group_id", groupID, "member_id", memberID) - - return nil -} - -// IsReady mocks the readiness check -func (m *MockGroupsIOClient) IsReady(ctx context.Context) error { - return nil // Mock client is always ready -} - -// GetCallLog returns the log of method calls for testing -func (m *MockGroupsIOClient) GetCallLog() []string { - return m.CallLog -} - -// ClearCallLog clears the call log -func (m *MockGroupsIOClient) ClearCallLog() { - m.CallLog = make([]string, 0) -} - -// GetServiceCount returns the total number of services -func (m *MockRepository) GetServiceCount() int { - m.mu.RLock() - defer m.mu.RUnlock() - - return len(m.services) -} - -// ==================== MAILING LIST READER OPERATIONS ==================== - -// GetGrpsIOMailingList retrieves a mailing list by UID (interface implementation) -func (m *MockRepository) GetGrpsIOMailingList(ctx context.Context, uid string) (*model.GrpsIOMailingList, uint64, error) { - return m.GetGrpsIOMailingListWithRevision(ctx, uid) -} - -// GetMailingListRevision retrieves only the revision for a given UID (interface implementation) -func (m *MockRepository) GetMailingListRevision(ctx context.Context, uid string) (uint64, error) { - return m.GetGrpsIOMailingListRevision(ctx, uid) -} - -// GetGrpsIOMailingListWithRevision retrieves a mailing list by UID with revision (internal helper) -func (m *MockRepository) GetGrpsIOMailingListWithRevision(ctx context.Context, uid string) (*model.GrpsIOMailingList, uint64, error) { - slog.DebugContext(ctx, "mock mailing list: getting mailing list with revision", "mailing_list_uid", uid) - - // Check error simulation first - if err := m.checkErrorSimulation("GetGrpsIOMailingList", uid); err != nil { - return nil, 0, err - } - - m.mu.RLock() - defer m.mu.RUnlock() - - mailingList, exists := m.mailingLists[uid] - if !exists { - return nil, 0, errors.NewNotFound("mailing list not found") - } - - // Return a deep copy to avoid data races - mailingListCopy := *mailingList - mailingListCopy.Committees = make([]model.Committee, len(mailingList.Committees)) - for i, c := range mailingList.Committees { - mailingListCopy.Committees[i] = model.Committee{ - UID: c.UID, - Name: c.Name, - AllowedVotingStatuses: append([]string(nil), c.AllowedVotingStatuses...), - } - } - - revision := m.mailingListRevisions[uid] - if revision == 0 { - revision = 1 - } - - return &mailingListCopy, revision, nil -} - -// GetGrpsIOMailingListRevision retrieves only the revision for a mailing list -func (m *MockRepository) GetGrpsIOMailingListRevision(ctx context.Context, uid string) (uint64, error) { - slog.DebugContext(ctx, "mock mailing list: getting revision", "mailing_list_uid", uid) - - m.mu.RLock() - defer m.mu.RUnlock() - - if _, exists := m.mailingLists[uid]; !exists { - return 0, errors.NewNotFound("mailing list not found") - } - - revision := m.mailingListRevisions[uid] - if revision == 0 { - revision = 1 - } - - return revision, nil -} - -// GetMailingListByGroupID retrieves a mailing list by GroupsIO subgroup ID -// Returns NotFound error if mailing list doesn't exist -func (m *MockRepository) GetMailingListByGroupID(ctx context.Context, groupID uint64) (*model.GrpsIOMailingList, uint64, error) { - slog.DebugContext(ctx, "mock mailing list: getting mailing list by group_id", "group_id", groupID) - - // Check error simulation first - if err := m.checkErrorSimulation("GetMailingListByGroupID", fmt.Sprintf("%d", groupID)); err != nil { - return nil, 0, err - } - - m.mu.RLock() - defer m.mu.RUnlock() - - // Iterate through all mailing lists to find match - for uid, mailingList := range m.mailingLists { - if mailingList.GroupID != nil && uint64(*mailingList.GroupID) == groupID { - // Return deep copy to avoid data races - mailingListCopy := *mailingList - revision := m.mailingListRevisions[uid] - if revision == 0 { - revision = 1 - } - - slog.DebugContext(ctx, "found mailing list with matching group_id", - "mailing_list_uid", mailingListCopy.UID, - "group_name", mailingListCopy.GroupName, - "group_id", groupID, - "revision", revision) - - return &mailingListCopy, revision, nil - } - } - - slog.DebugContext(ctx, "mailing list not found by group_id", "group_id", groupID) - return nil, 0, errors.NewNotFound("mailing list not found") -} - -// GetMailingListsByCommittee retrieves all mailing lists for a committee -func (m *MockRepository) GetMailingListsByCommittee(ctx context.Context, committeeUID string) ([]*model.GrpsIOMailingList, error) { - slog.DebugContext(ctx, "mock mailing list: getting mailing lists by committee", "committee_uid", committeeUID) - - // Check error simulation first - if err := m.checkErrorSimulation("GetMailingListsByCommittee", committeeUID); err != nil { - return nil, err - } - - m.mu.RLock() - defer m.mu.RUnlock() - - var result []*model.GrpsIOMailingList - - // Iterate through all mailing lists to find matches - for _, mailingList := range m.mailingLists { - if slices.ContainsFunc(mailingList.Committees, func(c model.Committee) bool { - return c.UID == committeeUID - }) { - // Return deep copy to avoid data races - mailingListCopy := *mailingList - result = append(result, &mailingListCopy) - - slog.DebugContext(ctx, "found mailing list for committee", - "mailing_list_uid", mailingListCopy.UID, - "group_name", mailingListCopy.GroupName, - "committee_uid", committeeUID) - } - } - - if len(result) == 0 { - slog.DebugContext(ctx, "no mailing lists found for committee", "committee_uid", committeeUID) - return []*model.GrpsIOMailingList{}, nil - } - - slog.DebugContext(ctx, "mailing lists retrieved by committee", - "committee_uid", committeeUID, - "count", len(result)) - - return result, nil -} - -// CheckMailingListExists checks if a mailing list with the given name exists in parent service -func (m *MockRepository) CheckMailingListExists(ctx context.Context, parentID, groupName string) (bool, error) { - slog.DebugContext(ctx, "mock mailing list: checking mailing list existence", "parent_id", parentID, "group_name", groupName) - - m.mu.RLock() - defer m.mu.RUnlock() - - for _, mailingList := range m.mailingLists { - if mailingList.ServiceUID == parentID && mailingList.GroupName == groupName { - return true, nil - } - } - - return false, nil -} - -// GetGrpsIOMailingListSettings retrieves mailing list settings by UID with revision -func (m *MockRepository) GetGrpsIOMailingListSettings(ctx context.Context, uid string) (*model.GrpsIOMailingListSettings, uint64, error) { - slog.DebugContext(ctx, "mock mailing list settings: getting settings", "mailing_list_uid", uid) - - m.mu.RLock() - defer m.mu.RUnlock() - - settings, exists := m.mailingListSettings[uid] - if !exists { - return nil, 0, errors.NewNotFound("mailing list settings not found") - } - - revision := m.mailingListSettingsRevisions[uid] - if revision == 0 { - revision = 1 - } - - // Return deep copy to avoid data races - settingsCopy := *settings - return &settingsCopy, revision, nil -} - -// GetMailingListSettingsRevision retrieves only the revision for mailing list settings -func (m *MockRepository) GetMailingListSettingsRevision(ctx context.Context, uid string) (uint64, error) { - slog.DebugContext(ctx, "mock mailing list settings: getting settings revision", "mailing_list_uid", uid) - - m.mu.RLock() - defer m.mu.RUnlock() - - if _, exists := m.mailingListSettings[uid]; !exists { - return 0, errors.NewNotFound("mailing list settings not found") - } - - revision := m.mailingListSettingsRevisions[uid] - if revision == 0 { - revision = 1 - } - - return revision, nil -} - -// ==================== MAILING LIST WRITER OPERATIONS ==================== - -// CreateGrpsIOMailingList creates a new mailing list in the mock storage (interface implementation) -func (m *MockRepository) CreateGrpsIOMailingList(ctx context.Context, mailingList *model.GrpsIOMailingList) (*model.GrpsIOMailingList, uint64, error) { - slog.DebugContext(ctx, "mock mailing list: creating mailing list", "mailing_list_id", mailingList.UID) - - // Check error simulation first - if err := m.checkErrorSimulation("CreateGrpsIOMailingList", mailingList.UID); err != nil { - return nil, 0, err - } - - m.mu.Lock() - defer m.mu.Unlock() - - // Check if mailing list already exists - if _, exists := m.mailingLists[mailingList.UID]; exists { - return nil, 0, errors.NewConflict("mailing list already exists") - } - - // Set created/updated timestamps - now := time.Now() - mailingList.CreatedAt = now - mailingList.UpdatedAt = now - - // Store mailing list copy to avoid external modifications - mailingListCopy := *mailingList - mailingListCopy.Committees = make([]model.Committee, len(mailingList.Committees)) - for i, c := range mailingList.Committees { - mailingListCopy.Committees[i] = model.Committee{ - UID: c.UID, - Name: c.Name, - AllowedVotingStatuses: append([]string(nil), c.AllowedVotingStatuses...), - } - } - - m.mailingLists[mailingList.UID] = &mailingListCopy - m.mailingListRevisions[mailingList.UID] = 1 - m.mailingListIndexKeys[mailingList.BuildIndexKey(ctx)] = &mailingListCopy - - // Return mailing list copy - resultCopy := mailingListCopy - resultCopy.Committees = make([]model.Committee, len(mailingListCopy.Committees)) - for i, c := range mailingListCopy.Committees { - resultCopy.Committees[i] = model.Committee{ - UID: c.UID, - Name: c.Name, - AllowedVotingStatuses: append([]string(nil), c.AllowedVotingStatuses...), - } - } - - return &resultCopy, 1, nil -} - -// UpdateGrpsIOMailingList updates an existing mailing list (interface implementation) -func (m *MockRepository) UpdateGrpsIOMailingList(ctx context.Context, mailingList *model.GrpsIOMailingList) (*model.GrpsIOMailingList, error) { - slog.DebugContext(ctx, "mock mailing list: updating mailing list", "mailing_list_uid", mailingList.UID) - - // Check error simulation first - if err := m.checkErrorSimulation("UpdateGrpsIOMailingList", mailingList.UID); err != nil { - return nil, err - } - - m.mu.Lock() - defer m.mu.Unlock() - - // Check if mailing list exists - existingMailingList, exists := m.mailingLists[mailingList.UID] - if !exists { - return nil, errors.NewNotFound("mailing list not found") - } - - // Remove old index key - oldIndexKey := existingMailingList.BuildIndexKey(ctx) - delete(m.mailingListIndexKeys, oldIndexKey) - - // Preserve created timestamp, update updated timestamp - mailingList.CreatedAt = existingMailingList.CreatedAt - mailingList.UpdatedAt = time.Now() - - // Store mailing list copy - mailingListCopy := *mailingList - mailingListCopy.Committees = make([]model.Committee, len(mailingList.Committees)) - for i, c := range mailingList.Committees { - mailingListCopy.Committees[i] = model.Committee{ - UID: c.UID, - Name: c.Name, - AllowedVotingStatuses: append([]string(nil), c.AllowedVotingStatuses...), - } - } - - m.mailingLists[mailingList.UID] = &mailingListCopy - currentRevision := m.mailingListRevisions[mailingList.UID] - m.mailingListRevisions[mailingList.UID] = currentRevision + 1 - m.mailingListIndexKeys[mailingList.BuildIndexKey(ctx)] = &mailingListCopy - - // Return mailing list copy - resultCopy := mailingListCopy - resultCopy.Committees = make([]model.Committee, len(mailingListCopy.Committees)) - for i, c := range mailingListCopy.Committees { - resultCopy.Committees[i] = model.Committee{ - UID: c.UID, - Name: c.Name, - AllowedVotingStatuses: append([]string(nil), c.AllowedVotingStatuses...), - } - } - - return &resultCopy, nil -} - -// UpdateGrpsIOMailingListWithRevision updates an existing mailing list with revision checking (internal helper) -func (m *MockRepository) UpdateGrpsIOMailingListWithRevision(ctx context.Context, uid string, mailingList *model.GrpsIOMailingList, expectedRevision uint64) (*model.GrpsIOMailingList, uint64, error) { - slog.DebugContext(ctx, "mock mailing list: updating mailing list with revision", "mailing_list_uid", uid, "expected_revision", expectedRevision) - - m.mu.Lock() - defer m.mu.Unlock() - - // Check if mailing list exists - existingMailingList, exists := m.mailingLists[uid] - if !exists { - return nil, 0, errors.NewNotFound("mailing list not found") - } - - // Check revision - currentRevision := m.mailingListRevisions[uid] - if currentRevision != expectedRevision { - return nil, 0, errors.NewConflict(fmt.Sprintf("revision mismatch: expected %d, got %d", expectedRevision, currentRevision)) - } - - // Remove old index key - oldIndexKey := existingMailingList.BuildIndexKey(ctx) - delete(m.mailingListIndexKeys, oldIndexKey) - - // Preserve created timestamp, update updated timestamp - mailingList.CreatedAt = existingMailingList.CreatedAt - mailingList.UpdatedAt = time.Now() - mailingList.UID = uid // Ensure UID matches - - // Store mailing list copy - mailingListCopy := *mailingList - mailingListCopy.Committees = make([]model.Committee, len(mailingList.Committees)) - for i, c := range mailingList.Committees { - mailingListCopy.Committees[i] = model.Committee{ - UID: c.UID, - Name: c.Name, - AllowedVotingStatuses: append([]string(nil), c.AllowedVotingStatuses...), - } - } - - m.mailingLists[uid] = &mailingListCopy - newRevision := currentRevision + 1 - m.mailingListRevisions[uid] = newRevision - m.mailingListIndexKeys[mailingList.BuildIndexKey(ctx)] = &mailingListCopy - - // Return mailing list copy - resultCopy := mailingListCopy - resultCopy.Committees = make([]model.Committee, len(mailingListCopy.Committees)) - for i, c := range mailingListCopy.Committees { - resultCopy.Committees[i] = model.Committee{ - UID: c.UID, - Name: c.Name, - AllowedVotingStatuses: append([]string(nil), c.AllowedVotingStatuses...), - } - } - - return &resultCopy, newRevision, nil -} - -// DeleteGrpsIOMailingList deletes a mailing list (interface implementation) -func (m *MockRepository) DeleteGrpsIOMailingList(ctx context.Context, uid string) error { - slog.DebugContext(ctx, "mock mailing list: deleting mailing list", "mailing_list_uid", uid) - - // Check error simulation first - if err := m.checkErrorSimulation("DeleteGrpsIOMailingList", uid); err != nil { - return err - } - - m.mu.Lock() - defer m.mu.Unlock() - - // Check if mailing list exists - mailingList, exists := m.mailingLists[uid] - if !exists { - return errors.NewNotFound("mailing list not found") - } - - // Get the index key before deleting - indexKey := mailingList.BuildIndexKey(ctx) - - // Delete mailing list and its indices - delete(m.mailingLists, uid) - delete(m.mailingListRevisions, uid) - delete(m.mailingListIndexKeys, indexKey) - - return nil -} - -// DeleteGrpsIOMailingListWithRevision deletes a mailing list with revision checking (internal helper) -func (m *MockRepository) DeleteGrpsIOMailingListWithRevision(ctx context.Context, uid string, expectedRevision uint64) error { - slog.DebugContext(ctx, "mock mailing list: deleting mailing list with revision", "mailing_list_uid", uid, "expected_revision", expectedRevision) - - m.mu.Lock() - defer m.mu.Unlock() - - // Check if mailing list exists - mailingList, exists := m.mailingLists[uid] - if !exists { - return errors.NewNotFound("mailing list not found") - } - - // Check revision - currentRevision := m.mailingListRevisions[uid] - if currentRevision != expectedRevision { - return errors.NewConflict(fmt.Sprintf("revision mismatch: expected %d, got %d", expectedRevision, currentRevision)) - } - - // Get the index key before deleting - indexKey := mailingList.BuildIndexKey(ctx) - - // Delete mailing list and its indices - delete(m.mailingLists, uid) - delete(m.mailingListRevisions, uid) - delete(m.mailingListIndexKeys, indexKey) - - return nil -} - -// CreateSecondaryIndices creates secondary indices for a mailing list (mock implementation) -func (m *MockRepository) CreateSecondaryIndices(ctx context.Context, mailingList *model.GrpsIOMailingList) ([]string, error) { - slog.DebugContext(ctx, "mock mailing list: creating secondary indices", "mailing_list_uid", mailingList.UID) - - // Mock implementation - return mock keys that would be created - createdKeys := []string{ - fmt.Sprintf(constants.KVLookupGroupsIOMailingListServicePrefix, mailingList.ServiceUID), - fmt.Sprintf(constants.KVLookupGroupsIOMailingListProjectPrefix, mailingList.ProjectUID), - } - - for _, committee := range mailingList.Committees { - if committee.UID != "" { - createdKeys = append(createdKeys, fmt.Sprintf(constants.KVLookupGroupsIOMailingListCommitteePrefix, committee.UID)) - } - } - - return createdKeys, nil -} - -// UniqueMailingListGroupName validates that group name is unique within parent service -func (m *MockRepository) UniqueMailingListGroupName(ctx context.Context, mailingList *model.GrpsIOMailingList) (string, error) { - constraintKey := fmt.Sprintf("lookup:mailing_list:constraint:%s:%s", mailingList.ServiceUID, mailingList.GroupName) - slog.DebugContext(ctx, "mock: validating unique mailing list group name", "constraint_key", constraintKey) - - m.mu.RLock() - defer m.mu.RUnlock() - - // Check if there's already a mailing list with the same group name and parent - for _, existingList := range m.mailingLists { - if existingList.ServiceUID == mailingList.ServiceUID && existingList.GroupName == mailingList.GroupName { - // Skip if it's the same mailing list (during updates) - if mailingList.UID != "" && existingList.UID == mailingList.UID { - continue - } - return existingList.UID, errors.NewConflict(fmt.Sprintf("mailing list with group name '%s' already exists in service '%s'", mailingList.GroupName, mailingList.ServiceUID)) - } - } - - return constraintKey, nil -} - -// AddMailingList adds a mailing list to the mock data (useful for testing) -func (m *MockRepository) AddMailingList(mailingList *model.GrpsIOMailingList) { - m.mu.Lock() - defer m.mu.Unlock() - - m.mailingLists[mailingList.UID] = mailingList - m.mailingListRevisions[mailingList.UID] = 1 -} - -// GetMailingListCount returns the number of mailing lists in mock data (useful for testing) -func (m *MockRepository) GetMailingListCount() int { - m.mu.RLock() - defer m.mu.RUnlock() - return len(m.mailingLists) -} - -// CreateGrpsIOMailingListSettings creates new mailing list settings -func (m *MockRepository) CreateGrpsIOMailingListSettings(ctx context.Context, settings *model.GrpsIOMailingListSettings) (*model.GrpsIOMailingListSettings, uint64, error) { - slog.DebugContext(ctx, "mock mailing list settings: creating settings", "mailing_list_uid", settings.UID) - - // Check error simulation first - if err := m.checkErrorSimulation("CreateGrpsIOMailingListSettings", settings.UID); err != nil { - return nil, 0, err - } - - m.mu.Lock() - defer m.mu.Unlock() - - // Check if settings already exist - if _, exists := m.mailingListSettings[settings.UID]; exists { - return nil, 0, errors.NewConflict(fmt.Sprintf("mailing list settings with UID %s already exists", settings.UID)) - } - - // Set timestamps - now := time.Now().UTC() - settings.CreatedAt = now - settings.UpdatedAt = now - - // Store settings copy - settingsCopy := *settings - settingsCopy.Writers = make([]model.UserInfo, len(settings.Writers)) - copy(settingsCopy.Writers, settings.Writers) - settingsCopy.Auditors = make([]model.UserInfo, len(settings.Auditors)) - copy(settingsCopy.Auditors, settings.Auditors) - - m.mailingListSettings[settings.UID] = &settingsCopy - m.mailingListSettingsRevisions[settings.UID] = 1 - - // Return settings copy - resultCopy := settingsCopy - resultCopy.Writers = make([]model.UserInfo, len(settingsCopy.Writers)) - copy(resultCopy.Writers, settingsCopy.Writers) - resultCopy.Auditors = make([]model.UserInfo, len(settingsCopy.Auditors)) - copy(resultCopy.Auditors, settingsCopy.Auditors) - - return &resultCopy, 1, nil -} - -// UpdateGrpsIOMailingListSettings updates mailing list settings with revision checking -func (m *MockRepository) UpdateGrpsIOMailingListSettings(ctx context.Context, settings *model.GrpsIOMailingListSettings, expectedRevision uint64) (*model.GrpsIOMailingListSettings, uint64, error) { - slog.DebugContext(ctx, "mock mailing list settings: updating settings", "mailing_list_uid", settings.UID, "expected_revision", expectedRevision) - - m.mu.Lock() - defer m.mu.Unlock() - - // Check if settings exist - existingSettings, exists := m.mailingListSettings[settings.UID] - if !exists { - return nil, 0, errors.NewNotFound("mailing list settings not found") - } - - // Check revision - currentRevision := m.mailingListSettingsRevisions[settings.UID] - if currentRevision != expectedRevision { - return nil, 0, errors.NewConflict(fmt.Sprintf("revision mismatch: expected %d, got %d", expectedRevision, currentRevision)) - } - - // Preserve created timestamp, update updated timestamp - settings.CreatedAt = existingSettings.CreatedAt - settings.UpdatedAt = time.Now() - - // Store settings copy - settingsCopy := *settings - settingsCopy.Writers = make([]model.UserInfo, len(settings.Writers)) - copy(settingsCopy.Writers, settings.Writers) - settingsCopy.Auditors = make([]model.UserInfo, len(settings.Auditors)) - copy(settingsCopy.Auditors, settings.Auditors) - - m.mailingListSettings[settings.UID] = &settingsCopy - newRevision := currentRevision + 1 - m.mailingListSettingsRevisions[settings.UID] = newRevision - - // Return settings copy - resultCopy := settingsCopy - resultCopy.Writers = make([]model.UserInfo, len(settingsCopy.Writers)) - copy(resultCopy.Writers, settingsCopy.Writers) - resultCopy.Auditors = make([]model.UserInfo, len(settingsCopy.Auditors)) - copy(resultCopy.Auditors, settingsCopy.Auditors) - - return &resultCopy, newRevision, nil -} - -// ==================== MEMBER READER OPERATIONS ==================== - -// GetGrpsIOMember retrieves a member by UID (interface implementation) -func (m *MockRepository) GetGrpsIOMember(ctx context.Context, uid string) (*model.GrpsIOMember, uint64, error) { - slog.DebugContext(ctx, "mock member: getting member", "member_uid", uid) - - // Check error simulation first - if err := m.checkErrorSimulation("GetGrpsIOMember", uid); err != nil { - return nil, 0, err - } - - m.mu.RLock() - defer m.mu.RUnlock() - - member, exists := m.members[uid] - if !exists { - return nil, 0, errors.NewNotFound(fmt.Sprintf("member with UID %s not found", uid)) - } - - // Return a deep copy of the member to avoid data races - memberCopy := *member - revision := m.memberRevisions[uid] - return &memberCopy, revision, nil -} - -// GetMemberRevision retrieves only the revision for a given member UID (interface implementation) -func (m *MockRepository) GetMemberRevision(ctx context.Context, uid string) (uint64, error) { - slog.DebugContext(ctx, "mock member: getting member revision", "member_uid", uid) - - m.mu.RLock() - defer m.mu.RUnlock() - - if rev, exists := m.memberRevisions[uid]; exists { - return rev, nil - } - - return 0, errors.NewNotFound("member not found") -} - -// GetMemberByGroupsIOMemberID retrieves member by Groups.io member ID (interface implementation) -func (m *MockRepository) GetMemberByGroupsIOMemberID(ctx context.Context, memberID uint64) (*model.GrpsIOMember, uint64, error) { - slog.DebugContext(ctx, "mock member: getting member by Groups.io member ID", "member_id", memberID) - - m.mu.RLock() - defer m.mu.RUnlock() - - // Search through all members for matching Groups.io member ID - for uid, member := range m.members { - if member.MemberID != nil && uint64(*member.MemberID) == memberID { - memberCopy := *member - revision := m.memberRevisions[uid] - return &memberCopy, revision, nil - } - } - - return nil, 0, errors.NewNotFound(fmt.Sprintf("member with Groups.io member ID %d not found", memberID)) -} - -// GetMemberByEmail retrieves member by email within mailing list (interface implementation) -func (m *MockRepository) GetMemberByEmail(ctx context.Context, mailingListUID, email string) (*model.GrpsIOMember, uint64, error) { - slog.DebugContext(ctx, "mock member: getting member by email", - "mailing_list_uid", mailingListUID, - "email", email) - - m.mu.RLock() - defer m.mu.RUnlock() - - // Search through all members for matching mailing list and email - for uid, member := range m.members { - if member.MailingListUID == mailingListUID && member.Email == email { - memberCopy := *member - revision := m.memberRevisions[uid] - return &memberCopy, revision, nil - } - } - - return nil, 0, errors.NewNotFound(fmt.Sprintf("member with email %s not found in mailing list %s", email, mailingListUID)) -} - -// CountMembersInMailingList counts all members in a mailing list -// Used as fallback when Groups.io client is unavailable -func (m *MockRepository) CountMembersInMailingList(ctx context.Context, mailingListUID string) (int, error) { - m.mu.RLock() - defer m.mu.RUnlock() - - count := 0 - for _, member := range m.members { - if member.MailingListUID == mailingListUID { - count++ - } - } - - return count, nil -} - -// GetMembersForMailingList returns all members for a given mailing list (test helper) -func (m *MockRepository) GetMembersForMailingList(mailingListUID string) []*model.GrpsIOMember { - m.mu.RLock() - defer m.mu.RUnlock() - - var result []*model.GrpsIOMember - for _, member := range m.members { - if member.MailingListUID == mailingListUID { - memberCopy := *member - result = append(result, &memberCopy) - } - } - return result -} - -// AddMember adds a member to the mock repository for testing -func (m *MockRepository) AddMember(member *model.GrpsIOMember) { - m.mu.Lock() - defer m.mu.Unlock() - - // Store member copy to avoid external modifications - memberCopy := *member - - m.members[member.UID] = &memberCopy - m.memberRevisions[member.UID] = 1 - // Generate index key for the member - ctx := context.Background() - m.memberIndexKeys[member.BuildIndexKey(ctx)] = &memberCopy -} - -// GetMemberCount returns the number of members in the mock repository -func (m *MockRepository) GetMemberCount() int { - m.mu.RLock() - defer m.mu.RUnlock() - return len(m.members) -} - -// ClearMembers clears all member data from the mock repository -func (m *MockRepository) ClearMembers() { - m.mu.Lock() - defer m.mu.Unlock() - m.members = make(map[string]*model.GrpsIOMember) - m.memberRevisions = make(map[string]uint64) - m.memberIndexKeys = make(map[string]*model.GrpsIOMember) -} - -// stringPtr is a helper function that returns a pointer to a string value -func stringPtr(s string) *string { - return &s -} diff --git a/internal/infrastructure/mock/grpsio_webhook_processor.go b/internal/infrastructure/mock/grpsio_webhook_processor.go deleted file mode 100644 index cfc5f23..0000000 --- a/internal/infrastructure/mock/grpsio_webhook_processor.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package mock - -import ( - "context" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" -) - -// MockGrpsIOWebhookProcessorWithError implements GrpsIOWebhookProcessor for testing error scenarios -type MockGrpsIOWebhookProcessorWithError struct { - err error -} - -// NewMockGrpsIOWebhookProcessorWithError creates a webhook processor that always returns the given error -func NewMockGrpsIOWebhookProcessorWithError(err error) port.GrpsIOWebhookProcessor { - return &MockGrpsIOWebhookProcessorWithError{ - err: err, - } -} - -// ProcessEvent always returns the configured error -func (m *MockGrpsIOWebhookProcessorWithError) ProcessEvent(ctx context.Context, event *model.GrpsIOWebhookEvent) error { - return m.err -} diff --git a/internal/infrastructure/mock/grpsio_webhook_validator.go b/internal/infrastructure/mock/grpsio_webhook_validator.go deleted file mode 100644 index 20a2a78..0000000 --- a/internal/infrastructure/mock/grpsio_webhook_validator.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package mock - -import "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" - -// MockGrpsIOWebhookValidator implements GrpsIOWebhookValidator for testing -type MockGrpsIOWebhookValidator struct{} - -// NewMockGrpsIOWebhookValidator creates a new mock GroupsIO webhook validator -func NewMockGrpsIOWebhookValidator() port.GrpsIOWebhookValidator { - return &MockGrpsIOWebhookValidator{} -} - -// ValidateSignature always returns nil in mock mode -func (m *MockGrpsIOWebhookValidator) ValidateSignature(body []byte, signature string) error { - return nil // Always valid in mock mode -} - -// IsValidEvent always returns true in mock mode -func (m *MockGrpsIOWebhookValidator) IsValidEvent(eventType string) bool { - return true // All events valid in mock mode -} diff --git a/internal/infrastructure/mock/message_publisher.go b/internal/infrastructure/mock/message_publisher.go deleted file mode 100644 index c225d4b..0000000 --- a/internal/infrastructure/mock/message_publisher.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package mock - -import ( - "context" - "log/slog" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" -) - -// mockMessagePublisher is a mock implementation of the MessagePublisher interface -type mockMessagePublisher struct{} - -// Ensure mockMessagePublisher implements the MessagePublisher interface -var _ port.MessagePublisher = (*mockMessagePublisher)(nil) - -// NewMockMessagePublisher creates a new mock publisher for testing -func NewMockMessagePublisher() port.MessagePublisher { - return &mockMessagePublisher{} -} - -// Indexer publishes indexer messages (mock implementation - logs only) -func (m *mockMessagePublisher) Indexer(ctx context.Context, subject string, message any) error { - slog.InfoContext(ctx, "mock indexer message published", - "subject", subject, - "message_type", "indexer", - ) - return nil -} - -// Access publishes access control messages (mock implementation - logs only) -func (m *mockMessagePublisher) Access(ctx context.Context, subject string, message any) error { - slog.InfoContext(ctx, "mock access control message published", - "subject", subject, - "message_type", "access", - ) - return nil -} - -// Internal publishes internal service events (mock implementation - logs only) -func (m *mockMessagePublisher) Internal(ctx context.Context, subject string, message any) error { - slog.InfoContext(ctx, "mock internal event published", - "subject", subject, - "message_type", "internal", - ) - return nil -} diff --git a/internal/infrastructure/nats/client.go b/internal/infrastructure/nats/client.go deleted file mode 100644 index 01d997c..0000000 --- a/internal/infrastructure/nats/client.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -// Package nats provides NATS messaging client implementation and related utilities. -package nats - -import ( - "context" - "log/slog" - "time" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" - - "github.com/nats-io/nats.go" - "github.com/nats-io/nats.go/jetstream" -) - -// NATSClient wraps the NATS connection and provides access control operations -type NATSClient struct { - conn *nats.Conn - config Config - kvStore map[string]jetstream.KeyValue - timeout time.Duration -} - -// NATSClientInterface defines the interface for NATS operations -// This allows for easy mocking and testing -type NATSClientInterface interface { - Close() error - IsReady(ctx context.Context) error -} - -// Close gracefully closes the NATS connection -func (c *NATSClient) Close() error { - if c.conn != nil { - c.conn.Close() - } - return nil -} - -// IsReady checks if the NATS client is ready -func (c *NATSClient) IsReady(ctx context.Context) error { - if c.conn == nil { - slog.ErrorContext(ctx, "NATS client is not initialized or not connected") - return errors.NewServiceUnavailable("NATS client is not initialized or not connected") - } - if !c.conn.IsConnected() || c.conn.IsDraining() { - slog.ErrorContext(ctx, "NATS client is not ready", - "connected", c.conn.IsConnected(), - "draining", c.conn.IsDraining(), - ) - return errors.NewServiceUnavailable("NATS client is not ready, connection is not established or is draining") - } - slog.DebugContext(ctx, "NATS client is ready", "url", c.conn.ConnectedUrl()) - return nil -} - -// QueueSubscribe creates a queue subscription for load-balanced message processing -// Returns subscription handle and error -func (c *NATSClient) QueueSubscribe(subject, queue string, handler nats.MsgHandler) (*nats.Subscription, error) { - if c.conn == nil { - return nil, errors.NewServiceUnavailable("NATS connection not initialized") - } - if !c.conn.IsConnected() { - return nil, errors.NewServiceUnavailable("NATS connection not ready") - } - return c.conn.QueueSubscribe(subject, queue, handler) -} - -// KeyValueStore creates a JetStream client and gets the key-value store for projects. -func (c *NATSClient) KeyValueStore(ctx context.Context, bucketName string) error { - js, err := jetstream.New(c.conn) - if err != nil { - slog.ErrorContext(ctx, "error creating NATS JetStream client", - "error", err, - "nats_url", c.conn.ConnectedUrl(), - ) - return err - } - kvStore, err := js.KeyValue(ctx, bucketName) - if err != nil { - slog.ErrorContext(ctx, "error getting NATS JetStream key-value store", - "error", err, - "nats_url", c.conn.ConnectedUrl(), - "bucket", bucketName, - ) - return err - } - - if c.kvStore == nil { - c.kvStore = make(map[string]jetstream.KeyValue) - } - c.kvStore[bucketName] = kvStore - return nil -} - -// NewClient creates a new NATS client with the given configuration -func NewClient(ctx context.Context, config Config) (*NATSClient, error) { - slog.InfoContext(ctx, "creating NATS client", - "url", config.URL, - "timeout", config.Timeout, - ) - - // Validate configuration - if config.URL == "" { - return nil, errors.NewUnexpected("NATS URL is required") - } - - // Configure NATS connection options - opts := []nats.Option{ - nats.Name(constants.ServiceName), - nats.Timeout(config.Timeout), - nats.MaxReconnects(config.MaxReconnect), - nats.ReconnectWait(config.ReconnectWait), - nats.DisconnectErrHandler(func(nc *nats.Conn, err error) { - slog.WarnContext(ctx, "NATS disconnected", - "error", err, - "url", nc.ConnectedUrl(), - "status", nc.Status(), - ) - }), - nats.ReconnectHandler(func(nc *nats.Conn) { - slog.InfoContext(ctx, "NATS reconnected", "url", nc.ConnectedUrl()) - }), - nats.ErrorHandler(func(_ *nats.Conn, s *nats.Subscription, err error) { - if s != nil { - slog.With("error", err, "subject", s.Subject, "queue", s.Queue).Error("async NATS error") - } else { - slog.With("error", err).Error("async NATS error outside subscription") - } - }), - nats.ClosedHandler(func(nc *nats.Conn) { - slog.InfoContext(ctx, "NATS connection closed", - "url", nc.ConnectedUrl(), - "status", nc.Status(), - ) - }), - } - - // Establish connection - conn, err := nats.Connect(config.URL, opts...) - if err != nil { - return nil, errors.NewServiceUnavailable("failed to connect to NATS", err) - } - - client := &NATSClient{ - conn: conn, - config: config, - timeout: config.Timeout, - } - - // Initialize key-value stores for services, service settings, mailing lists, mailing list settings, and members - for _, bucketName := range []string{ - constants.KVBucketNameGroupsIOServices, - constants.KVBucketNameGroupsIOServiceSettings, - constants.KVBucketNameGroupsIOMailingLists, - constants.KVBucketNameGroupsIOMailingListSettings, - constants.KVBucketNameGroupsIOMembers, - } { - if err := client.KeyValueStore(ctx, bucketName); err != nil { - slog.ErrorContext(ctx, "failed to initialize NATS key-value store", - "error", err, - "bucket", bucketName, - ) - return nil, errors.NewServiceUnavailable("failed to initialize NATS key-value store", err) - } - } - - slog.InfoContext(ctx, "NATS client created successfully", - "connected_url", conn.ConnectedUrl(), - "status", conn.Status(), - ) - - return client, nil -} diff --git a/internal/infrastructure/nats/messaging_publish.go b/internal/infrastructure/nats/messaging_publish.go deleted file mode 100644 index 1bbeb8c..0000000 --- a/internal/infrastructure/nats/messaging_publish.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package nats - -import ( - "context" - "encoding/json" - "log/slog" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" -) - -// messagingPublisher implements the MessagePublisher interface using NATS -type messagingPublisher struct { - client *NATSClient -} - -// Indexer publishes indexer messages for search and discovery services -// These messages are consumed by indexing services to maintain search indexes -func (m *messagingPublisher) Indexer(ctx context.Context, subject string, message any) error { - return m.publish(ctx, subject, message, "indexer") -} - -// Access publishes access control messages for OpenFGA permission management -// These messages are consumed by the fga-sync service to update permission tuples -func (m *messagingPublisher) Access(ctx context.Context, subject string, message any) error { - return m.publish(ctx, subject, message, "access") -} - -// Internal publishes internal service events for inter-service communication -// These messages are consumed by internal services for event-driven workflows -func (m *messagingPublisher) Internal(ctx context.Context, subject string, message any) error { - return m.publish(ctx, subject, message, "internal") -} - -// publish is the common method for publishing messages to NATS -func (m *messagingPublisher) publish(ctx context.Context, subject string, message any, messageType string) error { - // Check if client is ready - if err := m.client.IsReady(ctx); err != nil { - slog.ErrorContext(ctx, "NATS client is not ready for publishing", - "error", err, - "subject", subject, - "message_type", messageType, - ) - return errors.NewServiceUnavailable("NATS client is not ready", err) - } - - // Marshal message to JSON - data, err := json.Marshal(message) - if err != nil { - slog.ErrorContext(ctx, "failed to marshal message to JSON", - "error", err, - "subject", subject, - "message_type", messageType, - ) - return errors.NewUnexpected("failed to marshal message", err) - } - - // Publish message - if err := m.client.conn.Publish(subject, data); err != nil { - slog.ErrorContext(ctx, "failed to publish message to NATS", - "error", err, - "subject", subject, - "message_type", messageType, - ) - return errors.NewServiceUnavailable("failed to publish message", err) - } - - slog.DebugContext(ctx, "message published successfully", - "subject", subject, - "message_type", messageType, - "message_size", len(data), - ) - - return nil -} - -// NewMessagePublisher creates a new MessagePublisher using NATS -func NewMessagePublisher(client *NATSClient) port.MessagePublisher { - return &messagingPublisher{ - client: client, - } -} diff --git a/internal/infrastructure/nats/messaging_request.go b/internal/infrastructure/nats/messaging_request.go deleted file mode 100644 index ffc47e0..0000000 --- a/internal/infrastructure/nats/messaging_request.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package nats - -import ( - "context" - "encoding/json" - "fmt" - "log/slog" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" -) - -type messageRequest struct { - client *NATSClient -} - -func (m *messageRequest) get(ctx context.Context, subject, uid string) (string, error) { - - data := []byte(uid) - msg, err := m.client.conn.RequestWithContext(ctx, subject, data) - if err != nil { - return "", err - } - - // Try to parse as JSON error response first - var errorResponse struct { - Error string `json:"error"` - } - if err := json.Unmarshal(msg.Data, &errorResponse); err == nil && errorResponse.Error != "" { - slog.WarnContext(ctx, "message responded with an error", "subject", subject, "uid", uid, "error", errorResponse.Error) - return "", errors.NewUnexpected(errorResponse.Error) - } - - attribute := string(msg.Data) - if attribute == "" { - return "", errors.NewNotFound(fmt.Sprintf("project attribute %s not found for uid: %s", subject, uid)) - } - - return attribute, nil - -} - -func (m *messageRequest) ProjectSlug(ctx context.Context, uid string) (string, error) { - return m.get(ctx, constants.ProjectGetSlugSubject, uid) -} - -func (m *messageRequest) ProjectName(ctx context.Context, uid string) (string, error) { - return m.get(ctx, constants.ProjectGetNameSubject, uid) -} - -func (m *messageRequest) ProjectParentUID(ctx context.Context, uid string) (string, error) { - return m.get(ctx, constants.ProjectGetParentUIDSubject, uid) -} - -func (m *messageRequest) CommitteeName(ctx context.Context, uid string) (string, error) { - return m.get(ctx, constants.CommitteeGetNameSubject, uid) -} - -// ListMembers retrieves all members for a given committee via NATS request/reply -func (m *messageRequest) ListMembers(ctx context.Context, committeeUID string) ([]model.CommitteeMember, error) { - slog.DebugContext(ctx, "requesting committee members via NATS", - "committee_uid", committeeUID, - "subject", constants.CommitteeListMembersSubject) - - // Send committee UID as request - data := []byte(committeeUID) - msg, err := m.client.conn.RequestWithContext(ctx, constants.CommitteeListMembersSubject, data) - if err != nil { - slog.ErrorContext(ctx, "failed to request committee members", - "error", err, - "committee_uid", committeeUID) - return nil, errors.NewServiceUnavailable(fmt.Sprintf("committee-api unavailable: %v", err)) - } - - // Response should be empty for not found - if len(msg.Data) == 0 { - slog.WarnContext(ctx, "committee not found or has no members", - "committee_uid", committeeUID) - return []model.CommitteeMember{}, nil - } - - // Unmarshal response - var members []model.CommitteeMember - if err := json.Unmarshal(msg.Data, &members); err != nil { - slog.ErrorContext(ctx, "failed to unmarshal committee members response", - "error", err, - "committee_uid", committeeUID) - return nil, fmt.Errorf("failed to unmarshal committee members: %w", err) - } - - slog.InfoContext(ctx, "successfully retrieved committee members", - "committee_uid", committeeUID, - "member_count", len(members)) - - return members, nil -} - -// NewEntityAttributeReader creates a new entity attribute reader implementation using NATS messaging. -func NewEntityAttributeReader(client *NATSClient) port.EntityAttributeReader { - return &messageRequest{ - client: client, - } -} diff --git a/internal/infrastructure/nats/models.go b/internal/infrastructure/nats/models.go deleted file mode 100644 index 564e1d1..0000000 --- a/internal/infrastructure/nats/models.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package nats - -import ( - "time" -) - -// Config represents NATS configuration -type Config struct { - // URL is the NATS server URL - URL string `json:"url"` - // Timeout is the request timeout duration - Timeout time.Duration `json:"timeout"` - // MaxReconnect is the maximum number of reconnection attempts - MaxReconnect int `json:"max_reconnect"` - // ReconnectWait is the time to wait between reconnection attempts - ReconnectWait time.Duration `json:"reconnect_wait"` -} - -// AccessCheckNATSRequest represents a NATS request for access checking -type AccessCheckNATSRequest struct { - // Subject is the NATS subject for the request - Subject string `json:"subject"` - // Message is the serialized request data - Message []byte `json:"message"` - // Timeout is the request timeout duration - Timeout time.Duration `json:"timeout"` -} - -// AccessCheckNATSResponse represents a NATS response for access checking -type AccessCheckNATSResponse map[string]string diff --git a/internal/infrastructure/nats/storage.go b/internal/infrastructure/nats/storage.go deleted file mode 100644 index d43e6d3..0000000 --- a/internal/infrastructure/nats/storage.go +++ /dev/null @@ -1,1515 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package nats - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "log/slog" - "strings" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - errs "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/redaction" - - "github.com/nats-io/nats.go/jetstream" -) - -// bucketRoutingRules define routing rules as a slice for ordered evaluation -var bucketRoutingRules = []struct { - prefix string - bucket string -}{ - {constants.GroupsIOMailingListKeyPrefix, constants.KVBucketNameGroupsIOMailingLists}, - {constants.GroupsIOMemberLookupKeyPrefix, constants.KVBucketNameGroupsIOMembers}, - {constants.GroupsIOServiceLookupKeyPrefix, constants.KVBucketNameGroupsIOServices}, -} - -type storage struct { - client *NATSClient -} - -// Client returns the underlying NATS client for subscriptions -func (s *storage) Client() *NATSClient { - return s.client -} - -// IndexSpec defines a secondary index to create -type IndexSpec struct { - Name string // Index name for logging - KeyFormat string // Key format string with %d or %s placeholder for ID - Int64ID *int64 // Pointer to int64 ID value, nil values are skipped - StringID *string // Pointer to string ID value, nil/empty values are skipped -} - -// GetGrpsIOService retrieves a single service by ID and returns ETag revision -func (s *storage) GetGrpsIOService(ctx context.Context, uid string) (*model.GrpsIOService, uint64, error) { - slog.DebugContext(ctx, "nats storage: getting service", - "service_uid", uid) - - service := &model.GrpsIOService{} - rev, err := s.get(ctx, constants.KVBucketNameGroupsIOServices, uid, service, false) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.DebugContext(ctx, "service not found", "service_uid", uid, "error", err) - return nil, 0, errs.NewNotFound("service not found") - } - slog.ErrorContext(ctx, "failed to get service", "error", err, "service_uid", uid) - return nil, 0, errs.NewServiceUnavailable("failed to get service") - } - - slog.DebugContext(ctx, "nats storage: service retrieved", - "service_uid", uid, - "type", service.Type, - "revision", rev) - - return service, rev, nil -} - -// GetRevision retrieves only the revision for a given UID (reader interface) -func (s *storage) GetRevision(ctx context.Context, uid string) (uint64, error) { - slog.DebugContext(ctx, "nats storage: getting service revision", - "service_uid", uid) - - rev, err := s.get(ctx, constants.KVBucketNameGroupsIOServices, uid, &model.GrpsIOService{}, true) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.DebugContext(ctx, "service not found for revision", "service_uid", uid, "error", err) - return 0, errs.NewNotFound("service not found") - } - slog.ErrorContext(ctx, "failed to get service revision", "error", err, "service_uid", uid) - return 0, errs.NewServiceUnavailable("failed to get service revision") - } - - slog.DebugContext(ctx, "nats storage: service revision retrieved", - "service_uid", uid, - "revision", rev) - - return rev, nil -} - -// GetGrpsIOServiceSettings retrieves service settings by UID and returns ETag revision -func (s *storage) GetGrpsIOServiceSettings(ctx context.Context, uid string) (*model.GrpsIOServiceSettings, uint64, error) { - slog.DebugContext(ctx, "nats storage: getting service settings", - "service_uid", uid) - - settings := &model.GrpsIOServiceSettings{} - rev, err := s.get(ctx, constants.KVBucketNameGroupsIOServiceSettings, uid, settings, false) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.DebugContext(ctx, "service settings not found", "service_uid", uid, "error", err) - return nil, 0, errs.NewNotFound("service settings not found") - } - slog.ErrorContext(ctx, "failed to get service settings", "error", err, "service_uid", uid) - return nil, 0, errs.NewServiceUnavailable("failed to get service settings") - } - - slog.DebugContext(ctx, "nats storage: service settings retrieved", - "service_uid", uid, - "revision", rev) - - return settings, rev, nil -} - -// GetSettingsRevision retrieves only the revision for service settings -func (s *storage) GetSettingsRevision(ctx context.Context, uid string) (uint64, error) { - slog.DebugContext(ctx, "nats storage: getting service settings revision", - "service_uid", uid) - - rev, err := s.get(ctx, constants.KVBucketNameGroupsIOServiceSettings, uid, &model.GrpsIOServiceSettings{}, true) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.DebugContext(ctx, "service settings not found for revision", "service_uid", uid, "error", err) - return 0, errs.NewNotFound("service settings not found") - } - slog.ErrorContext(ctx, "failed to get service settings revision", "error", err, "service_uid", uid) - return 0, errs.NewServiceUnavailable("failed to get service settings revision") - } - - slog.DebugContext(ctx, "nats storage: service settings revision retrieved", - "service_uid", uid, - "revision", rev) - - return rev, nil -} - -// CreateGrpsIOServiceSettings creates new service settings in NATS KV store -func (s *storage) CreateGrpsIOServiceSettings(ctx context.Context, settings *model.GrpsIOServiceSettings) (*model.GrpsIOServiceSettings, uint64, error) { - slog.DebugContext(ctx, "nats storage: creating service settings", - "service_uid", settings.UID) - - rev, err := s.put(ctx, constants.KVBucketNameGroupsIOServiceSettings, settings.UID, settings) - if err != nil { - slog.ErrorContext(ctx, "failed to create service settings", "error", err, "service_uid", settings.UID) - return nil, 0, errs.NewServiceUnavailable("failed to create service settings") - } - - slog.DebugContext(ctx, "nats storage: service settings created", - "service_uid", settings.UID, - "revision", rev) - - return settings, rev, nil -} - -func (s *storage) UpdateGrpsIOServiceSettings(ctx context.Context, settings *model.GrpsIOServiceSettings, expectedRevision uint64) (*model.GrpsIOServiceSettings, uint64, error) { - slog.DebugContext(ctx, "nats storage: updating service settings", - "service_uid", settings.UID, - "expected_revision", expectedRevision) - - rev, err := s.putWithRevision(ctx, constants.KVBucketNameGroupsIOServiceSettings, settings.UID, settings, expectedRevision) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.WarnContext(ctx, "service settings not found on update", "service_uid", settings.UID) - return nil, 0, errs.NewNotFound("service settings not found") - } - if s.isRevisionMismatch(err) { - slog.WarnContext(ctx, "revision mismatch on update", "service_uid", settings.UID, "expected_revision", expectedRevision) - return nil, 0, errs.NewConflict("revision mismatch") - } - slog.ErrorContext(ctx, "failed to update service settings", "error", err, "service_uid", settings.UID) - return nil, 0, errs.NewServiceUnavailable("failed to update service settings") - } - - slog.DebugContext(ctx, "nats storage: service settings updated", - "service_uid", settings.UID, - "revision", rev) - - return settings, rev, nil -} - -// get retrieves a model from the NATS KV store by bucket and UID. -// It unmarshals the data into the provided model and returns the revision. -// If the UID is empty, it returns a validation error. -// It can be used for any model that has the similar need for fetching data by UID. -func (s *storage) get(ctx context.Context, bucket, uid string, model any, onlyRevision bool) (uint64, error) { - if uid == "" { - return 0, errs.NewValidation("UID cannot be empty") - } - - kv, exists := s.client.kvStore[bucket] - if !exists || kv == nil { - return 0, errs.NewServiceUnavailable("KV bucket not available") - } - - data, errGet := kv.Get(ctx, uid) - if errGet != nil { - return 0, errGet - } - - if !onlyRevision { - errUnmarshal := json.Unmarshal(data.Value(), model) - if errUnmarshal != nil { - return 0, errUnmarshal - } - } - - return data.Revision(), nil -} - -// isRevisionMismatch checks if an error indicates a revision mismatch (CAS failure) -// This handles API error codes that JetStream returns for wrong last sequence -func (s *storage) isRevisionMismatch(err error) bool { - var jsErr jetstream.JetStreamError - if errors.As(err, &jsErr) && jsErr.APIError() != nil && - jsErr.APIError().ErrorCode == jetstream.JSErrCodeStreamWrongLastSequence { - return true - } - return false -} - -// GetServiceRevision retrieves only the revision number for a given UID without unmarshaling the data -// This method will be used in future for conditional requests and caching scenarios -func (s *storage) GetServiceRevision(ctx context.Context, bucket, uid string) (uint64, error) { - return s.get(ctx, bucket, uid, &model.GrpsIOService{}, true) -} - -// CreateGrpsIOService creates a new service in NATS KV store -func (s *storage) CreateGrpsIOService(ctx context.Context, service *model.GrpsIOService, settings *model.GrpsIOServiceSettings) (*model.GrpsIOService, *model.GrpsIOServiceSettings, uint64, error) { - slog.DebugContext(ctx, "nats storage: creating service", - "service_id", service.UID, - "service_type", service.Type) - - rev, err := s.put(ctx, constants.KVBucketNameGroupsIOServices, service.UID, service) - if err != nil { - slog.ErrorContext(ctx, "failed to create service", "error", err, "service_id", service.UID) - return nil, nil, 0, errs.NewServiceUnavailable("failed to create service") - } - - slog.DebugContext(ctx, "nats storage: service created", - "service_id", service.UID, - "revision", rev) - - return service, settings, rev, nil -} - -// CreateServiceSecondaryIndices creates all secondary indices for the service (public interface) -func (s *storage) CreateServiceSecondaryIndices(ctx context.Context, service *model.GrpsIOService) ([]string, error) { - return s.createServiceSecondaryIndices(ctx, service) -} - -// createServiceSecondaryIndices creates all secondary indices for the service (internal implementation) -func (s *storage) createServiceSecondaryIndices(ctx context.Context, service *model.GrpsIOService) ([]string, error) { - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOServices] - if !exists || kv == nil { - return nil, errs.NewServiceUnavailable("KV bucket not available") - } - - createdKeys, err := s.createSecondaryIndices(ctx, kv, service.UID, []IndexSpec{ - {Name: "groupid", KeyFormat: constants.KVLookupGroupsIOServiceByGroupIDPrefix, Int64ID: service.GroupID}, - {Name: "projectuid", KeyFormat: constants.KVLookupGroupsIOServiceByProjectUIDPrefix, StringID: &service.ProjectUID}, - }) - if err != nil { - return createdKeys, err - } - - slog.DebugContext(ctx, "service secondary indices created successfully", - "service_uid", service.UID, - "project_uid", service.ProjectUID, - "indices_created", createdKeys) - return createdKeys, nil -} - -// UpdateGrpsIOService updates an existing service in NATS KV store with revision checking -func (s *storage) UpdateGrpsIOService(ctx context.Context, uid string, service *model.GrpsIOService, expectedRevision uint64) (*model.GrpsIOService, uint64, error) { - slog.DebugContext(ctx, "nats storage: updating service", - "service_uid", uid, - "expected_revision", expectedRevision) - - rev, err := s.putWithRevision(ctx, constants.KVBucketNameGroupsIOServices, uid, service, expectedRevision) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.WarnContext(ctx, "service not found on update", "service_uid", uid) - return nil, 0, errs.NewNotFound("service not found") - } - if s.isRevisionMismatch(err) { - slog.WarnContext(ctx, "revision mismatch on update", "service_uid", uid, "expected_revision", expectedRevision) - return nil, 0, errs.NewConflict("revision mismatch") - } - slog.ErrorContext(ctx, "failed to update service", "error", err, "service_uid", uid) - return nil, 0, errs.NewServiceUnavailable("failed to update service") - } - - slog.DebugContext(ctx, "nats storage: service updated", - "service_uid", uid, - "revision", rev) - - return service, rev, nil -} - -// DeleteGrpsIOService deletes a service from NATS KV store with revision checking -func (s *storage) DeleteGrpsIOService(ctx context.Context, uid string, expectedRevision uint64, service *model.GrpsIOService) error { - slog.DebugContext(ctx, "nats storage: deleting service", - "service_uid", uid, - "expected_revision", expectedRevision) - - // Delete the main service record - err := s.delete(ctx, constants.KVBucketNameGroupsIOServices, uid, expectedRevision) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.WarnContext(ctx, "service not found on delete", "service_uid", uid) - return errs.NewNotFound("service not found") - } - if s.isRevisionMismatch(err) { - slog.WarnContext(ctx, "revision mismatch on delete", "service_uid", uid, "expected_revision", expectedRevision) - return errs.NewConflict("revision mismatch") - } - slog.ErrorContext(ctx, "failed to delete service", "error", err, "service_uid", uid) - return errs.NewServiceUnavailable("failed to delete service") - } - - // Clean up unique constraint. - // Verification is necessary here to prevent deleting a constraint that might have been reused - // by another service with the same parameters. Only delete if the constraint still points to this service's UID. - constraintKey := fmt.Sprintf(constants.KVLookupGroupsIOServicePrefix, service.BuildIndexKey(ctx)) - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOServices] - if exists && kv != nil { - entry, err := kv.Get(ctx, constraintKey) - if err == nil && string(entry.Value()) == service.UID { - // Only delete if it still points to our UID - if delErr := kv.Delete(ctx, constraintKey, jetstream.LastRevision(entry.Revision())); delErr != nil { - slog.DebugContext(ctx, "failed to delete constraint key during cleanup", "error", delErr, "key", constraintKey) - } else { - slog.DebugContext(ctx, "service constraint cleaned up successfully", "constraint_key", constraintKey) - } - } - // Silently skip if not found or points to different UID (best effort cleanup) - } - - slog.DebugContext(ctx, "nats storage: service deleted", - "service_uid", uid) - - return nil -} - -// GetServicesByGroupID retrieves all services for a given GroupsIO parent group ID -// A single parent group can have multiple services (1 primary + N formation/shared) -// Returns empty slice if no services found (not an error) -// Performance: O(1) indexed lookup using secondary index -func (s *storage) GetServicesByGroupID(ctx context.Context, groupID uint64) ([]*model.GrpsIOService, error) { - slog.DebugContext(ctx, "nats storage: getting services by group_id (indexed)", - "group_id", groupID) - - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOServices] - if !exists || kv == nil { - return nil, errs.NewServiceUnavailable("KV bucket not available") - } - - indexPrefix := fmt.Sprintf(constants.KVLookupGroupsIOServiceByGroupIDPrefix, groupID) - - // Extract UIDs from secondary index using helper - uids, err := s.getUIDsFromSecondaryIndex(ctx, kv, indexPrefix, 0) // 0 = get all - if err != nil { - return nil, errs.NewServiceUnavailable("failed to list services") - } - - if len(uids) == 0 { - slog.DebugContext(ctx, "no services found for group_id", "group_id", groupID) - return []*model.GrpsIOService{}, nil - } - - // Fetch services sequentially (unchanged behavior) - var services []*model.GrpsIOService - for _, uid := range uids { - service, _, err := s.GetGrpsIOService(ctx, uid) - if err != nil { - slog.DebugContext(ctx, "failed to get service, skipping", "service_uid", uid, "error", err) - continue - } - - services = append(services, service) - slog.DebugContext(ctx, "found service with matching group_id", - "service_uid", service.UID, - "service_type", service.Type, - "group_id", groupID) - } - - slog.DebugContext(ctx, "nats storage: services retrieved by group_id (indexed)", - "group_id", groupID, - "count", len(services)) - - return services, nil -} - -// GetServicesByGroupID retrieves all services for a given GroupsIO parent group ID -// A single parent group can have multiple services (1 primary + N formation/shared) -// Returns empty slice if no services found (not an error) -// Performance: O(1) indexed lookup using secondary index -func (s *storage) GetServicesByProjectUID(ctx context.Context, projectUID string) ([]*model.GrpsIOService, error) { - slog.DebugContext(ctx, "nats storage: getting services by project_uid (indexed)", - "project_uid", projectUID) - - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOServices] - if !exists || kv == nil { - return nil, errs.NewServiceUnavailable("KV bucket not available") - } - - indexPrefix := fmt.Sprintf(constants.KVLookupGroupsIOServiceByProjectUIDPrefix, projectUID) - - // Extract UIDs from secondary index using helper - uids, err := s.getUIDsFromSecondaryIndex(ctx, kv, indexPrefix, 0) // 0 = get all - if err != nil { - return nil, errs.NewServiceUnavailable("failed to list services") - } - - if len(uids) == 0 { - slog.DebugContext(ctx, "no services found for project_uid", "project_uid", projectUID) - return []*model.GrpsIOService{}, nil - } - - // Fetch services sequentially (unchanged behavior) - var services []*model.GrpsIOService - for _, uid := range uids { - service, _, err := s.GetGrpsIOService(ctx, uid) - if err != nil { - slog.DebugContext(ctx, "failed to get service, skipping", "service_uid", uid, "error", err) - continue - } - - services = append(services, service) - slog.DebugContext(ctx, "found service with matching project_uid", - "service_uid", service.UID, - "service_type", service.Type, - "project_uid", projectUID) - } - - slog.DebugContext(ctx, "nats storage: services retrieved by project_uid (indexed)", - "project_uid", projectUID, - "count", len(services)) - - return services, nil -} - -// getUIDsFromSecondaryIndex extracts entity UIDs from a secondary index -// Returns empty slice if no matches found (not an error) -// -// Parameters: -// - ctx: Context for logging and cancellation -// - kv: NATS KV bucket containing the index -// - indexPrefix: Index key prefix (e.g., "idx:service:groupid:123456") -// - maxResults: 0 = return all UIDs, 1 = early exit after first match, N = stop after N matches -// -// Returns: -// - []string: UIDs extracted from matching index entries -// - error: Only returns error on kv.Keys() failure, logs and skips individual entry failures -// -// Performance: O(K) where K = total keys in bucket (NATS API limitation) -func (s *storage) getUIDsFromSecondaryIndex( - ctx context.Context, - kv jetstream.KeyValue, - indexPrefix string, - maxResults int, -) ([]string, error) { - keys, err := kv.Keys(ctx, jetstream.IgnoreDeletes()) - if err != nil { - slog.ErrorContext(ctx, "failed to list index keys", "error", err, "prefix", indexPrefix) - return nil, err - } - - var uids []string - for _, key := range keys { - // Filter by prefix - if !strings.HasPrefix(key, indexPrefix+"/") { - continue - } - - // Get index entry to extract UID - entry, err := kv.Get(ctx, key) - if err != nil { - slog.DebugContext(ctx, "failed to get index entry, skipping", "key", key, "error", err) - continue - } - - uid := string(entry.Value()) - uids = append(uids, uid) - - slog.DebugContext(ctx, "extracted UID from index", "uid", uid, "index_key", key) - - // Early exit for single-item queries - if maxResults > 0 && len(uids) >= maxResults { - break - } - } - - return uids, nil -} - -// put stores a model in the NATS KV store by bucket and UID. -// It marshals the model into JSON and stores it, returning the revision. -func (s *storage) put(ctx context.Context, bucket, uid string, model any) (uint64, error) { - if uid == "" { - return 0, errs.NewValidation("UID cannot be empty") - } - - kv, exists := s.client.kvStore[bucket] - if !exists || kv == nil { - return 0, errs.NewServiceUnavailable("KV bucket not available") - } - - data, err := json.Marshal(model) - if err != nil { - return 0, err - } - - revision, err := kv.Put(ctx, uid, data) - if err != nil { - return 0, err - } - - return revision, nil -} - -// putWithRevision stores a model in the NATS KV store with expected revision checking. -// It performs conditional update based on the expected revision. -func (s *storage) putWithRevision(ctx context.Context, bucket, uid string, model any, expectedRevision uint64) (uint64, error) { - if uid == "" { - return 0, errs.NewValidation("UID cannot be empty") - } - - kv, exists := s.client.kvStore[bucket] - if !exists || kv == nil { - return 0, errs.NewServiceUnavailable("KV bucket not available") - } - - data, err := json.Marshal(model) - if err != nil { - return 0, err - } - - revision, err := kv.Update(ctx, uid, data, expectedRevision) - if err != nil { - return 0, err - } - - return revision, nil -} - -// delete removes a model from the NATS KV store by bucket and UID with revision checking. -func (s *storage) delete(ctx context.Context, bucket, uid string, expectedRevision uint64) error { - if uid == "" { - return errs.NewValidation("UID cannot be empty") - } - - kv, exists := s.client.kvStore[bucket] - if !exists || kv == nil { - return errs.NewServiceUnavailable("KV bucket not available") - } - - err := kv.Delete(ctx, uid, jetstream.LastRevision(expectedRevision)) - if err != nil { - return err - } - - return nil -} - -// UniqueProjectType validates that only one primary service exists per project -func (s *storage) UniqueProjectType(ctx context.Context, service *model.GrpsIOService) (string, error) { - uniqueKey := fmt.Sprintf(constants.KVLookupGroupsIOServicePrefix, service.BuildIndexKey(ctx)) - - slog.DebugContext(ctx, "validating unique project type constraint", - "project_uid", service.ProjectUID, - "service_type", service.Type, - "constraint_key", uniqueKey, - ) - - return s.createUniqueConstraint(ctx, uniqueKey, service.UID) -} - -// UniqueProjectPrefix validates that the prefix is unique within the project for formation services -func (s *storage) UniqueProjectPrefix(ctx context.Context, service *model.GrpsIOService) (string, error) { - uniqueKey := fmt.Sprintf(constants.KVLookupGroupsIOServicePrefix, service.BuildIndexKey(ctx)) - - slog.DebugContext(ctx, "validating unique project prefix constraint", - "project_uid", service.ProjectUID, - "service_prefix", service.Prefix, - "constraint_key", uniqueKey, - ) - - return s.createUniqueConstraint(ctx, uniqueKey, service.UID) -} - -// UniqueProjectGroupID validates that the group_id is unique within the project for shared services -func (s *storage) UniqueProjectGroupID(ctx context.Context, service *model.GrpsIOService) (string, error) { - uniqueKey := fmt.Sprintf(constants.KVLookupGroupsIOServicePrefix, service.BuildIndexKey(ctx)) - - slog.DebugContext(ctx, "validating unique project group_id constraint", - "project_uid", service.ProjectUID, - "service_group_id", service.GroupID, - "constraint_key", uniqueKey, - ) - - return s.createUniqueConstraint(ctx, uniqueKey, service.UID) -} - -// UniqueMailingListGroupName validates that group name is unique within parent service -func (s *storage) UniqueMailingListGroupName(ctx context.Context, mailingList *model.GrpsIOMailingList) (string, error) { - constraintKey := fmt.Sprintf(constants.KVLookupGroupsIOMailingListConstraintPrefix, mailingList.BuildIndexKey(ctx)) - - slog.DebugContext(ctx, "validating unique mailing list group name constraint", - "parent_uid", mailingList.ServiceUID, - "group_name", mailingList.GroupName, - "constraint_key", constraintKey) - - return s.createUniqueConstraintInBucket(ctx, constants.KVBucketNameGroupsIOMailingLists, constraintKey, mailingList.UID) -} - -// createUniqueConstraint creates a unique constraint key in NATS KV (services bucket) -func (s *storage) createUniqueConstraint(ctx context.Context, uniqueKey, serviceID string) (string, error) { - return s.createUniqueConstraintInBucket(ctx, constants.KVBucketNameGroupsIOServices, uniqueKey, serviceID) -} - -// createUniqueConstraintInBucket creates a unique constraint key in a specific NATS KV bucket -func (s *storage) createUniqueConstraintInBucket(ctx context.Context, bucket, uniqueKey, entityID string) (string, error) { - kv, exists := s.client.kvStore[bucket] - if !exists || kv == nil { - return uniqueKey, errs.NewServiceUnavailable("KV bucket not available") - } - - // Try to create the constraint key - this will fail if it already exists - _, err := kv.Create(ctx, uniqueKey, []byte(entityID)) - if err != nil { - if errors.Is(err, jetstream.ErrKeyExists) { - slog.WarnContext(ctx, "constraint violation - key already exists", - "constraint_key", uniqueKey, - "entity_id", entityID, - "bucket", bucket, - ) - return uniqueKey, errs.NewConflict("entity with same constraints already exists") - } - slog.ErrorContext(ctx, "failed to create unique constraint", - "error", err, - "constraint_key", uniqueKey, - "entity_id", entityID, - "bucket", bucket, - ) - return uniqueKey, errs.NewUnexpected("failed to create unique constraint", err) - } - - slog.DebugContext(ctx, "unique constraint created successfully", - "constraint_key", uniqueKey, - "entity_id", entityID, - "bucket", bucket, - ) - - return uniqueKey, nil -} - -// detectBucketForKey determines which bucket to use based on key prefix patterns -func (s *storage) detectBucketForKey(key string) string { - for _, rule := range bucketRoutingRules { - if strings.HasPrefix(key, rule.prefix) { - return rule.bucket - } - } - // Default to services bucket for entity UIDs and backward compatibility - return constants.KVBucketNameGroupsIOServices -} - -// GetKeyRevision retrieves the revision for a given key (used for cleanup operations) -func (s *storage) GetKeyRevision(ctx context.Context, key string) (uint64, error) { - bucket := s.detectBucketForKey(key) - return s.getKeyRevisionFromBucket(ctx, bucket, key) -} - -// getKeyRevisionFromBucket retrieves the revision for a given key from a specific bucket -func (s *storage) getKeyRevisionFromBucket(ctx context.Context, bucket, key string) (uint64, error) { - if key == "" { - return 0, errs.NewValidation("key cannot be empty") - } - - kv, exists := s.client.kvStore[bucket] - if !exists || kv == nil { - return 0, errs.NewServiceUnavailable("KV bucket not available") - } - - entry, err := kv.Get(ctx, key) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - return 0, errs.NewNotFound("key not found") - } - return 0, errs.NewServiceUnavailable("failed to get key revision", err) - } - - return entry.Revision(), nil -} - -// Delete removes a key with the given revision (used for cleanup and rollback) -func (s *storage) Delete(ctx context.Context, key string, revision uint64) error { - bucket := s.detectBucketForKey(key) - return s.deleteFromBucket(ctx, bucket, key, revision) -} - -// deleteFromBucket removes a key with the given revision from a specific bucket -func (s *storage) deleteFromBucket(ctx context.Context, bucket, key string, revision uint64) error { - if key == "" { - return errs.NewValidation("key cannot be empty") - } - - kv, exists := s.client.kvStore[bucket] - if !exists || kv == nil { - return errs.NewServiceUnavailable("KV bucket not available") - } - - err := kv.Delete(ctx, key, jetstream.LastRevision(revision)) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - // Key not found, consider it a success for idempotency - slog.WarnContext(ctx, "key not found during deletion", "key", key, "revision", revision, "bucket", bucket) - return nil - } - slog.ErrorContext(ctx, "failed to delete key", "error", err, "key", key, "revision", revision, "bucket", bucket) - return errs.NewServiceUnavailable("failed to delete key", err) - } - - slog.DebugContext(ctx, "key deleted successfully", "key", key, "revision", revision, "bucket", bucket) - return nil -} - -// IsReady checks if the storage is ready by verifying the client connection -func (s *storage) IsReady(ctx context.Context) error { - return s.client.IsReady(ctx) -} - -// GetGrpsIOMailingList retrieves a single mailing list by UID -func (s *storage) GetGrpsIOMailingList(ctx context.Context, uid string) (*model.GrpsIOMailingList, uint64, error) { - slog.DebugContext(ctx, "nats storage: getting mailing list", - "mailing_list_uid", uid) - - mailingList := &model.GrpsIOMailingList{} - rev, err := s.get(ctx, constants.KVBucketNameGroupsIOMailingLists, uid, mailingList, false) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.DebugContext(ctx, "mailing list not found", "mailing_list_uid", uid, "error", err) - return nil, 0, errs.NewNotFound("mailing list not found") - } - slog.ErrorContext(ctx, "failed to get mailing list", "error", err, "mailing_list_uid", uid) - return nil, 0, errs.NewServiceUnavailable("failed to get mailing list") - } - - slog.DebugContext(ctx, "nats storage: mailing list retrieved", - "mailing_list_uid", uid, - "group_name", mailingList.GroupName, - "revision", rev) - - return mailingList, rev, nil -} - -// GetMailingListRevision retrieves only the revision for a given UID -func (s *storage) GetMailingListRevision(ctx context.Context, uid string) (uint64, error) { - slog.DebugContext(ctx, "nats storage: getting mailing list revision", "mailing_list_uid", uid) - - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOMailingLists] - if !exists || kv == nil { - return 0, errs.NewServiceUnavailable("KV bucket not available") - } - - entry, err := kv.Get(ctx, uid) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.DebugContext(ctx, "mailing list not found", "mailing_list_uid", uid, "error", err) - return 0, errs.NewNotFound("mailing list not found") - } - slog.ErrorContext(ctx, "failed to get mailing list revision", "error", err, "mailing_list_uid", uid) - return 0, errs.NewServiceUnavailable("failed to get mailing list revision") - } - - slog.DebugContext(ctx, "nats storage: mailing list revision retrieved", "mailing_list_uid", uid, "revision", entry.Revision()) - return entry.Revision(), nil -} - -// CreateGrpsIOMailingList creates a new mailing list in NATS KV store (following service pattern) -func (s *storage) CreateGrpsIOMailingList(ctx context.Context, mailingList *model.GrpsIOMailingList) (*model.GrpsIOMailingList, uint64, error) { - slog.DebugContext(ctx, "nats storage: creating mailing list", - "mailing_list_id", mailingList.UID, - "group_name", mailingList.GroupName) - - rev, err := s.put(ctx, constants.KVBucketNameGroupsIOMailingLists, mailingList.UID, mailingList) - if err != nil { - slog.ErrorContext(ctx, "failed to create mailing list", "error", err, "mailing_list_id", mailingList.UID) - return nil, 0, errs.NewServiceUnavailable("failed to create mailing list") - } - - slog.DebugContext(ctx, "nats storage: mailing list created", - "mailing_list_id", mailingList.UID, - "revision", rev) - - return mailingList, rev, nil -} - -// createMailingListSecondaryIndices creates all secondary indices for the mailing list -func (s *storage) createMailingListSecondaryIndices(ctx context.Context, mailingList *model.GrpsIOMailingList) ([]string, error) { - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOMailingLists] - if !exists || kv == nil { - return nil, errs.NewServiceUnavailable("KV bucket not available") - } - - // Build base index specs - specs := []IndexSpec{ - {Name: "service", KeyFormat: constants.KVLookupGroupsIOMailingListServicePrefix, StringID: &mailingList.ServiceUID}, - {Name: "project", KeyFormat: constants.KVLookupGroupsIOMailingListProjectPrefix, StringID: &mailingList.ProjectUID}, - {Name: "groupid", KeyFormat: constants.KVLookupGroupsIOMailingListBySubgroupIDPrefix, Int64ID: mailingList.GroupID}, - } - - // Add committee indices for each committee in the array - for _, committee := range mailingList.Committees { - if committee.UID != "" { - committeeUID := committee.UID // Create local copy for pointer - specs = append(specs, IndexSpec{ - Name: "committee", - KeyFormat: constants.KVLookupGroupsIOMailingListCommitteePrefix, - StringID: &committeeUID, - }) - } - } - - createdKeys, err := s.createSecondaryIndices(ctx, kv, mailingList.UID, specs) - if err != nil { - return createdKeys, err - } - - slog.DebugContext(ctx, "secondary indices created successfully", - "mailing_list_uid", mailingList.UID, - "indices_created", createdKeys) - return createdKeys, nil -} - -// createOrSkipIndex creates an index or skips if it already exists -func (s *storage) createOrSkipIndex(ctx context.Context, kv jetstream.KeyValue, key, value, indexName string) (bool, error) { - _, err := kv.Create(ctx, key, []byte(value)) - if err != nil { - if errors.Is(err, jetstream.ErrKeyExists) { - slog.DebugContext(ctx, "index already exists, skipping", - "index", indexName, - "key", key) - return false, nil - } - slog.ErrorContext(ctx, "failed to create index", - "index", indexName, - "error", err, - "key", key) - return false, errs.NewServiceUnavailable(fmt.Sprintf("failed to create %s index", indexName)) - } - return true, nil -} - -// createSecondaryIndices creates multiple secondary indices with consistent error handling -func (s *storage) createSecondaryIndices( - ctx context.Context, - kv jetstream.KeyValue, - entityUID string, - specs []IndexSpec, -) ([]string, error) { - var createdKeys []string - - for _, spec := range specs { - var key string - - // Determine which ID type to use - if spec.Int64ID != nil { - key = fmt.Sprintf(spec.KeyFormat, *spec.Int64ID) + "/" + entityUID - } else if spec.StringID != nil && *spec.StringID != "" { - key = fmt.Sprintf(spec.KeyFormat, *spec.StringID) + "/" + entityUID - } else { - continue // Skip nil or empty indices - } - - created, err := s.createOrSkipIndex(ctx, kv, key, entityUID, spec.Name) - if err != nil { - return createdKeys, err - } - if created { - createdKeys = append(createdKeys, key) - } - } - - return createdKeys, nil -} - -// UpdateGrpsIOMailingList updates an existing mailing list with optimistic concurrency control -func (s *storage) UpdateGrpsIOMailingList(ctx context.Context, uid string, mailingList *model.GrpsIOMailingList, expectedRevision uint64) (*model.GrpsIOMailingList, uint64, error) { - slog.DebugContext(ctx, "nats storage: updating mailing list", - "mailing_list_uid", uid, - "expected_revision", expectedRevision) - - rev, err := s.putWithRevision(ctx, constants.KVBucketNameGroupsIOMailingLists, uid, mailingList, expectedRevision) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.WarnContext(ctx, "mailing list not found on update", "mailing_list_uid", uid) - return nil, 0, errs.NewNotFound("mailing list not found") - } - if s.isRevisionMismatch(err) { - slog.WarnContext(ctx, "revision mismatch on update", "mailing_list_uid", uid, "expected_revision", expectedRevision) - return nil, 0, errs.NewConflict("revision mismatch") - } - slog.ErrorContext(ctx, "failed to update mailing list", "error", err, "mailing_list_uid", uid) - return nil, 0, errs.NewServiceUnavailable("failed to update mailing list") - } - - slog.DebugContext(ctx, "nats storage: mailing list updated", - "mailing_list_uid", uid, - "revision", rev) - - return mailingList, rev, nil -} - -// GetGrpsIOMailingListSettings retrieves mailing list settings by UID and returns ETag revision -func (s *storage) GetGrpsIOMailingListSettings(ctx context.Context, uid string) (*model.GrpsIOMailingListSettings, uint64, error) { - slog.DebugContext(ctx, "nats storage: getting mailing list settings", - "mailing_list_uid", uid) - - settings := &model.GrpsIOMailingListSettings{} - rev, err := s.get(ctx, constants.KVBucketNameGroupsIOMailingListSettings, uid, settings, false) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.DebugContext(ctx, "mailing list settings not found", "mailing_list_uid", uid, "error", err) - return nil, 0, errs.NewNotFound("mailing list settings not found") - } - slog.ErrorContext(ctx, "failed to get mailing list settings", "error", err, "mailing_list_uid", uid) - return nil, 0, errs.NewServiceUnavailable("failed to get mailing list settings") - } - - slog.DebugContext(ctx, "nats storage: mailing list settings retrieved", - "mailing_list_uid", uid, - "revision", rev) - - return settings, rev, nil -} - -// GetMailingListSettingsRevision retrieves only the revision for mailing list settings -func (s *storage) GetMailingListSettingsRevision(ctx context.Context, uid string) (uint64, error) { - slog.DebugContext(ctx, "nats storage: getting mailing list settings revision", - "mailing_list_uid", uid) - - rev, err := s.get(ctx, constants.KVBucketNameGroupsIOMailingListSettings, uid, &model.GrpsIOMailingListSettings{}, true) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.DebugContext(ctx, "mailing list settings not found for revision", "mailing_list_uid", uid, "error", err) - return 0, errs.NewNotFound("mailing list settings not found") - } - slog.ErrorContext(ctx, "failed to get mailing list settings revision", "error", err, "mailing_list_uid", uid) - return 0, errs.NewServiceUnavailable("failed to get mailing list settings revision") - } - - slog.DebugContext(ctx, "nats storage: mailing list settings revision retrieved", - "mailing_list_uid", uid, - "revision", rev) - - return rev, nil -} - -// CreateGrpsIOMailingListSettings creates new mailing list settings in NATS KV store -func (s *storage) CreateGrpsIOMailingListSettings(ctx context.Context, settings *model.GrpsIOMailingListSettings) (*model.GrpsIOMailingListSettings, uint64, error) { - slog.DebugContext(ctx, "nats storage: creating mailing list settings", - "mailing_list_uid", settings.UID) - - rev, err := s.put(ctx, constants.KVBucketNameGroupsIOMailingListSettings, settings.UID, settings) - if err != nil { - slog.ErrorContext(ctx, "failed to create mailing list settings", "error", err, "mailing_list_uid", settings.UID) - return nil, 0, errs.NewServiceUnavailable("failed to create mailing list settings") - } - - slog.DebugContext(ctx, "nats storage: mailing list settings created", - "mailing_list_uid", settings.UID, - "revision", rev) - - return settings, rev, nil -} - -// UpdateGrpsIOMailingListSettings updates existing mailing list settings with revision checking -func (s *storage) UpdateGrpsIOMailingListSettings(ctx context.Context, settings *model.GrpsIOMailingListSettings, expectedRevision uint64) (*model.GrpsIOMailingListSettings, uint64, error) { - slog.DebugContext(ctx, "nats storage: updating mailing list settings", - "mailing_list_uid", settings.UID, - "expected_revision", expectedRevision) - - rev, err := s.putWithRevision(ctx, constants.KVBucketNameGroupsIOMailingListSettings, settings.UID, settings, expectedRevision) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.WarnContext(ctx, "mailing list settings not found on update", "mailing_list_uid", settings.UID) - return nil, 0, errs.NewNotFound("mailing list settings not found") - } - if s.isRevisionMismatch(err) { - slog.WarnContext(ctx, "revision mismatch on update", "mailing_list_uid", settings.UID, "expected_revision", expectedRevision) - return nil, 0, errs.NewConflict("revision mismatch") - } - slog.ErrorContext(ctx, "failed to update mailing list settings", "error", err, "mailing_list_uid", settings.UID) - return nil, 0, errs.NewServiceUnavailable("failed to update mailing list settings") - } - - slog.DebugContext(ctx, "nats storage: mailing list settings updated", - "mailing_list_uid", settings.UID, - "revision", rev) - - return settings, rev, nil -} - -// DeleteGrpsIOMailingList deletes a mailing list with optimistic concurrency control -func (s *storage) DeleteGrpsIOMailingList(ctx context.Context, uid string, expectedRevision uint64, mailingList *model.GrpsIOMailingList) error { - slog.DebugContext(ctx, "nats storage: deleting mailing list", - "mailing_list_uid", uid, - "expected_revision", expectedRevision) - - // Use the passed mailing list data - no need to fetch again - - // Delete the main record with optimistic concurrency control using helper - err := s.delete(ctx, constants.KVBucketNameGroupsIOMailingLists, uid, expectedRevision) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.WarnContext(ctx, "mailing list not found on delete", "mailing_list_uid", uid) - return errs.NewNotFound("mailing list not found") - } - if s.isRevisionMismatch(err) { - slog.WarnContext(ctx, "revision mismatch on delete", "mailing_list_uid", uid, "expected_revision", expectedRevision) - return errs.NewConflict("revision mismatch") - } - slog.ErrorContext(ctx, "failed to delete mailing list", "error", err, "mailing_list_uid", uid) - return errs.NewServiceUnavailable("failed to delete mailing list") - } - - // Clean up secondary indices (best effort - don't fail if they don't exist) - s.deleteMailingListSecondaryIndices(ctx, mailingList) - - // Clean up unique constraint (verify it belongs to this list) - constraintKey := fmt.Sprintf(constants.KVLookupGroupsIOMailingListConstraintPrefix, mailingList.BuildIndexKey(ctx)) - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOMailingLists] - if exists && kv != nil { - entry, err := kv.Get(ctx, constraintKey) - if err == nil && string(entry.Value()) == mailingList.UID { - // Only delete if it still points to our UID - if delErr := kv.Delete(ctx, constraintKey, jetstream.LastRevision(entry.Revision())); delErr != nil { - slog.DebugContext(ctx, "failed to delete constraint key during cleanup", "error", delErr, "key", constraintKey) - } - } - // Silently skip if not found or points to different UID (best effort cleanup) - } - - slog.DebugContext(ctx, "nats storage: mailing list deleted", "mailing_list_uid", uid) - return nil -} - -// deleteMailingListSecondaryIndices removes all secondary indices for a mailing list (best effort) -func (s *storage) deleteMailingListSecondaryIndices(ctx context.Context, mailingList *model.GrpsIOMailingList) { - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOMailingLists] - if !exists || kv == nil { - return - } - - // Service index - serviceKey := fmt.Sprintf(constants.KVLookupGroupsIOMailingListServicePrefix, mailingList.ServiceUID) + "/" + mailingList.UID - err := kv.Delete(ctx, serviceKey) - if err != nil && !errors.Is(err, jetstream.ErrKeyNotFound) { - slog.WarnContext(ctx, "failed to delete service index", "error", err, "key", serviceKey) - } - - // Project index - projectKey := fmt.Sprintf(constants.KVLookupGroupsIOMailingListProjectPrefix, mailingList.ProjectUID) + "/" + mailingList.UID - err = kv.Delete(ctx, projectKey) - if err != nil && !errors.Is(err, jetstream.ErrKeyNotFound) { - slog.WarnContext(ctx, "failed to delete project index", "error", err, "key", projectKey) - } - - // Committee indices (for each committee in the array) - for _, committee := range mailingList.Committees { - if committee.UID != "" { - committeeKey := fmt.Sprintf(constants.KVLookupGroupsIOMailingListCommitteePrefix, committee.UID) + "/" + mailingList.UID - err = kv.Delete(ctx, committeeKey) - if err != nil && !errors.Is(err, jetstream.ErrKeyNotFound) { - slog.WarnContext(ctx, "failed to delete committee index", "error", err, "key", committeeKey) - } - } - } -} - -// CreateSecondaryIndices creates secondary indices for a mailing list (used by orchestrator) -func (s *storage) CreateSecondaryIndices(ctx context.Context, mailingList *model.GrpsIOMailingList) ([]string, error) { - // Reuse the existing secondary index creation method and return the created keys - return s.createMailingListSecondaryIndices(ctx, mailingList) -} - -// CheckMailingListExists checks if a mailing list with the given name exists in parent service -func (s *storage) CheckMailingListExists(ctx context.Context, parentID, groupName string) (bool, error) { - // Create temporary mailing list to generate consistent constraint key - tempMailingList := &model.GrpsIOMailingList{ - ServiceUID: parentID, - GroupName: groupName, - } - constraintKey := fmt.Sprintf(constants.KVLookupGroupsIOMailingListConstraintPrefix, tempMailingList.BuildIndexKey(ctx)) - - slog.DebugContext(ctx, "nats storage: checking mailing list existence", - "parent_id", parentID, - "group_name", groupName, - "constraint_key", constraintKey) - - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOMailingLists] - if !exists || kv == nil { - return false, errs.NewServiceUnavailable("KV bucket not available") - } - - _, err := kv.Get(ctx, constraintKey) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - return false, nil // Doesn't exist - } - return false, errs.NewServiceUnavailable("failed to check mailing list existence") - } - - return true, nil // Exists -} - -// GetMailingListByGroupID retrieves a mailing list by GroupsIO subgroup ID -// Returns NotFound error if mailing list doesn't exist -// Performance: O(1) indexed lookup using secondary index -func (s *storage) GetMailingListByGroupID(ctx context.Context, groupID uint64) (*model.GrpsIOMailingList, uint64, error) { - slog.DebugContext(ctx, "nats storage: getting mailing list by group_id (indexed)", - "group_id", groupID) - - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOMailingLists] - if !exists || kv == nil { - return nil, 0, errs.NewServiceUnavailable("KV bucket not available") - } - - indexPrefix := fmt.Sprintf(constants.KVLookupGroupsIOMailingListBySubgroupIDPrefix, groupID) - - // Extract UIDs - maxResults=1 triggers early exit in helper - uids, err := s.getUIDsFromSecondaryIndex(ctx, kv, indexPrefix, 1) - if err != nil { - return nil, 0, errs.NewServiceUnavailable("failed to list mailing lists") - } - - if len(uids) == 0 { - slog.DebugContext(ctx, "mailing list not found by group_id", "group_id", groupID) - return nil, 0, errs.NewNotFound("mailing list not found") - } - - // Fetch the first (and only) mailing list - mailingList, rev, err := s.GetGrpsIOMailingList(ctx, uids[0]) - if err != nil { - return nil, 0, err - } - - slog.DebugContext(ctx, "found mailing list with matching group_id", - "mailing_list_uid", mailingList.UID, - "group_name", mailingList.GroupName, - "group_id", groupID, - "revision", rev) - - return mailingList, rev, nil -} - -// GetMailingListsByCommittee retrieves all mailing lists for a committee using secondary index -// Pattern: mirrors GetServicesByGroupID for indexed multi-result queries -func (s *storage) GetMailingListsByCommittee(ctx context.Context, committeeUID string) ([]*model.GrpsIOMailingList, error) { - slog.DebugContext(ctx, "nats storage: getting mailing lists by committee (indexed)", - "committee_uid", committeeUID) - - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOMailingLists] - if !exists || kv == nil { - return nil, errs.NewServiceUnavailable("KV bucket not available") - } - - indexPrefix := fmt.Sprintf(constants.KVLookupGroupsIOMailingListCommitteePrefix, committeeUID) - - // Extract UIDs from secondary index (0 = get all) - uids, err := s.getUIDsFromSecondaryIndex(ctx, kv, indexPrefix, 0) - if err != nil { - return nil, errs.NewServiceUnavailable("failed to list mailing lists by committee") - } - - if len(uids) == 0 { - slog.DebugContext(ctx, "no mailing lists found for committee", "committee_uid", committeeUID) - return []*model.GrpsIOMailingList{}, nil - } - - // Fetch mailing lists by UID - var mailingLists []*model.GrpsIOMailingList - for _, uid := range uids { - ml, _, err := s.GetGrpsIOMailingList(ctx, uid) - if err != nil { - slog.WarnContext(ctx, "failed to get mailing list, skipping", - "uid", uid, - "error", err) - continue - } - mailingLists = append(mailingLists, ml) - } - - slog.DebugContext(ctx, "mailing lists retrieved by committee (indexed)", - "committee_uid", committeeUID, - "count", len(mailingLists)) - - return mailingLists, nil -} - -// ================== Interface Assertions ================== - -// Verify storage implements repository interfaces at compile time -var ( - _ port.GrpsIOServiceRepository = (*storage)(nil) - _ port.GrpsIOMailingListRepository = (*storage)(nil) - _ port.GrpsIOMemberRepository = (*storage)(nil) -) - -// ================== GrpsIOMember operations ================== - -// UniqueMember creates a unique constraint for member email within mailing list -func (s *storage) UniqueMember(ctx context.Context, member *model.GrpsIOMember) (string, error) { - constraintKey := fmt.Sprintf(constants.KVLookupGroupsIOMemberConstraintPrefix, member.BuildIndexKey(ctx)) - - slog.DebugContext(ctx, "validating unique member constraint", - "mailing_list_uid", member.MailingListUID, - "email", redaction.RedactEmail(member.Email), - "constraint_key", constraintKey) - - return s.createUniqueConstraintInBucket(ctx, constants.KVBucketNameGroupsIOMembers, constraintKey, member.UID) -} - -// CreateGrpsIOMember stores a new member in NATS KV (following mailing list pattern) -func (s *storage) CreateGrpsIOMember(ctx context.Context, member *model.GrpsIOMember) (*model.GrpsIOMember, uint64, error) { - slog.DebugContext(ctx, "nats storage: creating member", - "member_id", member.UID, - "email", redaction.RedactEmail(member.Email), - "mailing_list_uid", member.MailingListUID) - - rev, err := s.put(ctx, constants.KVBucketNameGroupsIOMembers, member.UID, member) - if err != nil { - slog.ErrorContext(ctx, "failed to create member", "error", err, "member_id", member.UID) - return nil, 0, errs.NewServiceUnavailable("failed to create member") - } - - slog.DebugContext(ctx, "nats storage: member created", - "member_id", member.UID, - "revision", rev) - - return member, rev, nil -} - -// CreateMemberSecondaryIndices creates all secondary indices for the member -func (s *storage) CreateMemberSecondaryIndices(ctx context.Context, member *model.GrpsIOMember) ([]string, error) { - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOMembers] - if !exists || kv == nil { - return nil, errs.NewServiceUnavailable("KV bucket not available") - } - - createdKeys, err := s.createSecondaryIndices(ctx, kv, member.UID, []IndexSpec{ - {Name: "memberid", KeyFormat: constants.KVLookupGroupsIOMemberByMemberIDPrefix, Int64ID: member.MemberID}, - {Name: "groupid", KeyFormat: constants.KVLookupGroupsIOMemberByGroupIDPrefix, Int64ID: member.GroupID}, - }) - if err != nil { - return createdKeys, err - } - - slog.DebugContext(ctx, "member secondary indices created successfully", - "member_uid", member.UID, - "indices_created", createdKeys) - return createdKeys, nil -} - -// GetGrpsIOMember retrieves a member by UID -func (s *storage) GetGrpsIOMember(ctx context.Context, uid string) (*model.GrpsIOMember, uint64, error) { - slog.DebugContext(ctx, "nats storage: getting member", - "member_uid", uid) - - member := &model.GrpsIOMember{} - rev, err := s.get(ctx, constants.KVBucketNameGroupsIOMembers, uid, member, false) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.DebugContext(ctx, "member not found", "member_uid", uid, "error", err) - return nil, 0, errs.NewNotFound("member not found") - } - slog.ErrorContext(ctx, "failed to get member", "error", err, "member_uid", uid) - return nil, 0, errs.NewServiceUnavailable("failed to get member") - } - - slog.DebugContext(ctx, "nats storage: member retrieved", - "member_uid", uid, - "email", redaction.RedactEmail(member.Email), - "revision", rev) - - return member, rev, nil -} - -// UpdateGrpsIOMember updates an existing member with optimistic concurrency control -func (s *storage) UpdateGrpsIOMember(ctx context.Context, uid string, member *model.GrpsIOMember, expectedRevision uint64) (*model.GrpsIOMember, uint64, error) { - slog.DebugContext(ctx, "nats storage: updating member", - "member_uid", uid, - "email", redaction.RedactEmail(member.Email), - "expected_revision", expectedRevision) - - rev, err := s.putWithRevision(ctx, constants.KVBucketNameGroupsIOMembers, uid, member, expectedRevision) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.WarnContext(ctx, "member not found on update", "member_uid", uid) - return nil, 0, errs.NewNotFound("member not found") - } - if s.isRevisionMismatch(err) { - slog.WarnContext(ctx, "revision mismatch on update", "member_uid", uid, "expected_revision", expectedRevision) - return nil, 0, errs.NewConflict("revision mismatch") - } - slog.ErrorContext(ctx, "failed to update member", "error", err, "member_uid", uid) - return nil, 0, errs.NewServiceUnavailable("failed to update member") - } - - slog.DebugContext(ctx, "nats storage: member updated", - "member_uid", uid, - "email", redaction.RedactEmail(member.Email), - "revision", rev) - - return member, rev, nil -} - -// DeleteGrpsIOMember deletes a member with optimistic concurrency control -func (s *storage) DeleteGrpsIOMember(ctx context.Context, uid string, expectedRevision uint64, member *model.GrpsIOMember) error { - if member == nil { - return errs.NewValidation("member is required for deletion") - } - - slog.DebugContext(ctx, "nats storage: deleting member", - "member_uid", uid, - "expected_revision", expectedRevision) - - // Use the passed member data - no need to fetch again - - err := s.delete(ctx, constants.KVBucketNameGroupsIOMembers, uid, expectedRevision) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.WarnContext(ctx, "member not found on delete", "member_uid", uid) - return errs.NewNotFound("member not found") - } - if s.isRevisionMismatch(err) { - slog.WarnContext(ctx, "revision mismatch on delete", "member_uid", uid, "expected_revision", expectedRevision) - return errs.NewConflict("revision mismatch") - } - slog.ErrorContext(ctx, "failed to delete member", "error", err, "member_uid", uid) - return errs.NewServiceUnavailable("failed to delete member") - } - - // Clean up unique constraint (verify it belongs to this member) - constraintKey := fmt.Sprintf(constants.KVLookupGroupsIOMemberConstraintPrefix, member.BuildIndexKey(ctx)) - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOMembers] - if exists && kv != nil { - entry, err := kv.Get(ctx, constraintKey) - if err == nil && string(entry.Value()) == member.UID { - // Only delete if it still points to our UID - if delErr := kv.Delete(ctx, constraintKey, jetstream.LastRevision(entry.Revision())); delErr != nil { - slog.DebugContext(ctx, "failed to delete constraint key during cleanup", "error", delErr, "key", constraintKey) - } - } - // Silently skip if not found or points to different UID (best effort cleanup) - } - - slog.DebugContext(ctx, "nats storage: member deleted", - "member_uid", uid) - - return nil -} - -// GetMemberRevision retrieves only the revision for a given UID -func (s *storage) GetMemberRevision(ctx context.Context, uid string) (uint64, error) { - return s.get(ctx, constants.KVBucketNameGroupsIOMembers, uid, &model.GrpsIOMember{}, true) -} - -// GetMemberByGroupsIOMemberID retrieves member by Groups.io member ID using secondary index -// Pattern mirrors GetMailingListByGroupID -func (s *storage) GetMemberByGroupsIOMemberID(ctx context.Context, memberID uint64) (*model.GrpsIOMember, uint64, error) { - slog.DebugContext(ctx, "nats storage: getting member by Groups.io member ID", - "member_id", memberID) - - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOMembers] - if !exists || kv == nil { - return nil, 0, errs.NewServiceUnavailable("KV bucket not available") - } - - indexPrefix := fmt.Sprintf(constants.KVLookupGroupsIOMemberByMemberIDPrefix, memberID) - - // Extract UIDs - maxResults=1 triggers early exit in helper - uids, err := s.getUIDsFromSecondaryIndex(ctx, kv, indexPrefix, 1) - if err != nil { - return nil, 0, errs.NewServiceUnavailable("failed to list members") - } - - if len(uids) == 0 { - slog.DebugContext(ctx, "member not found by Groups.io member ID", "member_id", memberID) - return nil, 0, errs.NewNotFound("member not found") - } - - // Fetch the first (and only) member - member, rev, err := s.GetGrpsIOMember(ctx, uids[0]) - if err != nil { - return nil, 0, err - } - - slog.DebugContext(ctx, "found member with matching Groups.io member ID", - "member_uid", member.UID, - "email", redaction.RedactEmail(member.Email), - "member_id", memberID, - "revision", rev) - - return member, rev, nil -} - -// GetMemberByEmail retrieves member by email within mailing list -// Uses unique constraint key (same as UniqueMember creates) -func (s *storage) GetMemberByEmail(ctx context.Context, mailingListUID, email string) (*model.GrpsIOMember, uint64, error) { - slog.DebugContext(ctx, "nats storage: getting member by email", - "mailing_list_uid", mailingListUID, - "email", redaction.RedactEmail(email)) - - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOMembers] - if !exists || kv == nil { - return nil, 0, errs.NewServiceUnavailable("KV bucket not available") - } - - // Build unique constraint key (same format as UniqueMember) - tempMember := &model.GrpsIOMember{ - MailingListUID: mailingListUID, - Email: email, - } - constraintKey := fmt.Sprintf(constants.KVLookupGroupsIOMemberConstraintPrefix, - tempMember.BuildIndexKey(context.Background())) - - // Get the constraint entry (value is the member UID) - entry, err := kv.Get(ctx, constraintKey) - if err != nil { - if errors.Is(err, jetstream.ErrKeyNotFound) { - slog.DebugContext(ctx, "member not found by email", - "mailing_list_uid", mailingListUID, - "email", redaction.RedactEmail(email)) - return nil, 0, errs.NewNotFound("member not found") - } - slog.ErrorContext(ctx, "failed to get constraint key", "error", err, "key", constraintKey) - return nil, 0, errs.NewServiceUnavailable("failed to get member") - } - - memberUID := string(entry.Value()) - - // Fetch the actual member - member, rev, err := s.GetGrpsIOMember(ctx, memberUID) - if err != nil { - return nil, 0, err - } - - slog.DebugContext(ctx, "found member by email", - "member_uid", member.UID, - "mailing_list_uid", mailingListUID, - "revision", rev) - - return member, rev, nil -} - -// CountMembersInMailingList counts all members in a mailing list by iterating NATS KV -// Performance: O(K) where K = total keys in members bucket -// Used as fallback when Groups.io client is unavailable (mock mode, nil client) -func (s *storage) CountMembersInMailingList(ctx context.Context, mailingListUID string) (int, error) { - slog.DebugContext(ctx, "counting members in mailing list", - "mailing_list_uid", mailingListUID) - - kv, exists := s.client.kvStore[constants.KVBucketNameGroupsIOMembers] - if !exists || kv == nil { - return 0, errs.NewServiceUnavailable("KV bucket not available") - } - - // Get all keys in members bucket - keys, err := kv.Keys(ctx, jetstream.IgnoreDeletes()) - if err != nil { - slog.ErrorContext(ctx, "failed to list member keys", "error", err) - return 0, errs.NewServiceUnavailable("failed to count members") - } - - // Count members belonging to this mailing list - count := 0 - for _, key := range keys { - // Skip lookup/index keys, only count actual member UIDs - if strings.HasPrefix(key, constants.GroupsIOMemberLookupKeyPrefix) { - continue - } - - // Get member to check mailing_list_uid - member := &model.GrpsIOMember{} - _, err := s.get(ctx, constants.KVBucketNameGroupsIOMembers, key, member, false) - if err != nil { - // Skip members that can't be read (deleted concurrently, etc.) - slog.DebugContext(ctx, "skipping member during count", "key", key, "error", err) - continue - } - - if member.MailingListUID == mailingListUID { - count++ - } - } - - slog.DebugContext(ctx, "member count completed", - "mailing_list_uid", mailingListUID, - "count", count) - - return count, nil -} - -func NewStorage(client *NATSClient) port.GrpsIOReaderWriter { - return &storage{ - client: client, - } -} diff --git a/internal/infrastructure/proxy/client.go b/internal/infrastructure/proxy/client.go new file mode 100644 index 0000000..85816af --- /dev/null +++ b/internal/infrastructure/proxy/client.go @@ -0,0 +1,581 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +// Package proxy provides the ITX HTTP proxy client for GroupsIO operations. +package proxy + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/auth0/go-auth0/authentication" + "github.com/auth0/go-auth0/authentication/oauth" + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain" + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/models" + "golang.org/x/oauth2" +) + +const ( + tokenExpiryLeeway = 60 * time.Second + itxScope = "manage:groupsio" +) + +// Config holds ITX proxy configuration +type Config struct { + BaseURL string + ClientID string + PrivateKey string // RSA private key in PEM format + Auth0Domain string + Audience string + Timeout time.Duration +} + +// Client implements domain.ITXGroupsioClient +type Client struct { + httpClient *http.Client + config Config +} + +// auth0TokenSource implements oauth2.TokenSource using Auth0 SDK with private key +type auth0TokenSource struct { + ctx context.Context + authConfig *authentication.Authentication + audience string +} + +// Token implements the oauth2.TokenSource interface +func (a *auth0TokenSource) Token() (*oauth2.Token, error) { + ctx := a.ctx + if ctx == nil { + ctx = context.TODO() + } + + body := oauth.LoginWithClientCredentialsRequest{ + Audience: a.audience, + } + + tokenSet, err := a.authConfig.OAuth.LoginWithClientCredentials(ctx, body, oauth.IDTokenValidationOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to get token from Auth0: %w", err) + } + + token := &oauth2.Token{ + AccessToken: tokenSet.AccessToken, + TokenType: tokenSet.TokenType, + RefreshToken: tokenSet.RefreshToken, + Expiry: time.Now().Add(time.Duration(tokenSet.ExpiresIn)*time.Second - tokenExpiryLeeway), + } + + token = token.WithExtra(map[string]any{ + "scope": tokenSet.Scope, + }) + + return token, nil +} + +// NewClient creates a new ITX proxy client with OAuth2 M2M authentication using private key +func NewClient(config Config) *Client { + ctx := context.Background() + + if config.PrivateKey == "" { + panic("ITX_CLIENT_PRIVATE_KEY is required but not set") + } + + authConfig, err := authentication.New( + ctx, + config.Auth0Domain, + authentication.WithClientID(config.ClientID), + authentication.WithClientAssertion(config.PrivateKey, "RS256"), + ) + if err != nil { + panic(fmt.Sprintf("failed to create Auth0 client: %v (ensure ITX_CLIENT_PRIVATE_KEY contains a valid RSA private key in PEM format)", err)) + } + + tokenSource := &auth0TokenSource{ + ctx: ctx, + authConfig: authConfig, + audience: config.Audience, + } + + reuseTokenSource := oauth2.ReuseTokenSource(nil, tokenSource) + httpClient := oauth2.NewClient(ctx, reuseTokenSource) + httpClient.Timeout = config.Timeout + + return &Client{ + httpClient: httpClient, + config: config, + } +} + +// doRequest performs an HTTP request and returns the raw response body +func (c *Client) doRequest(ctx context.Context, method, url string, body []byte) ([]byte, int, error) { + var bodyReader io.Reader + if body != nil { + bodyReader = bytes.NewReader(body) + } + + httpReq, err := http.NewRequestWithContext(ctx, method, url, bodyReader) + if err != nil { + return nil, 0, domain.NewInternalError("failed to create request", err) + } + + if body != nil { + httpReq.Header.Set("Content-Type", "application/json") + } + httpReq.Header.Set("Accept", "application/json") + httpReq.Header.Set("x-scope", itxScope) + + resp, err := c.httpClient.Do(httpReq) + if err != nil { + return nil, 0, domain.NewUnavailableError("ITX service request failed", err) + } + defer func() { + _ = resp.Body.Close() + }() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, resp.StatusCode, domain.NewInternalError("failed to read response", err) + } + + return respBody, resp.StatusCode, nil +} + +// mapHTTPError converts HTTP status codes to domain errors +func (c *Client) mapHTTPError(statusCode int, body []byte) error { + msg := string(body) + switch statusCode { + case http.StatusNotFound: + return domain.NewNotFoundError(fmt.Sprintf("resource not found: %s", msg)) + case http.StatusBadRequest: + return domain.NewValidationError(fmt.Sprintf("bad request: %s", msg)) + case http.StatusConflict: + return domain.NewConflictError(fmt.Sprintf("conflict: %s", msg)) + case http.StatusServiceUnavailable, http.StatusBadGateway, http.StatusGatewayTimeout: + return domain.NewUnavailableError(fmt.Sprintf("ITX service unavailable: %s", msg)) + default: + return domain.NewInternalError(fmt.Sprintf("ITX error (status %d): %s", statusCode, msg)) + } +} + +// ---- GroupsioServiceClient implementation ---- + +// ListServices lists GroupsIO services, optionally filtered by project_id (v1 SFID) +func (c *Client) ListServices(ctx context.Context, projectID string) (*models.GroupsioServiceListResponse, error) { + url := fmt.Sprintf("%s/groupsio_service", c.config.BaseURL) + if projectID != "" { + url += fmt.Sprintf("?project_id=%s", projectID) + } + + body, status, err := c.doRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioServiceListResponse + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// CreateService creates a new GroupsIO service +func (c *Client) CreateService(ctx context.Context, req *models.GroupsioServiceRequest) (*models.GroupsioService, error) { + bodyBytes, err := json.Marshal(req) + if err != nil { + return nil, domain.NewInternalError("failed to marshal request", err) + } + + url := fmt.Sprintf("%s/groupsio_service", c.config.BaseURL) + body, status, err := c.doRequest(ctx, http.MethodPost, url, bodyBytes) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioService + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// GetService retrieves a GroupsIO service by ID +func (c *Client) GetService(ctx context.Context, serviceID string) (*models.GroupsioService, error) { + url := fmt.Sprintf("%s/groupsio_service/%s", c.config.BaseURL, serviceID) + body, status, err := c.doRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioService + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// UpdateService updates a GroupsIO service +func (c *Client) UpdateService(ctx context.Context, serviceID string, req *models.GroupsioServiceRequest) (*models.GroupsioService, error) { + bodyBytes, err := json.Marshal(req) + if err != nil { + return nil, domain.NewInternalError("failed to marshal request", err) + } + + url := fmt.Sprintf("%s/groupsio_service/%s", c.config.BaseURL, serviceID) + body, status, err := c.doRequest(ctx, http.MethodPut, url, bodyBytes) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioService + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// DeleteService deletes a GroupsIO service +func (c *Client) DeleteService(ctx context.Context, serviceID string) error { + url := fmt.Sprintf("%s/groupsio_service/%s", c.config.BaseURL, serviceID) + body, status, err := c.doRequest(ctx, http.MethodDelete, url, nil) + if err != nil { + return err + } + if status < 200 || status >= 300 { + return c.mapHTTPError(status, body) + } + return nil +} + +// GetProjects returns projects that have GroupsIO services +func (c *Client) GetProjects(ctx context.Context) (*models.GroupsioServiceProjectsResponse, error) { + url := fmt.Sprintf("%s/groupsio_service/_projects", c.config.BaseURL) + body, status, err := c.doRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioServiceProjectsResponse + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// FindParentService finds the parent service for a project (v1 SFID) +func (c *Client) FindParentService(ctx context.Context, projectID string) (*models.GroupsioService, error) { + url := fmt.Sprintf("%s/groupsio_service_find_parent?project_id=%s", c.config.BaseURL, projectID) + body, status, err := c.doRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioService + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// ---- GroupsioSubgroupClient implementation ---- + +// ListSubgroups lists subgroups, optionally filtered by project_id and/or committee_id (v1 SFIDs) +func (c *Client) ListSubgroups(ctx context.Context, projectID, committeeID string) (*models.GroupsioSubgroupListResponse, error) { + url := fmt.Sprintf("%s/groupsio_subgroup", c.config.BaseURL) + sep := "?" + if projectID != "" { + url += fmt.Sprintf("%sproject_id=%s", sep, projectID) + sep = "&" + } + if committeeID != "" { + url += fmt.Sprintf("%scommittee_id=%s", sep, committeeID) + } + + body, status, err := c.doRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioSubgroupListResponse + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// CreateSubgroup creates a new subgroup +func (c *Client) CreateSubgroup(ctx context.Context, req *models.GroupsioSubgroupRequest) (*models.GroupsioSubgroup, error) { + bodyBytes, err := json.Marshal(req) + if err != nil { + return nil, domain.NewInternalError("failed to marshal request", err) + } + + url := fmt.Sprintf("%s/groupsio_subgroup", c.config.BaseURL) + body, status, err := c.doRequest(ctx, http.MethodPost, url, bodyBytes) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioSubgroup + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// GetSubgroup retrieves a subgroup by ID +func (c *Client) GetSubgroup(ctx context.Context, subgroupID string) (*models.GroupsioSubgroup, error) { + url := fmt.Sprintf("%s/groupsio_subgroup/%s", c.config.BaseURL, subgroupID) + body, status, err := c.doRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioSubgroup + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// UpdateSubgroup updates a subgroup +func (c *Client) UpdateSubgroup(ctx context.Context, subgroupID string, req *models.GroupsioSubgroupRequest) (*models.GroupsioSubgroup, error) { + bodyBytes, err := json.Marshal(req) + if err != nil { + return nil, domain.NewInternalError("failed to marshal request", err) + } + + url := fmt.Sprintf("%s/groupsio_subgroup/%s", c.config.BaseURL, subgroupID) + body, status, err := c.doRequest(ctx, http.MethodPut, url, bodyBytes) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioSubgroup + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// DeleteSubgroup deletes a subgroup +func (c *Client) DeleteSubgroup(ctx context.Context, subgroupID string) error { + url := fmt.Sprintf("%s/groupsio_subgroup/%s", c.config.BaseURL, subgroupID) + body, status, err := c.doRequest(ctx, http.MethodDelete, url, nil) + if err != nil { + return err + } + if status < 200 || status >= 300 { + return c.mapHTTPError(status, body) + } + return nil +} + +// GetSubgroupCount returns the count of subgroups for a project (v1 SFID) +func (c *Client) GetSubgroupCount(ctx context.Context, projectID string) (*models.GroupsioSubgroupCountResponse, error) { + url := fmt.Sprintf("%s/groupsio/subgroup_count?project=%s", c.config.BaseURL, projectID) + body, status, err := c.doRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioSubgroupCountResponse + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// GetMemberCount returns the count of members in a subgroup +func (c *Client) GetMemberCount(ctx context.Context, subgroupID string) (*models.GroupsioMemberCountResponse, error) { + url := fmt.Sprintf("%s/groupsio_subgroup/%s/member_count", c.config.BaseURL, subgroupID) + body, status, err := c.doRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioMemberCountResponse + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// ---- GroupsioMemberClient implementation ---- + +// ListMembers lists members of a subgroup +func (c *Client) ListMembers(ctx context.Context, subgroupID string) (*models.GroupsioMemberListResponse, error) { + url := fmt.Sprintf("%s/groupsio_subgroup/%s/members", c.config.BaseURL, subgroupID) + body, status, err := c.doRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioMemberListResponse + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// AddMember adds a member to a subgroup +func (c *Client) AddMember(ctx context.Context, subgroupID string, req *models.GroupsioMemberRequest) (*models.GroupsioMember, error) { + bodyBytes, err := json.Marshal(req) + if err != nil { + return nil, domain.NewInternalError("failed to marshal request", err) + } + + url := fmt.Sprintf("%s/groupsio_subgroup/%s/members", c.config.BaseURL, subgroupID) + body, status, err := c.doRequest(ctx, http.MethodPost, url, bodyBytes) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioMember + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// GetMember retrieves a member by ID +func (c *Client) GetMember(ctx context.Context, subgroupID, memberID string) (*models.GroupsioMember, error) { + url := fmt.Sprintf("%s/groupsio_subgroup/%s/members/%s", c.config.BaseURL, subgroupID, memberID) + body, status, err := c.doRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioMember + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// UpdateMember updates a member +func (c *Client) UpdateMember(ctx context.Context, subgroupID, memberID string, req *models.GroupsioMemberRequest) (*models.GroupsioMember, error) { + bodyBytes, err := json.Marshal(req) + if err != nil { + return nil, domain.NewInternalError("failed to marshal request", err) + } + + url := fmt.Sprintf("%s/groupsio_subgroup/%s/members/%s", c.config.BaseURL, subgroupID, memberID) + body, status, err := c.doRequest(ctx, http.MethodPut, url, bodyBytes) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioMember + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} + +// DeleteMember removes a member from a subgroup +func (c *Client) DeleteMember(ctx context.Context, subgroupID, memberID string) error { + url := fmt.Sprintf("%s/groupsio_subgroup/%s/members/%s", c.config.BaseURL, subgroupID, memberID) + body, status, err := c.doRequest(ctx, http.MethodDelete, url, nil) + if err != nil { + return err + } + if status < 200 || status >= 300 { + return c.mapHTTPError(status, body) + } + return nil +} + +// InviteMembers sends invitations to multiple email addresses +func (c *Client) InviteMembers(ctx context.Context, subgroupID string, req *models.GroupsioInviteMembersRequest) error { + bodyBytes, err := json.Marshal(req) + if err != nil { + return domain.NewInternalError("failed to marshal request", err) + } + + url := fmt.Sprintf("%s/groupsio_subgroup/%s/invitemembers", c.config.BaseURL, subgroupID) + body, status, err := c.doRequest(ctx, http.MethodPost, url, bodyBytes) + if err != nil { + return err + } + if status < 200 || status >= 300 { + return c.mapHTTPError(status, body) + } + return nil +} + +// CheckSubscriber checks if an email is subscribed to a subgroup +func (c *Client) CheckSubscriber(ctx context.Context, req *models.GroupsioCheckSubscriberRequest) (*models.GroupsioCheckSubscriberResponse, error) { + bodyBytes, err := json.Marshal(req) + if err != nil { + return nil, domain.NewInternalError("failed to marshal request", err) + } + + url := fmt.Sprintf("%s/groupsio_checksubscriber", c.config.BaseURL) + body, status, err := c.doRequest(ctx, http.MethodPost, url, bodyBytes) + if err != nil { + return nil, err + } + if status < 200 || status >= 300 { + return nil, c.mapHTTPError(status, body) + } + + var result models.GroupsioCheckSubscriberResponse + if err := json.Unmarshal(body, &result); err != nil { + return nil, domain.NewInternalError("failed to parse response", err) + } + return &result, nil +} diff --git a/internal/infrastructure/proxy/client_test.go b/internal/infrastructure/proxy/client_test.go new file mode 100644 index 0000000..86e9484 --- /dev/null +++ b/internal/infrastructure/proxy/client_test.go @@ -0,0 +1,467 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package proxy + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain" + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/models" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// newTestClient creates a Client pointing at the given base URL, bypassing Auth0. +func newTestClient(baseURL string) *Client { + return &Client{ + httpClient: &http.Client{}, + config: Config{BaseURL: baseURL}, + } +} + +// ---- mapHTTPError ---- + +func TestMapHTTPError(t *testing.T) { + c := newTestClient("http://unused") + tests := []struct { + status int + errType domain.ErrorType + }{ + {http.StatusNotFound, domain.ErrorTypeNotFound}, + {http.StatusBadRequest, domain.ErrorTypeValidation}, + {http.StatusConflict, domain.ErrorTypeConflict}, + {http.StatusServiceUnavailable, domain.ErrorTypeUnavailable}, + {http.StatusBadGateway, domain.ErrorTypeUnavailable}, + {http.StatusGatewayTimeout, domain.ErrorTypeUnavailable}, + {http.StatusInternalServerError, domain.ErrorTypeInternal}, + {http.StatusUnprocessableEntity, domain.ErrorTypeInternal}, + } + for _, tt := range tests { + t.Run(http.StatusText(tt.status), func(t *testing.T) { + err := c.mapHTTPError(tt.status, []byte("detail")) + var domErr *domain.DomainError + require.True(t, errors.As(err, &domErr)) + assert.Equal(t, tt.errType, domErr.Type) + }) + } +} + +// ---- service endpoints ---- + +func TestClient_ListServices(t *testing.T) { + t.Run("success with no filter", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + assert.Equal(t, "/groupsio_service", r.URL.Path) + assert.Empty(t, r.URL.RawQuery) + assert.Equal(t, itxScope, r.Header.Get("x-scope")) + writeJSON(w, models.GroupsioServiceListResponse{ + Items: []*models.GroupsioService{{ID: "svc-1", ProjectID: "proj-sfid"}}, + Total: 1, + }) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.ListServices(context.Background(), "") + require.NoError(t, err) + require.Len(t, resp.Items, 1) + assert.Equal(t, "svc-1", resp.Items[0].ID) + }) + + t.Run("success with project_id filter", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "proj-sfid-001", r.URL.Query().Get("project_id")) + writeJSON(w, models.GroupsioServiceListResponse{Items: []*models.GroupsioService{}, Total: 0}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.ListServices(context.Background(), "proj-sfid-001") + require.NoError(t, err) + assert.Equal(t, 0, resp.Total) + }) + + t.Run("404 returns not found error", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "not found", http.StatusNotFound) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + _, err := c.ListServices(context.Background(), "") + require.Error(t, err) + assert.Equal(t, domain.ErrorTypeNotFound, domain.GetErrorType(err)) + }) +} + +func TestClient_CreateService(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "/groupsio_service", r.URL.Path) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + var req models.GroupsioServiceRequest + require.NoError(t, json.NewDecoder(r.Body).Decode(&req)) + assert.Equal(t, "proj-sfid", req.ProjectID) + + w.WriteHeader(http.StatusCreated) + writeJSON(w, models.GroupsioService{ID: "new-svc", ProjectID: req.ProjectID}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.CreateService(context.Background(), &models.GroupsioServiceRequest{ProjectID: "proj-sfid"}) + require.NoError(t, err) + assert.Equal(t, "new-svc", resp.ID) +} + +func TestClient_GetService(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + assert.Equal(t, "/groupsio_service/svc-42", r.URL.Path) + writeJSON(w, models.GroupsioService{ID: "svc-42"}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.GetService(context.Background(), "svc-42") + require.NoError(t, err) + assert.Equal(t, "svc-42", resp.ID) +} + +func TestClient_UpdateService(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPut, r.Method) + assert.Equal(t, "/groupsio_service/svc-42", r.URL.Path) + writeJSON(w, models.GroupsioService{ID: "svc-42", Status: "active"}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.UpdateService(context.Background(), "svc-42", &models.GroupsioServiceRequest{Status: "active"}) + require.NoError(t, err) + assert.Equal(t, "active", resp.Status) +} + +func TestClient_DeleteService(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodDelete, r.Method) + assert.Equal(t, "/groupsio_service/svc-42", r.URL.Path) + w.WriteHeader(http.StatusNoContent) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + err := c.DeleteService(context.Background(), "svc-42") + require.NoError(t, err) +} + +func TestClient_GetProjects(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/groupsio_service/_projects", r.URL.Path) + writeJSON(w, models.GroupsioServiceProjectsResponse{Projects: []string{"proj-a", "proj-b"}}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.GetProjects(context.Background()) + require.NoError(t, err) + assert.Equal(t, []string{"proj-a", "proj-b"}, resp.Projects) +} + +func TestClient_FindParentService(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/groupsio_service_find_parent", r.URL.Path) + assert.Equal(t, "proj-sfid-001", r.URL.Query().Get("project_id")) + writeJSON(w, models.GroupsioService{ID: "parent-svc"}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.FindParentService(context.Background(), "proj-sfid-001") + require.NoError(t, err) + assert.Equal(t, "parent-svc", resp.ID) +} + +// ---- subgroup endpoints ---- + +func TestClient_ListSubgroups(t *testing.T) { + t.Run("both filters", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/groupsio_subgroup", r.URL.Path) + assert.Equal(t, "proj-sfid", r.URL.Query().Get("project_id")) + assert.Equal(t, "comm-sfid", r.URL.Query().Get("committee_id")) + writeJSON(w, models.GroupsioSubgroupListResponse{ + Items: []*models.GroupsioSubgroup{{ID: "sg-1"}}, + Total: 1, + }) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.ListSubgroups(context.Background(), "proj-sfid", "comm-sfid") + require.NoError(t, err) + assert.Len(t, resp.Items, 1) + }) + + t.Run("no filters", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Empty(t, r.URL.RawQuery) + writeJSON(w, models.GroupsioSubgroupListResponse{}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + _, err := c.ListSubgroups(context.Background(), "", "") + require.NoError(t, err) + }) + + t.Run("project filter only", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "proj-sfid", r.URL.Query().Get("project_id")) + assert.Empty(t, r.URL.Query().Get("committee_id")) + writeJSON(w, models.GroupsioSubgroupListResponse{}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + _, err := c.ListSubgroups(context.Background(), "proj-sfid", "") + require.NoError(t, err) + }) +} + +func TestClient_CreateSubgroup(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "/groupsio_subgroup", r.URL.Path) + writeJSON(w, models.GroupsioSubgroup{ID: "sg-new", Name: "test-list"}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.CreateSubgroup(context.Background(), &models.GroupsioSubgroupRequest{Name: "test-list"}) + require.NoError(t, err) + assert.Equal(t, "sg-new", resp.ID) +} + +func TestClient_GetSubgroup(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/groupsio_subgroup/sg-42", r.URL.Path) + writeJSON(w, models.GroupsioSubgroup{ID: "sg-42"}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.GetSubgroup(context.Background(), "sg-42") + require.NoError(t, err) + assert.Equal(t, "sg-42", resp.ID) +} + +func TestClient_UpdateSubgroup(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPut, r.Method) + assert.Equal(t, "/groupsio_subgroup/sg-42", r.URL.Path) + writeJSON(w, models.GroupsioSubgroup{ID: "sg-42", Name: "updated"}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.UpdateSubgroup(context.Background(), "sg-42", &models.GroupsioSubgroupRequest{Name: "updated"}) + require.NoError(t, err) + assert.Equal(t, "updated", resp.Name) +} + +func TestClient_DeleteSubgroup(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodDelete, r.Method) + assert.Equal(t, "/groupsio_subgroup/sg-42", r.URL.Path) + w.WriteHeader(http.StatusNoContent) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + err := c.DeleteSubgroup(context.Background(), "sg-42") + require.NoError(t, err) +} + +func TestClient_GetSubgroupCount(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/groupsio/subgroup_count", r.URL.Path) + assert.Equal(t, "proj-sfid", r.URL.Query().Get("project")) + writeJSON(w, models.GroupsioSubgroupCountResponse{Count: 5}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.GetSubgroupCount(context.Background(), "proj-sfid") + require.NoError(t, err) + assert.Equal(t, 5, resp.Count) +} + +func TestClient_GetMemberCount(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/groupsio_subgroup/sg-42/member_count", r.URL.Path) + writeJSON(w, models.GroupsioMemberCountResponse{Count: 12}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.GetMemberCount(context.Background(), "sg-42") + require.NoError(t, err) + assert.Equal(t, 12, resp.Count) +} + +// ---- member endpoints ---- + +func TestClient_ListMembers(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/groupsio_subgroup/sg-42/members", r.URL.Path) + writeJSON(w, models.GroupsioMemberListResponse{ + Items: []*models.GroupsioMember{{ID: "m-1", Email: "a@example.com"}}, + Total: 1, + }) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.ListMembers(context.Background(), "sg-42") + require.NoError(t, err) + require.Len(t, resp.Items, 1) + assert.Equal(t, "a@example.com", resp.Items[0].Email) +} + +func TestClient_AddMember(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "/groupsio_subgroup/sg-42/members", r.URL.Path) + writeJSON(w, models.GroupsioMember{ID: "m-new", Email: "new@example.com"}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.AddMember(context.Background(), "sg-42", &models.GroupsioMemberRequest{Email: "new@example.com"}) + require.NoError(t, err) + assert.Equal(t, "m-new", resp.ID) +} + +func TestClient_GetMember(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/groupsio_subgroup/sg-42/members/m-7", r.URL.Path) + writeJSON(w, models.GroupsioMember{ID: "m-7"}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.GetMember(context.Background(), "sg-42", "m-7") + require.NoError(t, err) + assert.Equal(t, "m-7", resp.ID) +} + +func TestClient_UpdateMember(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPut, r.Method) + assert.Equal(t, "/groupsio_subgroup/sg-42/members/m-7", r.URL.Path) + writeJSON(w, models.GroupsioMember{ID: "m-7", Name: "Updated Name"}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.UpdateMember(context.Background(), "sg-42", "m-7", &models.GroupsioMemberRequest{Name: "Updated Name"}) + require.NoError(t, err) + assert.Equal(t, "Updated Name", resp.Name) +} + +func TestClient_DeleteMember(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodDelete, r.Method) + assert.Equal(t, "/groupsio_subgroup/sg-42/members/m-7", r.URL.Path) + w.WriteHeader(http.StatusNoContent) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + err := c.DeleteMember(context.Background(), "sg-42", "m-7") + require.NoError(t, err) +} + +func TestClient_InviteMembers(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "/groupsio_subgroup/sg-42/invitemembers", r.URL.Path) + + var req models.GroupsioInviteMembersRequest + require.NoError(t, json.NewDecoder(r.Body).Decode(&req)) + assert.Equal(t, []string{"a@example.com", "b@example.com"}, req.Emails) + + w.WriteHeader(http.StatusNoContent) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + err := c.InviteMembers(context.Background(), "sg-42", &models.GroupsioInviteMembersRequest{ + Emails: []string{"a@example.com", "b@example.com"}, + }) + require.NoError(t, err) +} + +func TestClient_CheckSubscriber(t *testing.T) { + t.Run("subscribed", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "/groupsio_checksubscriber", r.URL.Path) + writeJSON(w, models.GroupsioCheckSubscriberResponse{Subscribed: true}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.CheckSubscriber(context.Background(), &models.GroupsioCheckSubscriberRequest{ + Email: "a@example.com", SubgroupID: "sg-42", + }) + require.NoError(t, err) + assert.True(t, resp.Subscribed) + }) + + t.Run("not subscribed", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writeJSON(w, models.GroupsioCheckSubscriberResponse{Subscribed: false}) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + resp, err := c.CheckSubscriber(context.Background(), &models.GroupsioCheckSubscriberRequest{ + Email: "b@example.com", SubgroupID: "sg-42", + }) + require.NoError(t, err) + assert.False(t, resp.Subscribed) + }) +} + +func TestClient_InvalidJSON(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte("not valid json")) + })) + defer srv.Close() + + c := newTestClient(srv.URL) + _, err := c.GetService(context.Background(), "svc-1") + require.Error(t, err) + assert.Equal(t, domain.ErrorTypeInternal, domain.GetErrorType(err)) + assert.True(t, strings.Contains(err.Error(), "failed to parse response")) +} + +// writeJSON is a test helper that writes v as JSON to w. +func writeJSON(w http.ResponseWriter, v any) { + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(v); err != nil { + panic(err) + } +} diff --git a/internal/service/committee_sync_service.go b/internal/service/committee_sync_service.go deleted file mode 100644 index 4a53f15..0000000 --- a/internal/service/committee_sync_service.go +++ /dev/null @@ -1,613 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "encoding/json" - stderrors "errors" - "fmt" - "log/slog" - "slices" - - "github.com/google/uuid" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/redaction" - "github.com/nats-io/nats.go" -) - -// CommitteeSyncService handles committee member synchronization to mailing lists -// Pattern: mirrors grpsIOWebhookProcessor - ONE file with routing + business logic -type CommitteeSyncService struct { - mailingListReader port.GrpsIOMailingListReader - memberWriter port.GrpsIOMemberWriter - memberReader port.GrpsIOMemberReader - entityReader port.EntityAttributeReader // For querying committee-api -} - -// NewCommitteeSyncService creates a new committee sync service -func NewCommitteeSyncService( - mailingListReader port.GrpsIOMailingListReader, - memberWriter port.GrpsIOMemberWriter, - memberReader port.GrpsIOMemberReader, - entityReader port.EntityAttributeReader, -) *CommitteeSyncService { - return &CommitteeSyncService{ - mailingListReader: mailingListReader, - memberWriter: memberWriter, - memberReader: memberReader, - entityReader: entityReader, - } -} - -// HandleMessage routes NATS messages to appropriate handlers based on subject -// Pattern: mirrors grpsIOWebhookProcessor.ProcessEvent but returns error for acknowledgment -func (s *CommitteeSyncService) HandleMessage(ctx context.Context, msg *nats.Msg) error { - subject := msg.Subject - - slog.DebugContext(ctx, "received committee event", "subject", subject) - - var err error - switch subject { - case constants.CommitteeMemberCreatedSubject: - err = s.handleCreated(ctx, msg) - case constants.CommitteeMemberDeletedSubject: - err = s.handleDeleted(ctx, msg) - case constants.CommitteeMemberUpdatedSubject: - err = s.handleUpdated(ctx, msg) - default: - slog.WarnContext(ctx, "unknown committee event subject", "subject", subject) - return fmt.Errorf("unknown committee event subject: %s", subject) - } - - if err != nil { - slog.ErrorContext(ctx, "error processing committee event", - "error", err, - "subject", subject) - return err - } - - return nil -} - -// handleCreated processes committee member created events -func (s *CommitteeSyncService) handleCreated(ctx context.Context, msg *nats.Msg) error { - var event model.CommitteeMemberCreatedEvent - if err := json.Unmarshal(msg.Data, &event); err != nil { - slog.ErrorContext(ctx, "failed to unmarshal committee member created event", "error", err) - return fmt.Errorf("failed to unmarshal event: %w", err) - } - - slog.InfoContext(ctx, "processing committee member created event", - "member_uid", event.MemberUID, - "committee_uid", event.CommitteeUID, - "project_uid", event.ProjectUID, - "email", redaction.RedactEmail(event.Member.Email), - "voting_status", event.Member.VotingStatus) - - // Find all mailing lists for this committee - mailingLists, err := s.mailingListReader.GetMailingListsByCommittee(ctx, event.CommitteeUID) - if err != nil { - slog.ErrorContext(ctx, "failed to get mailing lists for committee", - "error", err, - "committee_uid", event.CommitteeUID) - return fmt.Errorf("failed to get mailing lists: %w", err) - } - - if len(mailingLists) == 0 { - slog.InfoContext(ctx, "no mailing lists found for committee (nothing to sync)", - "committee_uid", event.CommitteeUID) - return nil - } - - // For each mailing list, check if member should be added based on filters - for _, ml := range mailingLists { - // Check if this member's voting status matches the list's filters - for _, committee := range ml.Committees { - if matchesFilter(event.Member.VotingStatus, committee.AllowedVotingStatuses) { - slog.InfoContext(ctx, "adding committee member to matching mailing list", - "mailing_list_uid", ml.UID, - "group_name", ml.GroupName, - "email", redaction.RedactEmail(event.Member.Email), - "voting_status", event.Member.VotingStatus) - - if err := s.addMemberToList(ctx, ml, event.Member); err != nil { - slog.ErrorContext(ctx, "failed to add member to list", - "error", err, - "mailing_list_uid", ml.UID) - // Continue with other lists even if one fails - continue - } - } else { - slog.DebugContext(ctx, "member voting status does not match list filters", - "mailing_list_uid", ml.UID, - "voting_status", event.Member.VotingStatus, - "filters", committee.AllowedVotingStatuses) - } - } - } - - slog.InfoContext(ctx, "committee member created event processed successfully", - "member_uid", event.MemberUID) - - return nil -} - -// handleDeleted processes committee member deleted events -func (s *CommitteeSyncService) handleDeleted(ctx context.Context, msg *nats.Msg) error { - var event model.CommitteeMemberDeletedEvent - if err := json.Unmarshal(msg.Data, &event); err != nil { - slog.ErrorContext(ctx, "failed to unmarshal committee member deleted event", "error", err) - return fmt.Errorf("failed to unmarshal event: %w", err) - } - - slog.InfoContext(ctx, "processing committee member deleted event", - "member_uid", event.MemberUID, - "committee_uid", event.CommitteeUID, - "project_uid", event.ProjectUID, - "email", redaction.RedactEmail(event.Email)) - - // Find all mailing lists for this committee - mailingLists, err := s.mailingListReader.GetMailingListsByCommittee(ctx, event.CommitteeUID) - if err != nil { - slog.ErrorContext(ctx, "failed to get mailing lists for committee", - "error", err, - "committee_uid", event.CommitteeUID) - return fmt.Errorf("failed to get mailing lists: %w", err) - } - - if len(mailingLists) == 0 { - slog.InfoContext(ctx, "no mailing lists found for committee (nothing to sync)", - "committee_uid", event.CommitteeUID) - return nil - } - - // Remove or convert member from each mailing list - for _, ml := range mailingLists { - slog.InfoContext(ctx, "removing committee member from mailing list", - "mailing_list_uid", ml.UID, - "group_name", ml.GroupName, - "email", redaction.RedactEmail(event.Email), - "public", ml.Public) - - if err := s.removeMemberFromList(ctx, ml, event.Email); err != nil { - slog.ErrorContext(ctx, "failed to remove member from list", - "error", err, - "mailing_list_uid", ml.UID) - // Continue with other lists even if one fails - continue - } - } - - slog.InfoContext(ctx, "committee member deleted event processed successfully", - "member_uid", event.MemberUID) - - return nil -} - -// handleUpdated processes committee member updated events -func (s *CommitteeSyncService) handleUpdated(ctx context.Context, msg *nats.Msg) error { - var event model.CommitteeMemberUpdatedEvent - if err := json.Unmarshal(msg.Data, &event); err != nil { - slog.ErrorContext(ctx, "failed to unmarshal committee member updated event", "error", err) - return fmt.Errorf("failed to unmarshal event: %w", err) - } - - slog.InfoContext(ctx, "processing committee member updated event", - "member_uid", event.MemberUID, - "committee_uid", event.CommitteeUID, - "project_uid", event.ProjectUID, - "old_email", redaction.RedactEmail(event.OldMember.Email), - "new_email", redaction.RedactEmail(event.NewMember.Email), - "old_voting_status", event.OldMember.VotingStatus, - "new_voting_status", event.NewMember.VotingStatus) - - // Check if anything actually changed - emailChanged := event.OldMember.Email != event.NewMember.Email - statusChanged := event.OldMember.VotingStatus != event.NewMember.VotingStatus - - if !emailChanged && !statusChanged { - slog.DebugContext(ctx, "no email or voting status change, skipping sync", - "member_uid", event.MemberUID) - return nil - } - - // Query mailing lists ONCE (performance optimization and race condition prevention) - mailingLists, err := s.mailingListReader.GetMailingListsByCommittee(ctx, event.CommitteeUID) - if err != nil { - slog.ErrorContext(ctx, "failed to get mailing lists for committee", "error", err) - return fmt.Errorf("failed to get mailing lists for committee %s: %w", event.CommitteeUID, err) - } - - if len(mailingLists) == 0 { - slog.InfoContext(ctx, "no mailing lists found for committee (nothing to sync)", - "committee_uid", event.CommitteeUID) - return nil - } - - // Log what changed for observability - if emailChanged { - slog.InfoContext(ctx, "committee member email changed", - "member_uid", event.MemberUID, - "old_email", redaction.RedactEmail(event.OldMember.Email), - "new_email", redaction.RedactEmail(event.NewMember.Email)) - } - if statusChanged { - slog.InfoContext(ctx, "committee member voting status changed", - "member_uid", event.MemberUID, - "old_status", event.OldMember.VotingStatus, - "new_status", event.NewMember.VotingStatus) - } - - // Process each mailing list with consolidated logic - // This handles all combinations: email-only, status-only, or both changes - for _, ml := range mailingLists { - // Find the committee in the mailing list that matches the event - committeeIndex := slices.IndexFunc(ml.Committees, func(c model.Committee) bool { - return c.UID == event.CommitteeUID - }) - if committeeIndex == -1 { - // Committee not found in this mailing list, skip it - continue - } - - committee := ml.Committees[committeeIndex] - oldMatch := matchesFilter(event.OldMember.VotingStatus, committee.AllowedVotingStatuses) - newMatch := matchesFilter(event.NewMember.VotingStatus, committee.AllowedVotingStatuses) - - // Determine actions based on combined state - // Remove old member if: (1) email changed and was in list, OR (2) status changed and no longer matches - shouldRemove := oldMatch && (emailChanged || !newMatch) - // Add new member if: (1) email changed and matches filters, OR (2) status changed and now matches - shouldAdd := newMatch && (emailChanged || !oldMatch) - - if shouldRemove { - slog.InfoContext(ctx, "removing member from mailing list", - "mailing_list_uid", ml.UID, - "email", redaction.RedactEmail(event.OldMember.Email), - "reason", getRemovalReason(emailChanged, statusChanged)) - if err := s.removeMemberFromList(ctx, ml, event.OldMember.Email); err != nil { - slog.ErrorContext(ctx, "failed to remove member from list", - "error", err, - "mailing_list_uid", ml.UID) - continue // Continue with other lists even if one fails - } - } - - if shouldAdd { - slog.InfoContext(ctx, "adding member to mailing list", - "mailing_list_uid", ml.UID, - "email", redaction.RedactEmail(event.NewMember.Email), - "reason", getAdditionReason(emailChanged, statusChanged)) - if err := s.addMemberToList(ctx, ml, event.NewMember); err != nil { - slog.ErrorContext(ctx, "failed to add member to list", - "error", err, - "mailing_list_uid", ml.UID) - continue // Continue with other lists even if one fails - } - } - - // If both shouldRemove and shouldAdd are false, no change needed for this list - } - - slog.InfoContext(ctx, "committee member updated event processed successfully", - "member_uid", event.MemberUID) - - return nil -} - -// getRemovalReason returns a human-readable reason for member removal -func getRemovalReason(emailChanged, statusChanged bool) string { - if emailChanged && statusChanged { - return "email_and_status_changed" - } else if emailChanged { - return "email_changed" - } - return "status_no_longer_matches" -} - -// getAdditionReason returns a human-readable reason for member addition -func getAdditionReason(emailChanged, statusChanged bool) string { - if emailChanged && statusChanged { - return "new_email_and_status_matches" - } else if emailChanged { - return "new_email_matches_filters" - } - return "status_now_matches" -} - -// addMemberToList adds a committee member to a mailing list -func (s *CommitteeSyncService) addMemberToList(ctx context.Context, mailingList *model.GrpsIOMailingList, memberData model.CommitteeMemberEventData) error { - // Check if member already exists (idempotency) - existing, revision, err := s.memberReader.GetMemberByEmail(ctx, mailingList.UID, memberData.Email) - if err == nil && existing != nil { - slog.InfoContext(ctx, "member already exists in mailing list (idempotent)", - "mailing_list_uid", mailingList.UID, - "email", redaction.RedactEmail(memberData.Email), - "existing_member_type", existing.MemberType) - - // If existing member is "direct" type, upgrade to "committee" - if existing.MemberType == "direct" { - slog.InfoContext(ctx, "upgrading direct member to committee member", - "member_uid", existing.UID, - "email", redaction.RedactEmail(memberData.Email)) - - existing.MemberType = "committee" - existing.Source = constants.SourceCommittee - _, _, err = s.memberWriter.UpdateGrpsIOMember(ctx, existing.UID, existing, revision) - if err != nil { - return fmt.Errorf("failed to upgrade member %s from direct to committee type: %w", - existing.UID, - err) - } - - slog.InfoContext(ctx, "member upgraded from direct to committee type", - "member_uid", existing.UID, - "mailing_list_uid", mailingList.UID) - } - return nil - } - - // Check for errors other than NotFound - var notFoundErr errors.NotFound - if err != nil && !stderrors.As(err, ¬FoundErr) { - return fmt.Errorf("failed to check existing member in list %s for email %s: %w", - mailingList.UID, - redaction.RedactEmail(memberData.Email), - err) - } - - // Create new committee member - member := &model.GrpsIOMember{ - UID: uuid.New().String(), - MailingListUID: mailingList.UID, - Source: constants.SourceCommittee, // Committee sync events - Username: memberData.Username, - FirstName: memberData.FirstName, - LastName: memberData.LastName, - Email: memberData.Email, - Organization: memberData.Organization.Name, - JobTitle: memberData.JobTitle, - MemberType: "committee", // Committee members - DeliveryMode: "email", // Default delivery mode - ModStatus: "none", // Always "none" - no role mapping - Status: "normal", - } - - _, _, err = s.memberWriter.CreateGrpsIOMember(ctx, member) - if err != nil { - return fmt.Errorf("failed to create committee member in list %s (email: %s): %w", - mailingList.UID, - redaction.RedactEmail(memberData.Email), - err) - } - - slog.InfoContext(ctx, "committee member added to mailing list", - "member_uid", member.UID, - "mailing_list_uid", mailingList.UID, - "email", redaction.RedactEmail(memberData.Email)) - - return nil -} - -// removeMemberFromList removes or converts a committee member based on list visibility -func (s *CommitteeSyncService) removeMemberFromList(ctx context.Context, mailingList *model.GrpsIOMailingList, email string) error { - // Find member by email - existing, revision, err := s.memberReader.GetMemberByEmail(ctx, mailingList.UID, email) - if err != nil { - var notFoundErr errors.NotFound - if stderrors.As(err, ¬FoundErr) { - slog.InfoContext(ctx, "member not found in mailing list (idempotent)", - "mailing_list_uid", mailingList.UID, - "email", redaction.RedactEmail(email)) - return nil // Already removed - } - return fmt.Errorf("failed to look up member in list %s for email %s: %w", - mailingList.UID, - redaction.RedactEmail(email), - err) - } - - // Only process if member type is "committee" - if existing.MemberType != "committee" { - slog.InfoContext(ctx, "member is not committee type, skipping", - "member_uid", existing.UID, - "member_type", existing.MemberType) - return nil - } - - // Public lists: convert to "direct" type - // Private lists: delete member - if mailingList.Public { - slog.InfoContext(ctx, "converting committee member to direct member (public list)", - "member_uid", existing.UID, - "mailing_list_uid", mailingList.UID, - "email", redaction.RedactEmail(email)) - - existing.MemberType = "direct" - _, _, err = s.memberWriter.UpdateGrpsIOMember(ctx, existing.UID, existing, revision) - if err != nil { - return fmt.Errorf("failed to convert member %s to direct type in list %s: %w", - existing.UID, - mailingList.UID, - err) - } - } else { - slog.InfoContext(ctx, "deleting committee member (private list)", - "member_uid", existing.UID, - "mailing_list_uid", mailingList.UID, - "email", redaction.RedactEmail(email)) - - err = s.memberWriter.DeleteGrpsIOMember(ctx, existing.UID, revision, existing) - if err != nil { - return fmt.Errorf("failed to delete committee member %s from list %s: %w", - existing.UID, - mailingList.UID, - err) - } - } - - return nil -} - -// matchesFilter checks if a voting status matches any of the mailing list's committee filters -func matchesFilter(votingStatus string, filters []string) bool { - if len(filters) == 0 { - return false // No filters means no committee members - } - return slices.Contains(filters, votingStatus) -} - -// Public methods for use by MailingListSyncService and other services - -// SyncCommitteeMembersToMailingList queries committee members and adds matching ones to mailing list -// This is a public method that can be called by other services like MailingListSyncService -func (s *CommitteeSyncService) SyncCommitteeMembersToMailingList(ctx context.Context, mailingList *model.GrpsIOMailingList, committee model.Committee) error { - slog.InfoContext(ctx, "syncing committee members to mailing list", - "mailing_list_uid", mailingList.UID, - "committee_uid", committee.UID, - "committee_name", committee.Name, - "filters", committee.AllowedVotingStatuses) - - // Query committee members from committee-api - members, err := s.entityReader.ListMembers(ctx, committee.UID) - if err != nil { - slog.ErrorContext(ctx, "failed to list committee members", - "error", err, - "committee_uid", committee.UID) - return fmt.Errorf("failed to list committee members: %w", err) - } - - if len(members) == 0 { - slog.InfoContext(ctx, "committee has no members", - "committee_uid", committee.UID) - return nil - } - - // Add matching members to mailing list - addedCount := 0 - for _, member := range members { - // Check if member's voting status matches the committee's filters - if matchesFilter(member.Voting.Status, committee.AllowedVotingStatuses) { - // Convert CommitteeMember to CommitteeMemberEventData for addMemberToList - memberData := model.CommitteeMemberEventData{ - Email: member.Email, - FirstName: member.FirstName, - LastName: member.LastName, - Username: member.Username, - VotingStatus: member.Voting.Status, - Organization: model.Organization{Name: member.Organization.Name}, - JobTitle: member.JobTitle, - } - - if err := s.addMemberToList(ctx, mailingList, memberData); err != nil { - slog.ErrorContext(ctx, "failed to add member to mailing list", - "error", err, - "email", redaction.RedactEmail(member.Email)) - // Continue with other members - continue - } - addedCount++ - } - } - - slog.InfoContext(ctx, "committee members synced to mailing list", - "mailing_list_uid", mailingList.UID, - "committee_uid", committee.UID, - "total_members", len(members), - "added_count", addedCount) - - return nil -} - -// RemoveCommitteeMembersFromMailingList removes all committee members for a specific committee from the mailing list -func (s *CommitteeSyncService) RemoveCommitteeMembersFromMailingList(ctx context.Context, mailingList *model.GrpsIOMailingList, committee model.Committee) error { - slog.InfoContext(ctx, "removing committee members from mailing list", - "mailing_list_uid", mailingList.UID, - "committee_uid", committee.UID) - - // Query committee members to know which emails to remove - members, err := s.entityReader.ListMembers(ctx, committee.UID) - if err != nil { - slog.ErrorContext(ctx, "failed to list committee members for removal", - "error", err, - "committee_uid", committee.UID) - return fmt.Errorf("failed to list committee members: %w", err) - } - - removedCount := 0 - for _, member := range members { - if err := s.removeMemberFromList(ctx, mailingList, member.Email); err != nil { - slog.ErrorContext(ctx, "failed to remove member from mailing list", - "error", err, - "email", redaction.RedactEmail(member.Email)) - continue - } - removedCount++ - } - - slog.InfoContext(ctx, "committee members removed from mailing list", - "mailing_list_uid", mailingList.UID, - "committee_uid", committee.UID, - "removed_count", removedCount) - - return nil -} - -// ResyncCommitteeMembersForMailingList handles changes to committee filters by removing non-matching and adding matching members -func (s *CommitteeSyncService) ResyncCommitteeMembersForMailingList(ctx context.Context, mailingList *model.GrpsIOMailingList, oldCommittee, newCommittee model.Committee) error { - slog.InfoContext(ctx, "resyncing committee members due to filter changes", - "mailing_list_uid", mailingList.UID, - "committee_uid", newCommittee.UID, - "old_filters", oldCommittee.AllowedVotingStatuses, - "new_filters", newCommittee.AllowedVotingStatuses) - - // Query committee members - members, err := s.entityReader.ListMembers(ctx, newCommittee.UID) - if err != nil { - return fmt.Errorf("failed to list committee members: %w", err) - } - - for _, member := range members { - oldMatch := matchesFilter(member.Voting.Status, oldCommittee.AllowedVotingStatuses) - newMatch := matchesFilter(member.Voting.Status, newCommittee.AllowedVotingStatuses) - - // Member no longer matches - remove - if oldMatch && !newMatch { - if err := s.removeMemberFromList(ctx, mailingList, member.Email); err != nil { - slog.ErrorContext(ctx, "failed to remove member", - "error", err, - "email", redaction.RedactEmail(member.Email)) - continue - } - } - - // Member now matches - add - if !oldMatch && newMatch { - memberData := model.CommitteeMemberEventData{ - Email: member.Email, - FirstName: member.FirstName, - LastName: member.LastName, - Username: member.Username, - VotingStatus: member.Voting.Status, - Organization: model.Organization{Name: member.Organization.Name}, - JobTitle: member.JobTitle, - } - - if err := s.addMemberToList(ctx, mailingList, memberData); err != nil { - slog.ErrorContext(ctx, "failed to add member", - "error", err, - "email", redaction.RedactEmail(member.Email)) - continue - } - } - - // Member matched before and after - no action needed - } - - return nil -} diff --git a/internal/service/committee_sync_service_test.go b/internal/service/committee_sync_service_test.go deleted file mode 100644 index 21c4355..0000000 --- a/internal/service/committee_sync_service_test.go +++ /dev/null @@ -1,512 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "encoding/json" - "testing" - "time" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/mock" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/nats-io/nats.go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCommitteeSyncService_HandleMessage(t *testing.T) { - ctx := context.Background() - - // Setup mock repository - mockRepo := mock.NewMockRepository() - mailingListReader := mock.NewMockGrpsIOReader(mockRepo) - memberWriter := mock.NewMockGrpsIOMemberWriter(mockRepo) - memberReader := mock.NewMockGrpsIOMemberReader(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - // Create service - service := NewCommitteeSyncService(mailingListReader, memberWriter, memberReader, entityReader) - require.NotNil(t, service) - - t.Run("handles created event", func(t *testing.T) { - event := model.CommitteeMemberCreatedEvent{ - MemberUID: "member-123", - CommitteeUID: "committee-1", - ProjectUID: "project-456", - Member: model.CommitteeMemberEventData{ - Email: "test@example.com", - FirstName: "John", - LastName: "Doe", - Username: "johndoe", - VotingStatus: "Voting Rep", - Organization: model.Organization{Name: "ACME Corp"}, - JobTitle: "Engineer", - }, - Timestamp: time.Now(), - } - - data, err := json.Marshal(event) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.CommitteeMemberCreatedSubject, - Data: data, - } - - // Should not panic - service.HandleMessage(ctx, msg) - }) - - t.Run("handles deleted event", func(t *testing.T) { - event := model.CommitteeMemberDeletedEvent{ - MemberUID: "member-123", - CommitteeUID: "committee-1", - ProjectUID: "project-456", - Email: "test@example.com", - Timestamp: time.Now(), - } - - data, err := json.Marshal(event) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.CommitteeMemberDeletedSubject, - Data: data, - } - - // Should not panic - service.HandleMessage(ctx, msg) - }) - - t.Run("handles updated event", func(t *testing.T) { - event := model.CommitteeMemberUpdatedEvent{ - MemberUID: "member-123", - CommitteeUID: "committee-1", - ProjectUID: "project-456", - OldMember: model.CommitteeMemberEventData{ - Email: "old@example.com", - FirstName: "John", - LastName: "Doe", - Username: "johndoe", - VotingStatus: "Observer", - Organization: model.Organization{Name: "ACME Corp"}, - JobTitle: "Engineer", - }, - NewMember: model.CommitteeMemberEventData{ - Email: "new@example.com", - FirstName: "John", - LastName: "Doe", - Username: "johndoe", - VotingStatus: "Voting Rep", - Organization: model.Organization{Name: "ACME Corp"}, - JobTitle: "Senior Engineer", - }, - Timestamp: time.Now(), - } - - data, err := json.Marshal(event) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.CommitteeMemberUpdatedSubject, - Data: data, - } - - // Should not panic - service.HandleMessage(ctx, msg) - }) - - t.Run("handles unknown subject", func(t *testing.T) { - msg := &nats.Msg{ - Subject: "unknown.subject", - Data: []byte("{}"), - } - - // Should not panic, should log warning - service.HandleMessage(ctx, msg) - }) - - t.Run("handles invalid JSON", func(t *testing.T) { - msg := &nats.Msg{ - Subject: constants.CommitteeMemberCreatedSubject, - Data: []byte("invalid json"), - } - - // Should not panic, should log error - service.HandleMessage(ctx, msg) - }) -} - -func TestMatchesFilter(t *testing.T) { - tests := []struct { - name string - votingStatus string - filters []string - expected bool - }{ - { - name: "matches voting rep", - votingStatus: "Voting Rep", - filters: []string{"Voting Rep", "Observer"}, - expected: true, - }, - { - name: "matches observer", - votingStatus: "Observer", - filters: []string{"Voting Rep", "Observer"}, - expected: true, - }, - { - name: "does not match", - votingStatus: "Emeritus", - filters: []string{"Voting Rep", "Observer"}, - expected: false, - }, - { - name: "empty filters", - votingStatus: "Voting Rep", - filters: []string{}, - expected: false, - }, - { - name: "nil filters", - votingStatus: "Voting Rep", - filters: nil, - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := matchesFilter(tt.votingStatus, tt.filters) - assert.Equal(t, tt.expected, result) - }) - } -} - -func TestCommitteeSyncService_AddMemberToList(t *testing.T) { - ctx := context.Background() - - // Setup mock repository - mockRepo := mock.NewMockRepository() - mailingListReader := mock.NewMockGrpsIOReader(mockRepo) - memberWriter := mock.NewMockGrpsIOMemberWriter(mockRepo) - memberReader := mock.NewMockGrpsIOMemberReader(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - service := NewCommitteeSyncService(mailingListReader, memberWriter, memberReader, entityReader) - - mailingList := &model.GrpsIOMailingList{ - UID: "list-123", - GroupName: "dev", - Public: true, - } - - memberData := model.CommitteeMemberEventData{ - Email: "newmember@example.com", - FirstName: "Jane", - LastName: "Smith", - Username: "janesmith", - VotingStatus: "Voting Rep", - Organization: model.Organization{Name: "ACME Corp"}, - JobTitle: "Developer", - } - - t.Run("adds new member successfully", func(t *testing.T) { - err := service.addMemberToList(ctx, mailingList, memberData) - assert.NoError(t, err) - }) - - t.Run("handles existing member (idempotent)", func(t *testing.T) { - // Add member twice - should be idempotent - err := service.addMemberToList(ctx, mailingList, memberData) - assert.NoError(t, err) - - err = service.addMemberToList(ctx, mailingList, memberData) - assert.NoError(t, err) - }) -} - -func TestCommitteeSyncService_RemoveMemberFromList(t *testing.T) { - ctx := context.Background() - - // Setup mock repository - mockRepo := mock.NewMockRepository() - mailingListReader := mock.NewMockGrpsIOReader(mockRepo) - memberWriter := mock.NewMockGrpsIOMemberWriter(mockRepo) - memberReader := mock.NewMockGrpsIOMemberReader(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - service := NewCommitteeSyncService(mailingListReader, memberWriter, memberReader, entityReader) - - t.Run("handles member not found (idempotent)", func(t *testing.T) { - mailingList := &model.GrpsIOMailingList{ - UID: "list-123", - Public: true, - } - - err := service.removeMemberFromList(ctx, mailingList, "nonexistent@example.com") - assert.NoError(t, err) // Should be idempotent - }) -} - -// TestCommitteeSyncService_IntegrationWithMailingLists tests the full flow with actual mailing lists -func TestCommitteeSyncService_IntegrationWithMailingLists(t *testing.T) { - ctx := context.Background() - - t.Run("creates member when voting status matches filters", func(t *testing.T) { - // Setup with pre-populated mailing list - mockRepo := mock.NewMockRepository() - mockRepo.ClearAll() - - mailingList := &model.GrpsIOMailingList{ - UID: "test-list-1", - GroupName: "test-dev", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-123", - Name: "Test Committee", - AllowedVotingStatuses: []string{"Voting Rep", "Observer"}, - }, - }, - Public: true, - } - mockRepo.AddMailingList(mailingList) - - mailingListReader := mock.NewMockGrpsIOReader(mockRepo) - memberWriter := mock.NewMockGrpsIOMemberWriter(mockRepo) - memberReader := mock.NewMockGrpsIOMemberReader(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - service := NewCommitteeSyncService(mailingListReader, memberWriter, memberReader, entityReader) - - event := model.CommitteeMemberCreatedEvent{ - MemberUID: "member-123", - CommitteeUID: "committee-123", - ProjectUID: "project-456", - Member: model.CommitteeMemberEventData{ - Email: "voter@example.com", - FirstName: "John", - LastName: "Doe", - Username: "johndoe", - VotingStatus: "Voting Rep", - Organization: model.Organization{Name: "ACME"}, - JobTitle: "Engineer", - }, - } - - data, err := json.Marshal(event) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.CommitteeMemberCreatedSubject, - Data: data, - } - - service.HandleMessage(ctx, msg) - - // Verify member was created - members := mockRepo.GetMembersForMailingList("test-list-1") - assert.Equal(t, 1, len(members)) - assert.Equal(t, "voter@example.com", members[0].Email) - assert.Equal(t, "committee", members[0].MemberType) - assert.Equal(t, "none", members[0].ModStatus) - }) - - t.Run("does not create member when voting status does not match", func(t *testing.T) { - // Setup with pre-populated mailing list - mockRepo := mock.NewMockRepository() - mockRepo.ClearAll() - - mailingList := &model.GrpsIOMailingList{ - UID: "test-list-2", - GroupName: "test-voting-only", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-456", - Name: "Voting Committee", - AllowedVotingStatuses: []string{"Voting Rep"}, // Only voting reps - }, - }, - Public: true, - } - mockRepo.AddMailingList(mailingList) - - mailingListReader := mock.NewMockGrpsIOReader(mockRepo) - memberWriter := mock.NewMockGrpsIOMemberWriter(mockRepo) - memberReader := mock.NewMockGrpsIOMemberReader(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - service := NewCommitteeSyncService(mailingListReader, memberWriter, memberReader, entityReader) - - event := model.CommitteeMemberCreatedEvent{ - MemberUID: "member-456", - CommitteeUID: "committee-456", - ProjectUID: "project-456", - Member: model.CommitteeMemberEventData{ - Email: "emeritus@example.com", - VotingStatus: "Emeritus", // Not in filters - }, - } - - data, err := json.Marshal(event) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.CommitteeMemberCreatedSubject, - Data: data, - } - - service.HandleMessage(ctx, msg) - - // Verify member was NOT created - members := mockRepo.GetMembersForMailingList("test-list-2") - assert.Equal(t, 0, len(members)) - }) - - t.Run("deletes member from private list, converts on public list", func(t *testing.T) { - // Setup with both public and private lists - mockRepo := mock.NewMockRepository() - mockRepo.ClearAll() - - publicList := &model.GrpsIOMailingList{ - UID: "public-list", - GroupName: "public-dev", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-789", - Name: "Public Committee", - AllowedVotingStatuses: []string{"Voting Rep"}, - }, - }, - Public: true, - } - mockRepo.AddMailingList(publicList) - - privateList := &model.GrpsIOMailingList{ - UID: "private-list", - GroupName: "private-dev", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-789", - Name: "Private Committee", - AllowedVotingStatuses: []string{"Voting Rep"}, - }, - }, - Public: false, - } - mockRepo.AddMailingList(privateList) - - mailingListReader := mock.NewMockGrpsIOReader(mockRepo) - memberWriter := mock.NewMockGrpsIOMemberWriter(mockRepo) - memberReader := mock.NewMockGrpsIOMemberReader(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - service := NewCommitteeSyncService(mailingListReader, memberWriter, memberReader, entityReader) - - // First create a member in both lists - memberData := model.CommitteeMemberEventData{ - Email: "member@example.com", - FirstName: "Test", - LastName: "User", - VotingStatus: "Voting Rep", - } - service.addMemberToList(ctx, publicList, memberData) - service.addMemberToList(ctx, privateList, memberData) - - // Verify members created - assert.Equal(t, 1, len(mockRepo.GetMembersForMailingList("public-list"))) - assert.Equal(t, 1, len(mockRepo.GetMembersForMailingList("private-list"))) - - // Now delete the member - deleteEvent := model.CommitteeMemberDeletedEvent{ - MemberUID: "member-123", - CommitteeUID: "committee-789", - ProjectUID: "project-456", - Email: "member@example.com", - } - - data, err := json.Marshal(deleteEvent) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.CommitteeMemberDeletedSubject, - Data: data, - } - - service.HandleMessage(ctx, msg) - - // Verify public list member still exists but converted to "direct" - publicMembers := mockRepo.GetMembersForMailingList("public-list") - assert.Equal(t, 1, len(publicMembers)) - assert.Equal(t, "direct", publicMembers[0].MemberType) - - // Verify private list member was deleted - privateMembers := mockRepo.GetMembersForMailingList("private-list") - assert.Equal(t, 0, len(privateMembers)) - }) - - t.Run("handles voting status change - adds and removes appropriately", func(t *testing.T) { - mockRepo := mock.NewMockRepository() - mockRepo.ClearAll() - - mailingList := &model.GrpsIOMailingList{ - UID: "test-list-3", - GroupName: "test-voting", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-999", - Name: "Test Committee", - AllowedVotingStatuses: []string{"Voting Rep"}, // Only voting reps - }, - }, - Public: true, - } - mockRepo.AddMailingList(mailingList) - - mailingListReader := mock.NewMockGrpsIOReader(mockRepo) - memberWriter := mock.NewMockGrpsIOMemberWriter(mockRepo) - memberReader := mock.NewMockGrpsIOMemberReader(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - service := NewCommitteeSyncService(mailingListReader, memberWriter, memberReader, entityReader) - - // Update event: Observer -> Voting Rep (should add) - updateEvent := model.CommitteeMemberUpdatedEvent{ - MemberUID: "member-789", - CommitteeUID: "committee-999", - ProjectUID: "project-456", - OldMember: model.CommitteeMemberEventData{ - Email: "user@example.com", - VotingStatus: "Observer", // Not in filters - }, - NewMember: model.CommitteeMemberEventData{ - Email: "user@example.com", - VotingStatus: "Voting Rep", // Now matches - }, - } - - data, err := json.Marshal(updateEvent) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.CommitteeMemberUpdatedSubject, - Data: data, - } - - service.HandleMessage(ctx, msg) - - // Verify member was added - members := mockRepo.GetMembersForMailingList("test-list-3") - assert.Equal(t, 1, len(members)) - assert.Equal(t, "user@example.com", members[0].Email) - }) -} diff --git a/internal/service/grpsio_mailing_list_reader.go b/internal/service/grpsio_mailing_list_reader.go deleted file mode 100644 index d11de63..0000000 --- a/internal/service/grpsio_mailing_list_reader.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "log/slog" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" -) - -// GetGrpsIOMailingList retrieves a single mailing list by UID with revision -func (mlr *grpsIOReaderOrchestrator) GetGrpsIOMailingList(ctx context.Context, uid string) (*model.GrpsIOMailingList, uint64, error) { - slog.DebugContext(ctx, "executing get mailing list use case", - "mailing_list_uid", uid, - ) - - // Get mailing list from storage - mailingList, revision, err := mlr.grpsIOReader.GetGrpsIOMailingList(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to get mailing list", - "error", err, - "mailing_list_uid", uid, - ) - return nil, 0, err - } - - slog.DebugContext(ctx, "mailing list retrieved successfully", - "mailing_list_uid", uid, - "group_name", mailingList.GroupName, - "revision", revision, - ) - - return mailingList, revision, nil -} - -// GetMailingListRevision retrieves only the revision for a given UID -func (mlr *grpsIOReaderOrchestrator) GetMailingListRevision(ctx context.Context, uid string) (uint64, error) { - slog.DebugContext(ctx, "executing get mailing list revision use case", - "mailing_list_uid", uid, - ) - - // Get revision from storage - revision, err := mlr.grpsIOReader.GetMailingListRevision(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to get mailing list revision", - "error", err, - "mailing_list_uid", uid, - ) - return 0, err - } - - slog.DebugContext(ctx, "mailing list revision retrieved successfully", - "mailing_list_uid", uid, - "revision", revision, - ) - - return revision, nil -} - -// GetMailingListByGroupID retrieves a mailing list by GroupsIO subgroup ID -func (mlr *grpsIOReaderOrchestrator) GetMailingListByGroupID(ctx context.Context, groupID uint64) (*model.GrpsIOMailingList, uint64, error) { - slog.DebugContext(ctx, "executing get mailing list by group_id use case", - "group_id", groupID, - ) - - // Get mailing list from storage - mailingList, revision, err := mlr.grpsIOReader.GetMailingListByGroupID(ctx, groupID) - if err != nil { - slog.ErrorContext(ctx, "failed to get mailing list by group_id", - "error", err, - "group_id", groupID, - ) - return nil, 0, err - } - - slog.DebugContext(ctx, "mailing list retrieved successfully by group_id", - "mailing_list_uid", mailingList.UID, - "group_name", mailingList.GroupName, - "group_id", groupID, - "revision", revision, - ) - - return mailingList, revision, nil -} - -// GetGrpsIOMailingListSettings retrieves mailing list settings by UID with revision -func (mlr *grpsIOReaderOrchestrator) GetGrpsIOMailingListSettings(ctx context.Context, uid string) (*model.GrpsIOMailingListSettings, uint64, error) { - slog.DebugContext(ctx, "executing get mailing list settings use case", - "mailing_list_uid", uid, - ) - - // Get settings from storage - settings, revision, err := mlr.grpsIOReader.GetGrpsIOMailingListSettings(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to get mailing list settings", - "error", err, - "mailing_list_uid", uid, - ) - return nil, 0, err - } - - slog.DebugContext(ctx, "mailing list settings retrieved successfully", - "mailing_list_uid", uid, - "revision", revision, - ) - - return settings, revision, nil -} - -// GetMailingListSettingsRevision retrieves only the revision for mailing list settings -func (mlr *grpsIOReaderOrchestrator) GetMailingListSettingsRevision(ctx context.Context, uid string) (uint64, error) { - slog.DebugContext(ctx, "executing get mailing list settings revision use case", - "mailing_list_uid", uid, - ) - - // Get revision from storage - revision, err := mlr.grpsIOReader.GetMailingListSettingsRevision(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to get mailing list settings revision", - "error", err, - "mailing_list_uid", uid, - ) - return 0, err - } - - slog.DebugContext(ctx, "mailing list settings revision retrieved successfully", - "mailing_list_uid", uid, - "revision", revision, - ) - - return revision, nil -} diff --git a/internal/service/grpsio_mailing_list_reader_test.go b/internal/service/grpsio_mailing_list_reader_test.go deleted file mode 100644 index 984c091..0000000 --- a/internal/service/grpsio_mailing_list_reader_test.go +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "testing" - "time" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/mock" - errs "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" -) - -func TestGrpsIOReaderOrchestratorGetGrpsIOMailingList(t *testing.T) { - ctx := context.Background() - mockRepo := mock.NewMockRepository() - - // Setup test data - testMailingListUID := uuid.New().String() - testMailingList := &model.GrpsIOMailingList{ - UID: testMailingListUID, - GroupName: "dev", - Public: true, - Type: "discussion_open", - Committees: []model.Committee{ - {UID: "committee-1", Name: "Technical Advisory Committee", AllowedVotingStatuses: []string{"Voting Rep", "Observer"}}, - }, - Description: "Development discussions and technical matters for the project", - Title: "Development List", - SubjectTag: "[DEV]", - ServiceUID: "service-1", - ProjectUID: "test-project-uid", - ProjectName: "Test Project", - ProjectSlug: "test-project", - CreatedAt: time.Now().Add(-18 * time.Hour), - UpdatedAt: time.Now().Add(-2 * time.Hour), - } - - tests := []struct { - name string - setupMock func() - mailingListUID string - expectedError bool - errorType error - validateMailingList func(*testing.T, *model.GrpsIOMailingList) - }{ - { - name: "successful mailing list retrieval", - setupMock: func() { - mockRepo.ClearAll() - // Store the mailing list in mock repository - mockRepo.AddMailingList(testMailingList) - }, - mailingListUID: testMailingListUID, - expectedError: false, - validateMailingList: func(t *testing.T, mailingList *model.GrpsIOMailingList) { - assert.NotNil(t, mailingList) - assert.Equal(t, testMailingListUID, mailingList.UID) - assert.Equal(t, "dev", mailingList.GroupName) - assert.True(t, mailingList.Public) - assert.Equal(t, "discussion_open", mailingList.Type) - require.Len(t, mailingList.Committees, 1) - assert.Equal(t, "committee-1", mailingList.Committees[0].UID) - assert.Equal(t, "Technical Advisory Committee", mailingList.Committees[0].Name) - assert.Equal(t, []string{"Voting Rep", "Observer"}, mailingList.Committees[0].AllowedVotingStatuses) - assert.Equal(t, "Development discussions and technical matters for the project", mailingList.Description) - assert.Equal(t, "Development List", mailingList.Title) - assert.Equal(t, "[DEV]", mailingList.SubjectTag) - assert.Equal(t, "service-1", mailingList.ServiceUID) - assert.Equal(t, "test-project-uid", mailingList.ProjectUID) - assert.Equal(t, "Test Project", mailingList.ProjectName) - assert.Equal(t, "test-project", mailingList.ProjectSlug) - assert.NotZero(t, mailingList.CreatedAt) - assert.NotZero(t, mailingList.UpdatedAt) - }, - }, - { - name: "mailing list not found error", - setupMock: func() { - mockRepo.ClearAll() - // Don't store any mailing list - }, - mailingListUID: "nonexistent-mailing-list-uid", - expectedError: true, - errorType: errs.NotFound{}, - validateMailingList: func(t *testing.T, mailingList *model.GrpsIOMailingList) { - assert.Nil(t, mailingList) - }, - }, - { - name: "empty mailing list UID", - setupMock: func() { - mockRepo.ClearAll() - }, - mailingListUID: "", - expectedError: true, - errorType: errs.NotFound{}, - validateMailingList: func(t *testing.T, mailingList *model.GrpsIOMailingList) { - assert.Nil(t, mailingList) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Setup - tt.setupMock() - - // Create reader orchestrator - reader := NewGrpsIOReaderOrchestrator( - WithGrpsIOReader(mock.NewMockGrpsIOReader(mockRepo)), - ) - - // Execute - mailingList, _, err := reader.GetGrpsIOMailingList(ctx, tt.mailingListUID) - - // Validate - if tt.expectedError { - require.Error(t, err) - if tt.errorType != nil { - assert.IsType(t, tt.errorType, err) - } - } else { - require.NoError(t, err) - } - - tt.validateMailingList(t, mailingList) - }) - } -} - -func TestGrpsIOReaderOrchestratorMailingListIntegration(t *testing.T) { - ctx := context.Background() - mockRepo := mock.NewMockRepository() - mockRepo.ClearAll() - - // Setup comprehensive test data - projectUID := "integration-test-project-uid" - parentServiceUID := "integration-service-1" - committeeUID := "integration-committee-1" - - testMailingLists := []*model.GrpsIOMailingList{ - { - UID: "integration-mailing-list-1", - GroupName: "integration-dev", - Public: true, - Type: "discussion_open", - Committees: []model.Committee{ - {UID: committeeUID, Name: "Integration Technical Committee", AllowedVotingStatuses: []string{"Voting Rep", "Observer"}}, - }, - Description: "Integration development discussions and technical matters", - Title: "Integration Development List", - SubjectTag: "[INTEGRATION-DEV]", - ServiceUID: parentServiceUID, - ProjectUID: projectUID, - ProjectName: "Integration Test Project", - ProjectSlug: "integration-test-project", - CreatedAt: time.Now().Add(-48 * time.Hour), - UpdatedAt: time.Now().Add(-3 * time.Hour), - }, - { - UID: "integration-mailing-list-2", - GroupName: "integration-announce", - Public: true, - Type: "announcement", - Description: "Integration project announcements and important updates", - Title: "Integration Announcements", - SubjectTag: "[INTEGRATION-ANNOUNCE]", - ServiceUID: parentServiceUID, - ProjectUID: projectUID, - ProjectName: "Integration Test Project", - ProjectSlug: "integration-test-project", - CreatedAt: time.Now().Add(-36 * time.Hour), - UpdatedAt: time.Now().Add(-1 * time.Hour), - }, - } - - // Store the mailing lists - for _, ml := range testMailingLists { - mockRepo.AddMailingList(ml) - } - - // Create reader orchestrator - reader := NewGrpsIOReaderOrchestrator( - WithGrpsIOReader(mock.NewMockGrpsIOReader(mockRepo)), - ) - - t.Run("comprehensive mailing list operations", func(t *testing.T) { - // Test individual mailing list retrieval - ml1, _, err := reader.GetGrpsIOMailingList(ctx, "integration-mailing-list-1") - require.NoError(t, err) - require.NotNil(t, ml1) - assert.Equal(t, "integration-dev", ml1.GroupName) - assert.True(t, ml1.IsCommitteeBased()) - - ml2, _, err := reader.GetGrpsIOMailingList(ctx, "integration-mailing-list-2") - require.NoError(t, err) - require.NotNil(t, ml2) - assert.Equal(t, "integration-announce", ml2.GroupName) - assert.False(t, ml2.IsCommitteeBased()) // No committee fields set - - // Validate complete data integrity - assert.Equal(t, parentServiceUID, ml1.ServiceUID) - assert.Equal(t, parentServiceUID, ml2.ServiceUID) - assert.Equal(t, projectUID, ml1.ProjectUID) - assert.Equal(t, projectUID, ml2.ProjectUID) - - // Validate committee-based mailing list - require.Len(t, ml1.Committees, 1) - assert.Equal(t, committeeUID, ml1.Committees[0].UID) - assert.Equal(t, "Integration Technical Committee", ml1.Committees[0].Name) - assert.Equal(t, []string{"Voting Rep", "Observer"}, ml1.Committees[0].AllowedVotingStatuses) - - // Validate project details - assert.Equal(t, "Integration Test Project", ml1.ProjectName) - assert.Equal(t, "integration-test-project", ml1.ProjectSlug) - assert.Equal(t, "Integration Test Project", ml2.ProjectName) - assert.Equal(t, "integration-test-project", ml2.ProjectSlug) - }) -} - -// Helper function to create string pointer for mailing list tests -func mailingListStringPtr(s string) *string { - return &s -} diff --git a/internal/service/grpsio_mailing_list_writer.go b/internal/service/grpsio_mailing_list_writer.go deleted file mode 100644 index a68b988..0000000 --- a/internal/service/grpsio_mailing_list_writer.go +++ /dev/null @@ -1,1090 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - stdErrors "errors" - "fmt" - "log/slog" - "time" - - "github.com/google/uuid" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/groupsio" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/log" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/utils" -) - -// ensureMailingListIdempotent checks if mailing list with SubgroupID already exists -// Returns existing entity if found, nil if not found, error on failure -// This provides early-exit idempotency for all sources (API retries, webhooks, etc.) -func (ml *grpsIOWriterOrchestrator) ensureMailingListIdempotent( - ctx context.Context, - request *model.GrpsIOMailingList, -) (*model.GrpsIOMailingList, uint64, error) { - // Only check if SubgroupID is provided (webhook or API retry after Groups.io creation) - if request.GroupID == nil { - return nil, 0, nil // No SubgroupID, proceed with normal creation - } - - subgroupID := uint64(*request.GroupID) - - slog.DebugContext(ctx, "checking idempotency by subgroup_id", - "subgroup_id", subgroupID, - "source", request.Source) - - // Check secondary index for existing record - existing, revision, err := ml.grpsIOReader.GetMailingListByGroupID(ctx, subgroupID) - if err != nil { - // Use helper to handle idempotency lookup errors consistently - shouldContinue, handledErr := handleIdempotencyLookupError(ctx, err, "subgroup_id", fmt.Sprintf("%d", subgroupID)) - if !shouldContinue { - return nil, 0, handledErr - } - // NotFound - proceed with normal creation - slog.DebugContext(ctx, "no existing mailing list found by subgroup_id, proceeding with creation", - "subgroup_id", subgroupID) - return nil, 0, nil - } - - if existing != nil { - // Found existing record - idempotent success - slog.InfoContext(ctx, "mailing list already exists, returning existing record (idempotent)", - "mailing_list_uid", existing.UID, - "subgroup_id", subgroupID, - "existing_source", existing.Source, - "request_source", request.Source) - return existing, revision, nil - } - - return nil, 0, nil -} - -// CreateGrpsIOMailingList creates a new mailing list with comprehensive validation and messaging -// The settings parameter contains writers and auditors to be stored separately -func (ml *grpsIOWriterOrchestrator) CreateGrpsIOMailingList(ctx context.Context, request *model.GrpsIOMailingList, settings *model.GrpsIOMailingListSettings) (*model.GrpsIOMailingList, uint64, error) { - slog.DebugContext(ctx, "orchestrator: creating mailing list", - "group_name", request.GroupName, - "parent_uid", request.ServiceUID, - "committees_count", len(request.Committees), - "source", request.Source, - "group_id", request.GroupID) - - // LAYER 1: Early idempotency check (prevents wasted work) - if existing, revision, err := ml.ensureMailingListIdempotent(ctx, request); err != nil { - return nil, 0, err - } else if existing != nil { - return existing, revision, nil // Already exists - idempotent success - } - - // For rollback purposes - var ( - keys []string - rollbackRequired bool - rollbackSubgroupID *int64 - rollbackGroupsIODomain string - ) - defer func() { - if err := recover(); err != nil || rollbackRequired { - ml.deleteKeys(ctx, keys, true) - - // Clean up Groups.io subgroup ONLY if we created it (not webhook) - if rollbackSubgroupID != nil && - ml.groupsClient != nil && - request.Source == constants.SourceAPI { - if deleteErr := ml.groupsClient.DeleteSubgroup(ctx, rollbackGroupsIODomain, - utils.Int64PtrToUint64(rollbackSubgroupID)); deleteErr != nil { - slog.WarnContext(ctx, "failed to cleanup GroupsIO subgroup during rollback", - "error", deleteErr, "subgroup_id", *rollbackSubgroupID) - } - } - } - }() - - // Step 1: Generate UID and set timestamps - request.UID = uuid.New().String() - now := time.Now() - request.CreatedAt = now - request.UpdatedAt = now - - // Step 3: Validate basic fields - if err := request.ValidateBasicFields(); err != nil { - slog.WarnContext(ctx, "basic field validation failed", "error", err) - return nil, 0, err - } - - // Step 4: Validate committee fields - if err := request.ValidateCommitteeFields(); err != nil { - slog.WarnContext(ctx, "committee field validation failed", "error", err) - return nil, 0, err - } - - // Step 5: Validate parent service and inherit metadata - parentService, err := ml.validateAndInheritFromParent(ctx, request) - if err != nil { - return nil, 0, err - } - - // Step 6: Validate committees and populate metadata (if specified) - if err := ml.validateAndPopulateCommittees(ctx, request); err != nil { - return nil, 0, err - } - - // Step 7: Validate group name prefix for non-primary services - if err := request.ValidateGroupNamePrefix(parentService.Type, parentService.Prefix); err != nil { - slog.WarnContext(ctx, "group name prefix validation failed", "error", err) - return nil, 0, err - } - - // Step 8: Reserve unique constraints (LAYER 3: Catches duplicates by name) - constraintKey, err := ml.reserveMailingListConstraints(ctx, request) - if err != nil { - // LAYER 3.1: Graceful conflict handling for webhook race condition - var conflictErr errors.Conflict - if request.Source == constants.SourceWebhook && stdErrors.As(err, &conflictErr) { - // Webhook arrived while API was in-flight - // Check if existing record has same SubgroupID - if existing, revision, checkErr := ml.ensureMailingListIdempotent(ctx, request); checkErr == nil && existing != nil { - slog.InfoContext(ctx, "constraint conflict resolved - returning existing record (race condition)", - "mailing_list_uid", existing.UID, - "group_id", log.LogOptionalInt64(request.GroupID)) - return existing, revision, nil - } - } - // Genuine conflict or other error - rollbackRequired = true - return nil, 0, err - } - if constraintKey != "" { - keys = append(keys, constraintKey) - } - - // LAYER 2: Validate source - if err := constants.ValidateSource(request.Source); err != nil { - return nil, 0, err - } - - // LAYER 3: Source-based strategy dispatch for SubgroupID resolution - var ( - subgroupID *int64 - subgroupResult *groupsio.SubgroupObject - requiresCleanup bool - ) - - switch request.Source { - case constants.SourceAPI: - subgroupResult, requiresCleanup, err = ml.handleAPISourceMailingList(ctx, request, parentService) - if err != nil { - rollbackRequired = true - return nil, 0, err - } - if subgroupResult != nil { - id := int64(subgroupResult.ID) - subgroupID = &id - // Set subscriber count from Groups.io creation response - request.SubscriberCount = int(subgroupResult.SubsCount) - slog.InfoContext(ctx, "subscriber count retrieved from Groups.io creation", - "mailing_list_uid", request.UID, - "subscriber_count", request.SubscriberCount) - } - if requiresCleanup { - rollbackSubgroupID = subgroupID - rollbackGroupsIODomain = parentService.Domain - } - - case constants.SourceWebhook: - subgroupID, err = ml.handleWebhookSourceMailingList(ctx, request) - if err != nil { - return nil, 0, err - } - // For webhook source, count from NATS - count, countErr := ml.grpsIOReader.CountMembersInMailingList(ctx, request.UID) - if countErr != nil { - slog.WarnContext(ctx, "failed to count members for webhook source, defaulting to 0", - "error", countErr, "mailing_list_uid", request.UID) - request.SubscriberCount = 0 - } else { - request.SubscriberCount = count - } - - case constants.SourceMock: - subgroupID = ml.handleMockSourceMailingList(ctx, request) - // For mock source, count from NATS - count, countErr := ml.grpsIOReader.CountMembersInMailingList(ctx, request.UID) - if countErr != nil { - slog.WarnContext(ctx, "failed to count members for mock source, defaulting to 0", - "error", countErr, "mailing_list_uid", request.UID) - request.SubscriberCount = 0 - } else { - request.SubscriberCount = count - } - } - - // Set SubgroupID from strategy result - request.GroupID = subgroupID - - // Step 10: Create mailing list in storage (with Groups.io ID and subscriber count already set) - createdMailingList, revision, err := ml.grpsIOWriter.CreateGrpsIOMailingList(ctx, request) - if err != nil { - slog.ErrorContext(ctx, "failed to create mailing list in storage", "error", err) - rollbackRequired = true - return nil, 0, err - } - keys = append(keys, createdMailingList.UID) - - // Step 11: Create mailing list settings with provided writers/auditors - if settings == nil { - // Initialize empty settings if none provided (webhook/mock sources) - settings = &model.GrpsIOMailingListSettings{ - Writers: []model.UserInfo{}, - Auditors: []model.UserInfo{}, - } - } - settings.UID = createdMailingList.UID - settings.CreatedAt = now - settings.UpdatedAt = now - - _, _, err = ml.grpsIOWriter.CreateGrpsIOMailingListSettings(ctx, settings) - if err != nil { - slog.ErrorContext(ctx, "failed to create mailing list settings", - "error", err, - "mailing_list_uid", createdMailingList.UID, - ) - rollbackRequired = true - return nil, 0, err - } - keys = append(keys, createdMailingList.UID) // Settings use same UID as key - - slog.DebugContext(ctx, "mailing list settings created successfully", - "mailing_list_uid", createdMailingList.UID, - "writers_count", len(settings.Writers), - "auditors_count", len(settings.Auditors), - ) - - // Step 12: Create secondary indices for the mailing list - secondaryKeys, err := ml.createMailingListSecondaryIndices(ctx, createdMailingList) - if err != nil { - slog.ErrorContext(ctx, "failed to create mailing list secondary indices", "error", err) - rollbackRequired = true - return nil, 0, err - } - - // Add secondary keys to rollback list - keys = append(keys, secondaryKeys...) - - // Step 12: Publish messages concurrently (indexer + access control) - if err := ml.publishMailingListMessages(ctx, createdMailingList, settings); err != nil { - // Log warning but don't fail the operation - mailing list is already created - slog.WarnContext(ctx, "failed to publish messages", "error", err, "mailing_list_uid", createdMailingList.UID) - } - - slog.InfoContext(ctx, "mailing list created successfully", - "mailing_list_uid", createdMailingList.UID, - "group_name", createdMailingList.GroupName, - "source", createdMailingList.Source, - "group_id", log.LogOptionalInt64(createdMailingList.GroupID), - "parent_uid", createdMailingList.ServiceUID, - "public", createdMailingList.Public, - "committee_based", createdMailingList.IsCommitteeBased()) - - return createdMailingList, revision, nil -} - -// audienceAccessToGroupsIO converts audience_access enum to Groups.io restricted/invite_only flags -func audienceAccessToGroupsIO(audienceAccess string) (restricted, inviteOnly *bool) { - falseVal := false - trueVal := true - - switch audienceAccess { - case model.AudienceAccessApprovalRequired: - // Users must request to join and be approved by moderator - return &trueVal, &falseVal - case model.AudienceAccessInviteOnly: - // Only invited users can join - return &falseVal, &trueVal - default: // public - // Anyone can join - return &falseVal, &falseVal - } -} - -// createMailingListInGroupsIO handles Groups.io subgroup creation and returns the SubgroupObject -func (ml *grpsIOWriterOrchestrator) createMailingListInGroupsIO(ctx context.Context, mailingList *model.GrpsIOMailingList, parentService *model.GrpsIOService) (*groupsio.SubgroupObject, error) { - if ml.groupsClient == nil || parentService.GroupID == nil { - slog.WarnContext(ctx, "Groups.io integration disabled or parent group ID not found, skipping Groups.io creation") - return nil, nil // Skip Groups.io creation - } - - slog.InfoContext(ctx, "creating subgroup in Groups.io", - "domain", parentService.Domain, - "parent_group_id", *parentService.GroupID, - "subgroup_name", mailingList.GroupName, - "audience_access", mailingList.AudienceAccess, - ) - - // Convert audience_access to Groups.io fields - restricted, inviteOnly := audienceAccessToGroupsIO(mailingList.AudienceAccess) - - subgroupOptions := groupsio.SubgroupCreateOptions{ - ParentGroupID: utils.Int64PtrToUint64(parentService.GroupID), // Production field - GroupName: mailingList.GroupName, // Fixed: was SubgroupName - Desc: fmt.Sprintf("Mailing list for %s - %s", parentService.ProjectName, mailingList.GroupName), // Fixed: was Description - Restricted: restricted, // Audience access: approval_required - InviteOnly: inviteOnly, // Audience access: invite_only - // Privacy: leave empty to inherit from parent group (production pattern) - } - - subgroupResult, err := ml.groupsClient.CreateSubgroup( - ctx, - parentService.Domain, - utils.Int64PtrToUint64(parentService.GroupID), - subgroupOptions, - ) - if err != nil { - slog.ErrorContext(ctx, "Groups.io subgroup creation failed", - "error", err, - "domain", parentService.Domain, - "parent_group_id", *parentService.GroupID, - "subgroup_name", mailingList.GroupName, - ) - return nil, fmt.Errorf("groups.io subgroup creation failed: %w", err) - } - - slog.InfoContext(ctx, "Groups.io subgroup created successfully", - "subgroup_id", subgroupResult.ID, - "subscriber_count", subgroupResult.SubsCount, - "domain", parentService.Domain, - "parent_group_id", *parentService.GroupID, - ) - - return subgroupResult, nil -} - -// validateAndInheritFromParent validates parent service exists and inherits metadata -func (ml *grpsIOWriterOrchestrator) validateAndInheritFromParent(ctx context.Context, request *model.GrpsIOMailingList) (*model.GrpsIOService, error) { - slog.DebugContext(ctx, "validating parent service", "parent_uid", request.ServiceUID) - - // Get parent service from storage - parentService, _, err := ml.grpsIOReader.GetGrpsIOService(ctx, request.ServiceUID) - if err != nil { - slog.WarnContext(ctx, "parent service validation failed", "parent_uid", request.ServiceUID, "error", err) - return nil, errors.NewNotFound("parent service not found") - } - - // Inherit project metadata from parent service - request.ProjectUID = parentService.ProjectUID - request.ProjectName = parentService.ProjectName - request.ProjectSlug = parentService.ProjectSlug - - slog.DebugContext(ctx, "parent service validated successfully", - "parent_uid", request.ServiceUID, - "parent_type", parentService.Type, - "project_uid", parentService.ProjectUID, - "project_name", parentService.ProjectName, - "project_slug", parentService.ProjectSlug, - "prefix", parentService.Prefix) - - return parentService, nil -} - -// validateAndPopulateCommittees validates all committees exist and populates committee names -func (ml *grpsIOWriterOrchestrator) validateAndPopulateCommittees(ctx context.Context, request *model.GrpsIOMailingList) error { - if len(request.Committees) == 0 { - // No committees specified, validation not needed - return nil - } - - slog.DebugContext(ctx, "validating and populating committees", - "committees_count", len(request.Committees)) - - // Validate each committee and populate its name - for i, committee := range request.Committees { - if committee.UID == "" { - continue - } - - // Get committee name to validate it exists and populate metadata - committeeName, err := ml.entityReader.CommitteeName(ctx, committee.UID) - if err != nil { - slog.WarnContext(ctx, "committee validation failed", - "committee_uid", committee.UID, - "error", err) - return errors.NewNotFound(fmt.Sprintf("committee %s not found", committee.UID)) - } - - // Populate committee name - request.Committees[i].Name = committeeName - - slog.DebugContext(ctx, "committee validated and populated successfully", - "committee_uid", committee.UID, - "committee_name", committeeName) - } - - return nil -} - -// reserveMailingListConstraints reserves unique constraints for mailing list creation -func (ml *grpsIOWriterOrchestrator) reserveMailingListConstraints(ctx context.Context, mailingList *model.GrpsIOMailingList) (string, error) { - // For mailing lists, we have one constraint type: unique group name within parent service - return ml.grpsIOWriter.UniqueMailingListGroupName(ctx, mailingList) -} - -// publishMailingListMessages publishes indexer and access control messages for mailing list creation -func (ml *grpsIOWriterOrchestrator) publishMailingListMessages(ctx context.Context, mailingList *model.GrpsIOMailingList, settings *model.GrpsIOMailingListSettings) error { - if ml.publisher == nil { - slog.DebugContext(ctx, "publisher not configured, skipping message publishing", - "mailing_list_uid", mailingList.UID) - return nil - } - return ml.publishMailingListChange(ctx, nil, mailingList, settings, model.ActionCreated) -} - -// publishMailingListUpdateMessages publishes update messages for indexer and access control -func (ml *grpsIOWriterOrchestrator) publishMailingListUpdateMessages(ctx context.Context, oldMailingList, newMailingList *model.GrpsIOMailingList) error { - // Fetch real settings to include current writers/auditors in access control message - // If settings retrieval fails, we'll still publish the indexer message but skip ACL message - settings, _, err := ml.grpsIOReader.GetGrpsIOMailingListSettings(ctx, newMailingList.UID) - if err != nil { - slog.WarnContext(ctx, "failed to fetch mailing list settings for update messages, will skip access control message", - "error", err, - "mailing_list_uid", newMailingList.UID) - settings = nil // Explicitly set to nil so access control message is skipped - } - - return ml.publishMailingListChange(ctx, oldMailingList, newMailingList, settings, model.ActionUpdated) -} - -// publishMailingListDeleteMessages publishes delete messages for indexer and access control -func (ml *grpsIOWriterOrchestrator) publishMailingListDeleteMessages(ctx context.Context, uid string) error { - return ml.publishMailingListDeletion(ctx, uid) -} - -// buildMailingListIndexerMessage builds an indexer message for search capabilities -func (ml *grpsIOWriterOrchestrator) buildMailingListIndexerMessage(ctx context.Context, mailingList *model.GrpsIOMailingList, action model.MessageAction) (*model.IndexerMessage, error) { - indexerMessage := &model.IndexerMessage{ - Action: action, - Tags: mailingList.Tags(), - } - - // Build the message with proper context and authorization headers - return indexerMessage.Build(ctx, mailingList) -} - -// buildMailingListAccessControlMessage builds an access control message for OpenFGA -// settings parameter contains writers/auditors that should be included in the access control message -func (ml *grpsIOWriterOrchestrator) buildMailingListAccessControlMessage(mailingList *model.GrpsIOMailingList, settings *model.GrpsIOMailingListSettings) *model.AccessMessage { - references := map[string][]string{ - constants.RelationGroupsIOService: {mailingList.ServiceUID}, // Required for service-level permission inheritance (project inherited through service) - } - - // Add committee references for committee-based lists (enables committee-level authorization) - // Each committee gets its own reference key for OR logic (any committee grants access) - for _, committee := range mailingList.Committees { - if committee.UID != "" { - references[constants.RelationCommittee] = append(references[constants.RelationCommittee], committee.UID) - } - } - - relations := map[string][]string{} - if settings != nil { - // Convert UserInfo arrays to username strings for access control - if len(settings.Writers) > 0 { - writers := make([]string, 0, len(settings.Writers)) - for _, w := range settings.Writers { - if w.Username != nil && *w.Username != "" { - writers = append(writers, *w.Username) - } - } - if len(writers) > 0 { - relations[constants.RelationWriter] = writers - } - } - if len(settings.Auditors) > 0 { - auditors := make([]string, 0, len(settings.Auditors)) - for _, a := range settings.Auditors { - if a.Username != nil && *a.Username != "" { - auditors = append(auditors, *a.Username) - } - } - if len(auditors) > 0 { - relations[constants.RelationAuditor] = auditors - } - } - } - - return &model.AccessMessage{ - UID: mailingList.UID, - ObjectType: constants.ObjectTypeGroupsIOMailingList, - Public: mailingList.Public, // Using Public bool instead of Visibility - Relations: relations, - References: references, - } -} - -// UpdateGrpsIOMailingList updates an existing mailing list with optimistic concurrency control -func (ml *grpsIOWriterOrchestrator) UpdateGrpsIOMailingList(ctx context.Context, uid string, mailingList *model.GrpsIOMailingList, expectedRevision uint64) (*model.GrpsIOMailingList, uint64, error) { - slog.DebugContext(ctx, "orchestrator: updating mailing list", - "mailing_list_uid", uid, - "expected_revision", expectedRevision) - - // Step 1: Retrieve existing mailing list to validate and merge data - existing, existingRevision, err := ml.grpsIOReader.GetGrpsIOMailingList(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve existing mailing list", - "error", err, - "mailing_list_uid", uid, - ) - return nil, 0, err - } - - // Step 3: Verify revision matches to ensure optimistic locking - if existingRevision != expectedRevision { - slog.WarnContext(ctx, "revision mismatch during update", - "expected_revision", expectedRevision, - "current_revision", existingRevision, - "mailing_list_uid", uid, - ) - return nil, 0, errors.NewConflict("mailing list has been modified by another process") - } - - // Step 4: Merge existing data with updated fields - ml.mergeMailingListData(ctx, existing, mailingList) - - // Step 4.1: Re-validate fields after merge to ensure data integrity - if err := mailingList.ValidateBasicFields(); err != nil { - slog.WarnContext(ctx, "basic field validation failed during update", "error", err) - return nil, 0, err - } - if err := mailingList.ValidateCommitteeFields(); err != nil { - slog.WarnContext(ctx, "committee field validation failed during update", "error", err) - return nil, 0, err - } - - // Step 3.2: Validate parent service constraints and refresh committee name if needed - parentSvc, _, err := ml.grpsIOReader.GetGrpsIOService(ctx, mailingList.ServiceUID) - if err != nil { - slog.WarnContext(ctx, "parent service not found during update", "error", err, "parent_uid", mailingList.ServiceUID) - return nil, 0, errors.NewNotFound("parent service not found") - } - if err := mailingList.ValidateGroupNamePrefix(parentSvc.Type, parentSvc.Prefix); err != nil { - slog.WarnContext(ctx, "group name prefix validation failed during update", "error", err) - return nil, 0, err - } - - // Always refresh committee names to pick up any name changes in committee-service - if len(mailingList.Committees) > 0 { - if err := ml.validateAndPopulateCommittees(ctx, mailingList); err != nil { - return nil, 0, err - } - } - - // Step 4: Update in storage with revision check - updatedMailingList, newRevision, err := ml.grpsIOWriter.UpdateGrpsIOMailingList(ctx, uid, mailingList, expectedRevision) - if err != nil { - slog.ErrorContext(ctx, "failed to update mailing list in storage", - "error", err, - "mailing_list_uid", uid, - "expected_revision", expectedRevision) - return nil, 0, err - } - - slog.DebugContext(ctx, "mailing list updated successfully", - "mailing_list_uid", uid, - "revision", newRevision, - ) - - // Sync mailing list updates to Groups.io - ml.syncMailingListToGroupsIO(ctx, updatedMailingList) - - // Refresh subscriber count from Groups.io or count from storage - subscriberCount := ml.refreshSubscriberCount(ctx, updatedMailingList) - if subscriberCount != updatedMailingList.SubscriberCount { - // Update subscriber count in storage (best-effort, non-blocking) - updatedMailingList.SubscriberCount = subscriberCount - updatedCopy, newRev, err := ml.grpsIOWriter.UpdateGrpsIOMailingList(ctx, uid, updatedMailingList, newRevision) - if err != nil { - slog.WarnContext(ctx, "failed to update subscriber count after refresh, using cached value", - "error", err, "mailing_list_uid", uid, "subscriber_count", subscriberCount) - } else { - updatedMailingList = updatedCopy - newRevision = newRev - slog.InfoContext(ctx, "subscriber count refreshed successfully", - "mailing_list_uid", uid, "subscriber_count", subscriberCount) - } - } - - // Publish update messages - if ml.publisher != nil { - if err := ml.publishMailingListUpdateMessages(ctx, existing, updatedMailingList); err != nil { - slog.ErrorContext(ctx, "failed to publish update messages", "error", err) - // Don't fail the update on message publishing errors - } - } - - slog.InfoContext(ctx, "mailing list updated successfully", - "mailing_list_uid", uid, - "group_name", updatedMailingList.GroupName, - "new_revision", newRevision) - - return updatedMailingList, newRevision, nil -} - -// createMailingListSecondaryIndices creates all secondary indices for the mailing list in the orchestrator layer -func (ml *grpsIOWriterOrchestrator) createMailingListSecondaryIndices(ctx context.Context, mailingList *model.GrpsIOMailingList) ([]string, error) { - // Use CreateSecondaryIndices method from the storage layer interface - createdKeys, err := ml.grpsIOWriter.CreateSecondaryIndices(ctx, mailingList) - if err != nil { - slog.ErrorContext(ctx, "failed to create secondary indices", "error", err) - return nil, err - } - - slog.DebugContext(ctx, "secondary indices created successfully", - "mailing_list_uid", mailingList.UID, - "indices_created", createdKeys) - - return createdKeys, nil -} - -// publishIndexerMessage is a helper for indexer message publishing -func (ml *grpsIOWriterOrchestrator) publishIndexerMessage(ctx context.Context, message any, action model.MessageAction) error { - if err := ml.publisher.Indexer(ctx, constants.IndexGroupsIOMailingListSubject, message); err != nil { - slog.ErrorContext(ctx, "failed to publish indexer message", "error", err, "action", action) - return fmt.Errorf("failed to publish %s indexer message: %w", action, err) - } - return nil -} - -// publishMailingListChange publishes indexer, access control, and event notification messages for create/update operations -func (ml *grpsIOWriterOrchestrator) publishMailingListChange(ctx context.Context, oldMailingList, newMailingList *model.GrpsIOMailingList, settings *model.GrpsIOMailingListSettings, action model.MessageAction) error { - // For creates, newMailingList is the created list and oldMailingList is nil - // For updates, both are provided - mailingList := newMailingList - - slog.DebugContext(ctx, "publishing messages for mailing list", - "action", action, - "mailing_list_uid", mailingList.UID) - - // Build and publish indexer message - indexerMessage, err := ml.buildMailingListIndexerMessage(ctx, mailingList, action) - if err != nil { - return fmt.Errorf("failed to build %s indexer message: %w", action, err) - } - - if err := ml.publishIndexerMessage(ctx, indexerMessage, action); err != nil { - return err - } - - // Only publish access control message if settings are available - // This ensures we only emit ACL messages with real writers/auditors data - if settings != nil { - // Publish access control message with current writers/auditors from settings - accessMessage := ml.buildMailingListAccessControlMessage(mailingList, settings) - if err := ml.publisher.Access(ctx, constants.UpdateAccessGroupsIOMailingListSubject, accessMessage); err != nil { - slog.ErrorContext(ctx, "failed to publish access control message", "error", err, "action", action) - return fmt.Errorf("failed to publish %s access control message: %w", action, err) - } - } else { - slog.DebugContext(ctx, "skipping access control message - settings not available", - "action", action, - "mailing_list_uid", mailingList.UID) - } - - // Publish mailing list event notification for committee sync - if err := ml.publishMailingListEventNotification(ctx, oldMailingList, newMailingList, action); err != nil { - slog.WarnContext(ctx, "failed to publish mailing list event notification", - "error", err, - "action", action, - "mailing_list_uid", mailingList.UID) - // Don't fail - indexer and access control messages already sent - } - - slog.DebugContext(ctx, "messages published successfully", - "action", action, - "mailing_list_uid", mailingList.UID) - return nil -} - -// publishMailingListDeletion publishes indexer and access control messages for delete operations -func (ml *grpsIOWriterOrchestrator) publishMailingListDeletion(ctx context.Context, uid string) error { - slog.DebugContext(ctx, "publishing delete messages for mailing list", - "mailing_list_uid", uid) - - // Build deletion indexer message - deleteMessage := &model.IndexerMessage{ - Action: model.ActionDeleted, - Tags: []string{}, - } - - indexerMessage, err := deleteMessage.Build(ctx, uid) - if err != nil { - return fmt.Errorf("failed to build delete indexer message: %w", err) - } - - if err := ml.publishIndexerMessage(ctx, indexerMessage, model.ActionDeleted); err != nil { - return err - } - - // Publish access control deletion - if err := ml.publisher.Access(ctx, constants.DeleteAllAccessGroupsIOMailingListSubject, uid); err != nil { - slog.ErrorContext(ctx, "failed to publish delete access control message", "error", err) - return fmt.Errorf("failed to publish delete access control message: %w", err) - } - - slog.DebugContext(ctx, "delete messages published successfully", - "mailing_list_uid", uid) - return nil -} - -// DeleteGrpsIOMailingList deletes a mailing list with optimistic concurrency control -// Note: mailingList parameter contains server-fetched data from the service layer, -// not client-supplied data. Used for cleanup of secondary indices and constraints. -func (ml *grpsIOWriterOrchestrator) DeleteGrpsIOMailingList(ctx context.Context, uid string, expectedRevision uint64, mailingList *model.GrpsIOMailingList) error { - slog.DebugContext(ctx, "orchestrator: deleting mailing list", - "mailing_list_uid", uid, - "expected_revision", expectedRevision) - - // Use the passed mailing list data - no need to fetch again - mailingListData := mailingList - - // Step 2: Deletion validation - // Validates main group protection, announcement list protection, and committee associations - // TODO: LFXV2-353 - Enhance with Groups.io API integration to validate: - // - Active subscriber count thresholds - // - Recent message activity - // - Pending moderation queue items - // TODO: LFXV2-478 - Enhance with committee event handling to: - // - Block deletion if active committee sync is running - // - Trigger committee member cleanup - slog.DebugContext(ctx, "validating mailing list deletion", - "mailing_list_uid", uid, - "group_name", mailingListData.GroupName, - "public", mailingListData.Public) - - // Step 2.1: Delete subgroup from Groups.io (if client available and mailing list has SubgroupID) - ml.deleteSubgroupWithCleanup(ctx, mailingListData.ServiceUID, mailingListData.GroupID) - - // Delete from storage with revision check - err := ml.grpsIOWriter.DeleteGrpsIOMailingList(ctx, uid, expectedRevision, mailingListData) - if err != nil { - slog.ErrorContext(ctx, "failed to delete mailing list from storage", "error", err, "mailing_list_uid", uid) - return err - } - - // Publish delete messages - if ml.publisher != nil { - if err := ml.publishMailingListDeleteMessages(ctx, uid); err != nil { - slog.ErrorContext(ctx, "failed to publish delete messages", "error", err) - } - } - - slog.InfoContext(ctx, "mailing list deleted successfully", - "mailing_list_uid", uid, - "group_name", mailingListData.GroupName) - - return nil -} - -// mergeMailingListData merges existing mailing list data with updated fields, preserving immutable fields -func (ml *grpsIOWriterOrchestrator) mergeMailingListData(ctx context.Context, existing *model.GrpsIOMailingList, updated *model.GrpsIOMailingList) { - // Preserve immutable fields - updated.UID = existing.UID - updated.CreatedAt = existing.CreatedAt - updated.ProjectUID = existing.ProjectUID // Inherited from parent service - updated.ProjectName = existing.ProjectName // Inherited from parent service - updated.ProjectSlug = existing.ProjectSlug // Inherited from parent service - updated.ServiceUID = existing.ServiceUID // Parent reference is immutable - updated.GroupName = existing.GroupName // Group name is immutable due to unique constraint - - // Update timestamp - updated.UpdatedAt = time.Now() - - slog.DebugContext(ctx, "mailing list data merged", - "mailing_list_uid", existing.UID, - "mutable_fields", []string{"public", "audience_access", "type", "description", "title", "committees", "subject_tag", "writers", "auditors", "last_reviewed_at", "last_reviewed_by"}, - ) -} - -// syncMailingListToGroupsIO handles Groups.io mailing list update with proper error handling -func (ml *grpsIOWriterOrchestrator) syncMailingListToGroupsIO(ctx context.Context, mailingList *model.GrpsIOMailingList) { - // Guard clause: skip if Groups.io client not available or mailing list not synced - if ml.groupsClient == nil || mailingList.GroupID == nil { - slog.InfoContext(ctx, "Groups.io integration disabled or mailing list not synced - skipping Groups.io update") - return - } - - // Get domain using helper method - domain, err := ml.getGroupsIODomainForResource(ctx, mailingList.UID, constants.ResourceTypeMailingList) - if err != nil { - slog.WarnContext(ctx, "Groups.io mailing list sync skipped due to domain lookup failure, local update will proceed", - "error", err, "mailing_list_uid", mailingList.UID) - return - } - - // Convert audience_access to Groups.io fields - restricted, inviteOnly := audienceAccessToGroupsIO(mailingList.AudienceAccess) - - // Build update options from mailing list model - updates := groupsio.SubgroupUpdateOptions{ - Title: mailingList.Title, - Description: mailingList.Description, - SubjectTag: mailingList.SubjectTag, - Restricted: restricted, - InviteOnly: inviteOnly, - } - - // Perform Groups.io mailing list update - err = ml.groupsClient.UpdateSubgroup(ctx, domain, utils.Int64PtrToUint64(mailingList.GroupID), updates) - if err != nil { - slog.WarnContext(ctx, "Groups.io mailing list update failed, local update will proceed", - "error", err, "domain", domain, "group_id", *mailingList.GroupID) - } else { - slog.InfoContext(ctx, "Groups.io mailing list updated successfully", - "group_id", *mailingList.GroupID, "domain", domain) - } -} - -// refreshSubscriberCount gets the latest subscriber count from Groups.io or fallback to NATS counting -// Returns the count (never negative) - best-effort operation -func (ml *grpsIOWriterOrchestrator) refreshSubscriberCount(ctx context.Context, mailingList *model.GrpsIOMailingList) int { - // Try to get count from Groups.io API first - if ml.groupsClient != nil && mailingList.GroupID != nil { - domain, err := ml.getGroupsIODomainForResource(ctx, mailingList.UID, constants.ResourceTypeMailingList) - if err != nil { - slog.WarnContext(ctx, "failed to get domain for subscriber count refresh, falling back to NATS count", - "error", err, "mailing_list_uid", mailingList.UID) - } else { - groupDetails, err := ml.groupsClient.GetGroup(ctx, domain, uint64(*mailingList.GroupID)) - if err != nil { - slog.WarnContext(ctx, "failed to get group details from Groups.io, falling back to NATS count", - "error", err, "group_id", *mailingList.GroupID) - } else { - slog.InfoContext(ctx, "subscriber count refreshed from Groups.io", - "mailing_list_uid", mailingList.UID, "subscriber_count", groupDetails.SubsCount) - return int(groupDetails.SubsCount) - } - } - } - - // Fallback to NATS counting - count, err := ml.grpsIOReader.CountMembersInMailingList(ctx, mailingList.UID) - if err != nil { - slog.WarnContext(ctx, "failed to count members in NATS, preserving existing count", - "error", err, "mailing_list_uid", mailingList.UID, "existing_count", mailingList.SubscriberCount) - return mailingList.SubscriberCount - } - - slog.InfoContext(ctx, "subscriber count refreshed from NATS", - "mailing_list_uid", mailingList.UID, "subscriber_count", count) - return count -} - -// handleAPISourceMailingList handles API-initiated mailing list creation -// Returns the SubgroupObject with ID and subscriber count from Groups.io -func (ml *grpsIOWriterOrchestrator) handleAPISourceMailingList( - ctx context.Context, - request *model.GrpsIOMailingList, - parentService *model.GrpsIOService, -) (*groupsio.SubgroupObject, bool, error) { - slog.InfoContext(ctx, "source=api: creating subgroup in Groups.io", - "group_name", request.GroupName, - "parent_uid", parentService.UID) - - // Call createMailingListInGroupsIO to create subgroup and get full response - subgroupResult, err := ml.createMailingListInGroupsIO(ctx, request, parentService) - if err != nil { - slog.ErrorContext(ctx, "failed to create subgroup in Groups.io", - "error", err, - "group_name", request.GroupName) - return nil, false, err - } - - // Determine if cleanup is required (preserves existing rollback logic) - requiresCleanup := subgroupResult != nil && parentService.Domain != "" - - if subgroupResult != nil { - slog.InfoContext(ctx, "subgroup created successfully in Groups.io", - "subgroup_id", subgroupResult.ID, - "subscriber_count", subgroupResult.SubsCount) - } - - return subgroupResult, requiresCleanup, nil -} - -// handleWebhookSourceMailingList handles webhook-initiated mailing list adoption -// Preserves existing logic: validates SubgroupID and returns it -func (ml *grpsIOWriterOrchestrator) handleWebhookSourceMailingList( - ctx context.Context, - request *model.GrpsIOMailingList, -) (*int64, error) { - if request.GroupID == nil { - return nil, errors.NewValidation("webhook source requires GroupID to be provided") - } - - slog.InfoContext(ctx, "source=webhook: adopting webhook-provided subgroup", - "group_id", *request.GroupID, - "group_name", request.GroupName) - - return request.GroupID, nil -} - -// handleMockSourceMailingList handles mock/test mode mailing list creation -// Preserves existing logic: returns nil for subgroupID -func (ml *grpsIOWriterOrchestrator) handleMockSourceMailingList( - ctx context.Context, - request *model.GrpsIOMailingList, -) *int64 { - slog.InfoContext(ctx, "source=mock: skipping Groups.io coordination", - "group_name", request.GroupName) - return nil -} - -// publishMailingListEventNotification publishes internal events for mailing list changes -// These events are consumed by internal services like committee sync for event-driven workflows -func (ml *grpsIOWriterOrchestrator) publishMailingListEventNotification(ctx context.Context, oldMailingList, newMailingList *model.GrpsIOMailingList, action model.MessageAction) error { - var subject string - var event any - - switch action { - case model.ActionCreated: - subject = constants.MailingListCreatedSubject - event = model.MailingListCreatedEvent{ - MailingList: newMailingList, - } - slog.DebugContext(ctx, "publishing mailing list created event", - "subject", subject, - "mailing_list_uid", newMailingList.UID) - - case model.ActionUpdated: - subject = constants.MailingListUpdatedSubject - event = model.MailingListUpdatedEvent{ - OldMailingList: oldMailingList, - NewMailingList: newMailingList, - } - slog.DebugContext(ctx, "publishing mailing list updated event", - "subject", subject, - "mailing_list_uid", newMailingList.UID) - - default: - // Don't publish events for other actions (e.g., deleted) - slog.DebugContext(ctx, "skipping event notification for action", - "action", action) - return nil - } - - if err := ml.publisher.Internal(ctx, subject, event); err != nil { - slog.ErrorContext(ctx, "failed to publish internal event notification", - "error", err, - "subject", subject, - "action", action) - return fmt.Errorf("failed to publish internal event: %w", err) - } - - slog.InfoContext(ctx, "mailing list event notification published successfully", - "subject", subject, - "action", action) - - return nil -} - -// UpdateGrpsIOMailingListSettings updates mailing list settings and publishes indexer message -func (ml *grpsIOWriterOrchestrator) UpdateGrpsIOMailingListSettings(ctx context.Context, settings *model.GrpsIOMailingListSettings, expectedRevision uint64) (*model.GrpsIOMailingListSettings, uint64, error) { - slog.DebugContext(ctx, "executing update mailing list settings use case", - "mailing_list_uid", settings.UID, - "expected_revision", expectedRevision, - ) - - // Fetch existing settings to preserve created_at timestamp - existingSettings, existingRevision, err := ml.grpsIOReader.GetGrpsIOMailingListSettings(ctx, settings.UID) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve existing mailing list settings", - "error", err, - "mailing_list_uid", settings.UID, - ) - return nil, 0, err - } - - // Verify revision matches to ensure optimistic locking - if existingRevision != expectedRevision { - slog.WarnContext(ctx, "revision mismatch during settings update", - "expected_revision", expectedRevision, - "current_revision", existingRevision, - "mailing_list_uid", settings.UID, - ) - return nil, 0, errors.NewConflict("mailing list settings have been modified by another process") - } - - // Preserve created_at and update updated_at - settings.CreatedAt = existingSettings.CreatedAt - settings.UpdatedAt = time.Now() - - // Update settings in storage - updatedSettings, revision, err := ml.grpsIOWriter.UpdateGrpsIOMailingListSettings(ctx, settings, expectedRevision) - if err != nil { - slog.ErrorContext(ctx, "failed to update mailing list settings", - "error", err, - "mailing_list_uid", settings.UID, - "expected_revision", expectedRevision, - ) - return nil, 0, err - } - - slog.DebugContext(ctx, "mailing list settings updated successfully", - "mailing_list_uid", settings.UID, - "revision", revision, - ) - - // Publish settings indexer message - if ml.publisher != nil { - settingsIndexerMessage := &model.IndexerMessage{ - Action: model.ActionUpdated, - Tags: updatedSettings.Tags(), - } - builtMessage, errBuild := settingsIndexerMessage.Build(ctx, updatedSettings) - if errBuild != nil { - slog.ErrorContext(ctx, "failed to build settings indexer message", - "error", errBuild, - "mailing_list_uid", settings.UID, - ) - // Don't fail the update on message building errors - } else { - if errPublish := ml.publisher.Indexer(ctx, constants.IndexGroupsIOMailingListSettingsSubject, builtMessage); errPublish != nil { - slog.ErrorContext(ctx, "failed to publish settings indexer message", - "error", errPublish, - "mailing_list_uid", settings.UID, - ) - // Don't fail the update on message publishing errors - } - } - - // Also publish updated access control message with new writers/auditors - mailingList, _, errMailingList := ml.grpsIOReader.GetGrpsIOMailingList(ctx, settings.UID) - if errMailingList != nil { - slog.WarnContext(ctx, "failed to get mailing list for access control update", - "error", errMailingList, - "mailing_list_uid", settings.UID, - ) - } else { - // Build access control message using updated settings - accessMessage := ml.buildMailingListAccessControlMessage(mailingList, updatedSettings) - - if errAccess := ml.publisher.Access(ctx, constants.UpdateAccessGroupsIOMailingListSubject, accessMessage); errAccess != nil { - slog.ErrorContext(ctx, "failed to publish access control message", - "error", errAccess, - "mailing_list_uid", settings.UID, - ) - // Don't fail the update on message publishing errors - } - } - } - - slog.InfoContext(ctx, "mailing list settings update completed", - "mailing_list_uid", settings.UID, - "revision", revision, - ) - - return updatedSettings, revision, nil -} diff --git a/internal/service/grpsio_mailing_list_writer_test.go b/internal/service/grpsio_mailing_list_writer_test.go deleted file mode 100644 index bfb105d..0000000 --- a/internal/service/grpsio_mailing_list_writer_test.go +++ /dev/null @@ -1,1165 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/mock" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - errs "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" -) - -// TestMockMailingListWriter implements proper reservation logic for testing -type TestMockMailingListWriter struct { - mock *mock.MockRepository - reservations map[string]string // key -> reservationID for rollback -} - -func NewTestMockMailingListWriter(mockRepo *mock.MockRepository) *TestMockMailingListWriter { - return &TestMockMailingListWriter{ - mock: mockRepo, - reservations: make(map[string]string), - } -} - -func (w *TestMockMailingListWriter) CreateGrpsIOMailingList(ctx context.Context, mailingList *model.GrpsIOMailingList) (*model.GrpsIOMailingList, uint64, error) { - // Generate UID if not set - if mailingList.UID == "" { - mailingList.UID = uuid.New().String() - } - - now := time.Now() - mailingList.CreatedAt = now - mailingList.UpdatedAt = now - - // Store mailing list - return w.mock.CreateGrpsIOMailingList(ctx, mailingList) -} - -func (w *TestMockMailingListWriter) UpdateGrpsIOMailingList(ctx context.Context, uid string, mailingList *model.GrpsIOMailingList, expectedRevision uint64) (*model.GrpsIOMailingList, uint64, error) { - mockWriter := mock.NewMockGrpsIOWriter(w.mock) - return mockWriter.UpdateGrpsIOMailingList(ctx, uid, mailingList, expectedRevision) -} - -func (w *TestMockMailingListWriter) DeleteGrpsIOMailingList(ctx context.Context, uid string, expectedRevision uint64, mailingList *model.GrpsIOMailingList) error { - mockWriter := mock.NewMockGrpsIOWriter(w.mock) - return mockWriter.DeleteGrpsIOMailingList(ctx, uid, expectedRevision, mailingList) -} - -// UniqueMailingListGroupName reserves a unique group name within parent service -func (w *TestMockMailingListWriter) UniqueMailingListGroupName(ctx context.Context, mailingList *model.GrpsIOMailingList) (string, error) { - groupNameKey := mailingList.BuildIndexKey(ctx) - - // Use the mock's existing logic but invert the result for proper reservation behavior - mockWriter := mock.NewMockGrpsIOWriter(w.mock) - existingUID, err := mockWriter.UniqueMailingListGroupName(ctx, mailingList) - - // If we get a conflict error, that means it already exists - return the conflict - if err != nil { - var conflictErr errs.Conflict - if errors.As(err, &conflictErr) { - return existingUID, err - } - // If it's a "not found" error, that means it's unique - we can reserve it - reservationID := uuid.New().String() - w.reservations[groupNameKey] = reservationID - return reservationID, nil - } - - // Should not reach here with the current mock implementation - return existingUID, err -} - -// CreateSecondaryIndices creates secondary indices for mailing list -func (w *TestMockMailingListWriter) CreateSecondaryIndices(ctx context.Context, mailingList *model.GrpsIOMailingList) ([]string, error) { - mockWriter := mock.NewMockGrpsIOWriter(w.mock) - return mockWriter.CreateSecondaryIndices(ctx, mailingList) -} - -// GetKeyRevision gets revision for a key -func (w *TestMockMailingListWriter) GetKeyRevision(ctx context.Context, key string) (uint64, error) { - mockWriter := mock.NewMockGrpsIOWriter(w.mock) - return mockWriter.GetKeyRevision(ctx, key) -} - -// Delete deletes a key with revision -func (w *TestMockMailingListWriter) Delete(ctx context.Context, key string, revision uint64) error { - mockWriter := mock.NewMockGrpsIOWriter(w.mock) - return mockWriter.Delete(ctx, key, revision) -} - -func TestGrpsIOWriterOrchestrator_CreateGrpsIOMailingList(t *testing.T) { - testCases := []struct { - name string - setupMock func(*mock.MockRepository) - inputMailingList *model.GrpsIOMailingList - expectedError error - validate func(t *testing.T, result *model.GrpsIOMailingList, mockRepo *mock.MockRepository) - }{ - { - name: "successful mailing list creation without committee", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - - // Add parent service - service := &model.GrpsIOService{ - UID: "service-1", - Type: "primary", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - Prefix: "", - Domain: "lists.test.org", - GroupName: "test-project", - Public: true, - Status: "created", - } - mockRepo.AddService(service) - }, - inputMailingList: &model.GrpsIOMailingList{ - GroupName: "announce", - Public: true, - Type: "announcement", - Description: "Test announcement mailing list for the project", - Title: "Test Announcements", - Source: constants.SourceMock, - ServiceUID: "service-1", - }, - expectedError: nil, - validate: func(t *testing.T, result *model.GrpsIOMailingList, mockRepo *mock.MockRepository) { - assert.NotEmpty(t, result.UID) - assert.Equal(t, "project-1", result.ProjectUID) - assert.Equal(t, "Test Project", result.ProjectName) - assert.Equal(t, "test-project", result.ProjectSlug) - assert.Equal(t, "announce", result.GroupName) - assert.Equal(t, "service-1", result.ServiceUID) - assert.Empty(t, result.Committees) - assert.Equal(t, 1, mockRepo.GetMailingListCount()) - }, - }, - { - name: "successful mailing list creation with committee", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - - // Add parent service - service := &model.GrpsIOService{ - UID: "service-1", - Type: "primary", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - Prefix: "", - Domain: "lists.test.org", - GroupName: "test-project", - Public: true, - Status: "created", - } - mockRepo.AddService(service) - - // Add committee - mockRepo.AddCommittee("committee-1", "Technical Committee") - }, - inputMailingList: &model.GrpsIOMailingList{ - GroupName: "tsc-discuss", - Public: false, - Type: "discussion_moderated", - Committees: []model.Committee{ - {UID: "committee-1"}, - }, - Description: "Technical Steering Committee discussion list", - Title: "TSC Discussion", - Source: constants.SourceMock, - ServiceUID: "service-1", - }, - expectedError: nil, - validate: func(t *testing.T, result *model.GrpsIOMailingList, mockRepo *mock.MockRepository) { - assert.NotEmpty(t, result.UID) - require.Len(t, result.Committees, 1) - assert.Equal(t, "committee-1", result.Committees[0].UID) - assert.Equal(t, "Technical Committee", result.Committees[0].Name) - assert.Equal(t, "tsc-discuss", result.GroupName) - assert.False(t, result.Public) - assert.Equal(t, "discussion_moderated", result.Type) - assert.Equal(t, 1, mockRepo.GetMailingListCount()) - }, - }, - { - name: "successful creation with formation service prefix validation", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - - // Add formation service with prefix - service := &model.GrpsIOService{ - UID: "service-2", - Type: "formation", - ProjectUID: "project-2", - ProjectName: "Formation Project", - ProjectSlug: "formation-project", - Prefix: "form", - Domain: "lists.formation.org", - GroupName: "formation-project", - Public: true, - Status: "created", - } - mockRepo.AddService(service) - }, - inputMailingList: &model.GrpsIOMailingList{ - GroupName: "form-announce", - Public: true, - Type: "announcement", - Description: "Formation project announcements", - Title: "Formation Announcements", - Source: constants.SourceMock, - ServiceUID: "service-2", - }, - expectedError: nil, - validate: func(t *testing.T, result *model.GrpsIOMailingList, mockRepo *mock.MockRepository) { - assert.NotEmpty(t, result.UID) - assert.Equal(t, "form-announce", result.GroupName) - assert.Equal(t, "project-2", result.ProjectUID) - assert.Equal(t, 1, mockRepo.GetMailingListCount()) - }, - }, - { - name: "parent service not found error", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - // Don't add any services - }, - inputMailingList: &model.GrpsIOMailingList{ - GroupName: "announce", - Public: true, - Type: "announcement", - Description: "Test announcement mailing list", - Title: "Test Announcements", - Source: constants.SourceMock, - ServiceUID: "nonexistent-service", - }, - expectedError: errs.NotFound{}, - validate: func(t *testing.T, result *model.GrpsIOMailingList, mockRepo *mock.MockRepository) { - assert.Nil(t, result) - assert.Equal(t, 0, mockRepo.GetMailingListCount()) - }, - }, - { - name: "committee not found error", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - - // Add parent service - service := &model.GrpsIOService{ - UID: "service-1", - Type: "primary", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - Prefix: "", - Domain: "lists.test.org", - GroupName: "test-project", - Public: true, - Status: "created", - } - mockRepo.AddService(service) - // Don't add committee - }, - inputMailingList: &model.GrpsIOMailingList{ - GroupName: "committee-discuss", - Public: false, - Type: "discussion_moderated", - Committees: []model.Committee{ - {UID: "nonexistent-committee"}, - }, - Description: "Committee discussion list", - Title: "Committee Discussion", - Source: constants.SourceMock, - ServiceUID: "service-1", - }, - expectedError: errs.NotFound{}, - validate: func(t *testing.T, result *model.GrpsIOMailingList, mockRepo *mock.MockRepository) { - assert.Nil(t, result) - assert.Equal(t, 0, mockRepo.GetMailingListCount()) - }, - }, - { - name: "group name already exists error", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - - // Add parent service - service := &model.GrpsIOService{ - UID: "service-1", - Type: "primary", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - Prefix: "", - Domain: "lists.test.org", - GroupName: "test-project", - Public: true, - Status: "created", - } - mockRepo.AddService(service) - - // Add existing mailing list with same group name - existingMailingList := &model.GrpsIOMailingList{ - UID: "existing-list", - GroupName: "announce", - Source: constants.SourceMock, - ServiceUID: "service-1", - ProjectUID: "project-1", - Type: "announcement", - Description: "Existing announcement list", - Title: "Existing Announcements", - Public: true, - CreatedAt: time.Now().Add(-24 * time.Hour), - UpdatedAt: time.Now(), - } - mockRepo.AddMailingList(existingMailingList) - }, - inputMailingList: &model.GrpsIOMailingList{ - GroupName: "announce", // Same group name as existing - Public: true, - Type: "announcement", - Description: "New announcement list", - Title: "New Announcements", - Source: constants.SourceMock, - ServiceUID: "service-1", - }, - expectedError: errs.Conflict{}, - validate: func(t *testing.T, result *model.GrpsIOMailingList, mockRepo *mock.MockRepository) { - assert.Nil(t, result) - assert.Equal(t, 1, mockRepo.GetMailingListCount()) // Only the existing one - }, - }, - { - name: "invalid group name prefix for formation service", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - - // Add formation service with prefix - service := &model.GrpsIOService{ - UID: "service-2", - Type: "formation", - ProjectUID: "project-2", - ProjectName: "Formation Project", - ProjectSlug: "formation-project", - Prefix: "form", - Domain: "lists.formation.org", - GroupName: "formation-project", - Public: true, - Status: "created", - } - mockRepo.AddService(service) - }, - inputMailingList: &model.GrpsIOMailingList{ - GroupName: "announce", // Should be form-announce for formation service - Public: true, - Type: "announcement", - Description: "Invalid group name without prefix", - Title: "Invalid Announcements", - Source: constants.SourceMock, - ServiceUID: "service-2", - }, - expectedError: errs.Validation{}, - validate: func(t *testing.T, result *model.GrpsIOMailingList, mockRepo *mock.MockRepository) { - assert.Nil(t, result) - assert.Equal(t, 0, mockRepo.GetMailingListCount()) - }, - }, - { - name: "description too short validation error", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - - // Add parent service - service := &model.GrpsIOService{ - UID: "service-1", - Type: "primary", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - Prefix: "", - Domain: "lists.test.org", - GroupName: "test-project", - Public: true, - Status: "created", - } - mockRepo.AddService(service) - }, - inputMailingList: &model.GrpsIOMailingList{ - GroupName: "announce", - Public: true, - Type: "announcement", - Description: "Short", // Too short (less than 11 characters) - Title: "Test Announcements", - Source: constants.SourceMock, - ServiceUID: "service-1", - }, - expectedError: errs.Validation{}, - validate: func(t *testing.T, result *model.GrpsIOMailingList, mockRepo *mock.MockRepository) { - assert.Nil(t, result) - assert.Equal(t, 0, mockRepo.GetMailingListCount()) - }, - }, - { - name: "invalid mailing list type validation error", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - - // Add parent service - service := &model.GrpsIOService{ - UID: "service-1", - Type: "primary", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - Prefix: "", - Domain: "lists.test.org", - GroupName: "test-project", - Public: true, - Status: "created", - } - mockRepo.AddService(service) - }, - inputMailingList: &model.GrpsIOMailingList{ - GroupName: "announce", - Public: true, - Type: "invalid_type", // Invalid type - Description: "Test announcement mailing list", - Title: "Test Announcements", - Source: constants.SourceMock, - ServiceUID: "service-1", - }, - expectedError: errs.Validation{}, - validate: func(t *testing.T, result *model.GrpsIOMailingList, mockRepo *mock.MockRepository) { - assert.Nil(t, result) - assert.Equal(t, 0, mockRepo.GetMailingListCount()) - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - mockRepo := mock.NewMockRepository() - tc.setupMock(mockRepo) - - grpsIOReader := mock.NewMockGrpsIOReader(mockRepo) - grpsIOWriter := mock.NewMockGrpsIOWriter(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - publisher := mock.NewMockMessagePublisher() - - orchestrator := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(grpsIOReader), - WithGrpsIOWriter(grpsIOWriter), - WithEntityAttributeReader(entityReader), - WithPublisher(publisher), - ) - - // Execute - ctx := context.Background() - settings := &model.GrpsIOMailingListSettings{ - Writers: []model.UserInfo{}, - Auditors: []model.UserInfo{}, - } - result, revision, err := orchestrator.CreateGrpsIOMailingList(ctx, tc.inputMailingList, settings) - - // Validate - if tc.expectedError != nil { - require.Error(t, err) - assert.IsType(t, tc.expectedError, err) - } else { - require.NoError(t, err) - require.NotNil(t, result) - require.Greater(t, revision, uint64(0), "revision should be greater than 0") - } - - tc.validate(t, result, mockRepo) - }) - } -} - -// MockMessagePublisherWithError is a mock publisher that can return errors for testing -type MockMessagePublisherWithError struct { - indexerError error - accessError error - internalError error -} - -func (p *MockMessagePublisherWithError) Indexer(ctx context.Context, subject string, message interface{}) error { - if p.indexerError != nil { - return p.indexerError - } - return nil -} - -func (p *MockMessagePublisherWithError) Access(ctx context.Context, subject string, message interface{}) error { - if p.accessError != nil { - return p.accessError - } - return nil -} - -func (p *MockMessagePublisherWithError) Internal(ctx context.Context, subject string, message interface{}) error { - if p.internalError != nil { - return p.internalError - } - return nil -} - -func TestGrpsIOWriterOrchestrator_CreateGrpsIOMailingList_PublishingErrors(t *testing.T) { - testCases := []struct { - name string - indexerError error - accessError error - expectComplete bool // Should mailing list still be created despite publishing errors? - }{ - { - name: "indexer error does not fail creation", - indexerError: errors.New("indexer publishing failed"), - accessError: nil, - expectComplete: true, - }, - { - name: "access error does not fail creation", - indexerError: nil, - accessError: errors.New("access publishing failed"), - expectComplete: true, - }, - { - name: "both publishing errors do not fail creation", - indexerError: errors.New("indexer publishing failed"), - accessError: errors.New("access publishing failed"), - expectComplete: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - mockRepo := mock.NewMockRepository() - mockRepo.ClearAll() - - // Add parent service - service := &model.GrpsIOService{ - UID: "service-1", - Type: "primary", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - Prefix: "", - Domain: "lists.test.org", - GroupName: "test-project", - Public: true, - Status: "created", - } - mockRepo.AddService(service) - - grpsIOReader := mock.NewMockGrpsIOReader(mockRepo) - grpsIOWriter := mock.NewMockGrpsIOWriter(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - // Use custom publisher that can return errors - publisher := &MockMessagePublisherWithError{ - indexerError: tc.indexerError, - accessError: tc.accessError, - } - - orchestrator := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(grpsIOReader), - WithGrpsIOWriter(grpsIOWriter), - WithEntityAttributeReader(entityReader), - WithPublisher(publisher), - ) - - mailingList := &model.GrpsIOMailingList{ - GroupName: "announce", - Public: true, - Type: "announcement", - Description: "Test announcement mailing list for publishing errors", - Title: "Test Announcements", - Source: constants.SourceMock, - ServiceUID: "service-1", - } - - // Execute - ctx := context.Background() - settings := &model.GrpsIOMailingListSettings{ - Writers: []model.UserInfo{}, - Auditors: []model.UserInfo{}, - } - result, revision, err := orchestrator.CreateGrpsIOMailingList(ctx, mailingList, settings) - - // Validate - if tc.expectComplete { - assert.NoError(t, err) - assert.NotNil(t, result) - assert.NotEmpty(t, result.UID) - assert.Greater(t, revision, uint64(0), "revision should be greater than 0") - assert.Equal(t, 1, mockRepo.GetMailingListCount()) - } else { - assert.Error(t, err) - assert.Nil(t, result) - } - }) - } -} - -func TestGrpsIOWriterOrchestrator_buildMailingListIndexerMessage(t *testing.T) { - testCases := []struct { - name string - mailingList *model.GrpsIOMailingList - expectedError bool - }{ - { - name: "successful indexer message build", - mailingList: &model.GrpsIOMailingList{ - UID: "test-list", - ServiceUID: "test-service", - GroupName: "announce", - ProjectUID: "test-project", - Type: "announcement", - Public: true, - Description: "Test announcement list", - Title: "Test Announcements", - }, - expectedError: false, - }, - { - name: "build with nil mailing list", - mailingList: nil, - expectedError: false, // The Build method doesn't validate nil input - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - orchestrator := &grpsIOWriterOrchestrator{} - ctx := context.Background() - - // Execute - result, err := orchestrator.buildMailingListIndexerMessage(ctx, tc.mailingList, model.ActionCreated) - - // Validate - if tc.expectedError { - assert.Error(t, err) - assert.Nil(t, result) - } else { - assert.NoError(t, err) - assert.NotNil(t, result) - assert.Equal(t, model.ActionCreated, result.Action) - - // For nil mailing list case, validate that data is nil - if tc.mailingList == nil { - assert.Nil(t, result.Data) - } - } - }) - } -} - -func TestGrpsIOWriterOrchestrator_buildMailingListAccessControlMessage(t *testing.T) { - testCases := []struct { - name string - mailingList *model.GrpsIOMailingList - settings *model.GrpsIOMailingListSettings - expected *model.AccessMessage - }{ - { - name: "mailing list without committee", - mailingList: &model.GrpsIOMailingList{ - UID: "list-1", - ServiceUID: "service-1", - ProjectUID: "project-1", - Public: true, - }, - expected: &model.AccessMessage{ - UID: "list-1", - ObjectType: constants.ObjectTypeGroupsIOMailingList, - Public: true, - Relations: map[string][]string{}, - References: map[string][]string{ - constants.RelationGroupsIOService: {"service-1"}, - }, - }, - }, - { - name: "mailing list with committee", - mailingList: &model.GrpsIOMailingList{ - UID: "list-2", - ServiceUID: "service-2", - ProjectUID: "project-2", - Committees: []model.Committee{ - {UID: "committee-1"}, - }, - Public: false, - }, - expected: &model.AccessMessage{ - UID: "list-2", - ObjectType: constants.ObjectTypeGroupsIOMailingList, - Public: false, - Relations: map[string][]string{}, - References: map[string][]string{ - constants.RelationCommittee: {"committee-1"}, - constants.RelationGroupsIOService: {"service-2"}, - }, - }, - }, - { - name: "mailing list with writers from settings", - mailingList: &model.GrpsIOMailingList{ - UID: "list-3", - ServiceUID: "service-3", - ProjectUID: "project-3", - Public: true, - }, - settings: &model.GrpsIOMailingListSettings{ - UID: "list-3", - Writers: []model.UserInfo{ - {Username: stringPtr("user1")}, - {Username: stringPtr("user2")}, - }, - }, - expected: &model.AccessMessage{ - UID: "list-3", - ObjectType: constants.ObjectTypeGroupsIOMailingList, - Public: true, - Relations: map[string][]string{ - constants.RelationWriter: {"user1", "user2"}, - }, - References: map[string][]string{ - constants.RelationGroupsIOService: {"service-3"}, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - orchestrator := &grpsIOWriterOrchestrator{} - - // Execute - result := orchestrator.buildMailingListAccessControlMessage(tc.mailingList, tc.settings) - - // Validate - assert.Equal(t, tc.expected, result) - }) - } -} - -// TestGrpsIOWriterOrchestrator_UpdateGrpsIOMailingList tests the update functionality -func TestGrpsIOWriterOrchestrator_UpdateGrpsIOMailingList(t *testing.T) { - testCases := []struct { - name string - setupMock func(*mock.MockRepository) - existingUID string - updatedMailingList *model.GrpsIOMailingList - expectedRevision uint64 - expectedError error - validate func(*testing.T, *model.GrpsIOMailingList, *mock.MockRepository) - }{ - { - name: "successful_update_without_committee_change_preserves_name", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - - // Add parent service - service := &model.GrpsIOService{ - UID: "service-1", - Type: "primary", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - Domain: "lists.test.org", - GroupName: "test-project", - Public: true, - Status: "created", - } - mockRepo.AddService(service) - - // Add committee - mockRepo.AddCommittee("committee-1", "Technical Steering Committee") - - // Create existing mailing list - existing := &model.GrpsIOMailingList{ - UID: "list-1", - GroupName: "tsc-discuss", - Public: false, - Type: "discussion_moderated", - Committees: []model.Committee{ - {UID: "committee-1", Name: "Technical Steering Committee"}, - }, - Description: "Technical steering committee discussions", - Title: "TSC Discussion List", - ServiceUID: "service-1", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - CreatedAt: time.Now().Add(-time.Hour), - UpdatedAt: time.Now().Add(-time.Hour), - } - mockRepo.AddMailingList(existing) - }, - existingUID: "list-1", - updatedMailingList: &model.GrpsIOMailingList{ - GroupName: "tsc-discuss", - Public: true, // Changed - Type: "discussion_moderated", - Committees: []model.Committee{ - {UID: "committee-1"}, // Same committee - }, - Description: "Updated technical steering committee discussions", // Changed - Title: "TSC Discussion List", - ServiceUID: "service-1", - }, - expectedRevision: 1, - expectedError: nil, - validate: func(t *testing.T, result *model.GrpsIOMailingList, mockRepo *mock.MockRepository) { - require.Len(t, result.Committees, 1) - assert.Equal(t, "committee-1", result.Committees[0].UID) - assert.Equal(t, "Technical Steering Committee", result.Committees[0].Name) // Should be preserved - assert.Equal(t, true, result.Public) // Should be updated - assert.Equal(t, "Updated technical steering committee discussions", result.Description) // Should be updated - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - mockRepo := mock.NewMockRepository() - tc.setupMock(mockRepo) - - grpsIOReader := mock.NewMockGrpsIOReader(mockRepo) - grpsIOWriter := mock.NewMockGrpsIOWriter(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - publisher := mock.NewMockMessagePublisher() - - orchestrator := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(grpsIOReader), - WithGrpsIOWriter(grpsIOWriter), - WithEntityAttributeReader(entityReader), - WithPublisher(publisher), - ) - - // Execute - ctx := context.Background() - result, revision, err := orchestrator.UpdateGrpsIOMailingList(ctx, tc.existingUID, tc.updatedMailingList, tc.expectedRevision) - - // Validate - if tc.expectedError != nil { - require.Error(t, err) - assert.IsType(t, tc.expectedError, err) - assert.Nil(t, result) - assert.Equal(t, uint64(0), revision) - } else { - require.NoError(t, err) - require.NotNil(t, result) - assert.Greater(t, revision, uint64(0)) - - // Validate immutable fields are preserved - assert.Equal(t, tc.existingUID, result.UID) - assert.Equal(t, tc.updatedMailingList.ServiceUID, result.ServiceUID) - } - - tc.validate(t, result, mockRepo) - }) - } -} - -// TestGrpsIOWriterOrchestrator_mergeMailingListData tests the merge logic in isolation -func TestGrpsIOWriterOrchestrator_mergeMailingListData(t *testing.T) { - testCases := []struct { - name string - existing *model.GrpsIOMailingList - updated *model.GrpsIOMailingList - validate func(*testing.T, *model.GrpsIOMailingList) - }{ - { - name: "preserve_committees_when_unchanged", - existing: &model.GrpsIOMailingList{ - UID: "list-1", - GroupName: "tsc-discuss", - Committees: []model.Committee{ - {UID: "committee-1", Name: "Technical Steering Committee"}, - }, - CreatedAt: time.Now().Add(-time.Hour), - ServiceUID: "service-1", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - }, - updated: &model.GrpsIOMailingList{ - Committees: []model.Committee{ - {UID: "committee-1"}, // Same committee UID - }, - Public: true, // Changed field - }, - validate: func(t *testing.T, result *model.GrpsIOMailingList) { - require.Len(t, result.Committees, 1) - assert.Equal(t, "committee-1", result.Committees[0].UID) - assert.Equal(t, true, result.Public) - }, - }, - { - name: "clear_committees_when_removed", - existing: &model.GrpsIOMailingList{ - UID: "list-2", - GroupName: "general-discuss", - Committees: []model.Committee{ - {UID: "committee-1", Name: "Technical Steering Committee"}, - }, - CreatedAt: time.Now().Add(-time.Hour), - ServiceUID: "service-1", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - }, - updated: &model.GrpsIOMailingList{ - Committees: []model.Committee{}, // Empty committees (removing all) - Public: true, - }, - validate: func(t *testing.T, result *model.GrpsIOMailingList) { - assert.Empty(t, result.Committees, "Committees should be cleared when array is empty") - assert.Equal(t, true, result.Public) - }, - }, - { - name: "use_new_committees_when_changed", - existing: &model.GrpsIOMailingList{ - UID: "list-3", - GroupName: "committee-discuss", - Committees: []model.Committee{ - {UID: "committee-1", Name: "Technical Steering Committee"}, - }, - CreatedAt: time.Now().Add(-time.Hour), - ServiceUID: "service-1", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - }, - updated: &model.GrpsIOMailingList{ - Committees: []model.Committee{ - {UID: "committee-2", Name: "Governance Committee"}, // Different committee - }, - Public: true, - }, - validate: func(t *testing.T, result *model.GrpsIOMailingList) { - // When Committees change, new values should be used - require.Len(t, result.Committees, 1) - assert.Equal(t, "committee-2", result.Committees[0].UID) - assert.Equal(t, "Governance Committee", result.Committees[0].Name) - assert.Equal(t, true, result.Public) - }, - }, - { - name: "add_multiple_committees", - existing: &model.GrpsIOMailingList{ - UID: "list-4", - GroupName: "multi-committee", - Committees: []model.Committee{ - {UID: "committee-1", Name: "Technical Steering Committee"}, - }, - CreatedAt: time.Now().Add(-time.Hour), - ServiceUID: "service-1", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - }, - updated: &model.GrpsIOMailingList{ - Committees: []model.Committee{ - {UID: "committee-1", Name: "Technical Steering Committee"}, - {UID: "committee-2", Name: "Governance Committee"}, - }, - Public: true, - }, - validate: func(t *testing.T, result *model.GrpsIOMailingList) { - require.Len(t, result.Committees, 2) - assert.Equal(t, "committee-1", result.Committees[0].UID) - assert.Equal(t, "committee-2", result.Committees[1].UID) - assert.Equal(t, true, result.Public) - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - ctx := context.Background() - orchestrator := &grpsIOWriterOrchestrator{} - - // Execute - orchestrator.mergeMailingListData(ctx, tc.existing, tc.updated) - - // Validate immutable fields are always preserved - assert.Equal(t, tc.existing.UID, tc.updated.UID) - assert.Equal(t, tc.existing.CreatedAt, tc.updated.CreatedAt) - assert.Equal(t, tc.existing.ServiceUID, tc.updated.ServiceUID) - assert.Equal(t, tc.existing.GroupName, tc.updated.GroupName) - assert.Equal(t, tc.existing.ProjectUID, tc.updated.ProjectUID) - assert.Equal(t, tc.existing.ProjectName, tc.updated.ProjectName) - assert.Equal(t, tc.existing.ProjectSlug, tc.updated.ProjectSlug) - - // UpdatedAt should be set to current time - assert.True(t, tc.updated.UpdatedAt.After(tc.existing.CreatedAt)) - - // Run custom validation - tc.validate(t, tc.updated) - }) - } -} - -// TestGrpsIOWriterOrchestrator_syncMailingListToGroupsIO tests the syncMailingListToGroupsIO method -func TestGrpsIOWriterOrchestrator_syncMailingListToGroupsIO(t *testing.T) { - testCases := []struct { - name string - setupMocks func() (*grpsIOWriterOrchestrator, *mock.MockRepository) - mailingList *model.GrpsIOMailingList - expectSkip bool - expectWarning bool - validateLogs func(t *testing.T) - }{ - { - name: "skip sync when Groups.io client is nil", - setupMocks: func() (*grpsIOWriterOrchestrator, *mock.MockRepository) { - mockRepo := mock.NewMockRepository() - orchestrator := &grpsIOWriterOrchestrator{ - groupsClient: nil, // No client - } - return orchestrator, mockRepo - }, - mailingList: &model.GrpsIOMailingList{ - UID: "mailing-list-1", - GroupID: func() *int64 { i := int64(12345); return &i }(), - Title: "Test Mailing List", - }, - expectSkip: true, - }, - { - name: "skip sync when mailing list GroupID is nil", - setupMocks: func() (*grpsIOWriterOrchestrator, *mock.MockRepository) { - mockRepo := mock.NewMockRepository() - orchestrator := &grpsIOWriterOrchestrator{ - groupsClient: nil, // Could be any client, but GroupID is nil - } - return orchestrator, mockRepo - }, - mailingList: &model.GrpsIOMailingList{ - UID: "mailing-list-2", - GroupID: nil, // No group ID - not synced - Title: "Test Mailing List", - }, - expectSkip: true, - }, - { - name: "skip sync when domain lookup fails", - setupMocks: func() (*grpsIOWriterOrchestrator, *mock.MockRepository) { - mockRepo := mock.NewMockRepository() - mockReader := mock.NewMockGrpsIOReader(mockRepo) - - orchestrator := &grpsIOWriterOrchestrator{ - groupsClient: nil, // Any non-nil would work for this test - grpsIOReader: mockReader, - } - - // Note: We don't need actual client for this test as domain lookup will fail first - // The orchestrator has a nil client but that's OK since domain lookup happens first - return orchestrator, mockRepo - }, - mailingList: &model.GrpsIOMailingList{ - UID: "nonexistent-mailing-list", - GroupID: func() *int64 { i := int64(12345); return &i }(), - Title: "Test Mailing List", - }, - expectWarning: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - ctx := context.Background() - orchestrator, _ := tc.setupMocks() - - // Execute - should not panic regardless of nil clients or missing data - require.NotPanics(t, func() { - orchestrator.syncMailingListToGroupsIO(ctx, tc.mailingList) - }, "syncMailingListToGroupsIO should handle nil clients and missing data gracefully") - - // The method should complete without errors, handling all edge cases internally - // This validates that our error handling and guard clauses work correctly - }) - } -} - -func TestAudienceAccessToGroupsIO(t *testing.T) { - tests := []struct { - name string - audienceAccess string - expectedRestricted bool - expectedInviteOnly bool - }{ - { - name: "public - anyone can join", - audienceAccess: model.AudienceAccessPublic, - expectedRestricted: false, - expectedInviteOnly: false, - }, - { - name: "approval_required - restricted membership", - audienceAccess: model.AudienceAccessApprovalRequired, - expectedRestricted: true, - expectedInviteOnly: false, - }, - { - name: "invite_only - only invited users can join", - audienceAccess: model.AudienceAccessInviteOnly, - expectedRestricted: false, - expectedInviteOnly: true, - }, - { - name: "empty string defaults to public", - audienceAccess: "", - expectedRestricted: false, - expectedInviteOnly: false, - }, - { - name: "unknown value defaults to public", - audienceAccess: "unknown_value", - expectedRestricted: false, - expectedInviteOnly: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - restricted, inviteOnly := audienceAccessToGroupsIO(tt.audienceAccess) - - require.NotNil(t, restricted, "restricted should not be nil") - require.NotNil(t, inviteOnly, "inviteOnly should not be nil") - - assert.Equal(t, tt.expectedRestricted, *restricted, "restricted flag mismatch") - assert.Equal(t, tt.expectedInviteOnly, *inviteOnly, "inviteOnly flag mismatch") - }) - } -} - -// stringPtr is a helper function that returns a pointer to a string value -func stringPtr(s string) *string { - return &s -} - -// Helper functions (if needed in future) diff --git a/internal/service/grpsio_member_reader.go b/internal/service/grpsio_member_reader.go deleted file mode 100644 index 19728e6..0000000 --- a/internal/service/grpsio_member_reader.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "log/slog" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" -) - -// GetGrpsIOMember retrieves a member by UID -func (r *grpsIOReaderOrchestrator) GetGrpsIOMember(ctx context.Context, uid string) (*model.GrpsIOMember, uint64, error) { - if r.grpsIOReader == nil { - panic("grpsIOReader dependency is required but was not provided") - } - - slog.DebugContext(ctx, "executing get member use case", - "member_uid", uid, - ) - - // Get member from storage - member, revision, err := r.grpsIOReader.GetGrpsIOMember(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to get member", - "error", err, - "member_uid", uid, - ) - return nil, 0, err - } - - slog.DebugContext(ctx, "member retrieved successfully", - "member_uid", uid, - "revision", revision, - ) - - return member, revision, nil -} - -// GetMemberRevision retrieves only the revision for a given member UID -func (r *grpsIOReaderOrchestrator) GetMemberRevision(ctx context.Context, uid string) (uint64, error) { - if r.grpsIOReader == nil { - panic("grpsIOReader dependency is required but was not provided") - } - - slog.DebugContext(ctx, "executing get member revision use case", "member_uid", uid) - - revision, err := r.grpsIOReader.GetMemberRevision(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to get member revision", "error", err, "member_uid", uid) - return 0, err - } - - slog.DebugContext(ctx, "member revision retrieved successfully", "member_uid", uid, "revision", revision) - return revision, nil -} - -// GetMemberByGroupsIOMemberID retrieves member by Groups.io member ID -func (r *grpsIOReaderOrchestrator) GetMemberByGroupsIOMemberID(ctx context.Context, memberID uint64) (*model.GrpsIOMember, uint64, error) { - if r.grpsIOReader == nil { - panic("grpsIOReader dependency is required but was not provided") - } - - slog.DebugContext(ctx, "executing get member by Groups.io member ID use case", - "member_id", memberID) - - member, revision, err := r.grpsIOReader.GetMemberByGroupsIOMemberID(ctx, memberID) - if err != nil { - slog.ErrorContext(ctx, "failed to get member by Groups.io member ID", - "error", err, - "member_id", memberID) - return nil, 0, err - } - - slog.DebugContext(ctx, "member retrieved successfully by Groups.io member ID", - "member_uid", member.UID, - "member_id", memberID, - "revision", revision) - - return member, revision, nil -} - -// GetMemberByEmail retrieves member by email within mailing list -func (r *grpsIOReaderOrchestrator) GetMemberByEmail(ctx context.Context, mailingListUID, email string) (*model.GrpsIOMember, uint64, error) { - if r.grpsIOReader == nil { - panic("grpsIOReader dependency is required but was not provided") - } - - slog.DebugContext(ctx, "executing get member by email use case", - "mailing_list_uid", mailingListUID, - "email", email) - - member, revision, err := r.grpsIOReader.GetMemberByEmail(ctx, mailingListUID, email) - if err != nil { - slog.ErrorContext(ctx, "failed to get member by email", - "error", err, - "mailing_list_uid", mailingListUID) - return nil, 0, err - } - - slog.DebugContext(ctx, "member retrieved successfully by email", - "member_uid", member.UID, - "mailing_list_uid", mailingListUID, - "revision", revision) - - return member, revision, nil -} diff --git a/internal/service/grpsio_member_reader_test.go b/internal/service/grpsio_member_reader_test.go deleted file mode 100644 index d08a303..0000000 --- a/internal/service/grpsio_member_reader_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "testing" - "time" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/mock" - errs "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" -) - -func TestGrpsIOMemberReaderOrchestrator_GetGrpsIOMember(t *testing.T) { - ctx := context.Background() - mockRepo := mock.NewMockRepository() - - // Setup test data - testMemberUID := uuid.New().String() - testMailingListUID := uuid.New().String() - testMember := &model.GrpsIOMember{ - UID: testMemberUID, - MailingListUID: testMailingListUID, - MemberID: memberInt64Ptr(12345), - GroupID: memberInt64Ptr(67890), - Username: "testuser", - FirstName: "John", - LastName: "Doe", - Email: "john.doe@example.com", - Organization: "Acme Corp", - JobTitle: "Software Engineer", - MemberType: "committee", - DeliveryMode: "individual", - ModStatus: "none", - Status: "normal", - LastReviewedAt: memberStringPtr("2024-01-01T00:00:00Z"), - LastReviewedBy: memberStringPtr("reviewer-uid"), - CreatedAt: time.Now().Add(-24 * time.Hour), - UpdatedAt: time.Now(), - } - - tests := []struct { - name string - setupMock func() - memberUID string - expectedError bool - errorType error - validateMember func(*testing.T, *model.GrpsIOMember, uint64) - }{ - { - name: "successful member retrieval", - setupMock: func() { - mockRepo.ClearAll() - // Store the member in mock repository - mockRepo.AddMember(testMember) - }, - memberUID: testMemberUID, - expectedError: false, - validateMember: func(t *testing.T, member *model.GrpsIOMember, revision uint64) { - assert.NotNil(t, member) - assert.Equal(t, testMemberUID, member.UID) - assert.Equal(t, testMailingListUID, member.MailingListUID) - assert.Equal(t, memberInt64Ptr(12345), member.MemberID) - assert.Equal(t, memberInt64Ptr(67890), member.GroupID) - assert.Equal(t, "testuser", member.Username) - assert.Equal(t, "John", member.FirstName) - assert.Equal(t, "Doe", member.LastName) - assert.Equal(t, "john.doe@example.com", member.Email) - assert.Equal(t, "Acme Corp", member.Organization) - assert.Equal(t, "Software Engineer", member.JobTitle) - assert.Equal(t, "committee", member.MemberType) - assert.Equal(t, "individual", member.DeliveryMode) - assert.Equal(t, "none", member.ModStatus) - assert.Equal(t, "normal", member.Status) - assert.NotNil(t, member.LastReviewedAt) - assert.Equal(t, "2024-01-01T00:00:00Z", *member.LastReviewedAt) - assert.NotNil(t, member.LastReviewedBy) - assert.Equal(t, "reviewer-uid", *member.LastReviewedBy) - assert.NotZero(t, member.CreatedAt) - assert.NotZero(t, member.UpdatedAt) - assert.Equal(t, uint64(1), revision) // Mock returns revision 1 - }, - }, - { - name: "member not found error", - setupMock: func() { - mockRepo.ClearAll() - // Don't store any member - }, - memberUID: "nonexistent-member-uid", - expectedError: true, - errorType: errs.NotFound{}, - validateMember: func(t *testing.T, member *model.GrpsIOMember, revision uint64) { - assert.Nil(t, member) - assert.Equal(t, uint64(0), revision) - }, - }, - { - name: "empty member UID", - setupMock: func() { - mockRepo.ClearAll() - }, - memberUID: "", - expectedError: true, - errorType: errs.NotFound{}, - validateMember: func(t *testing.T, member *model.GrpsIOMember, revision uint64) { - assert.Nil(t, member) - assert.Equal(t, uint64(0), revision) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Setup - tt.setupMock() - - // Create reader orchestrator - reader := NewGrpsIOReaderOrchestrator( - WithGrpsIOReader(mock.NewMockGrpsIOReader(mockRepo)), - ) - - // Execute - member, revision, err := reader.GetGrpsIOMember(ctx, tt.memberUID) - - // Validate - if tt.expectedError { - require.Error(t, err) - if tt.errorType != nil { - assert.IsType(t, tt.errorType, err) - } - } else { - require.NoError(t, err) - } - - tt.validateMember(t, member, revision) - }) - } -} - -// Helper function to create string pointer -func memberStringPtr(s string) *string { - return &s -} - -// Helper function to create int64 pointer -func memberInt64Ptr(i int64) *int64 { - return &i -} diff --git a/internal/service/grpsio_member_writer.go b/internal/service/grpsio_member_writer.go deleted file mode 100644 index ea8851e..0000000 --- a/internal/service/grpsio_member_writer.go +++ /dev/null @@ -1,903 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - stdErrors "errors" - "fmt" - "log/slog" - "strings" - "sync" - "time" - - "github.com/google/uuid" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/groupsio" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/concurrent" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - errs "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/redaction" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/utils" -) - -// groupsioMailingListMemberStub represents the minimal data needed for member access control -type groupsioMailingListMemberStub struct { - // UID is the mailing list member ID. - UID string `json:"uid"` - // Username is the username (i.e. LFID) of the member. This is the identity of the user object in FGA. - Username string `json:"username"` - // MailingListUID is the mailing list ID for the mailing list the member belongs to. - MailingListUID string `json:"mailing_list_uid"` -} - -// ensureMemberIdempotent checks if member already exists by Groups.io member ID or email -// Returns existing entity if found, nil if not found, error on failure -// Pattern mirrors ensureMailingListIdempotent -func (o *grpsIOWriterOrchestrator) ensureMemberIdempotent( - ctx context.Context, - member *model.GrpsIOMember, -) (*model.GrpsIOMember, uint64, error) { - - // Strategy 1: Lookup by Groups.io member ID (webhook path) - if member.MemberID != nil { - memberID := uint64(*member.MemberID) - - slog.DebugContext(ctx, "checking idempotency by Groups.io member ID", - "member_id", memberID, - "source", member.Source) - - existing, revision, err := o.grpsIOReader.GetMemberByGroupsIOMemberID(ctx, memberID) - if err != nil { - // Use helper to handle idempotency lookup errors consistently - shouldContinue, handledErr := handleIdempotencyLookupError(ctx, err, "member_id", fmt.Sprintf("%d", memberID)) - if !shouldContinue { - return nil, 0, handledErr - } - // NotFound - fall through to Strategy 2 (email lookup) - } else if existing != nil { - // Found existing member - idempotent success - slog.InfoContext(ctx, "member already exists by Groups.io member ID (idempotent)", - "member_uid", existing.UID, - "member_id", memberID, - "existing_source", existing.Source, - "request_source", member.Source) - return existing, revision, nil - } - } - - // Strategy 2: Lookup by email (API retry or webhook without member ID) - existing, revision, err := o.grpsIOReader.GetMemberByEmail( - ctx, member.MailingListUID, member.Email) - if err != nil { - // Use helper to handle idempotency lookup errors consistently - shouldContinue, handledErr := handleIdempotencyLookupError(ctx, err, "email", redaction.RedactEmail(member.Email)) - if !shouldContinue { - return nil, 0, handledErr - } - // NotFound - proceed with creation - slog.DebugContext(ctx, "no existing member found, proceeding with creation") - return nil, 0, nil - } - - if existing != nil { - slog.InfoContext(ctx, "member already exists by email (idempotent)", - "member_uid", existing.UID, - "email", redaction.RedactEmail(member.Email), - "existing_source", existing.Source, - "request_source", member.Source) - return existing, revision, nil - } - - // Not found - proceed with creation - slog.DebugContext(ctx, "no existing member found, proceeding with creation") - return nil, 0, nil -} - -// CreateGrpsIOMember creates a new member with transactional operations and rollback following service pattern -func (o *grpsIOWriterOrchestrator) CreateGrpsIOMember(ctx context.Context, member *model.GrpsIOMember) (*model.GrpsIOMember, uint64, error) { - slog.DebugContext(ctx, "executing create member use case", - "member_email", redaction.RedactEmail(member.Email), - "mailing_list_uid", member.MailingListUID, - ) - - // LAYER 1: Early idempotency check (prevents wasted work) - // Pattern matches: ensureMailingListIdempotent in CreateGrpsIOMailingList - if existing, revision, err := o.ensureMemberIdempotent(ctx, member); err != nil { - return nil, 0, err - } else if existing != nil { - return existing, revision, nil // Already exists - idempotent success - } - - // Step 1: Validate timestamps - if err := member.ValidateLastReviewedAt(); err != nil { - slog.ErrorContext(ctx, "invalid LastReviewedAt timestamp", - "error", err, - "last_reviewed_at", member.LastReviewedAt, - ) - return nil, 0, errs.NewValidation(fmt.Sprintf("invalid LastReviewedAt: %s", err.Error())) - } - - // Step 2: Generate UID and set timestamps (server-side generation for security) - now := time.Now() - member.UID = uuid.New().String() // Always generate server-side, never trust client - member.CreatedAt = now - member.UpdatedAt = now - - // For rollback purposes - var ( - keys []string - rollbackRequired bool - rollbackMemberID *uint64 - rollbackGroupsIODomain string - ) - defer func() { - if err := recover(); err != nil || rollbackRequired { - o.deleteKeys(ctx, keys, true) - - // Clean up GroupsIO member if created - if rollbackMemberID != nil && o.groupsClient != nil { - if deleteErr := o.groupsClient.RemoveMember(ctx, rollbackGroupsIODomain, uint64(*member.GroupID), *rollbackMemberID); deleteErr != nil { - slog.WarnContext(ctx, "failed to cleanup GroupsIO member during rollback", "error", deleteErr, "group_id", *member.GroupID, "member_id", *rollbackMemberID) - } - } - } - }() - - // Step 3: Validate mailing list exists and populate metadata - if err := o.validateAndPopulateMailingList(ctx, member); err != nil { - slog.ErrorContext(ctx, "mailing list validation failed", - "error", err, - "mailing_list_uid", member.MailingListUID, - ) - return nil, 0, err - } - - slog.DebugContext(ctx, "mailing list validation successful", - "mailing_list_uid", member.MailingListUID, - ) - - // Step 4: Set default status if not provided - if member.Status == "" { - member.Status = "pending" - } - - // Step 5: Reserve unique constraints (member email per mailing list) - constraintKey, err := o.memberRepository.UniqueMember(ctx, member) - if err != nil { - rollbackRequired = true - return nil, 0, err - } - if constraintKey != "" { - keys = append(keys, constraintKey) - } - - // Step 6: Get mailing list (needed for all sources) - mailingList, _, err := o.grpsIOReader.GetGrpsIOMailingList(ctx, member.MailingListUID) - if err != nil { - slog.ErrorContext(ctx, "failed to get mailing list for Groups.io sync", "error", err, "mailing_list_uid", member.MailingListUID) - rollbackRequired = true - return nil, 0, err - } - - // Step 7: Handle member creation based on source (API, webhook, or mock) - memberID, groupID, requiresCleanup, err := o.handleMemberCreationBySource(ctx, member, mailingList) - if err != nil { - rollbackRequired = true - return nil, 0, err - } - - // Set rollback information if cleanup is required - if requiresCleanup { - rollbackMemberID = utils.Int64PtrToUint64Ptr(memberID) - // Get parent service domain for rollback (only when needed) - parentService, _, svcErr := o.grpsIOReader.GetGrpsIOService(ctx, mailingList.ServiceUID) - if svcErr == nil { - rollbackGroupsIODomain = parentService.GetDomain() - } - } - - member.MemberID = memberID - member.GroupID = groupID - - // Step 9: Create member in storage (with Groups.io IDs already set) - createdMember, revision, err := o.memberRepository.CreateGrpsIOMember(ctx, member) - if err != nil { - slog.ErrorContext(ctx, "failed to create member", - "error", err, - "member_email", redaction.RedactEmail(member.Email), - "mailing_list_uid", member.MailingListUID, - ) - rollbackRequired = true - return nil, 0, err - } - keys = append(keys, createdMember.UID) - - slog.DebugContext(ctx, "member created successfully", - "member_uid", createdMember.UID, - "revision", revision, - ) - - // Refresh subscriber count asynchronously after DirectAdd and NATS item creation - // Run in background so NATS secondary indices creation and message sending can complete concurrently - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer func() { - if r := recover(); r != nil { - slog.ErrorContext(ctx, "panic in updateMailingListSubscriberCount", - "panic", r, - "mailing_list_uid", mailingList.UID) - } - wg.Done() - }() - o.updateMailingListSubscriberCount(ctx, mailingList.UID) - }() - - // Step 9.5: Create secondary indices for Groups.io ID lookups - // Only create if member has Groups.io IDs (skip for mock/pending members) - // Pattern matches: createMailingListSecondaryIndices in CreateGrpsIOMailingList - if createdMember.MemberID != nil || createdMember.GroupID != nil { - secondaryKeys, err := o.memberRepository.CreateMemberSecondaryIndices(ctx, createdMember) - if err != nil { - slog.ErrorContext(ctx, "failed to create member secondary indices", - "error", err, - "member_uid", createdMember.UID, - ) - rollbackRequired = true - return nil, 0, err - } - keys = append(keys, secondaryKeys...) - - slog.DebugContext(ctx, "member secondary indices created", - "member_uid", createdMember.UID, - "indices_count", len(secondaryKeys)) - } - - // Step 10: Publish messages (indexer and access control) - if o.publisher != nil { - if err := o.publishMemberMessages(ctx, createdMember, model.ActionCreated); err != nil { - slog.ErrorContext(ctx, "failed to publish member messages", "error", err) - // Don't fail the operation on message failure, member creation succeeded - } - } - - wg.Wait() - - return createdMember, revision, nil -} - -// createMemberInGroupsIO handles Groups.io member creation and returns the IDs -func (o *grpsIOWriterOrchestrator) createMemberInGroupsIO(ctx context.Context, member *model.GrpsIOMember, mailingList *model.GrpsIOMailingList, parentService *model.GrpsIOService) (*int64, *int64, error) { - if o.groupsClient == nil || mailingList == nil || mailingList.GroupID == nil || parentService == nil || parentService.GroupID == nil { - return nil, nil, nil // Skip Groups.io creation - } - - domain := parentService.GetDomain() - - slog.InfoContext(ctx, "creating member in Groups.io", - "domain", domain, - "group_id", *mailingList.GroupID, - "email", member.Email, - ) - - // Prepare email slice (single email for our use case) - emails := []string{member.Email} - - // Prepare subgroup IDs slice (if adding to subgroup rather than main group) - var subgroupIDs []uint64 - if *parentService.GroupID != *mailingList.GroupID { - subgroupIDs = []uint64{uint64(*mailingList.GroupID)} - } - - result, err := o.groupsClient.DirectAdd( - ctx, - domain, - utils.Int64PtrToUint64(parentService.GroupID), - emails, - subgroupIDs, - ) - if err != nil { - slog.ErrorContext(ctx, "Groups.io member creation failed", - "error", err, - "domain", domain, - "group_id", *mailingList.GroupID, - "email", member.Email, - ) - return nil, nil, fmt.Errorf("groups.io member creation failed: %w", err) - } - - // Check for errors in the response - if len(result.Errors) > 0 { - firstError := result.Errors[0] - slog.ErrorContext(ctx, "Groups.io direct_add returned error", - "email", firstError.Email, - "status", firstError.Status, - "group_id", firstError.GroupID, - "domain", domain, - ) - return nil, nil, fmt.Errorf("failed to add member %s: %s", firstError.Email, firstError.Status) - } - - // Check if any members were added - if len(result.AddedMembers) == 0 { - slog.ErrorContext(ctx, "no members added via direct_add", - "email", member.Email, - "group_id", *mailingList.GroupID, - "domain", domain, - ) - return nil, nil, fmt.Errorf("no members were added for email %s", member.Email) - } - - // Get the first (and only) added member - addedMember := result.AddedMembers[0] - memberID := int64(addedMember.ID) - groupID := int64(addedMember.GroupID) - - slog.InfoContext(ctx, "Groups.io member created successfully", - "member_id", memberID, - "group_id", groupID, - "domain", domain, - "email", addedMember.Email, - ) - - return &memberID, &groupID, nil -} - -// UpdateGrpsIOMember updates an existing member following the service pattern with pre-fetch and validation -func (o *grpsIOWriterOrchestrator) UpdateGrpsIOMember(ctx context.Context, uid string, member *model.GrpsIOMember, expectedRevision uint64) (*model.GrpsIOMember, uint64, error) { - slog.DebugContext(ctx, "executing update member use case", - "member_uid", uid, - "expected_revision", expectedRevision, - ) - - // Step 1: Validate timestamps in input - if err := member.ValidateLastReviewedAt(); err != nil { - slog.ErrorContext(ctx, "invalid LastReviewedAt timestamp", - "error", err, - "last_reviewed_at", member.LastReviewedAt, - "member_uid", uid, - ) - return nil, 0, errs.NewValidation(fmt.Sprintf("invalid LastReviewedAt: %s", err.Error())) - } - - // Step 2: Retrieve existing member to validate and merge data - existing, existingRevision, err := o.grpsIOReader.GetGrpsIOMember(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve existing member", - "error", err, - "member_uid", uid, - ) - return nil, 0, err - } - - // Step 3: Verify revision matches to ensure optimistic locking - if existingRevision != expectedRevision { - slog.WarnContext(ctx, "revision mismatch during member update", - "expected_revision", expectedRevision, - "current_revision", existingRevision, - "member_uid", uid, - ) - return nil, 0, errs.NewConflict("member has been modified by another process") - } - - // Step 4: Protect immutable fields - if member.MailingListUID != "" && member.MailingListUID != existing.MailingListUID { - return nil, 0, errs.NewValidation("field 'mailing_list_uid' is immutable") - } - if member.Email != "" && member.Email != existing.Email { - return nil, 0, errs.NewValidation("field 'email' is immutable") - } - - // Step 5: Merge existing data with updated fields - o.mergeMemberData(ctx, existing, member) - - // Step 6: Update member in storage with optimistic concurrency control - updatedMember, revision, err := o.memberRepository.UpdateGrpsIOMember(ctx, uid, member, expectedRevision) - if err != nil { - slog.ErrorContext(ctx, "failed to update member", - "error", err, - "member_uid", uid, - ) - return nil, 0, err - } - - slog.DebugContext(ctx, "member updated successfully", - "member_uid", uid, - "revision", revision, - ) - - // Step 6.1: Handle member update sync based on source (API, webhook, or mock) - if err := o.handleMemberUpdateBySource(ctx, updatedMember); err != nil { - slog.WarnContext(ctx, "Groups.io sync failed but local update succeeded", - "error", err, "member_uid", uid) - // Don't fail the operation - local update succeeded - } - - // Step 7: Publish messages (indexer and access control) - if o.publisher != nil { - if err := o.publishMemberMessages(ctx, updatedMember, model.ActionUpdated); err != nil { - slog.ErrorContext(ctx, "failed to publish member update messages", "error", err) - // Don't fail the operation on message failure, update succeeded - } - } - - return updatedMember, revision, nil -} - -// DeleteGrpsIOMember deletes a member following the service pattern -func (o *grpsIOWriterOrchestrator) DeleteGrpsIOMember(ctx context.Context, uid string, expectedRevision uint64, member *model.GrpsIOMember) error { - slog.DebugContext(ctx, "executing delete member use case", - "member_uid", uid, - "expected_revision", expectedRevision, - ) - - if member == nil { - return errs.NewValidation("member is required for deletion") - } - - logger := slog.With("member_uid", uid, "source", member.Source) - if member.GroupID != nil { - logger = logger.With("group_id", *member.GroupID) - } - if member.MemberID != nil { - logger = logger.With("member_id", *member.MemberID) - } - - // Handle member deletion based on source (API, webhook, or mock) - err := o.handleMemberDeletionBySource(ctx, member) - if err != nil { - logger.ErrorContext(ctx, "failed to remove member from source", - "error", err, - ) - return fmt.Errorf("failed to remove member from source: %w", err) - } - - // Delete member from storage with optimistic concurrency control - err = o.memberRepository.DeleteGrpsIOMember(ctx, uid, expectedRevision, member) - if err != nil { - logger.ErrorContext(ctx, "failed to delete member", - "error", err, - ) - return err - } - - logger.DebugContext(ctx, "member deleted successfully") - - // Update the mailing list subscriber count asynchronously - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer func() { - if r := recover(); r != nil { - logger.ErrorContext(ctx, "panic in updateMailingListSubscriberCount", - "panic", r, - "mailing_list_uid", member.MailingListUID) - } - wg.Done() - }() - o.updateMailingListSubscriberCount(ctx, member.MailingListUID) - }() - - // Publish delete messages (indexer and access control) - if o.publisher != nil { - if err := o.publishMemberDeleteMessages(ctx, uid, *member); err != nil { - logger.ErrorContext(ctx, "failed to publish member delete messages", "error", err) - // Don't fail the operation on message failure, delete succeeded - } - } - - wg.Wait() - - return nil -} - -// validateAndPopulateMailingList validates mailing list exists and populates metadata -func (o *grpsIOWriterOrchestrator) validateAndPopulateMailingList(ctx context.Context, member *model.GrpsIOMember) error { - if member.MailingListUID == "" { - return errs.NewValidation("mailing_list_uid is required") - } - - // Validate mailing list exists - _, _, err := o.grpsIOReader.GetGrpsIOMailingList(ctx, member.MailingListUID) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve mailing list", - "error", err, - "mailing_list_uid", member.MailingListUID, - ) - return errs.NewNotFound("mailing list not found") - } - - return nil -} - -// publishMemberMessages publishes indexer and access control messages for member operations -func (o *grpsIOWriterOrchestrator) publishMemberMessages(ctx context.Context, member *model.GrpsIOMember, action model.MessageAction) error { - if o.publisher == nil { - slog.WarnContext(ctx, "publisher not available, skipping member message publishing") - return nil - } - - slog.DebugContext(ctx, "publishing messages for member", - "action", action, - "member_uid", member.UID) - - // Build indexer message - indexerMessage, err := o.buildMemberIndexerMessage(ctx, member, action) - if err != nil { - return fmt.Errorf("failed to build %s indexer message: %w", action, err) - } - - // Prepare messages to publish - messages := []func() error{ - func() error { - return o.publisher.Indexer(ctx, constants.IndexGroupsIOMemberSubject, indexerMessage) - }, - } - - // Only publish access control message if member has a username (required for FGA identity) - if member.Username != "" { - accessMessage := o.buildMemberAccessMessage(member) - messages = append(messages, func() error { - return o.publisher.Access(ctx, constants.PutMemberGroupsIOMailingListSubject, accessMessage) - }) - } else { - slog.DebugContext(ctx, "skipping access control message - member has no username", - "member_uid", member.UID) - } - - // Execute all messages concurrently - errPublishingMessage := concurrent.NewWorkerPool(len(messages)).Run(ctx, messages...) - if errPublishingMessage != nil { - slog.ErrorContext(ctx, "failed to publish member messages", - "error", errPublishingMessage, - "member_uid", member.UID, - ) - return errPublishingMessage - } - - slog.DebugContext(ctx, "messages published successfully", - "member_uid", member.UID, - "action", action, - ) - - return nil -} - -// publishMemberDeleteMessages publishes member delete messages concurrently -func (o *grpsIOWriterOrchestrator) publishMemberDeleteMessages(ctx context.Context, uid string, member model.GrpsIOMember) error { - if o.publisher == nil { - slog.WarnContext(ctx, "publisher not available, skipping member delete message publishing") - return nil - } - - indexerMessage := &model.IndexerMessage{ - Action: model.ActionDeleted, - Tags: []string{}, - } - - builtMessage, err := indexerMessage.Build(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to build member delete indexer message", "error", err, "member_uid", uid) - return fmt.Errorf("failed to build member delete indexer message: %w", err) - } - - // Prepare messages to publish - messages := []func() error{ - func() error { - return o.publisher.Indexer(ctx, constants.IndexGroupsIOMemberSubject, builtMessage) - }, - } - - // Only publish access control message if member has a username (required for FGA identity) - if member.Username != "" { - accessMessage := o.buildMemberAccessMessage(&member) - messages = append(messages, func() error { - return o.publisher.Access(ctx, constants.RemoveMemberGroupsIOMailingListSubject, accessMessage) - }) - } else { - slog.DebugContext(ctx, "skipping access control delete message - member has no username", - "member_uid", uid) - } - - // Execute all messages concurrently - errPublishingMessage := concurrent.NewWorkerPool(len(messages)).Run(ctx, messages...) - if errPublishingMessage != nil { - slog.ErrorContext(ctx, "failed to publish member delete messages", - "error", errPublishingMessage, - "member_uid", uid, - ) - return errPublishingMessage - } - - slog.DebugContext(ctx, "member delete messages published successfully", "member_uid", uid) - return nil -} - -// buildMemberIndexerMessage creates the indexer message using proper IndexerMessage.Build method -func (o *grpsIOWriterOrchestrator) buildMemberIndexerMessage(ctx context.Context, member *model.GrpsIOMember, action model.MessageAction) (*model.IndexerMessage, error) { - indexerMessage := &model.IndexerMessage{ - Action: action, - Tags: member.Tags(), - } - - // Build the message with proper context and authorization headers - return indexerMessage.Build(ctx, member) -} - -// buildMemberAccessMessage creates the access control message stub for OpenFGA integration -func (o *grpsIOWriterOrchestrator) buildMemberAccessMessage(member *model.GrpsIOMember) *groupsioMailingListMemberStub { - return &groupsioMailingListMemberStub{ - UID: member.UID, - Username: member.Username, - MailingListUID: member.MailingListUID, - } -} - -// mergeMemberData merges existing member data with updated fields, preserving immutable fields -func (o *grpsIOWriterOrchestrator) mergeMemberData(ctx context.Context, existing *model.GrpsIOMember, updated *model.GrpsIOMember) { - // Preserve immutable fields - updated.UID = existing.UID - updated.CreatedAt = existing.CreatedAt - updated.MailingListUID = existing.MailingListUID // Parent reference is immutable - updated.Email = existing.Email // Email is immutable due to unique constraint - - // Update timestamp - updated.UpdatedAt = time.Now() - - slog.DebugContext(ctx, "member data merged", - "member_uid", existing.UID, - "mutable_fields", []string{"status", "display_name"}, - ) -} - -// handleMemberCreationBySource handles member creation based on source type (API, webhook, or mock) -func (o *grpsIOWriterOrchestrator) handleMemberCreationBySource( - ctx context.Context, - member *model.GrpsIOMember, - mailingList *model.GrpsIOMailingList, -) (memberID *int64, groupID *int64, requiresCleanup bool, err error) { - if member == nil || mailingList == nil { - return nil, nil, false, errs.NewValidation("member and mailing list are required for creation") - } - - if member.Source == constants.SourceMock { - // Mock source: Skip Groups.io creation for testing - slog.InfoContext(ctx, "skipping Groups.io creation", - "member_uid", member.UID) - return nil, nil, false, nil - } - - if member.Source == constants.SourceWebhook { - // Webhook source: Skip Groups.io creation (webhook is source of truth) - // Use pre-provided IDs from webhook event - slog.InfoContext(ctx, "skipping Groups.io creation for webhook source", - "member_uid", member.UID, "source", member.Source) - return member.MemberID, member.GroupID, false, nil - } - - // Build logger with safe group_id handling - logger := slog.With("member_uid", member.UID, "source", member.Source) - if mailingList.GroupID != nil { - logger = logger.With("group_id", *mailingList.GroupID) - } - - // API source: Create member in Groups.io via API - // Guard: Skip if client not available or mailing list not synced - if o.groupsClient == nil || mailingList.GroupID == nil { - logger.InfoContext(ctx, "Groups.io client unavailable or mailing list not synced, treating as mock", - "email", redaction.RedactEmail(member.Email)) - return nil, nil, false, nil - } - - // Get parent service for Groups.io operations - parentService, _, err := o.grpsIOReader.GetGrpsIOService(ctx, mailingList.ServiceUID) - if err != nil { - logger.ErrorContext(ctx, "failed to get parent service for Groups.io sync", "error", err, "service_uid", mailingList.ServiceUID) - return nil, nil, false, err - } - - logger.InfoContext(ctx, "creating member in Groups.io", - "email", redaction.RedactEmail(member.Email)) - - // Create member in Groups.io - memberID, groupID, err = o.createMemberInGroupsIO(ctx, member, mailingList, parentService) - if err != nil { - logger.ErrorContext(ctx, "failed to create member in Groups.io", - "error", err, - "email", redaction.RedactEmail(member.Email)) - return nil, nil, false, err - } - - // Determine if cleanup is required for rollback - requiresCleanup = memberID != nil && parentService.GroupID != nil - - if memberID != nil { - logger.InfoContext(ctx, "member created successfully in Groups.io", - "member_id", *memberID) - } - - return memberID, groupID, requiresCleanup, nil -} - -// handleMemberUpdateBySource handles member updates based on source type (API, webhook, or mock) -// Returns error if Groups.io sync fails, but does not fail the overall update operation -func (o *grpsIOWriterOrchestrator) handleMemberUpdateBySource(ctx context.Context, member *model.GrpsIOMember) error { - if member == nil { - return errs.NewValidation("member is required for update") - } - - if member.Source == constants.SourceMock { - // Mock source: Skip Groups.io sync for testing - slog.InfoContext(ctx, "skipping Groups.io sync", - "member_uid", member.UID, "source", member.Source) - return nil - } - - if member.Source == constants.SourceWebhook { - // Webhook source: Skip Groups.io sync (webhook is source of truth) - slog.InfoContext(ctx, "skipping Groups.io sync for webhook source", - "member_uid", member.UID, "source", member.Source) - return nil - } - - logger := slog.With("member_uid", member.UID, "source", member.Source) - - // API source: Sync updates to Groups.io - // Guard: Skip if client not available or member not synced - if o.groupsClient == nil || member.MemberID == nil { - logger.InfoContext(ctx, "Groups.io integration disabled or member not synced - skipping Groups.io update") - return nil - } - logger = logger.With("member_id", *member.MemberID) - - // Get domain using helper method through member lookup chain - domain, err := o.getGroupsIODomainForResource(ctx, member.UID, constants.ResourceTypeMember) - if err != nil { - logger.WarnContext(ctx, "Groups.io member sync skipped due to domain lookup failure", - "error", err) - return err - } - logger = logger.With("domain", domain) - - groupsioModStatus := "" - switch member.ModStatus { - case "moderator": - groupsioModStatus = "sub_modstatus_moderator" - case "owner": - groupsioModStatus = "sub_modstatus_owner" - case "none": - groupsioModStatus = "sub_modstatus_none" - default: - return errs.NewValidation(fmt.Sprintf("invalid mod status: %s", member.ModStatus)) - } - - groupsioDeliveryMode := "" - switch member.DeliveryMode { - case "normal": - groupsioDeliveryMode = "email_delivery_single" - case "digest": - groupsioDeliveryMode = "email_delivery_digest" - case "none": - groupsioDeliveryMode = "email_delivery_none" - default: - return errs.NewValidation(fmt.Sprintf("invalid delivery mode: %s", member.DeliveryMode)) - } - - memberUpdates := groupsio.MemberUpdateOptions{ - FullName: strings.TrimSpace(fmt.Sprintf("%s %s", member.FirstName, member.LastName)), - ModStatus: groupsioModStatus, - DeliveryMode: groupsioDeliveryMode, - } - - // Perform Groups.io member update - err = o.groupsClient.UpdateMember(ctx, domain, utils.Int64PtrToUint64(member.MemberID), memberUpdates) - if err != nil { - logger.WarnContext(ctx, "Groups.io member update failed", - "error", err) - return err - } - - logger.InfoContext(ctx, "Groups.io member updated successfully") - return nil -} - -// handleMemberDeletionBySource handles member deletion based on source type (API, webhook, or mock) -// Returns error if Groups.io deletion fails -func (o *grpsIOWriterOrchestrator) handleMemberDeletionBySource(ctx context.Context, member *model.GrpsIOMember) error { - if member == nil { - return errs.NewValidation("member is required for deletion") - } - - if member.Source == constants.SourceMock { - // Mock source: Skip Groups.io deletion for testing - slog.InfoContext(ctx, "skipping Groups.io deletion", - "member_uid", member.UID) - return nil - } - - if member.Source == constants.SourceWebhook { - // Webhook source: Skip Groups.io deletion (webhook is source of truth) - slog.InfoContext(ctx, "skipping Groups.io deletion for webhook source", - "member_uid", member.UID, "source", member.Source) - return nil - } - - logger := slog.With("member_uid", member.UID, "source", member.Source) - if member.GroupID != nil { - logger = logger.With("group_id", *member.GroupID) - } - if member.MemberID != nil { - logger = logger.With("member_id", *member.MemberID) - } - - logger.InfoContext(ctx, "removing member from Groups.io") - return o.removeMemberFromGroupsIO(ctx, member) -} - -// updateMailingListSubscriberCount refreshes the subscriber count from Groups.io and falls back to NATS member item count, -// with retry logic for concurrent updates (max 3 attempts) -// This is a best-effort operation - failures are logged but don't fail the member operation -func (o *grpsIOWriterOrchestrator) updateMailingListSubscriberCount( - ctx context.Context, - mailingListUID string, -) { - const maxRetries = 3 - - for attempt := 1; attempt <= maxRetries; attempt++ { - // Read current mailing list with revision - mailingList, revision, err := o.grpsIOReader.GetGrpsIOMailingList(ctx, mailingListUID) - if err != nil { - slog.WarnContext(ctx, "failed to read mailing list for subscriber count update", - "error", err, "mailing_list_uid", mailingListUID, "attempt", attempt) - return - } - - // Fetch fresh count from Groups.io (or NATS fallback) instead of incrementing - oldCount := mailingList.SubscriberCount - newCount := o.refreshSubscriberCount(ctx, mailingList) - - // Update subscriber count - mailingList.SubscriberCount = newCount - - // Update with revision check (optimistic concurrency control) - _, _, err = o.grpsIOWriter.UpdateGrpsIOMailingList(ctx, mailingListUID, mailingList, revision) - if err != nil { - var conflictErr errs.Conflict - if stdErrors.As(err, &conflictErr) && attempt < maxRetries { - slog.InfoContext(ctx, "concurrent update detected for subscriber count, retrying", - "mailing_list_uid", mailingListUID, "attempt", attempt) - continue - } - slog.WarnContext(ctx, "failed to update subscriber count after retries", - "error", err, "mailing_list_uid", mailingListUID, "attempt", attempt) - return - } - - slog.InfoContext(ctx, "subscriber count updated successfully", - "mailing_list_uid", mailingListUID, "old_count", oldCount, "new_count", newCount) - - if o.publisher != nil { - // Publish indexer message with updated subscriber count (best-effort) - indexerMessage := &model.IndexerMessage{ - Action: model.ActionUpdated, - Tags: mailingList.Tags(), - } - - builtMessage, err := indexerMessage.Build(ctx, mailingList) - if err != nil { - slog.WarnContext(ctx, "failed to build indexer message for subscriber count update", - "error", err, "mailing_list_uid", mailingListUID) - return - } - if err := o.publisher.Indexer(ctx, constants.IndexGroupsIOMailingListSubject, builtMessage); err != nil { - slog.WarnContext(ctx, "failed to publish indexer message for subscriber count update", - "error", err, "mailing_list_uid", mailingListUID) - // Don't fail - the count update succeeded, indexer message is best-effort - } - } - - return - } -} diff --git a/internal/service/grpsio_member_writer_test.go b/internal/service/grpsio_member_writer_test.go deleted file mode 100644 index 0d091b3..0000000 --- a/internal/service/grpsio_member_writer_test.go +++ /dev/null @@ -1,804 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/mock" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - errs "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" -) - -func TestGrpsIOWriterOrchestrator_CreateGrpsIOMember(t *testing.T) { - testCases := []struct { - name string - setupMock func(*mock.MockRepository) - inputMember *model.GrpsIOMember - expectedError error - validate func(t *testing.T, result *model.GrpsIOMember, revision uint64, mockRepo *mock.MockRepository) - }{ - { - name: "successful committee member creation", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - // Add a mailing list for the member to belong to - testMailingList := &model.GrpsIOMailingList{ - UID: "mailing-list-1", - ServiceUID: "service-1", - Title: "Test Committee List", - Description: "Test committee mailing list", - Type: "discussion_open", - CreatedAt: time.Now().Add(-24 * time.Hour), - UpdatedAt: time.Now(), - } - mockRepo.AddMailingList(testMailingList) - }, - inputMember: &model.GrpsIOMember{ - MailingListUID: "mailing-list-1", - MemberID: writerInt64Ptr(12345), - GroupID: writerInt64Ptr(67890), - Username: "committee-member", - FirstName: "Committee", - LastName: "Member", - Email: "committee.member@example.com", - Organization: "Test Organization", - JobTitle: "Committee Chair", - MemberType: "committee", - DeliveryMode: "individual", - ModStatus: "none", - Status: "normal", - Source: constants.SourceWebhook, // Webhook source preserves pre-provided IDs - }, - expectedError: nil, - validate: func(t *testing.T, result *model.GrpsIOMember, revision uint64, mockRepo *mock.MockRepository) { - assert.NotEmpty(t, result.UID) - assert.Equal(t, "mailing-list-1", result.MailingListUID) - require.NotNil(t, result.MemberID) - assert.Equal(t, int64(12345), *result.MemberID) - assert.Equal(t, "committee-member", result.Username) - assert.Equal(t, "Committee", result.FirstName) - assert.Equal(t, "Member", result.LastName) - assert.Equal(t, "committee.member@example.com", result.Email) - assert.Equal(t, "committee", result.MemberType) - assert.Equal(t, "normal", result.Status) - assert.Equal(t, uint64(1), revision) - assert.Equal(t, 1, mockRepo.GetMemberCount()) - assert.NotZero(t, result.CreatedAt) - assert.NotZero(t, result.UpdatedAt) - }, - }, - { - name: "successful direct member creation with minimal fields", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - // Add a mailing list for the member to belong to - testMailingList := &model.GrpsIOMailingList{ - UID: "mailing-list-2", - ServiceUID: "service-2", - Title: "Test Direct List", - Description: "Test direct mailing list", - Type: "discussion_moderated", - CreatedAt: time.Now().Add(-24 * time.Hour), - UpdatedAt: time.Now(), - } - mockRepo.AddMailingList(testMailingList) - }, - inputMember: &model.GrpsIOMember{ - MailingListUID: "mailing-list-2", - FirstName: "Direct", - LastName: "Member", - Email: "direct.member@example.com", - MemberType: "direct", - Source: constants.SourceMock, - }, - expectedError: nil, - validate: func(t *testing.T, result *model.GrpsIOMember, revision uint64, mockRepo *mock.MockRepository) { - assert.NotEmpty(t, result.UID) - assert.Equal(t, "mailing-list-2", result.MailingListUID) - assert.Equal(t, "Direct", result.FirstName) - assert.Equal(t, "Member", result.LastName) - assert.Equal(t, "direct.member@example.com", result.Email) - assert.Equal(t, "direct", result.MemberType) - assert.Equal(t, "pending", result.Status) // Default status should be set - assert.Equal(t, uint64(1), revision) - assert.Equal(t, 1, mockRepo.GetMemberCount()) - }, - }, - { - name: "member creation with server-generated UID", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - // Add a mailing list for the member to belong to - testMailingList := &model.GrpsIOMailingList{ - UID: "mailing-list-3", - ServiceUID: "service-3", - Title: "Test Server Generated UID List", - Description: "Test mailing list with server-generated UID member", - Type: "created", - CreatedAt: time.Now().Add(-24 * time.Hour), - UpdatedAt: time.Now(), - } - mockRepo.AddMailingList(testMailingList) - }, - inputMember: &model.GrpsIOMember{ - UID: "client-provided-uid", // This should be ignored - MailingListUID: "mailing-list-3", - FirstName: "Server", - LastName: "Generated", - Email: "servergen@example.com", - MemberType: "committee", - Status: "normal", - Source: constants.SourceMock, - }, - expectedError: nil, - validate: func(t *testing.T, result *model.GrpsIOMember, revision uint64, mockRepo *mock.MockRepository) { - assert.NotEqual(t, "client-provided-uid", result.UID) // Should NOT preserve client UID - assert.NotEmpty(t, result.UID) // Should have server-generated UID - assert.Equal(t, "mailing-list-3", result.MailingListUID) - assert.Equal(t, "normal", result.Status) // Should preserve provided status - assert.Equal(t, uint64(1), revision) - assert.Equal(t, 1, mockRepo.GetMemberCount()) - }, - }, - { - name: "mailing list not found error", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - // Don't add any mailing lists - }, - inputMember: &model.GrpsIOMember{ - MailingListUID: "nonexistent-mailing-list", - FirstName: "Test", - LastName: "Member", - Email: "test@example.com", - MemberType: "committee", - }, - expectedError: errs.NotFound{}, - validate: func(t *testing.T, result *model.GrpsIOMember, revision uint64, mockRepo *mock.MockRepository) { - assert.Nil(t, result) - assert.Equal(t, uint64(0), revision) - assert.Equal(t, 0, mockRepo.GetMemberCount()) - }, - }, - { - name: "empty mailing list UID error", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - }, - inputMember: &model.GrpsIOMember{ - MailingListUID: "", // Empty mailing list UID - FirstName: "Test", - LastName: "Member", - Email: "test@example.com", - MemberType: "committee", - }, - expectedError: errs.Validation{}, - validate: func(t *testing.T, result *model.GrpsIOMember, revision uint64, mockRepo *mock.MockRepository) { - assert.Nil(t, result) - assert.Equal(t, uint64(0), revision) - assert.Equal(t, 0, mockRepo.GetMemberCount()) - }, - }, - { - name: "member with complete audit fields", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - // Add a mailing list for the member to belong to - testMailingList := &model.GrpsIOMailingList{ - UID: "mailing-list-audit", - ServiceUID: "service-audit", - Title: "Test Audit List", - Description: "Test audit mailing list", - Type: "created", - CreatedAt: time.Now().Add(-24 * time.Hour), - UpdatedAt: time.Now(), - } - mockRepo.AddMailingList(testMailingList) - }, - inputMember: &model.GrpsIOMember{ - MailingListUID: "mailing-list-audit", - MemberID: writerInt64Ptr(99999), - GroupID: writerInt64Ptr(88888), - Username: "audit-member", - FirstName: "Audit", - LastName: "Member", - Email: "audit.member@example.com", - Organization: "Audit Organization", - JobTitle: "Audit Manager", - MemberType: "committee", - DeliveryMode: "digest", - ModStatus: "moderator", - Status: "normal", - LastReviewedAt: writerStringPtr("2024-01-01T00:00:00Z"), - LastReviewedBy: writerStringPtr("reviewer-uid"), - Source: constants.SourceWebhook, // Webhook source preserves pre-provided IDs - }, - expectedError: nil, - validate: func(t *testing.T, result *model.GrpsIOMember, revision uint64, mockRepo *mock.MockRepository) { - assert.NotEmpty(t, result.UID) - assert.Equal(t, "mailing-list-audit", result.MailingListUID) - require.NotNil(t, result.MemberID) - assert.Equal(t, int64(99999), *result.MemberID) - require.NotNil(t, result.GroupID) - assert.Equal(t, int64(88888), *result.GroupID) - assert.Equal(t, "audit-member", result.Username) - assert.Equal(t, "Audit", result.FirstName) - assert.Equal(t, "Member", result.LastName) - assert.Equal(t, "audit.member@example.com", result.Email) - assert.Equal(t, "Audit Organization", result.Organization) - assert.Equal(t, "Audit Manager", result.JobTitle) - assert.Equal(t, "committee", result.MemberType) - assert.Equal(t, "digest", result.DeliveryMode) - assert.Equal(t, "moderator", result.ModStatus) - assert.Equal(t, "normal", result.Status) - assert.NotNil(t, result.LastReviewedAt) - assert.Equal(t, "2024-01-01T00:00:00Z", *result.LastReviewedAt) - assert.NotNil(t, result.LastReviewedBy) - assert.Equal(t, "reviewer-uid", *result.LastReviewedBy) - assert.Equal(t, uint64(1), revision) - assert.Equal(t, 1, mockRepo.GetMemberCount()) - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - mockRepo := mock.NewMockRepository() - tc.setupMock(mockRepo) - - // Create writer orchestrator with all dependencies - writer := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(mock.NewMockGrpsIOReader(mockRepo)), - WithGrpsIOWriter(mock.NewMockGrpsIOWriter(mockRepo)), - WithMemberRepository(mock.NewMockGrpsIOMemberRepository(mockRepo)), - WithEntityAttributeReader(mock.NewMockEntityAttributeReader(mockRepo)), - WithPublisher(mock.NewMockMessagePublisher()), - ) - - // Execute - result, revision, err := writer.CreateGrpsIOMember(context.Background(), tc.inputMember) - - // Validate - if tc.expectedError != nil { - require.Error(t, err) - assert.IsType(t, tc.expectedError, err) - } else { - require.NoError(t, err) - } - - tc.validate(t, result, revision, mockRepo) - }) - } -} - -func TestGrpsIOWriterOrchestrator_UpdateGrpsIOMember(t *testing.T) { - ctx := context.Background() - mockRepo := mock.NewMockRepository() - - t.Run("successful member update", func(t *testing.T) { - mockRepo.ClearAll() - - // Add existing member - existingMember := &model.GrpsIOMember{ - UID: "test-member", - MailingListUID: "test-list", - FirstName: "Original", - LastName: "Member", - Email: "original@example.com", - MemberType: "committee", - Status: "normal", - Source: constants.SourceMock, - CreatedAt: time.Now().Add(-24 * time.Hour), - UpdatedAt: time.Now().Add(-1 * time.Hour), - } - mockRepo.AddMember(existingMember) - - // Create writer orchestrator - writer := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(mock.NewMockGrpsIOReader(mockRepo)), - WithGrpsIOWriter(mock.NewMockGrpsIOWriter(mockRepo)), - WithMemberRepository(mock.NewMockGrpsIOMemberRepository(mockRepo)), - WithPublisher(mock.NewMockMessagePublisher()), - ) - - // Execute update - updatedMember := &model.GrpsIOMember{ - UID: "test-member", - MailingListUID: "test-list", - FirstName: "Updated", - LastName: "Member", - Email: "original@example.com", // Email should remain the same (immutable) - MemberType: "committee", - Status: "normal", - Source: constants.SourceMock, - CreatedAt: existingMember.CreatedAt, // Preserve created time - UpdatedAt: time.Now(), // This will be set by the orchestrator - } - - result, revision, err := writer.UpdateGrpsIOMember(ctx, "test-member", updatedMember, 1) - - // Validate - require.NoError(t, err) - assert.NotNil(t, result) - assert.Equal(t, uint64(2), revision) // Mock increments revision - assert.Equal(t, "Updated", result.FirstName) - assert.Equal(t, "Member", result.LastName) - assert.Equal(t, "original@example.com", result.Email) - assert.False(t, result.UpdatedAt.IsZero()) - }) - - t.Run("update non-existent member", func(t *testing.T) { - mockRepo.ClearAll() - - // Create writer orchestrator - writer := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(mock.NewMockGrpsIOReader(mockRepo)), - WithGrpsIOWriter(mock.NewMockGrpsIOWriter(mockRepo)), - WithMemberRepository(mock.NewMockGrpsIOMemberRepository(mockRepo)), - WithPublisher(mock.NewMockMessagePublisher()), - ) - - // Execute update on non-existent member - member := &model.GrpsIOMember{ - UID: "non-existent", - MailingListUID: "test-list", - FirstName: "Updated", - LastName: "Member", - Email: "updated@example.com", - MemberType: "committee", - Source: constants.SourceMock, - } - - result, revision, err := writer.UpdateGrpsIOMember(ctx, "non-existent", member, 1) - - // Validate - require.Error(t, err) - assert.IsType(t, errs.NotFound{}, err) - assert.Nil(t, result) - assert.Equal(t, uint64(0), revision) - }) -} - -func TestGrpsIOWriterOrchestrator_DeleteGrpsIOMember(t *testing.T) { - ctx := context.Background() - mockRepo := mock.NewMockRepository() - - t.Run("successful member deletion", func(t *testing.T) { - mockRepo.ClearAll() - - // Add existing member - existingMember := &model.GrpsIOMember{ - UID: "test-member", - MailingListUID: "test-list", - FirstName: "Test", - LastName: "Member", - Email: "test@example.com", - MemberType: "committee", - Status: "normal", - Source: constants.SourceMock, - CreatedAt: time.Now().Add(-24 * time.Hour), - UpdatedAt: time.Now().Add(-1 * time.Hour), - } - mockRepo.AddMember(existingMember) - - // Create writer orchestrator - writer := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(mock.NewMockGrpsIOReader(mockRepo)), - WithGrpsIOWriter(mock.NewMockGrpsIOWriter(mockRepo)), - WithMemberRepository(mock.NewMockGrpsIOMemberRepository(mockRepo)), - WithPublisher(mock.NewMockMessagePublisher()), - ) - - // Execute delete - err := writer.DeleteGrpsIOMember(ctx, "test-member", 1, existingMember) - - // Validate - require.NoError(t, err) - - // Verify member is deleted (should not be found) using reader orchestrator - reader := NewGrpsIOReaderOrchestrator( - WithGrpsIOReader(mock.NewMockGrpsIOReader(mockRepo)), - ) - _, _, err = reader.GetGrpsIOMember(ctx, "test-member") - require.Error(t, err) - assert.IsType(t, errs.NotFound{}, err) - }) - - t.Run("delete non-existent member", func(t *testing.T) { - mockRepo.ClearAll() - - // Create writer orchestrator - writer := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(mock.NewMockGrpsIOReader(mockRepo)), - WithGrpsIOWriter(mock.NewMockGrpsIOWriter(mockRepo)), - WithMemberRepository(mock.NewMockGrpsIOMemberRepository(mockRepo)), - WithPublisher(mock.NewMockMessagePublisher()), - ) - - // Execute delete on non-existent member with nil member (validation error) - err := writer.DeleteGrpsIOMember(ctx, "non-existent", 1, nil) - - // Validate - should return validation error for nil member - require.Error(t, err) - assert.IsType(t, errs.Validation{}, err) - }) - - t.Run("delete with wrong revision", func(t *testing.T) { - mockRepo.ClearAll() - - // Add existing member - existingMember := &model.GrpsIOMember{ - UID: "test-member", - MailingListUID: "test-list", - FirstName: "Test", - LastName: "Member", - Email: "test@example.com", - MemberType: "committee", - Status: "normal", - Source: constants.SourceMock, - CreatedAt: time.Now().Add(-24 * time.Hour), - UpdatedAt: time.Now().Add(-1 * time.Hour), - } - mockRepo.AddMember(existingMember) - - // Create writer orchestrator - writer := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(mock.NewMockGrpsIOReader(mockRepo)), - WithGrpsIOWriter(mock.NewMockGrpsIOWriter(mockRepo)), - WithMemberRepository(mock.NewMockGrpsIOMemberRepository(mockRepo)), - WithPublisher(mock.NewMockMessagePublisher()), - ) - - // Execute delete with wrong revision (mock expects revision 1, but we pass 999) - err := writer.DeleteGrpsIOMember(ctx, "test-member", 999, existingMember) - - // Validate - mock should return conflict error for revision mismatch - require.Error(t, err) - assert.IsType(t, errs.Conflict{}, err) - }) -} - -// Test member creation with duplicate email in same mailing list -func TestGrpsIOWriterOrchestrator_CreateGrpsIOMember_DuplicateEmail(t *testing.T) { - ctx := context.Background() - mockRepo := mock.NewMockRepository() - - t.Run("duplicate email in same mailing list should succeed in mock", func(t *testing.T) { - mockRepo.ClearAll() - - // Add a mailing list - testMailingList := &model.GrpsIOMailingList{ - UID: "test-mailing-list", - ServiceUID: "test-service", - Title: "Test List", - Description: "Test mailing list for duplicate email", - Type: "created", - CreatedAt: time.Now().Add(-24 * time.Hour), - UpdatedAt: time.Now(), - } - mockRepo.AddMailingList(testMailingList) - - // Create writer orchestrator - writer := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(mock.NewMockGrpsIOReader(mockRepo)), - WithGrpsIOWriter(mock.NewMockGrpsIOWriter(mockRepo)), - WithMemberRepository(mock.NewMockGrpsIOMemberRepository(mockRepo)), - WithPublisher(mock.NewMockMessagePublisher()), - ) - - sharedEmail := "duplicate@example.com" - - // Create first member - member1 := &model.GrpsIOMember{ - MailingListUID: "test-mailing-list", - FirstName: "First", - LastName: "Member", - Email: sharedEmail, - MemberType: "committee", - Status: "normal", - Source: constants.SourceMock, - } - - result1, revision1, err1 := writer.CreateGrpsIOMember(ctx, member1) - require.NoError(t, err1) // Mock allows duplicate - assert.NotEmpty(t, result1.UID) - assert.Equal(t, uint64(1), revision1) - - // Create second member with same email (should return existing member - idempotent) - member2 := &model.GrpsIOMember{ - MailingListUID: "test-mailing-list", - FirstName: "Second", - LastName: "Member", - Email: sharedEmail, - MemberType: "direct", - Status: "pending", - Source: constants.SourceMock, - } - - result2, revision2, err2 := writer.CreateGrpsIOMember(ctx, member2) - require.NoError(t, err2) // Idempotent - returns existing member - assert.NotEmpty(t, result2.UID) - assert.Equal(t, result1.UID, result2.UID) // Same UID (idempotent) - assert.Equal(t, uint64(1), revision2) - assert.Equal(t, 1, mockRepo.GetMemberCount()) // Only one member stored (idempotent) - }) -} - -// Test member creation validation scenarios -func TestGrpsIOWriterOrchestrator_CreateGrpsIOMember_ValidationScenarios(t *testing.T) { - ctx := context.Background() - - validationTests := []struct { - name string - setupMock func(*mock.MockRepository) - inputMember *model.GrpsIOMember - expectedError bool - errorContains string - validateResult func(*testing.T, *model.GrpsIOMember, uint64) - }{ - { - name: "member with all required fields", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - testMailingList := &model.GrpsIOMailingList{ - UID: "valid-list", - ServiceUID: "valid-service", - Title: "Valid List", - Type: "created", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - mockRepo.AddMailingList(testMailingList) - }, - inputMember: &model.GrpsIOMember{ - MailingListUID: "valid-list", - FirstName: "Valid", - LastName: "Member", - Email: "valid@example.com", - MemberType: "committee", - Source: constants.SourceMock, - }, - expectedError: false, - validateResult: func(t *testing.T, result *model.GrpsIOMember, revision uint64) { - assert.NotNil(t, result) - assert.NotEmpty(t, result.UID) - assert.Equal(t, "pending", result.Status) // Default status should be set - assert.Equal(t, uint64(1), revision) - }, - }, - { - name: "member creation with message publishing", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - testMailingList := &model.GrpsIOMailingList{ - UID: "messaging-list", - ServiceUID: "messaging-service", - Title: "Messaging List", - Type: "created", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - mockRepo.AddMailingList(testMailingList) - }, - inputMember: &model.GrpsIOMember{ - MailingListUID: "messaging-list", - Username: "messaging-user", - FirstName: "Messaging", - LastName: "Member", - Email: "messaging@example.com", - MemberType: "committee", - Status: "normal", - Source: constants.SourceMock, - }, - expectedError: false, - validateResult: func(t *testing.T, result *model.GrpsIOMember, revision uint64) { - assert.NotNil(t, result) - assert.Equal(t, "messaging-user", result.Username) - assert.Equal(t, "normal", result.Status) // Should preserve provided status - assert.Equal(t, uint64(1), revision) - }, - }, - } - - for _, tt := range validationTests { - t.Run(tt.name, func(t *testing.T) { - mockRepo := mock.NewMockRepository() - tt.setupMock(mockRepo) - - // Create writer orchestrator with message publisher - writer := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(mock.NewMockGrpsIOReader(mockRepo)), - WithGrpsIOWriter(mock.NewMockGrpsIOWriter(mockRepo)), - WithMemberRepository(mock.NewMockGrpsIOMemberRepository(mockRepo)), - WithEntityAttributeReader(mock.NewMockEntityAttributeReader(mockRepo)), - WithPublisher(mock.NewMockMessagePublisher()), - ) - - // Execute - result, revision, err := writer.CreateGrpsIOMember(ctx, tt.inputMember) - - // Validate - if tt.expectedError { - require.Error(t, err) - if tt.errorContains != "" { - assert.Contains(t, err.Error(), tt.errorContains) - } - } else { - require.NoError(t, err) - } - - tt.validateResult(t, result, revision) - }) - } -} - -// Test member creation with different member types -func TestGrpsIOWriterOrchestrator_CreateGrpsIOMember_MemberTypes(t *testing.T) { - ctx := context.Background() - - memberTypes := []struct { - memberType string - description string - }{ - {"committee", "Committee member type"}, - {"direct", "Direct member type"}, - } - - for _, mt := range memberTypes { - t.Run(mt.description, func(t *testing.T) { - mockRepo := mock.NewMockRepository() - mockRepo.ClearAll() - - // Add a mailing list - testMailingList := &model.GrpsIOMailingList{ - UID: "type-test-list", - ServiceUID: "type-test-service", - Title: "Type Test List", - Type: "created", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - mockRepo.AddMailingList(testMailingList) - - // Create writer orchestrator - writer := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(mock.NewMockGrpsIOReader(mockRepo)), - WithGrpsIOWriter(mock.NewMockGrpsIOWriter(mockRepo)), - WithMemberRepository(mock.NewMockGrpsIOMemberRepository(mockRepo)), - WithEntityAttributeReader(mock.NewMockEntityAttributeReader(mockRepo)), - WithPublisher(mock.NewMockMessagePublisher()), - ) - - // Create member with specific type - member := &model.GrpsIOMember{ - MailingListUID: "type-test-list", - FirstName: "Type", - LastName: "Test", - Email: "type.test@example.com", - MemberType: mt.memberType, - Source: constants.SourceMock, - } - - result, revision, err := writer.CreateGrpsIOMember(ctx, member) - - // Validate - require.NoError(t, err) - assert.NotNil(t, result) - assert.Equal(t, mt.memberType, result.MemberType) - assert.Equal(t, uint64(1), revision) - assert.Equal(t, 1, mockRepo.GetMemberCount()) - }) - } -} - -// Helper function to create string pointer -func writerStringPtr(s string) *string { - return &s -} - -// Helper function to create int64 pointer -func writerInt64Ptr(i int64) *int64 { - return &i -} - -// TestGrpsIOWriterOrchestrator_handleMemberUpdateBySource tests the handleMemberUpdateBySource method -// which dispatches member updates based on source type (API, webhook, or mock) -func TestGrpsIOWriterOrchestrator_handleMemberUpdateBySource(t *testing.T) { - testCases := []struct { - name string - setupMocks func() *grpsIOWriterOrchestrator - member *model.GrpsIOMember - expectError bool - }{ - { - name: "API source - skip sync when Groups.io client is nil", - setupMocks: func() *grpsIOWriterOrchestrator { - return &grpsIOWriterOrchestrator{ - groupsClient: nil, // No client - should skip gracefully - } - }, - member: &model.GrpsIOMember{ - UID: "member-1", - Source: constants.SourceAPI, - MemberID: func() *int64 { i := int64(12345); return &i }(), - FirstName: "John", - LastName: "Doe", - }, - expectError: false, - }, - { - name: "API source - skip sync when member MemberID is nil", - setupMocks: func() *grpsIOWriterOrchestrator { - return &grpsIOWriterOrchestrator{ - groupsClient: nil, - } - }, - member: &model.GrpsIOMember{ - UID: "member-2", - Source: constants.SourceAPI, - MemberID: nil, // No member ID - should skip gracefully - FirstName: "Jane", - LastName: "Smith", - }, - expectError: false, - }, - { - name: "webhook source - skip sync (webhook is source of truth)", - setupMocks: func() *grpsIOWriterOrchestrator { - return &grpsIOWriterOrchestrator{ - groupsClient: nil, - } - }, - member: &model.GrpsIOMember{ - UID: "member-3", - Source: constants.SourceWebhook, - MemberID: func() *int64 { i := int64(12345); return &i }(), - FirstName: "Bob", - LastName: "Johnson", - }, - expectError: false, - }, - { - name: "mock source - skip sync for testing", - setupMocks: func() *grpsIOWriterOrchestrator { - return &grpsIOWriterOrchestrator{ - groupsClient: nil, - } - }, - member: &model.GrpsIOMember{ - UID: "member-4", - Source: constants.SourceMock, - FirstName: "Alice", - LastName: "Williams", - }, - expectError: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - ctx := context.Background() - orchestrator := tc.setupMocks() - - // Execute - err := orchestrator.handleMemberUpdateBySource(ctx, tc.member) - - // Verify - if tc.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/internal/service/grpsio_reader.go b/internal/service/grpsio_reader.go deleted file mode 100644 index f9519d3..0000000 --- a/internal/service/grpsio_reader.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" -) - -// GrpsIOReader defines the composite interface that combines readers -type GrpsIOReader interface { - GrpsIOServiceReader - GrpsIOMailingListReader - GrpsIOMemberReader -} - -// GrpsIOServiceReader defines the interface for service read operations -type GrpsIOServiceReader interface { - // GetGrpsIOService retrieves a single service by ID and returns the revision - GetGrpsIOService(ctx context.Context, uid string) (*model.GrpsIOService, uint64, error) - // GetRevision retrieves only the revision for a given UID - GetRevision(ctx context.Context, uid string) (uint64, error) - // GetServicesByGroupID retrieves all services for a given GroupsIO parent group ID - GetServicesByGroupID(ctx context.Context, groupID uint64) ([]*model.GrpsIOService, error) - // GetServicesByProjectUID retrieves all services for a given project UID - GetServicesByProjectUID(ctx context.Context, projectUID string) ([]*model.GrpsIOService, error) - // GetGrpsIOServiceSettings retrieves service settings by service UID - GetGrpsIOServiceSettings(ctx context.Context, uid string) (*model.GrpsIOServiceSettings, uint64, error) - // GetSettingsRevision retrieves only the revision for service settings - GetSettingsRevision(ctx context.Context, uid string) (uint64, error) -} - -// GrpsIOMailingListReader defines the interface for mailing list read operations -type GrpsIOMailingListReader interface { - // GetGrpsIOMailingList retrieves a single mailing list by UID with revision - GetGrpsIOMailingList(ctx context.Context, uid string) (*model.GrpsIOMailingList, uint64, error) - // GetMailingListRevision retrieves only the revision for a given UID - GetMailingListRevision(ctx context.Context, uid string) (uint64, error) - // GetMailingListByGroupID retrieves a mailing list by GroupsIO subgroup ID - GetMailingListByGroupID(ctx context.Context, groupID uint64) (*model.GrpsIOMailingList, uint64, error) - // GetGrpsIOMailingListSettings retrieves mailing list settings by UID with revision - GetGrpsIOMailingListSettings(ctx context.Context, uid string) (*model.GrpsIOMailingListSettings, uint64, error) - // GetMailingListSettingsRevision retrieves only the revision for mailing list settings - GetMailingListSettingsRevision(ctx context.Context, uid string) (uint64, error) -} - -// GrpsIOMemberReader defines the interface for member read operations -type GrpsIOMemberReader interface { - GetGrpsIOMember(ctx context.Context, uid string) (*model.GrpsIOMember, uint64, error) - GetMemberRevision(ctx context.Context, uid string) (uint64, error) -} - -// grpsIOReaderOrchestratorOption defines a function type for setting options on the composite orchestrator -type grpsIOReaderOrchestratorOption func(*grpsIOReaderOrchestrator) - -// WithGrpsIOReader sets the service reader orchestrator -func WithGrpsIOReader(reader port.GrpsIOReader) grpsIOReaderOrchestratorOption { - return func(r *grpsIOReaderOrchestrator) { - r.grpsIOReader = reader - } -} - -// grpsIOReaderOrchestrator is the composite orchestrator that delegates to individual orchestrators -type grpsIOReaderOrchestrator struct { - grpsIOReader port.GrpsIOReader -} - -// NewGrpsIOReaderOrchestrator creates a new composite reader orchestrator using the option pattern -func NewGrpsIOReaderOrchestrator(opts ...grpsIOReaderOrchestratorOption) GrpsIOReader { - rc := &grpsIOReaderOrchestrator{} - for _, opt := range opts { - opt(rc) - } - - // Fail fast if required dependency is missing - if rc.grpsIOReader == nil { - panic("grpsIOReader dependency is required") - } - // Note: grpsIOReader provides all operations including member operations - - return rc -} diff --git a/internal/service/grpsio_service_reader.go b/internal/service/grpsio_service_reader.go deleted file mode 100644 index 5045b1f..0000000 --- a/internal/service/grpsio_service_reader.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "log/slog" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" -) - -// GetGrpsIOService retrieves a single service by ID -func (sr *grpsIOReaderOrchestrator) GetGrpsIOService(ctx context.Context, uid string) (*model.GrpsIOService, uint64, error) { - slog.DebugContext(ctx, "executing get service use case", - "service_uid", uid, - ) - - // Get service from storage - service, revision, err := sr.grpsIOReader.GetGrpsIOService(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to get service", - "error", err, - "service_uid", uid, - ) - return nil, 0, err - } - - slog.DebugContext(ctx, "service retrieved successfully", - "service_uid", uid, - "revision", revision, - ) - - return service, revision, nil -} - -// GetRevision retrieves only the revision for a given UID -func (sr *grpsIOReaderOrchestrator) GetRevision(ctx context.Context, uid string) (uint64, error) { - slog.DebugContext(ctx, "executing get revision use case", - "service_uid", uid, - ) - - // Get revision from storage - revision, err := sr.grpsIOReader.GetRevision(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to get service revision", - "error", err, - "service_uid", uid, - ) - return 0, err - } - - slog.DebugContext(ctx, "service revision retrieved successfully", - "service_uid", uid, - "revision", revision, - ) - - return revision, nil -} - -// GetServicesByGroupID retrieves all services for a given GroupsIO parent group ID -func (sr *grpsIOReaderOrchestrator) GetServicesByGroupID(ctx context.Context, groupID uint64) ([]*model.GrpsIOService, error) { - slog.DebugContext(ctx, "executing get services by group_id use case", - "group_id", groupID, - ) - - // Get services from storage - services, err := sr.grpsIOReader.GetServicesByGroupID(ctx, groupID) - if err != nil { - slog.ErrorContext(ctx, "failed to get services by group_id", - "error", err, - "group_id", groupID, - ) - return nil, err - } - - slog.DebugContext(ctx, "services retrieved successfully by group_id", - "group_id", groupID, - "count", len(services), - ) - - return services, nil -} - -// GetServicesByProjectUID retrieves all services for a given project UID -func (sr *grpsIOReaderOrchestrator) GetServicesByProjectUID(ctx context.Context, projectUID string) ([]*model.GrpsIOService, error) { - slog.DebugContext(ctx, "executing get services by project_uid use case", - "project_uid", projectUID, - ) - - // Get services from storage - services, err := sr.grpsIOReader.GetServicesByProjectUID(ctx, projectUID) - if err != nil { - slog.ErrorContext(ctx, "failed to get services by project_uid", - "error", err, - "project_uid", projectUID, - ) - return nil, err - } - - slog.DebugContext(ctx, "services retrieved successfully by project_uid", - "project_uid", projectUID, - "count", len(services), - ) - - return services, nil -} - -// GetGrpsIOServiceSettings retrieves service settings by service UID -func (sr *grpsIOReaderOrchestrator) GetGrpsIOServiceSettings(ctx context.Context, uid string) (*model.GrpsIOServiceSettings, uint64, error) { - slog.DebugContext(ctx, "executing get service settings use case", - "service_uid", uid, - ) - - // Get settings from storage - settings, revision, err := sr.grpsIOReader.GetGrpsIOServiceSettings(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to get service settings", - "error", err, - "service_uid", uid, - ) - return nil, 0, err - } - - slog.DebugContext(ctx, "service settings retrieved successfully", - "service_uid", uid, - "revision", revision, - ) - - return settings, revision, nil -} - -// GetSettingsRevision retrieves only the revision for service settings -func (sr *grpsIOReaderOrchestrator) GetSettingsRevision(ctx context.Context, uid string) (uint64, error) { - slog.DebugContext(ctx, "executing get settings revision use case", - "service_uid", uid, - ) - - // Get revision from storage - revision, err := sr.grpsIOReader.GetSettingsRevision(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to get service settings revision", - "error", err, - "service_uid", uid, - ) - return 0, err - } - - slog.DebugContext(ctx, "service settings revision retrieved successfully", - "service_uid", uid, - "revision", revision, - ) - - return revision, nil -} diff --git a/internal/service/grpsio_service_reader_test.go b/internal/service/grpsio_service_reader_test.go deleted file mode 100644 index e852534..0000000 --- a/internal/service/grpsio_service_reader_test.go +++ /dev/null @@ -1,353 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "testing" - "time" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/mock" - errs "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" -) - -func TestGrpsIOReaderOrchestratorGetGrpsIOService(t *testing.T) { - ctx := context.Background() - mockRepo := mock.NewMockRepository() - - // Setup test data - testServiceUID := uuid.New().String() - testService := &model.GrpsIOService{ - Type: "primary", - UID: testServiceUID, - Domain: "lists.testproject.org", - GroupID: serviceInt64Ptr(12345), - Status: "created", - GlobalOwners: []string{"admin@testproject.org"}, - Prefix: "", - ProjectSlug: "test-project", - ProjectName: "Test Project", - ProjectUID: "test-project-uid", - URL: "https://lists.testproject.org", - GroupName: "test-project", - Public: true, - CreatedAt: time.Now().Add(-24 * time.Hour), - UpdatedAt: time.Now(), - } - - tests := []struct { - name string - setupMock func() - serviceUID string - expectedError bool - errorType error - validateService func(*testing.T, *model.GrpsIOService, uint64) - }{ - { - name: "successful service retrieval", - setupMock: func() { - mockRepo.ClearAll() - // Store the service in mock repository - mockRepo.AddService(testService) - }, - serviceUID: testServiceUID, - expectedError: false, - validateService: func(t *testing.T, service *model.GrpsIOService, revision uint64) { - assert.NotNil(t, service) - assert.Equal(t, testServiceUID, service.UID) - assert.Equal(t, "primary", service.Type) - assert.Equal(t, "lists.testproject.org", service.Domain) - assert.Equal(t, serviceInt64Ptr(12345), service.GroupID) - assert.Equal(t, "created", service.Status) - assert.Equal(t, []string{"admin@testproject.org"}, service.GlobalOwners) - assert.Equal(t, "", service.Prefix) - assert.Equal(t, "test-project", service.ProjectSlug) - assert.Equal(t, "Test Project", service.ProjectName) - assert.Equal(t, "test-project-uid", service.ProjectUID) - assert.Equal(t, "https://lists.testproject.org", service.URL) - assert.Equal(t, "test-project", service.GroupName) - assert.True(t, service.Public) - assert.NotZero(t, service.CreatedAt) - assert.NotZero(t, service.UpdatedAt) - assert.Equal(t, uint64(1), revision) // Mock returns revision 1 - }, - }, - { - name: "service not found error", - setupMock: func() { - mockRepo.ClearAll() - // Don't store any service - }, - serviceUID: "nonexistent-service-uid", - expectedError: true, - errorType: errs.NotFound{}, - validateService: func(t *testing.T, service *model.GrpsIOService, revision uint64) { - assert.Nil(t, service) - assert.Equal(t, uint64(0), revision) - }, - }, - { - name: "empty service UID", - setupMock: func() { - mockRepo.ClearAll() - }, - serviceUID: "", - expectedError: true, - errorType: errs.NotFound{}, - validateService: func(t *testing.T, service *model.GrpsIOService, revision uint64) { - assert.Nil(t, service) - assert.Equal(t, uint64(0), revision) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Setup - tt.setupMock() - - // Create reader orchestrator - reader := NewGrpsIOReaderOrchestrator( - WithGrpsIOReader(mock.NewMockGrpsIOReader(mockRepo)), - ) - - // Execute - service, revision, err := reader.GetGrpsIOService(ctx, tt.serviceUID) - - // Validate - if tt.expectedError { - require.Error(t, err) - if tt.errorType != nil { - assert.IsType(t, tt.errorType, err) - } - } else { - require.NoError(t, err) - } - - tt.validateService(t, service, revision) - }) - } -} - -func TestGrpsIOReaderOrchestratorGetRevision(t *testing.T) { - ctx := context.Background() - mockRepo := mock.NewMockRepository() - - // Setup test data - testServiceUID := uuid.New().String() - testService := &model.GrpsIOService{ - Type: "primary", - UID: testServiceUID, - Domain: "lists.testproject.org", - GroupID: serviceInt64Ptr(12345), - Status: "created", - ProjectSlug: "test-project", - ProjectUID: "test-project-uid", - CreatedAt: time.Now().Add(-24 * time.Hour), - UpdatedAt: time.Now(), - } - - tests := []struct { - name string - setupMock func() - serviceUID string - expectedError bool - errorType error - validateRevision func(*testing.T, uint64) - }{ - { - name: "successful revision retrieval", - setupMock: func() { - mockRepo.ClearAll() - // Store the service in mock repository - mockRepo.AddService(testService) - }, - serviceUID: testServiceUID, - expectedError: false, - validateRevision: func(t *testing.T, revision uint64) { - assert.Equal(t, uint64(1), revision) // Mock returns revision 1 - }, - }, - { - name: "service not found error", - setupMock: func() { - mockRepo.ClearAll() - // Don't store any service - }, - serviceUID: "nonexistent-service-uid", - expectedError: true, - errorType: errs.NotFound{}, - validateRevision: func(t *testing.T, revision uint64) { - assert.Equal(t, uint64(0), revision) - }, - }, - { - name: "empty service UID", - setupMock: func() { - mockRepo.ClearAll() - }, - serviceUID: "", - expectedError: true, - errorType: errs.NotFound{}, - validateRevision: func(t *testing.T, revision uint64) { - assert.Equal(t, uint64(0), revision) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Setup - tt.setupMock() - - // Create reader orchestrator - reader := NewGrpsIOReaderOrchestrator( - WithGrpsIOReader(mock.NewMockGrpsIOReader(mockRepo)), - ) - - // Execute - revision, err := reader.GetRevision(ctx, tt.serviceUID) - - // Validate - if tt.expectedError { - require.Error(t, err) - if tt.errorType != nil { - assert.IsType(t, tt.errorType, err) - } - } else { - require.NoError(t, err) - } - - tt.validateRevision(t, revision) - }) - } -} - -func TestNewGrpsIOReaderOrchestrator(t *testing.T) { - mockRepo := mock.NewMockRepository() - - tests := []struct { - name string - options []grpsIOReaderOrchestratorOption - validate func(*testing.T, GrpsIOReader) - }{ - { - name: "create with no options panics", - options: []grpsIOReaderOrchestratorOption{}, - validate: func(t *testing.T, reader GrpsIOReader) { - // This should never be called since we expect a panic - t.Fatal("Expected panic but got reader") - }, - }, - { - name: "create with grpsio reader option", - options: []grpsIOReaderOrchestratorOption{ - WithGrpsIOReader(mock.NewMockGrpsIOReader(mockRepo)), - }, - validate: func(t *testing.T, reader GrpsIOReader) { - assert.NotNil(t, reader) - orchestrator, ok := reader.(*grpsIOReaderOrchestrator) - assert.True(t, ok) - assert.NotNil(t, orchestrator.grpsIOReader) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.name == "create with no options panics" { - assert.Panics(t, func() { - NewGrpsIOReaderOrchestrator(tt.options...) - }, "Expected panic when creating orchestrator without dependencies") - } else { - // Execute normally for other tests - reader := NewGrpsIOReaderOrchestrator(tt.options...) - // Validate - tt.validate(t, reader) - } - }) - } -} - -func TestGrpsIOReaderOrchestratorIntegration(t *testing.T) { - ctx := context.Background() - mockRepo := mock.NewMockRepository() - mockRepo.ClearAll() - - // Setup test data - testServiceUID := uuid.New().String() - testService := &model.GrpsIOService{ - Type: "formation", - UID: testServiceUID, - Domain: "lists.formation.testproject.org", - GroupID: serviceInt64Ptr(67890), - Status: "created", - GlobalOwners: []string{"formation@testproject.org", "admin@testproject.org"}, - Prefix: "formation", - ProjectSlug: "integration-test-project", - ProjectName: "Integration Test Project", - ProjectUID: "integration-test-project-uid", - URL: "https://lists.formation.testproject.org", - GroupName: "integration-test-project-formation", - Public: false, - CreatedAt: time.Now().Add(-48 * time.Hour), - UpdatedAt: time.Now().Add(-1 * time.Hour), - } - - // Store the service - mockRepo.AddService(testService) - - // Create reader orchestrator - reader := NewGrpsIOReaderOrchestrator( - WithGrpsIOReader(mock.NewMockGrpsIOReader(mockRepo)), - ) - - t.Run("get service and revision for same service", func(t *testing.T) { - // Get service - service, serviceRevision, err := reader.GetGrpsIOService(ctx, testServiceUID) - require.NoError(t, err) - require.NotNil(t, service) - - // Get revision - revision, err := reader.GetRevision(ctx, testServiceUID) - require.NoError(t, err) - - // Validate that both operations return consistent data - assert.Equal(t, testServiceUID, service.UID) - assert.Equal(t, serviceRevision, revision) // Should be same revision in mock - - // Validate complete service data - assert.Equal(t, "formation", service.Type) - assert.Equal(t, "lists.formation.testproject.org", service.Domain) - assert.Equal(t, serviceInt64Ptr(67890), service.GroupID) - assert.Equal(t, "created", service.Status) - assert.Equal(t, []string{"formation@testproject.org", "admin@testproject.org"}, service.GlobalOwners) - assert.Equal(t, "formation", service.Prefix) - assert.Equal(t, "integration-test-project", service.ProjectSlug) - assert.Equal(t, "Integration Test Project", service.ProjectName) - assert.Equal(t, "integration-test-project-uid", service.ProjectUID) - assert.Equal(t, "https://lists.formation.testproject.org", service.URL) - assert.Equal(t, "integration-test-project-formation", service.GroupName) - assert.False(t, service.Public) - - // Validate timestamps - assert.False(t, service.CreatedAt.IsZero()) - assert.False(t, service.UpdatedAt.IsZero()) - }) -} - -// Helper function to create string pointer -func serviceStringPtr(s string) *string { - return &s -} - -// Helper function to create int64 pointer -func serviceInt64Ptr(i int64) *int64 { - return &i -} diff --git a/internal/service/grpsio_service_writer.go b/internal/service/grpsio_service_writer.go deleted file mode 100644 index f0587ba..0000000 --- a/internal/service/grpsio_service_writer.go +++ /dev/null @@ -1,986 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "fmt" - "log/slog" - "time" - - "github.com/google/uuid" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/groupsio" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/concurrent" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/utils" -) - -// CreateGrpsIOService creates a new service and its settings with transactional operations and rollback -func (sw *grpsIOWriterOrchestrator) CreateGrpsIOService(ctx context.Context, service *model.GrpsIOService, settings *model.GrpsIOServiceSettings) (*model.GrpsIOServiceFull, uint64, error) { - slog.DebugContext(ctx, "executing create service use case", - "service_type", service.Type, - "service_domain", service.Domain, - "project_uid", service.ProjectUID, - ) - - // Step 1: Set service identifiers and timestamps (server-side generation for security) - now := time.Now() - service.UID = uuid.New().String() // Always generate server-side, never trust client - service.CreatedAt = now - service.UpdatedAt = now - - // Set settings UID to match service UID and timestamps - if settings != nil { - settings.UID = service.UID - settings.CreatedAt = now - settings.UpdatedAt = now - } - - // For rollback purposes - var ( - keys []string - rollbackRequired bool - serviceID *int64 - ) - defer func() { - if err := recover(); err != nil || rollbackRequired { - sw.deleteKeys(ctx, keys, true) - - // Clean up GroupsIO resource if created - if serviceID != nil && sw.groupsClient != nil { - if deleteErr := sw.groupsClient.DeleteGroup(ctx, service.GetDomain(), utils.Int64PtrToUint64(serviceID)); deleteErr != nil { - slog.WarnContext(ctx, "failed to cleanup GroupsIO group during rollback", "error", deleteErr, "group_id", *serviceID) - } - } - } - }() - - // Step 2: Validate project exists and populate metadata - if err := sw.validateAndPopulateProject(ctx, service); err != nil { - slog.ErrorContext(ctx, "project validation failed", - "error", err, - "project_uid", service.ProjectUID, - ) - return nil, 0, err - } - - slog.DebugContext(ctx, "project validation successful", - "project_uid", service.ProjectUID, - "project_slug", service.ProjectSlug, - "project_name", service.ProjectName, - ) - - // Step 2.5: Find and validate parent service for shared type - if service.Type == constants.ServiceTypeShared { - if err := sw.findAndValidateParentService(ctx, service); err != nil { - slog.ErrorContext(ctx, "parent service lookup failed", - "error", err, - "project_uid", service.ProjectUID, - ) - return nil, 0, err - } - - slog.DebugContext(ctx, "parent service found and validated", - "parent_service_uid", service.ParentServiceUID, - ) - } - - // Step 3: Reserve unique constraints based on service type - constraintKey, err := sw.reserveUniqueConstraints(ctx, service) - if err != nil { - rollbackRequired = true - return nil, 0, err - } - if constraintKey != "" { - keys = append(keys, constraintKey) - } - - // Step 4: Validate source (Service only supports API and Mock, not webhook) - if service.Source != constants.SourceAPI && service.Source != constants.SourceMock { - return nil, 0, errors.NewValidation( - fmt.Sprintf("service only supports api or mock source, got: %s", service.Source)) - } - - // Step 5: Source-based strategy dispatch (API and Mock only) - var ( - groupID *int64 - requiresCleanup bool - ) - - switch service.Source { - case constants.SourceAPI: - groupID, requiresCleanup, err = sw.handleAPISourceService(ctx, service) - if err != nil { - rollbackRequired = true - return nil, 0, err - } - if requiresCleanup { - serviceID = groupID - } - // Set status based on Groups.io creation (preserves existing logic) - if groupID != nil { - service.Status = "active" - } else { - service.Status = "pending" - } - - case constants.SourceMock: - groupID = sw.handleMockSourceService(ctx, service) - service.Status = "pending" - - default: - // Should never reach here due to validation above - return nil, 0, errors.NewValidation( - fmt.Sprintf("unsupported source for service: %s", service.Source)) - } - - service.GroupID = groupID - - // Step 6: Create service in storage (with Groups.io ID already set) - createdService, _, revision, err := sw.grpsIOWriter.CreateGrpsIOService(ctx, service, nil) - if err != nil { - slog.ErrorContext(ctx, "failed to create service", - "error", err, - "service_type", service.Type, - "service_domain", service.Domain, - ) - rollbackRequired = true - return nil, 0, err - } - - slog.DebugContext(ctx, "service created successfully", - "service_uid", createdService.UID, - "revision", revision, - ) - - // Step 7: Create service settings - var createdSettings *model.GrpsIOServiceSettings - if settings != nil { - createdSettings, _, err = sw.grpsIOWriter.CreateGrpsIOServiceSettings(ctx, settings) - if err != nil { - slog.ErrorContext(ctx, "failed to create service settings", - "error", err, - "service_uid", createdService.UID, - ) - rollbackRequired = true - return nil, 0, err - } - - slog.DebugContext(ctx, "service settings created successfully", - "service_uid", createdSettings.UID, - ) - } - - // Step 8: Create secondary indices - secondaryKeys, err := sw.createServiceSecondaryIndices(ctx, createdService) - if err != nil { - slog.WarnContext(ctx, "failed to create service secondary indices", "error", err) - // Don't fail the operation, service creation succeeded - } else { - keys = append(keys, secondaryKeys...) - } - - // Step 9: Publish messages (indexer and access control) - if sw.publisher != nil { - if err := sw.publishServiceMessages(ctx, createdService, createdSettings, model.ActionCreated); err != nil { - slog.ErrorContext(ctx, "failed to publish messages", "error", err) - // Don't fail the operation on message failure, service creation succeeded - } - } - - return &model.GrpsIOServiceFull{ - Base: createdService, - Settings: createdSettings, - }, revision, nil -} - -// createServiceInGroupsIO handles Groups.io group creation and returns the ID -func (sw *grpsIOWriterOrchestrator) createServiceInGroupsIO(ctx context.Context, service *model.GrpsIOService) (*int64, error) { - if sw.groupsClient == nil { - return nil, nil // Skip Groups.io creation - } - - // Use domain methods for effective values - effectiveDomain := service.GetDomain() - effectiveGroupName := service.GetGroupName() - - slog.InfoContext(ctx, "creating group in Groups.io", - "domain", effectiveDomain, - "group_name", effectiveGroupName, - ) - - groupOptions := groupsio.GroupCreateOptions{ - GroupName: effectiveGroupName, - Desc: fmt.Sprintf("Mailing lists for %s", service.ProjectName), // Fixed: was Description - Privacy: "group_privacy_unlisted_public", // Production value - SubGroupAccess: "sub_group_owners", // Production value - EmailDelivery: "email_delivery_none", // Production value - } - - groupResult, err := sw.groupsClient.CreateGroup(ctx, effectiveDomain, groupOptions) - if err != nil { - slog.ErrorContext(ctx, "Groups.io group creation failed", - "error", err, - "domain", effectiveDomain, - "group_name", service.GroupName, - ) - return nil, fmt.Errorf("groups.io creation failed: %w", err) - } - - groupID := int64(groupResult.ID) - slog.InfoContext(ctx, "Groups.io group created successfully", - "group_id", groupResult.ID, - "domain", service.Domain, - ) - - // Step 2: Update group with additional settings that cannot be set at creation time - announce := true - updateOptions := groupsio.GroupUpdateOptions{ - Announce: &announce, - ReplyTo: "group_reply_to_sender", - MembersVisible: "group_view_members_moderators", - CalendarAccess: "group_access_none", - FilesAccess: "group_access_none", - DatabaseAccess: "group_access_none", - WikiAccess: "group_access_none", - PhotosAccess: "group_access_none", - MemberDirectoryAccess: "group_access_moderators_only", - PollsAccess: "polls_access_none", - ChatAccess: "group_access_none", - } - - err = sw.groupsClient.UpdateGroup(ctx, effectiveDomain, uint64(groupID), updateOptions) - if err != nil { - slog.WarnContext(ctx, "Groups.io group update failed, but group creation succeeded", - "error", err, - "group_id", groupID, - "domain", effectiveDomain, - ) - // Don't fail the creation if update fails, as the group was created successfully - // TODO: Will be fixed in next PR to handle the sync status - } else { - slog.InfoContext(ctx, "Groups.io group updated with additional settings", - "group_id", groupID, - "domain", effectiveDomain, - ) - } - - return &groupID, nil -} - -// UpdateGrpsIOService updates an existing service with transactional patterns -func (sw *grpsIOWriterOrchestrator) UpdateGrpsIOService(ctx context.Context, uid string, service *model.GrpsIOService, expectedRevision uint64) (*model.GrpsIOService, uint64, error) { - slog.DebugContext(ctx, "executing update service use case", - "service_uid", uid, - "expected_revision", expectedRevision, - "project_uid", service.ProjectUID, - ) - - // For rollback purposes and cleanup - var ( - staleKeys []string - newKeys []string - rollbackRequired bool - updateSucceeded bool - ) - defer func() { - if err := recover(); err != nil || rollbackRequired { - // Rollback new keys - sw.deleteKeys(ctx, newKeys, true) - } - if updateSucceeded && len(staleKeys) > 0 { - slog.DebugContext(ctx, "cleaning up stale keys", - "keys_count", len(staleKeys), - ) - go func() { - // Cleanup stale keys in a separate goroutine - // Use WithoutCancel to inherit values (tracing, auth) but not cancellation from parent request - // This ensures cleanup completes even if original request times out - ctxCleanup, cancel := context.WithTimeout(context.WithoutCancel(ctx), time.Second*10) - defer cancel() - - sw.deleteKeys(ctxCleanup, staleKeys, false) - slog.DebugContext(ctxCleanup, "stale keys cleanup completed", - "keys_count", len(staleKeys), - ) - }() - } - }() - - // Validate project exists and populate metadata - if err := sw.validateAndPopulateProject(ctx, service); err != nil { - slog.ErrorContext(ctx, "project validation failed during update", - "error", err, - "project_uid", service.ProjectUID, - "service_uid", uid, - ) - return nil, 0, err - } - - slog.DebugContext(ctx, "project validation successful for update", - "project_uid", service.ProjectUID, - "project_slug", service.ProjectSlug, - "project_name", service.ProjectName, - "service_uid", uid, - ) - - // Retrieve existing service to merge data properly - existing, existingRevision, err := sw.grpsIOReader.GetGrpsIOService(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve existing service", - "error", err, - "service_uid", uid, - ) - return nil, 0, err - } - - // Verify revision matches to ensure optimistic locking - if existingRevision != expectedRevision { - slog.WarnContext(ctx, "revision mismatch during update", - "expected_revision", expectedRevision, - "current_revision", existingRevision, - "service_uid", uid, - ) - return nil, 0, errors.NewConflict("service has been modified by another process") - } - - // Merge existing data with updated fields - sw.mergeServiceData(ctx, existing, service) - - // Update service in storage - updatedService, revision, err := sw.grpsIOWriter.UpdateGrpsIOService(ctx, uid, service, expectedRevision) - if err != nil { - slog.ErrorContext(ctx, "failed to update service", - "error", err, - "service_uid", uid, - "expected_revision", expectedRevision, - ) - rollbackRequired = true - return nil, 0, err - } - - slog.DebugContext(ctx, "service updated successfully", - "service_uid", uid, - "revision", revision, - ) - - // Sync service updates to Groups.io - sw.syncServiceToGroupsIO(ctx, updatedService) - - // Fetch settings and publish update messages - if sw.publisher != nil { - // Fetch settings for message publishing - settings, _, errSettings := sw.grpsIOReader.GetGrpsIOServiceSettings(ctx, updatedService.UID) - if errSettings != nil { - slog.WarnContext(ctx, "failed to get service settings for message publishing", - "error", errSettings, - "service_uid", updatedService.UID, - ) - settings = nil // Continue without settings - } - - if err := sw.publishServiceMessages(ctx, updatedService, settings, model.ActionUpdated); err != nil { - slog.ErrorContext(ctx, "failed to publish update messages", "error", err) - // Don't fail the update on message publishing errors - } - } - - // Mark update as successful for defer cleanup - updateSucceeded = true - return updatedService, revision, nil -} - -// UpdateGrpsIOServiceSettings updates service settings and publishes indexer message -func (sw *grpsIOWriterOrchestrator) UpdateGrpsIOServiceSettings(ctx context.Context, settings *model.GrpsIOServiceSettings, expectedRevision uint64) (*model.GrpsIOServiceSettings, uint64, error) { - slog.DebugContext(ctx, "executing update service settings use case", - "service_uid", settings.UID, - "expected_revision", expectedRevision, - ) - - // Fetch existing settings to preserve created_at timestamp - existingSettings, existingRevision, err := sw.grpsIOReader.GetGrpsIOServiceSettings(ctx, settings.UID) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve existing service settings", - "error", err, - "service_uid", settings.UID, - ) - return nil, 0, err - } - - // Verify revision matches to ensure optimistic locking - if existingRevision != expectedRevision { - slog.WarnContext(ctx, "revision mismatch during settings update", - "expected_revision", expectedRevision, - "current_revision", existingRevision, - "service_uid", settings.UID, - ) - return nil, 0, errors.NewConflict("service settings have been modified by another process") - } - - // Preserve created_at and update updated_at - settings.CreatedAt = existingSettings.CreatedAt - settings.UpdatedAt = time.Now() - - // Update settings in storage - updatedSettings, revision, err := sw.grpsIOWriter.UpdateGrpsIOServiceSettings(ctx, settings, expectedRevision) - if err != nil { - slog.ErrorContext(ctx, "failed to update service settings", - "error", err, - "service_uid", settings.UID, - "expected_revision", expectedRevision, - ) - return nil, 0, err - } - - slog.DebugContext(ctx, "service settings updated successfully", - "service_uid", settings.UID, - "revision", revision, - ) - - // Publish settings indexer message - if sw.publisher != nil { - settingsIndexerMessage := &model.IndexerMessage{ - Action: model.ActionUpdated, - Tags: updatedSettings.Tags(), - } - builtMessage, errBuild := settingsIndexerMessage.Build(ctx, updatedSettings) - if errBuild != nil { - slog.ErrorContext(ctx, "failed to build settings indexer message", - "error", errBuild, - "service_uid", settings.UID, - ) - // Don't fail the update on message building errors - } else { - if errPublish := sw.publisher.Indexer(ctx, constants.IndexGroupsIOServiceSettingsSubject, builtMessage); errPublish != nil { - slog.ErrorContext(ctx, "failed to publish settings indexer message", - "error", errPublish, - "service_uid", settings.UID, - ) - // Don't fail the update on message publishing errors - } - } - - // Also publish updated access control message with new writers/auditors - service, _, errService := sw.grpsIOReader.GetGrpsIOService(ctx, settings.UID) - if errService != nil { - slog.WarnContext(ctx, "failed to get service for access control update", - "error", errService, - "service_uid", settings.UID, - ) - } else { - // Build access control message using updated settings - relations := map[string][]string{} - if len(service.GlobalOwners) > 0 { - relations[constants.RelationOwner] = service.GlobalOwners - } - if len(updatedSettings.Writers) > 0 { - writers := make([]string, 0, len(updatedSettings.Writers)) - for _, w := range updatedSettings.Writers { - if w.Username != nil && *w.Username != "" { - writers = append(writers, *w.Username) - } - } - if len(writers) > 0 { - relations[constants.RelationWriter] = writers - } - } - if len(updatedSettings.Auditors) > 0 { - auditors := make([]string, 0, len(updatedSettings.Auditors)) - for _, a := range updatedSettings.Auditors { - if a.Username != nil && *a.Username != "" { - auditors = append(auditors, *a.Username) - } - } - if len(auditors) > 0 { - relations[constants.RelationAuditor] = auditors - } - } - - accessMessage := &model.AccessMessage{ - UID: service.UID, - ObjectType: constants.ObjectTypeGroupsIOService, - Public: service.Public, - Relations: relations, - References: map[string][]string{ - constants.RelationProject: {service.ProjectUID}, - }, - } - - if errAccess := sw.publisher.Access(ctx, constants.UpdateAccessGroupsIOServiceSubject, accessMessage); errAccess != nil { - slog.ErrorContext(ctx, "failed to publish access control message", - "error", errAccess, - "service_uid", settings.UID, - ) - // Don't fail the update on message publishing errors - } - } - } - - return updatedSettings, revision, nil -} - -// DeleteGrpsIOService deletes a service with message publishing -func (sw *grpsIOWriterOrchestrator) DeleteGrpsIOService(ctx context.Context, uid string, expectedRevision uint64, service *model.GrpsIOService) error { - slog.DebugContext(ctx, "executing delete service use case", - "service_uid", uid, - "expected_revision", expectedRevision, - ) - - if service != nil { - slog.DebugContext(ctx, "service data provided for deletion", - "service_uid", service.UID, - "service_type", service.Type, - "project_uid", service.ProjectUID, - ) - } else { - slog.DebugContext(ctx, "no service data provided for deletion - will rely on storage layer for validation") - } - - // Step 1: Delete the main service record (storage layer handles constraint cleanup) - err := sw.grpsIOWriter.DeleteGrpsIOService(ctx, uid, expectedRevision, service) - if err != nil { - slog.ErrorContext(ctx, "failed to delete service", - "error", err, - "service_uid", uid, - "expected_revision", expectedRevision, - ) - return err - } - - slog.DebugContext(ctx, "service record deleted successfully", - "service_uid", uid, - ) - - // Step 2: Publish delete messages - if sw.publisher != nil { - if err := sw.publishServiceDeleteMessages(ctx, uid); err != nil { - slog.ErrorContext(ctx, "failed to publish delete messages", "error", err) - } - } - - slog.DebugContext(ctx, "service deletion completed successfully", - "service_uid", uid, - ) - - return nil -} - -// validateAndPopulateProject validates project exists and populates project metadata -func (sw *grpsIOWriterOrchestrator) validateAndPopulateProject(ctx context.Context, service *model.GrpsIOService) error { - if service.ProjectUID == "" { - return errors.NewValidation("project_uid is required") - } - - // Fetch project slug - slug, err := sw.entityReader.ProjectSlug(ctx, service.ProjectUID) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve project slug", - "error", err, - "project_uid", service.ProjectUID, - ) - return err - } - - // Fetch project name - name, err := sw.entityReader.ProjectName(ctx, service.ProjectUID) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve project name", - "error", err, - "project_uid", service.ProjectUID, - ) - return err - } - - // Populate service with project metadata - service.ProjectSlug = slug - service.ProjectName = name - - return nil -} - -// findAndValidateParentService finds the primary service for the parent project and sets it as parent -func (sw *grpsIOWriterOrchestrator) findAndValidateParentService(ctx context.Context, service *model.GrpsIOService) error { - if service.ProjectUID == "" { - return errors.NewValidation("project_uid is required to find parent service") - } - - slog.DebugContext(ctx, "looking up parent project for shared service creation", - "project_uid", service.ProjectUID, - ) - - // Get parent project UID - this is required for shared services - parentProjectUID, err := sw.entityReader.ProjectParentUID(ctx, service.ProjectUID) - if err != nil { - slog.ErrorContext(ctx, "no parent project found - cannot create shared service", - "project_uid", service.ProjectUID, - "error", err, - ) - return errors.NewValidation("shared services can only be created for sub-projects that have a parent project") - } - - if parentProjectUID == "" { - slog.ErrorContext(ctx, "parent project UID is empty - cannot create shared service", - "project_uid", service.ProjectUID, - ) - return errors.NewValidation("shared services can only be created for sub-projects that have a parent project") - } - - slog.InfoContext(ctx, "found parent project, looking for its primary service", - "project_uid", service.ProjectUID, - "parent_project_uid", parentProjectUID, - ) - - // Find primary service for the parent project - primaryService, err := sw.findPrimaryServiceForProject(ctx, parentProjectUID) - if err != nil { - slog.ErrorContext(ctx, "failed to find primary service for parent project", - "error", err, - "parent_project_uid", parentProjectUID, - ) - return errors.NewValidation("parent project must have a primary service before creating shared services") - } - - // Set the parent service UID - service.ParentServiceUID = primaryService.UID - - slog.InfoContext(ctx, "found and set primary service as parent for shared service", - "parent_service_uid", service.ParentServiceUID, - "parent_project_uid", parentProjectUID, - "project_uid", service.ProjectUID, - ) - - return nil -} - -// findPrimaryServiceForProject finds the primary service for a given project -func (sw *grpsIOWriterOrchestrator) findPrimaryServiceForProject(ctx context.Context, projectUID string) (*model.GrpsIOService, error) { - slog.DebugContext(ctx, "looking up primary service for project", - "project_uid", projectUID, - ) - - // Get all services for the project using the project UID index - services, err := sw.grpsIOReader.GetServicesByProjectUID(ctx, projectUID) - if err != nil { - slog.ErrorContext(ctx, "failed to retrieve services for project", - "error", err, - "project_uid", projectUID, - ) - return nil, err - } - - // Find the primary service among all services - for _, service := range services { - if service.Type == constants.ServiceTypePrimary { - slog.InfoContext(ctx, "found primary service for project", - "project_uid", projectUID, - "service_uid", service.UID, - ) - return service, nil - } - } - - // No primary service found - slog.WarnContext(ctx, "no primary service found for project", - "project_uid", projectUID, - "services_checked", len(services), - ) - return nil, errors.NewValidation("no primary service exists for this project") -} - -// reserveUniqueConstraints reserves unique constraints based on service type -func (sw *grpsIOWriterOrchestrator) reserveUniqueConstraints(ctx context.Context, service *model.GrpsIOService) (string, error) { - switch service.Type { - case constants.ServiceTypePrimary: - // Primary service: unique by project only - return sw.grpsIOWriter.UniqueProjectType(ctx, service) - case constants.ServiceTypeFormation: - // Formation service: unique by project + prefix - return sw.grpsIOWriter.UniqueProjectPrefix(ctx, service) - case constants.ServiceTypeShared: - // Shared service: unique by project + group_id - return sw.grpsIOWriter.UniqueProjectGroupID(ctx, service) - default: - slog.WarnContext(ctx, "unknown service type - no constraint validation", "type", service.Type) - return "", nil - } -} - -// createServiceSecondaryIndices creates secondary indices for external GroupsIO IDs -func (sw *grpsIOWriterOrchestrator) createServiceSecondaryIndices(ctx context.Context, service *model.GrpsIOService) ([]string, error) { - // Delegate to storage layer (port interface) - // This interface method needs to be added to port.GrpsIOServiceWriter - type serviceIndexCreator interface { - CreateServiceSecondaryIndices(ctx context.Context, service *model.GrpsIOService) ([]string, error) - } - - if indexCreator, ok := sw.grpsIOWriter.(serviceIndexCreator); ok { - return indexCreator.CreateServiceSecondaryIndices(ctx, service) - } - - // If storage implementation doesn't support indexing (e.g., mock), skip silently - slog.DebugContext(ctx, "storage implementation doesn't support secondary indices") - return nil, nil -} - -// publishServiceMessages publishes service and settings indexer messages, plus service access control message -func (sw *grpsIOWriterOrchestrator) publishServiceMessages(ctx context.Context, service *model.GrpsIOService, settings *model.GrpsIOServiceSettings, action model.MessageAction) error { - if sw.publisher == nil { - slog.WarnContext(ctx, "publisher not available, skipping service message publishing") - return nil - } - - // Build service indexer message - indexerMessage := &model.IndexerMessage{ - Action: action, - Tags: service.Tags(), - } - builtIndexerMessage, err := indexerMessage.Build(ctx, service) - if err != nil { - return fmt.Errorf("failed to build service indexer message: %w", err) - } - - // Build settings indexer message if settings exist - var builtSettingsIndexerMessage *model.IndexerMessage - if settings != nil { - settingsIndexerMessage := &model.IndexerMessage{ - Action: action, - Tags: settings.Tags(), - } - var errSettings error - builtSettingsIndexerMessage, errSettings = settingsIndexerMessage.Build(ctx, settings) - if errSettings != nil { - return fmt.Errorf("failed to build settings indexer message: %w", errSettings) - } - } - - // Build access control message using settings data - relations := map[string][]string{} - if len(service.GlobalOwners) > 0 { - relations[constants.RelationOwner] = service.GlobalOwners - } - if settings != nil { - // Convert UserInfo arrays to string arrays using username for access control - if len(settings.Writers) > 0 { - writers := make([]string, 0, len(settings.Writers)) - for _, w := range settings.Writers { - if w.Username != nil && *w.Username != "" { - writers = append(writers, *w.Username) - } - } - if len(writers) > 0 { - relations[constants.RelationWriter] = writers - } - } - if len(settings.Auditors) > 0 { - auditors := make([]string, 0, len(settings.Auditors)) - for _, a := range settings.Auditors { - if a.Username != nil && *a.Username != "" { - auditors = append(auditors, *a.Username) - } - } - if len(auditors) > 0 { - relations[constants.RelationAuditor] = auditors - } - } - } - - accessMessage := &model.AccessMessage{ - UID: service.UID, - ObjectType: constants.ObjectTypeGroupsIOService, - Public: service.Public, - Relations: relations, - References: map[string][]string{ - constants.RelationProject: {service.ProjectUID}, - }, - } - - // Publish messages concurrently - messages := []func() error{ - func() error { - return sw.publisher.Indexer(ctx, constants.IndexGroupsIOServiceSubject, builtIndexerMessage) - }, - func() error { - return sw.publisher.Access(ctx, constants.UpdateAccessGroupsIOServiceSubject, accessMessage) - }, - } - - // Add settings indexer message if available - if builtSettingsIndexerMessage != nil { - messages = append(messages, func() error { - return sw.publisher.Indexer(ctx, constants.IndexGroupsIOServiceSettingsSubject, builtSettingsIndexerMessage) - }) - } - - // Execute all messages concurrently - errPublishingMessage := concurrent.NewWorkerPool(len(messages)).Run(ctx, messages...) - if errPublishingMessage != nil { - slog.ErrorContext(ctx, "failed to publish service messages", - "error", errPublishingMessage, - "service_id", service.UID, - ) - return errPublishingMessage - } - - slog.DebugContext(ctx, "service messages published successfully", - "service_id", service.UID, - "action", action, - ) - - return nil -} - -// publishServiceDeleteMessages publishes service delete messages concurrently -func (sw *grpsIOWriterOrchestrator) publishServiceDeleteMessages(ctx context.Context, uid string) error { - if sw.publisher == nil { - slog.WarnContext(ctx, "publisher not available, skipping service delete message publishing") - return nil - } - - // For delete messages, we just need the UID - indexerMessage := &model.IndexerMessage{ - Action: model.ActionDeleted, - Tags: []string{}, - } - - builtMessage, err := indexerMessage.Build(ctx, uid) - if err != nil { - slog.ErrorContext(ctx, "failed to build service delete indexer message", "error", err, "service_uid", uid) - return fmt.Errorf("failed to build service delete indexer message: %w", err) - } - - // Publish delete messages concurrently - messages := []func() error{ - func() error { - return sw.publisher.Indexer(ctx, constants.IndexGroupsIOServiceSubject, builtMessage) - }, - func() error { - return sw.publisher.Access(ctx, constants.DeleteAllAccessGroupsIOServiceSubject, uid) - }, - } - - // Execute all messages concurrently - errPublishingMessage := concurrent.NewWorkerPool(len(messages)).Run(ctx, messages...) - if errPublishingMessage != nil { - slog.ErrorContext(ctx, "failed to publish service delete messages", - "error", errPublishingMessage, - "service_uid", uid, - ) - return errPublishingMessage - } - - slog.DebugContext(ctx, "service delete messages published successfully", "service_uid", uid) - return nil -} - -// mergeServiceData merges existing service data with updated fields -func (sw *grpsIOWriterOrchestrator) mergeServiceData(ctx context.Context, existing *model.GrpsIOService, updated *model.GrpsIOService) { - // Preserve immutable fields - updated.UID = existing.UID - updated.CreatedAt = existing.CreatedAt - updated.ProjectUID = existing.ProjectUID - updated.Type = existing.Type - updated.Prefix = existing.Prefix - updated.Domain = existing.Domain - updated.GroupID = existing.GroupID - updated.URL = existing.URL - updated.GroupName = existing.GroupName - - // Update timestamp - updated.UpdatedAt = time.Now() - - slog.DebugContext(ctx, "service data merged", - "service_id", existing.UID, - "mutable_fields", []string{"global_owners", "status", "public"}, - ) -} - -// syncServiceToGroupsIO handles Groups.io service update with proper error handling -func (sw *grpsIOWriterOrchestrator) syncServiceToGroupsIO(ctx context.Context, service *model.GrpsIOService) { - // Guard clause: skip if Groups.io client not available or service not synced - if sw.groupsClient == nil || service.GroupID == nil { - slog.InfoContext(ctx, "Groups.io integration disabled or service not synced - skipping Groups.io update") - return - } - - // Get domain using helper method - domain, err := sw.getGroupsIODomainForResource(ctx, service.UID, constants.ResourceTypeService) - if err != nil { - slog.WarnContext(ctx, "Groups.io service sync skipped due to domain lookup failure, local update will proceed", - "error", err, "service_uid", service.UID) - return - } - - // Build update options from service model - updates := groupsio.GroupUpdateOptions{ - GlobalOwners: service.GlobalOwners, - } - - // Perform Groups.io service update - err = sw.groupsClient.UpdateGroup(ctx, domain, utils.Int64PtrToUint64(service.GroupID), updates) - if err != nil { - slog.WarnContext(ctx, "Groups.io service update failed, local update will proceed", - "error", err, "domain", domain, "group_id", *service.GroupID) - } else { - slog.InfoContext(ctx, "Groups.io service updated successfully", - "group_id", *service.GroupID, "domain", domain) - } -} - -// handleAPISourceService handles API-initiated service creation -// Preserves existing logic: calls createServiceInGroupsIO and returns cleanup flag -// For shared services with pre-provided GroupID, preserves the input value -func (sw *grpsIOWriterOrchestrator) handleAPISourceService( - ctx context.Context, - service *model.GrpsIOService, -) (*int64, bool, error) { - // Check if GroupID is already provided (shared service scenario) - if service.GroupID != nil { - slog.InfoContext(ctx, "source=api: using pre-provided group_id for shared service", - "group_id", *service.GroupID, - "domain", service.GetDomain()) - return service.GroupID, false, nil - } - - slog.InfoContext(ctx, "source=api: creating group in Groups.io", - "domain", service.GetDomain(), - "project_uid", service.ProjectUID) - - // Call existing createServiceInGroupsIO method (preserves all existing logic) - groupID, err := sw.createServiceInGroupsIO(ctx, service) - if err != nil { - slog.ErrorContext(ctx, "failed to create group in Groups.io", - "error", err, - "domain", service.GetDomain()) - return nil, false, err - } - - // Determine if cleanup is required (preserves existing rollback logic) - requiresCleanup := groupID != nil - - if groupID != nil { - slog.InfoContext(ctx, "group created successfully in Groups.io", - "group_id", *groupID) - } else { - slog.InfoContext(ctx, "Groups.io client not available, service will be in pending state") - } - - return groupID, requiresCleanup, nil -} - -// handleMockSourceService handles mock/test mode service creation -// Preserves existing logic: returns nil for groupID -func (sw *grpsIOWriterOrchestrator) handleMockSourceService( - ctx context.Context, - service *model.GrpsIOService, -) *int64 { - slog.InfoContext(ctx, "source=mock: skipping Groups.io coordination", - "domain", service.GetDomain()) - return nil -} - -// Note: No handleWebhookSourceService needed -// Services are created explicitly via API, not discovered from webhooks -// Only mailing lists (subgroups) and members can be webhook-adopted diff --git a/internal/service/grpsio_service_writer_test.go b/internal/service/grpsio_service_writer_test.go deleted file mode 100644 index 9201b98..0000000 --- a/internal/service/grpsio_service_writer_test.go +++ /dev/null @@ -1,1065 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "errors" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/mock" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - errs "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" -) - -func TestGrpsIOWriterOrchestrator_CreateGrpsIOService(t *testing.T) { - testCases := []struct { - name string - setupMock func(*mock.MockRepository) - inputService *model.GrpsIOService - expectedError error - validate func(t *testing.T, result *model.GrpsIOService, settings *model.GrpsIOServiceSettings, revision uint64, mockRepo *mock.MockRepository) - }{ - { - name: "successful primary service creation", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - mockRepo.AddProject("project-1", "test-project", "Test Project") - }, - inputService: &model.GrpsIOService{ - Type: "primary", - Domain: "lists.test.org", - GroupID: writerServiceInt64Ptr(12345), - GlobalOwners: []string{"admin@test.org"}, - Prefix: "", - ProjectUID: "project-1", - URL: "https://lists.test.org", - GroupName: "test-project", - Public: true, - Status: "created", - Source: constants.SourceMock, - }, - expectedError: nil, - validate: func(t *testing.T, result *model.GrpsIOService, settings *model.GrpsIOServiceSettings, revision uint64, mockRepo *mock.MockRepository) { - assert.NotEmpty(t, result.UID) - assert.Equal(t, "primary", result.Type) - assert.Equal(t, "project-1", result.ProjectUID) - assert.Equal(t, "Test Project", result.ProjectName) - assert.Equal(t, "test-project", result.ProjectSlug) - assert.Equal(t, "lists.test.org", result.Domain) - assert.Equal(t, uint64(1), revision) - assert.Equal(t, 1, mockRepo.GetServiceCount()) - }, - }, - { - name: "successful formation service creation with prefix", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - mockRepo.AddProject("project-2", "formation-project", "Formation Project") - }, - inputService: &model.GrpsIOService{ - Type: "formation", - Domain: "lists.formation.org", - GroupID: writerServiceInt64Ptr(23456), - GlobalOwners: []string{"admin@formation.org"}, - Prefix: "form", - ProjectUID: "project-2", - URL: "https://lists.formation.org", - GroupName: "formation-project", - Public: true, - Status: "created", - Source: constants.SourceMock, - }, - expectedError: nil, - validate: func(t *testing.T, result *model.GrpsIOService, settings *model.GrpsIOServiceSettings, revision uint64, mockRepo *mock.MockRepository) { - assert.NotEmpty(t, result.UID) - assert.Equal(t, "formation", result.Type) - assert.Equal(t, "form", result.Prefix) - assert.Equal(t, "project-2", result.ProjectUID) - assert.Equal(t, "Formation Project", result.ProjectName) - assert.Equal(t, "formation-project", result.ProjectSlug) - assert.Equal(t, uint64(1), revision) - assert.Equal(t, 1, mockRepo.GetServiceCount()) - }, - }, - { - name: "successful shared service creation with group ID", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - // Set up parent project with primary service - mockRepo.AddProject("parent-project-3", "parent-shared-project", "Parent Shared Project") - mockRepo.AddService(&model.GrpsIOService{ - UID: "parent-service-3", - Type: constants.ServiceTypePrimary, - Domain: "lists.parent.org", - GroupID: writerServiceInt64Ptr(99999), - GlobalOwners: []string{"admin@parent.org"}, - Prefix: "", - ProjectUID: "parent-project-3", - URL: "https://lists.parent.org", - GroupName: "parent-group", - Public: true, - Status: "created", - Source: constants.SourceMock, - }) - - // Set up child project and link to parent - mockRepo.AddProject("project-3", "shared-project", "Shared Project") - mockRepo.SetProjectParent("project-3", "parent-project-3") - }, - inputService: &model.GrpsIOService{ - Type: "shared", - Domain: "lists.shared.org", - GroupID: writerServiceInt64Ptr(34567), - GlobalOwners: []string{"admin@shared.org"}, - Prefix: "", - ProjectUID: "project-3", - URL: "https://lists.shared.org", - GroupName: "shared-project", - Public: false, - Status: "created", - Source: constants.SourceMock, - }, - expectedError: nil, - validate: func(t *testing.T, result *model.GrpsIOService, settings *model.GrpsIOServiceSettings, revision uint64, mockRepo *mock.MockRepository) { - assert.NotEmpty(t, result.UID) - assert.Equal(t, "shared", result.Type) - assert.Nil(t, result.GroupID) // Mock source doesn't coordinate with Groups.io, so GroupID is nil - assert.False(t, result.Public) - assert.Equal(t, "project-3", result.ProjectUID) - assert.Equal(t, "parent-service-3", result.ParentServiceUID, "should have parent service UID set") - assert.Equal(t, uint64(1), revision) // Each service gets its own revision counter - assert.Equal(t, 2, mockRepo.GetServiceCount()) // Parent service + this shared service - }, - }, - { - name: "project not found error", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - // Don't add any projects - }, - inputService: &model.GrpsIOService{ - Type: "primary", - Domain: "lists.test.org", - GroupID: writerServiceInt64Ptr(12345), - GlobalOwners: []string{"admin@test.org"}, - Prefix: "", - ProjectUID: "nonexistent-project", - URL: "https://lists.test.org", - GroupName: "test-project", - Public: true, - Status: "created", - Source: constants.SourceMock, - }, - expectedError: errs.NotFound{}, - validate: func(t *testing.T, result *model.GrpsIOService, settings *model.GrpsIOServiceSettings, revision uint64, mockRepo *mock.MockRepository) { - assert.Nil(t, result) - assert.Equal(t, uint64(0), revision) - assert.Equal(t, 0, mockRepo.GetServiceCount()) - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - mockRepo := mock.NewMockRepository() - tc.setupMock(mockRepo) - - grpsIOReader := mock.NewMockGrpsIOReader(mockRepo) - grpsIOWriter := mock.NewMockGrpsIOWriter(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - publisher := mock.NewMockMessagePublisher() - - orchestrator := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(grpsIOReader), - WithGrpsIOWriter(grpsIOWriter), - WithEntityAttributeReader(entityReader), - WithPublisher(publisher), - ) - - // Execute - ctx := context.Background() - fullResult, revision, err := orchestrator.CreateGrpsIOService(ctx, tc.inputService, nil) - - // Validate - if tc.expectedError != nil { - require.Error(t, err) - assert.IsType(t, tc.expectedError, err) - } else { - require.NoError(t, err) - require.NotNil(t, fullResult) - } - - var result *model.GrpsIOService - var settings *model.GrpsIOServiceSettings - if fullResult != nil { - result = fullResult.Base - settings = fullResult.Settings - } - tc.validate(t, result, settings, revision, mockRepo) - }) - } -} - -func TestGrpsIOWriterOrchestrator_CreateGrpsIOService_PublishingErrors(t *testing.T) { - testCases := []struct { - name string - indexerError error - accessError error - expectComplete bool // Should service still be created despite publishing errors? - }{ - { - name: "indexer error does not fail creation", - indexerError: errors.New("indexer publishing failed"), - accessError: nil, - expectComplete: true, - }, - { - name: "access error does not fail creation", - indexerError: nil, - accessError: errors.New("access publishing failed"), - expectComplete: true, - }, - { - name: "both publishing errors do not fail creation", - indexerError: errors.New("indexer publishing failed"), - accessError: errors.New("access publishing failed"), - expectComplete: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - mockRepo := mock.NewMockRepository() - mockRepo.ClearAll() - mockRepo.AddProject("project-1", "test-project", "Test Project") - - grpsIOReader := mock.NewMockGrpsIOReader(mockRepo) - grpsIOWriter := mock.NewMockGrpsIOWriter(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - // Use custom publisher that can return errors - publisher := &MockMessagePublisherWithError{ - indexerError: tc.indexerError, - accessError: tc.accessError, - } - - orchestrator := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(grpsIOReader), - WithGrpsIOWriter(grpsIOWriter), - WithEntityAttributeReader(entityReader), - WithPublisher(publisher), - ) - - service := &model.GrpsIOService{ - Type: "primary", - Domain: "lists.test.org", - GroupID: writerServiceInt64Ptr(12345), - GlobalOwners: []string{"admin@test.org"}, - Prefix: "", - ProjectUID: "project-1", - URL: "https://lists.test.org", - GroupName: "test-project", - Public: true, - Status: "created", - Source: constants.SourceMock, - } - - // Execute - ctx := context.Background() - fullResult, revision, err := orchestrator.CreateGrpsIOService(ctx, service, nil) - - // Validate - if tc.expectComplete { - assert.NoError(t, err) - assert.NotNil(t, fullResult) - result := fullResult.Base - assert.NotEmpty(t, result.UID) - assert.Equal(t, uint64(1), revision) - assert.Equal(t, 1, mockRepo.GetServiceCount()) - } else { - assert.Error(t, err) - assert.Nil(t, fullResult) - assert.Equal(t, uint64(0), revision) - } - }) - } -} - -// Note: UpdateGrpsIOService tests are complex due to mock implementation limitations -// The update functionality is tested through integration tests - -func TestGrpsIOWriterOrchestrator_DeleteGrpsIOService(t *testing.T) { - testCases := []struct { - name string - setupMock func(*mock.MockRepository) (*model.GrpsIOService, uint64) - uid string - revision uint64 - expectedError error - validate func(t *testing.T, mockRepo *mock.MockRepository) - }{ - { - name: "successful service deletion", - setupMock: func(mockRepo *mock.MockRepository) (*model.GrpsIOService, uint64) { - mockRepo.AddProject("project-1", "test-project", "Test Project") - - service := &model.GrpsIOService{ - UID: "service-1", - Type: "primary", - Domain: "lists.test.org", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - GroupName: "test-project", - GlobalOwners: []string{"admin@test.org"}, - Public: true, - Status: "created", - } - mockRepo.AddService(service) - return service, uint64(1) - }, - uid: "service-1", - revision: uint64(1), - expectedError: nil, - validate: func(t *testing.T, mockRepo *mock.MockRepository) { - // Verify service is deleted by trying to get it - _, _, err := mockRepo.GetGrpsIOService(context.Background(), "service-1") - var notFoundErr errs.NotFound - assert.True(t, errors.As(err, ¬FoundErr), "Service should be deleted") - }, - }, - { - name: "delete non-existent service", - setupMock: func(mockRepo *mock.MockRepository) (*model.GrpsIOService, uint64) { - mockRepo.AddProject("project-1", "test-project", "Test Project") - return nil, uint64(1) - }, - uid: "non-existent-service", - revision: uint64(1), - expectedError: errs.NotFound{}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - mockRepo := mock.NewMockRepository() - mockRepo.ClearAll() - - var setupService *model.GrpsIOService - if tc.setupMock != nil { - setupService, _ = tc.setupMock(mockRepo) - } - - grpsIOReader := mock.NewMockGrpsIOReader(mockRepo) - grpsIOWriter := mock.NewMockGrpsIOWriter(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - publisher := mock.NewMockMessagePublisher() - - orchestrator := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(grpsIOReader), - WithGrpsIOWriter(grpsIOWriter), - WithEntityAttributeReader(entityReader), - WithPublisher(publisher), - ) - - // Execute - ctx := context.Background() - err := orchestrator.DeleteGrpsIOService(ctx, tc.uid, tc.revision, setupService) - - // Validate - if tc.expectedError != nil { - require.Error(t, err) - assert.IsType(t, tc.expectedError, err) - } else { - require.NoError(t, err) - if tc.validate != nil { - tc.validate(t, mockRepo) - } - } - - // Additional validation for successful deletes - if tc.expectedError == nil && setupService != nil { - // Verify that the service is no longer accessible - _, _, err := mockRepo.GetGrpsIOService(ctx, tc.uid) - var notFoundErr errs.NotFound - assert.True(t, errors.As(err, ¬FoundErr), "Service should be deleted from repository") - } - }) - } -} - -func TestGrpsIOWriterOrchestrator_UpdateGrpsIOService_ConflictHandling(t *testing.T) { - testCases := []struct { - name string - setupMock func(*mock.MockRepository) (*model.GrpsIOService, uint64) - uid string - expectedRevision uint64 - actualRevision uint64 - expectedError error - validate func(t *testing.T, mockRepo *mock.MockRepository) - }{ - { - name: "revision mismatch returns conflict error", - setupMock: func(mockRepo *mock.MockRepository) (*model.GrpsIOService, uint64) { - mockRepo.ClearAll() - mockRepo.AddProject("project-1", "test-project", "Test Project") - - service := &model.GrpsIOService{ - UID: "service-1", - Type: "primary", - Domain: "lists.test.org", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - GroupName: "test-project", - GlobalOwners: []string{"admin@test.org"}, - Public: true, - Status: "created", - } - - // Create service and simulate revision mismatch - // First add the service normally (revision 1) - mockRepo.AddService(service) - // Then create another copy and update it to increment revision - serviceCopy := *service - serviceCopy.Status = "updated to increment revision" - tempWriter := mock.NewMockGrpsIOServiceWriter(mockRepo) - _, _, _ = tempWriter.UpdateGrpsIOService(context.Background(), service.UID, &serviceCopy, 1) //nolint:errcheck // Test setup - // Now the service has revision 2, but client will try with revision 1 - - return service, 2 - }, - uid: "service-1", - expectedRevision: 1, // Client thinks revision is 1 - actualRevision: 2, // But actual revision is 2 - expectedError: errs.Conflict{}, - validate: func(t *testing.T, mockRepo *mock.MockRepository) { - // Service should still exist with revision 2 - _, rev, err := mockRepo.GetGrpsIOService(context.Background(), "service-1") - assert.NoError(t, err) - assert.Equal(t, uint64(2), rev, "Revision should remain unchanged after conflict") - }, - }, - { - name: "service not found during update", - setupMock: func(mockRepo *mock.MockRepository) (*model.GrpsIOService, uint64) { - mockRepo.ClearAll() - mockRepo.AddProject("project-1", "test-project", "Test Project") - return nil, 0 - }, - uid: "non-existent-service", - expectedRevision: 1, - actualRevision: 0, - expectedError: errs.NotFound{}, - validate: func(t *testing.T, mockRepo *mock.MockRepository) { - assert.Equal(t, 0, mockRepo.GetServiceCount(), "No services should exist") - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - mockRepo := mock.NewMockRepository() - setupService, _ := tc.setupMock(mockRepo) - - grpsIOReader := mock.NewMockGrpsIOReader(mockRepo) - grpsIOWriter := mock.NewMockGrpsIOWriter(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - publisher := mock.NewMockMessagePublisher() - - orchestrator := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(grpsIOReader), - WithGrpsIOWriter(grpsIOWriter), - WithEntityAttributeReader(entityReader), - WithPublisher(publisher), - ) - - // Prepare update data - var updateService *model.GrpsIOService - if setupService != nil { - updateService = &model.GrpsIOService{ - UID: tc.uid, - Type: setupService.Type, - Domain: setupService.Domain, - ProjectUID: setupService.ProjectUID, - ProjectName: setupService.ProjectName, - ProjectSlug: setupService.ProjectSlug, - GroupName: setupService.GroupName, - GlobalOwners: []string{"updated@test.org"}, // Changed field - Public: setupService.Public, - Status: setupService.Status, - } - } else { - updateService = &model.GrpsIOService{ - UID: tc.uid, - Type: "primary", - ProjectUID: "project-1", // Add required fields to avoid validation errors - GlobalOwners: []string{"admin@test.org"}, - } - } - - // Execute - ctx := context.Background() - result, revision, err := orchestrator.UpdateGrpsIOService(ctx, tc.uid, updateService, tc.expectedRevision) - - // Validate error type - if tc.expectedError != nil { - assert.Error(t, err) - - switch tc.expectedError.(type) { - case errs.Conflict: - var conflictErr errs.Conflict - assert.True(t, errors.As(err, &conflictErr), "Expected Conflict error, got %T", err) - case errs.NotFound: - var notFoundErr errs.NotFound - assert.True(t, errors.As(err, ¬FoundErr), "Expected NotFound error, got %T", err) - } - assert.Nil(t, result) - assert.Equal(t, uint64(0), revision) - } else { - assert.NoError(t, err) - assert.NotNil(t, result) - assert.Greater(t, revision, uint64(0)) - } - - // Run additional validation - if tc.validate != nil { - tc.validate(t, mockRepo) - } - }) - } -} - -func TestGrpsIOWriterOrchestrator_DeleteGrpsIOService_ConflictHandling(t *testing.T) { - testCases := []struct { - name string - setupMock func(*mock.MockRepository) (*model.GrpsIOService, uint64) - uid string - expectedRevision uint64 - expectedError error - validate func(t *testing.T, mockRepo *mock.MockRepository) - }{ - { - name: "revision mismatch on delete returns conflict error", - setupMock: func(mockRepo *mock.MockRepository) (*model.GrpsIOService, uint64) { - mockRepo.ClearAll() - mockRepo.AddProject("project-1", "test-project", "Test Project") - - service := &model.GrpsIOService{ - UID: "service-1", - Type: "formation", // Non-primary service can be deleted - Domain: "lists.test.org", - ProjectUID: "project-1", - ProjectName: "Test Project", - ProjectSlug: "test-project", - GroupName: "form-test", - Prefix: "form-", - GlobalOwners: []string{"admin@test.org"}, - Public: true, - Status: "created", - } - - // Create service and simulate revision mismatch - // First add the service normally (revision 1) - mockRepo.AddService(service) - // Then update it twice to get revision 3 - serviceCopy := *service - serviceCopy.Status = "updated to increment revision" - tempWriter := mock.NewMockGrpsIOServiceWriter(mockRepo) - _, _, _ = tempWriter.UpdateGrpsIOService(context.Background(), service.UID, &serviceCopy, 1) //nolint:errcheck // Test setup - _, _, _ = tempWriter.UpdateGrpsIOService(context.Background(), service.UID, &serviceCopy, 2) //nolint:errcheck // Test setup - // Now the service has revision 3, but client will try with revision 1 - - return service, 3 - }, - uid: "service-1", - expectedRevision: 1, // Client thinks revision is 1 - expectedError: errs.Conflict{}, - validate: func(t *testing.T, mockRepo *mock.MockRepository) { - // Service should still exist after failed delete - _, rev, err := mockRepo.GetGrpsIOService(context.Background(), "service-1") - assert.NoError(t, err) - assert.Equal(t, uint64(3), rev, "Service should still exist with original revision") - assert.Equal(t, 1, mockRepo.GetServiceCount(), "Service should not be deleted") - }, - }, - { - name: "delete non-existent service returns not found", - setupMock: func(mockRepo *mock.MockRepository) (*model.GrpsIOService, uint64) { - mockRepo.ClearAll() - return nil, 0 - }, - uid: "non-existent-service", - expectedRevision: 1, - expectedError: errs.NotFound{}, - validate: func(t *testing.T, mockRepo *mock.MockRepository) { - assert.Equal(t, 0, mockRepo.GetServiceCount(), "No services should exist") - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - mockRepo := mock.NewMockRepository() - setupService, _ := tc.setupMock(mockRepo) - - grpsIOReader := mock.NewMockGrpsIOReader(mockRepo) - grpsIOWriter := mock.NewMockGrpsIOWriter(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - publisher := mock.NewMockMessagePublisher() - - orchestrator := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(grpsIOReader), - WithGrpsIOWriter(grpsIOWriter), - WithEntityAttributeReader(entityReader), - WithPublisher(publisher), - ) - - // Execute - ctx := context.Background() - err := orchestrator.DeleteGrpsIOService(ctx, tc.uid, tc.expectedRevision, setupService) - - // Validate error type - if tc.expectedError != nil { - assert.Error(t, err) - - switch tc.expectedError.(type) { - case errs.Conflict: - var conflictErr errs.Conflict - assert.True(t, errors.As(err, &conflictErr), "Expected Conflict error, got %T", err) - case errs.NotFound: - var notFoundErr errs.NotFound - assert.True(t, errors.As(err, ¬FoundErr), "Expected NotFound error, got %T", err) - } - } else { - assert.NoError(t, err) - } - - // Run additional validation - if tc.validate != nil { - tc.validate(t, mockRepo) - } - }) - } -} - -// Helper function to create int64 pointer -func writerServiceInt64Ptr(i int64) *int64 { - return &i -} - -func TestGrpsIOWriterOrchestrator_syncServiceToGroupsIO(t *testing.T) { - testCases := []struct { - name string - setupMock func(*mock.MockRepository) - service *model.GrpsIOService - useNilClient bool - expectedToNotPanic bool - }{ - { - name: "sync with nil GroupsIO client should skip gracefully", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - mockRepo.AddProject("project-1", "test-project", "Test Project") - }, - service: &model.GrpsIOService{ - UID: "service-123", - Type: "primary", - Domain: "lists.test.org", - GroupID: writerServiceInt64Ptr(12345), - GlobalOwners: []string{"admin@test.org", "owner@test.org"}, - ProjectUID: "project-1", - Status: "active", - }, - useNilClient: true, - expectedToNotPanic: true, - }, - { - name: "sync with nil GroupID should skip gracefully", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - }, - service: &model.GrpsIOService{ - UID: "service-123", - Type: "primary", - Domain: "lists.test.org", - GroupID: nil, // Not synced to GroupsIO yet - GlobalOwners: []string{"admin@test.org"}, - ProjectUID: "project-1", - Status: "active", - }, - useNilClient: true, - expectedToNotPanic: true, - }, - { - name: "sync handles domain lookup failure gracefully", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - // Don't add project - this will cause domain lookup to fail - }, - service: &model.GrpsIOService{ - UID: "service-123", - Type: "primary", - Domain: "lists.test.org", - GroupID: writerServiceInt64Ptr(12345), - GlobalOwners: []string{"admin@test.org"}, - ProjectUID: "nonexistent-project", - Status: "active", - }, - useNilClient: true, - expectedToNotPanic: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - mockRepo := mock.NewMockRepository() - tc.setupMock(mockRepo) - - grpsIOReader := mock.NewMockGrpsIOReader(mockRepo) - grpsIOWriter := mock.NewMockGrpsIOWriter(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - // Create orchestrator without GroupsIO client (simulate mock mode) - orchestrator := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(grpsIOReader), - WithGrpsIOWriter(grpsIOWriter), - WithEntityAttributeReader(entityReader), - // No WithGroupsIOClient - this simulates mock mode - ) - - // Execute - should not panic - ctx := context.Background() - concreteOrchestrator := orchestrator.(*grpsIOWriterOrchestrator) - - // This should execute without panicking - assert.NotPanics(t, func() { - concreteOrchestrator.syncServiceToGroupsIO(ctx, tc.service) - }, "syncServiceToGroupsIO should handle all error cases gracefully") - }) - } -} - -// Note: buildServiceIndexerMessage and buildServiceAccessControlMessage methods are private -// and are tested indirectly through the Create/Update/Delete operations above - -func TestGrpsIOWriterOrchestrator_CreateSharedServiceWithParent(t *testing.T) { - testCases := []struct { - name string - setupMock func(*mock.MockRepository) - inputService *model.GrpsIOService - expectedError bool - errorContains string - validate func(t *testing.T, result *model.GrpsIOService, settings *model.GrpsIOServiceSettings, revision uint64, mockRepo *mock.MockRepository) - }{ - { - name: "successful shared service creation with parent primary service", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - - // Add parent project - mockRepo.AddProject("parent-project-uid", "parent-project", "Parent Project") - - // Add sub-project with parent relationship - mockRepo.AddProject("sub-project-uid", "sub-project", "Sub Project") - mockRepo.SetProjectParent("sub-project-uid", "parent-project-uid") - - // Create primary service for parent project - primaryService := &model.GrpsIOService{ - UID: "primary-service-uid", - Type: constants.ServiceTypePrimary, - Domain: "lists.parent.org", - GroupID: writerServiceInt64Ptr(12345), - GlobalOwners: []string{"admin@parent.org"}, - ProjectUID: "parent-project-uid", - ProjectSlug: "parent-project", - ProjectName: "Parent Project", - URL: "https://lists.parent.org", - GroupName: "parent-project", - Public: true, - Status: "created", - Source: constants.SourceMock, - } - mockRepo.AddService(primaryService) - }, - inputService: &model.GrpsIOService{ - Type: constants.ServiceTypeShared, - Domain: "lists.sub.org", - Prefix: "shared", - ProjectUID: "sub-project-uid", - URL: "https://lists.sub.org", - GroupName: "sub-project-shared", - Public: false, - Status: "created", - Source: constants.SourceMock, - }, - expectedError: false, - validate: func(t *testing.T, result *model.GrpsIOService, settings *model.GrpsIOServiceSettings, revision uint64, mockRepo *mock.MockRepository) { - require.NotNil(t, result) - assert.NotEmpty(t, result.UID) - assert.Equal(t, constants.ServiceTypeShared, result.Type) - assert.Equal(t, "sub-project-uid", result.ProjectUID) - assert.Equal(t, "primary-service-uid", result.ParentServiceUID, "parent_service_uid should be automatically set") - assert.Equal(t, "shared", result.Prefix) - assert.Equal(t, uint64(1), revision) - }, - }, - { - name: "shared service fails when project has no parent", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - - // Add project without parent - mockRepo.AddProject("standalone-project-uid", "standalone-project", "Standalone Project") - // Don't set parent - will return not found error - }, - inputService: &model.GrpsIOService{ - Type: constants.ServiceTypeShared, - Domain: "lists.standalone.org", - Prefix: "shared", - ProjectUID: "standalone-project-uid", - URL: "https://lists.standalone.org", - GroupName: "standalone-shared", - Public: false, - Status: "created", - Source: constants.SourceMock, - }, - expectedError: true, - errorContains: "shared services can only be created for sub-projects that have a parent project", - }, - { - name: "shared service fails when parent project has no primary service", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - - // Add parent project - mockRepo.AddProject("parent-project-uid", "parent-project", "Parent Project") - - // Add sub-project with parent relationship - mockRepo.AddProject("sub-project-uid", "sub-project", "Sub Project") - mockRepo.SetProjectParent("sub-project-uid", "parent-project-uid") - - // Don't create primary service for parent project - }, - inputService: &model.GrpsIOService{ - Type: constants.ServiceTypeShared, - Domain: "lists.sub.org", - Prefix: "shared", - ProjectUID: "sub-project-uid", - URL: "https://lists.sub.org", - GroupName: "sub-project-shared", - Public: false, - Status: "created", - Source: constants.SourceMock, - }, - expectedError: true, - errorContains: "parent project must have a primary service before creating shared services", - }, - { - name: "shared service finds parent service among multiple services", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - - // Add parent project - mockRepo.AddProject("parent-project-uid", "parent-project", "Parent Project") - - // Add sub-project with parent relationship - mockRepo.AddProject("sub-project-uid", "sub-project", "Sub Project") - mockRepo.SetProjectParent("sub-project-uid", "parent-project-uid") - - // Create multiple services for parent project - formationService := &model.GrpsIOService{ - UID: "formation-service-uid", - Type: constants.ServiceTypeFormation, - Domain: "lists.parent.org", - Prefix: "formation", - ProjectUID: "parent-project-uid", - Status: "created", - Source: constants.SourceMock, - } - mockRepo.AddService(formationService) - - primaryService := &model.GrpsIOService{ - UID: "primary-service-uid", - Type: constants.ServiceTypePrimary, - Domain: "lists.parent.org", - GroupID: writerServiceInt64Ptr(12345), - GlobalOwners: []string{"admin@parent.org"}, - ProjectUID: "parent-project-uid", - ProjectSlug: "parent-project", - ProjectName: "Parent Project", - URL: "https://lists.parent.org", - GroupName: "parent-project", - Public: true, - Status: "created", - Source: constants.SourceMock, - } - mockRepo.AddService(primaryService) - }, - inputService: &model.GrpsIOService{ - Type: constants.ServiceTypeShared, - Domain: "lists.sub.org", - Prefix: "shared", - ProjectUID: "sub-project-uid", - URL: "https://lists.sub.org", - GroupName: "sub-project-shared", - Public: false, - Status: "created", - Source: constants.SourceMock, - }, - expectedError: false, - validate: func(t *testing.T, result *model.GrpsIOService, settings *model.GrpsIOServiceSettings, revision uint64, mockRepo *mock.MockRepository) { - require.NotNil(t, result) - assert.Equal(t, "primary-service-uid", result.ParentServiceUID, "should find primary service among multiple services") - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - mockRepo := mock.NewMockRepository() - tc.setupMock(mockRepo) - - grpsIOReader := mock.NewMockGrpsIOReader(mockRepo) - grpsIOWriter := mock.NewMockGrpsIOWriter(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - orchestrator := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(grpsIOReader), - WithGrpsIOWriter(grpsIOWriter), - WithEntityAttributeReader(entityReader), - ) - - // Execute - ctx := context.Background() - fullResult, revision, err := orchestrator.CreateGrpsIOService(ctx, tc.inputService, nil) - - // Assert - if tc.expectedError { - require.Error(t, err) - if tc.errorContains != "" { - assert.Contains(t, err.Error(), tc.errorContains) - } - assert.Nil(t, fullResult) - assert.Equal(t, uint64(0), revision) - } else { - require.NoError(t, err) - require.NotNil(t, fullResult) - var result *model.GrpsIOService - var settings *model.GrpsIOServiceSettings - if fullResult != nil { - result = fullResult.Base - settings = fullResult.Settings - } - if tc.validate != nil { - tc.validate(t, result, settings, revision, mockRepo) - } - } - }) - } -} - -func TestGrpsIOWriterOrchestrator_findPrimaryServiceForProject(t *testing.T) { - testCases := []struct { - name string - setupMock func(*mock.MockRepository) - projectUID string - expectedError bool - expectedResult string - }{ - { - name: "finds primary service successfully", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - mockRepo.AddProject("project-1", "test-project", "Test Project") - - primaryService := &model.GrpsIOService{ - UID: "primary-service-uid", - Type: constants.ServiceTypePrimary, - ProjectUID: "project-1", - GlobalOwners: []string{"admin@test.org"}, - Status: "created", - Source: constants.SourceMock, - } - mockRepo.AddService(primaryService) - }, - projectUID: "project-1", - expectedError: false, - expectedResult: "primary-service-uid", - }, - { - name: "returns error when no primary service exists", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - mockRepo.AddProject("project-1", "test-project", "Test Project") - // No services added - }, - projectUID: "project-1", - expectedError: true, - }, - { - name: "returns error when only formation service exists", - setupMock: func(mockRepo *mock.MockRepository) { - mockRepo.ClearAll() - mockRepo.AddProject("project-1", "test-project", "Test Project") - - formationService := &model.GrpsIOService{ - UID: "formation-service-uid", - Type: constants.ServiceTypeFormation, - ProjectUID: "project-1", - Prefix: "formation", - Status: "created", - Source: constants.SourceMock, - } - mockRepo.AddService(formationService) - }, - projectUID: "project-1", - expectedError: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - mockRepo := mock.NewMockRepository() - tc.setupMock(mockRepo) - - grpsIOReader := mock.NewMockGrpsIOReader(mockRepo) - grpsIOWriter := mock.NewMockGrpsIOWriter(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - orchestrator := NewGrpsIOWriterOrchestrator( - WithGrpsIOWriterReader(grpsIOReader), - WithGrpsIOWriter(grpsIOWriter), - WithEntityAttributeReader(entityReader), - ).(*grpsIOWriterOrchestrator) - - // Execute - ctx := context.Background() - result, err := orchestrator.findPrimaryServiceForProject(ctx, tc.projectUID) - - // Assert - if tc.expectedError { - require.Error(t, err) - assert.Nil(t, result) - } else { - require.NoError(t, err) - require.NotNil(t, result) - assert.Equal(t, tc.expectedResult, result.UID) - assert.Equal(t, constants.ServiceTypePrimary, result.Type) - } - }) - } -} diff --git a/internal/service/grpsio_webhook_processor.go b/internal/service/grpsio_webhook_processor.go deleted file mode 100644 index 04e3c34..0000000 --- a/internal/service/grpsio_webhook_processor.go +++ /dev/null @@ -1,463 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - stderrors "errors" - "fmt" - "log/slog" - "strings" - - "golang.org/x/text/cases" - "golang.org/x/text/language" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/redaction" -) - -// grpsIOWebhookProcessor orchestrates webhook event processing with required dependencies -type grpsIOWebhookProcessor struct { - serviceReader port.GrpsIOServiceReader - mailingListReader port.GrpsIOMailingListReader - mailingListWriter port.GrpsIOMailingListWriter - memberReader port.GrpsIOMemberReader - memberWriter port.GrpsIOMemberWriter -} - -// WebhookProcessorOption configures the webhook processor -type WebhookProcessorOption func(*grpsIOWebhookProcessor) - -// WithServiceReader sets the service reader dependency -func WithServiceReader(reader port.GrpsIOServiceReader) WebhookProcessorOption { - return func(p *grpsIOWebhookProcessor) { - p.serviceReader = reader - } -} - -// WithMailingListReader sets the mailing list reader dependency -func WithMailingListReader(reader port.GrpsIOMailingListReader) WebhookProcessorOption { - return func(p *grpsIOWebhookProcessor) { - p.mailingListReader = reader - } -} - -// WithMailingListWriter sets the mailing list writer dependency -func WithMailingListWriter(writer port.GrpsIOMailingListWriter) WebhookProcessorOption { - return func(p *grpsIOWebhookProcessor) { - p.mailingListWriter = writer - } -} - -// WithMemberReader sets the member reader dependency -func WithMemberReader(reader port.GrpsIOMemberReader) WebhookProcessorOption { - return func(p *grpsIOWebhookProcessor) { - p.memberReader = reader - } -} - -// WithMemberWriter sets the member writer dependency -func WithMemberWriter(writer port.GrpsIOMemberWriter) WebhookProcessorOption { - return func(p *grpsIOWebhookProcessor) { - p.memberWriter = writer - } -} - -// NewGrpsIOWebhookProcessor creates a new GroupsIO webhook processor with dependencies -func NewGrpsIOWebhookProcessor(opts ...WebhookProcessorOption) port.GrpsIOWebhookProcessor { - processor := &grpsIOWebhookProcessor{} - - for _, opt := range opts { - opt(processor) - } - - return processor -} - -// ProcessEvent routes webhook events to appropriate handlers -func (p *grpsIOWebhookProcessor) ProcessEvent(ctx context.Context, event *model.GrpsIOWebhookEvent) error { - slog.InfoContext(ctx, "processing groupsio webhook event", "event_type", event.Action) - - switch event.Action { - case constants.SubGroupCreatedEvent: - return p.handleSubGroupCreated(ctx, event) - case constants.SubGroupDeletedEvent: - return p.handleSubGroupDeleted(ctx, event) - case constants.SubGroupMemberAddedEvent: - return p.handleMemberAdded(ctx, event) - case constants.SubGroupMemberRemovedEvent: - return p.handleMemberRemoved(ctx, event) - case constants.SubGroupMemberBannedEvent: - return p.handleMemberBanned(ctx, event) - default: - slog.WarnContext(ctx, "unknown groupsio webhook event type", "event_type", event.Action) - return nil // Ignore unknown events - } -} - -// MINIMAL HANDLERS - Log and validate only - -func (p *grpsIOWebhookProcessor) handleSubGroupCreated(ctx context.Context, event *model.GrpsIOWebhookEvent) error { - if event.Group == nil { - return errors.NewValidation("missing group information in created_subgroup event") - } - - parentGroupID := uint64(event.Group.ParentGroupID) - subgroupID := uint64(event.Group.ID) - subgroupSuffix := event.Extra // e.g., "developers" from "myproject+developers" - fullSubgroupName := fmt.Sprintf("%s+%s", event.Group.Name, subgroupSuffix) - - slog.InfoContext(ctx, "received created_subgroup event", - "subgroup_name", fullSubgroupName, - "parent_group_id", parentGroupID, - "subgroup_id", subgroupID, - "subgroup_suffix", subgroupSuffix) - - // Step 1: Find all services for the parent group_id - services, err := p.serviceReader.GetServicesByGroupID(ctx, parentGroupID) - if err != nil { - slog.ErrorContext(ctx, "failed to get services by group_id", - "parent_group_id", parentGroupID, - "error", err) - return fmt.Errorf("failed to get services by group_id: %w", err) - } - - if len(services) == 0 { - slog.WarnContext(ctx, "no services found for parent group_id - subgroup will not be adopted", - "parent_group_id", parentGroupID, - "subgroup_name", fullSubgroupName) - return nil // Not an error - subgroup just won't be adopted - } - - // Step 2: Determine which service should adopt this subgroup - // Priority: prefix-matching service > primary service - adoptingService := p.findAdoptingService(ctx, services, subgroupSuffix) - if adoptingService == nil { - slog.WarnContext(ctx, "no suitable service found to adopt subgroup", - "parent_group_id", parentGroupID, - "subgroup_name", fullSubgroupName) - return nil // Not an error - just log and skip - } - - // Step 3: Prepare mailing list for creation (idempotency handled by orchestrator) - subgroupIDInt := int64(subgroupID) - mailingList := &model.GrpsIOMailingList{ - ServiceUID: adoptingService.UID, - ProjectUID: adoptingService.ProjectUID, - GroupName: fullSubgroupName, - GroupID: &subgroupIDInt, - Source: constants.SourceWebhook, // Orchestrator uses this for dispatch - Type: model.TypeDiscussionOpen, - Description: "Auto-created from Groups.io webhook", - Title: fullSubgroupName, - } - - // Note: UID, CreatedAt, UpdatedAt, and validation are handled by orchestrator - // Note: Idempotency check moved to orchestrator (ensureMailingListIdempotent) - - // Step 4: Create mailing list (orchestrator handles all validation and idempotency) - createdList, _, err := p.mailingListWriter.CreateGrpsIOMailingList(ctx, mailingList) - if err != nil { - slog.ErrorContext(ctx, "failed to create mailing list", - "error", err, - "mailing_list_uid", mailingList.UID, - "service_uid", adoptingService.UID) - return fmt.Errorf("failed to create mailing list: %w", err) - } - - slog.InfoContext(ctx, "successfully adopted subgroup", - "mailing_list_uid", createdList.UID, - "service_uid", adoptingService.UID, - "service_type", adoptingService.Type, - "subgroup_name", fullSubgroupName, - "subgroup_id", subgroupID) - - return nil -} - -func (p *grpsIOWebhookProcessor) handleSubGroupDeleted(ctx context.Context, event *model.GrpsIOWebhookEvent) error { - subgroupID := uint64(event.ExtraID) - if subgroupID == 0 { - slog.WarnContext(ctx, "deleted_subgroup event missing subgroup_id; ignoring") - return nil - } - - slog.InfoContext(ctx, "received deleted_subgroup event", - "subgroup_id", subgroupID) - - // Step 1: Find mailing list by subgroup_id - mailingList, revision, err := p.mailingListReader.GetMailingListByGroupID(ctx, subgroupID) - if err != nil { - var notFoundErr errors.NotFound - if stderrors.As(err, ¬FoundErr) { - slog.WarnContext(ctx, "mailing list not found for deletion - may have been deleted already", - "subgroup_id", subgroupID) - return nil // Idempotent - not an error if already deleted - } - slog.ErrorContext(ctx, "failed to get mailing list by group_id", - "subgroup_id", subgroupID, - "error", err) - return fmt.Errorf("failed to get mailing list by group_id: %w", err) - } - - slog.InfoContext(ctx, "found mailing list for deletion", - "mailing_list_uid", mailingList.UID, - "service_uid", mailingList.ServiceUID, - "group_name", mailingList.GroupName, - "revision", revision) - - // Step 2: Delete mailing list from NATS KV with optimistic concurrency control - err = p.mailingListWriter.DeleteGrpsIOMailingList(ctx, mailingList.UID, revision, mailingList) - if err != nil { - return handleDeleteError(ctx, err, "mailing list", mailingList.UID, revision) - } - - slog.InfoContext(ctx, "successfully deleted mailing list", - "mailing_list_uid", mailingList.UID, - "service_uid", mailingList.ServiceUID, - "subgroup_id", subgroupID) - - // TODO: NATS Publishing PR #3 - // 1. Check if this was the last subgroup for EnabledServices event - // Call: p.mailingListReader.ListMailingListsByServiceUID(ctx, mailingList.ServiceUID) - // 2. Publish member events to NATS using full MemberInfo (see model/grpsio_webhook_event.go TODOs) - // 3. Ensure downstream consumers (Zoom, Query Service) receive all required fields - - return nil -} - -func (p *grpsIOWebhookProcessor) handleMemberAdded(ctx context.Context, event *model.GrpsIOWebhookEvent) error { - if event.MemberInfo == nil { - return errors.NewValidation("missing member info in added_member event") - } - - memberID := int64(event.MemberInfo.ID) - groupID := int64(event.MemberInfo.GroupID) - email := event.MemberInfo.Email - status := event.MemberInfo.Status - - slog.InfoContext(ctx, "received added_member event", - "member_id", memberID, - "group_id", groupID, - "email", redaction.RedactEmail(email), - "status", status) - - // Step 1: Find mailing list by group_id - // Pattern: Same as handleSubGroupCreated finds service by parent_group_id - mailingList, _, err := p.mailingListReader.GetMailingListByGroupID(ctx, uint64(groupID)) - if err != nil { - var notFoundErr errors.NotFound - if stderrors.As(err, ¬FoundErr) { - slog.WarnContext(ctx, "mailing list not found for parent group_id - member will not be adopted", - "group_id", groupID, - "email", redaction.RedactEmail(email)) - return nil // Not an error - member just won't be adopted - } - slog.ErrorContext(ctx, "failed to get mailing list by group_id", - "group_id", groupID, - "error", err) - return fmt.Errorf("failed to get mailing list by group_id: %w", err) - } - - slog.InfoContext(ctx, "found mailing list for member", - "mailing_list_uid", mailingList.UID, - "group_name", mailingList.GroupName) - - // Step 2: Prepare member for creation (idempotency handled by orchestrator) - // Pattern: Same as handleSubGroupCreated builds MailingList model - firstName, lastName := parseNameFromEmail(email) - - member := &model.GrpsIOMember{ - MailingListUID: mailingList.UID, - Email: email, - FirstName: firstName, - LastName: lastName, - Status: status, - MemberID: &memberID, - GroupID: &groupID, - Source: constants.SourceWebhook, // Critical for source dispatch - } - - // Note: UID, CreatedAt, UpdatedAt, and validation are handled by orchestrator - // Note: Idempotency check moved to orchestrator (ensureMemberIdempotent) - - // Step 3: Create member (orchestrator handles all validation and idempotency) - // Pattern: Same as calling CreateGrpsIOMailingList - createdMember, _, err := p.memberWriter.CreateGrpsIOMember(ctx, member) - if err != nil { - slog.ErrorContext(ctx, "failed to create member", - "error", err, - "email", redaction.RedactEmail(email), - "mailing_list_uid", mailingList.UID) - return fmt.Errorf("failed to create member: %w", err) - } - - slog.InfoContext(ctx, "successfully adopted member", - "member_uid", createdMember.UID, - "mailing_list_uid", mailingList.UID, - "email", redaction.RedactEmail(email), - "member_id", memberID) - - return nil -} - -func (p *grpsIOWebhookProcessor) handleMemberRemoved(ctx context.Context, event *model.GrpsIOWebhookEvent) error { - if event.MemberInfo == nil { - return errors.NewValidation("missing member info in removed_member event") - } - - memberID := uint64(event.MemberInfo.ID) - email := event.MemberInfo.Email - - slog.InfoContext(ctx, "received removed_member event", - "member_id", memberID, - "email", redaction.RedactEmail(email)) - - // Step 1: Find member by Groups.io member ID - // Pattern: Same as handleSubGroupDeleted finds mailing list by subgroup_id - member, revision, err := p.memberReader.GetMemberByGroupsIOMemberID(ctx, memberID) - if err != nil { - var notFoundErr errors.NotFound - if stderrors.As(err, ¬FoundErr) { - slog.WarnContext(ctx, "member not found for deletion - may have been deleted already", - "member_id", memberID) - return nil // Idempotent - not an error if already deleted - } - slog.ErrorContext(ctx, "failed to get member by Groups.io member ID", - "member_id", memberID, - "error", err) - return fmt.Errorf("failed to get member by Groups.io member ID: %w", err) - } - - slog.InfoContext(ctx, "found member for deletion", - "member_uid", member.UID, - "email", redaction.RedactEmail(email), - "revision", revision) - - // Step 2: Delete member with optimistic concurrency control - // Pattern: Same as DeleteGrpsIOMailingList - err = p.memberWriter.DeleteGrpsIOMember(ctx, member.UID, revision, member) - if err != nil { - return handleDeleteError(ctx, err, "member", member.UID, revision) - } - - slog.InfoContext(ctx, "successfully deleted member", - "member_uid", member.UID, - "email", redaction.RedactEmail(email), - "member_id", memberID) - - return nil -} - -func (p *grpsIOWebhookProcessor) handleMemberBanned(ctx context.Context, event *model.GrpsIOWebhookEvent) error { - if event.MemberInfo == nil { - return errors.NewValidation("missing member info in ban_members event") - } - - slog.InfoContext(ctx, "received ban_members event", - "member_id", event.MemberInfo.ID, - "email", redaction.RedactEmail(event.MemberInfo.Email)) - - // Banning is equivalent to removal - reuse removal logic - slog.InfoContext(ctx, "treating banned member as removed") - - return p.handleMemberRemoved(ctx, event) -} - -// parseNameFromEmail extracts a reasonable name from email address -// Example: "john.doe@example.com" -> ("John", "Doe") -func parseNameFromEmail(email string) (firstName, lastName string) { - // Split on @ to get local part - parts := strings.Split(email, "@") - if len(parts) < 2 || parts[0] == "" { - return "Unknown", "" - } - - localPart := parts[0] - - // Try splitting on dots, underscores, hyphens, or plus signs - nameParts := strings.FieldsFunc(localPart, func(r rune) bool { - return r == '.' || r == '_' || r == '-' || r == '+' - }) - - // Use cases.Title from golang.org/x/text instead of deprecated strings.Title - caser := cases.Title(language.Und) - if len(nameParts) >= 2 { - return caser.String(nameParts[0]), caser.String(nameParts[1]) - } - if len(nameParts) == 1 { - return caser.String(nameParts[0]), "" - } - - // Fallback: use local part but don't leak full email - return caser.String(localPart), "" -} - -// Helper methods - -// findAdoptingService determines which service should adopt a subgroup -// Priority: prefix-matching service > primary service -func (p *grpsIOWebhookProcessor) findAdoptingService(ctx context.Context, services []*model.GrpsIOService, subgroupSuffix string) *model.GrpsIOService { - var primaryService *model.GrpsIOService - - // First pass: look for prefix match - for _, service := range services { - // Track primary service as fallback - if service.Type == constants.ServiceTypePrimary { - primaryService = service - } - - // Check if service prefix matches subgroup suffix - if service.Prefix != "" && strings.HasPrefix(subgroupSuffix, service.Prefix) { - slog.InfoContext(ctx, "found prefix-matching service", - "service_uid", service.UID, - "service_type", service.Type, - "service_prefix", service.Prefix, - "subgroup_suffix", subgroupSuffix) - return service // Prefix match takes precedence - } - } - - // If no prefix match, use primary service as fallback - if primaryService != nil { - slog.InfoContext(ctx, "using primary service (no prefix match)", - "service_uid", primaryService.UID, - "service_type", primaryService.Type) - return primaryService - } - - return nil -} - -// handleDeleteError handles common error patterns for delete operations -// Returns nil for NotFound (idempotent), specific error for Conflict, and wraps other errors -func handleDeleteError(ctx context.Context, err error, entityType, entityID string, revision uint64) error { - var notFoundErr errors.NotFound - var conflictErr errors.Conflict - - if stderrors.As(err, ¬FoundErr) { - slog.WarnContext(ctx, "entity not found during deletion - may have been deleted already", - "entity_type", entityType, - "entity_id", entityID) - return nil // Idempotent - } - - if stderrors.As(err, &conflictErr) { - slog.ErrorContext(ctx, "revision mismatch during deletion - concurrent modification detected", - "entity_type", entityType, - "entity_id", entityID, - "expected_revision", revision, - "error", err) - return fmt.Errorf("revision mismatch during %s deletion: %w", entityType, err) - } - - slog.ErrorContext(ctx, "failed to delete entity", - "entity_type", entityType, - "entity_id", entityID, - "error", err) - return fmt.Errorf("failed to delete %s: %w", entityType, err) -} diff --git a/internal/service/grpsio_webhook_processor_test.go b/internal/service/grpsio_webhook_processor_test.go deleted file mode 100644 index a896e2a..0000000 --- a/internal/service/grpsio_webhook_processor_test.go +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "testing" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/mock" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/stretchr/testify/assert" -) - -// TestProcessEvent_CreatedSubgroup tests created_subgroup event processing -func TestProcessEvent_CreatedSubgroup(t *testing.T) { - mockRepo := mock.NewMockRepository() - processor := NewGrpsIOWebhookProcessor( - WithServiceReader(mockRepo), - WithMailingListReader(mockRepo), - WithMailingListWriter(mock.NewMockGrpsIOMailingListWriter(mockRepo)), - ) - ctx := context.Background() - - t.Run("valid created_subgroup event", func(t *testing.T) { - event := &model.GrpsIOWebhookEvent{ - Action: constants.SubGroupCreatedEvent, - Group: &model.GroupInfo{ - ID: 123, - Name: "test-group", - ParentGroupID: 456, - }, - Extra: "developers", - } - - err := processor.ProcessEvent(ctx, event) - assert.NoError(t, err) - }) - - t.Run("created_subgroup event with missing group info", func(t *testing.T) { - event := &model.GrpsIOWebhookEvent{ - Action: constants.SubGroupCreatedEvent, - Extra: "developers", - } - - err := processor.ProcessEvent(ctx, event) - assert.Error(t, err) - assert.Contains(t, err.Error(), "missing group information") - }) - - t.Run("created_subgroup event with empty extra", func(t *testing.T) { - event := &model.GrpsIOWebhookEvent{ - Action: constants.SubGroupCreatedEvent, - Group: &model.GroupInfo{ - ID: 123, - Name: "test-group", - ParentGroupID: 456, - }, - Extra: "", - } - - err := processor.ProcessEvent(ctx, event) - assert.NoError(t, err) - }) -} - -// TestProcessEvent_DeletedSubgroup tests deleted_subgroup event processing -func TestProcessEvent_DeletedSubgroup(t *testing.T) { - mockRepo := mock.NewMockRepository() - processor := NewGrpsIOWebhookProcessor( - WithServiceReader(mockRepo), - WithMailingListReader(mockRepo), - WithMailingListWriter(mock.NewMockGrpsIOMailingListWriter(mockRepo)), - ) - ctx := context.Background() - - t.Run("valid deleted_subgroup event", func(t *testing.T) { - event := &model.GrpsIOWebhookEvent{ - Action: constants.SubGroupDeletedEvent, - ExtraID: 789, - } - - err := processor.ProcessEvent(ctx, event) - assert.NoError(t, err) - }) - - t.Run("deleted_subgroup event with zero extra_id", func(t *testing.T) { - event := &model.GrpsIOWebhookEvent{ - Action: constants.SubGroupDeletedEvent, - ExtraID: 0, - } - - err := processor.ProcessEvent(ctx, event) - assert.NoError(t, err) - }) - - t.Run("deleted_subgroup event with missing extra_id", func(t *testing.T) { - event := &model.GrpsIOWebhookEvent{ - Action: constants.SubGroupDeletedEvent, - } - - err := processor.ProcessEvent(ctx, event) - assert.NoError(t, err) // extra_id defaults to 0 - }) -} - -// TestProcessEvent_MemberAdded tests added_member event processing -func TestProcessEvent_MemberAdded(t *testing.T) { - mockRepo := mock.NewMockRepository() - processor := NewGrpsIOWebhookProcessor( - WithServiceReader(mockRepo), - WithMailingListReader(mockRepo), - WithMailingListWriter(mock.NewMockGrpsIOMailingListWriter(mockRepo)), - WithMemberReader(mockRepo), - WithMemberWriter(mock.NewMockGrpsIOMemberWriter(mockRepo)), - ) - ctx := context.Background() - - t.Run("valid added_member event", func(t *testing.T) { - event := &model.GrpsIOWebhookEvent{ - Action: constants.SubGroupMemberAddedEvent, - MemberInfo: &model.MemberInfo{ - ID: 1, - GroupID: 123, - Email: "test@example.com", - Status: "approved", - }, - } - - err := processor.ProcessEvent(ctx, event) - assert.NoError(t, err) - }) - - t.Run("added_member event with missing member_info", func(t *testing.T) { - event := &model.GrpsIOWebhookEvent{ - Action: constants.SubGroupMemberAddedEvent, - } - - err := processor.ProcessEvent(ctx, event) - assert.Error(t, err) - assert.Contains(t, err.Error(), "missing member info") - }) - - t.Run("added_member event with partial member_info", func(t *testing.T) { - event := &model.GrpsIOWebhookEvent{ - Action: constants.SubGroupMemberAddedEvent, - MemberInfo: &model.MemberInfo{ - Email: "test@example.com", - // Missing other fields - }, - } - - err := processor.ProcessEvent(ctx, event) - assert.NoError(t, err) // Partial info is allowed - }) -} - -// TestProcessEvent_MemberRemoved tests removed_member event processing -func TestProcessEvent_MemberRemoved(t *testing.T) { - mockRepo := mock.NewMockRepository() - processor := NewGrpsIOWebhookProcessor( - WithServiceReader(mockRepo), - WithMailingListReader(mockRepo), - WithMailingListWriter(mock.NewMockGrpsIOMailingListWriter(mockRepo)), - WithMemberReader(mockRepo), - WithMemberWriter(mock.NewMockGrpsIOMemberWriter(mockRepo)), - ) - ctx := context.Background() - - t.Run("valid removed_member event", func(t *testing.T) { - event := &model.GrpsIOWebhookEvent{ - Action: constants.SubGroupMemberRemovedEvent, - MemberInfo: &model.MemberInfo{ - ID: 1, - GroupID: 123, - Email: "test@example.com", - Status: "approved", - }, - } - - err := processor.ProcessEvent(ctx, event) - assert.NoError(t, err) - }) - - t.Run("removed_member event with missing member_info", func(t *testing.T) { - event := &model.GrpsIOWebhookEvent{ - Action: constants.SubGroupMemberRemovedEvent, - } - - err := processor.ProcessEvent(ctx, event) - assert.Error(t, err) - assert.Contains(t, err.Error(), "missing member info") - }) -} - -// TestProcessEvent_MemberBanned tests ban_members event processing -func TestProcessEvent_MemberBanned(t *testing.T) { - mockRepo := mock.NewMockRepository() - processor := NewGrpsIOWebhookProcessor( - WithServiceReader(mockRepo), - WithMailingListReader(mockRepo), - WithMailingListWriter(mock.NewMockGrpsIOMailingListWriter(mockRepo)), - WithMemberReader(mockRepo), - WithMemberWriter(mock.NewMockGrpsIOMemberWriter(mockRepo)), - ) - ctx := context.Background() - - t.Run("valid ban_members event", func(t *testing.T) { - event := &model.GrpsIOWebhookEvent{ - Action: constants.SubGroupMemberBannedEvent, - MemberInfo: &model.MemberInfo{ - ID: 1, - GroupID: 123, - Email: "test@example.com", - Status: "banned", - }, - } - - err := processor.ProcessEvent(ctx, event) - assert.NoError(t, err) - }) - - t.Run("ban_members event with missing member_info", func(t *testing.T) { - event := &model.GrpsIOWebhookEvent{ - Action: constants.SubGroupMemberBannedEvent, - } - - err := processor.ProcessEvent(ctx, event) - assert.Error(t, err) - assert.Contains(t, err.Error(), "missing member info") - }) -} - -// TestProcessEvent_UnknownEventType tests unknown event type handling -func TestProcessEvent_UnknownEventType(t *testing.T) { - mockRepo := mock.NewMockRepository() - processor := NewGrpsIOWebhookProcessor( - WithServiceReader(mockRepo), - WithMailingListReader(mockRepo), - WithMailingListWriter(mock.NewMockGrpsIOMailingListWriter(mockRepo)), - ) - ctx := context.Background() - - event := &model.GrpsIOWebhookEvent{ - Action: "unknown_event_type", - } - - err := processor.ProcessEvent(ctx, event) - assert.NoError(t, err) // Unknown events are ignored, not errors -} - -// NOTE: Retry logic tests have been moved to pkg/utils/retry_test.go -// The retry utilities (RetryConfig, RetryWithExponentialBackoff) are now -// general-purpose utilities in the utils package. - -// TestNewGrpsIOWebhookProcessor tests processor creation -func TestNewGrpsIOWebhookProcessor(t *testing.T) { - mockRepo := mock.NewMockRepository() - processor := NewGrpsIOWebhookProcessor( - WithServiceReader(mockRepo), - WithMailingListReader(mockRepo), - WithMailingListWriter(mock.NewMockGrpsIOMailingListWriter(mockRepo)), - ) - - assert.NotNil(t, processor) - assert.Implements(t, (*port.GrpsIOWebhookProcessor)(nil), processor) -} diff --git a/internal/service/grpsio_writer.go b/internal/service/grpsio_writer.go deleted file mode 100644 index cc3ff40..0000000 --- a/internal/service/grpsio_writer.go +++ /dev/null @@ -1,363 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - stderrors "errors" - "fmt" - "log/slog" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/port" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/groupsio" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - errs "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/errors" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/utils" -) - -// handleIdempotencyLookupError processes errors from idempotency lookups and determines the appropriate action -// Returns true if the error was handled (caller should continue), false if it should be propagated -// -// This helper handles distributed systems challenges with NATS KV + Database: -// - NotFound: Expected during idempotency checks - safe to continue -// - ServiceUnavailable: NATS/storage unavailable - fail operation to prevent duplicates -// - Other errors: Unexpected errors - propagate for investigation -func handleIdempotencyLookupError(ctx context.Context, err error, lookupType, identifier string) (bool, error) { - var notFoundErr errs.NotFound - var unavailableErr errs.ServiceUnavailable - - if stderrors.As(err, ¬FoundErr) { - // NotFound is expected during idempotency checks - safe to continue - slog.DebugContext(ctx, "not found during idempotency check, will continue", - "lookup_type", lookupType, - "identifier", identifier) - return true, nil - } - - if stderrors.As(err, &unavailableErr) { - // Storage unavailable - cannot verify idempotency safely - // Fail operation to prevent potential duplicates - slog.ErrorContext(ctx, "storage unavailable during idempotency check, cannot verify if entity exists", - "error", err, - "lookup_type", lookupType, - "identifier", identifier) - return false, err // Propagate ServiceUnavailable → HTTP 503 → client retry - } - - // Unexpected error (data corruption, permission denied, etc.) - propagate - slog.ErrorContext(ctx, "unexpected error during idempotency check", - "error", err, - "lookup_type", lookupType, - "identifier", identifier) - return false, fmt.Errorf("idempotency check failed: %w", err) -} - -// GrpsIOWriter defines the composite interface that combines writers -type GrpsIOWriter interface { - GrpsIOServiceWriter - GrpsIOMailingListWriter - port.GrpsIOMemberWriter -} - -// GrpsIOServiceWriter defines the interface for service write operations -type GrpsIOServiceWriter interface { - // CreateGrpsIOService creates a new service and its settings, and returns the service, settings, and revision - CreateGrpsIOService(ctx context.Context, service *model.GrpsIOService, settings *model.GrpsIOServiceSettings) (*model.GrpsIOServiceFull, uint64, error) - - // UpdateGrpsIOService updates an existing service with expected revision and returns updated service with new revision - UpdateGrpsIOService(ctx context.Context, uid string, service *model.GrpsIOService, expectedRevision uint64) (*model.GrpsIOService, uint64, error) - - // DeleteGrpsIOService deletes a service by UID with expected revision - // Pass the existing service data to DeleteGrpsIOService to allow the storage layer to perform - // constraint cleanup based on the current state of the service. The 'service' parameter provides - // necessary context for deleting related constraints or dependent records. - DeleteGrpsIOService(ctx context.Context, uid string, expectedRevision uint64, service *model.GrpsIOService) error - - // UpdateGrpsIOServiceSettings updates service settings with expected revision and returns updated settings with new revision - UpdateGrpsIOServiceSettings(ctx context.Context, settings *model.GrpsIOServiceSettings, expectedRevision uint64) (*model.GrpsIOServiceSettings, uint64, error) -} - -// GrpsIOMailingListWriter defines the interface for mailing list write operations -type GrpsIOMailingListWriter interface { - // CreateGrpsIOMailingList creates a new mailing list and returns the mailing list with revision - CreateGrpsIOMailingList(ctx context.Context, request *model.GrpsIOMailingList, settings *model.GrpsIOMailingListSettings) (*model.GrpsIOMailingList, uint64, error) - - // UpdateGrpsIOMailingList updates an existing mailing list with expected revision and returns updated mailing list with new revision - UpdateGrpsIOMailingList(ctx context.Context, uid string, mailingList *model.GrpsIOMailingList, expectedRevision uint64) (*model.GrpsIOMailingList, uint64, error) - - // DeleteGrpsIOMailingList deletes a mailing list by UID with expected revision - DeleteGrpsIOMailingList(ctx context.Context, uid string, expectedRevision uint64, mailingList *model.GrpsIOMailingList) error - - // UpdateGrpsIOMailingListSettings updates mailing list settings with expected revision and returns updated settings with new revision - UpdateGrpsIOMailingListSettings(ctx context.Context, settings *model.GrpsIOMailingListSettings, expectedRevision uint64) (*model.GrpsIOMailingListSettings, uint64, error) -} - -// grpsIOWriterOrchestratorOption defines a function type for setting options on the composite orchestrator -type grpsIOWriterOrchestratorOption func(*grpsIOWriterOrchestrator) - -// WithGrpsIOWriter sets the writer orchestrator -func WithGrpsIOWriter(writer port.GrpsIOWriter) grpsIOWriterOrchestratorOption { - return func(w *grpsIOWriterOrchestrator) { - w.grpsIOWriter = writer - } -} - -// WithGrpsIOWriterReader sets the reader orchestrator for writer -func WithGrpsIOWriterReader(reader port.GrpsIOReader) grpsIOWriterOrchestratorOption { - return func(w *grpsIOWriterOrchestrator) { - w.grpsIOReader = reader - } -} - -// WithEntityAttributeReader sets the entity attribute reader -func WithEntityAttributeReader(reader port.EntityAttributeReader) grpsIOWriterOrchestratorOption { - return func(w *grpsIOWriterOrchestrator) { - w.entityReader = reader - } -} - -// WithPublisher sets the publisher -func WithPublisher(publisher port.MessagePublisher) grpsIOWriterOrchestratorOption { - return func(w *grpsIOWriterOrchestrator) { - w.publisher = publisher - } -} - -// WithGroupsIOClient sets the GroupsIO client (may be nil for mock/disabled mode) -func WithGroupsIOClient(client groupsio.ClientInterface) grpsIOWriterOrchestratorOption { - return func(w *grpsIOWriterOrchestrator) { - w.groupsClient = client - } -} - -// WithMemberRepository sets the member repository for direct storage operations -func WithMemberRepository(repo port.GrpsIOMemberRepository) grpsIOWriterOrchestratorOption { - return func(w *grpsIOWriterOrchestrator) { - w.memberRepository = repo - } -} - -// grpsIOWriterOrchestrator orchestrates the service writing process -type grpsIOWriterOrchestrator struct { - grpsIOWriter port.GrpsIOWriter - memberRepository port.GrpsIOMemberRepository - grpsIOReader port.GrpsIOReader - entityReader port.EntityAttributeReader - publisher port.MessagePublisher - groupsClient groupsio.ClientInterface // May be nil for mock/disabled mode -} - -// NewGrpsIOWriterOrchestrator creates a new composite writer orchestrator using the option pattern -func NewGrpsIOWriterOrchestrator(opts ...grpsIOWriterOrchestratorOption) GrpsIOWriter { - uc := &grpsIOWriterOrchestrator{} - for _, opt := range opts { - opt(uc) - } - - return uc -} - -// BaseGrpsIOWriter methods - delegated to underlying writer - -// GetKeyRevision retrieves the revision for a given key (used for cleanup operations) -func (o *grpsIOWriterOrchestrator) GetKeyRevision(ctx context.Context, key string) (uint64, error) { - return o.grpsIOWriter.GetKeyRevision(ctx, key) -} - -// Delete removes a key with the given revision (used for cleanup and rollback) -func (o *grpsIOWriterOrchestrator) Delete(ctx context.Context, key string, revision uint64) error { - return o.grpsIOWriter.Delete(ctx, key, revision) -} - -// UniqueMember validates member email is unique within mailing list -func (o *grpsIOWriterOrchestrator) UniqueMember(ctx context.Context, member *model.GrpsIOMember) (string, error) { - return o.memberRepository.UniqueMember(ctx, member) -} - -// CreateMemberSecondaryIndices creates lookup indices for Groups.io IDs -func (o *grpsIOWriterOrchestrator) CreateMemberSecondaryIndices(ctx context.Context, member *model.GrpsIOMember) ([]string, error) { - return o.memberRepository.CreateMemberSecondaryIndices(ctx, member) -} - -// Common methods implementation - -// deleteKeys removes keys by getting their revision and deleting them -// This is used both for rollback scenarios and cleanup operations -func (o *grpsIOWriterOrchestrator) deleteKeys(ctx context.Context, keys []string, isRollback bool) { - if len(keys) == 0 { - return - } - - slog.DebugContext(ctx, "deleting keys", - "keys", keys, - "is_rollback", isRollback, - ) - - for _, key := range keys { - // Get revision using reader interface first (for entity UIDs), then try direct storage (for constraint keys) - var rev uint64 - var errGet error - - // Try to get revision using reader interface first (works for entity UIDs) - if o.grpsIOReader != nil { - rev, errGet = o.grpsIOReader.GetRevision(ctx, key) - } - - // If reader method fails, try the direct storage approach (works for constraint keys) - if errGet != nil { - rev, errGet = o.grpsIOWriter.GetKeyRevision(ctx, key) - } - - if errGet != nil { - slog.ErrorContext(ctx, "failed to get revision for key deletion", - "key", key, - "error", errGet, - "is_rollback", isRollback, - ) - continue - } - - // Delete the key using the revision - err := o.grpsIOWriter.Delete(ctx, key, rev) - if err != nil { - slog.ErrorContext(ctx, "failed to delete key", - "key", key, - "error", err, - "is_rollback", isRollback, - ) - } else { - slog.DebugContext(ctx, "successfully deleted key", - "key", key, - "is_rollback", isRollback, - ) - } - } - - slog.DebugContext(ctx, "key deletion completed", - "keys_count", len(keys), - "is_rollback", isRollback, - ) -} - -// getGroupsIODomainForResource resolves the Groups.io domain for a resource -// Handles both direct service lookup and member -> mailing list -> service lookup chains -// getServiceFromMailingList retrieves the parent service for a mailing list -func (o *grpsIOWriterOrchestrator) getServiceFromMailingList(ctx context.Context, mailingListUID string) (*model.GrpsIOService, error) { - mailingList, _, err := o.grpsIOReader.GetGrpsIOMailingList(ctx, mailingListUID) - if err != nil { - slog.ErrorContext(ctx, "failed to get mailing list for Groups.io domain", "error", err, "mailing_list_uid", mailingListUID) - return nil, err - } - - parentService, _, err := o.grpsIOReader.GetGrpsIOService(ctx, mailingList.ServiceUID) - if err != nil { - slog.ErrorContext(ctx, "failed to get parent service for Groups.io domain", "error", err, "service_uid", mailingList.ServiceUID) - return nil, err - } - - return parentService, nil -} - -func (o *grpsIOWriterOrchestrator) getGroupsIODomainForResource(ctx context.Context, resourceUID string, resourceType string) (string, error) { - switch resourceType { - case constants.ResourceTypeService: - // Direct service lookup - service, _, err := o.grpsIOReader.GetGrpsIOService(ctx, resourceUID) - if err != nil { - slog.ErrorContext(ctx, "failed to get service for Groups.io domain", "error", err, "service_uid", resourceUID) - return "", err - } - return service.GetDomain(), nil - - case constants.ResourceTypeMember: - // Member -> Mailing List -> Service lookup chain - member, _, err := o.grpsIOReader.GetGrpsIOMember(ctx, resourceUID) - if err != nil { - slog.ErrorContext(ctx, "failed to get member for Groups.io domain", "error", err, "member_uid", resourceUID) - return "", err - } - - parentService, err := o.getServiceFromMailingList(ctx, member.MailingListUID) - if err != nil { - return "", err - } - - return parentService.GetDomain(), nil - - case constants.ResourceTypeMailingList: - // Mailing List -> Service lookup - parentService, err := o.getServiceFromMailingList(ctx, resourceUID) - if err != nil { - return "", err - } - - return parentService.GetDomain(), nil - - default: - return "", fmt.Errorf("unsupported resource type: %s", resourceType) - } -} - -// deleteSubgroupWithCleanup handles Groups.io subgroup deletion with proper error handling -func (o *grpsIOWriterOrchestrator) deleteSubgroupWithCleanup(ctx context.Context, serviceUID string, subgroupID *int64) { - // Guard clause: skip if Groups.io client not available or subgroup not synced - if o.groupsClient == nil || subgroupID == nil { - slog.InfoContext(ctx, "Groups.io integration disabled or mailing list not synced - skipping subgroup deletion") - return - } - - // Get domain using helper method - domain, err := o.getGroupsIODomainForResource(ctx, serviceUID, constants.ResourceTypeService) - if err != nil { - slog.WarnContext(ctx, "Groups.io subgroup cleanup skipped due to parent service lookup failure, local deletion will proceed", - "error", err, "service_uid", serviceUID) - return - } - - // Perform Groups.io subgroup deletion - err = o.groupsClient.DeleteSubgroup(ctx, domain, utils.Int64PtrToUint64(subgroupID)) - if err != nil { - slog.WarnContext(ctx, "Groups.io subgroup deletion failed, local deletion will proceed - orphaned subgroups can be cleaned up later", - "error", err, "domain", domain, "subgroup_id", *subgroupID) - } else { - slog.InfoContext(ctx, "Groups.io subgroup deleted successfully", - "subgroup_id", *subgroupID, "domain", domain) - } -} - -// removeMemberFromGroupsIO handles Groups.io member deletion with proper error handling -func (o *grpsIOWriterOrchestrator) removeMemberFromGroupsIO(ctx context.Context, member *model.GrpsIOMember) error { - // Guard clause: skip if Groups.io client not available or member not synced - if o.groupsClient == nil || member == nil || member.MemberID == nil { - slog.InfoContext(ctx, "Groups.io integration disabled or member not synced - skipping Groups.io deletion") - return nil - } - - logger := slog.With("member_uid", member.UID) - if member.GroupID != nil { - logger = logger.With("group_id", *member.GroupID) - } - if member.MemberID != nil { - logger = logger.With("member_id", *member.MemberID) - } - - // Get domain using helper method through member lookup chain - domain, err := o.getGroupsIODomainForResource(ctx, member.UID, constants.ResourceTypeMember) - if err != nil { - logger.ErrorContext(ctx, "failed to get Groups.io domain for member", "error", err) - return fmt.Errorf("failed to get Groups.io domain for member: %w", err) - } - logger = logger.With("domain", domain) - - // Perform Groups.io member removal - err = o.groupsClient.RemoveMember(ctx, domain, utils.Int64PtrToUint64(member.GroupID), utils.Int64PtrToUint64(member.MemberID)) - if err != nil { - logger.ErrorContext(ctx, "Groups.io member deletion failed", "error", err) - return fmt.Errorf("Groups.io member deletion failed: %w", err) - } - - logger.InfoContext(ctx, "Groups.io member deleted successfully") - return nil -} diff --git a/internal/service/itx/groupsio_member.go b/internal/service/itx/groupsio_member.go new file mode 100644 index 0000000..17d6bf7 --- /dev/null +++ b/internal/service/itx/groupsio_member.go @@ -0,0 +1,58 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package itx + +import ( + "context" + + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain" + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/models" +) + +// GroupsioMemberService handles ITX GroupsIO member operations (pass-through, no ID mapping needed). +type GroupsioMemberService struct { + client domain.GroupsioMemberClient +} + +// NewGroupsioMemberService creates a new GroupsIO member handler. +func NewGroupsioMemberService(client domain.GroupsioMemberClient) *GroupsioMemberService { + return &GroupsioMemberService{ + client: client, + } +} + +// ListMembers lists members of a subgroup. +func (s *GroupsioMemberService) ListMembers(ctx context.Context, subgroupID string) (*models.GroupsioMemberListResponse, error) { + return s.client.ListMembers(ctx, subgroupID) +} + +// AddMember adds a member to a subgroup. +func (s *GroupsioMemberService) AddMember(ctx context.Context, subgroupID string, req *models.GroupsioMemberRequest) (*models.GroupsioMember, error) { + return s.client.AddMember(ctx, subgroupID, req) +} + +// GetMember retrieves a member by ID. +func (s *GroupsioMemberService) GetMember(ctx context.Context, subgroupID, memberID string) (*models.GroupsioMember, error) { + return s.client.GetMember(ctx, subgroupID, memberID) +} + +// UpdateMember updates a member. +func (s *GroupsioMemberService) UpdateMember(ctx context.Context, subgroupID, memberID string, req *models.GroupsioMemberRequest) (*models.GroupsioMember, error) { + return s.client.UpdateMember(ctx, subgroupID, memberID, req) +} + +// DeleteMember removes a member from a subgroup. +func (s *GroupsioMemberService) DeleteMember(ctx context.Context, subgroupID, memberID string) error { + return s.client.DeleteMember(ctx, subgroupID, memberID) +} + +// InviteMembers sends invitations to multiple email addresses. +func (s *GroupsioMemberService) InviteMembers(ctx context.Context, subgroupID string, req *models.GroupsioInviteMembersRequest) error { + return s.client.InviteMembers(ctx, subgroupID, req) +} + +// CheckSubscriber checks if an email is subscribed to a subgroup. +func (s *GroupsioMemberService) CheckSubscriber(ctx context.Context, req *models.GroupsioCheckSubscriberRequest) (*models.GroupsioCheckSubscriberResponse, error) { + return s.client.CheckSubscriber(ctx, req) +} diff --git a/internal/service/itx/groupsio_member_test.go b/internal/service/itx/groupsio_member_test.go new file mode 100644 index 0000000..067c3b3 --- /dev/null +++ b/internal/service/itx/groupsio_member_test.go @@ -0,0 +1,171 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package itx + +import ( + "context" + "errors" + "testing" + + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/models" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Member operations have no ID mapping — they delegate directly to the client. + +func TestGroupsioMemberService_ListMembers(t *testing.T) { + client := &mockMemberClient{ + listMembers: func(_ context.Context, subgroupID string) (*models.GroupsioMemberListResponse, error) { + assert.Equal(t, "sg-42", subgroupID) + return &models.GroupsioMemberListResponse{ + Items: []*models.GroupsioMember{ + {ID: "m-1", Email: "alice@example.com"}, + {ID: "m-2", Email: "bob@example.com"}, + }, + Total: 2, + }, nil + }, + } + + svc := NewGroupsioMemberService(client) + resp, err := svc.ListMembers(context.Background(), "sg-42") + + require.NoError(t, err) + assert.Equal(t, 2, resp.Total) + assert.Equal(t, "alice@example.com", resp.Items[0].Email) +} + +func TestGroupsioMemberService_AddMember(t *testing.T) { + client := &mockMemberClient{ + addMember: func(_ context.Context, subgroupID string, req *models.GroupsioMemberRequest) (*models.GroupsioMember, error) { + assert.Equal(t, "sg-42", subgroupID) + assert.Equal(t, "new@example.com", req.Email) + return &models.GroupsioMember{ID: "m-new", Email: req.Email}, nil + }, + } + + svc := NewGroupsioMemberService(client) + resp, err := svc.AddMember(context.Background(), "sg-42", &models.GroupsioMemberRequest{Email: "new@example.com"}) + + require.NoError(t, err) + assert.Equal(t, "m-new", resp.ID) + assert.Equal(t, "new@example.com", resp.Email) +} + +func TestGroupsioMemberService_GetMember(t *testing.T) { + client := &mockMemberClient{ + getMember: func(_ context.Context, subgroupID, memberID string) (*models.GroupsioMember, error) { + assert.Equal(t, "sg-42", subgroupID) + assert.Equal(t, "m-7", memberID) + return &models.GroupsioMember{ID: "m-7", Name: "Alice"}, nil + }, + } + + svc := NewGroupsioMemberService(client) + resp, err := svc.GetMember(context.Background(), "sg-42", "m-7") + + require.NoError(t, err) + assert.Equal(t, "Alice", resp.Name) +} + +func TestGroupsioMemberService_UpdateMember(t *testing.T) { + client := &mockMemberClient{ + updateMember: func(_ context.Context, subgroupID, memberID string, req *models.GroupsioMemberRequest) (*models.GroupsioMember, error) { + assert.Equal(t, "sg-42", subgroupID) + assert.Equal(t, "m-7", memberID) + return &models.GroupsioMember{ID: "m-7", Name: req.Name}, nil + }, + } + + svc := NewGroupsioMemberService(client) + resp, err := svc.UpdateMember(context.Background(), "sg-42", "m-7", &models.GroupsioMemberRequest{Name: "Updated"}) + + require.NoError(t, err) + assert.Equal(t, "Updated", resp.Name) +} + +func TestGroupsioMemberService_DeleteMember(t *testing.T) { + called := false + client := &mockMemberClient{ + deleteMember: func(_ context.Context, subgroupID, memberID string) error { + assert.Equal(t, "sg-42", subgroupID) + assert.Equal(t, "m-7", memberID) + called = true + return nil + }, + } + + svc := NewGroupsioMemberService(client) + err := svc.DeleteMember(context.Background(), "sg-42", "m-7") + + require.NoError(t, err) + assert.True(t, called) +} + +func TestGroupsioMemberService_InviteMembers(t *testing.T) { + client := &mockMemberClient{ + inviteMembers: func(_ context.Context, subgroupID string, req *models.GroupsioInviteMembersRequest) error { + assert.Equal(t, "sg-42", subgroupID) + assert.Equal(t, []string{"a@example.com", "b@example.com"}, req.Emails) + return nil + }, + } + + svc := NewGroupsioMemberService(client) + err := svc.InviteMembers(context.Background(), "sg-42", &models.GroupsioInviteMembersRequest{ + Emails: []string{"a@example.com", "b@example.com"}, + }) + require.NoError(t, err) +} + +func TestGroupsioMemberService_CheckSubscriber_Subscribed(t *testing.T) { + client := &mockMemberClient{ + checkSubscriber: func(_ context.Context, req *models.GroupsioCheckSubscriberRequest) (*models.GroupsioCheckSubscriberResponse, error) { + assert.Equal(t, "a@example.com", req.Email) + assert.Equal(t, "sg-42", req.SubgroupID) + return &models.GroupsioCheckSubscriberResponse{Subscribed: true}, nil + }, + } + + svc := NewGroupsioMemberService(client) + resp, err := svc.CheckSubscriber(context.Background(), &models.GroupsioCheckSubscriberRequest{ + Email: "a@example.com", + SubgroupID: "sg-42", + }) + + require.NoError(t, err) + assert.True(t, resp.Subscribed) +} + +func TestGroupsioMemberService_CheckSubscriber_NotSubscribed(t *testing.T) { + client := &mockMemberClient{ + checkSubscriber: func(_ context.Context, _ *models.GroupsioCheckSubscriberRequest) (*models.GroupsioCheckSubscriberResponse, error) { + return &models.GroupsioCheckSubscriberResponse{Subscribed: false}, nil + }, + } + + svc := NewGroupsioMemberService(client) + resp, err := svc.CheckSubscriber(context.Background(), &models.GroupsioCheckSubscriberRequest{ + Email: "b@example.com", SubgroupID: "sg-42", + }) + + require.NoError(t, err) + assert.False(t, resp.Subscribed) +} + +func TestGroupsioMemberService_ClientError_Propagated(t *testing.T) { + clientErr := errors.New("member client error") + client := &mockMemberClient{ + getMember: func(_ context.Context, _, _ string) (*models.GroupsioMember, error) { + return nil, clientErr + }, + } + + svc := NewGroupsioMemberService(client) + _, err := svc.GetMember(context.Background(), "sg-42", "m-7") + + require.Error(t, err) + assert.True(t, errors.Is(err, clientErr)) +} diff --git a/internal/service/itx/groupsio_service.go b/internal/service/itx/groupsio_service.go new file mode 100644 index 0000000..9ad94a9 --- /dev/null +++ b/internal/service/itx/groupsio_service.go @@ -0,0 +1,142 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +// Package itx provides ITX proxy service implementations for GroupsIO operations. +package itx + +import ( + "context" + + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain" + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/models" +) + +// GroupsioServiceService handles ITX GroupsIO service operations with ID mapping. +type GroupsioServiceService struct { + client domain.GroupsioServiceClient + idMapper domain.IDMapper +} + +// NewGroupsioServiceService creates a new GroupsIO service handler. +func NewGroupsioServiceService(client domain.GroupsioServiceClient, idMapper domain.IDMapper) *GroupsioServiceService { + return &GroupsioServiceService{ + client: client, + idMapper: idMapper, + } +} + +// ListServices lists GroupsIO services, mapping project_uid (v2) -> project_id (v1) before calling ITX. +func (s *GroupsioServiceService) ListServices(ctx context.Context, projectUID string) (*models.GroupsioServiceListResponse, error) { + v1ProjectID := "" + if projectUID != "" { + var err error + v1ProjectID, err = s.idMapper.MapProjectV2ToV1(ctx, projectUID) + if err != nil { + return nil, err + } + } + + resp, err := s.client.ListServices(ctx, v1ProjectID) + if err != nil { + return nil, err + } + + // Map project_id (v1) -> project_uid (v2) in responses + for _, svc := range resp.Items { + if svc.ProjectID != "" { + v2UID, mapErr := s.idMapper.MapProjectV1ToV2(ctx, svc.ProjectID) + if mapErr != nil { + return nil, mapErr + } + svc.ProjectID = v2UID + } + } + + return resp, nil +} + +// CreateService creates a new GroupsIO service, mapping project_uid (v2) -> project_id (v1). +func (s *GroupsioServiceService) CreateService(ctx context.Context, req *models.GroupsioServiceRequest) (*models.GroupsioService, error) { + if req.ProjectID != "" { + v1ID, err := s.idMapper.MapProjectV2ToV1(ctx, req.ProjectID) + if err != nil { + return nil, err + } + req.ProjectID = v1ID + } + + resp, err := s.client.CreateService(ctx, req) + if err != nil { + return nil, err + } + + return s.mapServiceResponse(ctx, resp) +} + +// GetService retrieves a GroupsIO service by ID, mapping project_id (v1) -> project_uid (v2) in response. +func (s *GroupsioServiceService) GetService(ctx context.Context, serviceID string) (*models.GroupsioService, error) { + resp, err := s.client.GetService(ctx, serviceID) + if err != nil { + return nil, err + } + + return s.mapServiceResponse(ctx, resp) +} + +// UpdateService updates a GroupsIO service, mapping project_uid (v2) -> project_id (v1). +func (s *GroupsioServiceService) UpdateService(ctx context.Context, serviceID string, req *models.GroupsioServiceRequest) (*models.GroupsioService, error) { + if req.ProjectID != "" { + v1ID, err := s.idMapper.MapProjectV2ToV1(ctx, req.ProjectID) + if err != nil { + return nil, err + } + req.ProjectID = v1ID + } + + resp, err := s.client.UpdateService(ctx, serviceID, req) + if err != nil { + return nil, err + } + + return s.mapServiceResponse(ctx, resp) +} + +// DeleteService deletes a GroupsIO service. +func (s *GroupsioServiceService) DeleteService(ctx context.Context, serviceID string) error { + return s.client.DeleteService(ctx, serviceID) +} + +// GetProjects returns projects that have GroupsIO services. +func (s *GroupsioServiceService) GetProjects(ctx context.Context) (*models.GroupsioServiceProjectsResponse, error) { + return s.client.GetProjects(ctx) +} + +// FindParentService finds the parent service for a project, mapping project_uid (v2) -> project_id (v1). +func (s *GroupsioServiceService) FindParentService(ctx context.Context, projectUID string) (*models.GroupsioService, error) { + v1ID, err := s.idMapper.MapProjectV2ToV1(ctx, projectUID) + if err != nil { + return nil, err + } + + resp, err := s.client.FindParentService(ctx, v1ID) + if err != nil { + return nil, err + } + + return s.mapServiceResponse(ctx, resp) +} + +// mapServiceResponse maps project_id (v1) -> project_uid (v2) in a service response. +func (s *GroupsioServiceService) mapServiceResponse(ctx context.Context, svc *models.GroupsioService) (*models.GroupsioService, error) { + if svc == nil { + return nil, nil + } + if svc.ProjectID != "" { + v2UID, err := s.idMapper.MapProjectV1ToV2(ctx, svc.ProjectID) + if err != nil { + return nil, err + } + svc.ProjectID = v2UID + } + return svc, nil +} diff --git a/internal/service/itx/groupsio_service_test.go b/internal/service/itx/groupsio_service_test.go new file mode 100644 index 0000000..694671e --- /dev/null +++ b/internal/service/itx/groupsio_service_test.go @@ -0,0 +1,251 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package itx + +import ( + "context" + "errors" + "testing" + + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/models" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + projV2 = "proj-uuid-aaaaaaaa" + projV1 = "proj-sfid-00000001" +) + +var v2ToV1 = map[string]string{projV2: projV1} +var v1ToV2 = map[string]string{projV1: projV2} + +func TestGroupsioServiceService_ListServices_NoFilter(t *testing.T) { + var calledWithProjectID string + client := &mockServiceClient{ + listServices: func(_ context.Context, projectID string) (*models.GroupsioServiceListResponse, error) { + calledWithProjectID = projectID + return &models.GroupsioServiceListResponse{ + Items: []*models.GroupsioService{{ID: "svc-1", ProjectID: projV1}}, + Total: 1, + }, nil + }, + } + + svc := NewGroupsioServiceService(client, swappingMapper(v2ToV1, v1ToV2)) + resp, err := svc.ListServices(context.Background(), "") + + require.NoError(t, err) + assert.Empty(t, calledWithProjectID, "empty filter should not call mapper") + require.Len(t, resp.Items, 1) + assert.Equal(t, projV2, resp.Items[0].ProjectID, "v1 project_id in response should be mapped to v2 UUID") +} + +func TestGroupsioServiceService_ListServices_WithProjectFilter(t *testing.T) { + var receivedV1ID string + client := &mockServiceClient{ + listServices: func(_ context.Context, projectID string) (*models.GroupsioServiceListResponse, error) { + receivedV1ID = projectID + return &models.GroupsioServiceListResponse{ + Items: []*models.GroupsioService{{ID: "svc-1", ProjectID: projV1}}, + }, nil + }, + } + + svc := NewGroupsioServiceService(client, swappingMapper(v2ToV1, v1ToV2)) + resp, err := svc.ListServices(context.Background(), projV2) + + require.NoError(t, err) + assert.Equal(t, projV1, receivedV1ID, "v2 project UID should be mapped to v1 before calling client") + assert.Equal(t, projV2, resp.Items[0].ProjectID, "v1 project_id in response should be mapped back to v2") +} + +func TestGroupsioServiceService_ListServices_EmptyProjectIDInResponse(t *testing.T) { + client := &mockServiceClient{ + listServices: func(_ context.Context, _ string) (*models.GroupsioServiceListResponse, error) { + return &models.GroupsioServiceListResponse{ + Items: []*models.GroupsioService{{ID: "svc-no-project"}}, + }, nil + }, + } + + svc := NewGroupsioServiceService(client, passthroughMapper()) + resp, err := svc.ListServices(context.Background(), "") + require.NoError(t, err) + assert.Empty(t, resp.Items[0].ProjectID) +} + +func TestGroupsioServiceService_CreateService(t *testing.T) { + var receivedReq *models.GroupsioServiceRequest + client := &mockServiceClient{ + createService: func(_ context.Context, req *models.GroupsioServiceRequest) (*models.GroupsioService, error) { + receivedReq = req + return &models.GroupsioService{ID: "svc-new", ProjectID: req.ProjectID}, nil + }, + } + + svc := NewGroupsioServiceService(client, swappingMapper(v2ToV1, v1ToV2)) + resp, err := svc.CreateService(context.Background(), &models.GroupsioServiceRequest{ + ProjectID: projV2, + Type: "primary", + }) + + require.NoError(t, err) + assert.Equal(t, projV1, receivedReq.ProjectID, "request should use v1 SFID") + assert.Equal(t, projV2, resp.ProjectID, "response should use v2 UUID") +} + +func TestGroupsioServiceService_CreateService_EmptyProjectID(t *testing.T) { + client := &mockServiceClient{ + createService: func(_ context.Context, req *models.GroupsioServiceRequest) (*models.GroupsioService, error) { + assert.Empty(t, req.ProjectID) + return &models.GroupsioService{ID: "svc-new"}, nil + }, + } + + svc := NewGroupsioServiceService(client, passthroughMapper()) + resp, err := svc.CreateService(context.Background(), &models.GroupsioServiceRequest{}) + require.NoError(t, err) + assert.Equal(t, "svc-new", resp.ID) +} + +func TestGroupsioServiceService_GetService(t *testing.T) { + client := &mockServiceClient{ + getService: func(_ context.Context, serviceID string) (*models.GroupsioService, error) { + assert.Equal(t, "svc-42", serviceID) + return &models.GroupsioService{ID: serviceID, ProjectID: projV1}, nil + }, + } + + svc := NewGroupsioServiceService(client, swappingMapper(v2ToV1, v1ToV2)) + resp, err := svc.GetService(context.Background(), "svc-42") + + require.NoError(t, err) + assert.Equal(t, "svc-42", resp.ID) + assert.Equal(t, projV2, resp.ProjectID) +} + +func TestGroupsioServiceService_UpdateService(t *testing.T) { + var receivedReq *models.GroupsioServiceRequest + client := &mockServiceClient{ + updateService: func(_ context.Context, serviceID string, req *models.GroupsioServiceRequest) (*models.GroupsioService, error) { + assert.Equal(t, "svc-42", serviceID) + receivedReq = req + return &models.GroupsioService{ID: serviceID, ProjectID: req.ProjectID}, nil + }, + } + + svc := NewGroupsioServiceService(client, swappingMapper(v2ToV1, v1ToV2)) + resp, err := svc.UpdateService(context.Background(), "svc-42", &models.GroupsioServiceRequest{ + ProjectID: projV2, + Status: "active", + }) + + require.NoError(t, err) + assert.Equal(t, projV1, receivedReq.ProjectID) + assert.Equal(t, "active", receivedReq.Status) + assert.Equal(t, projV2, resp.ProjectID) +} + +func TestGroupsioServiceService_DeleteService(t *testing.T) { + called := false + client := &mockServiceClient{ + deleteService: func(_ context.Context, serviceID string) error { + assert.Equal(t, "svc-42", serviceID) + called = true + return nil + }, + } + + svc := NewGroupsioServiceService(client, passthroughMapper()) + err := svc.DeleteService(context.Background(), "svc-42") + + require.NoError(t, err) + assert.True(t, called) +} + +func TestGroupsioServiceService_GetProjects(t *testing.T) { + client := &mockServiceClient{ + getProjects: func(_ context.Context) (*models.GroupsioServiceProjectsResponse, error) { + return &models.GroupsioServiceProjectsResponse{Projects: []string{"proj-a", "proj-b"}}, nil + }, + } + + svc := NewGroupsioServiceService(client, passthroughMapper()) + resp, err := svc.GetProjects(context.Background()) + + require.NoError(t, err) + assert.Equal(t, []string{"proj-a", "proj-b"}, resp.Projects) +} + +func TestGroupsioServiceService_FindParentService(t *testing.T) { + var receivedProjectID string + client := &mockServiceClient{ + findParentService: func(_ context.Context, projectID string) (*models.GroupsioService, error) { + receivedProjectID = projectID + return &models.GroupsioService{ID: "parent-svc", ProjectID: projV1}, nil + }, + } + + svc := NewGroupsioServiceService(client, swappingMapper(v2ToV1, v1ToV2)) + resp, err := svc.FindParentService(context.Background(), projV2) + + require.NoError(t, err) + assert.Equal(t, projV1, receivedProjectID, "should call ITX with v1 SFID") + assert.Equal(t, projV2, resp.ProjectID, "response should have v2 UUID") +} + +func TestGroupsioServiceService_MapperError_PropagatedOnRequest(t *testing.T) { + mapErr := errors.New("mapping failed") + mapper := &mockIDMapper{ + mapProjectV2ToV1: func(_ context.Context, _ string) (string, error) { return "", mapErr }, + mapProjectV1ToV2: func(_ context.Context, id string) (string, error) { return id, nil }, + } + client := &mockServiceClient{ + createService: func(_ context.Context, _ *models.GroupsioServiceRequest) (*models.GroupsioService, error) { + t.Fatal("client should not be called when mapper fails") + return nil, nil + }, + } + + svc := NewGroupsioServiceService(client, mapper) + _, err := svc.CreateService(context.Background(), &models.GroupsioServiceRequest{ProjectID: projV2}) + + require.Error(t, err) + assert.True(t, errors.Is(err, mapErr)) +} + +func TestGroupsioServiceService_MapperError_PropagatedOnResponse(t *testing.T) { + mapErr := errors.New("reverse mapping failed") + mapper := &mockIDMapper{ + mapProjectV2ToV1: func(_ context.Context, id string) (string, error) { return id, nil }, + mapProjectV1ToV2: func(_ context.Context, _ string) (string, error) { return "", mapErr }, + } + client := &mockServiceClient{ + getService: func(_ context.Context, _ string) (*models.GroupsioService, error) { + return &models.GroupsioService{ID: "svc-1", ProjectID: projV1}, nil + }, + } + + svc := NewGroupsioServiceService(client, mapper) + _, err := svc.GetService(context.Background(), "svc-1") + + require.Error(t, err) + assert.True(t, errors.Is(err, mapErr)) +} + +func TestGroupsioServiceService_ClientError_Propagated(t *testing.T) { + clientErr := errors.New("client error") + client := &mockServiceClient{ + getService: func(_ context.Context, _ string) (*models.GroupsioService, error) { + return nil, clientErr + }, + } + + svc := NewGroupsioServiceService(client, passthroughMapper()) + _, err := svc.GetService(context.Background(), "svc-1") + + require.Error(t, err) + assert.True(t, errors.Is(err, clientErr)) +} diff --git a/internal/service/itx/groupsio_subgroup.go b/internal/service/itx/groupsio_subgroup.go new file mode 100644 index 0000000..a51f979 --- /dev/null +++ b/internal/service/itx/groupsio_subgroup.go @@ -0,0 +1,173 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package itx + +import ( + "context" + + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain" + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/models" +) + +// GroupsioSubgroupService handles ITX GroupsIO subgroup operations with ID mapping. +type GroupsioSubgroupService struct { + client domain.GroupsioSubgroupClient + idMapper domain.IDMapper +} + +// NewGroupsioSubgroupService creates a new GroupsIO subgroup handler. +func NewGroupsioSubgroupService(client domain.GroupsioSubgroupClient, idMapper domain.IDMapper) *GroupsioSubgroupService { + return &GroupsioSubgroupService{ + client: client, + idMapper: idMapper, + } +} + +// ListSubgroups lists subgroups, mapping v2 UIDs -> v1 SFIDs before calling ITX. +func (s *GroupsioSubgroupService) ListSubgroups(ctx context.Context, projectUID, committeeUID string) (*models.GroupsioSubgroupListResponse, error) { + v1ProjectID := "" + if projectUID != "" { + var err error + v1ProjectID, err = s.idMapper.MapProjectV2ToV1(ctx, projectUID) + if err != nil { + return nil, err + } + } + + v1CommitteeID := "" + if committeeUID != "" { + var err error + v1CommitteeID, err = s.idMapper.MapCommitteeV2ToV1(ctx, committeeUID) + if err != nil { + return nil, err + } + } + + resp, err := s.client.ListSubgroups(ctx, v1ProjectID, v1CommitteeID) + if err != nil { + return nil, err + } + + // Map v1 SFIDs -> v2 UIDs in responses + for _, sg := range resp.Items { + if mapErr := s.mapSubgroupResponseIDs(ctx, sg); mapErr != nil { + return nil, mapErr + } + } + + return resp, nil +} + +// CreateSubgroup creates a new subgroup, mapping v2 UIDs -> v1 SFIDs. +func (s *GroupsioSubgroupService) CreateSubgroup(ctx context.Context, req *models.GroupsioSubgroupRequest) (*models.GroupsioSubgroup, error) { + if req.ProjectID != "" { + v1ID, err := s.idMapper.MapProjectV2ToV1(ctx, req.ProjectID) + if err != nil { + return nil, err + } + req.ProjectID = v1ID + } + + if req.CommitteeID != "" { + v1ID, err := s.idMapper.MapCommitteeV2ToV1(ctx, req.CommitteeID) + if err != nil { + return nil, err + } + req.CommitteeID = v1ID + } + + resp, err := s.client.CreateSubgroup(ctx, req) + if err != nil { + return nil, err + } + + if err := s.mapSubgroupResponseIDs(ctx, resp); err != nil { + return nil, err + } + return resp, nil +} + +// GetSubgroup retrieves a subgroup by ID, mapping v1 SFIDs -> v2 UIDs in response. +func (s *GroupsioSubgroupService) GetSubgroup(ctx context.Context, subgroupID string) (*models.GroupsioSubgroup, error) { + resp, err := s.client.GetSubgroup(ctx, subgroupID) + if err != nil { + return nil, err + } + + if err := s.mapSubgroupResponseIDs(ctx, resp); err != nil { + return nil, err + } + return resp, nil +} + +// UpdateSubgroup updates a subgroup, mapping v2 UIDs -> v1 SFIDs. +func (s *GroupsioSubgroupService) UpdateSubgroup(ctx context.Context, subgroupID string, req *models.GroupsioSubgroupRequest) (*models.GroupsioSubgroup, error) { + if req.ProjectID != "" { + v1ID, err := s.idMapper.MapProjectV2ToV1(ctx, req.ProjectID) + if err != nil { + return nil, err + } + req.ProjectID = v1ID + } + + if req.CommitteeID != "" { + v1ID, err := s.idMapper.MapCommitteeV2ToV1(ctx, req.CommitteeID) + if err != nil { + return nil, err + } + req.CommitteeID = v1ID + } + + resp, err := s.client.UpdateSubgroup(ctx, subgroupID, req) + if err != nil { + return nil, err + } + + if err := s.mapSubgroupResponseIDs(ctx, resp); err != nil { + return nil, err + } + return resp, nil +} + +// DeleteSubgroup deletes a subgroup. +func (s *GroupsioSubgroupService) DeleteSubgroup(ctx context.Context, subgroupID string) error { + return s.client.DeleteSubgroup(ctx, subgroupID) +} + +// GetSubgroupCount returns the count of subgroups for a project. +func (s *GroupsioSubgroupService) GetSubgroupCount(ctx context.Context, projectUID string) (*models.GroupsioSubgroupCountResponse, error) { + v1ID, err := s.idMapper.MapProjectV2ToV1(ctx, projectUID) + if err != nil { + return nil, err + } + + return s.client.GetSubgroupCount(ctx, v1ID) +} + +// GetMemberCount returns the count of members in a subgroup. +func (s *GroupsioSubgroupService) GetMemberCount(ctx context.Context, subgroupID string) (*models.GroupsioMemberCountResponse, error) { + return s.client.GetMemberCount(ctx, subgroupID) +} + +// mapSubgroupResponseIDs maps v1 SFIDs -> v2 UIDs in a subgroup response. +func (s *GroupsioSubgroupService) mapSubgroupResponseIDs(ctx context.Context, sg *models.GroupsioSubgroup) error { + if sg == nil { + return nil + } + if sg.ProjectID != "" { + v2UID, err := s.idMapper.MapProjectV1ToV2(ctx, sg.ProjectID) + if err != nil { + return err + } + sg.ProjectID = v2UID + } + if sg.CommitteeID != "" { + v2UID, err := s.idMapper.MapCommitteeV1ToV2(ctx, sg.CommitteeID) + if err != nil { + return err + } + sg.CommitteeID = v2UID + } + return nil +} diff --git a/internal/service/itx/groupsio_subgroup_test.go b/internal/service/itx/groupsio_subgroup_test.go new file mode 100644 index 0000000..0d6fed0 --- /dev/null +++ b/internal/service/itx/groupsio_subgroup_test.go @@ -0,0 +1,226 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package itx + +import ( + "context" + "errors" + "testing" + + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/models" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + commV2 = "comm-uuid-bbbbbbbb" + commV1 = "comm-sfid-00000002" +) + +var subgroupV2ToV1 = map[string]string{projV2: projV1, commV2: commV1} +var subgroupV1ToV2 = map[string]string{projV1: projV2, commV1: commV2} + +func TestGroupsioSubgroupService_ListSubgroups_BothFilters(t *testing.T) { + var receivedProjectID, receivedCommitteeID string + client := &mockSubgroupClient{ + listSubgroups: func(_ context.Context, projectID, committeeID string) (*models.GroupsioSubgroupListResponse, error) { + receivedProjectID = projectID + receivedCommitteeID = committeeID + return &models.GroupsioSubgroupListResponse{ + Items: []*models.GroupsioSubgroup{ + {ID: "sg-1", ProjectID: projV1, CommitteeID: commV1}, + }, + Total: 1, + }, nil + }, + } + + svc := NewGroupsioSubgroupService(client, swappingMapper(subgroupV2ToV1, subgroupV1ToV2)) + resp, err := svc.ListSubgroups(context.Background(), projV2, commV2) + + require.NoError(t, err) + assert.Equal(t, projV1, receivedProjectID, "v2 project UID should map to v1 SFID") + assert.Equal(t, commV1, receivedCommitteeID, "v2 committee UID should map to v1 SFID") + require.Len(t, resp.Items, 1) + assert.Equal(t, projV2, resp.Items[0].ProjectID, "v1 project_id in response should map back to v2") + assert.Equal(t, commV2, resp.Items[0].CommitteeID, "v1 committee_id in response should map back to v2") +} + +func TestGroupsioSubgroupService_ListSubgroups_NoFilters(t *testing.T) { + client := &mockSubgroupClient{ + listSubgroups: func(_ context.Context, projectID, committeeID string) (*models.GroupsioSubgroupListResponse, error) { + assert.Empty(t, projectID) + assert.Empty(t, committeeID) + return &models.GroupsioSubgroupListResponse{}, nil + }, + } + + svc := NewGroupsioSubgroupService(client, passthroughMapper()) + _, err := svc.ListSubgroups(context.Background(), "", "") + require.NoError(t, err) +} + +func TestGroupsioSubgroupService_CreateSubgroup(t *testing.T) { + var receivedReq *models.GroupsioSubgroupRequest + client := &mockSubgroupClient{ + createSubgroup: func(_ context.Context, req *models.GroupsioSubgroupRequest) (*models.GroupsioSubgroup, error) { + receivedReq = req + return &models.GroupsioSubgroup{ + ID: "sg-new", + ProjectID: req.ProjectID, + CommitteeID: req.CommitteeID, + Name: req.Name, + }, nil + }, + } + + svc := NewGroupsioSubgroupService(client, swappingMapper(subgroupV2ToV1, subgroupV1ToV2)) + resp, err := svc.CreateSubgroup(context.Background(), &models.GroupsioSubgroupRequest{ + ProjectID: projV2, + CommitteeID: commV2, + Name: "dev-list", + }) + + require.NoError(t, err) + assert.Equal(t, projV1, receivedReq.ProjectID, "request should carry v1 project SFID") + assert.Equal(t, commV1, receivedReq.CommitteeID, "request should carry v1 committee SFID") + assert.Equal(t, projV2, resp.ProjectID, "response should have v2 project UUID") + assert.Equal(t, commV2, resp.CommitteeID, "response should have v2 committee UUID") + assert.Equal(t, "dev-list", resp.Name) +} + +func TestGroupsioSubgroupService_CreateSubgroup_EmptyIDs(t *testing.T) { + client := &mockSubgroupClient{ + createSubgroup: func(_ context.Context, req *models.GroupsioSubgroupRequest) (*models.GroupsioSubgroup, error) { + assert.Empty(t, req.ProjectID) + assert.Empty(t, req.CommitteeID) + return &models.GroupsioSubgroup{ID: "sg-new"}, nil + }, + } + + svc := NewGroupsioSubgroupService(client, passthroughMapper()) + resp, err := svc.CreateSubgroup(context.Background(), &models.GroupsioSubgroupRequest{}) + require.NoError(t, err) + assert.Equal(t, "sg-new", resp.ID) +} + +func TestGroupsioSubgroupService_GetSubgroup(t *testing.T) { + client := &mockSubgroupClient{ + getSubgroup: func(_ context.Context, subgroupID string) (*models.GroupsioSubgroup, error) { + assert.Equal(t, "sg-42", subgroupID) + return &models.GroupsioSubgroup{ + ID: "sg-42", + ProjectID: projV1, + CommitteeID: commV1, + }, nil + }, + } + + svc := NewGroupsioSubgroupService(client, swappingMapper(subgroupV2ToV1, subgroupV1ToV2)) + resp, err := svc.GetSubgroup(context.Background(), "sg-42") + + require.NoError(t, err) + assert.Equal(t, "sg-42", resp.ID) + assert.Equal(t, projV2, resp.ProjectID) + assert.Equal(t, commV2, resp.CommitteeID) +} + +func TestGroupsioSubgroupService_UpdateSubgroup(t *testing.T) { + var receivedReq *models.GroupsioSubgroupRequest + client := &mockSubgroupClient{ + updateSubgroup: func(_ context.Context, subgroupID string, req *models.GroupsioSubgroupRequest) (*models.GroupsioSubgroup, error) { + assert.Equal(t, "sg-42", subgroupID) + receivedReq = req + return &models.GroupsioSubgroup{ + ID: "sg-42", + ProjectID: req.ProjectID, + CommitteeID: req.CommitteeID, + }, nil + }, + } + + svc := NewGroupsioSubgroupService(client, swappingMapper(subgroupV2ToV1, subgroupV1ToV2)) + resp, err := svc.UpdateSubgroup(context.Background(), "sg-42", &models.GroupsioSubgroupRequest{ + ProjectID: projV2, + CommitteeID: commV2, + }) + + require.NoError(t, err) + assert.Equal(t, projV1, receivedReq.ProjectID) + assert.Equal(t, commV1, receivedReq.CommitteeID) + assert.Equal(t, projV2, resp.ProjectID) + assert.Equal(t, commV2, resp.CommitteeID) +} + +func TestGroupsioSubgroupService_DeleteSubgroup(t *testing.T) { + called := false + client := &mockSubgroupClient{ + deleteSubgroup: func(_ context.Context, subgroupID string) error { + assert.Equal(t, "sg-42", subgroupID) + called = true + return nil + }, + } + + svc := NewGroupsioSubgroupService(client, passthroughMapper()) + err := svc.DeleteSubgroup(context.Background(), "sg-42") + require.NoError(t, err) + assert.True(t, called) +} + +func TestGroupsioSubgroupService_GetSubgroupCount(t *testing.T) { + var receivedProjectID string + client := &mockSubgroupClient{ + getSubgroupCount: func(_ context.Context, projectID string) (*models.GroupsioSubgroupCountResponse, error) { + receivedProjectID = projectID + return &models.GroupsioSubgroupCountResponse{Count: 7}, nil + }, + } + + svc := NewGroupsioSubgroupService(client, swappingMapper(subgroupV2ToV1, subgroupV1ToV2)) + resp, err := svc.GetSubgroupCount(context.Background(), projV2) + + require.NoError(t, err) + assert.Equal(t, projV1, receivedProjectID, "v2 project UID should be mapped to v1 before calling client") + assert.Equal(t, 7, resp.Count) +} + +func TestGroupsioSubgroupService_GetMemberCount(t *testing.T) { + client := &mockSubgroupClient{ + getMemberCount: func(_ context.Context, subgroupID string) (*models.GroupsioMemberCountResponse, error) { + assert.Equal(t, "sg-42", subgroupID) + return &models.GroupsioMemberCountResponse{Count: 15}, nil + }, + } + + svc := NewGroupsioSubgroupService(client, passthroughMapper()) + resp, err := svc.GetMemberCount(context.Background(), "sg-42") + + require.NoError(t, err) + assert.Equal(t, 15, resp.Count) +} + +func TestGroupsioSubgroupService_CommitteeMapperError(t *testing.T) { + mapErr := errors.New("committee mapping failed") + mapper := &mockIDMapper{ + mapProjectV2ToV1: func(_ context.Context, id string) (string, error) { return id, nil }, + mapProjectV1ToV2: func(_ context.Context, id string) (string, error) { return id, nil }, + mapCommitteeV2ToV1: func(_ context.Context, _ string) (string, error) { return "", mapErr }, + mapCommitteeV1ToV2: func(_ context.Context, id string) (string, error) { return id, nil }, + } + client := &mockSubgroupClient{ + createSubgroup: func(_ context.Context, _ *models.GroupsioSubgroupRequest) (*models.GroupsioSubgroup, error) { + t.Fatal("client should not be called when mapper fails") + return nil, nil + }, + } + + svc := NewGroupsioSubgroupService(client, mapper) + _, err := svc.CreateSubgroup(context.Background(), &models.GroupsioSubgroupRequest{ + CommitteeID: commV2, + }) + + require.Error(t, err) + assert.True(t, errors.Is(err, mapErr)) +} diff --git a/internal/service/itx/mocks_test.go b/internal/service/itx/mocks_test.go new file mode 100644 index 0000000..211a206 --- /dev/null +++ b/internal/service/itx/mocks_test.go @@ -0,0 +1,159 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package itx + +import ( + "context" + + "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/models" +) + +// mockIDMapper is a controllable IDMapper for tests. +type mockIDMapper struct { + mapProjectV2ToV1 func(ctx context.Context, v2UID string) (string, error) + mapProjectV1ToV2 func(ctx context.Context, v1SFID string) (string, error) + mapCommitteeV2ToV1 func(ctx context.Context, v2UID string) (string, error) + mapCommitteeV1ToV2 func(ctx context.Context, v1SFID string) (string, error) +} + +func (m *mockIDMapper) MapProjectV2ToV1(ctx context.Context, v2UID string) (string, error) { + return m.mapProjectV2ToV1(ctx, v2UID) +} +func (m *mockIDMapper) MapProjectV1ToV2(ctx context.Context, v1SFID string) (string, error) { + return m.mapProjectV1ToV2(ctx, v1SFID) +} +func (m *mockIDMapper) MapCommitteeV2ToV1(ctx context.Context, v2UID string) (string, error) { + return m.mapCommitteeV2ToV1(ctx, v2UID) +} +func (m *mockIDMapper) MapCommitteeV1ToV2(ctx context.Context, v1SFID string) (string, error) { + return m.mapCommitteeV1ToV2(ctx, v1SFID) +} + +// passthroughMapper returns input IDs unchanged (like NoOpMapper). +func passthroughMapper() *mockIDMapper { + identity := func(_ context.Context, id string) (string, error) { return id, nil } + return &mockIDMapper{ + mapProjectV2ToV1: identity, + mapProjectV1ToV2: identity, + mapCommitteeV2ToV1: identity, + mapCommitteeV1ToV2: identity, + } +} + +// swappingMapper maps v2→v1 and v1→v2 using provided lookup tables. +func swappingMapper(v2ToV1, v1ToV2 map[string]string) *mockIDMapper { + lookup := func(table map[string]string) func(_ context.Context, id string) (string, error) { + return func(_ context.Context, id string) (string, error) { + if v, ok := table[id]; ok { + return v, nil + } + return id, nil + } + } + return &mockIDMapper{ + mapProjectV2ToV1: lookup(v2ToV1), + mapProjectV1ToV2: lookup(v1ToV2), + mapCommitteeV2ToV1: lookup(v2ToV1), + mapCommitteeV1ToV2: lookup(v1ToV2), + } +} + +// mockServiceClient implements domain.GroupsioServiceClient for tests. +type mockServiceClient struct { + listServices func(ctx context.Context, projectID string) (*models.GroupsioServiceListResponse, error) + createService func(ctx context.Context, req *models.GroupsioServiceRequest) (*models.GroupsioService, error) + getService func(ctx context.Context, serviceID string) (*models.GroupsioService, error) + updateService func(ctx context.Context, serviceID string, req *models.GroupsioServiceRequest) (*models.GroupsioService, error) + deleteService func(ctx context.Context, serviceID string) error + getProjects func(ctx context.Context) (*models.GroupsioServiceProjectsResponse, error) + findParentService func(ctx context.Context, projectID string) (*models.GroupsioService, error) +} + +func (m *mockServiceClient) ListServices(ctx context.Context, projectID string) (*models.GroupsioServiceListResponse, error) { + return m.listServices(ctx, projectID) +} +func (m *mockServiceClient) CreateService(ctx context.Context, req *models.GroupsioServiceRequest) (*models.GroupsioService, error) { + return m.createService(ctx, req) +} +func (m *mockServiceClient) GetService(ctx context.Context, serviceID string) (*models.GroupsioService, error) { + return m.getService(ctx, serviceID) +} +func (m *mockServiceClient) UpdateService(ctx context.Context, serviceID string, req *models.GroupsioServiceRequest) (*models.GroupsioService, error) { + return m.updateService(ctx, serviceID, req) +} +func (m *mockServiceClient) DeleteService(ctx context.Context, serviceID string) error { + return m.deleteService(ctx, serviceID) +} +func (m *mockServiceClient) GetProjects(ctx context.Context) (*models.GroupsioServiceProjectsResponse, error) { + return m.getProjects(ctx) +} +func (m *mockServiceClient) FindParentService(ctx context.Context, projectID string) (*models.GroupsioService, error) { + return m.findParentService(ctx, projectID) +} + +// mockSubgroupClient implements domain.GroupsioSubgroupClient for tests. +type mockSubgroupClient struct { + listSubgroups func(ctx context.Context, projectID, committeeID string) (*models.GroupsioSubgroupListResponse, error) + createSubgroup func(ctx context.Context, req *models.GroupsioSubgroupRequest) (*models.GroupsioSubgroup, error) + getSubgroup func(ctx context.Context, subgroupID string) (*models.GroupsioSubgroup, error) + updateSubgroup func(ctx context.Context, subgroupID string, req *models.GroupsioSubgroupRequest) (*models.GroupsioSubgroup, error) + deleteSubgroup func(ctx context.Context, subgroupID string) error + getSubgroupCount func(ctx context.Context, projectID string) (*models.GroupsioSubgroupCountResponse, error) + getMemberCount func(ctx context.Context, subgroupID string) (*models.GroupsioMemberCountResponse, error) +} + +func (m *mockSubgroupClient) ListSubgroups(ctx context.Context, projectID, committeeID string) (*models.GroupsioSubgroupListResponse, error) { + return m.listSubgroups(ctx, projectID, committeeID) +} +func (m *mockSubgroupClient) CreateSubgroup(ctx context.Context, req *models.GroupsioSubgroupRequest) (*models.GroupsioSubgroup, error) { + return m.createSubgroup(ctx, req) +} +func (m *mockSubgroupClient) GetSubgroup(ctx context.Context, subgroupID string) (*models.GroupsioSubgroup, error) { + return m.getSubgroup(ctx, subgroupID) +} +func (m *mockSubgroupClient) UpdateSubgroup(ctx context.Context, subgroupID string, req *models.GroupsioSubgroupRequest) (*models.GroupsioSubgroup, error) { + return m.updateSubgroup(ctx, subgroupID, req) +} +func (m *mockSubgroupClient) DeleteSubgroup(ctx context.Context, subgroupID string) error { + return m.deleteSubgroup(ctx, subgroupID) +} +func (m *mockSubgroupClient) GetSubgroupCount(ctx context.Context, projectID string) (*models.GroupsioSubgroupCountResponse, error) { + return m.getSubgroupCount(ctx, projectID) +} +func (m *mockSubgroupClient) GetMemberCount(ctx context.Context, subgroupID string) (*models.GroupsioMemberCountResponse, error) { + return m.getMemberCount(ctx, subgroupID) +} + +// mockMemberClient implements domain.GroupsioMemberClient for tests. +type mockMemberClient struct { + listMembers func(ctx context.Context, subgroupID string) (*models.GroupsioMemberListResponse, error) + addMember func(ctx context.Context, subgroupID string, req *models.GroupsioMemberRequest) (*models.GroupsioMember, error) + getMember func(ctx context.Context, subgroupID, memberID string) (*models.GroupsioMember, error) + updateMember func(ctx context.Context, subgroupID, memberID string, req *models.GroupsioMemberRequest) (*models.GroupsioMember, error) + deleteMember func(ctx context.Context, subgroupID, memberID string) error + inviteMembers func(ctx context.Context, subgroupID string, req *models.GroupsioInviteMembersRequest) error + checkSubscriber func(ctx context.Context, req *models.GroupsioCheckSubscriberRequest) (*models.GroupsioCheckSubscriberResponse, error) +} + +func (m *mockMemberClient) ListMembers(ctx context.Context, subgroupID string) (*models.GroupsioMemberListResponse, error) { + return m.listMembers(ctx, subgroupID) +} +func (m *mockMemberClient) AddMember(ctx context.Context, subgroupID string, req *models.GroupsioMemberRequest) (*models.GroupsioMember, error) { + return m.addMember(ctx, subgroupID, req) +} +func (m *mockMemberClient) GetMember(ctx context.Context, subgroupID, memberID string) (*models.GroupsioMember, error) { + return m.getMember(ctx, subgroupID, memberID) +} +func (m *mockMemberClient) UpdateMember(ctx context.Context, subgroupID, memberID string, req *models.GroupsioMemberRequest) (*models.GroupsioMember, error) { + return m.updateMember(ctx, subgroupID, memberID, req) +} +func (m *mockMemberClient) DeleteMember(ctx context.Context, subgroupID, memberID string) error { + return m.deleteMember(ctx, subgroupID, memberID) +} +func (m *mockMemberClient) InviteMembers(ctx context.Context, subgroupID string, req *models.GroupsioInviteMembersRequest) error { + return m.inviteMembers(ctx, subgroupID, req) +} +func (m *mockMemberClient) CheckSubscriber(ctx context.Context, req *models.GroupsioCheckSubscriberRequest) (*models.GroupsioCheckSubscriberResponse, error) { + return m.checkSubscriber(ctx, req) +} diff --git a/internal/service/mailing_list_sync_service.go b/internal/service/mailing_list_sync_service.go deleted file mode 100644 index 2cb30fd..0000000 --- a/internal/service/mailing_list_sync_service.go +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "encoding/json" - "fmt" - "log/slog" - "slices" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/nats-io/nats.go" -) - -// MailingListSyncService handles mailing list creation/update events and syncs committee members -// Pattern: Delegates committee member operations to CommitteeSyncService for reusability -type MailingListSyncService struct { - committeeSyncService *CommitteeSyncService -} - -// NewMailingListSyncService creates a new mailing list sync service -func NewMailingListSyncService( - committeeSyncService *CommitteeSyncService, -) *MailingListSyncService { - return &MailingListSyncService{ - committeeSyncService: committeeSyncService, - } -} - -// HandleMessage routes NATS messages to appropriate handlers based on subject -func (s *MailingListSyncService) HandleMessage(ctx context.Context, msg *nats.Msg) error { - subject := msg.Subject - - slog.DebugContext(ctx, "received mailing list event", "subject", subject) - - var err error - switch subject { - case constants.MailingListCreatedSubject: - err = s.handleCreated(ctx, msg) - case constants.MailingListUpdatedSubject: - err = s.handleUpdated(ctx, msg) - default: - slog.WarnContext(ctx, "unknown mailing list event subject", "subject", subject) - return fmt.Errorf("unknown mailing list event subject: %s", subject) - } - - if err != nil { - slog.ErrorContext(ctx, "error processing mailing list event", - "error", err, - "subject", subject) - return err - } - - return nil -} - -// handleCreated processes mailing list created events and syncs all committee members -func (s *MailingListSyncService) handleCreated(ctx context.Context, msg *nats.Msg) error { - var event model.MailingListCreatedEvent - if err := json.Unmarshal(msg.Data, &event); err != nil { - slog.ErrorContext(ctx, "failed to unmarshal mailing list created event", "error", err) - return fmt.Errorf("failed to unmarshal event: %w", err) - } - - if event.MailingList == nil { - slog.ErrorContext(ctx, "mailing list is nil in created event") - return fmt.Errorf("mailing list is nil in created event") - } - - mailingList := event.MailingList - - slog.InfoContext(ctx, "processing mailing list created event", - "mailing_list_uid", mailingList.UID, - "group_name", mailingList.GroupName, - "committee_count", len(mailingList.Committees)) - - // If no committees, nothing to sync - if len(mailingList.Committees) == 0 { - slog.DebugContext(ctx, "mailing list has no committees, skipping member sync", - "mailing_list_uid", mailingList.UID) - return nil - } - - // Sync members for each committee - delegate to CommitteeSyncService - for _, committee := range mailingList.Committees { - if err := s.committeeSyncService.SyncCommitteeMembersToMailingList(ctx, mailingList, committee); err != nil { - slog.ErrorContext(ctx, "failed to sync committee members", - "error", err, - "mailing_list_uid", mailingList.UID, - "committee_uid", committee.UID) - // Continue with other committees even if one fails - continue - } - } - - slog.InfoContext(ctx, "mailing list created event processed successfully", - "mailing_list_uid", mailingList.UID) - - return nil -} - -// handleUpdated processes mailing list updated events and syncs committee member changes -func (s *MailingListSyncService) handleUpdated(ctx context.Context, msg *nats.Msg) error { - var event model.MailingListUpdatedEvent - if err := json.Unmarshal(msg.Data, &event); err != nil { - slog.ErrorContext(ctx, "failed to unmarshal mailing list updated event", "error", err) - return fmt.Errorf("failed to unmarshal event: %w", err) - } - - if event.OldMailingList == nil || event.NewMailingList == nil { - slog.ErrorContext(ctx, "old or new mailing list is nil in updated event") - return fmt.Errorf("old or new mailing list is nil in updated event") - } - - oldML := event.OldMailingList - newML := event.NewMailingList - - slog.InfoContext(ctx, "processing mailing list updated event", - "mailing_list_uid", newML.UID, - "group_name", newML.GroupName, - "old_committee_count", len(oldML.Committees), - "new_committee_count", len(newML.Committees)) - - // Detect committee changes - addedCommittees, removedCommittees, modifiedCommittees := detectCommitteeChanges(oldML.Committees, newML.Committees) - - slog.DebugContext(ctx, "detected committee changes", - "mailing_list_uid", newML.UID, - "added", len(addedCommittees), - "removed", len(removedCommittees), - "modified", len(modifiedCommittees)) - - // Handle removed committees - remove their members - for _, committee := range removedCommittees { - if err := s.committeeSyncService.RemoveCommitteeMembersFromMailingList(ctx, newML, committee); err != nil { - slog.ErrorContext(ctx, "failed to remove committee members", - "error", err, - "committee_uid", committee.UID) - continue - } - } - - // Handle added committees - add their members - for _, committee := range addedCommittees { - if err := s.committeeSyncService.SyncCommitteeMembersToMailingList(ctx, newML, committee); err != nil { - slog.ErrorContext(ctx, "failed to sync new committee members", - "error", err, - "committee_uid", committee.UID) - continue - } - } - - // Handle modified committees - resync members (filters may have changed) - for _, change := range modifiedCommittees { - if err := s.committeeSyncService.ResyncCommitteeMembersForMailingList(ctx, newML, change.old, change.new); err != nil { - slog.ErrorContext(ctx, "failed to resync modified committee members", - "error", err, - "committee_uid", change.new.UID) - continue - } - } - - slog.InfoContext(ctx, "mailing list updated event processed successfully", - "mailing_list_uid", newML.UID) - - return nil -} - -// committeeChange represents a change in committee configuration -type committeeChange struct { - old model.Committee - new model.Committee -} - -// detectCommitteeChanges compares old and new committee arrays and returns added, removed, and modified committees -func detectCommitteeChanges(oldCommittees, newCommittees []model.Committee) (added, removed []model.Committee, modified []committeeChange) { - // Build maps for easy lookup - oldMap := make(map[string]model.Committee) - for _, c := range oldCommittees { - oldMap[c.UID] = c - } - - newMap := make(map[string]model.Committee) - for _, c := range newCommittees { - newMap[c.UID] = c - } - - // Find added and modified committees - for uid, newCommittee := range newMap { - if oldCommittee, exists := oldMap[uid]; exists { - // Committee exists in both - check if filters changed - if !slices.Equal(oldCommittee.AllowedVotingStatuses, newCommittee.AllowedVotingStatuses) { - modified = append(modified, committeeChange{old: oldCommittee, new: newCommittee}) - } - } else { - // Committee only in new - added - added = append(added, newCommittee) - } - } - - // Find removed committees - for uid, oldCommittee := range oldMap { - if _, exists := newMap[uid]; !exists { - removed = append(removed, oldCommittee) - } - } - - return added, removed, modified -} diff --git a/internal/service/mailing_list_sync_service_test.go b/internal/service/mailing_list_sync_service_test.go deleted file mode 100644 index 08f05a5..0000000 --- a/internal/service/mailing_list_sync_service_test.go +++ /dev/null @@ -1,639 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package service - -import ( - "context" - "encoding/json" - "testing" - - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/domain/model" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/internal/infrastructure/mock" - "github.com/linuxfoundation/lfx-v2-mailing-list-service/pkg/constants" - "github.com/nats-io/nats.go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestMailingListSyncService_HandleMessage(t *testing.T) { - ctx := context.Background() - - // Setup mock repository - mockRepo := mock.NewMockRepository() - mailingListReader := mock.NewMockGrpsIOReader(mockRepo) - memberWriter := mock.NewMockGrpsIOMemberWriter(mockRepo) - memberReader := mock.NewMockGrpsIOMemberReader(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - // Create services - committeeSyncService := NewCommitteeSyncService(mailingListReader, memberWriter, memberReader, entityReader) - service := NewMailingListSyncService(committeeSyncService) - require.NotNil(t, service) - - t.Run("handles created event", func(t *testing.T) { - mailingList := &model.GrpsIOMailingList{ - UID: "ml-created-1", - GroupName: "test-created", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-1", - Name: "Test Committee", - AllowedVotingStatuses: []string{"Voting Rep"}, - }, - }, - Public: true, - } - - event := model.MailingListCreatedEvent{ - MailingList: mailingList, - } - - data, err := json.Marshal(event) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.MailingListCreatedSubject, - Data: data, - } - - err = service.HandleMessage(ctx, msg) - assert.NoError(t, err) - }) - - t.Run("handles created event with no committees", func(t *testing.T) { - mailingList := &model.GrpsIOMailingList{ - UID: "ml-created-2", - GroupName: "test-no-committees", - ServiceUID: "service-1", - Committees: []model.Committee{}, - Public: true, - } - - event := model.MailingListCreatedEvent{ - MailingList: mailingList, - } - - data, err := json.Marshal(event) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.MailingListCreatedSubject, - Data: data, - } - - err = service.HandleMessage(ctx, msg) - assert.NoError(t, err) - }) - - t.Run("handles updated event", func(t *testing.T) { - oldMailingList := &model.GrpsIOMailingList{ - UID: "ml-updated-1", - GroupName: "test-updated", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-1", - Name: "Committee 1", - AllowedVotingStatuses: []string{"Voting Rep"}, - }, - }, - Public: true, - } - - newMailingList := &model.GrpsIOMailingList{ - UID: "ml-updated-1", - GroupName: "test-updated", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-1", - Name: "Committee 1", - AllowedVotingStatuses: []string{"Voting Rep", "Observer"}, // Modified filters - }, - { - UID: "committee-2", - Name: "Committee 2", - AllowedVotingStatuses: []string{"Voting Rep"}, // Added committee - }, - }, - Public: true, - } - - event := model.MailingListUpdatedEvent{ - OldMailingList: oldMailingList, - NewMailingList: newMailingList, - } - - data, err := json.Marshal(event) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.MailingListUpdatedSubject, - Data: data, - } - - err = service.HandleMessage(ctx, msg) - assert.NoError(t, err) - }) - - t.Run("handles unknown subject", func(t *testing.T) { - msg := &nats.Msg{ - Subject: "unknown.subject", - Data: []byte("{}"), - } - - err := service.HandleMessage(ctx, msg) - assert.Error(t, err) - assert.Contains(t, err.Error(), "unknown mailing list event subject") - }) - - t.Run("handles invalid JSON for created event", func(t *testing.T) { - msg := &nats.Msg{ - Subject: constants.MailingListCreatedSubject, - Data: []byte("invalid json"), - } - - err := service.HandleMessage(ctx, msg) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to unmarshal") - }) - - t.Run("handles invalid JSON for updated event", func(t *testing.T) { - msg := &nats.Msg{ - Subject: constants.MailingListUpdatedSubject, - Data: []byte("invalid json"), - } - - err := service.HandleMessage(ctx, msg) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to unmarshal") - }) - - t.Run("handles nil mailing list in created event", func(t *testing.T) { - event := model.MailingListCreatedEvent{ - MailingList: nil, - } - - data, err := json.Marshal(event) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.MailingListCreatedSubject, - Data: data, - } - - err = service.HandleMessage(ctx, msg) - assert.Error(t, err) - assert.Contains(t, err.Error(), "mailing list is nil") - }) - - t.Run("handles nil mailing lists in updated event", func(t *testing.T) { - event := model.MailingListUpdatedEvent{ - OldMailingList: nil, - NewMailingList: nil, - } - - data, err := json.Marshal(event) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.MailingListUpdatedSubject, - Data: data, - } - - err = service.HandleMessage(ctx, msg) - assert.Error(t, err) - assert.Contains(t, err.Error(), "old or new mailing list is nil") - }) -} - -func TestDetectCommitteeChanges(t *testing.T) { - t.Run("detects added committees", func(t *testing.T) { - oldCommittees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep"}}, - } - - newCommittees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep"}}, - {UID: "committee-2", Name: "Committee 2", AllowedVotingStatuses: []string{"Observer"}}, - {UID: "committee-3", Name: "Committee 3", AllowedVotingStatuses: []string{"Voting Rep"}}, - } - - added, removed, modified := detectCommitteeChanges(oldCommittees, newCommittees) - - assert.Len(t, added, 2) - assert.Len(t, removed, 0) - assert.Len(t, modified, 0) - - // Verify added committees - addedUIDs := make([]string, len(added)) - for i, c := range added { - addedUIDs[i] = c.UID - } - assert.Contains(t, addedUIDs, "committee-2") - assert.Contains(t, addedUIDs, "committee-3") - }) - - t.Run("detects removed committees", func(t *testing.T) { - oldCommittees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep"}}, - {UID: "committee-2", Name: "Committee 2", AllowedVotingStatuses: []string{"Observer"}}, - {UID: "committee-3", Name: "Committee 3", AllowedVotingStatuses: []string{"Voting Rep"}}, - } - - newCommittees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep"}}, - } - - added, removed, modified := detectCommitteeChanges(oldCommittees, newCommittees) - - assert.Len(t, added, 0) - assert.Len(t, removed, 2) - assert.Len(t, modified, 0) - - // Verify removed committees - removedUIDs := make([]string, len(removed)) - for i, c := range removed { - removedUIDs[i] = c.UID - } - assert.Contains(t, removedUIDs, "committee-2") - assert.Contains(t, removedUIDs, "committee-3") - }) - - t.Run("detects modified committees - filter changes", func(t *testing.T) { - oldCommittees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep"}}, - {UID: "committee-2", Name: "Committee 2", AllowedVotingStatuses: []string{"Observer"}}, - } - - newCommittees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep", "Observer"}}, // Modified - {UID: "committee-2", Name: "Committee 2", AllowedVotingStatuses: []string{"Observer"}}, // Unchanged - } - - added, removed, modified := detectCommitteeChanges(oldCommittees, newCommittees) - - assert.Len(t, added, 0) - assert.Len(t, removed, 0) - assert.Len(t, modified, 1) - - assert.Equal(t, "committee-1", modified[0].old.UID) - assert.Equal(t, "committee-1", modified[0].new.UID) - assert.Equal(t, []string{"Voting Rep"}, modified[0].old.AllowedVotingStatuses) - assert.Equal(t, []string{"Voting Rep", "Observer"}, modified[0].new.AllowedVotingStatuses) - }) - - t.Run("detects multiple changes", func(t *testing.T) { - oldCommittees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep"}}, - {UID: "committee-2", Name: "Committee 2", AllowedVotingStatuses: []string{"Observer"}}, - {UID: "committee-3", Name: "Committee 3", AllowedVotingStatuses: []string{"Voting Rep"}}, - } - - newCommittees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep", "Observer"}}, // Modified - {UID: "committee-4", Name: "Committee 4", AllowedVotingStatuses: []string{"Voting Rep"}}, // Added - // committee-2 and committee-3 removed - } - - added, removed, modified := detectCommitteeChanges(oldCommittees, newCommittees) - - assert.Len(t, added, 1) - assert.Len(t, removed, 2) - assert.Len(t, modified, 1) - - assert.Equal(t, "committee-4", added[0].UID) - assert.Equal(t, "committee-1", modified[0].new.UID) - - removedUIDs := make([]string, len(removed)) - for i, c := range removed { - removedUIDs[i] = c.UID - } - assert.Contains(t, removedUIDs, "committee-2") - assert.Contains(t, removedUIDs, "committee-3") - }) - - t.Run("empty old committees - all added", func(t *testing.T) { - oldCommittees := []model.Committee{} - - newCommittees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep"}}, - {UID: "committee-2", Name: "Committee 2", AllowedVotingStatuses: []string{"Observer"}}, - } - - added, removed, modified := detectCommitteeChanges(oldCommittees, newCommittees) - - assert.Len(t, added, 2) - assert.Len(t, removed, 0) - assert.Len(t, modified, 0) - }) - - t.Run("empty new committees - all removed", func(t *testing.T) { - oldCommittees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep"}}, - {UID: "committee-2", Name: "Committee 2", AllowedVotingStatuses: []string{"Observer"}}, - } - - newCommittees := []model.Committee{} - - added, removed, modified := detectCommitteeChanges(oldCommittees, newCommittees) - - assert.Len(t, added, 0) - assert.Len(t, removed, 2) - assert.Len(t, modified, 0) - }) - - t.Run("no changes", func(t *testing.T) { - committees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep"}}, - {UID: "committee-2", Name: "Committee 2", AllowedVotingStatuses: []string{"Observer"}}, - } - - added, removed, modified := detectCommitteeChanges(committees, committees) - - assert.Len(t, added, 0) - assert.Len(t, removed, 0) - assert.Len(t, modified, 0) - }) - - t.Run("order changes only - no functional change", func(t *testing.T) { - oldCommittees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep"}}, - {UID: "committee-2", Name: "Committee 2", AllowedVotingStatuses: []string{"Observer"}}, - } - - newCommittees := []model.Committee{ - {UID: "committee-2", Name: "Committee 2", AllowedVotingStatuses: []string{"Observer"}}, - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep"}}, - } - - added, removed, modified := detectCommitteeChanges(oldCommittees, newCommittees) - - assert.Len(t, added, 0) - assert.Len(t, removed, 0) - assert.Len(t, modified, 0) - }) - - t.Run("filter order changes are detected as modification", func(t *testing.T) { - oldCommittees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep", "Observer"}}, - } - - newCommittees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Observer", "Voting Rep"}}, - } - - added, removed, modified := detectCommitteeChanges(oldCommittees, newCommittees) - - assert.Len(t, added, 0) - assert.Len(t, removed, 0) - // slices.Equal is order-sensitive, so this should detect a change - assert.Len(t, modified, 1) - }) - - t.Run("nil committees are treated as empty", func(t *testing.T) { - var oldCommittees []model.Committee = nil - - newCommittees := []model.Committee{ - {UID: "committee-1", Name: "Committee 1", AllowedVotingStatuses: []string{"Voting Rep"}}, - } - - added, removed, modified := detectCommitteeChanges(oldCommittees, newCommittees) - - assert.Len(t, added, 1) - assert.Len(t, removed, 0) - assert.Len(t, modified, 0) - }) -} - -// TestMailingListSyncService_Integration tests the full flow with mailing list events -// Note: These tests verify that event handling completes without errors. -// Full member synchronization is tested in TestCommitteeSyncService_IntegrationWithMailingLists -func TestMailingListSyncService_Integration(t *testing.T) { - ctx := context.Background() - - t.Run("created event with committees completes without error", func(t *testing.T) { - mockRepo := mock.NewMockRepository() - mockRepo.ClearAll() - - mailingList := &model.GrpsIOMailingList{ - UID: "ml-integration-1", - GroupName: "test-integration", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-1", - Name: "Test Committee", - AllowedVotingStatuses: []string{"Voting Rep"}, - }, - }, - Public: true, - } - - mailingListReader := mock.NewMockGrpsIOReader(mockRepo) - memberWriter := mock.NewMockGrpsIOMemberWriter(mockRepo) - memberReader := mock.NewMockGrpsIOMemberReader(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - committeeSyncService := NewCommitteeSyncService(mailingListReader, memberWriter, memberReader, entityReader) - service := NewMailingListSyncService(committeeSyncService) - - event := model.MailingListCreatedEvent{ - MailingList: mailingList, - } - - data, err := json.Marshal(event) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.MailingListCreatedSubject, - Data: data, - } - - err = service.HandleMessage(ctx, msg) - assert.NoError(t, err) - }) - - t.Run("updated event handles committee additions without error", func(t *testing.T) { - mockRepo := mock.NewMockRepository() - mockRepo.ClearAll() - - oldMailingList := &model.GrpsIOMailingList{ - UID: "ml-integration-2", - GroupName: "test-update", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-1", - Name: "Committee 1", - AllowedVotingStatuses: []string{"Voting Rep"}, - }, - }, - Public: true, - } - - newMailingList := &model.GrpsIOMailingList{ - UID: "ml-integration-2", - GroupName: "test-update", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-1", - Name: "Committee 1", - AllowedVotingStatuses: []string{"Voting Rep"}, - }, - { - UID: "committee-2", - Name: "Committee 2", - AllowedVotingStatuses: []string{"Voting Rep"}, - }, - }, - Public: true, - } - - mailingListReader := mock.NewMockGrpsIOReader(mockRepo) - memberWriter := mock.NewMockGrpsIOMemberWriter(mockRepo) - memberReader := mock.NewMockGrpsIOMemberReader(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - committeeSyncService := NewCommitteeSyncService(mailingListReader, memberWriter, memberReader, entityReader) - service := NewMailingListSyncService(committeeSyncService) - - event := model.MailingListUpdatedEvent{ - OldMailingList: oldMailingList, - NewMailingList: newMailingList, - } - - data, err := json.Marshal(event) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.MailingListUpdatedSubject, - Data: data, - } - - err = service.HandleMessage(ctx, msg) - assert.NoError(t, err) - }) - - t.Run("updated event handles committee removals without error", func(t *testing.T) { - mockRepo := mock.NewMockRepository() - mockRepo.ClearAll() - - oldMailingList := &model.GrpsIOMailingList{ - UID: "ml-integration-3", - GroupName: "test-removal", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-1", - Name: "Committee 1", - AllowedVotingStatuses: []string{"Voting Rep"}, - }, - { - UID: "committee-2", - Name: "Committee 2", - AllowedVotingStatuses: []string{"Voting Rep"}, - }, - }, - Public: false, // Private list - } - - newMailingList := &model.GrpsIOMailingList{ - UID: "ml-integration-3", - GroupName: "test-removal", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-1", - Name: "Committee 1", - AllowedVotingStatuses: []string{"Voting Rep"}, - }, - }, - Public: false, - } - - mailingListReader := mock.NewMockGrpsIOReader(mockRepo) - memberWriter := mock.NewMockGrpsIOMemberWriter(mockRepo) - memberReader := mock.NewMockGrpsIOMemberReader(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - committeeSyncService := NewCommitteeSyncService(mailingListReader, memberWriter, memberReader, entityReader) - service := NewMailingListSyncService(committeeSyncService) - - event := model.MailingListUpdatedEvent{ - OldMailingList: oldMailingList, - NewMailingList: newMailingList, - } - - data, err := json.Marshal(event) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.MailingListUpdatedSubject, - Data: data, - } - - err = service.HandleMessage(ctx, msg) - assert.NoError(t, err) - }) - - t.Run("updated event handles filter modifications without error", func(t *testing.T) { - mockRepo := mock.NewMockRepository() - mockRepo.ClearAll() - - oldMailingList := &model.GrpsIOMailingList{ - UID: "ml-integration-4", - GroupName: "test-filter-change", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-1", - Name: "Committee 1", - AllowedVotingStatuses: []string{"Voting Rep"}, // Only voting reps - }, - }, - Public: true, - } - - newMailingList := &model.GrpsIOMailingList{ - UID: "ml-integration-4", - GroupName: "test-filter-change", - ServiceUID: "service-1", - Committees: []model.Committee{ - { - UID: "committee-1", - Name: "Committee 1", - AllowedVotingStatuses: []string{"Voting Rep", "Observer"}, // Added observers - }, - }, - Public: true, - } - - mailingListReader := mock.NewMockGrpsIOReader(mockRepo) - memberWriter := mock.NewMockGrpsIOMemberWriter(mockRepo) - memberReader := mock.NewMockGrpsIOMemberReader(mockRepo) - entityReader := mock.NewMockEntityAttributeReader(mockRepo) - - committeeSyncService := NewCommitteeSyncService(mailingListReader, memberWriter, memberReader, entityReader) - service := NewMailingListSyncService(committeeSyncService) - - event := model.MailingListUpdatedEvent{ - OldMailingList: oldMailingList, - NewMailingList: newMailingList, - } - - data, err := json.Marshal(event) - require.NoError(t, err) - - msg := &nats.Msg{ - Subject: constants.MailingListUpdatedSubject, - Data: data, - } - - err = service.HandleMessage(ctx, msg) - assert.NoError(t, err) - }) -}