├── .dockerignore ├── .github └── workflows │ └── dev.yml ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── docker-compose.yml ├── go.mod ├── go.sum ├── scripts ├── clear_none_image.sh ├── generate_rsa_key.sh └── install_protoc.sh ├── sdk ├── auth-service │ ├── auth_service.go │ ├── auth_service_grpc.go │ ├── proto │ │ └── token │ │ │ └── token.pb.go │ └── types.go ├── line-chatbot │ └── proto │ │ ├── chatbot │ │ └── chatbot.pb.go │ │ └── event │ │ └── event.pb.go ├── master-service │ ├── master_service.go │ ├── master_service_grpc.go │ ├── proto │ │ ├── acl │ │ │ └── acl.pb.go │ │ └── apps │ │ │ └── apps.pb.go │ └── types.go ├── notification-service │ └── proto │ │ └── push-notif │ │ └── push-notif.pb.go ├── order-service │ └── proto │ │ ├── master │ │ └── master.pb.go │ │ └── order │ │ └── order.pb.go ├── sdk.go ├── storage-service │ ├── README.md │ ├── proto │ │ └── storage │ │ │ └── storage.pb.go │ ├── storage_service.go │ ├── storage_service_grpc.go │ └── types.go └── user-service │ ├── proto │ ├── auth │ │ └── auth.pb.go │ └── member │ │ └── member.pb.go │ ├── types.go │ ├── user-service.go │ └── user_service_grpc.go └── services ├── auth-service ├── .env.sample ├── .gitignore ├── Dockerfile ├── Makefile ├── api │ ├── graphql │ │ ├── _common.graphql │ │ ├── _schema.graphql │ │ └── token.graphql │ ├── jsonschema │ │ └── schema.json │ └── proto │ │ └── token │ │ └── token.proto ├── candi.json ├── configs │ ├── configs.go │ └── rsa │ │ └── key.go ├── deployments │ └── k8s │ │ └── dev-auth-service.yaml ├── docs │ └── .gitkeep ├── internal │ ├── modules │ │ └── token │ │ │ ├── delivery │ │ │ ├── graphqlhandler │ │ │ │ ├── mutation_resolver.go │ │ │ │ ├── query_resolver.go │ │ │ │ └── root_resolver.go │ │ │ ├── grpchandler │ │ │ │ └── grpchandler.go │ │ │ └── workerhandler │ │ │ │ └── redis_handler.go │ │ │ ├── domain │ │ │ ├── claim.go │ │ │ └── payload.go │ │ │ ├── module.go │ │ │ ├── repository │ │ │ ├── repository.go │ │ │ └── repository_mongo.go │ │ │ └── usecase │ │ │ ├── usecase.go │ │ │ └── usecase_impl.go │ └── service.go ├── main.go └── pkg │ ├── helper │ └── helper.go │ └── shared │ ├── domain │ └── token.go │ ├── environment.go │ ├── repository │ ├── repository.go │ └── repository_mongo.go │ ├── token_validator.go │ └── usecase │ └── usecase.go ├── line-chatbot ├── .env.sample ├── .gitignore ├── Dockerfile ├── Makefile ├── api │ ├── graphql │ │ ├── _schema.graphql │ │ ├── chatbot.graphql │ │ └── event.graphql │ ├── jsonschema │ │ ├── push-message.json │ │ └── schema.json │ └── proto │ │ ├── chatbot │ │ └── chatbot.proto │ │ └── event │ │ └── event.proto ├── candi.json ├── configs │ └── configs.go ├── deployments │ └── k8s │ │ └── dev-line-chatbot.yml ├── docs │ └── .gitkeep ├── internal │ ├── modules │ │ ├── chatbot │ │ │ ├── delivery │ │ │ │ ├── graphqlhandler │ │ │ │ │ ├── mutation_resolver.go │ │ │ │ │ ├── query_resolver.go │ │ │ │ │ ├── root_resolver.go │ │ │ │ │ └── subscription_resolver.go │ │ │ │ └── resthandler │ │ │ │ │ └── resthandler.go │ │ │ ├── domain │ │ │ │ ├── domain.go │ │ │ │ └── payload.go │ │ │ ├── module.go │ │ │ ├── repository │ │ │ │ ├── repository.go │ │ │ │ ├── repository_mongo.go │ │ │ │ └── repository_sql.go │ │ │ └── usecase │ │ │ │ ├── usecase.go │ │ │ │ └── usecase_impl.go │ │ └── event │ │ │ ├── delivery │ │ │ ├── graphqlhandler │ │ │ │ ├── field_resolver.go │ │ │ │ ├── mutation_resolver.go │ │ │ │ ├── query_resolver.go │ │ │ │ ├── root_resolver.go │ │ │ │ └── subscription_resolver.go │ │ │ └── resthandler │ │ │ │ └── resthandler.go │ │ │ ├── domain │ │ │ ├── event.go │ │ │ └── profile.go │ │ │ ├── module.go │ │ │ ├── repository │ │ │ ├── repository.go │ │ │ ├── repository_mongo.go │ │ │ └── repository_sql.go │ │ │ └── usecase │ │ │ ├── usecase.go │ │ │ └── usecase_impl.go │ └── service.go ├── main.go └── pkg │ ├── helper │ ├── const.go │ └── helper.go │ └── shared │ ├── domain │ ├── chatbot.go │ └── event.go │ ├── environment.go │ ├── linebot-api │ ├── linebot.go │ └── linebot_http_impl.go │ ├── repository │ ├── repository.go │ └── repository_mongo.go │ ├── token_validator.go │ ├── translator │ ├── translator.go │ └── translator_http_impl.go │ └── usecase │ └── usecase.go ├── master-service ├── .env.sample ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── api │ ├── graphql │ │ ├── _common.graphql │ │ ├── _schema.graphql │ │ ├── acl.graphql │ │ └── apps.graphql │ ├── jsonschema │ │ └── schema.json │ └── proto │ │ ├── acl │ │ └── acl.proto │ │ └── apps │ │ └── apps.proto ├── candi.json ├── cmd │ └── migration │ │ ├── main.go │ │ └── mongo.go ├── configs │ └── configs.go ├── deployments │ └── k8s │ │ └── master-service.yaml ├── docs │ ├── .gitkeep │ └── Master-Service.postman_collection.json ├── internal │ ├── modules │ │ ├── acl │ │ │ ├── delivery │ │ │ │ ├── graphqlhandler │ │ │ │ │ ├── field_resolver.go │ │ │ │ │ ├── mutation_resolver.go │ │ │ │ │ ├── query_resolver.go │ │ │ │ │ ├── root_resolver.go │ │ │ │ │ └── subscription_resolver.go │ │ │ │ ├── grpchandler │ │ │ │ │ └── grpchandler.go │ │ │ │ └── resthandler │ │ │ │ │ └── resthandler.go │ │ │ ├── domain │ │ │ │ ├── filter.go │ │ │ │ └── payload.go │ │ │ ├── module.go │ │ │ ├── repository │ │ │ │ ├── repository.go │ │ │ │ ├── repository_acl.go │ │ │ │ └── repository_role.go │ │ │ └── usecase │ │ │ │ ├── usecase.go │ │ │ │ └── usecase_impl.go │ │ └── apps │ │ │ ├── delivery │ │ │ ├── graphqlhandler │ │ │ │ ├── field_resolver.go │ │ │ │ ├── mutation_resolver.go │ │ │ │ ├── query_resolver.go │ │ │ │ ├── root_resolver.go │ │ │ │ └── subscription_resolver.go │ │ │ ├── grpchandler │ │ │ │ └── grpchandler.go │ │ │ └── resthandler │ │ │ │ └── resthandler.go │ │ │ ├── domain │ │ │ ├── filter.go │ │ │ ├── payload.go │ │ │ └── response.go │ │ │ ├── module.go │ │ │ ├── repository │ │ │ ├── repository.go │ │ │ ├── repository_apps.go │ │ │ └── repository_permission.go │ │ │ └── usecase │ │ │ ├── usecase.go │ │ │ └── usecase_impl.go │ └── service.go ├── main.go └── pkg │ ├── helper │ └── helper.go │ └── shared │ ├── domain │ ├── acl.go │ ├── apps.go │ └── apps_test.go │ ├── environment.go │ ├── repository │ ├── repository.go │ └── repository_mongo.go │ ├── token_validator.go │ └── usecase │ ├── common │ └── common.go │ └── usecase.go ├── notification-service ├── .env.sample ├── .gitignore ├── Dockerfile ├── Makefile ├── api │ ├── graphql │ │ ├── _schema.graphql │ │ └── push-notif.graphql │ ├── jsonschema │ │ └── schema.json │ └── proto │ │ └── push-notif │ │ └── push-notif.proto ├── candi.json ├── configs │ └── configs.go ├── deployments │ └── k8s │ │ └── notification-service.yml ├── docs │ └── .gitkeep ├── internal │ ├── modules │ │ └── push-notif │ │ │ ├── delivery │ │ │ ├── graphqlhandler │ │ │ │ ├── field_resolver.go │ │ │ │ ├── mutation_resolver.go │ │ │ │ ├── query_resolver.go │ │ │ │ ├── root_resolver.go │ │ │ │ └── subscription_resolver.go │ │ │ ├── grpchandler │ │ │ │ └── grpchandler.go │ │ │ ├── resthandler │ │ │ │ └── resthandler.go │ │ │ └── workerhandler │ │ │ │ └── redis_handler.go │ │ │ ├── domain │ │ │ ├── domain.go │ │ │ ├── request_payload.go │ │ │ └── topic.go │ │ │ ├── module.go │ │ │ ├── repository │ │ │ ├── repository.go │ │ │ └── repository_mongo.go │ │ │ └── usecase │ │ │ ├── usecase.go │ │ │ ├── usecase_impl.go │ │ │ └── usecase_subscriber.go │ └── service.go ├── main.go └── pkg │ ├── helper │ └── helper.go │ └── shared │ ├── domain │ └── push-notif.go │ ├── environment.go │ ├── repository │ ├── repository.go │ └── repository_mongo.go │ ├── token_validator.go │ └── usecase │ └── usecase.go ├── order-service ├── .env.sample ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── api │ ├── graphql │ │ ├── _schema.graphql │ │ ├── master.graphql │ │ └── order.graphql │ ├── jsonschema │ │ └── schema.json │ └── proto │ │ ├── master │ │ └── master.proto │ │ └── order │ │ └── order.proto ├── candi.json ├── configs │ └── configs.go ├── deployments │ └── k8s │ │ └── order-service.yml ├── docs │ └── .gitkeep ├── internal │ ├── modules │ │ ├── master │ │ │ ├── delivery │ │ │ │ ├── graphqlhandler │ │ │ │ │ ├── mutation_resolver.go │ │ │ │ │ ├── query_resolver.go │ │ │ │ │ ├── root_resolver.go │ │ │ │ │ └── subscription_resolver.go │ │ │ │ └── grpchandler │ │ │ │ │ └── grpchandler.go │ │ │ ├── domain │ │ │ │ └── payload.go │ │ │ ├── module.go │ │ │ ├── repository │ │ │ │ ├── repository.go │ │ │ │ ├── repository_mongo.go │ │ │ │ └── repository_sql.go │ │ │ └── usecase │ │ │ │ ├── usecase.go │ │ │ │ └── usecase_impl.go │ │ └── order │ │ │ ├── delivery │ │ │ ├── graphqlhandler │ │ │ │ ├── mutation_resolver.go │ │ │ │ ├── query_resolver.go │ │ │ │ ├── root_resolver.go │ │ │ │ └── subscription_resolver.go │ │ │ └── grpchandler │ │ │ │ └── grpchandler.go │ │ │ ├── domain │ │ │ └── payload.go │ │ │ ├── module.go │ │ │ ├── repository │ │ │ ├── repository.go │ │ │ ├── repository_mongo.go │ │ │ └── repository_sql.go │ │ │ └── usecase │ │ │ ├── usecase.go │ │ │ └── usecase_impl.go │ └── service.go ├── main.go └── pkg │ ├── helper │ └── helper.go │ └── shared │ ├── domain │ ├── master.go │ └── order.go │ ├── environment.go │ ├── repository │ ├── repository.go │ ├── repository_mongo.go │ └── repository_sql.go │ ├── token_validator.go │ └── usecase │ └── usecase.go ├── storage-service ├── .env.sample ├── .gitignore ├── Dockerfile ├── Makefile ├── api │ ├── graphql │ │ ├── _schema.graphql │ │ └── storage.graphql │ ├── jsonschema │ │ └── schema.json │ └── proto │ │ └── storage │ │ └── storage.proto ├── candi.json ├── configs │ └── configs.go ├── deployments │ └── k8s │ │ └── storage-service.yml ├── docs │ └── .gitkeep ├── internal │ ├── modules │ │ └── storage │ │ │ ├── delivery │ │ │ └── grpchandler │ │ │ │ └── grpchandler.go │ │ │ ├── domain │ │ │ ├── domain.go │ │ │ └── payload.go │ │ │ ├── module.go │ │ │ ├── repository │ │ │ ├── repository.go │ │ │ └── repository_mongo.go │ │ │ └── usecase │ │ │ ├── usecase.go │ │ │ └── usecase_impl.go │ └── service.go ├── main.go └── pkg │ ├── helper │ └── helper.go │ └── shared │ ├── domain │ └── storage.go │ ├── environment.go │ ├── repository │ ├── repository.go │ └── repository_mongo.go │ ├── token_validator.go │ └── usecase │ └── usecase.go └── user-service ├── .env.sample ├── .gitignore ├── Dockerfile ├── Makefile ├── api ├── graphql │ ├── _common.graphql │ ├── _schema.graphql │ ├── auth.graphql │ └── member.graphql ├── jsonschema │ └── schema.json └── proto │ ├── auth │ └── auth.proto │ └── member │ └── member.proto ├── candi.json ├── cmd └── migration │ └── main.go ├── configs └── configs.go ├── deployments └── k8s │ └── user-service.yml ├── docs ├── .gitkeep └── User-Service.postman_collection.json ├── internal ├── modules │ ├── auth │ │ ├── delivery │ │ │ ├── graphqlhandler │ │ │ │ ├── mutation_resolver.go │ │ │ │ ├── query_resolver.go │ │ │ │ ├── root_resolver.go │ │ │ │ └── subscription_resolver.go │ │ │ └── resthandler │ │ │ │ └── resthandler.go │ │ ├── domain │ │ │ └── payload.go │ │ ├── module.go │ │ ├── repository │ │ │ ├── repository.go │ │ │ ├── repository_mongo.go │ │ │ └── repository_sql.go │ │ └── usecase │ │ │ ├── usecase.go │ │ │ └── usecase_impl.go │ └── member │ │ ├── delivery │ │ ├── graphqlhandler │ │ │ ├── field_serializer_resolver.go │ │ │ ├── mutation_resolver.go │ │ │ ├── query_resolver.go │ │ │ ├── root_resolver.go │ │ │ └── subscription_resolver.go │ │ ├── grpchandler │ │ │ └── grpchandler.go │ │ ├── resthandler │ │ │ └── resthandler.go │ │ └── workerhandler │ │ │ ├── cron_handler.go │ │ │ ├── kafka_handler.go │ │ │ └── taskqueue_handler.go │ │ ├── domain │ │ └── payload.go │ │ ├── module.go │ │ ├── repository │ │ ├── repository.go │ │ ├── repository_mongo.go │ │ └── repository_sql.go │ │ └── usecase │ │ ├── usecase.go │ │ └── usecase_impl.go └── service.go ├── main.go └── pkg ├── helper ├── helper.go └── password.go └── shared ├── domain ├── auth.go └── member.go ├── environment.go ├── repository ├── repository.go ├── repository_mongo.go └── repository_sql.go ├── token_validator.go └── usecase └── usecase.go /.dockerignore: -------------------------------------------------------------------------------- 1 | vendor/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | backend-microservices 3 | bin 4 | .env 5 | main_service.go 6 | *.pem 7 | *.key 8 | .vscode/ 9 | __debug_bin 10 | coverage.txt 11 | mocks/ 12 | .DS_Store 13 | .scannerwork/ 14 | example/ 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1 2 | FROM golang:1.16.4-alpine3.13 AS dependency_builder 3 | 4 | WORKDIR /go/src 5 | ENV GO111MODULE=on 6 | 7 | RUN apk update 8 | RUN apk add --no-cache bash ca-certificates git 9 | 10 | COPY go.mod . 11 | COPY go.sum . 12 | 13 | RUN go mod download 14 | 15 | # Stage 2 16 | FROM dependency_builder AS service_builder 17 | 18 | ARG SERVICE_NAME 19 | WORKDIR /usr/app 20 | 21 | COPY sdk sdk 22 | COPY services/$SERVICE_NAME services/$SERVICE_NAME 23 | COPY go.mod . 24 | COPY go.sum . 25 | RUN CGO_ENABLED=0 GOOS=linux go build -o bin services/$SERVICE_NAME/*.go 26 | 27 | # Stage 3 28 | FROM alpine:latest 29 | 30 | ARG SERVICE_NAME 31 | ARG BUILD_NUMBER 32 | RUN apk --no-cache add ca-certificates tzdata 33 | RUN cp /usr/share/zoneinfo/Asia/Jakarta /etc/localtime 34 | WORKDIR /root/ 35 | ENV WORKDIR=services/$SERVICE_NAME/ 36 | ENV BUILD_NUMBER=$BUILD_NUMBER 37 | 38 | RUN mkdir -p /root/services/$SERVICE_NAME 39 | RUN mkdir -p /root/services/$SERVICE_NAME/api 40 | RUN mkdir -p /root/services/$SERVICE_NAME/api/configs 41 | COPY --from=service_builder /usr/app/bin bin 42 | COPY --from=service_builder /usr/app/services/$SERVICE_NAME/.env /root/services/$SERVICE_NAME/.env 43 | COPY --from=service_builder /usr/app/services/$SERVICE_NAME/api /root/services/$SERVICE_NAME/api 44 | 45 | ENTRYPOINT ["./bin"] 46 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | master-service: 4 | network_mode: host 5 | image: "agungdp22/master-service:latest" 6 | volumes: 7 | - ${PWD}/services/master-service/.env:/root/services/master-service/.env 8 | 9 | auth-service: 10 | network_mode: host 11 | image: "agungdp22/auth-service:latest" 12 | volumes: 13 | - ${PWD}/services/auth-service/.env:/root/services/auth-service/.env 14 | 15 | user-service: 16 | network_mode: host 17 | image: "agungdp22/user-service:latest" 18 | volumes: 19 | - ${PWD}/services/user-service/.env:/root/services/user-service/.env 20 | 21 | storage-service: 22 | network_mode: host 23 | image: "agungdp22/storage-service:latest" 24 | volumes: 25 | - ${PWD}/services/storage-service/.env:/root/services/storage-service/.env 26 | 27 | line-chatbot: 28 | network_mode: host 29 | image: "agungdp22/line-chatbot:latest" 30 | volumes: 31 | - ${PWD}/services/line-chatbot/.env:/root/services/line-chatbot/.env 32 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module monorepo 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/golang/protobuf v1.5.0 8 | github.com/google/uuid v1.2.0 9 | github.com/labstack/echo v3.3.10+incompatible 10 | github.com/lib/pq v1.10.1 11 | github.com/line/line-bot-sdk-go v7.8.0+incompatible 12 | github.com/minio/minio-go/v6 v6.0.57 13 | github.com/stretchr/testify v1.7.0 14 | go.mongodb.org/mongo-driver v1.5.2 15 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a 16 | google.golang.org/grpc v1.37.0 17 | google.golang.org/protobuf v1.26.0 18 | gorm.io/driver/postgres v1.1.0 19 | gorm.io/gorm v1.21.10 20 | pkg.agungdp.dev/candi v1.5.32 21 | ) 22 | -------------------------------------------------------------------------------- /scripts/clear_none_image.sh: -------------------------------------------------------------------------------- 1 | 2 | docker rmi -f $(docker images | grep "none" | awk '{print $3}') -------------------------------------------------------------------------------- /scripts/generate_rsa_key.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PASS=${1} 4 | openssl genrsa -passout pass:$PASS -out private.key 1024 5 | openssl rsa -in private.key -outform PEM -passin pass:$PASS -pubout -out public.pem 6 | -------------------------------------------------------------------------------- /scripts/install_protoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PROTOC_ZIP=protoc-3.7.1-linux-x86_64.zip 4 | curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.7.1/$PROTOC_ZIP 5 | unzip -o $PROTOC_ZIP -d /usr/local bin/protoc 6 | unzip -o $PROTOC_ZIP -d /usr/local 'include/*' 7 | rm -f $PROTOC_ZIP -------------------------------------------------------------------------------- /sdk/auth-service/auth_service.go: -------------------------------------------------------------------------------- 1 | package authservice 2 | 3 | import ( 4 | "context" 5 | 6 | "pkg.agungdp.dev/candi/candishared" 7 | ) 8 | 9 | // AuthService abstract interface 10 | type AuthService interface { 11 | ValidateToken(ctx context.Context, token string) (*candishared.TokenClaim, error) 12 | GenerateToken(ctx context.Context, req PayloadGenerateToken) (resp *ResponseGenerateToken, err error) 13 | } 14 | -------------------------------------------------------------------------------- /sdk/auth-service/types.go: -------------------------------------------------------------------------------- 1 | package authservice 2 | 3 | import "github.com/dgrijalva/jwt-go" 4 | 5 | // PayloadGenerateToken payload 6 | type PayloadGenerateToken struct { 7 | UserID string 8 | Username string 9 | } 10 | 11 | // ResponseClaim for token claim data 12 | type ResponseClaim struct { 13 | jwt.StandardClaims 14 | DeviceID string `json:"did"` 15 | User struct { 16 | ID string `json:"id"` 17 | Username string `json:"username"` 18 | } `json:"user"` 19 | Alg string `json:"-"` 20 | } 21 | 22 | // ResponseGenerateToken model 23 | type ResponseGenerateToken struct { 24 | Token string `json:"token"` 25 | RefreshToken string `json:"refresh_token"` 26 | Claim ResponseClaim `json:"claim"` 27 | } 28 | -------------------------------------------------------------------------------- /sdk/master-service/master_service.go: -------------------------------------------------------------------------------- 1 | package masterservice 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // MasterService abstract interface 8 | type MasterService interface { 9 | CheckPermission(ctx context.Context, userID string, permissionCode string) (role string, err error) 10 | GetUserApps(ctx context.Context, userID string) (userApps []UserApps, err error) 11 | } 12 | -------------------------------------------------------------------------------- /sdk/master-service/types.go: -------------------------------------------------------------------------------- 1 | package masterservice 2 | 3 | // PayloadCheckPermission payload 4 | type PayloadCheckPermission struct { 5 | UserID string 6 | PermissionCode string 7 | } 8 | 9 | // UserApps response data 10 | type UserApps struct { 11 | ID string `json:"id"` 12 | Code string `json:"code"` 13 | Name string `json:"name"` 14 | Icon string `json:"icon"` 15 | FrontendURL string `json:"frontendUrl"` 16 | BackendURL string `json:"backendUrl"` 17 | Role struct { 18 | ID string `json:"id"` 19 | Code string `json:"code"` 20 | Name string `json:"name"` 21 | } `json:"role"` 22 | } 23 | -------------------------------------------------------------------------------- /sdk/storage-service/README.md: -------------------------------------------------------------------------------- 1 | # Storage Service GRPC Client 2 | 3 | Example (using local file) 4 | ```go 5 | package main 6 | 7 | import ( 8 | "context" 9 | "os" 10 | 11 | storageservice "monorepo/sdk/storage-service" 12 | 13 | "pkg.agungdp.dev/candi/candihelper" 14 | ) 15 | 16 | func main() { 17 | 18 | file, err := os.Open("[path-to-your-file]") 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | fileInfo, err := file.Stat() 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | storageService, _ := storageservice.NewStorageServiceGRPC("[storage-service host]", "Basic xxxxx", 50*candihelper.MByte) 29 | storageService.Upload(context.Background(), file, storageservice.Header{ 30 | Size: fileInfo.Size(), 31 | Folder: "", Filename: "file.ext", 32 | }) 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /sdk/storage-service/storage_service.go: -------------------------------------------------------------------------------- 1 | package storageservice 2 | 3 | import ( 4 | "context" 5 | "io" 6 | ) 7 | 8 | // StorageService abstraction 9 | type StorageService interface { 10 | Upload(ctx context.Context, file io.Reader, metadata Header) (Response, error) 11 | } 12 | -------------------------------------------------------------------------------- /sdk/storage-service/types.go: -------------------------------------------------------------------------------- 1 | package storageservice 2 | 3 | // Header model 4 | type Header struct { 5 | ContentType string 6 | Folder string 7 | Filename string 8 | Size int64 9 | } 10 | 11 | // Response model 12 | type Response struct { 13 | Location string `json:"file"` 14 | Size int64 `json:"size"` 15 | } 16 | -------------------------------------------------------------------------------- /sdk/user-service/types.go: -------------------------------------------------------------------------------- 1 | package userservice 2 | 3 | // Member model 4 | type Member struct { 5 | ID string `json:"id"` 6 | Username string `json:"username"` 7 | Password string `json:"password"` 8 | Fullname string `json:"fullname"` 9 | CreatedAt string `json:"createdAt"` 10 | ModifiedAt string `json:"modifiedAt"` 11 | } 12 | -------------------------------------------------------------------------------- /sdk/user-service/user-service.go: -------------------------------------------------------------------------------- 1 | package userservice 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // UserService abstract interface 8 | type UserService interface { 9 | GetMember(ctx context.Context, id string) (Member, error) 10 | } 11 | -------------------------------------------------------------------------------- /services/auth-service/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | vendor 3 | main_service.go 4 | auth-service 5 | coverage.txt 6 | *.rsa 7 | *.rsa.pub 8 | -------------------------------------------------------------------------------- /services/auth-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1 2 | FROM golang:1.14.9-alpine3.12 AS dependency_builder 3 | 4 | WORKDIR /go/src 5 | ENV GO111MODULE=on 6 | 7 | RUN apk update 8 | RUN apk add --no-cache bash ca-certificates git make 9 | 10 | COPY go.mod . 11 | COPY go.sum . 12 | 13 | RUN go mod download 14 | 15 | # Stage 2 16 | FROM dependency_builder AS service_builder 17 | 18 | WORKDIR /usr/app 19 | 20 | COPY . . 21 | RUN CGO_ENABLED=0 GOOS=linux go build -ldflags '-w -s' -a -o bin 22 | 23 | # Stage 3 24 | FROM alpine:latest 25 | 26 | RUN apk --no-cache add ca-certificates tzdata 27 | WORKDIR /root/ 28 | 29 | RUN mkdir -p /root/api 30 | RUN mkdir -p /root/cmd/auth-service 31 | RUN mkdir -p /root/config/key 32 | COPY --from=service_builder /usr/app/bin bin 33 | COPY --from=service_builder /usr/app/.env .env 34 | COPY --from=service_builder /usr/app/api /root/api 35 | 36 | ENTRYPOINT ["./bin"] 37 | -------------------------------------------------------------------------------- /services/auth-service/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : build run 2 | 3 | build: 4 | go build -o bin 5 | 6 | run: build 7 | ./bin 8 | 9 | proto: 10 | $(foreach proto_file, $(shell find api/proto -name '*.proto'),\ 11 | protoc --proto_path=api/proto --go_out=plugins=grpc:api/proto \ 12 | --go_opt=paths=source_relative $(proto_file);) 13 | 14 | docker: 15 | docker build -t auth-service:latest . 16 | 17 | run-container: 18 | docker run --name=auth-service --network="host" -d auth-service 19 | 20 | # unit test & calculate code coverage 21 | test: 22 | @if [ -f coverage.txt ]; then rm coverage.txt; fi; 23 | @echo ">> running unit test and calculate coverage" 24 | @go test ./... -cover -coverprofile=coverage.txt -covermode=count -coverpkg=$(PACKAGES) 25 | @go tool cover -func=coverage.txt 26 | 27 | clear: 28 | rm bin auth-service 29 | -------------------------------------------------------------------------------- /services/auth-service/api/graphql/_common.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.8.17. 2 | 3 | input FilterListInputResolver { 4 | limit: Int 5 | page: Int 6 | "Optional (asc desc)" 7 | sort: FilterSortEnum 8 | "Optional" 9 | order_by: String 10 | "Optional" 11 | show_all: Boolean 12 | "Optional" 13 | search: String 14 | } 15 | 16 | type MetaResolver { 17 | page: Int! 18 | limit: Int! 19 | total_records: Int! 20 | total_pages: Int! 21 | } 22 | 23 | enum FilterSortEnum { 24 | asc 25 | desc 26 | } 27 | -------------------------------------------------------------------------------- /services/auth-service/api/graphql/_schema.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.8.17. 2 | 3 | schema { 4 | query: Query 5 | mutation: Mutation 6 | } 7 | 8 | type Query { 9 | token: TokenQueryResolver 10 | } 11 | 12 | type Mutation { 13 | token: TokenMutationResolver 14 | } 15 | -------------------------------------------------------------------------------- /services/auth-service/api/graphql/token.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.8.17. 2 | 3 | # TokenModule Resolver Area 4 | type TokenQueryResolver { 5 | get_detail_token(id: String!): TokenResolver! 6 | } 7 | 8 | type TokenMutationResolver { 9 | refresh_token(token: String!, refresh_token: String!): TokenResolver! 10 | } 11 | 12 | type TokenResolver { 13 | token: String! 14 | refresh_token: String! 15 | } 16 | -------------------------------------------------------------------------------- /services/auth-service/api/jsonschema/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "example", 4 | "title": "json schema type", 5 | "type": "object", 6 | "properties": {} 7 | } 8 | -------------------------------------------------------------------------------- /services/auth-service/api/proto/token/token.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | package token; 3 | option go_package = "monorepo/services/auth-service/api/proto/token"; 4 | 5 | service TokenHandler { 6 | rpc ValidateToken (PayloadValidate) returns (ResponseValidation); 7 | rpc GenerateToken (UserData) returns (ResponseGenerate); 8 | } 9 | 10 | message Request { 11 | string Message=1; 12 | } 13 | 14 | message Response { 15 | string Message=1; 16 | } 17 | 18 | // Validate token payload 19 | message PayloadValidate { 20 | string Token = 1; 21 | } 22 | 23 | message ResponseValidation { 24 | bool Success = 1; 25 | message ClaimData { 26 | string Audience = 1; 27 | int64 ExpiresAt = 2; 28 | int64 IssuedAt = 3; 29 | string Issuer = 4; 30 | int64 NotBefore = 5; 31 | string Subject = 6; 32 | string DeviceID = 7; 33 | UserData User = 8; 34 | } 35 | ClaimData Claim = 2; 36 | } 37 | 38 | message ResponseGenerate { 39 | bool Success = 1; 40 | 41 | message Token { 42 | string Token = 1; 43 | string RefreshToken = 2; 44 | ClaimData Claim = 3; 45 | } 46 | 47 | Token Data = 2; 48 | } 49 | 50 | message ClaimData { 51 | string Audience = 1; 52 | int64 ExpiresAt = 2; 53 | int64 IssuedAt = 3; 54 | string Issuer = 4; 55 | int64 NotBefore = 5; 56 | string Subject = 6; 57 | string DeviceID = 7; 58 | UserData User = 8; 59 | } 60 | 61 | message UserData { 62 | string ID = 1; 63 | string Username = 2; 64 | } 65 | -------------------------------------------------------------------------------- /services/auth-service/candi.json: -------------------------------------------------------------------------------- 1 | {"Version":"v1.4.0","Header":"Code generated by candi v1.4.0.","LibraryName":"pkg.agungdp.dev/candi","ServiceName":"auth-service","PackagePrefix":"monorepo/services/auth-service","ProtoSource":"monorepo/sdk/auth-service/proto","RestHandler":false,"GRPCHandler":true,"GraphQLHandler":false,"KafkaHandler":false,"SchedulerHandler":false,"RedisSubsHandler":true,"TaskQueueHandler":false,"IsWorkerActive":true,"RedisDeps":true,"SQLDeps":false,"MongoDeps":true,"SQLUseGORM":false,"SQLDriver":"","Modules":[{"ModuleName":"token"}]} -------------------------------------------------------------------------------- /services/auth-service/configs/rsa/key.go: -------------------------------------------------------------------------------- 1 | package rsa 2 | 3 | import ( 4 | "crypto/rsa" 5 | _ "embed" 6 | 7 | "github.com/dgrijalva/jwt-go" 8 | "pkg.agungdp.dev/candi/codebase/interfaces" 9 | ) 10 | 11 | var ( 12 | //go:embed public.pem 13 | verifyBytes []byte 14 | 15 | //go:embed private.key 16 | signBytes []byte 17 | ) 18 | 19 | type rsaKey struct { 20 | publicKey *rsa.PublicKey 21 | privateKey *rsa.PrivateKey 22 | } 23 | 24 | // InitKey rsa 25 | func InitKey() interfaces.RSAKey { 26 | publicKey, err := jwt.ParseRSAPublicKeyFromPEM(verifyBytes) 27 | if err != nil { 28 | panic("missing rsa public key file, make sure you are running `generate_rsa_key` script and put the file in configs/rsa directory") 29 | } 30 | privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(signBytes) 31 | if err != nil { 32 | panic("missing rsa private key file, make sure you are running `generate_rsa_key` script and put the file in configs/rsa directory") 33 | } 34 | 35 | verifyBytes, signBytes = nil, nil 36 | return &rsaKey{ 37 | publicKey: publicKey, 38 | privateKey: privateKey, 39 | } 40 | } 41 | 42 | func (r rsaKey) PrivateKey() *rsa.PrivateKey { 43 | return r.privateKey 44 | } 45 | 46 | func (r rsaKey) PublicKey() *rsa.PublicKey { 47 | return r.publicKey 48 | } 49 | -------------------------------------------------------------------------------- /services/auth-service/deployments/k8s/dev-auth-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: dev-auth-service 5 | labels: 6 | app: dev-auth-service 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: dev-auth-service 12 | template: 13 | metadata: 14 | labels: 15 | app: dev-auth-service 16 | spec: 17 | containers: 18 | - name: dev-auth-service 19 | image: gcr.io/agungdp-218613/auth-service:latest 20 | ports: 21 | - containerPort: 8002 22 | imagePullSecrets: 23 | - name: gcr-json-key 24 | --- 25 | apiVersion: v1 26 | kind: Service 27 | metadata: 28 | name: dev-auth-service 29 | spec: 30 | type: LoadBalancer 31 | ports: 32 | - name: tcp-listener 33 | targetPort: 8002 34 | port: 80 35 | protocol: TCP 36 | selector: 37 | app: dev-auth-service 38 | 39 | -------------------------------------------------------------------------------- /services/auth-service/docs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungdwiprasetyo/backend-microservices/c216a6afdb64cc057c494299b54bd8ac9378ef2e/services/auth-service/docs/.gitkeep -------------------------------------------------------------------------------- /services/auth-service/internal/modules/token/delivery/graphqlhandler/mutation_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.17. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/auth-service/internal/modules/token/domain" 8 | 9 | "pkg.agungdp.dev/candi/tracer" 10 | ) 11 | 12 | type mutationResolver struct { 13 | root *GraphQLHandler 14 | } 15 | 16 | // RefreshToken resolver 17 | func (m *mutationResolver) RefreshToken(ctx context.Context, input struct{ Token, RefreshToken string }) (res domain.ResponseGenerateToken, err error) { 18 | trace := tracer.StartTrace(ctx, "TokenDeliveryGraphQL:RefreshToken") 19 | defer trace.Finish() 20 | ctx = trace.Context() 21 | 22 | // tokenClaim := candishared.ParseTokenClaimFromContext(ctx) // must using GraphQLBearerAuth in middleware for this resolver 23 | 24 | return m.root.uc.Refresh(ctx, input.Token, input.RefreshToken) 25 | } 26 | -------------------------------------------------------------------------------- /services/auth-service/internal/modules/token/delivery/graphqlhandler/query_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.17. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | 8 | shareddomain "monorepo/services/auth-service/pkg/shared/domain" 9 | 10 | "pkg.agungdp.dev/candi/tracer" 11 | ) 12 | 13 | type queryResolver struct { 14 | root *GraphQLHandler 15 | } 16 | 17 | // GetDetailToken resolver 18 | func (q *queryResolver) GetDetailToken(ctx context.Context, input struct{ ID string }) (data shareddomain.Token, err error) { 19 | trace := tracer.StartTrace(ctx, "TokenDeliveryGraphQL:GetDetailToken") 20 | defer trace.Finish() 21 | ctx = trace.Context() 22 | 23 | // tokenClaim := candishared.ParseTokenClaimFromContext(ctx) // must using GraphQLBearerAuth in middleware for this resolver 24 | 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /services/auth-service/internal/modules/token/delivery/graphqlhandler/root_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.17. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "monorepo/services/auth-service/internal/modules/token/usecase" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/types" 9 | "pkg.agungdp.dev/candi/codebase/interfaces" 10 | ) 11 | 12 | // GraphQLHandler model 13 | type GraphQLHandler struct { 14 | mw interfaces.Middleware 15 | uc usecase.TokenUsecase 16 | validator interfaces.Validator 17 | } 18 | 19 | // NewGraphQLHandler delivery 20 | func NewGraphQLHandler(mw interfaces.Middleware, uc usecase.TokenUsecase, validator interfaces.Validator) *GraphQLHandler { 21 | return &GraphQLHandler{ 22 | mw: mw, uc: uc, validator: validator, 23 | } 24 | } 25 | 26 | // RegisterMiddleware register resolver based on schema in "api/graphql/*" path 27 | func (h *GraphQLHandler) RegisterMiddleware(mwGroup *types.MiddlewareGroup) { 28 | mwGroup.Add("TokenQueryResolver.get_all_token", h.mw.GraphQLBearerAuth, h.mw.GraphQLPermissionACL("resource.public")) 29 | mwGroup.Add("TokenQueryResolver.get_detail_token", h.mw.GraphQLBearerAuth) 30 | mwGroup.Add("TokenMutationResolver.save_token", h.mw.GraphQLBearerAuth, h.mw.GraphQLPermissionACL("resource.public")) 31 | mwGroup.Add("TokenMutationResolver.delete_token", h.mw.GraphQLBearerAuth, h.mw.GraphQLPermissionACL("resource.public")) 32 | } 33 | 34 | // Query method 35 | func (h *GraphQLHandler) Query() interface{} { 36 | return &queryResolver{root: h} 37 | } 38 | 39 | // Mutation method 40 | func (h *GraphQLHandler) Mutation() interface{} { 41 | return &mutationResolver{root: h} 42 | } 43 | 44 | // Subscription method 45 | func (h *GraphQLHandler) Subscription() interface{} { 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /services/auth-service/internal/modules/token/delivery/workerhandler/redis_handler.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.17. 2 | 3 | package workerhandler 4 | 5 | import ( 6 | "context" 7 | "encoding/json" 8 | 9 | "monorepo/services/auth-service/internal/modules/token/domain" 10 | "monorepo/services/auth-service/internal/modules/token/usecase" 11 | 12 | "pkg.agungdp.dev/candi/codebase/factory/types" 13 | "pkg.agungdp.dev/candi/codebase/interfaces" 14 | "pkg.agungdp.dev/candi/tracer" 15 | ) 16 | 17 | // RedisHandler struct 18 | type RedisHandler struct { 19 | uc usecase.TokenUsecase 20 | validator interfaces.Validator 21 | } 22 | 23 | // NewRedisHandler constructor 24 | func NewRedisHandler(uc usecase.TokenUsecase, validator interfaces.Validator) *RedisHandler { 25 | return &RedisHandler{ 26 | uc: uc, 27 | validator: validator, 28 | } 29 | } 30 | 31 | // MountHandlers mount handler group 32 | func (h *RedisHandler) MountHandlers(group *types.WorkerHandlerGroup) { 33 | group.Add(domain.RedisTokenExpiredKeyConst, h.handleTokenExpired) 34 | } 35 | 36 | func (h *RedisHandler) handleTokenExpired(ctx context.Context, message []byte) error { 37 | trace := tracer.StartTrace(ctx, "TokenDeliveryRedis:HandleTokenExpired") 38 | defer trace.Finish() 39 | ctx = trace.Context() 40 | 41 | var data domain.RedisTokenExpiredKey 42 | json.Unmarshal(message, &data) 43 | return h.uc.RevokeByKey(ctx, data.DeviceID, data.UserID) 44 | } 45 | -------------------------------------------------------------------------------- /services/auth-service/internal/modules/token/domain/claim.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "github.com/dgrijalva/jwt-go" 4 | 5 | const ( 6 | 7 | // HS256 const 8 | HS256 = "HS256" 9 | 10 | // RS256 const 11 | RS256 = "RS256" 12 | ) 13 | 14 | // Claim for token claim data 15 | type Claim struct { 16 | jwt.StandardClaims 17 | DeviceID string `json:"did"` 18 | User struct { 19 | ID string `json:"id"` 20 | Username string `json:"username"` 21 | } `json:"user"` 22 | Alg string `json:"-"` 23 | } 24 | -------------------------------------------------------------------------------- /services/auth-service/internal/modules/token/domain/payload.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | const ( 4 | // RedisTokenExpiredKeyConst const 5 | RedisTokenExpiredKeyConst = "expiredtoken" 6 | ) 7 | 8 | // RedisTokenExpiredKey for redis pubsub model 9 | type RedisTokenExpiredKey struct { 10 | DeviceID string `json:"deviceid"` 11 | UserID string `json:"userid"` 12 | } 13 | 14 | // ResponseGenerateToken model 15 | type ResponseGenerateToken struct { 16 | Token string `json:"token"` 17 | RefreshToken string `json:"refresh_token"` 18 | Claim map[string]interface{} `json:"claim"` 19 | } 20 | -------------------------------------------------------------------------------- /services/auth-service/internal/modules/token/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.17. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | 8 | shareddomain "monorepo/services/auth-service/pkg/shared/domain" 9 | 10 | "pkg.agungdp.dev/candi/candishared" 11 | ) 12 | 13 | // TokenRepository abstract interface 14 | type TokenRepository interface { 15 | FetchAll(ctx context.Context, filter *candishared.Filter) ([]shareddomain.Token, error) 16 | Count(ctx context.Context, filter *candishared.Filter) int 17 | Find(ctx context.Context, data *shareddomain.Token) error 18 | Save(ctx context.Context, data *shareddomain.Token) error 19 | } 20 | -------------------------------------------------------------------------------- /services/auth-service/internal/modules/token/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.17. 2 | 3 | package usecase 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/auth-service/internal/modules/token/domain" 8 | ) 9 | 10 | // TokenUsecase abstraction 11 | type TokenUsecase interface { 12 | Generate(ctx context.Context, payload *domain.Claim) (resp domain.ResponseGenerateToken, err error) 13 | Refresh(ctx context.Context, token, refreshToken string) (resp domain.ResponseGenerateToken, err error) 14 | Validate(ctx context.Context, tokenString string) (claim *domain.Claim, err error) 15 | Revoke(ctx context.Context, token string) error 16 | RevokeByKey(ctx context.Context, deviceID, userID string) error 17 | } 18 | -------------------------------------------------------------------------------- /services/auth-service/main.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "runtime/debug" 8 | 9 | "pkg.agungdp.dev/candi/codebase/app" 10 | "pkg.agungdp.dev/candi/config" 11 | 12 | service "monorepo/services/auth-service/internal" 13 | ) 14 | 15 | const serviceName = "auth-service" 16 | 17 | func main() { 18 | defer func() { 19 | if r := recover(); r != nil { 20 | fmt.Printf("\x1b[31;1mFailed to start %s service: %v\x1b[0m\n", serviceName, r) 21 | fmt.Printf("Stack trace: \n%s\n", debug.Stack()) 22 | } 23 | }() 24 | 25 | cfg := config.Init(serviceName) 26 | defer cfg.Exit() 27 | 28 | srv := service.NewService(serviceName, cfg) 29 | app.New(srv).Run() 30 | } 31 | -------------------------------------------------------------------------------- /services/auth-service/pkg/helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper -------------------------------------------------------------------------------- /services/auth-service/pkg/shared/domain/token.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "time" 4 | 5 | // Token model 6 | type Token struct { 7 | ID string `bson:"_id" json:"id"` 8 | UserID string `bson:"user_id" json:"user_id"` 9 | DeviceID string `bson:"device_id" json:"device_id"` 10 | Token string `bson:"token" json:"token"` 11 | RefreshToken string `bson:"refresh_token" json:"refresh_token"` 12 | IsActive *bool `bson:"is_active" json:"is_active"` 13 | Claims map[string]interface{} `bson:"claims" json:"claims"` 14 | ExpiredAt time.Time `bson:"expired_at" json:"expired_at"` 15 | CreatedAt time.Time `bson:"created_at" json:"created_at"` 16 | ModifiedAt time.Time `bson:"modified_at" json:"modified_at"` 17 | } 18 | 19 | // CollectionName for token model 20 | func (Token) CollectionName() string { 21 | return "tokens" 22 | } 23 | -------------------------------------------------------------------------------- /services/auth-service/pkg/shared/environment.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package shared 4 | 5 | import "time" 6 | 7 | // Environment additional in this service 8 | type Environment struct { 9 | // more additional environment with struct tag is environment key example: 10 | // ExampleHost string `env:"EXAMPLE_HOST"` 11 | JWTRefreshTokenAge time.Duration `env:"JWT_REFRESH_TOKEN_AGE"` 12 | JWTAccessTokenAge time.Duration `env:"JWT_ACCESS_TOKEN_AGE"` 13 | } 14 | 15 | var sharedEnv Environment 16 | 17 | // GetEnv get global additional environment 18 | func GetEnv() Environment { 19 | return sharedEnv 20 | } 21 | 22 | // SetEnv get global additional environment 23 | func SetEnv(env Environment) { 24 | sharedEnv = env 25 | } 26 | -------------------------------------------------------------------------------- /services/auth-service/pkg/shared/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. DO NOT EDIT. 2 | 3 | package repository 4 | 5 | import ( 6 | "sync" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 9 | ) 10 | 11 | var ( 12 | once sync.Once 13 | ) 14 | 15 | // SetSharedRepository set the global singleton "RepoSQL" and "RepoMongo" implementation 16 | func SetSharedRepository(deps dependency.Dependency) { 17 | once.Do(func() { 18 | // setSharedRepoSQL(deps.GetSQLDatabase().ReadDB(), deps.GetSQLDatabase().WriteDB()) 19 | setSharedRepoMongo(deps.GetMongoDatabase().ReadDB(), deps.GetMongoDatabase().WriteDB()) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /services/auth-service/pkg/shared/repository/repository_mongo.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. DO NOT EDIT. 2 | 3 | package repository 4 | 5 | import ( 6 | "go.mongodb.org/mongo-driver/mongo" 7 | tokenrepo "monorepo/services/auth-service/internal/modules/token/repository" 8 | ) 9 | 10 | // RepoMongo uow 11 | type RepoMongo struct { 12 | readDB, writeDB *mongo.Database 13 | 14 | // register all repository from modules 15 | TokenRepo tokenrepo.TokenRepository 16 | } 17 | 18 | var globalRepoMongo = new(RepoMongo) 19 | 20 | // setSharedRepoMongo set the global singleton "RepoMongo" implementation 21 | func setSharedRepoMongo(readDB, writeDB *mongo.Database) { 22 | globalRepoMongo = &RepoMongo{ 23 | readDB: readDB, writeDB: writeDB, 24 | TokenRepo: tokenrepo.NewTokenRepoMongo(readDB, writeDB), 25 | } 26 | } 27 | 28 | // GetSharedRepoMongo returns the global singleton "RepoMongo" implementation 29 | func GetSharedRepoMongo() *RepoMongo { 30 | return globalRepoMongo 31 | } 32 | -------------------------------------------------------------------------------- /services/auth-service/pkg/shared/token_validator.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package shared 4 | 5 | import ( 6 | "context" 7 | 8 | "pkg.agungdp.dev/candi/candishared" 9 | ) 10 | 11 | // DefaultTokenValidator for token validator 12 | type DefaultTokenValidator struct { 13 | } 14 | 15 | // ValidateToken implement TokenValidator 16 | func (v *DefaultTokenValidator) ValidateToken(ctx context.Context, token string) (*candishared.TokenClaim, error) { 17 | return &candishared.TokenClaim{}, nil 18 | } 19 | -------------------------------------------------------------------------------- /services/auth-service/pkg/shared/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package usecase 4 | 5 | import ( 6 | tokenusecase "monorepo/services/auth-service/internal/modules/token/usecase" 7 | "sync" 8 | 9 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 10 | ) 11 | 12 | type ( 13 | // Usecase unit of work for all usecase in modules 14 | Usecase interface { 15 | Token() tokenusecase.TokenUsecase 16 | } 17 | 18 | usecaseUow struct { 19 | token tokenusecase.TokenUsecase 20 | } 21 | ) 22 | 23 | var usecaseInst *usecaseUow 24 | var once sync.Once 25 | 26 | // SetSharedUsecase set singleton usecase unit of work instance 27 | func SetSharedUsecase(deps dependency.Dependency) { 28 | once.Do(func() { 29 | usecaseInst = &usecaseUow{ 30 | token: tokenusecase.NewTokenUsecase(deps), 31 | } 32 | }) 33 | } 34 | 35 | // GetSharedUsecase get usecase unit of work instance 36 | func GetSharedUsecase() Usecase { 37 | return usecaseInst 38 | } 39 | func (uc *usecaseUow) Token() tokenusecase.TokenUsecase { 40 | return uc.token 41 | } 42 | -------------------------------------------------------------------------------- /services/line-chatbot/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | vendor 3 | main_service.go 4 | line-chatbot 5 | coverage.txt 6 | -------------------------------------------------------------------------------- /services/line-chatbot/Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1 2 | FROM golang:1.14.9-alpine3.12 AS dependency_builder 3 | 4 | WORKDIR /go/src 5 | ENV GO111MODULE=on 6 | 7 | RUN apk update 8 | RUN apk add --no-cache bash ca-certificates git make 9 | 10 | COPY go.mod . 11 | COPY go.sum . 12 | 13 | RUN go mod download 14 | 15 | # Stage 2 16 | FROM dependency_builder AS service_builder 17 | 18 | WORKDIR /usr/app 19 | 20 | COPY . . 21 | RUN CGO_ENABLED=0 GOOS=linux go build -ldflags '-w -s' -a -o bin 22 | 23 | # Stage 3 24 | FROM alpine:latest 25 | 26 | RUN apk --no-cache add ca-certificates tzdata 27 | WORKDIR /root/ 28 | 29 | RUN mkdir -p /root/api 30 | RUN mkdir -p /root/cmd/line-chatbot 31 | RUN mkdir -p /root/config/key 32 | COPY --from=service_builder /usr/app/bin bin 33 | COPY --from=service_builder /usr/app/.env .env 34 | COPY --from=service_builder /usr/app/api /root/api 35 | 36 | ENTRYPOINT ["./bin"] 37 | -------------------------------------------------------------------------------- /services/line-chatbot/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : build run 2 | 3 | build: 4 | go build -o bin 5 | 6 | run: build 7 | ./bin 8 | 9 | proto: 10 | $(foreach proto_file, $(shell find api/proto -name '*.proto'),\ 11 | protoc --proto_path=api/proto --go_out=plugins=grpc:api/proto \ 12 | --go_opt=paths=source_relative $(proto_file);) 13 | 14 | docker: 15 | docker build -t line-chatbot:latest . 16 | 17 | run-container: 18 | docker run --name=line-chatbot --network="host" -d line-chatbot 19 | 20 | # unit test & calculate code coverage 21 | test: 22 | @if [ -f coverage.txt ]; then rm coverage.txt; fi; 23 | @echo ">> running unit test and calculate coverage" 24 | @go test ./... -cover -coverprofile=coverage.txt -covermode=count -coverpkg=$(PACKAGES) 25 | @go tool cover -func=coverage.txt 26 | 27 | clear: 28 | rm bin line-chatbot 29 | -------------------------------------------------------------------------------- /services/line-chatbot/api/graphql/_schema.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.3.1. 2 | 3 | schema { 4 | query: Query 5 | # mutation: Mutation 6 | # subscription: Subscription 7 | } 8 | 9 | type Query { 10 | event: EventQueryModule 11 | } 12 | 13 | # type Mutation { 14 | # event: EventMutationModule 15 | # } 16 | 17 | # type Subscription { 18 | # event: EventSubscriptionModule 19 | # } 20 | 21 | scalar Time 22 | scalar ByteArray 23 | scalar ObjectId 24 | 25 | type Meta { 26 | page: Int! 27 | limit: Int! 28 | totalRecords: Int! 29 | totalPages: Int! 30 | } 31 | 32 | input Filter { 33 | page: Int! 34 | limit: Int! 35 | sort: String! 36 | orderBy: String! 37 | } 38 | -------------------------------------------------------------------------------- /services/line-chatbot/api/graphql/chatbot.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.3.1. 2 | 3 | # ChatbotModule Module Area 4 | type ChatbotQueryModule { 5 | hello(): String! 6 | } 7 | 8 | type ChatbotMutationModule { 9 | hello(): String! 10 | } 11 | 12 | type ChatbotSubscriptionModule { 13 | hello(): String! 14 | } 15 | -------------------------------------------------------------------------------- /services/line-chatbot/api/graphql/event.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.3.1. 2 | 3 | ################### EventModule Module Area 4 | type EventQueryModule { 5 | getAll(filter: Filter): Events 6 | } 7 | 8 | # type EventMutationModule { 9 | # } 10 | 11 | # type EventSubscriptionModule { 12 | # } 13 | 14 | type Events { 15 | meta: Meta 16 | data: [Event]! 17 | } 18 | 19 | type Event { 20 | id: String! 21 | replyToken: String! 22 | type: String! 23 | timestamp: String! 24 | sourceId: String! 25 | sourceType: String! 26 | message: EventMessage 27 | error: String 28 | } 29 | 30 | type EventMessage { 31 | id: String! 32 | type: String! 33 | text: String! 34 | response: String! 35 | } 36 | -------------------------------------------------------------------------------- /services/line-chatbot/api/jsonschema/push-message.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "push-message", 4 | "title": "json schema type", 5 | "type": "object", 6 | "properties": { 7 | "to": { 8 | "type": "string", 9 | "minLength": 1 10 | }, 11 | "title": { 12 | "type": "string", 13 | "minLength": 1 14 | }, 15 | "message": { 16 | "type": "string", 17 | "minLength": 1 18 | } 19 | }, 20 | "required": ["to", "title", "message"] 21 | } 22 | -------------------------------------------------------------------------------- /services/line-chatbot/api/jsonschema/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "example", 4 | "title": "json schema type", 5 | "type": "object", 6 | "properties": {} 7 | } 8 | -------------------------------------------------------------------------------- /services/line-chatbot/api/proto/chatbot/chatbot.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | package chatbot; 3 | option go_package = "monorepo/services/line-chatbot/api/proto/chatbot"; 4 | 5 | service ChatbotHandler { 6 | rpc Hello(Request) returns (Response); 7 | } 8 | 9 | message Request { 10 | string Message=1; 11 | } 12 | 13 | message Response { 14 | string Message=1; 15 | } -------------------------------------------------------------------------------- /services/line-chatbot/api/proto/event/event.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | package event; 3 | option go_package = "monorepo/services/line-chatbot/api/proto/event"; 4 | 5 | service EventHandler { 6 | rpc Hello(Request) returns (Response); 7 | } 8 | 9 | message Request { 10 | string Message=1; 11 | } 12 | 13 | message Response { 14 | string Message=1; 15 | } -------------------------------------------------------------------------------- /services/line-chatbot/candi.json: -------------------------------------------------------------------------------- 1 | {"Version":"v1.4.0","Header":"Code generated by candi v1.4.0.","LibraryName":"pkg.agungdp.dev/candi","ServiceName":"line-chatbot","PackagePrefix":"monorepo/services/line-chatbot","ProtoSource":"monorepo/sdk/line-chatbot/proto","RestHandler":true,"GRPCHandler":false,"GraphQLHandler":true,"KafkaHandler":false,"SchedulerHandler":false,"RedisSubsHandler":false,"TaskQueueHandler":false,"IsWorkerActive":false,"RedisDeps":true,"SQLDeps":false,"MongoDeps":true,"SQLUseGORM":false,"SQLDriver":"","Modules":[{"ModuleName":"chatbot"},{"ModuleName":"event"}]} -------------------------------------------------------------------------------- /services/line-chatbot/deployments/k8s/dev-line-chatbot.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: dev-line-chatbot 5 | labels: 6 | app: dev-line-chatbot 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: dev-line-chatbot 12 | template: 13 | metadata: 14 | labels: 15 | app: dev-line-chatbot 16 | spec: 17 | containers: 18 | - name: dev-line-chatbot 19 | image: line-chatbot:latest 20 | imagePullPolicy: Never 21 | ports: 22 | - containerPort: 8888 23 | --- 24 | apiVersion: v1 25 | kind: Service 26 | metadata: 27 | name: dev-line-chatbot 28 | spec: 29 | ports: 30 | - name: http 31 | targetPort: 8888 32 | port: 80 33 | selector: 34 | app: dev-line-chatbot 35 | 36 | -------------------------------------------------------------------------------- /services/line-chatbot/docs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungdwiprasetyo/backend-microservices/c216a6afdb64cc057c494299b54bd8ac9378ef2e/services/line-chatbot/docs/.gitkeep -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/chatbot/delivery/graphqlhandler/mutation_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import "context" 6 | 7 | type mutationResolver struct { 8 | root *GraphQLHandler 9 | } 10 | 11 | // Hello resolver 12 | func (m *mutationResolver) Hello(ctx context.Context) (string, error) { 13 | return "Hello", nil 14 | } 15 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/chatbot/delivery/graphqlhandler/query_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | 8 | "pkg.agungdp.dev/candi/candishared" 9 | "pkg.agungdp.dev/candi/tracer" 10 | ) 11 | 12 | type queryResolver struct { 13 | root *GraphQLHandler 14 | } 15 | 16 | // Hello resolver 17 | func (q *queryResolver) Hello(ctx context.Context) (string, error) { 18 | trace := tracer.StartTrace(ctx, "DeliveryGraphQL-Hello") 19 | defer trace.Finish() 20 | ctx = trace.Context() 21 | 22 | tokenClaim := candishared.ParseTokenClaimFromContext(ctx) // must using GraphQLBearerAuth in middleware for this resolver 23 | 24 | return "hello, with your session (" + tokenClaim.Audience + ")", nil 25 | } 26 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/chatbot/delivery/graphqlhandler/root_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "monorepo/services/line-chatbot/internal/modules/chatbot/usecase" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/types" 9 | "pkg.agungdp.dev/candi/codebase/interfaces" 10 | ) 11 | 12 | // GraphQLHandler model 13 | type GraphQLHandler struct { 14 | mw interfaces.Middleware 15 | uc usecase.ChatbotUsecase 16 | validator interfaces.Validator 17 | } 18 | 19 | // NewGraphQLHandler delivery 20 | func NewGraphQLHandler(mw interfaces.Middleware, uc usecase.ChatbotUsecase, validator interfaces.Validator) *GraphQLHandler { 21 | return &GraphQLHandler{ 22 | mw: mw, uc: uc, validator: validator, 23 | } 24 | } 25 | 26 | // RegisterMiddleware register resolver based on schema in "api/graphql/*" path 27 | func (h *GraphQLHandler) RegisterMiddleware(mwGroup *types.MiddlewareGroup) { 28 | mwGroup.Add("ChatbotQueryModule.hello", h.mw.GraphQLBearerAuth) 29 | mwGroup.Add("ChatbotMutationModule.hello", h.mw.GraphQLBasicAuth) 30 | } 31 | 32 | // Query method 33 | func (h *GraphQLHandler) Query() interface{} { 34 | return &queryResolver{root: h} 35 | } 36 | 37 | // Mutation method 38 | func (h *GraphQLHandler) Mutation() interface{} { 39 | return &mutationResolver{root: h} 40 | } 41 | 42 | // Subscription method 43 | func (h *GraphQLHandler) Subscription() interface{} { 44 | return &subscriptionResolver{root: h} 45 | } 46 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/chatbot/delivery/graphqlhandler/subscription_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import "context" 6 | 7 | type subscriptionResolver struct { 8 | root *GraphQLHandler 9 | } 10 | 11 | // Hello resolver 12 | func (s *subscriptionResolver) Hello(ctx context.Context) <-chan string { 13 | output := make(chan string) 14 | 15 | go func() { 16 | output <- "Hello" 17 | }() 18 | 19 | return output 20 | } 21 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/chatbot/domain/domain.go: -------------------------------------------------------------------------------- 1 | package domain 2 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/chatbot/domain/payload.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | // PushMessagePayload model 4 | type PushMessagePayload struct { 5 | To string `json:"to"` 6 | Title string `json:"title"` 7 | Message string `json:"message"` 8 | } 9 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/chatbot/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | // ChatbotRepository abstract interface 10 | type ChatbotRepository interface { 11 | // add method 12 | FindHello(ctx context.Context) (string, error) 13 | } 14 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/chatbot/repository/repository_mongo.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | 8 | "go.mongodb.org/mongo-driver/mongo" 9 | 10 | "pkg.agungdp.dev/candi/tracer" 11 | ) 12 | 13 | type chatbotRepoMongo struct { 14 | readDB, writeDB *mongo.Database 15 | } 16 | 17 | // NewChatbotRepoMongo mongo repo constructor 18 | func NewChatbotRepoMongo(readDB, writeDB *mongo.Database) ChatbotRepository { 19 | return &chatbotRepoMongo{ 20 | readDB, writeDB, 21 | } 22 | } 23 | 24 | func (r *chatbotRepoMongo) FindHello(ctx context.Context) (string, error) { 25 | trace := tracer.StartTrace(ctx, "ChatbotRepoMongo:FindHello") 26 | defer trace.Finish() 27 | 28 | return "Hello from repo mongo layer", nil 29 | } 30 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/chatbot/repository/repository_sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | "database/sql" 8 | 9 | "pkg.agungdp.dev/candi/tracer" 10 | ) 11 | 12 | type chatbotRepoSQL struct { 13 | readDB, writeDB *sql.DB 14 | tx *sql.Tx 15 | } 16 | 17 | // NewChatbotRepoSQL mongo repo constructor 18 | func NewChatbotRepoSQL(readDB, writeDB *sql.DB, tx *sql.Tx) ChatbotRepository { 19 | return &chatbotRepoSQL{ 20 | readDB, writeDB, tx, 21 | } 22 | } 23 | 24 | func (r *chatbotRepoSQL) FindHello(ctx context.Context) (string, error) { 25 | trace := tracer.StartTrace(ctx, "ChatbotRepoSQL:FindHello") 26 | defer trace.Finish() 27 | 28 | return "Hello from repo sql layer", nil 29 | } 30 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/chatbot/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package usecase 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/line-chatbot/internal/modules/chatbot/domain" 8 | 9 | "github.com/line/line-bot-sdk-go/linebot" 10 | ) 11 | 12 | // ChatbotUsecase abstraction 13 | type ChatbotUsecase interface { 14 | ProcessCallback(ctx context.Context, events []*linebot.Event) error 15 | ReplyMessage(ctx context.Context, event *linebot.Event, messages ...string) error 16 | PushMessageToChannel(ctx context.Context, payload domain.PushMessagePayload) error 17 | } 18 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/event/delivery/graphqlhandler/field_resolver.go: -------------------------------------------------------------------------------- 1 | package graphqlhandler 2 | 3 | import ( 4 | "time" 5 | 6 | "monorepo/services/line-chatbot/internal/modules/event/domain" 7 | 8 | "pkg.agungdp.dev/candi/candihelper" 9 | "pkg.agungdp.dev/candi/candishared" 10 | ) 11 | 12 | type EventResolver struct { 13 | e domain.Event 14 | message EventMessage 15 | } 16 | 17 | func (r *EventResolver) ID() string { 18 | return r.e.ID.Hex() 19 | } 20 | func (r *EventResolver) ReplyToken() string { 21 | return r.e.ReplyToken 22 | } 23 | func (r *EventResolver) Type() string { 24 | return r.e.Type 25 | } 26 | func (r *EventResolver) Timestamp() string { 27 | return r.e.Timestamp.In(candihelper.AsiaJakartaLocalTime).Format(time.RFC3339) 28 | } 29 | func (r *EventResolver) SourceId() string { 30 | return r.e.SourceID 31 | } 32 | func (r *EventResolver) SourceType() string { 33 | return r.e.SourceType 34 | } 35 | func (r *EventResolver) Message() *EventMessage { 36 | return &r.message 37 | } 38 | func (r *EventResolver) Error() *string { 39 | return r.e.Error 40 | } 41 | 42 | type EventMessage struct { 43 | e domain.Event 44 | } 45 | 46 | func (r *EventMessage) ID() string { 47 | return r.e.Message.ID 48 | } 49 | func (r *EventMessage) Type() string { 50 | return r.e.Message.Type 51 | } 52 | func (r *EventMessage) Text() string { 53 | return r.e.Message.Text 54 | } 55 | func (r *EventMessage) Response() string { 56 | return r.e.Message.Response 57 | } 58 | 59 | type EventListResolver struct { 60 | m candishared.Meta 61 | events []*EventResolver 62 | } 63 | 64 | func (r *EventListResolver) Meta() *candishared.Meta { 65 | return &r.m 66 | } 67 | func (r *EventListResolver) Data() []*EventResolver { 68 | return r.events 69 | } 70 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/event/delivery/graphqlhandler/mutation_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import "context" 6 | 7 | type mutationResolver struct { 8 | root *GraphQLHandler 9 | } 10 | 11 | // Hello resolver 12 | func (m *mutationResolver) Hello(ctx context.Context) (string, error) { 13 | return "Hello", nil 14 | } 15 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/event/delivery/graphqlhandler/query_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | 8 | "pkg.agungdp.dev/candi/candishared" 9 | ) 10 | 11 | type queryResolver struct { 12 | root *GraphQLHandler 13 | } 14 | 15 | // Hello resolver 16 | func (q *queryResolver) GetAll(ctx context.Context, input struct{ Filter *candishared.Filter }) (*EventListResolver, error) { 17 | if input.Filter == nil { 18 | input.Filter = &candishared.Filter{ 19 | Page: 1, Limit: 10, 20 | } 21 | } 22 | events, meta, err := q.root.uc.FindAll(ctx, input.Filter) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | var eventResolvers []*EventResolver 28 | for _, event := range events { 29 | eventResolvers = append(eventResolvers, &EventResolver{ 30 | e: event, 31 | message: EventMessage{ 32 | e: event, 33 | }, 34 | }) 35 | } 36 | 37 | resolvers := EventListResolver{ 38 | m: meta, 39 | events: eventResolvers, 40 | } 41 | return &resolvers, nil 42 | } 43 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/event/delivery/graphqlhandler/root_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "monorepo/services/line-chatbot/internal/modules/event/usecase" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/types" 9 | "pkg.agungdp.dev/candi/codebase/interfaces" 10 | ) 11 | 12 | // GraphQLHandler model 13 | type GraphQLHandler struct { 14 | mw interfaces.Middleware 15 | uc usecase.EventUsecase 16 | validator interfaces.Validator 17 | } 18 | 19 | // NewGraphQLHandler delivery 20 | func NewGraphQLHandler(mw interfaces.Middleware, uc usecase.EventUsecase, validator interfaces.Validator) *GraphQLHandler { 21 | return &GraphQLHandler{ 22 | mw: mw, uc: uc, validator: validator, 23 | } 24 | } 25 | 26 | // RegisterMiddleware register resolver based on schema in "api/graphql/*" path 27 | func (h *GraphQLHandler) RegisterMiddleware(mwGroup *types.MiddlewareGroup) { 28 | mwGroup.Add("EventQueryModule.getAll", h.mw.GraphQLBearerAuth) 29 | mwGroup.Add("EventMutationModule.hello", h.mw.GraphQLBasicAuth) 30 | } 31 | 32 | // Query method 33 | func (h *GraphQLHandler) Query() interface{} { 34 | return &queryResolver{root: h} 35 | } 36 | 37 | // Mutation method 38 | func (h *GraphQLHandler) Mutation() interface{} { 39 | return &mutationResolver{root: h} 40 | } 41 | 42 | // Subscription method 43 | func (h *GraphQLHandler) Subscription() interface{} { 44 | return &subscriptionResolver{root: h} 45 | } 46 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/event/delivery/graphqlhandler/subscription_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import "context" 6 | 7 | type subscriptionResolver struct { 8 | root *GraphQLHandler 9 | } 10 | 11 | // Hello resolver 12 | func (s *subscriptionResolver) Hello(ctx context.Context) <-chan string { 13 | output := make(chan string) 14 | 15 | go func() { 16 | output <- "Hello" 17 | }() 18 | 19 | return output 20 | } 21 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/event/delivery/resthandler/resthandler.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package resthandler 4 | 5 | import ( 6 | "net/http" 7 | 8 | "github.com/labstack/echo" 9 | 10 | "monorepo/services/line-chatbot/internal/modules/event/usecase" 11 | 12 | "pkg.agungdp.dev/candi/candihelper" 13 | "pkg.agungdp.dev/candi/candishared" 14 | "pkg.agungdp.dev/candi/codebase/interfaces" 15 | "pkg.agungdp.dev/candi/tracer" 16 | "pkg.agungdp.dev/candi/wrapper" 17 | ) 18 | 19 | // RestHandler handler 20 | type RestHandler struct { 21 | mw interfaces.Middleware 22 | uc usecase.EventUsecase 23 | validator interfaces.Validator 24 | } 25 | 26 | // NewRestHandler create new rest handler 27 | func NewRestHandler(mw interfaces.Middleware, uc usecase.EventUsecase, validator interfaces.Validator) *RestHandler { 28 | return &RestHandler{ 29 | mw: mw, uc: uc, validator: validator, 30 | } 31 | } 32 | 33 | // Mount handler with root "/" 34 | // handling version in here 35 | func (h *RestHandler) Mount(root *echo.Group) { 36 | v1Root := root.Group(candihelper.V1) 37 | 38 | event := v1Root.Group("/event") 39 | event.GET("", h.hello, echo.WrapMiddleware(h.mw.HTTPBearerAuth)) 40 | } 41 | 42 | func (h *RestHandler) hello(c echo.Context) error { 43 | trace := tracer.StartTrace(c.Request().Context(), "DeliveryREST:Hello") 44 | defer trace.Finish() 45 | 46 | tokenClaim := c.Get(string(candishared.ContextKeyTokenClaim)).(*candishared.TokenClaim) // must using HTTPBearerAuth in middleware for this handler 47 | 48 | return wrapper.NewHTTPResponse(http.StatusOK, "hello, with your session ("+tokenClaim.Audience+")").JSON(c.Response()) 49 | } 50 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/event/domain/event.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "time" 5 | 6 | "go.mongodb.org/mongo-driver/bson/primitive" 7 | ) 8 | 9 | // Event domain model 10 | type Event struct { 11 | ID primitive.ObjectID `bson:"_id" json:"id"` 12 | ReplyToken string `bson:"reply_token" json:"replyToken"` 13 | Type string `bson:"type" json:"type"` 14 | Timestamp time.Time `bson:"timestamp" json:"timestamp"` 15 | SourceID string `bson:"source_id" json:"sourceId"` 16 | SourceType string `bson:"source_type" json:"sourceType"` 17 | Message struct { 18 | ID string `bson:"id" json:"id"` 19 | Type string `bson:"type" json:"type"` 20 | Text string `bson:"text" json:"text"` 21 | Response string `bson:"response" json:"response"` 22 | } `bson:"message" json:"message"` 23 | Error *string `bson:"error" json:"error"` 24 | } 25 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/event/domain/profile.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "time" 4 | 5 | // Profile model 6 | type Profile struct { 7 | ID string `bson:"id" json:"id"` 8 | Type string `bson:"type" json:"type"` 9 | Name string `bson:"name" json:"name"` 10 | Avatar string `bson:"avatar" json:"avatar"` 11 | StatusMessage string `bson:"status_message" json:"statusMessage"` 12 | CreatedAt time.Time `bson:"createdAt" json:"createdAt"` 13 | ModifiedAt time.Time `bson:"modifiedAt" json:"modifiedAt"` 14 | } 15 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/event/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/line-chatbot/internal/modules/event/domain" 8 | 9 | "pkg.agungdp.dev/candi/candishared" 10 | ) 11 | 12 | // EventRepository abstract interface 13 | type EventRepository interface { 14 | FindAll(ctx context.Context, filter *candishared.Filter) <-chan candishared.Result 15 | Count(ctx context.Context, filter *candishared.Filter) <-chan int 16 | Save(ctx context.Context, data *domain.Event) <-chan error 17 | } 18 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/event/repository/repository_sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | "database/sql" 8 | 9 | "pkg.agungdp.dev/candi/tracer" 10 | ) 11 | 12 | type eventRepoSQL struct { 13 | readDB, writeDB *sql.DB 14 | tx *sql.Tx 15 | } 16 | 17 | // NewEventRepoSQL mongo repo constructor 18 | func NewEventRepoSQL(readDB, writeDB *sql.DB, tx *sql.Tx) EventRepository { 19 | return nil 20 | } 21 | 22 | func (r *eventRepoSQL) FindHello(ctx context.Context) (string, error) { 23 | trace := tracer.StartTrace(ctx, "EventRepoSQL:FindHello") 24 | defer trace.Finish() 25 | 26 | return "Hello from repo sql layer", nil 27 | } 28 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/event/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package usecase 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/line-chatbot/internal/modules/event/domain" 8 | 9 | "pkg.agungdp.dev/candi/candishared" 10 | ) 11 | 12 | // EventUsecase abstraction 13 | type EventUsecase interface { 14 | FindAll(ctx context.Context, filter *candishared.Filter) (events []domain.Event, meta candishared.Meta, err error) 15 | } 16 | -------------------------------------------------------------------------------- /services/line-chatbot/internal/modules/event/usecase/usecase_impl.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package usecase 4 | 5 | import ( 6 | "context" 7 | 8 | "monorepo/services/line-chatbot/internal/modules/event/domain" 9 | "monorepo/services/line-chatbot/pkg/shared/repository" 10 | 11 | "pkg.agungdp.dev/candi/candishared" 12 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 13 | "pkg.agungdp.dev/candi/codebase/interfaces" 14 | ) 15 | 16 | type eventUsecaseImpl struct { 17 | cache interfaces.Cache 18 | 19 | repoMongo *repository.RepoMongo 20 | } 21 | 22 | // NewEventUsecase usecase impl constructor 23 | func NewEventUsecase(deps dependency.Dependency) EventUsecase { 24 | return &eventUsecaseImpl{ 25 | cache: deps.GetRedisPool().Cache(), 26 | 27 | repoMongo: repository.GetSharedRepoMongo(), 28 | } 29 | } 30 | 31 | func (uc *eventUsecaseImpl) FindAll(ctx context.Context, filter *candishared.Filter) (events []domain.Event, meta candishared.Meta, err error) { 32 | filter.CalculateOffset() 33 | 34 | count := uc.repoMongo.EventRepo.Count(ctx, filter) 35 | repoRes := <-uc.repoMongo.EventRepo.FindAll(ctx, filter) 36 | if repoRes.Error != nil { 37 | err = repoRes.Error 38 | return 39 | } 40 | 41 | events = repoRes.Data.([]domain.Event) 42 | meta = candishared.NewMeta(int(filter.Page), int(filter.Limit), <-count) 43 | 44 | return 45 | } 46 | -------------------------------------------------------------------------------- /services/line-chatbot/main.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "runtime/debug" 8 | 9 | "pkg.agungdp.dev/candi/codebase/app" 10 | "pkg.agungdp.dev/candi/config" 11 | 12 | service "monorepo/services/line-chatbot/internal" 13 | ) 14 | 15 | const serviceName = "line-chatbot" 16 | 17 | func main() { 18 | defer func() { 19 | if r := recover(); r != nil { 20 | fmt.Printf("\x1b[31;1mFailed to start %s service: %v\x1b[0m\n", serviceName, r) 21 | fmt.Printf("Stack trace: \n%s\n", debug.Stack()) 22 | } 23 | }() 24 | 25 | cfg := config.Init(serviceName) 26 | defer cfg.Exit() 27 | 28 | srv := service.NewService(serviceName, cfg) 29 | app.New(srv).Run() 30 | } 31 | -------------------------------------------------------------------------------- /services/line-chatbot/pkg/helper/const.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | const ( 4 | // LineClient constanta 5 | LineClient = "lineClient" 6 | ) 7 | -------------------------------------------------------------------------------- /services/line-chatbot/pkg/helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper -------------------------------------------------------------------------------- /services/line-chatbot/pkg/shared/domain/chatbot.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | // LineMessage domain model 4 | type LineMessage struct { 5 | To string `json:"to"` 6 | Messages []LineContentMessage `json:"messages"` 7 | } 8 | 9 | // LineContentMessage model 10 | type LineContentMessage struct { 11 | Type string `json:"type"` 12 | AltText string `json:"altText"` 13 | Contents LineContentFormat `json:"contents"` 14 | } 15 | 16 | // LineContentFormat model 17 | type LineContentFormat struct { 18 | Type string `json:"type"` 19 | Body LineContentBody `json:"body"` 20 | } 21 | 22 | // LineContentBody model 23 | type LineContentBody struct { 24 | Type string `json:"type"` 25 | Layout string `json:"layout"` 26 | Contents []LineContent `json:"contents"` 27 | } 28 | 29 | // LineContent model 30 | type LineContent struct { 31 | Type string `json:"type"` 32 | Text string `json:"text"` 33 | } 34 | -------------------------------------------------------------------------------- /services/line-chatbot/pkg/shared/domain/event.go: -------------------------------------------------------------------------------- 1 | package domain -------------------------------------------------------------------------------- /services/line-chatbot/pkg/shared/environment.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package shared 4 | 5 | // Environment additional in this service 6 | type Environment struct { 7 | // more additional environment with struct tag is environment key example: 8 | // ExampleHost string `env:"EXAMPLE_HOST"` 9 | 10 | AuthServiceHost string `env:"AUTH_SERVICE_HOST"` 11 | AuthServiceKey string `env:"AUTH_SERVICE_KEY"` 12 | LineClientSecret string `env:"LINE_CLIENT_SECRET"` 13 | LineClientToken string `env:"LINE_CLIENT_TOKEN"` 14 | ChatbotHost string `env:"CHATBOT_HOST"` 15 | TranslatorHost string `env:"TRANSLATOR_HOST"` 16 | TranslatorKey string `env:"TRANSLATOR_KEY"` 17 | } 18 | 19 | var sharedEnv Environment 20 | 21 | // GetEnv get global additional environment 22 | func GetEnv() Environment { 23 | return sharedEnv 24 | } 25 | 26 | // SetEnv get global additional environment 27 | func SetEnv(env Environment) { 28 | sharedEnv = env 29 | } 30 | -------------------------------------------------------------------------------- /services/line-chatbot/pkg/shared/linebot-api/linebot.go: -------------------------------------------------------------------------------- 1 | package linebotapi 2 | 3 | import ( 4 | "context" 5 | "monorepo/services/line-chatbot/pkg/shared/domain" 6 | ) 7 | 8 | // Linebot abstract interface 9 | type Linebot interface { 10 | ProcessText(ctx context.Context, text string) string 11 | PushMessage(ctx context.Context, message *domain.LineMessage) error 12 | } 13 | -------------------------------------------------------------------------------- /services/line-chatbot/pkg/shared/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. DO NOT EDIT. 2 | 3 | package repository 4 | 5 | import ( 6 | "sync" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 9 | ) 10 | 11 | var ( 12 | once sync.Once 13 | ) 14 | 15 | // SetSharedRepository set the global singleton "RepoSQL" and "RepoMongo" implementation 16 | func SetSharedRepository(deps dependency.Dependency) { 17 | once.Do(func() { 18 | // setSharedRepoSQL(deps.GetSQLDatabase().ReadDB(), deps.GetSQLDatabase().WriteDB()) 19 | setSharedRepoMongo(deps.GetMongoDatabase().ReadDB(), deps.GetMongoDatabase().WriteDB()) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /services/line-chatbot/pkg/shared/repository/repository_mongo.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. DO NOT EDIT. 2 | 3 | package repository 4 | 5 | import ( 6 | "go.mongodb.org/mongo-driver/mongo" 7 | chatbotrepo "monorepo/services/line-chatbot/internal/modules/chatbot/repository" 8 | eventrepo "monorepo/services/line-chatbot/internal/modules/event/repository" 9 | ) 10 | 11 | // RepoMongo uow 12 | type RepoMongo struct { 13 | readDB, writeDB *mongo.Database 14 | 15 | // register all repository from modules 16 | ChatbotRepo chatbotrepo.ChatbotRepository 17 | EventRepo eventrepo.EventRepository 18 | } 19 | 20 | var globalRepoMongo = new(RepoMongo) 21 | 22 | // setSharedRepoMongo set the global singleton "RepoMongo" implementation 23 | func setSharedRepoMongo(readDB, writeDB *mongo.Database) { 24 | globalRepoMongo = &RepoMongo{ 25 | readDB: readDB, writeDB: writeDB, 26 | ChatbotRepo: chatbotrepo.NewChatbotRepoMongo(readDB, writeDB), 27 | EventRepo: eventrepo.NewEventRepoMongo(readDB, writeDB), 28 | } 29 | } 30 | 31 | // GetSharedRepoMongo returns the global singleton "RepoMongo" implementation 32 | func GetSharedRepoMongo() *RepoMongo { 33 | return globalRepoMongo 34 | } 35 | -------------------------------------------------------------------------------- /services/line-chatbot/pkg/shared/token_validator.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package shared 4 | 5 | import ( 6 | "context" 7 | 8 | "pkg.agungdp.dev/candi/candishared" 9 | ) 10 | 11 | // DefaultTokenValidator for token validator 12 | type DefaultTokenValidator struct { 13 | } 14 | 15 | // ValidateToken implement TokenValidator 16 | func (v *DefaultTokenValidator) ValidateToken(ctx context.Context, token string) (*candishared.TokenClaim, error) { 17 | return &candishared.TokenClaim{}, nil 18 | } 19 | -------------------------------------------------------------------------------- /services/line-chatbot/pkg/shared/translator/translator.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import "context" 4 | 5 | // Translator abstract interface 6 | type Translator interface { 7 | Translate(ctx context.Context, from, to, text string) (result string) 8 | } 9 | -------------------------------------------------------------------------------- /services/line-chatbot/pkg/shared/translator/translator_http_impl.go: -------------------------------------------------------------------------------- 1 | package translator 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "monorepo/services/line-chatbot/pkg/shared" 7 | "net/http" 8 | "net/url" 9 | "strings" 10 | "time" 11 | 12 | "github.com/labstack/echo" 13 | "pkg.agungdp.dev/candi/candiutils" 14 | ) 15 | 16 | // translatorHTTPImpl implementation 17 | type translatorHTTPImpl struct { 18 | httpReq candiutils.HTTPRequest 19 | } 20 | 21 | // NewTranslatorHTTP constructor 22 | func NewTranslatorHTTP() Translator { 23 | return &translatorHTTPImpl{ 24 | httpReq: candiutils.NewHTTPRequest( 25 | candiutils.HTTPRequestSetRetries(5), 26 | candiutils.HTTPRequestSetSleepBetweenRetry(500*time.Millisecond), 27 | candiutils.HTTPRequestSetHTTPErrorCodeThreshold(http.StatusBadRequest), 28 | ), 29 | } 30 | } 31 | 32 | // Translate method 33 | func (t *translatorHTTPImpl) Translate(ctx context.Context, from, to, text string) (result string) { 34 | value := url.Values{} 35 | value.Set("key", shared.GetEnv().TranslatorKey) 36 | value.Set("lang", from+"-"+to) 37 | value.Add("text", text) 38 | 39 | resp, respCode, err := t.httpReq.Do(ctx, http.MethodPost, shared.GetEnv().TranslatorHost, []byte(value.Encode()), map[string]string{ 40 | echo.HeaderContentType: echo.MIMEApplicationForm, 41 | }) 42 | if err != nil { 43 | return err.Error() 44 | } 45 | 46 | if respCode >= http.StatusBadRequest { 47 | return "" 48 | } 49 | 50 | var response struct { 51 | Code int `json:"code"` 52 | Lang string `json:"lang"` 53 | Text []string `json:"text"` 54 | } 55 | 56 | json.Unmarshal(resp, &response) 57 | return strings.Join(response.Text, " ") 58 | } 59 | -------------------------------------------------------------------------------- /services/line-chatbot/pkg/shared/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package usecase 4 | 5 | import ( 6 | chatbotusecase "monorepo/services/line-chatbot/internal/modules/chatbot/usecase" 7 | eventusecase "monorepo/services/line-chatbot/internal/modules/event/usecase" 8 | "sync" 9 | 10 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 11 | ) 12 | 13 | type ( 14 | // Usecase unit of work for all usecase in modules 15 | Usecase interface { 16 | Chatbot() chatbotusecase.ChatbotUsecase 17 | Event() eventusecase.EventUsecase 18 | } 19 | 20 | usecaseUow struct { 21 | chatbot chatbotusecase.ChatbotUsecase 22 | event eventusecase.EventUsecase 23 | } 24 | ) 25 | 26 | var usecaseInst *usecaseUow 27 | var once sync.Once 28 | 29 | // SetSharedUsecase set singleton usecase unit of work instance 30 | func SetSharedUsecase(deps dependency.Dependency) { 31 | once.Do(func() { 32 | usecaseInst = &usecaseUow{ 33 | chatbot: chatbotusecase.NewChatbotUsecase(deps), 34 | event: eventusecase.NewEventUsecase(deps), 35 | } 36 | }) 37 | } 38 | 39 | // GetSharedUsecase get usecase unit of work instance 40 | func GetSharedUsecase() Usecase { 41 | return usecaseInst 42 | } 43 | func (uc *usecaseUow) Chatbot() chatbotusecase.ChatbotUsecase { 44 | return uc.chatbot 45 | } 46 | func (uc *usecaseUow) Event() eventusecase.EventUsecase { 47 | return uc.event 48 | } 49 | -------------------------------------------------------------------------------- /services/master-service/.env.sample: -------------------------------------------------------------------------------- 1 | # Basic env configuration 2 | ENVIRONMENT=development #development,staging,production 3 | DEBUG_MODE=true 4 | NO_AUTH=true 5 | 6 | # Service Handlers 7 | ## Server 8 | USE_REST=true 9 | USE_GRPC=true 10 | USE_GRAPHQL=false 11 | ## Worker 12 | USE_KAFKA_CONSUMER=false 13 | USE_CRON_SCHEDULER=false 14 | USE_REDIS_SUBSCRIBER=false 15 | USE_TASK_QUEUE_WORKER=false 16 | 17 | HTTP_PORT=8000 18 | GRPC_PORT=8002 # uncomment this env if separate port listener http & grpc server, comment this env if use shared listener from http & grpc in same port (use HTTP_PORT) 19 | 20 | TASK_QUEUE_DASHBOARD_PORT=8080 21 | TASK_QUEUE_DASHBOARD_MAX_CLIENT=5 22 | 23 | # use consul for distributed lock if run in multiple instance 24 | USE_CONSUL=false 25 | CONSUL_AGENT_HOST=127.0.0.1:8500 26 | CONSUL_MAX_JOB_REBALANCE=10 # if worker execute total job in env config, rebalance worker to another active intance 27 | 28 | BASIC_AUTH_USERNAME=user 29 | BASIC_AUTH_PASS=pass 30 | 31 | MONGODB_HOST_WRITE=mongodb://user:pass@localhost:27017 32 | MONGODB_HOST_READ=mongodb://user:pass@localhost:27017 33 | MONGODB_DATABASE_NAME=master-service 34 | 35 | SQL_DB_READ_DSN=sql://user:pass@localhost:5432/db_name?sslmode=disable 36 | SQL_DB_WRITE_DSN=sql://user:pass@localhost:5432/db_name?sslmode=disable 37 | 38 | REDIS_READ_DSN=redis://:pass@localhost:6379/0 39 | REDIS_WRITE_DSN=redis://:pass@localhost:6379/0 40 | 41 | KAFKA_BROKERS=localhost:9092 42 | KAFKA_CLIENT_VERSION=2.0.0 43 | KAFKA_CLIENT_ID=master-service 44 | KAFKA_CONSUMER_GROUP=master-service 45 | 46 | JAEGER_TRACING_HOST=127.0.0.1:5775 47 | GRAPHQL_SCHEMA_DIR="api/graphql" 48 | JSON_SCHEMA_DIR="api/jsonschema" 49 | 50 | MAX_GOROUTINES=100 51 | 52 | # Additional env 53 | USER_SERVICE_HOST= 54 | USER_SERVICE_AUTH_KEY= 55 | 56 | AUTH_SERVICE_HOST= 57 | AUTH_SERVICE_KEY= 58 | -------------------------------------------------------------------------------- /services/master-service/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | vendor 3 | main_service.go 4 | master-service 5 | coverage.txt 6 | -------------------------------------------------------------------------------- /services/master-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1 2 | FROM golang:1.14.9-alpine3.12 AS dependency_builder 3 | 4 | WORKDIR /go/src 5 | ENV GO111MODULE=on 6 | 7 | RUN apk update 8 | RUN apk add --no-cache bash ca-certificates git make 9 | 10 | COPY go.mod . 11 | COPY go.sum . 12 | 13 | RUN go mod download 14 | 15 | # Stage 2 16 | FROM dependency_builder AS service_builder 17 | 18 | WORKDIR /usr/app 19 | 20 | COPY . . 21 | RUN CGO_ENABLED=0 GOOS=linux go build -ldflags '-w -s' -a -o bin 22 | 23 | # Stage 3 24 | FROM alpine:latest 25 | 26 | RUN apk --no-cache add ca-certificates tzdata 27 | WORKDIR /root/ 28 | 29 | RUN mkdir -p /root/api 30 | RUN mkdir -p /root/cmd/master-service 31 | RUN mkdir -p /root/config/key 32 | COPY --from=service_builder /usr/app/bin bin 33 | COPY --from=service_builder /usr/app/.env .env 34 | COPY --from=service_builder /usr/app/api /root/api 35 | 36 | ENTRYPOINT ["./bin"] 37 | -------------------------------------------------------------------------------- /services/master-service/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : build run 2 | 3 | build: 4 | go build -o bin 5 | 6 | run: build 7 | ./bin 8 | 9 | proto: 10 | $(foreach proto_file, $(shell find api/proto -name '*.proto'),\ 11 | protoc --proto_path=api/proto --go_out=plugins=grpc:api/proto \ 12 | --go_opt=paths=source_relative $(proto_file);) 13 | 14 | docker: 15 | docker build -t master-service:latest . 16 | 17 | run-container: 18 | docker run --name=master-service --network="host" -d master-service 19 | 20 | # unit test & calculate code coverage 21 | test: 22 | @if [ -f coverage.txt ]; then rm coverage.txt; fi; 23 | @echo ">> running unit test and calculate coverage" 24 | @go test ./... -cover -coverprofile=coverage.txt -covermode=count -coverpkg=$(PACKAGES) 25 | @go tool cover -func=coverage.txt 26 | 27 | clear: 28 | rm bin master-service 29 | -------------------------------------------------------------------------------- /services/master-service/README.md: -------------------------------------------------------------------------------- 1 | # Master-Service 2 | 3 | ## Build and run service 4 | ``` 5 | $ make run 6 | ``` 7 | If include GRPC handler, run `$ make proto` for generate rpc files from proto (must install `protoc` compiler min version `libprotoc 3.14.0`) 8 | 9 | ## Run unit test & calculate code coverage 10 | ``` 11 | $ make test 12 | ``` 13 | 14 | ## Create docker image 15 | ``` 16 | $ make docker 17 | ``` 18 | -------------------------------------------------------------------------------- /services/master-service/api/graphql/_common.graphql: -------------------------------------------------------------------------------- 1 | input FilterListInputResolver { 2 | limit: Int 3 | page: Int 4 | "Optional (asc desc)" 5 | sort: FilterSortEnum 6 | "Optional" 7 | order_by: String 8 | "Optional" 9 | show_all: Boolean 10 | "Optional" 11 | search: String 12 | } 13 | 14 | type Meta { 15 | page: Int! 16 | limit: Int! 17 | total_records: Int! 18 | total_pages: Int! 19 | } 20 | 21 | enum FilterSortEnum { 22 | asc 23 | desc 24 | } 25 | -------------------------------------------------------------------------------- /services/master-service/api/graphql/_schema.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.3.1. 2 | 3 | schema { 4 | query: Query 5 | mutation: Mutation 6 | } 7 | 8 | type Query { 9 | master_acl: AclQueryResolver 10 | master_apps: AppsQueryResolver 11 | } 12 | 13 | type Mutation { 14 | master_acl: AclMutationResolver 15 | master_apps: AppsMutationResolver 16 | } 17 | -------------------------------------------------------------------------------- /services/master-service/api/graphql/acl.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.4.0. 2 | 3 | # AclResolver Resolver Area 4 | type AclQueryResolver { 5 | get_all_role(filter: FilterListInputResolver): RoleListResolver! 6 | get_detail_role(id: String!): RoleACLResolver! 7 | } 8 | 9 | type AclMutationResolver { 10 | add_role(data: AddRoleInputResolver!): String! 11 | grant_user(data: GrantUserInputResolver!): String! 12 | revoke_user(data: GrantUserInputResolver!): String! 13 | } 14 | 15 | type RoleListResolver { 16 | meta: Meta! 17 | data: [RoleACLResolver!]! 18 | } 19 | 20 | type RoleACLResolver { 21 | id: String! 22 | code: String! 23 | name: String! 24 | apps: RoleAppResolver! 25 | } 26 | 27 | type RoleAppResolver { 28 | id: String! 29 | code: String! 30 | name: String! 31 | } 32 | 33 | input AddRoleInputResolver { 34 | apps_code: String! 35 | code: String! 36 | name: String! 37 | permissions: [String!]! 38 | } 39 | 40 | input GrantUserInputResolver { 41 | user_id: String! 42 | role_id: String! 43 | } 44 | -------------------------------------------------------------------------------- /services/master-service/api/graphql/apps.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.4.0. 2 | 3 | # AppsResolver Resolver Area 4 | type AppsQueryResolver { 5 | get_all(filter: FilterListInputResolver): AppsListResolver! 6 | get_detail(code: String!): AppDetailResolver! 7 | get_me_permissions(apps_code: String!): [PermissionResolver!]! 8 | get_all_me_apps(): [UserAppsResolver!]! 9 | } 10 | 11 | type AppsMutationResolver { 12 | save(data: AppsInputResolver!): String! 13 | save_permissions(app_id: String!, permission: AppsPermissionInputResolver!): String! 14 | } 15 | 16 | type AppsListResolver { 17 | meta: Meta! 18 | data: [AppsResolver!]! 19 | } 20 | 21 | type AppsResolver { 22 | id: String! 23 | code: String! 24 | name: String! 25 | createdAt: String! 26 | modifiedAt: String! 27 | } 28 | 29 | type PermissionResolver { 30 | id: String! 31 | code: String! 32 | name: String! 33 | icon: String! 34 | url: String! 35 | new_page: Boolean! 36 | childs: [PermissionResolver!]! 37 | createdAt: String! 38 | modifiedAt: String! 39 | } 40 | 41 | input AppsInputResolver { 42 | code: String! 43 | name: String! 44 | } 45 | 46 | type UserAppsResolver { 47 | id: String! 48 | code: String! 49 | name: String! 50 | icon: String! 51 | frontend_url: String! 52 | backend_url: String! 53 | role: RoleAppResolver! 54 | } 55 | 56 | input AppsPermissionInputResolver { 57 | code: String! 58 | name: String! 59 | icon: String! 60 | url: String! 61 | new_page: Boolean! 62 | childs: [AppsPermissionInputResolver!]! 63 | } 64 | 65 | type AppDetailResolver { 66 | id: String! 67 | code: String! 68 | name: String! 69 | icon: String! 70 | frontend_url: String! 71 | backend_url: String! 72 | permissions: [PermissionResolver!]! 73 | } 74 | -------------------------------------------------------------------------------- /services/master-service/api/jsonschema/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "example", 4 | "title": "json schema type", 5 | "type": "object", 6 | "properties": {} 7 | } 8 | -------------------------------------------------------------------------------- /services/master-service/api/proto/acl/acl.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | package acl; 3 | option go_package = "monorepo/services/master-service/api/proto/acl"; 4 | 5 | service AclHandler { 6 | rpc CheckPermission(CheckPermissionRequest) returns (CheckPermissionResponse); 7 | } 8 | 9 | message CheckPermissionRequest { 10 | string UserID=1; 11 | string PermissionCode=2; 12 | } 13 | 14 | message CheckPermissionResponse { 15 | string RoleID=1; 16 | } 17 | -------------------------------------------------------------------------------- /services/master-service/api/proto/apps/apps.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | package apps; 3 | option go_package = "monorepo/services/master-service/api/proto/apps"; 4 | 5 | service AppsHandler { 6 | rpc GetUserApps(RequestUserApps) returns (stream ResponseUserApps); 7 | } 8 | 9 | message RequestUserApps { 10 | string UserID=1; 11 | } 12 | 13 | message ResponseUserApps { 14 | string ID=1; 15 | string Code=2; 16 | string Name=3; 17 | string Icon=4; 18 | string FrontendUrl=5; 19 | string BackendUrl=6; 20 | message RoleType { 21 | string ID=1; 22 | string Code=2; 23 | string Name=3; 24 | } 25 | RoleType Role = 7; 26 | } -------------------------------------------------------------------------------- /services/master-service/candi.json: -------------------------------------------------------------------------------- 1 | {"Version":"v1.5.6","Header":"Code generated by candi v1.5.6.","LibraryName":"pkg.agungdp.dev/candi","ServiceName":"master-service","PackagePrefix":"monorepo/services/master-service","ProtoSource":"monorepo/sdk/master-service/proto","RestHandler":true,"GRPCHandler":true,"GraphQLHandler":false,"KafkaHandler":false,"SchedulerHandler":false,"RedisSubsHandler":false,"TaskQueueHandler":false,"IsWorkerActive":false,"RedisDeps":true,"SQLDeps":false,"MongoDeps":true,"SQLUseGORM":false,"SQLDriver":"","Modules":[{"ModuleName":"acl"},{"ModuleName":"apps"}]} -------------------------------------------------------------------------------- /services/master-service/cmd/migration/main.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.18. 2 | 3 | package main 4 | 5 | import ( 6 | "context" 7 | "time" 8 | 9 | "pkg.agungdp.dev/candi/config/database" 10 | "pkg.agungdp.dev/candi/config/env" 11 | 12 | _ "github.com/lib/pq" 13 | ) 14 | 15 | func main() { 16 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) 17 | defer cancel() 18 | 19 | env.Load("master-service") 20 | migrateMongo(ctx, database.InitMongoDB(ctx)) 21 | } 22 | -------------------------------------------------------------------------------- /services/master-service/deployments/k8s/master-service.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungdwiprasetyo/backend-microservices/c216a6afdb64cc057c494299b54bd8ac9378ef2e/services/master-service/deployments/k8s/master-service.yaml -------------------------------------------------------------------------------- /services/master-service/docs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungdwiprasetyo/backend-microservices/c216a6afdb64cc057c494299b54bd8ac9378ef2e/services/master-service/docs/.gitkeep -------------------------------------------------------------------------------- /services/master-service/internal/modules/acl/delivery/graphqlhandler/field_resolver.go: -------------------------------------------------------------------------------- 1 | package graphqlhandler 2 | 3 | import ( 4 | "monorepo/services/master-service/internal/modules/acl/domain" 5 | 6 | "pkg.agungdp.dev/candi/candihelper" 7 | "pkg.agungdp.dev/candi/candishared" 8 | ) 9 | 10 | // CommonFilter basic filter model 11 | type CommonFilter struct { 12 | Limit *int `json:"limit" validate:"omitempty,gte=1"` 13 | Page *int `json:"page" validate:"omitempty,gte=1"` 14 | Search *string `json:"search"` 15 | Sort *string `json:"sort" validate:"omitempty,oneof='desc' 'asc'"` 16 | ShowAll *bool `json:"show_all"` 17 | OrderBy *string `json:"order_by"` 18 | } 19 | 20 | // ToSharedFilter method 21 | func (f *CommonFilter) ToSharedFilter() (filter candishared.Filter) { 22 | filter.Search = candihelper.PtrToString(f.Search) 23 | filter.OrderBy = candihelper.PtrToString(f.OrderBy) 24 | filter.Sort = candihelper.PtrToString(f.Sort) 25 | filter.ShowAll = candihelper.PtrToBool(f.ShowAll) 26 | 27 | if f.Limit == nil { 28 | filter.Limit = 10 29 | } else { 30 | filter.Limit = *f.Limit 31 | } 32 | if f.Page == nil { 33 | filter.Page = 1 34 | } else { 35 | filter.Page = *f.Page 36 | } 37 | 38 | return 39 | } 40 | 41 | // RoleResult resolver 42 | type RoleResult struct { 43 | Meta candishared.Meta 44 | Data []domain.RoleResponse 45 | } 46 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/acl/delivery/graphqlhandler/mutation_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.18. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/master-service/internal/modules/acl/domain" 8 | 9 | "pkg.agungdp.dev/candi/tracer" 10 | ) 11 | 12 | type mutationResolver struct { 13 | root *GraphQLHandler 14 | } 15 | 16 | // AddRole resolver 17 | func (m *mutationResolver) AddRole(ctx context.Context, input struct{ Data domain.AddRoleRequest }) (string, error) { 18 | trace := tracer.StartTrace(ctx, "AclDeliveryGraphQL:AddRole") 19 | defer trace.Finish() 20 | ctx = trace.Context() 21 | 22 | _, err := m.root.uc.SaveRole(trace.Context(), input.Data) 23 | if err != nil { 24 | return "", err 25 | } 26 | return "Success", nil 27 | } 28 | 29 | // GrantUser resolver 30 | func (m *mutationResolver) GrantUser(ctx context.Context, input struct{ Data domain.GrantUserRequest }) (string, error) { 31 | trace := tracer.StartTrace(ctx, "AclDeliveryGraphQL:GrantUser") 32 | defer trace.Finish() 33 | ctx = trace.Context() 34 | 35 | if err := m.root.uc.GrantUser(ctx, input.Data); err != nil { 36 | return "", err 37 | } 38 | 39 | return "Success", nil 40 | } 41 | 42 | // AddRole resolver 43 | func (m *mutationResolver) RevokeUser(ctx context.Context, input struct{ Data domain.GrantUserRequest }) (string, error) { 44 | trace := tracer.StartTrace(ctx, "AclDeliveryGraphQL:RevokeUser") 45 | defer trace.Finish() 46 | ctx = trace.Context() 47 | 48 | if err := m.root.uc.RevokeUserRole(ctx, input.Data.UserID, input.Data.RoleID); err != nil { 49 | return "", err 50 | } 51 | return "Success", nil 52 | } 53 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/acl/delivery/graphqlhandler/query_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.18. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/master-service/internal/modules/acl/domain" 8 | 9 | "pkg.agungdp.dev/candi/tracer" 10 | ) 11 | 12 | type queryResolver struct { 13 | root *GraphQLHandler 14 | } 15 | 16 | // GetAllRole resolver 17 | func (q *queryResolver) GetAllRole(ctx context.Context, input struct{ Filter *CommonFilter }) (resp RoleResult, err error) { 18 | trace := tracer.StartTrace(ctx, "AclDeliveryGraphQL:GetAllRole") 19 | defer trace.Finish() 20 | ctx = trace.Context() 21 | 22 | if input.Filter == nil { 23 | input.Filter = new(CommonFilter) 24 | } 25 | data, meta, err := q.root.uc.GetAllRole(ctx, domain.RoleListFilter{Filter: input.Filter.ToSharedFilter()}) 26 | if err != nil { 27 | return resp, err 28 | } 29 | 30 | resp.Meta = meta 31 | resp.Data = data 32 | return 33 | } 34 | 35 | // GetDetailRole resolver 36 | func (q *queryResolver) GetDetailRole(ctx context.Context, input struct{ ID string }) (resp domain.RoleResponse, err error) { 37 | trace := tracer.StartTrace(ctx, "AclDeliveryGraphQL:GetDetailRole") 38 | defer trace.Finish() 39 | ctx = trace.Context() 40 | 41 | return q.root.uc.GetDetailRole(ctx, input.ID) 42 | } 43 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/acl/delivery/graphqlhandler/subscription_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.18. 2 | 3 | package graphqlhandler 4 | 5 | import "context" 6 | 7 | type subscriptionResolver struct { 8 | root *GraphQLHandler 9 | } 10 | 11 | // Hello resolver 12 | func (s *subscriptionResolver) Hello(ctx context.Context) <-chan string { 13 | output := make(chan string) 14 | 15 | go func() { 16 | output <- "Hello" 17 | }() 18 | 19 | return output 20 | } 21 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/acl/domain/filter.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "pkg.agungdp.dev/candi/candishared" 4 | 5 | // RoleListFilter filter 6 | type RoleListFilter struct { 7 | candishared.Filter 8 | AppsID string `json:"apps_id"` 9 | RoleIDs []string `json:"role_ids"` 10 | } 11 | 12 | // ACLFilter filter 13 | type ACLFilter struct { 14 | candishared.Filter 15 | AppsID string 16 | UserID string 17 | } 18 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/acl/domain/payload.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | shareddomain "monorepo/services/master-service/pkg/shared/domain" 5 | ) 6 | 7 | // GrantUserRequest payload 8 | type GrantUserRequest struct { 9 | UserID string `json:"userId"` 10 | RoleID string `json:"roleId"` 11 | } 12 | 13 | // AddRoleRequest model 14 | type AddRoleRequest struct { 15 | AppsCode string `json:"appsCode"` 16 | Code string `json:"code"` 17 | Name string `json:"name"` 18 | Permissions []string `json:"permissions"` 19 | } 20 | 21 | // AddRoleResponse model 22 | type AddRoleResponse struct { 23 | ID string `json:"id"` 24 | AppsID string `json:"appsId"` 25 | Code string `json:"code"` 26 | Name string `json:"name"` 27 | Permissions []shareddomain.Permission `json:"permissions"` 28 | } 29 | 30 | // Permissions model 31 | type Permissions struct { 32 | ID string `json:"id"` 33 | } 34 | 35 | // CheckPermissionRequest payload 36 | type CheckPermissionRequest struct { 37 | UserID string `json:"userId"` 38 | PermissionCode string `json:"permissionCode"` 39 | } 40 | 41 | // CheckPermissionResponse payload 42 | type CheckPermissionResponse struct { 43 | RoleID string `json:"userId"` 44 | Access []shareddomain.Permission `json:"access"` 45 | } 46 | 47 | // RoleResponse response payload 48 | type RoleResponse struct { 49 | ID string `json:"id"` 50 | Code string `json:"code"` 51 | Name string `json:"name"` 52 | Apps struct { 53 | ID string `json:"id"` 54 | Code string `json:"code"` 55 | Name string `json:"name"` 56 | } `json:"apps"` 57 | Permissions []shareddomain.Permission `json:"permissions,omitempty"` 58 | } 59 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/acl/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.18. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/master-service/internal/modules/acl/domain" 8 | shareddomain "monorepo/services/master-service/pkg/shared/domain" 9 | ) 10 | 11 | // ACLRepository abstract interface 12 | type ACLRepository interface { 13 | FetchAll(ctx context.Context, filter domain.ACLFilter) (data []shareddomain.ACL, err error) 14 | FindByUserID(ctx context.Context, userID string) (data []shareddomain.ACL, err error) 15 | Find(ctx context.Context, data *shareddomain.ACL) (err error) 16 | Save(ctx context.Context, data *shareddomain.ACL) (err error) 17 | Delete(ctx context.Context, data *shareddomain.ACL) (err error) 18 | } 19 | 20 | // RoleRepository abstract interface 21 | type RoleRepository interface { 22 | FetchAll(ctx context.Context, filter domain.RoleListFilter) (data []shareddomain.Role, err error) 23 | GroupByID(ctx context.Context, roleID ...string) map[string]shareddomain.Role 24 | Count(ctx context.Context, filter domain.RoleListFilter) int64 25 | Find(ctx context.Context, data *shareddomain.Role) (err error) 26 | Save(ctx context.Context, data *shareddomain.Role) (err error) 27 | } 28 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/acl/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.18. 2 | 3 | package usecase 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/master-service/internal/modules/acl/domain" 8 | 9 | "pkg.agungdp.dev/candi/candishared" 10 | ) 11 | 12 | // ACLUsecase abstraction 13 | type ACLUsecase interface { 14 | SaveRole(ctx context.Context, payload domain.AddRoleRequest) (resp domain.RoleResponse, err error) 15 | GrantUser(ctx context.Context, payload domain.GrantUserRequest) (err error) 16 | CheckPermission(ctx context.Context, userID string, permissionCode string) (role string, err error) 17 | GetAllRole(ctx context.Context, filter domain.RoleListFilter) ([]domain.RoleResponse, candishared.Meta, error) 18 | GetDetailRole(ctx context.Context, roleID string) (data domain.RoleResponse, err error) 19 | RevokeUserRole(ctx context.Context, userID, roleID string) (err error) 20 | } 21 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/apps/delivery/graphqlhandler/field_resolver.go: -------------------------------------------------------------------------------- 1 | package graphqlhandler 2 | 3 | import ( 4 | "monorepo/services/master-service/internal/modules/apps/domain" 5 | shareddomain "monorepo/services/master-service/pkg/shared/domain" 6 | 7 | "pkg.agungdp.dev/candi/candihelper" 8 | "pkg.agungdp.dev/candi/candishared" 9 | ) 10 | 11 | // CommonFilter basic filter model 12 | type CommonFilter struct { 13 | Limit *int `json:"limit" validate:"omitempty,gte=1"` 14 | Page *int `json:"page" validate:"omitempty,gte=1"` 15 | Search *string `json:"search"` 16 | Sort *string `json:"sort" validate:"omitempty,oneof='desc' 'asc'"` 17 | ShowAll *bool `json:"show_all"` 18 | OrderBy *string `json:"order_by"` 19 | } 20 | 21 | // ToSharedFilter method 22 | func (f *CommonFilter) ToSharedFilter() (filter domain.FilterApps) { 23 | filter.Search = candihelper.PtrToString(f.Search) 24 | filter.OrderBy = candihelper.PtrToString(f.OrderBy) 25 | filter.Sort = candihelper.PtrToString(f.Sort) 26 | filter.ShowAll = candihelper.PtrToBool(f.ShowAll) 27 | 28 | if f.Limit == nil { 29 | filter.Limit = 10 30 | } else { 31 | filter.Limit = *f.Limit 32 | } 33 | if f.Page == nil { 34 | filter.Page = 1 35 | } else { 36 | filter.Page = *f.Page 37 | } 38 | 39 | return 40 | } 41 | 42 | // AppListResolver resolver 43 | type AppListResolver struct { 44 | Meta candishared.Meta 45 | Data []shareddomain.Apps 46 | } 47 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/apps/delivery/graphqlhandler/mutation_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.18. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/master-service/internal/modules/apps/domain" 8 | shareddomain "monorepo/services/master-service/pkg/shared/domain" 9 | 10 | "pkg.agungdp.dev/candi/tracer" 11 | ) 12 | 13 | type mutationResolver struct { 14 | root *GraphQLHandler 15 | } 16 | 17 | // Save resolver 18 | func (m *mutationResolver) Save(ctx context.Context, input struct{ Data shareddomain.Apps }) (string, error) { 19 | trace := tracer.StartTrace(ctx, "AppsDeliveryGraphQL:Save") 20 | defer trace.Finish() 21 | ctx = trace.Context() 22 | 23 | return "Success", m.root.uc.Save(ctx, &input.Data) 24 | } 25 | 26 | // SavePermissions resolver 27 | func (m *mutationResolver) SavePermissions(ctx context.Context, input struct { 28 | AppID string 29 | Permission domain.PermissionRequest 30 | }) (string, error) { 31 | trace := tracer.StartTrace(ctx, "AppsDeliveryGraphQL:SavePermissions") 32 | defer trace.Finish() 33 | ctx = trace.Context() 34 | 35 | return "Success", m.root.uc.SaveAppsPermission(ctx, input.AppID, input.Permission) 36 | } 37 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/apps/delivery/graphqlhandler/root_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.18. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "monorepo/services/master-service/internal/modules/apps/usecase" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/types" 9 | "pkg.agungdp.dev/candi/codebase/interfaces" 10 | ) 11 | 12 | // GraphQLHandler model 13 | type GraphQLHandler struct { 14 | mw interfaces.Middleware 15 | uc usecase.AppsUsecase 16 | validator interfaces.Validator 17 | } 18 | 19 | // NewGraphQLHandler delivery 20 | func NewGraphQLHandler(mw interfaces.Middleware, uc usecase.AppsUsecase, validator interfaces.Validator) *GraphQLHandler { 21 | return &GraphQLHandler{ 22 | mw: mw, uc: uc, validator: validator, 23 | } 24 | } 25 | 26 | // RegisterMiddleware register resolver based on schema in "api/graphql/*" path 27 | func (h *GraphQLHandler) RegisterMiddleware(mwGroup *types.MiddlewareGroup) { 28 | mwGroup.Add("AppsQueryResolver.get_all", h.mw.GraphQLBearerAuth, h.mw.GraphQLPermissionACL("apps.getAllApps")) 29 | mwGroup.Add("AppsQueryResolver.get_me_permissions", h.mw.GraphQLBearerAuth) 30 | mwGroup.Add("AppsQueryResolver.get_all_me_apps", h.mw.GraphQLBearerAuth) 31 | mwGroup.Add("AppsMutationResolver.save", h.mw.GraphQLBearerAuth, h.mw.GraphQLPermissionACL("apps.addApps")) 32 | mwGroup.Add("AppsMutationResolver.save_permissions", h.mw.GraphQLBearerAuth) 33 | } 34 | 35 | // Query method 36 | func (h *GraphQLHandler) Query() interface{} { 37 | return &queryResolver{root: h} 38 | } 39 | 40 | // Mutation method 41 | func (h *GraphQLHandler) Mutation() interface{} { 42 | return &mutationResolver{root: h} 43 | } 44 | 45 | // Subscription method 46 | func (h *GraphQLHandler) Subscription() interface{} { 47 | return &subscriptionResolver{root: h} 48 | } 49 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/apps/delivery/graphqlhandler/subscription_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.18. 2 | 3 | package graphqlhandler 4 | 5 | import "context" 6 | 7 | type subscriptionResolver struct { 8 | root *GraphQLHandler 9 | } 10 | 11 | // Hello resolver 12 | func (s *subscriptionResolver) Hello(ctx context.Context) <-chan string { 13 | output := make(chan string) 14 | 15 | go func() { 16 | output <- "Hello" 17 | }() 18 | 19 | return output 20 | } 21 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/apps/domain/filter.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "pkg.agungdp.dev/candi/candishared" 4 | 5 | type FilterModule struct { 6 | candishared.Filter 7 | AppsID string 8 | } 9 | 10 | type FilterApps struct { 11 | candishared.Filter 12 | IDs []string 13 | } 14 | 15 | type FilterPermission struct { 16 | candishared.Filter 17 | AppID string `json:"app_id"` 18 | PermissionIDs []string 19 | Codes []string 20 | } 21 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/apps/domain/payload.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | // AppsRequest model 4 | type AppsRequest struct { 5 | Code string `json:"code"` 6 | Name string `json:"name"` 7 | Permissions []PermissionRequest `json:"permissions"` 8 | } 9 | 10 | // PermissionRequest model 11 | type PermissionRequest struct { 12 | ID string `json:"-"` 13 | ParentID string `json:"-"` 14 | Code string `json:"code"` 15 | Name string `json:"name"` 16 | Icon string `json:"icon"` 17 | URL string `json:"url"` 18 | NewPage bool `json:"new_page"` 19 | Childs []PermissionRequest `json:"childs"` 20 | } 21 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/apps/domain/response.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | shareddomain "monorepo/services/master-service/pkg/shared/domain" 5 | ) 6 | 7 | // AppDetail response data 8 | type AppDetail struct { 9 | ID string `json:"id"` 10 | Code string `json:"code"` 11 | Name string `json:"name"` 12 | Icon string `json:"icon"` 13 | FrontendURL string `json:"frontendUrl"` 14 | BackendURL string `json:"backendUrl"` 15 | Permissions []shareddomain.Permission `json:"permissions"` 16 | } 17 | 18 | // Permission response data 19 | type Permission struct { 20 | FullCode string `json:"fullCode"` 21 | } 22 | 23 | // UserApps response data 24 | type UserApps struct { 25 | ID string `json:"id"` 26 | Code string `json:"code"` 27 | Name string `json:"name"` 28 | Icon string `json:"icon"` 29 | FrontendURL string `json:"frontendUrl"` 30 | BackendURL string `json:"backendUrl"` 31 | Role struct { 32 | ID string `json:"id"` 33 | Code string `json:"code"` 34 | Name string `json:"name"` 35 | } `json:"role"` 36 | } 37 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/apps/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.18. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/master-service/internal/modules/apps/domain" 8 | shareddomain "monorepo/services/master-service/pkg/shared/domain" 9 | 10 | "pkg.agungdp.dev/candi/candishared" 11 | ) 12 | 13 | // AppsRepository abstract interface 14 | type AppsRepository interface { 15 | FetchAll(ctx context.Context, filter domain.FilterApps) ([]shareddomain.Apps, error) 16 | Find(ctx context.Context, data *shareddomain.Apps) (err error) 17 | Count(ctx context.Context, filter domain.FilterApps) (count int64) 18 | Save(ctx context.Context, data *shareddomain.Apps) (err error) 19 | } 20 | 21 | // PermissionRepository abstract interface 22 | type PermissionRepository interface { 23 | FetchAll(ctx context.Context, filter domain.FilterPermission) ([]shareddomain.Permission, error) 24 | GroupByAppsID(ctx context.Context, appsID ...string) (groups map[string][]shareddomain.Permission) 25 | GroupByParentID(ctx context.Context, parentIDs ...string) (groups map[string][]shareddomain.Permission) 26 | Find(ctx context.Context, data *shareddomain.Permission) (err error) 27 | Count(ctx context.Context, filter candishared.Filter) (count int64) 28 | Save(ctx context.Context, data *shareddomain.Permission) (err error) 29 | } 30 | -------------------------------------------------------------------------------- /services/master-service/internal/modules/apps/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.18. 2 | 3 | package usecase 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/master-service/internal/modules/apps/domain" 8 | shareddomain "monorepo/services/master-service/pkg/shared/domain" 9 | 10 | "pkg.agungdp.dev/candi/candishared" 11 | ) 12 | 13 | // AppsUsecase abstraction 14 | type AppsUsecase interface { 15 | FindAll(ctx context.Context, filter domain.FilterApps) (data []shareddomain.Apps, meta candishared.Meta, err error) 16 | GetDetailApp(ctx context.Context, appID string) (data domain.AppDetail, err error) 17 | Save(ctx context.Context, data *shareddomain.Apps) (err error) 18 | SaveAppsPermission(ctx context.Context, appID string, permissions domain.PermissionRequest) (err error) 19 | GetAllPermissions(ctx context.Context, filter domain.FilterPermission) (data []domain.Permission, meta candishared.Meta, err error) 20 | GetAllUserPermissions(ctx context.Context, appsID, userID string) (data []shareddomain.Permission, err error) 21 | GetUserApps(ctx context.Context, userID string) (data []domain.UserApps, err error) 22 | } 23 | -------------------------------------------------------------------------------- /services/master-service/main.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.5.6. 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "runtime/debug" 8 | 9 | 10 | 11 | "pkg.agungdp.dev/candi/codebase/app" 12 | "pkg.agungdp.dev/candi/config" 13 | 14 | service "monorepo/services/master-service/internal" 15 | ) 16 | 17 | const serviceName = "master-service" 18 | 19 | func main() { 20 | defer func() { 21 | if r := recover(); r != nil { 22 | fmt.Printf("\x1b[31;1mFailed to start %s service: %v\x1b[0m\n", serviceName, r) 23 | fmt.Printf("Stack trace: \n%s\n", debug.Stack()) 24 | } 25 | }() 26 | 27 | cfg := config.Init(serviceName) 28 | defer cfg.Exit() 29 | 30 | srv := service.NewService(serviceName, cfg) 31 | app.New(srv).Run() 32 | } 33 | -------------------------------------------------------------------------------- /services/master-service/pkg/helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper 2 | -------------------------------------------------------------------------------- /services/master-service/pkg/shared/domain/acl.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "time" 4 | 5 | // ACL model 6 | type ACL struct { 7 | ID string `json:"id" bson:"_id"` 8 | UserID string `json:"userId" bson:"userId"` 9 | AppsID string `json:"appsId" bson:"appsId"` 10 | RoleID string `json:"roleId" bson:"roleId"` 11 | CreatedAt time.Time `json:"createdAt" bson:"createdAt"` 12 | ModifiedAt time.Time `json:"modifiedAt" bson:"modifiedAt"` 13 | } 14 | 15 | // CollectionName for model 16 | func (ACL) CollectionName() string { 17 | return "access_control_list" 18 | } 19 | 20 | // TableName for model 21 | func (ACL) TableName() string { 22 | return "access_control_list" 23 | } 24 | 25 | // Role model 26 | type Role struct { 27 | ID string `json:"id" bson:"_id"` 28 | AppsID string `json:"appsId" bson:"appsId"` 29 | Code string `json:"code" bson:"code"` 30 | Name string `json:"name" bson:"name"` 31 | Permissions map[string]string `json:"permissions" bson:"permissions"` 32 | CreatedAt time.Time `json:"createdAt" bson:"createdAt"` 33 | ModifiedAt time.Time `json:"modifiedAt" bson:"modifiedAt"` 34 | } 35 | 36 | // CollectionName for model 37 | func (Role) CollectionName() string { 38 | return "roles" 39 | } 40 | 41 | // TableName for model 42 | func (Role) TableName() string { 43 | return "roles" 44 | } 45 | -------------------------------------------------------------------------------- /services/master-service/pkg/shared/domain/apps_test.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestGetFullPathParentFromChild(t *testing.T) { 11 | perm := Permission{Code: "user-service", Childs: []Permission{ 12 | {Code: "member", Childs: []Permission{ 13 | {Code: "getAllMember", Childs: []Permission{ 14 | {Code: "addMember"}, 15 | {Code: "updateMember", Childs: []Permission{ 16 | {Code: "upgradeMember"}, 17 | }}, 18 | {Code: "deleteMember"}, 19 | }}, 20 | }}, 21 | {Code: "merchant", Childs: []Permission{ 22 | {Code: "getAllMerchant", Childs: []Permission{ 23 | {Code: "addMerchant"}, 24 | {Code: "upgradeMerchant"}, 25 | }}, 26 | }}, 27 | }} 28 | 29 | allVisitedPath := perm.GetAllVisitedPath() 30 | var case1, case2, case3 []string 31 | 32 | for _, perm := range allVisitedPath["upgradeMember"] { 33 | case1 = append(case1, perm.Code) 34 | } 35 | assert.Equal(t, "user-service.member.getAllMember.updateMember", strings.Join(case1, ".")) 36 | 37 | for _, perm := range allVisitedPath["upgradeMerchant"] { 38 | case2 = append(case2, perm.Code) 39 | } 40 | assert.Equal(t, "user-service.merchant.getAllMerchant", strings.Join(case2, ".")) 41 | 42 | for _, perm := range allVisitedPath["getAllMerchant"] { 43 | case3 = append(case3, perm.Code) 44 | } 45 | assert.Equal(t, "user-service.merchant", strings.Join(case3, ".")) 46 | } 47 | -------------------------------------------------------------------------------- /services/master-service/pkg/shared/environment.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.5.6. 2 | 3 | package shared 4 | 5 | // Environment additional in this service 6 | type Environment struct { 7 | // more additional environment with struct tag is environment key example: 8 | // ExampleHost string `env:"EXAMPLE_HOST"` 9 | UserServiceHost string `env:"USER_SERVICE_HOST"` 10 | UserServiceAuthKey string `env:"USER_SERVICE_AUTH_KEY"` 11 | AuthServiceHost string `env:"AUTH_SERVICE_HOST"` 12 | AuthServiceKey string `env:"AUTH_SERVICE_KEY"` 13 | } 14 | 15 | var sharedEnv Environment 16 | 17 | // GetEnv get global additional environment 18 | func GetEnv() Environment { 19 | return sharedEnv 20 | } 21 | 22 | // SetEnv get global additional environment 23 | func SetEnv(env Environment) { 24 | sharedEnv = env 25 | } 26 | -------------------------------------------------------------------------------- /services/master-service/pkg/shared/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.5.6. DO NOT EDIT. 2 | 3 | package repository 4 | 5 | import ( 6 | "sync" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 9 | ) 10 | 11 | var ( 12 | once sync.Once 13 | ) 14 | 15 | // SetSharedRepository set the global singleton "RepoSQL" and "RepoMongo" implementation 16 | func SetSharedRepository(deps dependency.Dependency) { 17 | once.Do(func() { 18 | // setSharedRepoSQL(deps.GetSQLDatabase().ReadDB(), deps.GetSQLDatabase().WriteDB()) 19 | setSharedRepoMongo(deps.GetMongoDatabase().ReadDB(), deps.GetMongoDatabase().WriteDB()) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /services/master-service/pkg/shared/repository/repository_mongo.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package repository 4 | 5 | import ( 6 | aclrepo "monorepo/services/master-service/internal/modules/acl/repository" 7 | appsrepo "monorepo/services/master-service/internal/modules/apps/repository" 8 | 9 | "go.mongodb.org/mongo-driver/mongo" 10 | ) 11 | 12 | // RepoMongo uow 13 | type RepoMongo struct { 14 | readDB, writeDB *mongo.Database 15 | 16 | // register all repository from modules 17 | ACLRepo aclrepo.ACLRepository 18 | RoleRepo aclrepo.RoleRepository 19 | AppsRepo appsrepo.AppsRepository 20 | PermissionRepo appsrepo.PermissionRepository 21 | } 22 | 23 | var globalRepoMongo = new(RepoMongo) 24 | 25 | // setSharedRepoMongo set the global singleton "RepoMongo" implementation 26 | func setSharedRepoMongo(readDB, writeDB *mongo.Database) { 27 | globalRepoMongo = &RepoMongo{ 28 | readDB: readDB, writeDB: writeDB, 29 | ACLRepo: aclrepo.NewACLRepoMongo(readDB, writeDB), 30 | RoleRepo: aclrepo.NewRoleRepoMongo(readDB, writeDB), 31 | AppsRepo: appsrepo.NewAppsRepoMongo(readDB, writeDB), 32 | PermissionRepo: appsrepo.NewPermissionRepoMongo(readDB, writeDB), 33 | } 34 | } 35 | 36 | // GetSharedRepoMongo returns the global singleton "RepoMongo" implementation 37 | func GetSharedRepoMongo() *RepoMongo { 38 | return globalRepoMongo 39 | } 40 | -------------------------------------------------------------------------------- /services/master-service/pkg/shared/token_validator.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.5.6. 2 | 3 | package shared 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | 9 | "pkg.agungdp.dev/candi/candishared" 10 | ) 11 | 12 | // DefaultTokenValidator for token validator example 13 | type DefaultTokenValidator struct { 14 | } 15 | 16 | // ValidateToken implement TokenValidator 17 | func (v DefaultTokenValidator) ValidateToken(ctx context.Context, token string) (*candishared.TokenClaim, error) { 18 | var tokenClaim candishared.TokenClaim 19 | return &tokenClaim, nil 20 | } 21 | 22 | // DefaultACLPermissionChecker for acl permission checker example 23 | type DefaultACLPermissionChecker struct { 24 | } 25 | 26 | // CheckPermission implement interfaces.ACLPermissionChecker 27 | func (a DefaultACLPermissionChecker) CheckPermission(ctx context.Context, userID string, permissionCode string) (role string, err error) { 28 | // if permissionCode != "resource.public" { 29 | // return role, errors.New("Forbidden") 30 | // } 31 | fmt.Printf("users with id '%s' can access resource with permission code '%s' (return role for this user is 'superadmin')\n", userID, permissionCode) 32 | return "superadmin", nil 33 | } 34 | -------------------------------------------------------------------------------- /services/master-service/pkg/shared/usecase/common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "context" 5 | appsdomain "monorepo/services/master-service/internal/modules/apps/domain" 6 | ) 7 | 8 | var commonUC Usecase 9 | 10 | // Usecase common abstraction for bridging shared method inter usecase in module 11 | type Usecase interface { 12 | // method from another usecase 13 | GetDetailApp(ctx context.Context, appID string) (data appsdomain.AppDetail, err error) 14 | CheckPermission(ctx context.Context, userID string, permissionCode string) (role string, err error) 15 | } 16 | 17 | // SetCommonUsecase constructor 18 | func SetCommonUsecase(uc Usecase) { 19 | commonUC = uc 20 | } 21 | 22 | // GetCommonUsecase get common usecase 23 | func GetCommonUsecase() Usecase { 24 | return commonUC 25 | } 26 | -------------------------------------------------------------------------------- /services/master-service/pkg/shared/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.5.6. 2 | 3 | package usecase 4 | 5 | import ( 6 | aclusecase "monorepo/services/master-service/internal/modules/acl/usecase" 7 | appsusecase "monorepo/services/master-service/internal/modules/apps/usecase" 8 | "monorepo/services/master-service/pkg/shared/usecase/common" 9 | "sync" 10 | 11 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 12 | ) 13 | 14 | type ( 15 | // Usecase unit of work for all usecase in modules 16 | Usecase interface { 17 | ACL() aclusecase.ACLUsecase 18 | Apps() appsusecase.AppsUsecase 19 | } 20 | 21 | usecaseUow struct { 22 | aclusecase.ACLUsecase 23 | appsusecase.AppsUsecase 24 | } 25 | ) 26 | 27 | var usecaseInst *usecaseUow 28 | var once sync.Once 29 | 30 | // SetSharedUsecase set singleton usecase unit of work instance 31 | func SetSharedUsecase(deps dependency.Dependency) { 32 | once.Do(func() { 33 | usecaseInst = &usecaseUow{ 34 | ACLUsecase: aclusecase.NewACLUsecase(deps), 35 | AppsUsecase: appsusecase.NewAppsUsecase(deps), 36 | } 37 | common.SetCommonUsecase(usecaseInst) 38 | }) 39 | } 40 | 41 | // GetSharedUsecase get usecase unit of work instance 42 | func GetSharedUsecase() Usecase { 43 | return usecaseInst 44 | } 45 | func (uc *usecaseUow) ACL() aclusecase.ACLUsecase { 46 | return uc.ACLUsecase 47 | } 48 | func (uc *usecaseUow) Apps() appsusecase.AppsUsecase { 49 | return uc.AppsUsecase 50 | } 51 | -------------------------------------------------------------------------------- /services/notification-service/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | vendor 3 | main_service.go 4 | notification-service 5 | coverage.txt 6 | -------------------------------------------------------------------------------- /services/notification-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1 2 | FROM golang:1.14.9-alpine3.12 AS dependency_builder 3 | 4 | WORKDIR /go/src 5 | ENV GO111MODULE=on 6 | 7 | RUN apk update 8 | RUN apk add --no-cache bash ca-certificates git make 9 | 10 | COPY go.mod . 11 | COPY go.sum . 12 | 13 | RUN go mod download 14 | 15 | # Stage 2 16 | FROM dependency_builder AS service_builder 17 | 18 | WORKDIR /usr/app 19 | 20 | COPY . . 21 | RUN CGO_ENABLED=0 GOOS=linux go build -ldflags '-w -s' -a -o bin 22 | 23 | # Stage 3 24 | FROM alpine:latest 25 | 26 | RUN apk --no-cache add ca-certificates tzdata 27 | WORKDIR /root/ 28 | 29 | RUN mkdir -p /root/api 30 | RUN mkdir -p /root/cmd/notification-service 31 | RUN mkdir -p /root/config/key 32 | COPY --from=service_builder /usr/app/bin bin 33 | COPY --from=service_builder /usr/app/.env .env 34 | COPY --from=service_builder /usr/app/api /root/api 35 | 36 | ENTRYPOINT ["./bin"] 37 | -------------------------------------------------------------------------------- /services/notification-service/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : build run 2 | 3 | build: 4 | go build -o bin 5 | 6 | run: build 7 | ./bin 8 | 9 | proto: 10 | $(foreach proto_file, $(shell find api/proto -name '*.proto'),\ 11 | protoc --proto_path=api/proto --go_out=plugins=grpc:api/proto \ 12 | --go_opt=paths=source_relative $(proto_file);) 13 | 14 | docker: 15 | docker build -t notification-service:latest . 16 | 17 | run-container: 18 | docker run --name=notification-service --network="host" -d notification-service 19 | 20 | # unit test & calculate code coverage 21 | test: 22 | @if [ -f coverage.txt ]; then rm coverage.txt; fi; 23 | @echo ">> running unit test and calculate coverage" 24 | @go test ./... -cover -coverprofile=coverage.txt -covermode=count -coverpkg=$(PACKAGES) 25 | @go tool cover -func=coverage.txt 26 | 27 | clear: 28 | rm bin notification-service 29 | -------------------------------------------------------------------------------- /services/notification-service/api/graphql/_schema.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.3.1. 2 | 3 | schema { 4 | query: Query 5 | mutation: Mutation 6 | subscription: Subscription 7 | } 8 | 9 | type Query { 10 | pushNotif: PushNotifQueryModule 11 | } 12 | 13 | type Mutation { 14 | pushNotif: PushNotifMutationModule 15 | } 16 | 17 | type Subscription { 18 | pushNotif: PushNotifSubscriptionModule 19 | } -------------------------------------------------------------------------------- /services/notification-service/api/graphql/push-notif.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.3.1. 2 | 3 | ################### PushNotifModule Module Area 4 | type PushNotifQueryModule { 5 | hello(): String! 6 | } 7 | 8 | type PushNotifMutationModule { 9 | push(payload: PushPayload!): String! 10 | scheduledNotification(payload: ScheduledNotifPayload!): String! 11 | publishMessageToTopic(message: String!, topic: String!): SubscriberEvent! 12 | scheduledBroadcastEvent(scheduled_at: String!, event: EventPayload!): String! 13 | } 14 | 15 | type PushNotifSubscriptionModule { 16 | listenTopic(token: String!, topic: String!): SubscriberEvent! 17 | } 18 | 19 | type SubscriberEvent { 20 | topic: String! 21 | id: String! 22 | message: String! 23 | timestamp: Int! 24 | } 25 | 26 | input PushPayload { 27 | to: String! 28 | title: String! 29 | message: String! 30 | } 31 | 32 | input ScheduledNotifPayload { 33 | scheduledAt: String! 34 | data: PushPayload! 35 | } 36 | 37 | input EventPayload { 38 | id: String! 39 | topic: String! 40 | message: String! 41 | } 42 | -------------------------------------------------------------------------------- /services/notification-service/api/jsonschema/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "example", 4 | "title": "json schema type", 5 | "type": "object", 6 | "properties": {} 7 | } 8 | -------------------------------------------------------------------------------- /services/notification-service/api/proto/push-notif/push-notif.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | package pushnotif; 3 | option go_package = "monorepo/services/notification-service/api/proto/push-notif"; 4 | 5 | service PushNotifHandler { 6 | rpc Hello(Request) returns (Response); 7 | } 8 | 9 | message Request { 10 | string Message=1; 11 | } 12 | 13 | message Response { 14 | string Message=1; 15 | } -------------------------------------------------------------------------------- /services/notification-service/candi.json: -------------------------------------------------------------------------------- 1 | {"Version":"v1.4.0","Header":"Code generated by candi v1.4.0.","LibraryName":"pkg.agungdp.dev/candi","ServiceName":"notification-service","PackagePrefix":"monorepo/services/notification-service","ProtoSource":"monorepo/sdk/notification-service/proto","RestHandler":false,"GRPCHandler":false,"GraphQLHandler":true,"KafkaHandler":false,"SchedulerHandler":false,"RedisSubsHandler":true,"TaskQueueHandler":false,"IsWorkerActive":true,"RedisDeps":true,"SQLDeps":false,"MongoDeps":true,"SQLUseGORM":false,"SQLDriver":"","Modules":[{"ModuleName":"push-notif"}]} -------------------------------------------------------------------------------- /services/notification-service/deployments/k8s/notification-service.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungdwiprasetyo/backend-microservices/c216a6afdb64cc057c494299b54bd8ac9378ef2e/services/notification-service/deployments/k8s/notification-service.yml -------------------------------------------------------------------------------- /services/notification-service/docs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungdwiprasetyo/backend-microservices/c216a6afdb64cc057c494299b54bd8ac9378ef2e/services/notification-service/docs/.gitkeep -------------------------------------------------------------------------------- /services/notification-service/internal/modules/push-notif/delivery/graphqlhandler/field_resolver.go: -------------------------------------------------------------------------------- 1 | package graphqlhandler 2 | 3 | import "monorepo/services/notification-service/internal/modules/push-notif/domain" 4 | 5 | type pushInputResolver struct { 6 | Payload *domain.PushNotifRequestPayload 7 | } 8 | 9 | type scheduleNotifInputResolver struct { 10 | Payload struct { 11 | ScheduledAt string 12 | Data *domain.PushNotifRequestPayload 13 | } 14 | } 15 | 16 | type subscribeInputResolver struct { 17 | Token string 18 | Topic string 19 | } 20 | 21 | type inputTopicEvent struct { 22 | ID string 23 | Message string 24 | Topic string 25 | } 26 | -------------------------------------------------------------------------------- /services/notification-service/internal/modules/push-notif/delivery/graphqlhandler/query_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | 8 | "pkg.agungdp.dev/candi/candishared" 9 | "pkg.agungdp.dev/candi/tracer" 10 | ) 11 | 12 | type queryResolver struct { 13 | root *GraphQLHandler 14 | } 15 | 16 | // Hello resolver 17 | func (q *queryResolver) Hello(ctx context.Context) (string, error) { 18 | trace := tracer.StartTrace(ctx, "DeliveryGraphQL-Hello") 19 | defer trace.Finish() 20 | ctx = trace.Context() 21 | 22 | tokenClaim := candishared.ParseTokenClaimFromContext(ctx) // must using GraphQLBearerAuth in middleware for this resolver 23 | 24 | return q.root.uc.Hello(ctx) + ", with your session (" + tokenClaim.Audience + ")", nil 25 | } 26 | -------------------------------------------------------------------------------- /services/notification-service/internal/modules/push-notif/delivery/graphqlhandler/root_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "monorepo/services/notification-service/internal/modules/push-notif/usecase" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/types" 9 | "pkg.agungdp.dev/candi/codebase/interfaces" 10 | ) 11 | 12 | // GraphQLHandler model 13 | type GraphQLHandler struct { 14 | mw interfaces.Middleware 15 | uc usecase.PushNotifUsecase 16 | validator interfaces.Validator 17 | } 18 | 19 | // NewGraphQLHandler delivery 20 | func NewGraphQLHandler(mw interfaces.Middleware, uc usecase.PushNotifUsecase, validator interfaces.Validator) *GraphQLHandler { 21 | return &GraphQLHandler{ 22 | mw: mw, uc: uc, validator: validator, 23 | } 24 | } 25 | 26 | // RegisterMiddleware register resolver based on schema in "api/graphql/*" path 27 | func (h *GraphQLHandler) RegisterMiddleware(mwGroup *types.MiddlewareGroup) { 28 | mwGroup.Add("PushNotifQueryModule.hello", h.mw.GraphQLBearerAuth) 29 | mwGroup.Add("PushNotifMutationModule.hello", h.mw.GraphQLBasicAuth) 30 | } 31 | 32 | // Query method 33 | func (h *GraphQLHandler) Query() interface{} { 34 | return &queryResolver{root: h} 35 | } 36 | 37 | // Mutation method 38 | func (h *GraphQLHandler) Mutation() interface{} { 39 | return &mutationResolver{root: h} 40 | } 41 | 42 | // Subscription method 43 | func (h *GraphQLHandler) Subscription() interface{} { 44 | return &subscriptionResolver{root: h} 45 | } 46 | -------------------------------------------------------------------------------- /services/notification-service/internal/modules/push-notif/delivery/graphqlhandler/subscription_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | 8 | "monorepo/services/notification-service/internal/modules/push-notif/domain" 9 | 10 | "pkg.agungdp.dev/candi/candishared" 11 | ) 12 | 13 | type subscriptionResolver struct { 14 | root *GraphQLHandler 15 | } 16 | 17 | func (s *subscriptionResolver) ListenTopic(ctx context.Context, input subscribeInputResolver) (<-chan *domain.Event, error) { 18 | 19 | tokenClaim := candishared.ParseTokenClaimFromContext(ctx) 20 | 21 | clientID := tokenClaim.Subject 22 | return s.root.uc.AddSubscriber(ctx, clientID, input.Topic), nil 23 | } 24 | -------------------------------------------------------------------------------- /services/notification-service/internal/modules/push-notif/delivery/grpchandler/grpchandler.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package grpchandler 4 | 5 | import ( 6 | "monorepo/services/notification-service/internal/modules/push-notif/usecase" 7 | 8 | "google.golang.org/grpc" 9 | 10 | "pkg.agungdp.dev/candi/codebase/factory/types" 11 | "pkg.agungdp.dev/candi/codebase/interfaces" 12 | ) 13 | 14 | // GRPCHandler rpc handler 15 | type GRPCHandler struct { 16 | mw interfaces.Middleware 17 | uc usecase.PushNotifUsecase 18 | validator interfaces.Validator 19 | } 20 | 21 | // NewGRPCHandler func 22 | func NewGRPCHandler(mw interfaces.Middleware, uc usecase.PushNotifUsecase, validator interfaces.Validator) *GRPCHandler { 23 | return &GRPCHandler{ 24 | mw: mw, uc: uc, validator: validator, 25 | } 26 | } 27 | 28 | // Register grpc server 29 | func (h *GRPCHandler) Register(server *grpc.Server, mwGroup *types.MiddlewareGroup) { 30 | // proto.RegisterPushNotifHandlerServer(server, h) 31 | 32 | // // register middleware for method 33 | // mwGroup.AddProto(proto.File_push_notif_push_notif_proto, "Hello", h.mw.GRPCBearerAuth) 34 | // } 35 | 36 | // // Hello rpc method 37 | // func (h *GRPCHandler) Hello(ctx context.Context, req *proto.Request) (*proto.Response, error) { 38 | // trace := tracer.StartTrace(ctx, "PushNotifDeliveryGRPC:Hello") 39 | // defer trace.Finish() 40 | // ctx = trace.Context() 41 | 42 | // tokenClaim := candishared.ParseTokenClaimFromContext(ctx) // must using GRPCBearerAuth in middleware for this handler 43 | 44 | // return &proto.Response{ 45 | // Message: h.uc.Hello(ctx) + ", with your session (" + tokenClaim.Audience + ")", 46 | // }, nil 47 | } 48 | -------------------------------------------------------------------------------- /services/notification-service/internal/modules/push-notif/delivery/resthandler/resthandler.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package resthandler 4 | 5 | import ( 6 | "net/http" 7 | 8 | "github.com/labstack/echo" 9 | 10 | "monorepo/services/notification-service/internal/modules/push-notif/usecase" 11 | 12 | "pkg.agungdp.dev/candi/candihelper" 13 | "pkg.agungdp.dev/candi/candishared" 14 | "pkg.agungdp.dev/candi/codebase/interfaces" 15 | "pkg.agungdp.dev/candi/tracer" 16 | "pkg.agungdp.dev/candi/wrapper" 17 | ) 18 | 19 | // RestHandler handler 20 | type RestHandler struct { 21 | mw interfaces.Middleware 22 | uc usecase.PushNotifUsecase 23 | validator interfaces.Validator 24 | } 25 | 26 | // NewRestHandler create new rest handler 27 | func NewRestHandler(mw interfaces.Middleware, uc usecase.PushNotifUsecase, validator interfaces.Validator) *RestHandler { 28 | return &RestHandler{ 29 | mw: mw, uc: uc, validator: validator, 30 | } 31 | } 32 | 33 | // Mount handler with root "/" 34 | // handling version in here 35 | func (h *RestHandler) Mount(root *echo.Group) { 36 | v1Root := root.Group(candihelper.V1) 37 | 38 | pushnotif := v1Root.Group("/pushnotif") 39 | pushnotif.GET("", h.hello, echo.WrapMiddleware(h.mw.HTTPBearerAuth)) 40 | } 41 | 42 | func (h *RestHandler) hello(c echo.Context) error { 43 | trace := tracer.StartTrace(c.Request().Context(), "DeliveryREST:Hello") 44 | defer trace.Finish() 45 | ctx := trace.Context() 46 | 47 | tokenClaim := c.Get(string(candishared.ContextKeyTokenClaim)).(*candishared.TokenClaim) // must using HTTPBearerAuth in middleware for this handler 48 | 49 | return wrapper.NewHTTPResponse(http.StatusOK, h.uc.Hello(ctx)+", with your session ("+tokenClaim.Audience+")").JSON(c.Response()) 50 | } 51 | -------------------------------------------------------------------------------- /services/notification-service/internal/modules/push-notif/domain/domain.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | // PushRequest model 4 | type PushRequest struct { 5 | To string `json:"to"` 6 | Notification *Notification `json:"notification"` 7 | Data map[string]interface{} `json:"data"` 8 | } 9 | 10 | // Notification model 11 | type Notification struct { 12 | Title string `json:"title"` 13 | Body string `json:"body"` 14 | Image string `json:"image"` 15 | Sound string `json:"sound"` 16 | MutableContent bool `json:"mutable-content"` 17 | ResourceID string `json:"resourceId"` 18 | ResourceName string `json:"resoureceName"` 19 | } 20 | 21 | // PushResponse response data 22 | type PushResponse struct { 23 | Error bool `json:"error"` 24 | Body interface{} `json:"body"` 25 | } 26 | 27 | // Event domain 28 | type Event struct { 29 | ID string `json:"id"` 30 | Topic string `json:"topic"` 31 | Message string `json:"message"` 32 | Timestamp int `json:"timestamp"` 33 | } 34 | -------------------------------------------------------------------------------- /services/notification-service/internal/modules/push-notif/domain/request_payload.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | // PushNotifRequestPayload model payload 4 | type PushNotifRequestPayload struct { 5 | To string `json:"to"` 6 | Title string `json:"title"` 7 | Message string `json:"message"` 8 | } 9 | -------------------------------------------------------------------------------- /services/notification-service/internal/modules/push-notif/domain/topic.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "time" 4 | 5 | type Topic struct { 6 | ID string `bson:"_id" json:"id"` 7 | Name string `bson:"name" json:"name"` 8 | Subscribers []*Subscriber `bson:"subscribers" json:"subscribers"` 9 | CreatedAt time.Time `bson:"createdAt" json:"createdAt"` 10 | ModifiedAt time.Time `bson:"modifiedAt" json:"modifiedAt"` 11 | } 12 | 13 | type Subscriber struct { 14 | ID string `bson:"id" json:"id"` 15 | Topic string `bson:"-" json:"topic"` 16 | Name string `bson:"name" json:"name"` 17 | IsActive bool `bson:"isActive" json:"isActive"` 18 | CreatedAt time.Time `bson:"createdAt" json:"createdAt"` 19 | ModifiedAt time.Time `bson:"modifiedAt" json:"modifiedAt"` 20 | 21 | Events chan<- *Event `bson:"-" json:"-"` 22 | } 23 | -------------------------------------------------------------------------------- /services/notification-service/internal/modules/push-notif/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/notification-service/internal/modules/push-notif/domain" 8 | 9 | "pkg.agungdp.dev/candi/candishared" 10 | ) 11 | 12 | // PushNotifRepository abstract interface 13 | type PushNotifRepository interface { 14 | // add method 15 | FindHello(ctx context.Context) (string, error) 16 | Save(ctx context.Context, data *domain.Topic) <-chan error 17 | FindTopic(ctx context.Context, where domain.Topic) <-chan candishared.Result 18 | RemoveSubscriber(ctx context.Context, subscriber *domain.Subscriber) <-chan error 19 | FindSubscriber(ctx context.Context, topicName string, subscriber *domain.Subscriber) <-chan candishared.Result 20 | } 21 | -------------------------------------------------------------------------------- /services/notification-service/internal/modules/push-notif/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package usecase 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/notification-service/internal/modules/push-notif/domain" 8 | "time" 9 | ) 10 | 11 | // PushNotifUsecase abstraction 12 | type PushNotifUsecase interface { 13 | // add method 14 | Hello(ctx context.Context) string 15 | 16 | SendNotification(ctx context.Context, request *domain.PushNotifRequestPayload) error 17 | SendScheduledNotification(ctx context.Context, scheduledAt time.Time, request *domain.PushNotifRequestPayload) (err error) 18 | SendScheduledEvent(ctx context.Context, scheduledAt time.Time, request *domain.Event) (err error) 19 | 20 | PublishMessageToTopic(ctx context.Context, event *domain.Event) (*domain.Event, error) 21 | AddSubscriber(ctx context.Context, clientID, topic string) <-chan *domain.Event 22 | } 23 | -------------------------------------------------------------------------------- /services/notification-service/main.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "runtime/debug" 8 | 9 | "pkg.agungdp.dev/candi/codebase/app" 10 | "pkg.agungdp.dev/candi/config" 11 | 12 | service "monorepo/services/notification-service/internal" 13 | ) 14 | 15 | const serviceName = "notification-service" 16 | 17 | func main() { 18 | defer func() { 19 | if r := recover(); r != nil { 20 | fmt.Printf("\x1b[31;1mFailed to start %s service: %v\x1b[0m\n", serviceName, r) 21 | fmt.Printf("Stack trace: \n%s\n", debug.Stack()) 22 | } 23 | }() 24 | 25 | cfg := config.Init(serviceName) 26 | defer cfg.Exit() 27 | 28 | srv := service.NewService(serviceName, cfg) 29 | app.New(srv).Run() 30 | } 31 | -------------------------------------------------------------------------------- /services/notification-service/pkg/helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper -------------------------------------------------------------------------------- /services/notification-service/pkg/shared/domain/push-notif.go: -------------------------------------------------------------------------------- 1 | package domain -------------------------------------------------------------------------------- /services/notification-service/pkg/shared/environment.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package shared 4 | 5 | // Environment additional in this service 6 | type Environment struct { 7 | // more additional environment with struct tag is environment key example: 8 | // ExampleHost string `env:"EXAMPLE_HOST"` 9 | 10 | AuthServiceHost string `env:"AUTH_SERVICE_HOST"` 11 | AuthServiceKey string `env:"AUTH_SERVICE_KEY"` 12 | } 13 | 14 | var sharedEnv Environment 15 | 16 | // GetEnv get global additional environment 17 | func GetEnv() Environment { 18 | return sharedEnv 19 | } 20 | 21 | // SetEnv get global additional environment 22 | func SetEnv(env Environment) { 23 | sharedEnv = env 24 | } 25 | -------------------------------------------------------------------------------- /services/notification-service/pkg/shared/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. DO NOT EDIT. 2 | 3 | package repository 4 | 5 | import ( 6 | "sync" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 9 | ) 10 | 11 | var ( 12 | once sync.Once 13 | ) 14 | 15 | // SetSharedRepository set the global singleton "RepoSQL" and "RepoMongo" implementation 16 | func SetSharedRepository(deps dependency.Dependency) { 17 | once.Do(func() { 18 | // setSharedRepoSQL(deps.GetSQLDatabase().ReadDB(), deps.GetSQLDatabase().WriteDB()) 19 | setSharedRepoMongo(deps.GetMongoDatabase().ReadDB(), deps.GetMongoDatabase().WriteDB()) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /services/notification-service/pkg/shared/repository/repository_mongo.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. DO NOT EDIT. 2 | 3 | package repository 4 | 5 | import ( 6 | "go.mongodb.org/mongo-driver/mongo" 7 | pushnotifrepo "monorepo/services/notification-service/internal/modules/push-notif/repository" 8 | ) 9 | 10 | // RepoMongo uow 11 | type RepoMongo struct { 12 | readDB, writeDB *mongo.Database 13 | 14 | // register all repository from modules 15 | PushNotifRepo pushnotifrepo.PushNotifRepository 16 | } 17 | 18 | var globalRepoMongo = new(RepoMongo) 19 | 20 | // setSharedRepoMongo set the global singleton "RepoMongo" implementation 21 | func setSharedRepoMongo(readDB, writeDB *mongo.Database) { 22 | globalRepoMongo = &RepoMongo{ 23 | readDB: readDB, writeDB: writeDB, 24 | PushNotifRepo: pushnotifrepo.NewPushNotifRepoMongo(readDB, writeDB), 25 | } 26 | } 27 | 28 | // GetSharedRepoMongo returns the global singleton "RepoMongo" implementation 29 | func GetSharedRepoMongo() *RepoMongo { 30 | return globalRepoMongo 31 | } 32 | -------------------------------------------------------------------------------- /services/notification-service/pkg/shared/token_validator.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package shared 4 | 5 | import ( 6 | "context" 7 | 8 | "pkg.agungdp.dev/candi/candishared" 9 | ) 10 | 11 | // DefaultTokenValidator for token validator 12 | type DefaultTokenValidator struct { 13 | } 14 | 15 | // ValidateToken implement TokenValidator 16 | func (v *DefaultTokenValidator) ValidateToken(ctx context.Context, token string) (*candishared.TokenClaim, error) { 17 | return &candishared.TokenClaim{}, nil 18 | } 19 | -------------------------------------------------------------------------------- /services/notification-service/pkg/shared/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package usecase 4 | 5 | import ( 6 | pushnotifusecase "monorepo/services/notification-service/internal/modules/push-notif/usecase" 7 | "sync" 8 | 9 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 10 | ) 11 | 12 | type ( 13 | // Usecase unit of work for all usecase in modules 14 | Usecase interface { 15 | PushNotif() pushnotifusecase.PushNotifUsecase 16 | } 17 | 18 | usecaseUow struct { 19 | pushnotif pushnotifusecase.PushNotifUsecase 20 | } 21 | ) 22 | 23 | var usecaseInst *usecaseUow 24 | var once sync.Once 25 | 26 | // SetSharedUsecase set singleton usecase unit of work instance 27 | func SetSharedUsecase(deps dependency.Dependency) { 28 | once.Do(func() { 29 | usecaseInst = &usecaseUow{ 30 | pushnotif: pushnotifusecase.NewPushNotifUsecase(deps), 31 | } 32 | }) 33 | } 34 | 35 | // GetSharedUsecase get usecase unit of work instance 36 | func GetSharedUsecase() Usecase { 37 | return usecaseInst 38 | } 39 | func (uc *usecaseUow) PushNotif() pushnotifusecase.PushNotifUsecase { 40 | return uc.pushnotif 41 | } 42 | -------------------------------------------------------------------------------- /services/order-service/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | vendor 3 | main_service.go 4 | order-service 5 | coverage.txt 6 | -------------------------------------------------------------------------------- /services/order-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1 2 | FROM golang:1.14.9-alpine3.12 AS dependency_builder 3 | 4 | WORKDIR /go/src 5 | ENV GO111MODULE=on 6 | 7 | RUN apk update 8 | RUN apk add --no-cache bash ca-certificates git make 9 | 10 | COPY go.mod . 11 | COPY go.sum . 12 | 13 | RUN go mod download 14 | 15 | # Stage 2 16 | FROM dependency_builder AS service_builder 17 | 18 | WORKDIR /usr/app 19 | 20 | COPY . . 21 | RUN CGO_ENABLED=0 GOOS=linux go build -ldflags '-w -s' -a -o bin 22 | 23 | # Stage 3 24 | FROM alpine:latest 25 | 26 | RUN apk --no-cache add ca-certificates tzdata 27 | WORKDIR /root/ 28 | 29 | RUN mkdir -p /root/api 30 | RUN mkdir -p /root/cmd/order-service 31 | RUN mkdir -p /root/config/key 32 | COPY --from=service_builder /usr/app/bin bin 33 | COPY --from=service_builder /usr/app/.env .env 34 | COPY --from=service_builder /usr/app/api /root/api 35 | 36 | ENTRYPOINT ["./bin"] 37 | -------------------------------------------------------------------------------- /services/order-service/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : build run 2 | 3 | build: 4 | go build -o bin 5 | 6 | run: build 7 | ./bin 8 | 9 | proto: 10 | $(foreach proto_file, $(shell find api/proto -name '*.proto'),\ 11 | protoc --proto_path=api/proto --go_out=plugins=grpc:api/proto \ 12 | --go_opt=paths=source_relative $(proto_file);) 13 | 14 | docker: 15 | docker build -t order-service:latest . 16 | 17 | run-container: 18 | docker run --name=order-service --network="host" -d order-service 19 | 20 | # unit test & calculate code coverage 21 | test: 22 | @if [ -f coverage.txt ]; then rm coverage.txt; fi; 23 | @echo ">> running unit test and calculate coverage" 24 | @go test ./... -cover -coverprofile=coverage.txt -covermode=count -coverpkg=$(PACKAGES) 25 | @go tool cover -func=coverage.txt 26 | 27 | clear: 28 | rm bin order-service 29 | -------------------------------------------------------------------------------- /services/order-service/README.md: -------------------------------------------------------------------------------- 1 | # Order-Service 2 | 3 | ## Build and run service 4 | ``` 5 | $ make run 6 | ``` 7 | If include GRPC handler, run `$ make proto` for generate rpc files from proto (must install `protoc` compiler min version `libprotoc 3.14.0`) 8 | 9 | ## Run unit test & calculate code coverage 10 | ``` 11 | $ make test 12 | ``` 13 | 14 | ## Create docker image 15 | ``` 16 | $ make docker 17 | ``` 18 | -------------------------------------------------------------------------------- /services/order-service/api/graphql/_schema.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.3.3. 2 | 3 | schema { 4 | query: Query 5 | mutation: Mutation 6 | subscription: Subscription 7 | } 8 | 9 | type Query { 10 | master: MasterQueryModule 11 | order: OrderQueryModule 12 | } 13 | 14 | type Mutation { 15 | master: MasterMutationModule 16 | order: OrderMutationModule 17 | } 18 | 19 | type Subscription { 20 | master: MasterSubscriptionModule 21 | order: OrderSubscriptionModule 22 | } 23 | -------------------------------------------------------------------------------- /services/order-service/api/graphql/master.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.3.3. 2 | 3 | # MasterModule Module Area 4 | type MasterQueryModule { 5 | hello(): String! 6 | } 7 | 8 | type MasterMutationModule { 9 | hello(): String! 10 | } 11 | 12 | type MasterSubscriptionModule { 13 | hello(): String! 14 | } 15 | -------------------------------------------------------------------------------- /services/order-service/api/graphql/order.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.3.3. 2 | 3 | # OrderModule Module Area 4 | type OrderQueryModule { 5 | hello(): String! 6 | } 7 | 8 | type OrderMutationModule { 9 | hello(): String! 10 | } 11 | 12 | type OrderSubscriptionModule { 13 | hello(): String! 14 | } 15 | -------------------------------------------------------------------------------- /services/order-service/api/jsonschema/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "example", 4 | "title": "json schema type", 5 | "type": "object", 6 | "properties": {} 7 | } 8 | -------------------------------------------------------------------------------- /services/order-service/api/proto/master/master.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | package master; 3 | option go_package = "monorepo/services/order-service/api/proto/master"; 4 | 5 | service MasterHandler { 6 | rpc Hello(Request) returns (Response); 7 | } 8 | 9 | message Request { 10 | string Message=1; 11 | } 12 | 13 | message Response { 14 | string Message=1; 15 | } -------------------------------------------------------------------------------- /services/order-service/api/proto/order/order.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | package order; 3 | option go_package = "monorepo/services/order-service/api/proto/order"; 4 | 5 | service OrderHandler { 6 | rpc Hello(Request) returns (Response); 7 | } 8 | 9 | message Request { 10 | string Message=1; 11 | } 12 | 13 | message Response { 14 | string Message=1; 15 | } -------------------------------------------------------------------------------- /services/order-service/candi.json: -------------------------------------------------------------------------------- 1 | {"Version":"v1.4.0","Header":"Code generated by candi v1.4.0.","LibraryName":"pkg.agungdp.dev/candi","ServiceName":"order-service","PackagePrefix":"monorepo/services/order-service","ProtoSource":"monorepo/sdk/order-service/proto","RestHandler":false,"GRPCHandler":true,"GraphQLHandler":true,"KafkaHandler":false,"SchedulerHandler":false,"RedisSubsHandler":false,"TaskQueueHandler":false,"IsWorkerActive":false,"RedisDeps":true,"SQLDeps":false,"MongoDeps":true,"SQLUseGORM":false,"SQLDriver":"","Modules":[{"ModuleName":"master"},{"ModuleName":"order"}]} -------------------------------------------------------------------------------- /services/order-service/deployments/k8s/order-service.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungdwiprasetyo/backend-microservices/c216a6afdb64cc057c494299b54bd8ac9378ef2e/services/order-service/deployments/k8s/order-service.yml -------------------------------------------------------------------------------- /services/order-service/docs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungdwiprasetyo/backend-microservices/c216a6afdb64cc057c494299b54bd8ac9378ef2e/services/order-service/docs/.gitkeep -------------------------------------------------------------------------------- /services/order-service/internal/modules/master/delivery/graphqlhandler/mutation_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package graphqlhandler 4 | 5 | import "context" 6 | 7 | type mutationResolver struct { 8 | root *GraphQLHandler 9 | } 10 | 11 | // Hello resolver 12 | func (m *mutationResolver) Hello(ctx context.Context) (string, error) { 13 | return "Hello", nil 14 | } 15 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/master/delivery/graphqlhandler/query_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | 8 | "pkg.agungdp.dev/candi/candishared" 9 | "pkg.agungdp.dev/candi/tracer" 10 | ) 11 | 12 | type queryResolver struct { 13 | root *GraphQLHandler 14 | } 15 | 16 | // Hello resolver 17 | func (q *queryResolver) Hello(ctx context.Context) (string, error) { 18 | trace := tracer.StartTrace(ctx, "DeliveryGraphQL-Hello") 19 | defer trace.Finish() 20 | ctx = trace.Context() 21 | 22 | tokenClaim := candishared.ParseTokenClaimFromContext(ctx) // must using GraphQLBearerAuth in middleware for this resolver 23 | 24 | return q.root.uc.Hello(ctx) + ", with your session (" + tokenClaim.Audience + ")", nil 25 | } 26 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/master/delivery/graphqlhandler/root_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "monorepo/services/order-service/internal/modules/master/usecase" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/types" 9 | "pkg.agungdp.dev/candi/codebase/interfaces" 10 | ) 11 | 12 | // GraphQLHandler model 13 | type GraphQLHandler struct { 14 | mw interfaces.Middleware 15 | uc usecase.MasterUsecase 16 | validator interfaces.Validator 17 | } 18 | 19 | // NewGraphQLHandler delivery 20 | func NewGraphQLHandler(mw interfaces.Middleware, uc usecase.MasterUsecase, validator interfaces.Validator) *GraphQLHandler { 21 | return &GraphQLHandler{ 22 | mw: mw, uc: uc, validator: validator, 23 | } 24 | } 25 | 26 | // RegisterMiddleware register resolver based on schema in "api/graphql/*" path 27 | func (h *GraphQLHandler) RegisterMiddleware(mwGroup *types.MiddlewareGroup) { 28 | mwGroup.Add("MasterQueryModule.hello", h.mw.GraphQLBearerAuth) 29 | mwGroup.Add("MasterMutationModule.hello", h.mw.GraphQLBasicAuth) 30 | } 31 | 32 | // Query method 33 | func (h *GraphQLHandler) Query() interface{} { 34 | return &queryResolver{root: h} 35 | } 36 | 37 | // Mutation method 38 | func (h *GraphQLHandler) Mutation() interface{} { 39 | return &mutationResolver{root: h} 40 | } 41 | 42 | // Subscription method 43 | func (h *GraphQLHandler) Subscription() interface{} { 44 | return &subscriptionResolver{root: h} 45 | } 46 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/master/delivery/graphqlhandler/subscription_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package graphqlhandler 4 | 5 | import "context" 6 | 7 | type subscriptionResolver struct { 8 | root *GraphQLHandler 9 | } 10 | 11 | // Hello resolver 12 | func (s *subscriptionResolver) Hello(ctx context.Context) <-chan string { 13 | output := make(chan string) 14 | 15 | go func() { 16 | output <- "Hello" 17 | }() 18 | 19 | return output 20 | } 21 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/master/delivery/grpchandler/grpchandler.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package grpchandler 4 | 5 | import ( 6 | "context" 7 | 8 | proto "monorepo/sdk/order-service/proto/master" 9 | "monorepo/services/order-service/internal/modules/master/usecase" 10 | 11 | "google.golang.org/grpc" 12 | 13 | "pkg.agungdp.dev/candi/candishared" 14 | "pkg.agungdp.dev/candi/codebase/factory/types" 15 | "pkg.agungdp.dev/candi/codebase/interfaces" 16 | "pkg.agungdp.dev/candi/tracer" 17 | ) 18 | 19 | // GRPCHandler rpc handler 20 | type GRPCHandler struct { 21 | mw interfaces.Middleware 22 | uc usecase.MasterUsecase 23 | validator interfaces.Validator 24 | } 25 | 26 | // NewGRPCHandler func 27 | func NewGRPCHandler(mw interfaces.Middleware, uc usecase.MasterUsecase, validator interfaces.Validator) *GRPCHandler { 28 | return &GRPCHandler{ 29 | mw: mw, uc: uc, validator: validator, 30 | } 31 | } 32 | 33 | // Register grpc server 34 | func (h *GRPCHandler) Register(server *grpc.Server, mwGroup *types.MiddlewareGroup) { 35 | proto.RegisterMasterHandlerServer(server, h) 36 | 37 | // register middleware for method 38 | mwGroup.AddProto(proto.File_master_master_proto, "Hello", h.mw.GRPCBearerAuth) 39 | } 40 | 41 | // Hello rpc method 42 | func (h *GRPCHandler) Hello(ctx context.Context, req *proto.Request) (*proto.Response, error) { 43 | trace := tracer.StartTrace(ctx, "MasterDeliveryGRPC:Hello") 44 | defer trace.Finish() 45 | ctx = trace.Context() 46 | 47 | tokenClaim := candishared.ParseTokenClaimFromContext(ctx) // must using GRPCBearerAuth in middleware for this handler 48 | 49 | return &proto.Response{ 50 | Message: h.uc.Hello(ctx) + ", with your session (" + tokenClaim.Audience + ")", 51 | }, nil 52 | } 53 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/master/domain/payload.go: -------------------------------------------------------------------------------- 1 | package domain -------------------------------------------------------------------------------- /services/order-service/internal/modules/master/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | // MasterRepository abstract interface 10 | type MasterRepository interface { 11 | // add method 12 | FindHello(ctx context.Context) (string, error) 13 | } 14 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/master/repository/repository_mongo.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | 8 | "go.mongodb.org/mongo-driver/mongo" 9 | 10 | "pkg.agungdp.dev/candi/tracer" 11 | ) 12 | 13 | type masterRepoMongo struct { 14 | readDB, writeDB *mongo.Database 15 | } 16 | 17 | // NewMasterRepoMongo mongo repo constructor 18 | func NewMasterRepoMongo(readDB, writeDB *mongo.Database) MasterRepository { 19 | return &masterRepoMongo{ 20 | readDB, writeDB, 21 | } 22 | } 23 | 24 | func (r *masterRepoMongo) FindHello(ctx context.Context) (string, error) { 25 | trace := tracer.StartTrace(ctx, "MasterRepoMongo:FindHello") 26 | defer trace.Finish() 27 | 28 | return "Hello from repo mongo layer", nil 29 | } 30 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/master/repository/repository_sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | "database/sql" 8 | 9 | "pkg.agungdp.dev/candi/tracer" 10 | ) 11 | 12 | type masterRepoSQL struct { 13 | readDB, writeDB *sql.DB 14 | tx *sql.Tx 15 | } 16 | 17 | // NewMasterRepoSQL mongo repo constructor 18 | func NewMasterRepoSQL(readDB, writeDB *sql.DB, tx *sql.Tx) MasterRepository { 19 | return &masterRepoSQL{ 20 | readDB, writeDB, tx, 21 | } 22 | } 23 | 24 | func (r *masterRepoSQL) FindHello(ctx context.Context) (string, error) { 25 | trace := tracer.StartTrace(ctx, "MasterRepoSQL:FindHello") 26 | defer trace.Finish() 27 | 28 | return "Hello from repo sql layer", nil 29 | } 30 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/master/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package usecase 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | // MasterUsecase abstraction 10 | type MasterUsecase interface { 11 | // add method 12 | Hello(ctx context.Context) string 13 | } 14 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/master/usecase/usecase_impl.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package usecase 4 | 5 | import ( 6 | "context" 7 | 8 | "monorepo/services/order-service/pkg/shared/repository" 9 | 10 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 11 | "pkg.agungdp.dev/candi/codebase/interfaces" 12 | "pkg.agungdp.dev/candi/tracer" 13 | ) 14 | 15 | type masterUsecaseImpl struct { 16 | cache interfaces.Cache 17 | 18 | repoMongo *repository.RepoMongo 19 | } 20 | 21 | // NewMasterUsecase usecase impl constructor 22 | func NewMasterUsecase(deps dependency.Dependency) MasterUsecase { 23 | return &masterUsecaseImpl{ 24 | cache: deps.GetRedisPool().Cache(), 25 | 26 | repoMongo: repository.GetSharedRepoMongo(), 27 | } 28 | } 29 | 30 | func (uc *masterUsecaseImpl) Hello(ctx context.Context) (msg string) { 31 | trace := tracer.StartTrace(ctx, "MasterUsecase:Hello") 32 | defer trace.Finish() 33 | ctx = trace.Context() 34 | 35 | msg, _ = uc.repoMongo.MasterRepo.FindHello(ctx) 36 | return 37 | } 38 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/order/delivery/graphqlhandler/mutation_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package graphqlhandler 4 | 5 | import "context" 6 | 7 | type mutationResolver struct { 8 | root *GraphQLHandler 9 | } 10 | 11 | // Hello resolver 12 | func (m *mutationResolver) Hello(ctx context.Context) (string, error) { 13 | return "Hello", nil 14 | } 15 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/order/delivery/graphqlhandler/query_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | 8 | "pkg.agungdp.dev/candi/candishared" 9 | "pkg.agungdp.dev/candi/tracer" 10 | ) 11 | 12 | type queryResolver struct { 13 | root *GraphQLHandler 14 | } 15 | 16 | // Hello resolver 17 | func (q *queryResolver) Hello(ctx context.Context) (string, error) { 18 | trace := tracer.StartTrace(ctx, "DeliveryGraphQL-Hello") 19 | defer trace.Finish() 20 | ctx = trace.Context() 21 | 22 | tokenClaim := candishared.ParseTokenClaimFromContext(ctx) // must using GraphQLBearerAuth in middleware for this resolver 23 | 24 | return q.root.uc.Hello(ctx) + ", with your session (" + tokenClaim.Audience + ")", nil 25 | } 26 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/order/delivery/graphqlhandler/root_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "monorepo/services/order-service/internal/modules/order/usecase" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/types" 9 | "pkg.agungdp.dev/candi/codebase/interfaces" 10 | ) 11 | 12 | // GraphQLHandler model 13 | type GraphQLHandler struct { 14 | mw interfaces.Middleware 15 | uc usecase.OrderUsecase 16 | validator interfaces.Validator 17 | } 18 | 19 | // NewGraphQLHandler delivery 20 | func NewGraphQLHandler(mw interfaces.Middleware, uc usecase.OrderUsecase, validator interfaces.Validator) *GraphQLHandler { 21 | return &GraphQLHandler{ 22 | mw: mw, uc: uc, validator: validator, 23 | } 24 | } 25 | 26 | // RegisterMiddleware register resolver based on schema in "api/graphql/*" path 27 | func (h *GraphQLHandler) RegisterMiddleware(mwGroup *types.MiddlewareGroup) { 28 | mwGroup.Add("OrderQueryModule.hello", h.mw.GraphQLBearerAuth) 29 | mwGroup.Add("OrderMutationModule.hello", h.mw.GraphQLBasicAuth) 30 | } 31 | 32 | // Query method 33 | func (h *GraphQLHandler) Query() interface{} { 34 | return &queryResolver{root: h} 35 | } 36 | 37 | // Mutation method 38 | func (h *GraphQLHandler) Mutation() interface{} { 39 | return &mutationResolver{root: h} 40 | } 41 | 42 | // Subscription method 43 | func (h *GraphQLHandler) Subscription() interface{} { 44 | return &subscriptionResolver{root: h} 45 | } 46 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/order/delivery/graphqlhandler/subscription_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package graphqlhandler 4 | 5 | import "context" 6 | 7 | type subscriptionResolver struct { 8 | root *GraphQLHandler 9 | } 10 | 11 | // Hello resolver 12 | func (s *subscriptionResolver) Hello(ctx context.Context) <-chan string { 13 | output := make(chan string) 14 | 15 | go func() { 16 | output <- "Hello" 17 | }() 18 | 19 | return output 20 | } 21 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/order/delivery/grpchandler/grpchandler.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package grpchandler 4 | 5 | import ( 6 | "context" 7 | 8 | proto "monorepo/sdk/order-service/proto/order" 9 | "monorepo/services/order-service/internal/modules/order/usecase" 10 | 11 | "google.golang.org/grpc" 12 | 13 | "pkg.agungdp.dev/candi/candishared" 14 | "pkg.agungdp.dev/candi/codebase/factory/types" 15 | "pkg.agungdp.dev/candi/codebase/interfaces" 16 | "pkg.agungdp.dev/candi/tracer" 17 | ) 18 | 19 | // GRPCHandler rpc handler 20 | type GRPCHandler struct { 21 | mw interfaces.Middleware 22 | uc usecase.OrderUsecase 23 | validator interfaces.Validator 24 | } 25 | 26 | // NewGRPCHandler func 27 | func NewGRPCHandler(mw interfaces.Middleware, uc usecase.OrderUsecase, validator interfaces.Validator) *GRPCHandler { 28 | return &GRPCHandler{ 29 | mw: mw, uc: uc, validator: validator, 30 | } 31 | } 32 | 33 | // Register grpc server 34 | func (h *GRPCHandler) Register(server *grpc.Server, mwGroup *types.MiddlewareGroup) { 35 | proto.RegisterOrderHandlerServer(server, h) 36 | 37 | // register middleware for method 38 | mwGroup.AddProto(proto.File_order_order_proto, "Hello", h.mw.GRPCBearerAuth) 39 | } 40 | 41 | // Hello rpc method 42 | func (h *GRPCHandler) Hello(ctx context.Context, req *proto.Request) (*proto.Response, error) { 43 | trace := tracer.StartTrace(ctx, "OrderDeliveryGRPC:Hello") 44 | defer trace.Finish() 45 | ctx = trace.Context() 46 | 47 | tokenClaim := candishared.ParseTokenClaimFromContext(ctx) // must using GRPCBearerAuth in middleware for this handler 48 | 49 | return &proto.Response{ 50 | Message: h.uc.Hello(ctx) + ", with your session (" + tokenClaim.Audience + ")", 51 | }, nil 52 | } 53 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/order/domain/payload.go: -------------------------------------------------------------------------------- 1 | package domain -------------------------------------------------------------------------------- /services/order-service/internal/modules/order/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | // OrderRepository abstract interface 10 | type OrderRepository interface { 11 | // add method 12 | FindHello(ctx context.Context) (string, error) 13 | } 14 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/order/repository/repository_mongo.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | 8 | "go.mongodb.org/mongo-driver/mongo" 9 | 10 | "pkg.agungdp.dev/candi/tracer" 11 | ) 12 | 13 | type orderRepoMongo struct { 14 | readDB, writeDB *mongo.Database 15 | } 16 | 17 | // NewOrderRepoMongo mongo repo constructor 18 | func NewOrderRepoMongo(readDB, writeDB *mongo.Database) OrderRepository { 19 | return &orderRepoMongo{ 20 | readDB, writeDB, 21 | } 22 | } 23 | 24 | func (r *orderRepoMongo) FindHello(ctx context.Context) (string, error) { 25 | trace := tracer.StartTrace(ctx, "OrderRepoMongo:FindHello") 26 | defer trace.Finish() 27 | 28 | return "Hello from repo mongo layer", nil 29 | } 30 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/order/repository/repository_sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | "database/sql" 8 | 9 | "pkg.agungdp.dev/candi/tracer" 10 | ) 11 | 12 | type orderRepoSQL struct { 13 | readDB, writeDB *sql.DB 14 | tx *sql.Tx 15 | } 16 | 17 | // NewOrderRepoSQL mongo repo constructor 18 | func NewOrderRepoSQL(readDB, writeDB *sql.DB, tx *sql.Tx) OrderRepository { 19 | return &orderRepoSQL{ 20 | readDB, writeDB, tx, 21 | } 22 | } 23 | 24 | func (r *orderRepoSQL) FindHello(ctx context.Context) (string, error) { 25 | trace := tracer.StartTrace(ctx, "OrderRepoSQL:FindHello") 26 | defer trace.Finish() 27 | 28 | return "Hello from repo sql layer", nil 29 | } 30 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/order/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package usecase 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | // OrderUsecase abstraction 10 | type OrderUsecase interface { 11 | // add method 12 | Hello(ctx context.Context) string 13 | } 14 | -------------------------------------------------------------------------------- /services/order-service/internal/modules/order/usecase/usecase_impl.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package usecase 4 | 5 | import ( 6 | "context" 7 | 8 | "monorepo/services/order-service/pkg/shared/repository" 9 | 10 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 11 | "pkg.agungdp.dev/candi/codebase/interfaces" 12 | "pkg.agungdp.dev/candi/tracer" 13 | ) 14 | 15 | type orderUsecaseImpl struct { 16 | cache interfaces.Cache 17 | 18 | repoMongo *repository.RepoMongo 19 | } 20 | 21 | // NewOrderUsecase usecase impl constructor 22 | func NewOrderUsecase(deps dependency.Dependency) OrderUsecase { 23 | return &orderUsecaseImpl{ 24 | cache: deps.GetRedisPool().Cache(), 25 | 26 | repoMongo: repository.GetSharedRepoMongo(), 27 | } 28 | } 29 | 30 | func (uc *orderUsecaseImpl) Hello(ctx context.Context) (msg string) { 31 | trace := tracer.StartTrace(ctx, "OrderUsecase:Hello") 32 | defer trace.Finish() 33 | ctx = trace.Context() 34 | 35 | msg, _ = uc.repoMongo.OrderRepo.FindHello(ctx) 36 | return 37 | } 38 | -------------------------------------------------------------------------------- /services/order-service/main.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "runtime/debug" 8 | 9 | "pkg.agungdp.dev/candi/codebase/app" 10 | "pkg.agungdp.dev/candi/config" 11 | 12 | service "monorepo/services/order-service/internal" 13 | ) 14 | 15 | const serviceName = "order-service" 16 | 17 | func main() { 18 | defer func() { 19 | if r := recover(); r != nil { 20 | fmt.Printf("\x1b[31;1mFailed to start %s service: %v\x1b[0m\n", serviceName, r) 21 | fmt.Printf("Stack trace: \n%s\n", debug.Stack()) 22 | } 23 | }() 24 | 25 | cfg := config.Init(serviceName) 26 | defer cfg.Exit() 27 | 28 | srv := service.NewService(serviceName, cfg) 29 | app.New(srv).Run() 30 | } 31 | -------------------------------------------------------------------------------- /services/order-service/pkg/helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper -------------------------------------------------------------------------------- /services/order-service/pkg/shared/domain/master.go: -------------------------------------------------------------------------------- 1 | package domain -------------------------------------------------------------------------------- /services/order-service/pkg/shared/domain/order.go: -------------------------------------------------------------------------------- 1 | package domain -------------------------------------------------------------------------------- /services/order-service/pkg/shared/environment.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package shared 4 | 5 | // Environment additional in this service 6 | type Environment struct { 7 | // more additional environment with struct tag is environment key example: 8 | // ExampleHost string `env:"EXAMPLE_HOST"` 9 | 10 | AuthServiceHost string `env:"AUTH_SERVICE_HOST"` 11 | AuthServiceKey string `env:"AUTH_SERVICE_KEY"` 12 | } 13 | 14 | var sharedEnv Environment 15 | 16 | // GetEnv get global additional environment 17 | func GetEnv() Environment { 18 | return sharedEnv 19 | } 20 | 21 | // SetEnv get global additional environment 22 | func SetEnv(env Environment) { 23 | sharedEnv = env 24 | } 25 | -------------------------------------------------------------------------------- /services/order-service/pkg/shared/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. DO NOT EDIT. 2 | 3 | package repository 4 | 5 | import ( 6 | "sync" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 9 | ) 10 | 11 | var ( 12 | once sync.Once 13 | ) 14 | 15 | // SetSharedRepository set the global singleton "RepoSQL" and "RepoMongo" implementation 16 | func SetSharedRepository(deps dependency.Dependency) { 17 | once.Do(func() { 18 | // setSharedRepoSQL(deps.GetSQLDatabase().ReadDB(), deps.GetSQLDatabase().WriteDB()) 19 | setSharedRepoMongo(deps.GetMongoDatabase().ReadDB(), deps.GetMongoDatabase().WriteDB()) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /services/order-service/pkg/shared/repository/repository_mongo.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. DO NOT EDIT. 2 | 3 | package repository 4 | 5 | import ( 6 | "go.mongodb.org/mongo-driver/mongo" 7 | masterrepo "monorepo/services/order-service/internal/modules/master/repository" 8 | orderrepo "monorepo/services/order-service/internal/modules/order/repository" 9 | ) 10 | 11 | // RepoMongo uow 12 | type RepoMongo struct { 13 | readDB, writeDB *mongo.Database 14 | 15 | // register all repository from modules 16 | MasterRepo masterrepo.MasterRepository 17 | OrderRepo orderrepo.OrderRepository 18 | } 19 | 20 | var globalRepoMongo = new(RepoMongo) 21 | 22 | // setSharedRepoMongo set the global singleton "RepoMongo" implementation 23 | func setSharedRepoMongo(readDB, writeDB *mongo.Database) { 24 | globalRepoMongo = &RepoMongo{ 25 | readDB: readDB, writeDB: writeDB, 26 | MasterRepo: masterrepo.NewMasterRepoMongo(readDB, writeDB), 27 | OrderRepo: orderrepo.NewOrderRepoMongo(readDB, writeDB), 28 | } 29 | } 30 | 31 | // GetSharedRepoMongo returns the global singleton "RepoMongo" implementation 32 | func GetSharedRepoMongo() *RepoMongo { 33 | return globalRepoMongo 34 | } 35 | -------------------------------------------------------------------------------- /services/order-service/pkg/shared/token_validator.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package shared 4 | 5 | import ( 6 | "context" 7 | 8 | "pkg.agungdp.dev/candi/candishared" 9 | ) 10 | 11 | // DefaultTokenValidator for token validator 12 | type DefaultTokenValidator struct { 13 | } 14 | 15 | // ValidateToken implement TokenValidator 16 | func (v *DefaultTokenValidator) ValidateToken(ctx context.Context, token string) (*candishared.TokenClaim, error) { 17 | return &candishared.TokenClaim{}, nil 18 | } 19 | -------------------------------------------------------------------------------- /services/order-service/pkg/shared/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package usecase 4 | 5 | import ( 6 | masterusecase "monorepo/services/order-service/internal/modules/master/usecase" 7 | orderusecase "monorepo/services/order-service/internal/modules/order/usecase" 8 | "sync" 9 | 10 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 11 | ) 12 | 13 | type ( 14 | // Usecase unit of work for all usecase in modules 15 | Usecase interface { 16 | Master() masterusecase.MasterUsecase 17 | Order() orderusecase.OrderUsecase 18 | } 19 | 20 | usecaseUow struct { 21 | master masterusecase.MasterUsecase 22 | order orderusecase.OrderUsecase 23 | } 24 | ) 25 | 26 | var usecaseInst *usecaseUow 27 | var once sync.Once 28 | 29 | // SetSharedUsecase set singleton usecase unit of work instance 30 | func SetSharedUsecase(deps dependency.Dependency) { 31 | once.Do(func() { 32 | usecaseInst = &usecaseUow{ 33 | master: masterusecase.NewMasterUsecase(deps), 34 | order: orderusecase.NewOrderUsecase(deps), 35 | } 36 | }) 37 | } 38 | 39 | // GetSharedUsecase get usecase unit of work instance 40 | func GetSharedUsecase() Usecase { 41 | return usecaseInst 42 | } 43 | func (uc *usecaseUow) Master() masterusecase.MasterUsecase { 44 | return uc.master 45 | } 46 | func (uc *usecaseUow) Order() orderusecase.OrderUsecase { 47 | return uc.order 48 | } 49 | -------------------------------------------------------------------------------- /services/storage-service/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | vendor 3 | main_service.go 4 | storage-service 5 | coverage.txt 6 | -------------------------------------------------------------------------------- /services/storage-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1 2 | FROM golang:1.14.9-alpine3.12 AS dependency_builder 3 | 4 | WORKDIR /go/src 5 | ENV GO111MODULE=on 6 | 7 | RUN apk update 8 | RUN apk add --no-cache bash ca-certificates git make 9 | 10 | COPY go.mod . 11 | COPY go.sum . 12 | 13 | RUN go mod download 14 | 15 | # Stage 2 16 | FROM dependency_builder AS service_builder 17 | 18 | WORKDIR /usr/app 19 | 20 | COPY . . 21 | RUN CGO_ENABLED=0 GOOS=linux go build -ldflags '-w -s' -a -o bin 22 | 23 | # Stage 3 24 | FROM alpine:latest 25 | 26 | RUN apk --no-cache add ca-certificates tzdata 27 | WORKDIR /root/ 28 | 29 | RUN mkdir -p /root/api 30 | RUN mkdir -p /root/cmd/storage-service 31 | RUN mkdir -p /root/config/key 32 | COPY --from=service_builder /usr/app/bin bin 33 | COPY --from=service_builder /usr/app/.env .env 34 | COPY --from=service_builder /usr/app/api /root/api 35 | 36 | ENTRYPOINT ["./bin"] 37 | -------------------------------------------------------------------------------- /services/storage-service/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : build run 2 | 3 | build: 4 | go build -o bin 5 | 6 | run: build 7 | ./bin 8 | 9 | proto: 10 | $(foreach proto_file, $(shell find api/proto -name '*.proto'),\ 11 | protoc --proto_path=api/proto --go_out=plugins=grpc:api/proto \ 12 | --go_opt=paths=source_relative $(proto_file);) 13 | 14 | docker: 15 | docker build -t storage-service:latest . 16 | 17 | run-container: 18 | docker run --name=storage-service --network="host" -d storage-service 19 | 20 | # unit test & calculate code coverage 21 | test: 22 | @if [ -f coverage.txt ]; then rm coverage.txt; fi; 23 | @echo ">> running unit test and calculate coverage" 24 | @go test ./... -cover -coverprofile=coverage.txt -covermode=count -coverpkg=$(PACKAGES) 25 | @go tool cover -func=coverage.txt 26 | 27 | clear: 28 | rm bin storage-service 29 | -------------------------------------------------------------------------------- /services/storage-service/api/graphql/_schema.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.3.1. 2 | 3 | schema { 4 | query: Query 5 | mutation: Mutation 6 | subscription: Subscription 7 | } 8 | 9 | type Query { 10 | storage: StorageQueryModule 11 | } 12 | 13 | type Mutation { 14 | storage: StorageMutationModule 15 | } 16 | 17 | type Subscription { 18 | storage: StorageSubscriptionModule 19 | } 20 | -------------------------------------------------------------------------------- /services/storage-service/api/graphql/storage.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.3.1. 2 | 3 | # StorageModule Module Area 4 | type StorageQueryModule { 5 | hello(): String! 6 | } 7 | 8 | type StorageMutationModule { 9 | hello(): String! 10 | } 11 | 12 | type StorageSubscriptionModule { 13 | hello(): String! 14 | } 15 | -------------------------------------------------------------------------------- /services/storage-service/api/jsonschema/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "example", 4 | "title": "json schema type", 5 | "type": "object", 6 | "properties": {} 7 | } 8 | -------------------------------------------------------------------------------- /services/storage-service/api/proto/storage/storage.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | package storage; 3 | option go_package = "monorepo/services/storage-service/api/proto/storage"; 4 | 5 | service StorageService { 6 | rpc Upload(stream Chunk) returns (UploadStatus) {} 7 | } 8 | 9 | message Chunk { 10 | bytes Content = 1; 11 | int64 TotalSize = 2; 12 | int64 Received = 3; 13 | } 14 | 15 | enum StatusCode { 16 | Unknown = 0; 17 | Ok = 1; 18 | Failed = 2; 19 | } 20 | 21 | message UploadStatus { 22 | string Message = 1; 23 | string File = 2; 24 | int64 Size = 3; 25 | StatusCode Code = 4; 26 | } 27 | -------------------------------------------------------------------------------- /services/storage-service/candi.json: -------------------------------------------------------------------------------- 1 | {"Version":"v1.4.0","Header":"Code generated by candi v1.4.0.","LibraryName":"pkg.agungdp.dev/candi","ServiceName":"storage-service","PackagePrefix":"monorepo/services/storage-service","ProtoSource":"monorepo/sdk/storage-service/proto","RestHandler":false,"GRPCHandler":true,"GraphQLHandler":false,"KafkaHandler":false,"SchedulerHandler":false,"RedisSubsHandler":false,"TaskQueueHandler":false,"IsWorkerActive":false,"RedisDeps":true,"SQLDeps":false,"MongoDeps":true,"SQLUseGORM":false,"SQLDriver":"","Modules":[{"ModuleName":"storage"}]} -------------------------------------------------------------------------------- /services/storage-service/deployments/k8s/storage-service.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungdwiprasetyo/backend-microservices/c216a6afdb64cc057c494299b54bd8ac9378ef2e/services/storage-service/deployments/k8s/storage-service.yml -------------------------------------------------------------------------------- /services/storage-service/docs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungdwiprasetyo/backend-microservices/c216a6afdb64cc057c494299b54bd8ac9378ef2e/services/storage-service/docs/.gitkeep -------------------------------------------------------------------------------- /services/storage-service/internal/modules/storage/domain/domain.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | // UploadMetadata model 4 | type UploadMetadata struct { 5 | Folder string 6 | Filename string 7 | ContentType string 8 | FileSize int64 9 | } 10 | -------------------------------------------------------------------------------- /services/storage-service/internal/modules/storage/domain/payload.go: -------------------------------------------------------------------------------- 1 | package domain -------------------------------------------------------------------------------- /services/storage-service/internal/modules/storage/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | // StorageRepository abstract interface 10 | type StorageRepository interface { 11 | // add method 12 | FindHello(ctx context.Context) (string, error) 13 | } 14 | -------------------------------------------------------------------------------- /services/storage-service/internal/modules/storage/repository/repository_mongo.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | 8 | "go.mongodb.org/mongo-driver/mongo" 9 | 10 | "pkg.agungdp.dev/candi/tracer" 11 | ) 12 | 13 | type storageRepoMongo struct { 14 | readDB, writeDB *mongo.Database 15 | } 16 | 17 | // NewStorageRepoMongo mongo repo constructor 18 | func NewStorageRepoMongo(readDB, writeDB *mongo.Database) StorageRepository { 19 | return &storageRepoMongo{ 20 | readDB, writeDB, 21 | } 22 | } 23 | 24 | func (r *storageRepoMongo) FindHello(ctx context.Context) (string, error) { 25 | trace := tracer.StartTrace(ctx, "StorageRepoMongo:FindHello") 26 | defer trace.Finish() 27 | 28 | return "Hello from repo mongo layer", nil 29 | } 30 | -------------------------------------------------------------------------------- /services/storage-service/internal/modules/storage/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package usecase 4 | 5 | import ( 6 | "context" 7 | "io" 8 | "monorepo/services/storage-service/internal/modules/storage/domain" 9 | ) 10 | 11 | // StorageUsecase abstraction 12 | type StorageUsecase interface { 13 | // add method 14 | Hello(ctx context.Context) string 15 | Upload(ctx context.Context, file io.Reader, metadata *domain.UploadMetadata) (err error) 16 | } 17 | -------------------------------------------------------------------------------- /services/storage-service/main.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "runtime/debug" 8 | 9 | "pkg.agungdp.dev/candi/codebase/app" 10 | "pkg.agungdp.dev/candi/config" 11 | 12 | service "monorepo/services/storage-service/internal" 13 | ) 14 | 15 | const serviceName = "storage-service" 16 | 17 | func main() { 18 | defer func() { 19 | if r := recover(); r != nil { 20 | fmt.Printf("\x1b[31;1mFailed to start %s service: %v\x1b[0m\n", serviceName, r) 21 | fmt.Printf("Stack trace: \n%s\n", debug.Stack()) 22 | } 23 | }() 24 | 25 | cfg := config.Init(serviceName) 26 | defer cfg.Exit() 27 | 28 | srv := service.NewService(serviceName, cfg) 29 | app.New(srv).Run() 30 | } 31 | -------------------------------------------------------------------------------- /services/storage-service/pkg/helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper -------------------------------------------------------------------------------- /services/storage-service/pkg/shared/domain/storage.go: -------------------------------------------------------------------------------- 1 | package domain -------------------------------------------------------------------------------- /services/storage-service/pkg/shared/environment.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package shared 4 | 5 | // Environment additional in this service 6 | type Environment struct { 7 | // more additional environment with struct tag is environment key example: 8 | // ExampleHost string `env:"EXAMPLE_HOST"` 9 | 10 | AuthServiceHost string `env:"AUTH_SERVICE_HOST"` 11 | AuthServiceKey string `env:"AUTH_SERVICE_KEY"` 12 | } 13 | 14 | var sharedEnv Environment 15 | 16 | // GetEnv get global additional environment 17 | func GetEnv() Environment { 18 | return sharedEnv 19 | } 20 | 21 | // SetEnv get global additional environment 22 | func SetEnv(env Environment) { 23 | sharedEnv = env 24 | } 25 | -------------------------------------------------------------------------------- /services/storage-service/pkg/shared/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. DO NOT EDIT. 2 | 3 | package repository 4 | 5 | import ( 6 | "sync" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 9 | ) 10 | 11 | var ( 12 | once sync.Once 13 | ) 14 | 15 | // SetSharedRepository set the global singleton "RepoSQL" and "RepoMongo" implementation 16 | func SetSharedRepository(deps dependency.Dependency) { 17 | once.Do(func() { 18 | // setSharedRepoSQL(deps.GetSQLDatabase().ReadDB(), deps.GetSQLDatabase().WriteDB()) 19 | setSharedRepoMongo(deps.GetMongoDatabase().ReadDB(), deps.GetMongoDatabase().WriteDB()) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /services/storage-service/pkg/shared/repository/repository_mongo.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. DO NOT EDIT. 2 | 3 | package repository 4 | 5 | import ( 6 | "go.mongodb.org/mongo-driver/mongo" 7 | storagerepo "monorepo/services/storage-service/internal/modules/storage/repository" 8 | ) 9 | 10 | // RepoMongo uow 11 | type RepoMongo struct { 12 | readDB, writeDB *mongo.Database 13 | 14 | // register all repository from modules 15 | StorageRepo storagerepo.StorageRepository 16 | } 17 | 18 | var globalRepoMongo = new(RepoMongo) 19 | 20 | // setSharedRepoMongo set the global singleton "RepoMongo" implementation 21 | func setSharedRepoMongo(readDB, writeDB *mongo.Database) { 22 | globalRepoMongo = &RepoMongo{ 23 | readDB: readDB, writeDB: writeDB, 24 | StorageRepo: storagerepo.NewStorageRepoMongo(readDB, writeDB), 25 | } 26 | } 27 | 28 | // GetSharedRepoMongo returns the global singleton "RepoMongo" implementation 29 | func GetSharedRepoMongo() *RepoMongo { 30 | return globalRepoMongo 31 | } 32 | -------------------------------------------------------------------------------- /services/storage-service/pkg/shared/token_validator.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package shared 4 | 5 | import ( 6 | "context" 7 | 8 | "pkg.agungdp.dev/candi/candishared" 9 | ) 10 | 11 | // DefaultTokenValidator for token validator 12 | type DefaultTokenValidator struct { 13 | } 14 | 15 | // ValidateToken implement TokenValidator 16 | func (v *DefaultTokenValidator) ValidateToken(ctx context.Context, token string) (*candishared.TokenClaim, error) { 17 | return &candishared.TokenClaim{}, nil 18 | } 19 | -------------------------------------------------------------------------------- /services/storage-service/pkg/shared/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package usecase 4 | 5 | import ( 6 | storageusecase "monorepo/services/storage-service/internal/modules/storage/usecase" 7 | "sync" 8 | 9 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 10 | ) 11 | 12 | type ( 13 | // Usecase unit of work for all usecase in modules 14 | Usecase interface { 15 | Storage() storageusecase.StorageUsecase 16 | } 17 | 18 | usecaseUow struct { 19 | storage storageusecase.StorageUsecase 20 | } 21 | ) 22 | 23 | var usecaseInst *usecaseUow 24 | var once sync.Once 25 | 26 | // SetSharedUsecase set singleton usecase unit of work instance 27 | func SetSharedUsecase(deps dependency.Dependency) { 28 | once.Do(func() { 29 | usecaseInst = &usecaseUow{ 30 | storage: storageusecase.NewStorageUsecase(deps), 31 | } 32 | }) 33 | } 34 | 35 | // GetSharedUsecase get usecase unit of work instance 36 | func GetSharedUsecase() Usecase { 37 | return usecaseInst 38 | } 39 | func (uc *usecaseUow) Storage() storageusecase.StorageUsecase { 40 | return uc.storage 41 | } 42 | -------------------------------------------------------------------------------- /services/user-service/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | vendor 3 | main_service.go 4 | user-service 5 | coverage.txt 6 | -------------------------------------------------------------------------------- /services/user-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1 2 | FROM golang:1.14.9-alpine3.12 AS dependency_builder 3 | 4 | WORKDIR /go/src 5 | ENV GO111MODULE=on 6 | 7 | RUN apk update 8 | RUN apk add --no-cache bash ca-certificates git make 9 | 10 | COPY go.mod . 11 | COPY go.sum . 12 | 13 | RUN go mod download 14 | 15 | # Stage 2 16 | FROM dependency_builder AS service_builder 17 | 18 | WORKDIR /usr/app 19 | 20 | COPY . . 21 | RUN CGO_ENABLED=0 GOOS=linux go build -ldflags '-w -s' -a -o bin 22 | 23 | # Stage 3 24 | FROM alpine:latest 25 | 26 | RUN apk --no-cache add ca-certificates tzdata 27 | WORKDIR /root/ 28 | 29 | RUN mkdir -p /root/api 30 | RUN mkdir -p /root/cmd/user-service 31 | RUN mkdir -p /root/config/key 32 | COPY --from=service_builder /usr/app/bin bin 33 | COPY --from=service_builder /usr/app/.env .env 34 | COPY --from=service_builder /usr/app/api /root/api 35 | 36 | ENTRYPOINT ["./bin"] 37 | -------------------------------------------------------------------------------- /services/user-service/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : build run 2 | 3 | build: 4 | go build -o bin 5 | 6 | run: build 7 | ./bin 8 | 9 | proto: 10 | $(foreach proto_file, $(shell find api/proto -name '*.proto'),\ 11 | protoc --proto_path=api/proto --go_out=plugins=grpc:api/proto \ 12 | --go_opt=paths=source_relative $(proto_file);) 13 | 14 | docker: 15 | docker build -t user-service:latest . 16 | 17 | run-container: 18 | docker run --name=user-service --network="host" -d user-service 19 | 20 | # unit test & calculate code coverage 21 | test: 22 | @if [ -f coverage.txt ]; then rm coverage.txt; fi; 23 | @echo ">> running unit test and calculate coverage" 24 | @go test ./... -cover -coverprofile=coverage.txt -covermode=count -coverpkg=$(PACKAGES) 25 | @go tool cover -func=coverage.txt 26 | 27 | clear: 28 | rm bin user-service 29 | -------------------------------------------------------------------------------- /services/user-service/api/graphql/_common.graphql: -------------------------------------------------------------------------------- 1 | input FilterListInputResolver { 2 | limit: Int 3 | page: Int 4 | "Optional (asc desc)" 5 | sort: FilterSortEnum 6 | "Optional" 7 | order_by: String 8 | "Optional" 9 | show_all: Boolean 10 | "Optional" 11 | search: String 12 | } 13 | 14 | type Meta { 15 | page: Int! 16 | limit: Int! 17 | total_records: Int! 18 | total_pages: Int! 19 | } 20 | 21 | enum FilterSortEnum { 22 | asc 23 | desc 24 | } 25 | -------------------------------------------------------------------------------- /services/user-service/api/graphql/_schema.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.3.1. 2 | 3 | schema { 4 | query: Query 5 | mutation: Mutation 6 | # subscription: Subscription 7 | } 8 | 9 | type Query { 10 | auth: AuthQueryResolver 11 | member: MemberQueryResolver 12 | } 13 | 14 | type Mutation { 15 | auth: AuthMutationResolver 16 | member: MemberMutationResolver 17 | } 18 | -------------------------------------------------------------------------------- /services/user-service/api/graphql/auth.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.3.1. 2 | 3 | # AuthResolver Module Area 4 | type AuthQueryResolver { 5 | hello(): String! 6 | } 7 | 8 | type AuthMutationResolver { 9 | login(username: String!, password: String!): ResponseLoginResolver! 10 | } 11 | 12 | type ResponseLoginResolver { 13 | token: String! 14 | refresh_token: String! 15 | userApps: [UserAppResolver!]! 16 | } 17 | 18 | type UserAppResolver { 19 | id: String! 20 | code: String! 21 | name: String! 22 | icon: String! 23 | frontendUrl: String! 24 | backendUrl: String! 25 | role: RoleResolver! 26 | } 27 | 28 | type RoleResolver { 29 | id: String! 30 | code: String! 31 | name: String! 32 | } -------------------------------------------------------------------------------- /services/user-service/api/graphql/member.graphql: -------------------------------------------------------------------------------- 1 | # Code generated by candi v1.8.17. 2 | 3 | # MemberResolver Resolver Area 4 | type MemberQueryResolver { 5 | get_all_member(filter: FilterListInputResolver): MemberListResolver! 6 | get_detail_member(id: String!): MemberResolver! 7 | } 8 | 9 | type MemberMutationResolver { 10 | register(data: MemberInputResolver!): String! 11 | } 12 | 13 | type MemberListResolver { 14 | meta: Meta! 15 | data: [MemberResolver!]! 16 | } 17 | 18 | type MemberResolver { 19 | id: String! 20 | username: String! 21 | fullname: String! 22 | created_at: String! 23 | modified_at: String! 24 | } 25 | 26 | input MemberInputResolver { 27 | username: String! 28 | email: String! 29 | fullname: String! 30 | password: String! 31 | } 32 | -------------------------------------------------------------------------------- /services/user-service/api/jsonschema/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "example", 4 | "title": "json schema type", 5 | "type": "object", 6 | "properties": {} 7 | } 8 | -------------------------------------------------------------------------------- /services/user-service/api/proto/auth/auth.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | package auth; 3 | option go_package = "monorepo/services/user-service/api/proto/auth"; 4 | 5 | service AuthHandler { 6 | rpc Hello(Request) returns (Response); 7 | } 8 | 9 | message Request { 10 | string Message=1; 11 | } 12 | 13 | message Response { 14 | string Message=1; 15 | } -------------------------------------------------------------------------------- /services/user-service/api/proto/member/member.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | package member; 3 | option go_package = "monorepo/services/user-service/api/proto/member"; 4 | 5 | service MemberHandler { 6 | rpc GetMember(GetMemberRequest) returns (GetMemberResponse); 7 | } 8 | 9 | message GetMemberRequest { 10 | string ID=1; 11 | } 12 | 13 | message GetMemberResponse { 14 | string ID = 1; 15 | string Username = 2; 16 | string Password = 3; 17 | string Fullname = 4; 18 | string CreatedAt = 5; 19 | string ModifiedAt = 6; 20 | } -------------------------------------------------------------------------------- /services/user-service/candi.json: -------------------------------------------------------------------------------- 1 | {"Version":"v1.4.0","Header":"Code generated by candi v1.4.0.","LibraryName":"pkg.agungdp.dev/candi","ServiceName":"user-service","PackagePrefix":"monorepo/services/user-service","ProtoSource":"monorepo/sdk/user-service/proto","IsMonorepo":false,"RestHandler":true,"GRPCHandler":true,"GraphQLHandler":true,"KafkaHandler":true,"SchedulerHandler":true,"RedisSubsHandler":false,"TaskQueueHandler":true,"PostgresListenerHandler":false,"RabbitMQHandler":false,"IsWorkerActive":true,"RedisDeps":true,"SQLDeps":false,"MongoDeps":true,"SQLUseGORM":false,"SQLDriver":"","Modules":[{"ModuleName":"auth"},{"ModuleName":"member"}]} -------------------------------------------------------------------------------- /services/user-service/deployments/k8s/user-service.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungdwiprasetyo/backend-microservices/c216a6afdb64cc057c494299b54bd8ac9378ef2e/services/user-service/deployments/k8s/user-service.yml -------------------------------------------------------------------------------- /services/user-service/docs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungdwiprasetyo/backend-microservices/c216a6afdb64cc057c494299b54bd8ac9378ef2e/services/user-service/docs/.gitkeep -------------------------------------------------------------------------------- /services/user-service/internal/modules/auth/delivery/graphqlhandler/mutation_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/user-service/internal/modules/auth/domain" 8 | 9 | "pkg.agungdp.dev/candi/tracer" 10 | ) 11 | 12 | type mutationResolver struct { 13 | root *GraphQLHandler 14 | } 15 | 16 | // Login resolver 17 | func (m *mutationResolver) Login(ctx context.Context, input struct { 18 | Username string 19 | Password string 20 | }) (resp domain.LoginResponse, err error) { 21 | 22 | trace := tracer.StartTrace(ctx, "DeliveryGraphQL:Login") 23 | defer trace.Finish() 24 | ctx = trace.Context() 25 | 26 | data, err := m.root.uc.Login(ctx, &domain.LoginRequest{Username: input.Username, Password: input.Password}) 27 | if err != nil { 28 | return resp, err 29 | } 30 | 31 | return data, nil 32 | } 33 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/auth/delivery/graphqlhandler/query_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | 8 | "pkg.agungdp.dev/candi/candishared" 9 | "pkg.agungdp.dev/candi/tracer" 10 | ) 11 | 12 | type queryResolver struct { 13 | root *GraphQLHandler 14 | } 15 | 16 | // Hello resolver 17 | func (q *queryResolver) Hello(ctx context.Context) (string, error) { 18 | trace := tracer.StartTrace(ctx, "DeliveryGraphQL-Hello") 19 | defer trace.Finish() 20 | ctx = trace.Context() 21 | 22 | tokenClaim := candishared.ParseTokenClaimFromContext(ctx) // must using GraphQLBearerAuth in middleware for this resolver 23 | 24 | return ", with your session (" + tokenClaim.Audience + ")", nil 25 | } 26 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/auth/delivery/graphqlhandler/root_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "monorepo/services/user-service/internal/modules/auth/usecase" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/types" 9 | "pkg.agungdp.dev/candi/codebase/interfaces" 10 | ) 11 | 12 | // GraphQLHandler model 13 | type GraphQLHandler struct { 14 | mw interfaces.Middleware 15 | uc usecase.AuthUsecase 16 | validator interfaces.Validator 17 | } 18 | 19 | // NewGraphQLHandler delivery 20 | func NewGraphQLHandler(mw interfaces.Middleware, uc usecase.AuthUsecase, validator interfaces.Validator) *GraphQLHandler { 21 | return &GraphQLHandler{ 22 | mw: mw, uc: uc, validator: validator, 23 | } 24 | } 25 | 26 | // RegisterMiddleware register resolver based on schema in "api/graphql/*" path 27 | func (h *GraphQLHandler) RegisterMiddleware(mwGroup *types.MiddlewareGroup) { 28 | mwGroup.Add("AuthQueryModule.hello", h.mw.GraphQLBearerAuth) 29 | mwGroup.Add("AuthMutationModule.hello", h.mw.GraphQLBasicAuth) 30 | } 31 | 32 | // Query method 33 | func (h *GraphQLHandler) Query() interface{} { 34 | return &queryResolver{root: h} 35 | } 36 | 37 | // Mutation method 38 | func (h *GraphQLHandler) Mutation() interface{} { 39 | return &mutationResolver{root: h} 40 | } 41 | 42 | // Subscription method 43 | func (h *GraphQLHandler) Subscription() interface{} { 44 | return &subscriptionResolver{root: h} 45 | } 46 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/auth/delivery/graphqlhandler/subscription_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package graphqlhandler 4 | 5 | import "context" 6 | 7 | type subscriptionResolver struct { 8 | root *GraphQLHandler 9 | } 10 | 11 | // Hello resolver 12 | func (s *subscriptionResolver) Hello(ctx context.Context) <-chan string { 13 | output := make(chan string) 14 | 15 | go func() { 16 | output <- "Hello" 17 | }() 18 | 19 | return output 20 | } 21 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/auth/domain/payload.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | masterservice "monorepo/sdk/master-service" 5 | shareddomain "monorepo/services/user-service/pkg/shared/domain" 6 | ) 7 | 8 | // LoginRequest request payload 9 | type LoginRequest struct { 10 | Username string `json:"username"` 11 | Password string `json:"password"` 12 | } 13 | 14 | // LoginResponse request payload 15 | type LoginResponse struct { 16 | Token string `json:"token"` 17 | RefreshToken string `json:"refresh_token"` 18 | Profile shareddomain.Member `json:"profile"` 19 | UserApps []masterservice.UserApps `json:"userApps"` 20 | } 21 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/auth/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | // AuthRepository abstract interface 10 | type AuthRepository interface { 11 | // add method 12 | FindHello(ctx context.Context) (string, error) 13 | } 14 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/auth/repository/repository_mongo.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | 8 | "go.mongodb.org/mongo-driver/mongo" 9 | 10 | "pkg.agungdp.dev/candi/tracer" 11 | ) 12 | 13 | type authRepoMongo struct { 14 | readDB, writeDB *mongo.Database 15 | } 16 | 17 | // NewAuthRepoMongo mongo repo constructor 18 | func NewAuthRepoMongo(readDB, writeDB *mongo.Database) AuthRepository { 19 | return &authRepoMongo{ 20 | readDB, writeDB, 21 | } 22 | } 23 | 24 | func (r *authRepoMongo) FindHello(ctx context.Context) (string, error) { 25 | trace := tracer.StartTrace(ctx, "AuthRepoMongo:FindHello") 26 | defer trace.Finish() 27 | 28 | return "Hello from repo mongo layer", nil 29 | } 30 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/auth/repository/repository_sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.3. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | "database/sql" 8 | 9 | "pkg.agungdp.dev/candi/tracer" 10 | ) 11 | 12 | type authRepoSQL struct { 13 | readDB, writeDB *sql.DB 14 | tx *sql.Tx 15 | } 16 | 17 | // NewAuthRepoSQL mongo repo constructor 18 | func NewAuthRepoSQL(readDB, writeDB *sql.DB, tx *sql.Tx) AuthRepository { 19 | return &authRepoSQL{ 20 | readDB, writeDB, tx, 21 | } 22 | } 23 | 24 | func (r *authRepoSQL) FindHello(ctx context.Context) (string, error) { 25 | trace := tracer.StartTrace(ctx, "AuthRepoSQL:FindHello") 26 | defer trace.Finish() 27 | 28 | return "Hello from repo sql layer", nil 29 | } 30 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/auth/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package usecase 4 | 5 | import ( 6 | "context" 7 | "monorepo/services/user-service/internal/modules/auth/domain" 8 | ) 9 | 10 | // AuthUsecase abstraction 11 | type AuthUsecase interface { 12 | Login(ctx context.Context, req *domain.LoginRequest) (resp domain.LoginResponse, err error) 13 | } 14 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/member/delivery/graphqlhandler/field_serializer_resolver.go: -------------------------------------------------------------------------------- 1 | package graphqlhandler 2 | 3 | import ( 4 | shareddomain "monorepo/services/user-service/pkg/shared/domain" 5 | 6 | "pkg.agungdp.dev/candi/candihelper" 7 | "pkg.agungdp.dev/candi/candishared" 8 | ) 9 | 10 | // CommonFilter basic filter model 11 | type CommonFilter struct { 12 | Limit *int 13 | Page *int 14 | Search *string 15 | Sort *string 16 | ShowAll *bool 17 | OrderBy *string 18 | } 19 | 20 | // toSharedFilter method 21 | func (f *CommonFilter) toSharedFilter() (filter candishared.Filter) { 22 | filter.Search = candihelper.PtrToString(f.Search) 23 | filter.OrderBy = candihelper.PtrToString(f.OrderBy) 24 | filter.Sort = candihelper.PtrToString(f.Sort) 25 | filter.ShowAll = candihelper.PtrToBool(f.ShowAll) 26 | 27 | if f.Limit == nil { 28 | filter.Limit = 10 29 | } else { 30 | filter.Limit = *f.Limit 31 | } 32 | if f.Page == nil { 33 | filter.Page = 1 34 | } else { 35 | filter.Page = *f.Page 36 | } 37 | 38 | return 39 | } 40 | 41 | // MemberListResolver resolver 42 | type MemberListResolver struct { 43 | Meta candishared.Meta 44 | Data []shareddomain.Member 45 | } 46 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/member/delivery/graphqlhandler/mutation_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.17. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | 8 | "monorepo/services/user-service/internal/modules/member/domain" 9 | shareddomain "monorepo/services/user-service/pkg/shared/domain" 10 | 11 | "pkg.agungdp.dev/candi/tracer" 12 | ) 13 | 14 | type mutationResolver struct { 15 | root *GraphQLHandler 16 | } 17 | 18 | // SaveMember resolver 19 | func (m *mutationResolver) SaveMember(ctx context.Context, input struct{ Data shareddomain.Member }) (ok string, err error) { 20 | trace := tracer.StartTrace(ctx, "MemberDeliveryGraphQL:SaveMember") 21 | defer trace.Finish() 22 | ctx = trace.Context() 23 | 24 | // tokenClaim := candishared.ParseTokenClaimFromContext(ctx) // must using GraphQLBearerAuth in middleware for this resolver 25 | 26 | if err := m.root.uc.SaveMember(ctx, &input.Data); err != nil { 27 | return ok, err 28 | } 29 | return "Success", nil 30 | } 31 | 32 | // SaveMember resolver 33 | func (m *mutationResolver) Register(ctx context.Context, input struct{ Data domain.RegisterPayload }) (ok string, err error) { 34 | trace := tracer.StartTrace(ctx, "MemberDeliveryGraphQL:Register") 35 | defer trace.Finish() 36 | ctx = trace.Context() 37 | 38 | // tokenClaim := candishared.ParseTokenClaimFromContext(ctx) // must using GraphQLBearerAuth in middleware for this resolver 39 | 40 | if err := m.root.uc.Register(ctx, &input.Data); err != nil { 41 | return ok, err 42 | } 43 | return "Success", nil 44 | } 45 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/member/delivery/graphqlhandler/query_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.17. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "context" 7 | 8 | shareddomain "monorepo/services/user-service/pkg/shared/domain" 9 | 10 | "pkg.agungdp.dev/candi/tracer" 11 | ) 12 | 13 | type queryResolver struct { 14 | root *GraphQLHandler 15 | } 16 | 17 | // GetAllMember resolver 18 | func (q *queryResolver) GetAllMember(ctx context.Context, input struct{ Filter *CommonFilter }) (results MemberListResolver, err error) { 19 | trace := tracer.StartTrace(ctx, "MemberDeliveryGraphQL:GetAllMember") 20 | defer trace.Finish() 21 | ctx = trace.Context() 22 | 23 | // tokenClaim := candishared.ParseTokenClaimFromContext(ctx) // must using GraphQLBearerAuth in middleware for this resolver 24 | 25 | if input.Filter == nil { 26 | input.Filter = new(CommonFilter) 27 | } 28 | filter := input.Filter.toSharedFilter() 29 | data, meta, err := q.root.uc.GetAllMember(ctx, filter) 30 | if err != nil { 31 | return results, err 32 | } 33 | 34 | return MemberListResolver{ 35 | Meta: meta, Data: data, 36 | }, nil 37 | } 38 | 39 | // GetDetailMember resolver 40 | func (q *queryResolver) GetDetailMember(ctx context.Context, input struct{ ID string }) (data shareddomain.Member, err error) { 41 | trace := tracer.StartTrace(ctx, "MemberDeliveryGraphQL:GetDetailMember") 42 | defer trace.Finish() 43 | ctx = trace.Context() 44 | 45 | // tokenClaim := candishared.ParseTokenClaimFromContext(ctx) // must using GraphQLBearerAuth in middleware for this resolver 46 | 47 | return q.root.uc.GetDetailMember(ctx, input.ID) 48 | } 49 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/member/delivery/graphqlhandler/root_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.17. 2 | 3 | package graphqlhandler 4 | 5 | import ( 6 | "monorepo/services/user-service/internal/modules/member/usecase" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/types" 9 | "pkg.agungdp.dev/candi/codebase/interfaces" 10 | ) 11 | 12 | // GraphQLHandler model 13 | type GraphQLHandler struct { 14 | mw interfaces.Middleware 15 | uc usecase.MemberUsecase 16 | validator interfaces.Validator 17 | } 18 | 19 | // NewGraphQLHandler delivery 20 | func NewGraphQLHandler(mw interfaces.Middleware, uc usecase.MemberUsecase, validator interfaces.Validator) *GraphQLHandler { 21 | return &GraphQLHandler{ 22 | mw: mw, uc: uc, validator: validator, 23 | } 24 | } 25 | 26 | // RegisterMiddleware register resolver based on schema in "api/graphql/*" path 27 | func (h *GraphQLHandler) RegisterMiddleware(mwGroup *types.MiddlewareGroup) { 28 | mwGroup.Add("MemberQueryResolver.get_all_member", h.mw.GraphQLBearerAuth) //, h.mw.GraphQLPermissionACL("resource.public")) 29 | mwGroup.Add("MemberQueryResolver.get_detail_member", h.mw.GraphQLBearerAuth) 30 | mwGroup.Add("MemberMutationResolver.register", h.mw.GraphQLBasicAuth) 31 | } 32 | 33 | // Query method 34 | func (h *GraphQLHandler) Query() interface{} { 35 | return &queryResolver{root: h} 36 | } 37 | 38 | // Mutation method 39 | func (h *GraphQLHandler) Mutation() interface{} { 40 | return &mutationResolver{root: h} 41 | } 42 | 43 | // Subscription method 44 | func (h *GraphQLHandler) Subscription() interface{} { 45 | return &subscriptionResolver{root: h} 46 | } 47 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/member/delivery/graphqlhandler/subscription_resolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.17. 2 | 3 | package graphqlhandler 4 | 5 | import "context" 6 | 7 | type subscriptionResolver struct { 8 | root *GraphQLHandler 9 | } 10 | 11 | // Hello resolver 12 | func (s *subscriptionResolver) Hello(ctx context.Context) <-chan string { 13 | output := make(chan string) 14 | 15 | go func() { 16 | output <- "Hello from Member" 17 | }() 18 | 19 | return output 20 | } 21 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/member/delivery/workerhandler/cron_handler.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.4.0. 2 | 3 | package workerhandler 4 | 5 | import ( 6 | "context" 7 | 8 | "monorepo/services/user-service/internal/modules/member/usecase" 9 | 10 | "pkg.agungdp.dev/candi/candihelper" 11 | "pkg.agungdp.dev/candi/codebase/factory/types" 12 | "pkg.agungdp.dev/candi/codebase/interfaces" 13 | "pkg.agungdp.dev/candi/tracer" 14 | ) 15 | 16 | // CronHandler struct 17 | type CronHandler struct { 18 | uc usecase.MemberUsecase 19 | validator interfaces.Validator 20 | } 21 | 22 | // NewCronHandler constructor 23 | func NewCronHandler(uc usecase.MemberUsecase, validator interfaces.Validator) *CronHandler { 24 | return &CronHandler{ 25 | uc: uc, 26 | validator: validator, 27 | } 28 | } 29 | 30 | // MountHandlers mount handler group 31 | func (h *CronHandler) MountHandlers(group *types.WorkerHandlerGroup) { 32 | group.Add(candihelper.CronJobKeyToString("member-scheduler", "message", "10s"), h.handleMember) 33 | } 34 | 35 | func (h *CronHandler) handleMember(ctx context.Context, message []byte) error { 36 | trace := tracer.StartTrace(ctx, "MemberDeliveryCron:HandleMember") 37 | defer trace.Finish() 38 | ctx = trace.Context() 39 | 40 | return h.uc.AutoGenerateMember(ctx) 41 | } 42 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/member/delivery/workerhandler/kafka_handler.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.4.0. 2 | 3 | package workerhandler 4 | 5 | import ( 6 | "context" 7 | 8 | "monorepo/services/user-service/internal/modules/member/usecase" 9 | "monorepo/services/user-service/pkg/shared" 10 | 11 | taskqueueworker "pkg.agungdp.dev/candi/codebase/app/task_queue_worker" 12 | "pkg.agungdp.dev/candi/codebase/factory/types" 13 | "pkg.agungdp.dev/candi/codebase/interfaces" 14 | "pkg.agungdp.dev/candi/tracer" 15 | ) 16 | 17 | // KafkaHandler struct 18 | type KafkaHandler struct { 19 | uc usecase.MemberUsecase 20 | validator interfaces.Validator 21 | } 22 | 23 | // NewKafkaHandler constructor 24 | func NewKafkaHandler(uc usecase.MemberUsecase, validator interfaces.Validator) *KafkaHandler { 25 | return &KafkaHandler{ 26 | uc: uc, 27 | validator: validator, 28 | } 29 | } 30 | 31 | // MountHandlers mount handler group 32 | func (h *KafkaHandler) MountHandlers(group *types.WorkerHandlerGroup) { 33 | group.Add(shared.GetEnv().KafkaTopicAutoGenerateMember, h.handleMember) // handling topic "KafkaTopicAutoGenerateMember" 34 | } 35 | 36 | // ProcessMessage from kafka consumer 37 | func (h *KafkaHandler) handleMember(ctx context.Context, message []byte) error { 38 | trace := tracer.StartTrace(ctx, "MemberDeliveryKafka:HandleMember") 39 | defer trace.Finish() 40 | ctx = trace.Context() 41 | 42 | return taskqueueworker.AddJob(shared.GetEnv().TaskAddMember, 10, message) 43 | } 44 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/member/delivery/workerhandler/taskqueue_handler.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.4.0. 2 | 3 | package workerhandler 4 | 5 | import ( 6 | "context" 7 | "encoding/json" 8 | "time" 9 | 10 | "monorepo/services/user-service/internal/modules/member/usecase" 11 | "monorepo/services/user-service/pkg/shared" 12 | "monorepo/services/user-service/pkg/shared/domain" 13 | 14 | "pkg.agungdp.dev/candi/candishared" 15 | "pkg.agungdp.dev/candi/codebase/factory/types" 16 | "pkg.agungdp.dev/candi/codebase/interfaces" 17 | "pkg.agungdp.dev/candi/tracer" 18 | ) 19 | 20 | // TaskQueueHandler struct 21 | type TaskQueueHandler struct { 22 | uc usecase.MemberUsecase 23 | validator interfaces.Validator 24 | } 25 | 26 | // NewTaskQueueHandler constructor 27 | func NewTaskQueueHandler(uc usecase.MemberUsecase, validator interfaces.Validator) *TaskQueueHandler { 28 | return &TaskQueueHandler{ 29 | uc: uc, 30 | validator: validator, 31 | } 32 | } 33 | 34 | // MountHandlers mount handler group 35 | func (h *TaskQueueHandler) MountHandlers(group *types.WorkerHandlerGroup) { 36 | group.Add(shared.GetEnv().TaskAddMember, h.taskAddMember) 37 | } 38 | 39 | func (h *TaskQueueHandler) taskAddMember(ctx context.Context, message []byte) error { 40 | trace := tracer.StartTrace(ctx, "MemberDeliveryTaskQueue:TaskAddMember") 41 | defer trace.Finish() 42 | ctx = trace.Context() 43 | 44 | var member domain.Member 45 | if err := json.Unmarshal(message, &member); err != nil { 46 | return err 47 | } 48 | 49 | if err := h.uc.SaveMember(ctx, &member); err != nil { 50 | return &candishared.ErrorRetrier{ 51 | Delay: 10 * time.Second, 52 | Message: err.Error(), 53 | } 54 | } 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/member/domain/payload.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | // RegisterPayload payload 4 | type RegisterPayload struct { 5 | Username string `json:"username"` 6 | Email string `json:"email"` 7 | Fullname string `json:"fullname"` 8 | Password string `json:"password"` 9 | } 10 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/member/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.17. 2 | 3 | package repository 4 | 5 | import ( 6 | "context" 7 | 8 | shareddomain "monorepo/services/user-service/pkg/shared/domain" 9 | 10 | "pkg.agungdp.dev/candi/candishared" 11 | ) 12 | 13 | // MemberRepository abstract interface 14 | type MemberRepository interface { 15 | FetchAll(ctx context.Context, filter *candishared.Filter) ([]shareddomain.Member, error) 16 | Count(ctx context.Context, filter *candishared.Filter) int 17 | Find(ctx context.Context, data *shareddomain.Member) error 18 | Save(ctx context.Context, data *shareddomain.Member) error 19 | } 20 | -------------------------------------------------------------------------------- /services/user-service/internal/modules/member/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.8.17. 2 | 3 | package usecase 4 | 5 | import ( 6 | "context" 7 | 8 | "monorepo/services/user-service/internal/modules/member/domain" 9 | shareddomain "monorepo/services/user-service/pkg/shared/domain" 10 | 11 | "pkg.agungdp.dev/candi/candishared" 12 | ) 13 | 14 | // MemberUsecase abstraction 15 | type MemberUsecase interface { 16 | GetAllMember(ctx context.Context, filter candishared.Filter) (data []shareddomain.Member, meta candishared.Meta, err error) 17 | GetDetailMember(ctx context.Context, id string) (data shareddomain.Member, err error) 18 | SaveMember(ctx context.Context, data *shareddomain.Member) (err error) 19 | Register(ctx context.Context, data *domain.RegisterPayload) (err error) 20 | AutoGenerateMember(ctx context.Context) error 21 | } 22 | -------------------------------------------------------------------------------- /services/user-service/main.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "runtime/debug" 8 | 9 | _ "github.com/lib/pq" 10 | "pkg.agungdp.dev/candi/codebase/app" 11 | "pkg.agungdp.dev/candi/config" 12 | 13 | service "monorepo/services/user-service/internal" 14 | ) 15 | 16 | const serviceName = "user-service" 17 | 18 | func main() { 19 | defer func() { 20 | if r := recover(); r != nil { 21 | fmt.Printf("\x1b[31;1mFailed to start %s service: %v\x1b[0m\n", serviceName, r) 22 | fmt.Printf("Stack trace: \n%s\n", debug.Stack()) 23 | } 24 | }() 25 | 26 | cfg := config.Init(serviceName) 27 | defer cfg.Exit() 28 | 29 | srv := service.NewService(serviceName, cfg) 30 | app.New(srv).Run() 31 | } 32 | -------------------------------------------------------------------------------- /services/user-service/pkg/helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper -------------------------------------------------------------------------------- /services/user-service/pkg/helper/password.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "hash" 7 | "math/rand" 8 | "time" 9 | 10 | "golang.org/x/crypto/pbkdf2" 11 | ) 12 | 13 | // Password model 14 | type Password struct { 15 | Diggest func() hash.Hash 16 | SaltSize int 17 | KeyLen int 18 | Iterations int 19 | } 20 | 21 | // HashResult model 22 | type HashResult struct { 23 | CipherText string 24 | Salt string 25 | } 26 | 27 | // NewPassword constructor 28 | func NewPassword(diggest func() hash.Hash, saltSize int, keyLen int, iter int) *Password { 29 | return &Password{ 30 | Diggest: diggest, 31 | SaltSize: saltSize, 32 | KeyLen: keyLen, 33 | Iterations: iter, 34 | } 35 | } 36 | 37 | func (p *Password) genSalt() string { 38 | saltBytes := make([]byte, p.SaltSize) 39 | rand.Seed(time.Now().UnixNano()) 40 | rand.Read(saltBytes) 41 | return base64.StdEncoding.EncodeToString(saltBytes) 42 | } 43 | 44 | // HashPassword method 45 | func (p *Password) HashPassword(password string) HashResult { 46 | saltString := p.genSalt() 47 | salt := bytes.NewBufferString(saltString).Bytes() 48 | df := pbkdf2.Key([]byte(password), salt, p.Iterations, p.KeyLen, p.Diggest) 49 | cipherText := base64.StdEncoding.EncodeToString(df) 50 | return HashResult{CipherText: cipherText, Salt: saltString} 51 | } 52 | 53 | // VerifyPassword method 54 | func (p *Password) VerifyPassword(password, cipherText, salt string) bool { 55 | saltBytes := bytes.NewBufferString(salt).Bytes() 56 | df := pbkdf2.Key([]byte(password), saltBytes, p.Iterations, p.KeyLen, p.Diggest) 57 | newCipherText := base64.StdEncoding.EncodeToString(df) 58 | return newCipherText == cipherText 59 | } 60 | -------------------------------------------------------------------------------- /services/user-service/pkg/shared/domain/auth.go: -------------------------------------------------------------------------------- 1 | package domain -------------------------------------------------------------------------------- /services/user-service/pkg/shared/domain/member.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import "time" 4 | 5 | // Member model 6 | type Member struct { 7 | ID string `json:"id" gorm:"column:id" bson:"_id"` 8 | Username string `json:"username" gorm:"column:username" bson:"username"` 9 | Password string `json:"-" gorm:"column:password" bson:"password"` 10 | PasswordSalt string `json:"-" gorm:"column:password_salt" bson:"password_salt"` 11 | Fullname string `json:"fullname" gorm:"column:fullname" bson:"fullname"` 12 | CreatedAt time.Time `json:"createdAt" gorm:"column:created_at" bson:"createdAt"` 13 | ModifiedAt time.Time `json:"modifiedAt" gorm:"column:modified_at" bson:"modifiedAt"` 14 | } 15 | 16 | // CollectionName for member model 17 | func (Member) CollectionName() string { 18 | return "members" 19 | } 20 | -------------------------------------------------------------------------------- /services/user-service/pkg/shared/environment.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package shared 4 | 5 | // Environment additional in this service 6 | type Environment struct { 7 | // more additional environment with struct tag is environment key example: 8 | // ExampleHost string `env:"EXAMPLE_HOST"` 9 | 10 | AuthServiceHost string `env:"AUTH_SERVICE_HOST"` 11 | AuthServiceKey string `env:"AUTH_SERVICE_KEY"` 12 | MasterServiceHost string `env:"MASTER_SERVICE_HOST"` 13 | MasterServiceKey string `env:"MASTER_SERVICE_KEY"` 14 | 15 | KafkaTopicAutoGenerateMember string `env:"KAFKA_TOPIC_AUTO_GENERATE_MEMBER"` 16 | TaskAddMember string `env:"TASK_ADD_MEMBER"` 17 | } 18 | 19 | var sharedEnv Environment 20 | 21 | // GetEnv get global additional environment 22 | func GetEnv() Environment { 23 | return sharedEnv 24 | } 25 | 26 | // SetEnv get global additional environment 27 | func SetEnv(env Environment) { 28 | sharedEnv = env 29 | } 30 | -------------------------------------------------------------------------------- /services/user-service/pkg/shared/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. DO NOT EDIT. 2 | 3 | package repository 4 | 5 | import ( 6 | "sync" 7 | 8 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 9 | ) 10 | 11 | var ( 12 | once sync.Once 13 | ) 14 | 15 | // SetSharedRepository set the global singleton "RepoSQL" and "RepoMongo" implementation 16 | func SetSharedRepository(deps dependency.Dependency) { 17 | once.Do(func() { 18 | setSharedRepoSQL(deps.GetSQLDatabase().ReadDB(), deps.GetSQLDatabase().WriteDB()) 19 | setSharedRepoMongo(deps.GetMongoDatabase().ReadDB(), deps.GetMongoDatabase().WriteDB()) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /services/user-service/pkg/shared/repository/repository_mongo.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. DO NOT EDIT. 2 | 3 | package repository 4 | 5 | import ( 6 | "go.mongodb.org/mongo-driver/mongo" 7 | authrepo "monorepo/services/user-service/internal/modules/auth/repository" 8 | memberrepo "monorepo/services/user-service/internal/modules/member/repository" 9 | ) 10 | 11 | // RepoMongo uow 12 | type RepoMongo struct { 13 | readDB, writeDB *mongo.Database 14 | 15 | // register all repository from modules 16 | AuthRepo authrepo.AuthRepository 17 | MemberRepo memberrepo.MemberRepository 18 | } 19 | 20 | var globalRepoMongo = new(RepoMongo) 21 | 22 | // setSharedRepoMongo set the global singleton "RepoMongo" implementation 23 | func setSharedRepoMongo(readDB, writeDB *mongo.Database) { 24 | globalRepoMongo = &RepoMongo{ 25 | readDB: readDB, writeDB: writeDB, 26 | AuthRepo: authrepo.NewAuthRepoMongo(readDB, writeDB), 27 | MemberRepo: memberrepo.NewMemberRepoMongo(readDB, writeDB), 28 | } 29 | } 30 | 31 | // GetSharedRepoMongo returns the global singleton "RepoMongo" implementation 32 | func GetSharedRepoMongo() *RepoMongo { 33 | return globalRepoMongo 34 | } 35 | -------------------------------------------------------------------------------- /services/user-service/pkg/shared/token_validator.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package shared 4 | 5 | import ( 6 | "context" 7 | 8 | "pkg.agungdp.dev/candi/candishared" 9 | ) 10 | 11 | // DefaultTokenValidator for token validator 12 | type DefaultTokenValidator struct { 13 | } 14 | 15 | // ValidateToken implement TokenValidator 16 | func (v *DefaultTokenValidator) ValidateToken(ctx context.Context, token string) (*candishared.TokenClaim, error) { 17 | return &candishared.TokenClaim{}, nil 18 | } 19 | -------------------------------------------------------------------------------- /services/user-service/pkg/shared/usecase/usecase.go: -------------------------------------------------------------------------------- 1 | // Code generated by candi v1.3.1. 2 | 3 | package usecase 4 | 5 | import ( 6 | authusecase "monorepo/services/user-service/internal/modules/auth/usecase" 7 | memberusecase "monorepo/services/user-service/internal/modules/member/usecase" 8 | "sync" 9 | 10 | "pkg.agungdp.dev/candi/codebase/factory/dependency" 11 | ) 12 | 13 | type ( 14 | // Usecase unit of work for all usecase in modules 15 | Usecase interface { 16 | Auth() authusecase.AuthUsecase 17 | Member() memberusecase.MemberUsecase 18 | } 19 | 20 | usecaseUow struct { 21 | auth authusecase.AuthUsecase 22 | member memberusecase.MemberUsecase 23 | } 24 | ) 25 | 26 | var usecaseInst *usecaseUow 27 | var once sync.Once 28 | 29 | // SetSharedUsecase set singleton usecase unit of work instance 30 | func SetSharedUsecase(deps dependency.Dependency) { 31 | once.Do(func() { 32 | usecaseInst = &usecaseUow{ 33 | auth: authusecase.NewAuthUsecase(deps), 34 | member: memberusecase.NewMemberUsecase(deps), 35 | } 36 | }) 37 | } 38 | 39 | // GetSharedUsecase get usecase unit of work instance 40 | func GetSharedUsecase() Usecase { 41 | return usecaseInst 42 | } 43 | func (uc *usecaseUow) Auth() authusecase.AuthUsecase { 44 | return uc.auth 45 | } 46 | func (uc *usecaseUow) Member() memberusecase.MemberUsecase { 47 | return uc.member 48 | } 49 | --------------------------------------------------------------------------------