From 95a0604038ab0d2db3654bc074c5ce7e5d8776b8 Mon Sep 17 00:00:00 2001 From: Ruben Horn Date: Fri, 18 Feb 2022 19:48:25 +0100 Subject: [PATCH 1/6] api for getting participations for crowdactions/profiles --- docs/api2.yaml | 273 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 190 insertions(+), 83 deletions(-) diff --git a/docs/api2.yaml b/docs/api2.yaml index 1a8bbad..bd209d2 100644 --- a/docs/api2.yaml +++ b/docs/api2.yaml @@ -1,22 +1,22 @@ openapi: 3.0.1 info: title: collaction-dev - version: '1.0' + version: "1.0" servers: - - url: 'https://api{subdomainSuffix}.{domain}/' + - url: "https://api{subdomainSuffix}.{domain}/" variables: subdomainSuffix: - default: '-dev' + default: "-dev" domain: default: collaction.org paths: - '/crowdactions/{crowdactionID}': + "/crowdactions/{crowdactionID}": get: tags: - Crowdaction summary: Get details of a specific crowdaction parameters: - - $ref: '#/components/parameters/ApiVersionParameter' + - $ref: "#/components/parameters/ApiVersionParameter" - name: crowdactionID in: path required: true @@ -30,7 +30,7 @@ paths: type: string format: password responses: - '200': + "200": description: Crowdaction details content: application/json: @@ -48,8 +48,8 @@ paths: type: string example: >- sustainability#food#88615462-2789-4159-8659-2ecfd33ef305 - - $ref: '#/components/schemas/CrowdactionDetails' - '401': + - $ref: "#/components/schemas/CrowdactionDetails" + "401": description: Unauthorized (Invalid password) content: application/json: @@ -63,9 +63,73 @@ paths: type: string example: password: Invalid or missing password - '403': - $ref: '#/components/responses/UnsupportedClientVersion' - '404': + "403": + $ref: "#/components/responses/UnsupportedClientVersion" + "404": + description: Crowdaction not found + content: + application/json: + schema: + type: object + properties: + status: + type: string + default: fail + data: + type: string + example: + password: crowdaction does not exist + "/crowdactions/{crowdactionID}/participants": + get: + tags: + - Crowdaction + summary: Get details of a specific crowdaction + parameters: + - $ref: "#/components/parameters/ApiVersionParameter" + - name: crowdactionID + in: path + required: true + schema: + type: string + - in: query + name: password + description: Only include if crowdaction requires password + required: false + schema: + type: string + format: password + responses: + "200": + description: Crowdaction details + content: + application/json: + schema: + type: object + properties: + status: + type: string + default: success + data: + type: array + items: + $ref: "#/components/schemas/Participation" + "401": + description: Unauthorized (Invalid password) + content: + application/json: + schema: + type: object + properties: + status: + type: string + default: fail + data: + type: string + example: + password: Invalid or missing password + "403": + $ref: "#/components/responses/UnsupportedClientVersion" + "404": description: Crowdaction not found content: application/json: @@ -85,7 +149,7 @@ paths: - Crowdaction summary: Get list of crowdactions parameters: - - $ref: '#/components/parameters/ApiVersionParameter' + - $ref: "#/components/parameters/ApiVersionParameter" - name: status in: query required: false @@ -97,7 +161,7 @@ paths: - active - ended responses: - '200': + "200": description: List of crowdactions content: application/json: @@ -117,12 +181,12 @@ paths: type: string example: >- sustainability#food#88615462-2789-4159-8659-2ecfd33ef305 - - $ref: '#/components/schemas/CrowdactionListItem' - '403': - $ref: '#/components/responses/UnsupportedClientVersion' - '/crowdactions/{crowdactionID}/participation': + - $ref: "#/components/schemas/CrowdactionListItem" + "403": + $ref: "#/components/responses/UnsupportedClientVersion" + "/crowdactions/{crowdactionID}/participation": parameters: - - $ref: '#/components/parameters/ApiVersionParameter' + - $ref: "#/components/parameters/ApiVersionParameter" - name: crowdactionID in: path required: true @@ -133,7 +197,7 @@ paths: - Crowdaction summary: Get existing participation in a particular crowdaction responses: - '200': + "200": description: Success message content: application/json: @@ -144,10 +208,10 @@ paths: type: string default: success data: - $ref: '#/components/schemas/Participation' - '403': - $ref: '#/components/responses/UnsupportedClientVersion' - '404': + $ref: "#/components/schemas/Participation" + "403": + $ref: "#/components/responses/UnsupportedClientVersion" + "404": description: Not found (Not participating) content: application/json: @@ -168,7 +232,7 @@ paths: - Crowdaction summary: Stop participating in a particular crowdaction responses: - '200': + "200": description: Success message content: application/json: @@ -180,9 +244,9 @@ paths: default: success data: default: null - '403': - $ref: '#/components/responses/UnsupportedClientVersion' - '404': + "403": + $ref: "#/components/responses/UnsupportedClientVersion" + "404": description: Not found (Not participating) content: application/json: @@ -225,7 +289,7 @@ paths: - no-meat - no-dairy responses: - '201': + "201": description: Success message content: application/json: @@ -237,7 +301,7 @@ paths: default: success data: default: null - '400': + "400": description: Bad request (Invalid commitments) content: application/json: @@ -251,7 +315,7 @@ paths: type: string example: commitments: Required commitment "no-beef" missing - '401': + "401": description: Unauthorized (Invalid password) content: application/json: @@ -265,9 +329,9 @@ paths: type: string example: password: Invalid or missing password - '403': - $ref: '#/components/responses/UnsupportedClientVersion' - '409': + "403": + $ref: "#/components/responses/UnsupportedClientVersion" + "409": description: Conflict content: application/json: @@ -283,9 +347,9 @@ paths: crowdactionID: already participating security: - FirebaseAuthorizer: [] - '/profiles/{userID}': + "/profiles/{userID}": parameters: - - $ref: '#/components/parameters/ApiVersionParameter' + - $ref: "#/components/parameters/ApiVersionParameter" - name: userID in: path required: true @@ -300,9 +364,9 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Profile' + $ref: "#/components/schemas/Profile" responses: - '201': + "201": description: Profile was created successfully content: application/json: @@ -314,7 +378,7 @@ paths: status: type: string default: success - '400': + "400": description: Bad request (Validation failed) content: application/json: @@ -328,9 +392,9 @@ paths: type: object example: displayname: Name must not be empty - '403': - $ref: '#/components/responses/UnsupportedClientVersion' - '409': + "403": + $ref: "#/components/responses/UnsupportedClientVersion" + "409": description: Conflict content: application/json: @@ -355,11 +419,11 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Profile' + $ref: "#/components/schemas/Profile" security: - FirebaseAuthorizer: [] responses: - '200': + "200": description: Profile was updated successfully content: application/json: @@ -371,7 +435,7 @@ paths: status: type: string default: success - '400': + "400": description: Bad request (Validation failed) content: application/json: @@ -385,9 +449,9 @@ paths: type: object example: displayname: Name must not be empty - '403': - $ref: '#/components/responses/UnsupportedClientVersion' - '404': + "403": + $ref: "#/components/responses/UnsupportedClientVersion" + "404": description: Profile was not found content: application/json: @@ -406,7 +470,7 @@ paths: - Profile summary: View the profile of a user responses: - '200': + "200": description: Profile was found content: application/json: @@ -417,10 +481,53 @@ paths: type: string default: success data: - $ref: '#/components/schemas/Profile' - '403': - $ref: '#/components/responses/UnsupportedClientVersion' - '404': + $ref: "#/components/schemas/Profile" + "403": + $ref: "#/components/responses/UnsupportedClientVersion" + "404": + description: Profile was not found + content: + application/json: + schema: + type: object + properties: + status: + type: string + default: fail + data: + type: object + default: + userID: No such profile + /profiles/{userID}/participations: + parameters: + - $ref: "#/components/parameters/ApiVersionParameter" + - name: userID + in: path + required: true + schema: + type: string + get: + tags: + - Profile + summary: View the participations of a user + responses: + "200": + description: Profile was found + content: + application/json: + schema: + type: object + properties: + status: + type: string + default: success + data: + type: array + items: + $ref: "#/components/schemas/Participation" + "403": + $ref: "#/components/responses/UnsupportedClientVersion" + "404": description: Profile was not found content: application/json: @@ -436,13 +543,13 @@ paths: userID: No such profile /upload-profile-picture: parameters: - - $ref: '#/components/parameters/ApiVersionParameter' + - $ref: "#/components/parameters/ApiVersionParameter" get: tags: - Profile summary: Fetch an upload URL (POST) for the profile picture responses: - '200': + "200": description: >- Returns the URL where to upload (PUT) the square png image (250-1024px) @@ -460,13 +567,13 @@ paths: upload_url: type: string format: uri - '403': - $ref: '#/components/responses/UnsupportedClientVersion' + "403": + $ref: "#/components/responses/UnsupportedClientVersion" security: - FirebaseAuthorizer: [] /contact: parameters: - - $ref: '#/components/parameters/ApiVersionParameter' + - $ref: "#/components/parameters/ApiVersionParameter" post: tags: - Other @@ -479,7 +586,7 @@ paths: type: object properties: data: - $ref: '#/components/schemas/Email' + $ref: "#/components/schemas/Email" nonce: type: string example: c2dlcjIzc3NndnJzdnIyM @@ -487,7 +594,7 @@ paths: Nonce for PoW, see http://www.hashcash.org/ (Not yet implemented -> field will be ignored if present) responses: - '200': + "200": description: The status code indicates if the message was successfully sent content: application/json: @@ -499,7 +606,7 @@ paths: default: success data: default: null - '400': + "400": description: Bad request content: application/json: @@ -513,8 +620,8 @@ paths: type: object example: email: Not a valid email address - '403': - $ref: '#/components/responses/UnsupportedClientVersion' + "403": + $ref: "#/components/responses/UnsupportedClientVersion" components: parameters: ApiVersionParameter: @@ -527,7 +634,7 @@ components: schema: type: string pattern: '^[0-9]+\.[0-9]+$' - example: '1.0' + example: "1.0" responses: UnsupportedClientVersion: description: Forbidden (Client version unsupported) @@ -561,22 +668,22 @@ components: subcategory: type: string location: - $ref: '#/components/schemas/Location' + $ref: "#/components/schemas/Location" dates: - $ref: '#/components/schemas/CrowdactionDates' + $ref: "#/components/schemas/CrowdactionDates" password_required: type: boolean commitment_options: type: array items: - $ref: '#/components/schemas/CommitmentOption' + $ref: "#/components/schemas/CommitmentOption" participation: - $ref: '#/components/schemas/Date' + $ref: "#/components/schemas/Date" images: type: object properties: banner: - $ref: '#/components/schemas/Image' + $ref: "#/components/schemas/Image" CrowdactionListItem: type: object properties: @@ -587,18 +694,18 @@ components: subcategory: type: string location: - $ref: '#/components/schemas/Location' + $ref: "#/components/schemas/Location" dates: - $ref: '#/components/schemas/CrowdactionDates' + $ref: "#/components/schemas/CrowdactionDates" password_required: type: boolean participation: - $ref: '#/components/schemas/ParticipationSummary' + $ref: "#/components/schemas/ParticipationSummary" images: type: object properties: card: - $ref: '#/components/schemas/Image' + $ref: "#/components/schemas/Image" CrowdactionDates: type: object description: >- @@ -606,11 +713,11 @@ components: latter properties: start: - $ref: '#/components/schemas/Date' + $ref: "#/components/schemas/Date" end: - $ref: '#/components/schemas/Date' + $ref: "#/components/schemas/Date" join_before: - $ref: '#/components/schemas/Date' + $ref: "#/components/schemas/Date" Location: type: object properties: @@ -638,7 +745,7 @@ components: Commitments that are included in (and required by) this commitment. They can also be individually selected items: - $ref: '#/components/schemas/CommitmentOption' + $ref: "#/components/schemas/CommitmentOption" example: id: no-beef label: Not eating beef @@ -646,7 +753,7 @@ components: Date: type: string pattern: '\d{4}-\d{2}-\d{2}' - example: '2021-11-28' + example: "2021-11-28" ParticipationSummary: type: object properties: @@ -668,12 +775,12 @@ components: properties: url: type: string - example: 'https://picsum.photos/300/200' + example: "https://picsum.photos/300/200" format: uri blur_hash: type: string example: LEHV6nWB2yk8pyo0adR*.7kCMdnj - description: '(see https://blurha.sh/)' + description: "(see https://blurha.sh/)" Participation: type: object properties: @@ -684,7 +791,7 @@ components: description: Displayname of the user crowdactionID: type: string - example: 'sustainability#food#88615462-2789-4159-8659-2ecfd33ef305' + example: "sustainability#food#88615462-2789-4159-8659-2ecfd33ef305" title: type: string description: Title of the crowdactions @@ -697,7 +804,7 @@ components: - no-dairy description: Array of commitment ids date: - $ref: '#/components/schemas/Date' + $ref: "#/components/schemas/Date" Profile: type: object properties: @@ -705,7 +812,7 @@ components: type: string example: Max Murphy location: - $ref: '#/components/schemas/Location' + $ref: "#/components/schemas/Location" bio: type: string example: >- @@ -723,7 +830,7 @@ components: example: Hello world message: type: string - example: 'Please respond to this email :)' + example: "Please respond to this email :)" app_version: type: string pattern: '^(?:ios|android) [0-9]+\.[0-9]+\.[0-9]+\+[0-9]+$' From 5449dab51a94b8b0f4161f77e698322c5e65d11c Mon Sep 17 00:00:00 2001 From: midepeter Date: Thu, 24 Mar 2022 19:27:02 +0100 Subject: [PATCH 2/6] implementation of participations handler --- go.mod | 1 + go.sum | 3 ++ internal/models/profile.go | 2 +- internal/participation/service.go | 6 ++++ .../aws/participation/participations.go | 32 +++++++++++++++++++ pkg/repository/participation.go | 7 ++++ 6 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 pkg/handler/aws/participation/participations.go diff --git a/go.mod b/go.mod index d1913b4..2cac461 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/aws/aws-sdk-go v1.41.10 github.com/gin-gonic/gin v1.7.7 github.com/go-ozzo/ozzo-validation v3.6.0+incompatible + github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/go-playground/locales v0.14.0 github.com/go-playground/universal-translator v0.18.0 github.com/go-playground/validator/v10 v10.10.0 diff --git a/go.sum b/go.sum index 5f6577f..1c0801d 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-lambda-go v1.27.0 h1:aLzrJwdyHoF1A18YeVdJjX8Ixkd+bpogdxVInvHcWjM= @@ -17,6 +18,8 @@ github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= diff --git a/internal/models/profile.go b/internal/models/profile.go index cfcd86e..b1da906 100644 --- a/internal/models/profile.go +++ b/internal/models/profile.go @@ -2,7 +2,7 @@ package models import ( "github.com/CollActionteam/collaction_backend/internal/constants" - validation "github.com/go-ozzo/ozzo-validation" + validation "github.com/go-ozzo/ozzo-validation/v4" ) type Profile struct { diff --git a/internal/participation/service.go b/internal/participation/service.go index 379b3c9..eca89cd 100644 --- a/internal/participation/service.go +++ b/internal/participation/service.go @@ -14,6 +14,7 @@ type Service interface { GetParticipation(ctx context.Context, userID string, crowdactionID string) (*m.ParticipationRecord, error) RegisterParticipation(ctx context.Context, userID string, name string, crowdaction *models.Crowdaction, payload m.JoinPayload) error CancelParticipation(ctx context.Context, userID string, crowdaction *models.Crowdaction) error + GetParticipations(ctx context.Context, userID string) (*[]m.ParticipationRecord, error) } type participationService struct { @@ -68,3 +69,8 @@ func (s *participationService) CancelParticipation(ctx context.Context, userID s } return recordEvent(userID, crowdaction.CrowdactionID, part.Commitments, -1) } + + +func (s *participationService) GetParticipations(ctx context.Context, userID string) (*[]m.ParticipationRecord, error) { + +} \ No newline at end of file diff --git a/pkg/handler/aws/participation/participations.go b/pkg/handler/aws/participation/participations.go new file mode 100644 index 0000000..49f0da6 --- /dev/null +++ b/pkg/handler/aws/participation/participations.go @@ -0,0 +1,32 @@ +package main + +import ( + "context" + "encoding/json" + "net/http" + + m "github.com/CollActionteam/collaction_backend/internal/models" + "github.com/CollActionteam/collaction_backend/utils" + "github.com/aws/aws-lambda-go/events" +) + +type participations *[]m.ParticipationRecord + +func (h *ParticipationHandler) getParticipations(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) { + var err error + userID, _, _, err := retrieveInfoFromRequest(req) + if err != nil { + return handlerError(err), nil + } + + particpations, err := h.service.GetParticipations(ctx, userID) + if err != nil { + return utils.CreateMessageHttpResponse(http.StatusNotFound, "Invalid UserId"), err + } + + jsonPayload, _ := json.Marshal(particpations) + return events.APIGatewayV2HTTPResponse{ + StatusCode: http.StatusOK, + Body: string(jsonPayload), + }, nil +} diff --git a/pkg/repository/participation.go b/pkg/repository/participation.go index 495d069..852f18a 100644 --- a/pkg/repository/participation.go +++ b/pkg/repository/participation.go @@ -3,6 +3,7 @@ package repository import ( "context" "errors" + "github.com/CollActionteam/collaction_backend/internal/constants" m "github.com/CollActionteam/collaction_backend/internal/models" "github.com/CollActionteam/collaction_backend/models" @@ -15,6 +16,7 @@ type Participation interface { Get(ctx context.Context, userID string, crowdactionID string) (*m.ParticipationRecord, error) Register(ctx context.Context, userID string, name string, crowdaction *models.Crowdaction, payload m.JoinPayload) error Cancel(ctx context.Context, userID string, crowdaction *models.Crowdaction) error + List(ctx context.Context, userID string) (*[]m.ParticipationRecord, error) } type participation struct { @@ -68,3 +70,8 @@ func (s *participation) Cancel(ctx context.Context, userID string, crowdaction * sk := utils.PrefixParticipationSK_CrowdactionID + crowdaction.CrowdactionID return s.dbClient.DeleteDBItem(constants.TableName, pk, sk) } + +func (s *participation) List(ctx context.Context, userID string) (*[]m.ParticipationRecord, error) { + pk := utils.PrefixParticipationPK_UserID + userID + //s.dbClient. +} From e4f7cfe097fffad932a194412b5dd43b8189705d Mon Sep 17 00:00:00 2001 From: Ruben Horn Date: Sun, 1 May 2022 17:29:46 +0200 Subject: [PATCH 3/6] list participations/participants (not tested, no pagination) --- docs/api2.yaml | 14 ----- internal/constants/app.go | 1 + internal/participation/service.go | 13 +++-- pkg/handler/aws/participation/main.go | 11 ++-- .../aws/participation/participation.go | 2 +- .../aws/participation/participations.go | 32 ----------- pkg/handler/aws/participations/main.go | 16 ++++++ .../aws/participations/participations.go | 54 +++++++++++++++++++ pkg/repository/participation.go | 47 ++++++++++++++-- template.yaml | 29 ++++++++++ utils/api.go | 1 + 11 files changed, 161 insertions(+), 59 deletions(-) delete mode 100644 pkg/handler/aws/participation/participations.go create mode 100644 pkg/handler/aws/participations/main.go create mode 100644 pkg/handler/aws/participations/participations.go diff --git a/docs/api2.yaml b/docs/api2.yaml index b26f264..698f2e7 100644 --- a/docs/api2.yaml +++ b/docs/api2.yaml @@ -527,20 +527,6 @@ paths: $ref: "#/components/schemas/Participation" "403": $ref: "#/components/responses/UnsupportedClientVersion" - "404": - description: Profile was not found - content: - application/json: - schema: - type: object - properties: - status: - type: string - default: fail - data: - type: object - default: - userID: No such profile /upload-profile-picture: parameters: - $ref: "#/components/parameters/ApiVersionParameter" diff --git a/internal/constants/app.go b/internal/constants/app.go index 6fc46b3..0e17078 100644 --- a/internal/constants/app.go +++ b/internal/constants/app.go @@ -17,6 +17,7 @@ const ( var ( TableName = os.Getenv("TABLE_NAME") + IndexName = os.Getenv("INDEX_NAME") ProfileTablename = os.Getenv("PROFILE_TABLE") ParticipationQueueName = os.Getenv("PARTICIPATION_QUEUE") ) diff --git a/internal/participation/service.go b/internal/participation/service.go index eca89cd..63cb09e 100644 --- a/internal/participation/service.go +++ b/internal/participation/service.go @@ -2,6 +2,7 @@ package participation import ( "context" + "github.com/CollActionteam/collaction_backend/internal/constants" m "github.com/CollActionteam/collaction_backend/internal/models" "github.com/CollActionteam/collaction_backend/models" @@ -14,7 +15,8 @@ type Service interface { GetParticipation(ctx context.Context, userID string, crowdactionID string) (*m.ParticipationRecord, error) RegisterParticipation(ctx context.Context, userID string, name string, crowdaction *models.Crowdaction, payload m.JoinPayload) error CancelParticipation(ctx context.Context, userID string, crowdaction *models.Crowdaction) error - GetParticipations(ctx context.Context, userID string) (*[]m.ParticipationRecord, error) + GetParticipationsUser(ctx context.Context, userID string) (*[]m.ParticipationRecord, error) + GetParticipationsCrowdaction(ctx context.Context, crowdactionID string) (*[]m.ParticipationRecord, error) } type participationService struct { @@ -70,7 +72,10 @@ func (s *participationService) CancelParticipation(ctx context.Context, userID s return recordEvent(userID, crowdaction.CrowdactionID, part.Commitments, -1) } +func (s *participationService) GetParticipationsUser(ctx context.Context, userID string) (*[]m.ParticipationRecord, error) { + return s.participationRepository.ListByUser(ctx, userID) +} -func (s *participationService) GetParticipations(ctx context.Context, userID string) (*[]m.ParticipationRecord, error) { - -} \ No newline at end of file +func (s *participationService) GetParticipationsCrowdaction(ctx context.Context, crowdactionID string) (*[]m.ParticipationRecord, error) { + return s.participationRepository.ListByCrowdaction(ctx, crowdactionID) +} diff --git a/pkg/handler/aws/participation/main.go b/pkg/handler/aws/participation/main.go index 693c993..233750a 100644 --- a/pkg/handler/aws/participation/main.go +++ b/pkg/handler/aws/participation/main.go @@ -2,22 +2,23 @@ package main import ( "context" + "net/http" + "strings" + "github.com/CollActionteam/collaction_backend/utils" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" - "net/http" - "strings" ) func handler(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) { method := strings.ToLower(req.RequestContext.HTTP.Method) switch method { case "post": - return NewContactHandler().createParticipation(ctx, req) + return NewParticipationHandler().createParticipation(ctx, req) case "delete": - return NewContactHandler().deleteParticipation(ctx, req) + return NewParticipationHandler().deleteParticipation(ctx, req) case "get": - return NewContactHandler().getParticipation(ctx, req) + return NewParticipationHandler().getParticipation(ctx, req) default: return utils.CreateMessageHttpResponse(http.StatusNotImplemented, "not implemented"), nil diff --git a/pkg/handler/aws/participation/participation.go b/pkg/handler/aws/participation/participation.go index 5bdba7b..879add6 100644 --- a/pkg/handler/aws/participation/participation.go +++ b/pkg/handler/aws/participation/participation.go @@ -21,7 +21,7 @@ type ParticipationHandler struct { service participation.Service } -func NewContactHandler() *ParticipationHandler { +func NewParticipationHandler() *ParticipationHandler { participationRepository := repository.NewParticipation(aws.NewDynamo()) return &ParticipationHandler{service: participation.NewParticipationService(participationRepository)} } diff --git a/pkg/handler/aws/participation/participations.go b/pkg/handler/aws/participation/participations.go deleted file mode 100644 index 49f0da6..0000000 --- a/pkg/handler/aws/participation/participations.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "net/http" - - m "github.com/CollActionteam/collaction_backend/internal/models" - "github.com/CollActionteam/collaction_backend/utils" - "github.com/aws/aws-lambda-go/events" -) - -type participations *[]m.ParticipationRecord - -func (h *ParticipationHandler) getParticipations(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) { - var err error - userID, _, _, err := retrieveInfoFromRequest(req) - if err != nil { - return handlerError(err), nil - } - - particpations, err := h.service.GetParticipations(ctx, userID) - if err != nil { - return utils.CreateMessageHttpResponse(http.StatusNotFound, "Invalid UserId"), err - } - - jsonPayload, _ := json.Marshal(particpations) - return events.APIGatewayV2HTTPResponse{ - StatusCode: http.StatusOK, - Body: string(jsonPayload), - }, nil -} diff --git a/pkg/handler/aws/participations/main.go b/pkg/handler/aws/participations/main.go new file mode 100644 index 0000000..25f7f6f --- /dev/null +++ b/pkg/handler/aws/participations/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "context" + + "github.com/aws/aws-lambda-go/events" + "github.com/aws/aws-lambda-go/lambda" +) + +func handler(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) { + return NewParticipationsHandler().getParticipations(ctx, req) +} + +func main() { + lambda.Start(handler) +} diff --git a/pkg/handler/aws/participations/participations.go b/pkg/handler/aws/participations/participations.go new file mode 100644 index 0000000..c8435e9 --- /dev/null +++ b/pkg/handler/aws/participations/participations.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "strings" + + m "github.com/CollActionteam/collaction_backend/internal/models" + "github.com/CollActionteam/collaction_backend/internal/participation" + "github.com/CollActionteam/collaction_backend/pkg/repository" + "github.com/CollActionteam/collaction_backend/pkg/repository/aws" + "github.com/CollActionteam/collaction_backend/utils" + "github.com/aws/aws-lambda-go/events" +) + +type participations *[]m.ParticipationRecord + +type ParticipationsHandler struct { + service participation.Service +} + +func NewParticipationsHandler() *ParticipationsHandler { + participationRepository := repository.NewParticipation(aws.NewDynamo()) + return &ParticipationsHandler{service: participation.NewParticipationService(participationRepository)} +} + +func (h *ParticipationsHandler) getParticipations(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) { + var err error + var data *[]m.ParticipationRecord + path := req.RequestContext.HTTP.Path + // TODO pagination + if strings.HasPrefix(path, "/crowdactions") { + crowdactionID := req.PathParameters["crowdactionID"] + // TODO check password: + // 1. Fetch crowdaction + // 2. If the crowdaction is not found, return 404 + // 3. If the crowdaction is password protected, check the request for the password + data, err = h.service.GetParticipationsCrowdaction(ctx, crowdactionID) + } else if strings.HasPrefix(path, "/profiles") { + userID := req.PathParameters["userID"] + data, err = h.service.GetParticipationsUser(ctx, userID) + } else { + err = fmt.Errorf("invalid path: %s", path) + } + if err != nil { + handlerError(err) + } + return utils.GetDataHttpResponse(http.StatusOK, "", data), nil +} + +func handlerError(err error) events.APIGatewayV2HTTPResponse { + return utils.CreateMessageHttpResponse(http.StatusInternalServerError, err.Error()) +} diff --git a/pkg/repository/participation.go b/pkg/repository/participation.go index 852f18a..b42a8f7 100644 --- a/pkg/repository/participation.go +++ b/pkg/repository/participation.go @@ -9,14 +9,17 @@ import ( "github.com/CollActionteam/collaction_backend/models" "github.com/CollActionteam/collaction_backend/pkg/repository/aws" "github.com/CollActionteam/collaction_backend/utils" + "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/aws/aws-sdk-go/service/dynamodb/expression" ) type Participation interface { Get(ctx context.Context, userID string, crowdactionID string) (*m.ParticipationRecord, error) Register(ctx context.Context, userID string, name string, crowdaction *models.Crowdaction, payload m.JoinPayload) error Cancel(ctx context.Context, userID string, crowdaction *models.Crowdaction) error - List(ctx context.Context, userID string) (*[]m.ParticipationRecord, error) + ListByUser(ctx context.Context, userID string) (*[]m.ParticipationRecord, error) + ListByCrowdaction(ctx context.Context, crowdactionID string) (*[]m.ParticipationRecord, error) } type participation struct { @@ -71,7 +74,45 @@ func (s *participation) Cancel(ctx context.Context, userID string, crowdaction * return s.dbClient.DeleteDBItem(constants.TableName, pk, sk) } -func (s *participation) List(ctx context.Context, userID string) (*[]m.ParticipationRecord, error) { +func (s *participation) listByPK(ctx context.Context, pk string, useGSI bool) (*[]m.ParticipationRecord, error) { + var indexName *string = nil + if useGSI { + indexName = &constants.IndexName + } + participationRecords := []m.ParticipationRecord{} + // TODO refactor (do not directly interact with dynamo) + dbClient := utils.CreateDBClient() + keyCond := expression.Key(utils.PartitionKey).Equal(expression.Value(pk)) + expr, err := expression.NewBuilder(). + WithKeyCondition(keyCond). + Build() + if err != nil { + return nil, err + } + out, err := dbClient.Query(&dynamodb.QueryInput{ + TableName: &constants.TableName, + IndexName: indexName, + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), + FilterExpression: expr.Filter(), + }) + for _, item := range out.Items { + var participationRecord m.ParticipationRecord + itemErr := dynamodbattribute.UnmarshalMap(item, &participationRecord) + if itemErr == nil { + participationRecords = append(participationRecords, participationRecord) + } + } + return &participationRecords, err +} + +func (s *participation) ListByUser(ctx context.Context, userID string) (*[]m.ParticipationRecord, error) { pk := utils.PrefixParticipationPK_UserID + userID - //s.dbClient. + return s.listByPK(ctx, pk, false) +} + +func (s *participation) ListByCrowdaction(ctx context.Context, crowdactionID string) (*[]m.ParticipationRecord, error) { + pk := utils.PrefixParticipationSK_CrowdactionID + crowdactionID + return s.listByPK(ctx, pk, true) } diff --git a/template.yaml b/template.yaml index 26bafa4..0afa810 100644 --- a/template.yaml +++ b/template.yaml @@ -358,6 +358,35 @@ Resources: Action: - sqs:SendMessage Resource: !GetAtt ParticipationQueue.Arn + + ParticipationsFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: pkg/handler/aws/participations + Handler: participations + Runtime: go1.x + Environment: + Variables: + # Maybe use a different table to remove the overhead form the GSI on the other data + TABLE_NAME: !Ref SingleTable + INDEX_NAME: "invertedIndex" + Events: + Participants: + Type: HttpApi + Properties: + Path: /crowdactions/{crowdactionID}/participations + Method: any + ApiId: !Ref HttpApi + Participations: + Type: HttpApi + Properties: + Path: /profiles/{userID}/participations + Method: any + ApiId: !Ref HttpApi + Policies: + - DynamoDBCrudPolicy: + TableName: + !Ref SingleTable ProfileCRUDFunction: Type: AWS::Serverless::Function diff --git a/utils/api.go b/utils/api.go index d9bb6fe..abd2697 100644 --- a/utils/api.go +++ b/utils/api.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-lambda-go/events" ) +// Deprecated: Use GetDataHttpResponse instead (uses APIGatewayV2HTTPResponse) func GetMessageHttpResponse(statusCode int, msg string) events.APIGatewayProxyResponse { // "Cannot go wrong" jsonPayload, _ := json.Marshal(map[string]interface{}{"message": msg}) From 062cbb514f8e732156d352c6e3a8ae2944fe4647 Mon Sep 17 00:00:00 2001 From: Ruben Horn Date: Sun, 1 May 2022 22:28:26 +0200 Subject: [PATCH 4/6] added missing functions to mock --- pkg/mocks/repository/participation.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/mocks/repository/participation.go b/pkg/mocks/repository/participation.go index fcc98cd..b8126ae 100644 --- a/pkg/mocks/repository/participation.go +++ b/pkg/mocks/repository/participation.go @@ -2,6 +2,7 @@ package repository import ( "context" + m "github.com/CollActionteam/collaction_backend/internal/models" "github.com/CollActionteam/collaction_backend/models" "github.com/stretchr/testify/mock" @@ -25,3 +26,13 @@ func (s *Participation) Cancel(ctx context.Context, userID string, crowdaction * args := s.Mock.Called(ctx, userID, crowdaction) return args.Error(0) } + +func (s *Participation) ListByUser(ctx context.Context, userID string) (*[]m.ParticipationRecord, error) { + args := s.Mock.Called(ctx, userID) + return args.Get(0).(*[]m.ParticipationRecord), args.Error(1) +} + +func (s *Participation) ListByCrowdaction(ctx context.Context, crowdactionID string) (*[]m.ParticipationRecord, error) { + args := s.Mock.Called(ctx, crowdactionID) + return args.Get(0).(*[]m.ParticipationRecord), args.Error(1) +} From 99afc31ee8656ab8e45ecdaae1464aa454aee64b Mon Sep 17 00:00:00 2001 From: Nwokoye Chigozie Date: Wed, 4 May 2022 07:44:16 +0100 Subject: [PATCH 5/6] added unit test --- internal/participation/service_test.go | 41 +++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/internal/participation/service_test.go b/internal/participation/service_test.go index 96271c4..037478b 100644 --- a/internal/participation/service_test.go +++ b/internal/participation/service_test.go @@ -2,12 +2,13 @@ package participation_test import ( "context" + "testing" + m "github.com/CollActionteam/collaction_backend/internal/models" "github.com/CollActionteam/collaction_backend/internal/participation" "github.com/CollActionteam/collaction_backend/models" "github.com/CollActionteam/collaction_backend/pkg/mocks/repository" "github.com/stretchr/testify/assert" - "testing" ) func TestParticipation_RegisterParticipation(t *testing.T) { @@ -21,3 +22,41 @@ func TestParticipation_RegisterParticipation(t *testing.T) { assert.EqualError(t, err, "cannot change participation for this crowdaction anymore") }) } + +func Test_GetParticipationsUser(t *testing.T) { + ast := assert.New(t) + repository := &repository.Participation{} + cc := []m.ParticipationRecord{} + + t.Run("testing GetParticipationsUser", func(t *testing.T) { + userID := "123" + + repository.On("ListByUser", context.Background(), userID).Return(&cc, nil).Once() + + service := participation.NewParticipationService(repository) + crowdactions, err := service.GetParticipationsUser(context.Background(), userID) + ast.NoError(err) + ast.Equal(&cc, crowdactions) + + repository.AssertExpectations(t) + }) +} + +func Test_GetParticipationsCrowdaction(t *testing.T) { + ast := assert.New(t) + cc := []m.ParticipationRecord{} + repository := &repository.Participation{} + + t.Run("testing GetParticipationsCrowdaction", func(t *testing.T) { + crowdactionID := "1" + + repository.On("ListByCrowdaction", context.Background(), crowdactionID).Return(&cc, nil).Once() + + service := participation.NewParticipationService(repository) + crowdactions, err := service.GetParticipationsCrowdaction(context.Background(), crowdactionID) + ast.NoError(err) + ast.Equal(&cc, crowdactions) + + repository.AssertExpectations(t) + }) +} From 042bdedd77bf380c229911ba42070ca3ddc0f6ad Mon Sep 17 00:00:00 2001 From: Ruben Horn Date: Sat, 14 May 2022 10:30:39 +0200 Subject: [PATCH 6/6] debugging --- event_examples/crowdaction_participations.json | 11 +++++++++++ pkg/handler/aws/participations/participations.go | 5 ++++- template.yaml | 6 +++++- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 event_examples/crowdaction_participations.json diff --git a/event_examples/crowdaction_participations.json b/event_examples/crowdaction_participations.json new file mode 100644 index 0000000..00fa5da --- /dev/null +++ b/event_examples/crowdaction_participations.json @@ -0,0 +1,11 @@ +{ + "pathParameters": { + "crowdactionID": "sustainability#food#303e67d6" + }, + "requestContext": { + "http": { + "method": "GET", + "path": "/crowdactions/sustainability#food#303e67d6" + } + } + } \ No newline at end of file diff --git a/pkg/handler/aws/participations/participations.go b/pkg/handler/aws/participations/participations.go index 609060f..8940046 100644 --- a/pkg/handler/aws/participations/participations.go +++ b/pkg/handler/aws/participations/participations.go @@ -25,16 +25,18 @@ func NewParticipationsHandler() *ParticipationsHandler { } func (h *ParticipationsHandler) getParticipations(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) { - var err error + var err error = nil var data *[]m.ParticipationRecord path := req.RequestContext.HTTP.Path // TODO pagination + fmt.Printf("Participations function called with path: %s\n", path) // TODO remove if strings.HasPrefix(path, "/crowdactions") { crowdactionID := req.PathParameters["crowdactionID"] // TODO check password: // 1. Fetch crowdaction // 2. If the crowdaction is not found, return 404 // 3. If the crowdaction is password protected, check the request for the password + fmt.Printf("getParticipations: crowdactionID: %s\n", crowdactionID) // TODO remove data, err = h.service.GetParticipationsCrowdaction(ctx, crowdactionID) } else if strings.HasPrefix(path, "/profiles") { userID := req.PathParameters["userID"] @@ -43,6 +45,7 @@ func (h *ParticipationsHandler) getParticipations(ctx context.Context, req event err = fmt.Errorf("invalid path: %s", path) } if err != nil { + fmt.Printf("getParticipations: error: %s\n", err) // TODO remove handlerError(err) } return utils.GetDataHttpResponse(http.StatusOK, "", data), nil diff --git a/template.yaml b/template.yaml index a61fb94..9c7539d 100644 --- a/template.yaml +++ b/template.yaml @@ -284,7 +284,7 @@ Resources: FetchCrowdaction: Type: HttpApi Properties: - Path: /crowdactions/{crowdactionID} + Path: /crowdactions/{crowdactionID}/ Method: get ApiId: !Ref HttpApi Auth: @@ -373,12 +373,16 @@ Resources: Path: /crowdactions/{crowdactionID}/participations Method: any ApiId: !Ref HttpApi + Auth: + Authorizer: "NONE" Participations: Type: HttpApi Properties: Path: /profiles/{userID}/participations Method: any ApiId: !Ref HttpApi + Auth: + Authorizer: "NONE" Policies: - DynamoDBCrudPolicy: TableName: