Skip to content

Commit 1d7809a

Browse files
Merge pull request #22 from one-project-one-month/feature/email-service
Feature/email service
2 parents 081f45c + 10ddfcc commit 1d7809a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+5791
-129
lines changed

backend/cms-sys/cmd/main.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"errors"
55
"github.com/multi-tenants-cms-golang/cms-sys/internal/types"
6+
"github.com/multi-tenants-cms-golang/cms-sys/pkg/aws"
67
"gorm.io/gorm"
78
"os"
89
"os/signal"
@@ -23,10 +24,10 @@ import (
2324
)
2425

2526
type DISection struct {
26-
repo repository.AuthRepository
27-
srv service.AuthService
28-
handler handler.AuthHandle
29-
ownerHandler handler.OwnerHandle
27+
repo repository.AuthRepository
28+
srv service.AuthService
29+
handler handler.AuthHandle
30+
ownerHandler handler.OwnerHandle
3031
pageRequestHandler handler.PageRequestHandle
3132
}
3233

@@ -78,7 +79,7 @@ func main() {
7879
if err != nil {
7980
appLogger.WithError(err).Fatal("Failed to close Redis connection")
8081
}
81-
}()
82+
}()
8283
//if err := utils.InitJWTKeysFromVault(); err != nil {
8384
// log.Fatalf("Vault key init failed: %v", err)
8485
//}
@@ -193,7 +194,11 @@ func DependencyInjectionSection(logger *logrus.Logger, db *gorm.DB) *DISection {
193194
}
194195
srv := service.NewService(logger, repo)
195196
authHandler := handler.NewHandler(srv)
196-
197+
bucketName := utils.GetEnv("BUCKET_NAME", "")
198+
s3, err := aws.NewS3Service(bucketName, logger)
199+
if err != nil {
200+
logger.WithError(err).Fatal("Failed to create S3 service")
201+
}
197202
// Owner
198203
ownerRepo := repository.NewOwnerRepository(logger, db)
199204
ownerService := service.NewOwnerService(logger, ownerRepo, repo)
@@ -202,13 +207,13 @@ func DependencyInjectionSection(logger *logrus.Logger, db *gorm.DB) *DISection {
202207
// Page Request
203208
pageRequestRepo := repository.NewPageRequestRepository(logger, db)
204209
pageRequestSrv := service.NewPageRequestService(logger, pageRequestRepo)
205-
pageRequestHandler := handler.NewPageRequestHandler(pageRequestSrv)
210+
pageRequestHandler := handler.NewPageRequestHandler(pageRequestSrv, s3)
206211

207212
return &DISection{
208-
repo: repo,
209-
srv: srv,
210-
handler: authHandler,
211-
ownerHandler: ownerHandler,
213+
repo: repo,
214+
srv: srv,
215+
handler: authHandler,
216+
ownerHandler: ownerHandler,
212217
pageRequestHandler: pageRequestHandler,
213218
}
214-
}
219+
}

backend/cms-sys/go.mod

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ module github.com/multi-tenants-cms-golang/cms-sys
33
go 1.24.4
44

