From 4ce7802da5d824a8fdf11c8537447bcafcb3a2f2 Mon Sep 17 00:00:00 2001 From: just-pthai-it Date: Sat, 3 Aug 2024 01:37:49 +0700 Subject: [PATCH] Add hermes service. --- compose-development.yaml | 52 ++++++++- compose-local-single-service.yaml | 5 +- core/controllers/HermesController.go | 108 ++++++++++++++++++ .../hermes-forms/SendMailFormRequest.go | 18 +++ core/models/MailTemplate.go | 17 +++ core/repositories/MailTemplateRepository.go | 33 ++++++ core/routes/api-v1.go | 9 ++ ...0730162305_create_mail_templates_table.sql | 20 ++++ ...eate_seed_data_to_mail_templates_table.sql | 16 +++ storage/app/mail-layouts/layout1.html | 46 ++++++++ 10 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 core/controllers/HermesController.go create mode 100644 core/http/requests/hermes-forms/SendMailFormRequest.go create mode 100644 core/models/MailTemplate.go create mode 100644 core/repositories/MailTemplateRepository.go create mode 100644 database/migrations/hermes/20240730162305_create_mail_templates_table.sql create mode 100644 database/migrations/hermes/20240731162305_create_seed_data_to_mail_templates_table.sql create mode 100644 storage/app/mail-layouts/layout1.html diff --git a/compose-development.yaml b/compose-development.yaml index 1c5a8c5..1f86eac 100644 --- a/compose-development.yaml +++ b/compose-development.yaml @@ -5,7 +5,7 @@ services: - .env build: context: . - dockerfile: Dockerfile-local + dockerfile: Dockerfile-development restart: unless-stopped depends_on: - feedback-postgres @@ -34,6 +34,42 @@ services: networks: - feedback-postgres-network + + hermes-service: + container_name: ${APP_NAME}_hermes-service + env_file: + - .env + build: + context: . + dockerfile: Dockerfile-development + restart: unless-stopped + depends_on: + - hermes-postgres + environment: + SERVICE_NAME: hermes + DB_HOST: hermes-postgres + ports: + - 8080:8080 + - 40000:40000 + networks: + - hermes-service-network + - hermes-postgres-network + + hermes-postgres: + container_name: ${APP_NAME}_hermes-postgres + image: postgres:alpine3.19 + restart: unless-stopped + volumes: + - ${APP_NAME}_hermes_postgres-data:/var/lib/postgresql/data + environment: + POSTGRES_USER: $DB_USERNAME + POSTGRES_PASSWORD: $DB_PASSWORD + POSTGRES_DB: $DB_NAME + ports: + - 5432:5432 + networks: + - hermes-postgres-network + adminer: container_name: ${APP_NAME}_adminer-postgres image: adminer:4.8.1 @@ -42,8 +78,10 @@ services: - 8081:8080 depends_on: - feedback-postgres + - hermes-postgres networks: - feedback-postgres-network + - hermes-postgres-network networks: feedback-postgres-network: @@ -53,6 +91,16 @@ networks: name: ${APP_NAME}_feedback-service-network driver: bridge + hermes-postgres-network: + name: ${APP_NAME}_hermes_postgres-network + driver: bridge + hermes-service-network: + name: ${APP_NAME}_hermes-service-network + driver: bridge + volumes: feedback-postgres-data: - name: ${APP_NAME}_feedback_postgres-data \ No newline at end of file + name: ${APP_NAME}_feedback_postgres-data + + hermes-postgres-data: + name: ${APP_NAME}_hermes_postgres-data \ No newline at end of file diff --git a/compose-local-single-service.yaml b/compose-local-single-service.yaml index ebd17a2..e50a102 100644 --- a/compose-local-single-service.yaml +++ b/compose-local-single-service.yaml @@ -9,13 +9,16 @@ services: restart: unless-stopped depends_on: - postgres + security_opt: + - seccomp:unconfined volumes: - ./:/app ports: - 8080:8080 + - 40000:40000 networks: - - postgres-network - app-network + - postgres-network postgres: container_name: ${DOCKER_PREFIX_NAME}_postgres diff --git a/core/controllers/HermesController.go b/core/controllers/HermesController.go new file mode 100644 index 0000000..5e0dce2 --- /dev/null +++ b/core/controllers/HermesController.go @@ -0,0 +1,108 @@ +package controllers + +import ( + "TSS-microservices/common" + hermes_forms "TSS-microservices/core/http/requests/hermes-forms" + "TSS-microservices/core/models" + "TSS-microservices/core/repositories" + "bytes" + "fmt" + "github.com/gin-gonic/gin" + "net/http" + "net/smtp" + "os" + "strings" + "text/template" +) + +type HermesController struct { + Repository repositories.MailTemplateRepository +} + +// swagger:operation POST /hermes/send-mail Hermes SendMail +// +// # Send mail +// +// --- +// produces: +// - application/json +// consumes: +// - application/json +// responses: +// 200: +// description: send email +// schema: +// type: object +// properties: +// message: +// type: string +// 400: +// "$ref": "#/responses/ValidationError" + +func (controller *HermesController) SendMail(context *gin.Context) { + input := hermes_forms.SendMailFormRequest{} + if err := context.ShouldBindJSON(&input); err != nil { + context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + mailTemplate := models.MailTemplate{} + err := controller.Repository.GetByCode(&mailTemplate, input.Code) + if err != nil { + context.JSON(common.ErrorHandlerHttpResponse(err)) + return + } + + if input.Subject != "" { + mailTemplate.Subject = input.Subject + } + + message, err := controller.handleMailMessage(mailTemplate, input.PlaceHolder) + + if err != nil { + context.JSON(common.ErrorHandlerHttpResponse(err)) + return + } + + sendMail(message, input.Recipients) + + context.JSON(http.StatusCreated, gin.H{"message": "Successful!"}) +} + +func (controller *HermesController) handleMailMessage(mailTemplate models.MailTemplate, placeholder map[string]string) (string, error) { + for key, value := range placeholder { + mailTemplate.Content = strings.Replace(mailTemplate.Content, "{{."+key+"}}", value, -1) + } + + var message bytes.Buffer + mimeHeaders := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" + message.Write([]byte(fmt.Sprintf("Subject: "+mailTemplate.Subject+"\n%s\n\n", mimeHeaders))) + + mailTemplate1, _ := template.ParseFiles("storage/app/mail-layouts/" + mailTemplate.Layout + ".html") + + err := mailTemplate1.Execute(&message, struct { + Content string + }{Content: mailTemplate.Content}) + + if err != nil { + fmt.Println(err) + return "", err + } + + return message.String(), nil +} + +func sendMail(message string, recipients []string) { + mailHost := os.Getenv("MAIL_HOST") + mailPort := os.Getenv("MAIL_PORT") + sender := os.Getenv("MAIL_USERNAME") + password := os.Getenv("MAIL_PASSWORD") + + auth := smtp.PlainAuth("", sender, password, mailHost) + err := smtp.SendMail(mailHost+":"+mailPort, auth, sender, recipients, []byte(message)) + if err != nil { + fmt.Println(err) + return + } + fmt.Println("Email Sent Successfully!") +} diff --git a/core/http/requests/hermes-forms/SendMailFormRequest.go b/core/http/requests/hermes-forms/SendMailFormRequest.go new file mode 100644 index 0000000..364d40f --- /dev/null +++ b/core/http/requests/hermes-forms/SendMailFormRequest.go @@ -0,0 +1,18 @@ +package hermes_forms + +type SendMailFormRequest struct { + // required: true + Code string `json:"code" binding:"required"` + // required: true + Recipients []string `json:"recipients" binding:"required"` + PlaceHolder map[string]string `json:"placeholder" binding:"required"` + Subject string `json:"subject"` +} + +// swagger:parameters SendMail +type Payload struct { + // required: true + // in: body + // type: object + Body SendMailFormRequest +} diff --git a/core/models/MailTemplate.go b/core/models/MailTemplate.go new file mode 100644 index 0000000..17c842a --- /dev/null +++ b/core/models/MailTemplate.go @@ -0,0 +1,17 @@ +package models + +import ( + "gorm.io/gorm" + "time" +) + +type MailTemplate struct { + ID uint `gorm:"primaryKey" json:"id" binding:"numeric"` + Code string `json:"code" binding:"required"` + Subject string `json:"subject" binding:"required"` + Content string `json:"content" binding:"required"` + Layout string `json:"layout" binding:"required"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` +} diff --git a/core/repositories/MailTemplateRepository.go b/core/repositories/MailTemplateRepository.go new file mode 100644 index 0000000..21e1240 --- /dev/null +++ b/core/repositories/MailTemplateRepository.go @@ -0,0 +1,33 @@ +package repositories + +import ( + "TSS-microservices/core/models" + "TSS-microservices/database" +) + +type MailTemplateRepository struct { + database.Connection +} + +func (repository *MailTemplateRepository) ImplementFBRepo() { +} + +func (repository *MailTemplateRepository) Create(model *models.MailTemplate) error { + return repository.Connection.GormDb.Create(model).Error +} + +func (repository *MailTemplateRepository) GetById(model *models.MailTemplate) error { + return repository.Connection.GormDb.First(model, model.ID).Error +} + +func (repository *MailTemplateRepository) Paginate(MailTemplate *[]models.MailTemplate, limit int, offset int, conditions map[string]any) error { + if conditions == nil { + return repository.Connection.GormDb.Limit(limit).Offset(offset).Find(MailTemplate).Error + } + + return repository.Connection.GormDb.Where(conditions).Limit(limit).Offset(offset).Find(MailTemplate).Error +} + +func (repository *MailTemplateRepository) GetByCode(model *models.MailTemplate, code string) error { + return repository.Connection.GormDb.Where(map[string]any{"code": code}).First(model).Error +} diff --git a/core/routes/api-v1.go b/core/routes/api-v1.go index 2e2ca74..bf586e9 100644 --- a/core/routes/api-v1.go +++ b/core/routes/api-v1.go @@ -43,5 +43,14 @@ func NewApiRoutes(router *gin.Engine) { apiV1RouterGroup.GET("/feedback", feedbackController.GetMany) } + if serviceName == "hermes" { + hermesController := controllers.HermesController{ + Repository: repositories.MailTemplateRepository{ + Connection: databaseConnection, + }, + } + + apiV1RouterGroup.POST("/hermes/send-mail", hermesController.SendMail) + } } } diff --git a/database/migrations/hermes/20240730162305_create_mail_templates_table.sql b/database/migrations/hermes/20240730162305_create_mail_templates_table.sql new file mode 100644 index 0000000..270e38b --- /dev/null +++ b/database/migrations/hermes/20240730162305_create_mail_templates_table.sql @@ -0,0 +1,20 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE mail_templates +( + id SERIAL PRIMARY KEY, -- Use SERIAL for auto-incrementing integer + code VARCHAR(50) NOT NULL UNIQUE, + subject VARCHAR(100) NOT NULL UNIQUE, + content text NOT NULL DEFAULT '', + layout VARCHAR(50) NOT NULL UNIQUE, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + deleted_at TIMESTAMP DEFAULT NULL +) WITH (OIDS = FALSE); +-- OIDS are not typically used in PostgreSQL +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE IF EXISTS mail_templates; +-- +goose StatementEnd diff --git a/database/migrations/hermes/20240731162305_create_seed_data_to_mail_templates_table.sql b/database/migrations/hermes/20240731162305_create_seed_data_to_mail_templates_table.sql new file mode 100644 index 0000000..552921a --- /dev/null +++ b/database/migrations/hermes/20240731162305_create_seed_data_to_mail_templates_table.sql @@ -0,0 +1,16 @@ +-- +goose Up +-- +goose StatementBegin +INSERT INTO mail_templates (code, subject, content, layout, created_at, updated_at) +VALUES ('reset_password', + 'Yêu cầu đặt lại mật khẩu.', + '

Xin chào {{.Name}},
Bạn đã yêu cầu đặt lại mật khẩu. Hãy truy cập vào đường link phía dưới để đặt lại mật khẩu:

Đặt lại mật khẩu

Nếu không phải là bạn đã yêu cầu đặt lại mật khẩu, hãy bỏ qua email này.

Trân trọng.

Đội ngũ phát triển UTCKetnoi
', + 'layout1', + NOW(), NOW()); +-- OIDS are not typically used in PostgreSQL +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DELETE FROM mail_templates; +-- +goose StatementEnd + diff --git a/storage/app/mail-layouts/layout1.html b/storage/app/mail-layouts/layout1.html new file mode 100644 index 0000000..76c95da --- /dev/null +++ b/storage/app/mail-layouts/layout1.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + +
+ + + + + + + {{ .Content }} + + + + + +
+ + + HỆ THỐNG QUẢN LÝ LỊCH GIẢNG DẠY + +
+
+ + +