Skip to content

Commit

Permalink
Add hermes service.
Browse files Browse the repository at this point in the history
  • Loading branch information
just-pthai-it committed Aug 2, 2024
1 parent fc8be98 commit 4ce7802
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 3 deletions.
52 changes: 50 additions & 2 deletions compose-development.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ services:
- .env
build:
context: .
dockerfile: Dockerfile-local
dockerfile: Dockerfile-development
restart: unless-stopped
depends_on:
- feedback-postgres
Expand Down Expand Up @@ -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
Expand All @@ -42,8 +78,10 @@ services:
- 8081:8080
depends_on:
- feedback-postgres
- hermes-postgres
networks:
- feedback-postgres-network
- hermes-postgres-network

networks:
feedback-postgres-network:
Expand All @@ -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
name: ${APP_NAME}_feedback_postgres-data

hermes-postgres-data:
name: ${APP_NAME}_hermes_postgres-data
5 changes: 4 additions & 1 deletion compose-local-single-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
108 changes: 108 additions & 0 deletions core/controllers/HermesController.go
Original file line number Diff line number Diff line change
@@ -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!")
}
18 changes: 18 additions & 0 deletions core/http/requests/hermes-forms/SendMailFormRequest.go
Original file line number Diff line number Diff line change
@@ -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
}
17 changes: 17 additions & 0 deletions core/models/MailTemplate.go
Original file line number Diff line number Diff line change
@@ -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"`
}
33 changes: 33 additions & 0 deletions core/repositories/MailTemplateRepository.go
Original file line number Diff line number Diff line change
@@ -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
}
9 changes: 9 additions & 0 deletions core/routes/api-v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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.',
'<td style="padding: 1rem; padding-top: 1.5rem !important; padding-bottom: 1.5rem !important;"> <p> Xin chào {{.Name}},<br /> 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:<br /> </p> <p><a href="{{.Url}}" style="color: #04af04; font-size: 15px; text-decoration: none;">Đặt lại mật khẩu</a></p> <p>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.</p> <p>Trân trọng.</p> <div style="margin-top: 0.25rem; font-weight: bold; font-style: italic; color: #04af04;">Đội ngũ phát triển UTCKetnoi</div> </td>',
'layout1',
NOW(), NOW());
-- OIDS are not typically used in PostgreSQL
-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin
DELETE FROM mail_templates;
-- +goose StatementEnd

46 changes: 46 additions & 0 deletions storage/app/mail-layouts/layout1.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">

<head>
<title></title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>

<body>
<table style="width: 100%; height: 100%; font-family: Arial, Helvetica, sans-serif; border-collapse: collapse; font-family: Arial, Helvetica, sans-serif; font-size: 14px;">
<tbody>
<tr>
<td style="width: 100%; height: 100%; padding: 20px" class="box-wrapper">
<table style="border-collapse: collapse; width: 600px; margin: auto; border: 1px solid #dddddd;">
<tbody>
<tr class="header">
<td style="height: 100%; display: flex; padding: 1rem; border-bottom: 1px solid #dddddd;">
<a style="display: flex; align-items: center; text-decoration: none;" href="https://utcketnoi.edu.vn" target="_blank">
<img style="height: 100%; height: 65px; display: block; object-fit: cover;" src="https://utcketnoi.edu.vn/assets/img/logo-text.png" />
<span style="margin-left: 0.75rem; color: #000000; font-size: 20px; font-weight: 600;"> HỆ THỐNG QUẢN LÝ LỊCH GIẢNG DẠY </span>
</a>
</td>
</tr>
<tr class="body">
{{ .Content }}
</tr>
<tr class="footer">
<td style="text-align: center; padding: 1rem; font-size: 13px;">
<div style="height: 1px; margin: 0 5rem 1rem; background-color: #1976d2; opacity: 0.36;"></div>
<a href="https://utcketnoi.edu.vn" target="_blank"> UTCKetnoi </a><br />
<span style="display: block; margin-top: 0.25rem; color: #333333;"> Đây là tin nhắn tự động, vui lòng không phản hồi </span>
<span style="display: block; margin-top: 0.25rem; color: #333333;"> Để được hỗ trợ, vui lòng nhắn tin qua tài khoản hỗ trợ <a href="https://m.me/utcketnoi" target="_blank">tại đây</a>
</span>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>

</html>

0 comments on commit 4ce7802

Please sign in to comment.