55
require (
6+
github.com/aws/aws-sdk-go-v2/config v1.29.17
7+
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0
68
github.com/go-playground/validator/v10 v10.27.0
79
github.com/gofiber/fiber/v2 v2.52.8
810
github.com/golang-jwt/jwt/v5 v5.2.2
@@ -17,6 +19,22 @@ require (
1719

1820
require (
1921
github.com/andybalholm/brotli v1.1.0 // indirect
22+
github.com/aws/aws-sdk-go-v2 v1.36.5 // indirect
23+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
24+
github.com/aws/aws-sdk-go-v2/credentials v1.17.70 // indirect
25+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect
26+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect
27+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect
28+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
29+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect
30+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
31+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 // indirect
32+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect
33+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 // indirect
34+
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect
35+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect
36+
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect
37+
github.com/aws/smithy-go v1.22.4 // indirect
2038
github.com/cespare/xxhash/v2 v2.3.0 // indirect
2139
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
2240
github.com/gabriel-vasile/mimetype v1.4.8 // indirect

backend/cms-sys/go.sum

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,41 @@
11
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
22
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
3+
github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0=
4+
github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
5+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA=
6+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY=
7+
github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0=
8+
github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8=
9+
github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0=
10+
github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc=
11+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU=
12+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8=
13+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s=
14+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo=
15+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM=
16+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8=
17+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
18+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
19+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs=
20+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36/go.mod h1:gDhdAV6wL3PmPqBhiPbnlS447GoWs8HTTOYef9/9Inw=
21+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc=
22+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ=
23+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074=
24+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE=
25+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI=
26+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=
27+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=
28+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=
29+
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0 h1:5Y75q0RPQoAbieyOuGLhjV9P3txvYgXv2lg0UwJOfmE=
30+
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
31+
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg=
32+
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc=
33+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg=
34+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E=
35+
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0=
36+
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w=
37+
github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw=
38+
github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
339
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
440
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
541
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=

backend/cms-sys/internal/handler/page_request_handler.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package handler
33
import (
44
"github.com/go-playground/validator/v10"
55
"github.com/gofiber/fiber/v2"
6+
"github.com/multi-tenants-cms-golang/cms-sys/pkg/aws"
67

78
"github.com/multi-tenants-cms-golang/cms-sys/internal/service"
89
"github.com/multi-tenants-cms-golang/cms-sys/internal/types"
@@ -17,19 +18,30 @@ type PageRequestHandle interface {
1718
type PageRequestHandler struct {
1819
service service.PageRequestService
1920
validator *validator.Validate
21+
s3Service *aws.S3Service
2022
}
2123

2224
var _ PageRequestHandle = (*PageRequestHandler)(nil)
2325

24-
func NewPageRequestHandler(service service.PageRequestService) PageRequestHandle {
26+
func NewPageRequestHandler(
27+
service service.PageRequestService,
28+
s3 *aws.S3Service,
29+
) PageRequestHandle {
2530
return &PageRequestHandler{
2631
service: service,
2732
validator: validator.New(),
33+
s3Service: s3,
2834
}
2935
}
3036

3137
func (h *PageRequestHandler) Create(c *fiber.Ctx) error {
3238
var req types.CreatePageRequest
39+
if form, err := c.MultipartForm(); err != nil {
40+
if files := form.File["logo"]; len(files) > 0 {
41+
req.LogoFile = files[0]
42+
}
43+
}
44+
3345
if err := c.BodyParser(&req); err != nil {
3446
return utils.BadRequestResponse(c, "Invalid request body", err.Error())
3547
}
@@ -38,7 +50,16 @@ func (h *PageRequestHandler) Create(c *fiber.Ctx) error {
3850
return utils.BadRequestResponse(c, "Validation failed", err.Error())
3951
}
4052

41-
pageRequestResponse, err := h.service.CreatePageRequest(req)
53+
var logoURL *string
54+
if req.LogoFile != nil {
55+
uploadedURL, err := h.s3Service.UploadFile(req.LogoFile)
56+
if err != nil {
57+
return utils.InternalServerErrorResponse(c, "Failed to upload logo", err.Error())
58+
}
59+
logoURL = uploadedURL
60+
}
61+
62+
pageRequestResponse, err := h.service.CreatePageRequest(req,logoURL)
4263
if err != nil {
4364
return utils.InternalServerErrorResponse(c, "Failed to create page request", err.Error())
4465
}

backend/cms-sys/internal/service/page_request_service.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ package service
22

33
import (
44
"errors"
5-
"math"
65
"github.com/google/uuid"
6+
"math"
77
"strings"
88

9+
"github.com/multi-tenants-cms-golang/cms-sys/internal/mapper"
910
"github.com/multi-tenants-cms-golang/cms-sys/internal/repository"
1011
"github.com/multi-tenants-cms-golang/cms-sys/internal/types"
1112
"github.com/multi-tenants-cms-golang/cms-sys/pkg/utils"
12-
"github.com/multi-tenants-cms-golang/cms-sys/internal/mapper"
1313
"github.com/sirupsen/logrus"
1414
"time"
1515
)
1616

1717
type PageRequestService interface {
18-
CreatePageRequest(req types.CreatePageRequest) (*types.PageRequestResponse, error)
18+
CreatePageRequest(req types.CreatePageRequest, logourl *string) (*types.PageRequestResponse, error)
1919
GetAllPageRequests(req *types.PaginateRequest) ([]*types.PageRequestResponse, *utils.Pagination, error)
2020
}
2121

@@ -33,7 +33,7 @@ func NewPageRequestService(logger *logrus.Logger, repo repository.PageRequestRep
3333
}
3434
}
3535

36-
func (s *PageRequestServiceImpl) CreatePageRequest(req types.CreatePageRequest) (*types.PageRequestResponse, error) {
36+
func (s *PageRequestServiceImpl) CreatePageRequest(req types.CreatePageRequest, logoUrl *string) (*types.PageRequestResponse, error) {
3737
ownerUUID, err := uuid.Parse(strings.TrimSpace(req.OwnerID))
3838
if err != nil {
3939
return nil, errors.New("invalid owner ID format")
@@ -46,7 +46,7 @@ func (s *PageRequestServiceImpl) CreatePageRequest(req types.CreatePageRequest)
4646
Title: req.Title,
4747
Description: &req.Description,
4848
PageUrl: req.PageUrl,
49-
LogoUrl: req.Logo, // ko swan will integrate with s3 later
49+
LogoUrl: logoUrl,
5050
CreatedAt: time.Now(),
5151
UpdatedAt: time.Now(),
5252
}

backend/cms-sys/internal/types/request.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package types
22

3+
import "mime/multipart"
4+
35
type LoginRequest struct {
46
Email string `json:"email" validate:"required,email"`
57
Password string `json:"password" validate:"required,min=6"`
@@ -41,15 +43,15 @@ type PaginateRequest struct {
4143
}
4244

4345
type CreatePageRequest struct {
44-
OwnerID string `json:"ownerId" validate:"required,uuid4"`
45-
RequestType string `json:"requestType"`
46-
Title string `json:"title"`
47-
Description string `json:"description"`
48-
PageUrl *string `json:"pageUrl"`
49-
Logo *string `json:"logo"`
46+
OwnerID string `json:"ownerId" validate:"required,uuid4"`
47+
RequestType string `json:"requestType"`
48+
Title string `json:"title"`
49+
Description string `json:"description"`
50+
PageUrl *string `json:"pageUrl"`
51+
LogoFile *multipart.FileHeader `json:"logo"`
5052
}
5153

5254
type OwnerDeleteRequest struct {
5355
IDs []string `json:"ids" validate:"required"`
5456
ForceDelete bool `json:"forceDelete"`
55-
}
57+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package aws
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/aws/aws-sdk-go-v2/aws"
7+
"github.com/aws/aws-sdk-go-v2/config"
8+
"github.com/aws/aws-sdk-go-v2/credentials"
9+
"github.com/aws/aws-sdk-go-v2/service/s3"
10+
"github.com/sirupsen/logrus"
11+
"mime/multipart"
12+
"os"
13+
"path/filepath"
14+
"time"
15+
)
16+
17+
type S3Service struct {
18+
client *s3.Client
19+
bucket string
20+
logger *logrus.Logger
21+
}
22+
23+
func NewS3Service(bucket string, logger *logrus.Logger) (*S3Service, error) {
24+
accessKey := os.Getenv("AWS_ACCESS_KEY")
25+
secretKey := os.Getenv("AWS_SECRET_KEY")
26+
region := os.Getenv("AWS_REGION")
27+
28+
cfg, err := config.LoadDefaultConfig(context.Background(),
29+
config.WithRegion(region),
30+
config.WithCredentialsProvider(
31+
credentials.NewStaticCredentialsProvider(accessKey, secretKey, ""),
32+
),
33+
)
34+
if err != nil {
35+
return nil, err
36+
}
37+
client := s3.NewFromConfig(cfg)
38+
svc := &S3Service{
39+
client: client,
40+
bucket: bucket,
41+
logger: logger,
42+
}
43+
return svc, nil
44+
}
45+
46+
func (s *S3Service) UploadFile(file *multipart.FileHeader) (*string, error) {
47+
src, err := file.Open()
48+
if err != nil {
49+
s.logger.Errorf("failed to open file: %v", err.Error())
50+
return nil, err
51+
}
52+
defer func(src multipart.File) {
53+
err := src.Close()
54+
if err != nil {
55+
s.logger.Errorf("failed to close file: %v", err.Error())
56+
}
57+
}(src)
58+
ext := filepath.Ext(file.Filename)
59+
filename := "logos/" + time.Now().Format("20060102150405") + ext
60+
61+
_, err = s.client.PutObject(context.Background(), &s3.PutObjectInput{
62+
Bucket: aws.String(s.bucket),
63+
Key: aws.String(filename),
64+
Body: src,
65+
ACL: "public-read",
66+
})
67+
68+
if err != nil {
69+
s.logger.Errorf("failed to upload file: %v", err.Error())
70+
return nil, err
71+
}
72+
fileUrl := fmt.Sprintf("https://%s%s%s", s.bucket, ".s3.amazonaws.com/", filename)
73+
return &fileUrl, nil
74+
75+
}

backend/email-service/Dockerfile

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1-
FROM golang:1.21-alpine AS builder
1+
FROM golang:1.24-alpine AS builder
22

33
WORKDIR /app
4+
5+
RUN apk add --no-cache git ca-certificates tzdata
6+
47
COPY go.mod go.sum ./
58
RUN go mod download
69

710
COPY . .
8-
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
911

10-
FROM alpine:latest
11-
RUN apk --no-cache add ca-certificates
12-
WORKDIR /root/
12+
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-w -s" -o server ./cmd
13+
14+
FROM scratch
1315

14-
COPY --from=builder /app/main .
15-
COPY --from=builder /app/templates ./templates/
16+
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
17+
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
18+
19+
COPY --from=builder /app/server /server
1620

1721
EXPOSE 8080
18-
CMD ["./main"]
22+
23+
CMD ["/server"]

backend/email-service/cmd/main.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ func main() {
5353
defer nc.Close()
5454

5555
emailCh := make(chan natss.EmailRequest, 100)
56-
57-
// Create email service
5856
emailService, err := email.NewService(
5957
logger,
6058
cfg.SMTP.Host,

backend/email-service/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func Load() (*Config, error) {
6767
}
6868

6969
if err := cfg.validate(); err != nil {
70-
return nil, fmt.Errorf("config validation failed: %w", err)
70+
return nil, fmt.Errorf("convert validation failed: %w", err)
7171
}
7272

7373
return cfg, nil

0 commit comments

Comments
 (0)