From a11b63a32a7b85fe3afe36089ff75e15eae445ca Mon Sep 17 00:00:00 2001 From: Mahdi Khanzadi Date: Tue, 20 Aug 2024 13:32:58 +0200 Subject: [PATCH] render email templates --- .../article/getArticle/usecase_test.go | 2 +- .../auth/forgetpassword/usecase.go | 11 ++- .../auth/forgetpassword/usecase_test.go | 53 +++++++++++- backend/application/auth/register/usecase.go | 15 +++- .../application/auth/register/usecase_test.go | 52 ++++++++++-- backend/domain/renderer.go | 7 ++ backend/infrastructure/template/mock.go | 21 +++++ backend/infrastructure/template/renderer.go | 81 +++++++++++++++++++ backend/main.go | 17 ++-- .../http/api/auth/forgetpassword.go | 3 + .../presentation/http/api/auth/register.go | 2 + .../http/api/auth/resetpassword.go | 2 + backend/presentation/http/api/auth/verify.go | 2 + .../resources/view/layout/base.layout.tmpl | 13 +++ .../resources/view/mail/auth/register.tmpl | 34 ++++++++ .../view/mail/auth/reset-password.tmpl | 34 ++++++++ 16 files changed, 330 insertions(+), 19 deletions(-) create mode 100644 backend/domain/renderer.go create mode 100644 backend/infrastructure/template/mock.go create mode 100644 backend/infrastructure/template/renderer.go create mode 100644 backend/resources/view/layout/base.layout.tmpl create mode 100644 backend/resources/view/mail/auth/register.tmpl create mode 100644 backend/resources/view/mail/auth/reset-password.tmpl diff --git a/backend/application/article/getArticle/usecase_test.go b/backend/application/article/getArticle/usecase_test.go index 10710437..b742c39f 100644 --- a/backend/application/article/getArticle/usecase_test.go +++ b/backend/application/article/getArticle/usecase_test.go @@ -164,7 +164,7 @@ func TestUseCase_Execute(t *testing.T) { assert.ErrorIs(t, err, expectedErr) }) - t.Run("error on increasing view count is not reflected on response", func(t *testing.T) { + t.Run("error on increasing template count is not reflected on response", func(t *testing.T) { var ( articlesRepository articles.MockArticlesRepository elementsRepository elements.MockElementsRepository diff --git a/backend/application/auth/forgetpassword/usecase.go b/backend/application/auth/forgetpassword/usecase.go index 6ef88420..e8058c57 100644 --- a/backend/application/auth/forgetpassword/usecase.go +++ b/backend/application/auth/forgetpassword/usecase.go @@ -11,11 +11,17 @@ import ( "github.com/khanzadimahdi/testproject/infrastructure/jwt" ) +const ( + templateName = "resources/template/mail/auth/reset-password" + resetPasswordURL = "https://tarhche.com/auth/reset-password?token=" +) + type UseCase struct { userRepository user.Repository jwt *jwt.JWT mailer domain.Mailer mailFrom string + template domain.Renderer } func NewUseCase( @@ -23,12 +29,14 @@ func NewUseCase( JWT *jwt.JWT, mailer domain.Mailer, mailFrom string, + template domain.Renderer, ) *UseCase { return &UseCase{ userRepository: userRepository, jwt: JWT, mailer: mailer, mailFrom: mailFrom, + template: template, } } @@ -52,7 +60,8 @@ func (uc *UseCase) Execute(request Request) (*Response, error) { resetPasswordToken = base64.URLEncoding.EncodeToString([]byte(resetPasswordToken)) var msg bytes.Buffer - if _, err := msg.WriteString(resetPasswordToken); err != nil { + viewData := map[string]string{"resetPasswordURL": resetPasswordURL + resetPasswordToken} + if err := uc.template.Render(&msg, templateName, viewData); err != nil { return nil, err } diff --git a/backend/application/auth/forgetpassword/usecase_test.go b/backend/application/auth/forgetpassword/usecase_test.go index 10fc8fc6..d46a4af7 100644 --- a/backend/application/auth/forgetpassword/usecase_test.go +++ b/backend/application/auth/forgetpassword/usecase_test.go @@ -12,6 +12,7 @@ import ( "github.com/khanzadimahdi/testproject/infrastructure/email" "github.com/khanzadimahdi/testproject/infrastructure/jwt" "github.com/khanzadimahdi/testproject/infrastructure/repository/mocks/users" + "github.com/khanzadimahdi/testproject/infrastructure/template" ) func TestUseCase_Execute(t *testing.T) { @@ -27,6 +28,7 @@ func TestUseCase_Execute(t *testing.T) { var ( userRepository users.MockUsersRepository mailer email.MockMailer + renderer template.MockRenderer request = Request{ Identity: "something@somewhere.loc", @@ -41,10 +43,13 @@ func TestUseCase_Execute(t *testing.T) { userRepository.On("GetOneByIdentity", request.Identity).Once().Return(u, nil) defer userRepository.AssertExpectations(t) + renderer.On("Render", mock.Anything, templateName, mock.Anything).Once().Return(nil) + defer renderer.AssertExpectations(t) + mailer.On("SendMail", mailFrom, u.Email, "Reset Password", mock.AnythingOfType("[]uint8")).Once().Return(nil) defer mailer.AssertExpectations(t) - response, err := NewUseCase(&userRepository, j, &mailer, mailFrom).Execute(request) + response, err := NewUseCase(&userRepository, j, &mailer, mailFrom, &renderer).Execute(request) assert.NoError(t, err) assert.NotNil(t, response) }) @@ -53,6 +58,7 @@ func TestUseCase_Execute(t *testing.T) { var ( userRepository users.MockUsersRepository mailer email.MockMailer + renderer template.MockRenderer request = Request{} expectedResponse = Response{ @@ -62,9 +68,10 @@ func TestUseCase_Execute(t *testing.T) { } ) - response, err := NewUseCase(&userRepository, j, &mailer, mailFrom).Execute(request) + response, err := NewUseCase(&userRepository, j, &mailer, mailFrom, &renderer).Execute(request) userRepository.AssertNotCalled(t, "GetOneByIdentity") + renderer.AssertNotCalled(t, "Render") mailer.AssertNotCalled(t, "SendMail") assert.NoError(t, err) @@ -76,6 +83,7 @@ func TestUseCase_Execute(t *testing.T) { var ( userRepository users.MockUsersRepository mailer email.MockMailer + renderer template.MockRenderer request = Request{ Identity: "something@somewhere.loc", @@ -87,7 +95,40 @@ func TestUseCase_Execute(t *testing.T) { userRepository.On("GetOneByIdentity", request.Identity).Once().Return(user.User{}, expectedErr) defer userRepository.AssertExpectations(t) - response, err := NewUseCase(&userRepository, j, &mailer, mailFrom).Execute(request) + response, err := NewUseCase(&userRepository, j, &mailer, mailFrom, &renderer).Execute(request) + + renderer.AssertNotCalled(t, "Render") + mailer.AssertNotCalled(t, "SendMail") + + assert.ErrorIs(t, expectedErr, err) + assert.Nil(t, response) + }) + + t.Run("error on rendering template", func(t *testing.T) { + var ( + userRepository users.MockUsersRepository + mailer email.MockMailer + renderer template.MockRenderer + + request = Request{ + Identity: "something@somewhere.loc", + } + + u = user.User{ + UUID: "user-uuid", + Email: request.Identity, + } + + expectedErr = errors.New("something bad happened") + ) + + userRepository.On("GetOneByIdentity", request.Identity).Once().Return(u, nil) + defer userRepository.AssertExpectations(t) + + renderer.On("Render", mock.Anything, templateName, mock.Anything).Once().Return(expectedErr) + defer renderer.AssertExpectations(t) + + response, err := NewUseCase(&userRepository, j, &mailer, mailFrom, &renderer).Execute(request) mailer.AssertNotCalled(t, "SendMail") @@ -99,6 +140,7 @@ func TestUseCase_Execute(t *testing.T) { var ( userRepository users.MockUsersRepository mailer email.MockMailer + renderer template.MockRenderer request = Request{ Identity: "something@somewhere.loc", @@ -115,10 +157,13 @@ func TestUseCase_Execute(t *testing.T) { userRepository.On("GetOneByIdentity", request.Identity).Once().Return(u, nil) defer userRepository.AssertExpectations(t) + renderer.On("Render", mock.Anything, templateName, mock.Anything).Once().Return(nil) + defer renderer.AssertExpectations(t) + mailer.On("SendMail", mailFrom, u.Email, "Reset Password", mock.AnythingOfType("[]uint8")).Once().Return(expectedErr) defer mailer.AssertExpectations(t) - response, err := NewUseCase(&userRepository, j, &mailer, mailFrom).Execute(request) + response, err := NewUseCase(&userRepository, j, &mailer, mailFrom, &renderer).Execute(request) assert.ErrorIs(t, expectedErr, err) assert.Nil(t, response) }) diff --git a/backend/application/auth/register/usecase.go b/backend/application/auth/register/usecase.go index 11795b98..c5130525 100644 --- a/backend/application/auth/register/usecase.go +++ b/backend/application/auth/register/usecase.go @@ -12,11 +12,17 @@ import ( "github.com/khanzadimahdi/testproject/infrastructure/jwt" ) +const ( + templateName = "resources/template/mail/auth/register" + registrationURL = "https://tarhche.com/auth/reset-password?token=" +) + type UseCase struct { userRepository user.Repository jwt *jwt.JWT mailer domain.Mailer mailFrom string + template domain.Renderer } func NewUseCase( @@ -24,12 +30,14 @@ func NewUseCase( JWT *jwt.JWT, mailer domain.Mailer, mailFrom string, + template domain.Renderer, ) *UseCase { return &UseCase{ userRepository: userRepository, jwt: JWT, mailer: mailer, mailFrom: mailFrom, + template: template, } } @@ -50,15 +58,16 @@ func (uc *UseCase) Execute(request Request) (*Response, error) { }, nil } - resetPasswordToken, err := uc.registrationToken(request.Identity) + registrationToken, err := uc.registrationToken(request.Identity) if err != nil { return nil, err } - resetPasswordToken = base64.URLEncoding.EncodeToString([]byte(resetPasswordToken)) + registrationToken = base64.URLEncoding.EncodeToString([]byte(registrationToken)) var msg bytes.Buffer - if _, err := msg.WriteString(resetPasswordToken); err != nil { + viewData := map[string]string{"registrationURL": registrationURL + registrationToken} + if err := uc.template.Render(&msg, templateName, viewData); err != nil { return nil, err } diff --git a/backend/application/auth/register/usecase_test.go b/backend/application/auth/register/usecase_test.go index ad18b21e..7fbde8b5 100644 --- a/backend/application/auth/register/usecase_test.go +++ b/backend/application/auth/register/usecase_test.go @@ -13,6 +13,7 @@ import ( "github.com/khanzadimahdi/testproject/infrastructure/email" "github.com/khanzadimahdi/testproject/infrastructure/jwt" "github.com/khanzadimahdi/testproject/infrastructure/repository/mocks/users" + "github.com/khanzadimahdi/testproject/infrastructure/template" ) func TestUseCase_Execute(t *testing.T) { @@ -28,6 +29,7 @@ func TestUseCase_Execute(t *testing.T) { var ( userRepository users.MockUsersRepository mailer email.MockMailer + renderer template.MockRenderer r = Request{ Identity: "test@mail.com", @@ -37,10 +39,13 @@ func TestUseCase_Execute(t *testing.T) { userRepository.On("GetOneByIdentity", r.Identity).Once().Return(user.User{}, nil) defer userRepository.AssertExpectations(t) + renderer.On("Render", mock.Anything, templateName, mock.Anything).Once().Return(nil) + defer renderer.AssertExpectations(t) + mailer.On("SendMail", mailFrom, r.Identity, "Registration", mock.AnythingOfType("[]uint8")).Once().Return(nil) defer mailer.AssertExpectations(t) - response, err := NewUseCase(&userRepository, j, &mailer, mailFrom).Execute(r) + response, err := NewUseCase(&userRepository, j, &mailer, mailFrom, &renderer).Execute(r) assert.NoError(t, err) assert.NotNil(t, response) @@ -51,15 +56,17 @@ func TestUseCase_Execute(t *testing.T) { var ( userRepository users.MockUsersRepository mailer email.MockMailer + renderer template.MockRenderer r = Request{ Identity: "somethingForTest", } ) - response, err := NewUseCase(&userRepository, j, &mailer, mailFrom).Execute(r) + response, err := NewUseCase(&userRepository, j, &mailer, mailFrom, &renderer).Execute(r) userRepository.AssertNotCalled(t, "GetOneByIdentity") + renderer.AssertNotCalled(t, "Render") mailer.AssertNotCalled(t, "SendMail") assert.NoError(t, err) @@ -71,6 +78,7 @@ func TestUseCase_Execute(t *testing.T) { var ( userRepository users.MockUsersRepository mailer email.MockMailer + renderer template.MockRenderer r = Request{ Identity: "test@mail.com", @@ -90,8 +98,9 @@ func TestUseCase_Execute(t *testing.T) { userRepository.On("GetOneByIdentity", r.Identity).Once().Return(u, nil) defer userRepository.AssertExpectations(t) - response, err := NewUseCase(&userRepository, j, &mailer, mailFrom).Execute(r) + response, err := NewUseCase(&userRepository, j, &mailer, mailFrom, &renderer).Execute(r) + renderer.AssertNotCalled(t, "Render") mailer.AssertNotCalled(t, "SendMail") assert.NoError(t, err) @@ -103,6 +112,7 @@ func TestUseCase_Execute(t *testing.T) { var ( userRepository users.MockUsersRepository mailer email.MockMailer + renderer template.MockRenderer r = Request{ Identity: "test@mail.com", @@ -118,7 +128,35 @@ func TestUseCase_Execute(t *testing.T) { userRepository.On("GetOneByIdentity", r.Identity).Once().Return(u, expectedError) defer userRepository.AssertExpectations(t) - response, err := NewUseCase(&userRepository, j, &mailer, mailFrom).Execute(r) + response, err := NewUseCase(&userRepository, j, &mailer, mailFrom, &renderer).Execute(r) + + renderer.AssertNotCalled(t, "Render") + mailer.AssertNotCalled(t, "SendMail") + + assert.ErrorIs(t, err, expectedError) + assert.Nil(t, response) + }) + + t.Run("error on rendering template", func(t *testing.T) { + var ( + userRepository users.MockUsersRepository + mailer email.MockMailer + renderer template.MockRenderer + + r = Request{ + Identity: "test@mail.com", + } + + expectedError = errors.New("some error") + ) + + userRepository.On("GetOneByIdentity", r.Identity).Once().Return(user.User{}, domain.ErrNotExists) + defer userRepository.AssertExpectations(t) + + renderer.On("Render", mock.Anything, templateName, mock.Anything).Once().Return(expectedError) + defer renderer.AssertExpectations(t) + + response, err := NewUseCase(&userRepository, j, &mailer, mailFrom, &renderer).Execute(r) mailer.AssertNotCalled(t, "SendMail") @@ -130,6 +168,7 @@ func TestUseCase_Execute(t *testing.T) { var ( userRepository users.MockUsersRepository mailer email.MockMailer + renderer template.MockRenderer r = Request{ Identity: "test@mail.com", @@ -141,10 +180,13 @@ func TestUseCase_Execute(t *testing.T) { userRepository.On("GetOneByIdentity", r.Identity).Once().Return(user.User{}, domain.ErrNotExists) defer userRepository.AssertExpectations(t) + renderer.On("Render", mock.Anything, templateName, mock.Anything).Once().Return(nil) + defer renderer.AssertExpectations(t) + mailer.On("SendMail", mailFrom, r.Identity, "Registration", mock.AnythingOfType("[]uint8")).Once().Return(expectedError) defer mailer.AssertExpectations(t) - response, err := NewUseCase(&userRepository, j, &mailer, mailFrom).Execute(r) + response, err := NewUseCase(&userRepository, j, &mailer, mailFrom, &renderer).Execute(r) assert.ErrorIs(t, err, expectedError) assert.Nil(t, response) diff --git a/backend/domain/renderer.go b/backend/domain/renderer.go new file mode 100644 index 00000000..127bdab6 --- /dev/null +++ b/backend/domain/renderer.go @@ -0,0 +1,7 @@ +package domain + +import "io" + +type Renderer interface { + Render(writer io.Writer, templateName string, data any) error +} diff --git a/backend/infrastructure/template/mock.go b/backend/infrastructure/template/mock.go new file mode 100644 index 00000000..490891f1 --- /dev/null +++ b/backend/infrastructure/template/mock.go @@ -0,0 +1,21 @@ +package template + +import ( + "io" + + "github.com/stretchr/testify/mock" + + "github.com/khanzadimahdi/testproject/domain" +) + +type MockRenderer struct { + mock.Mock +} + +var _ domain.Renderer = &MockRenderer{} + +func (r *MockRenderer) Render(writer io.Writer, templateName string, data any) error { + args := r.Mock.Called(writer, templateName, data) + + return args.Error(0) +} diff --git a/backend/infrastructure/template/renderer.go b/backend/infrastructure/template/renderer.go new file mode 100644 index 00000000..50a67bc0 --- /dev/null +++ b/backend/infrastructure/template/renderer.go @@ -0,0 +1,81 @@ +package template + +import ( + "html/template" + "io" + "io/fs" + "path" + "slices" + "strings" + "sync" + + "github.com/khanzadimahdi/testproject/domain" +) + +type Renderer struct { + fileSystem fs.FS + extension string + + lock sync.Mutex + files []string + cache map[string]*template.Template +} + +var _ domain.Renderer = &Renderer{} + +func NewRenderer(fileSystem fs.FS, extension string) *Renderer { + return &Renderer{ + fileSystem: fileSystem, + extension: extension, + cache: make(map[string]*template.Template), + } +} + +func (r *Renderer) Render(writer io.Writer, templateName string, data any) error { + r.lock.Lock() + defer r.lock.Unlock() + + if len(r.files) == 0 { + files, err := walkDir(r.fileSystem, r.extension) + if err != nil { + return err + } + slices.Sort(files) + + r.files = files + } + + index, ok := slices.BinarySearch(r.files, templateName+"."+r.extension) + if !ok { + return nil + } + + if _, ok := r.cache[templateName]; !ok { + t, err := template.New(path.Base(r.files[index])).ParseFS(r.fileSystem, r.files...) + if err != nil { + return err + } + r.cache[templateName] = t + } + + return r.cache[templateName].Execute(writer, data) +} + +func walkDir(fileSystem fs.FS, extension string) ([]string, error) { + var files []string + + err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error { + if d.IsDir() { + return nil + } + + if strings.HasSuffix(path, "."+extension) { + files = append(files, path) + return nil + } + + return nil + }) + + return files, err +} diff --git a/backend/main.go b/backend/main.go index 768ac720..2851abea 100644 --- a/backend/main.go +++ b/backend/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "embed" "fmt" "log" "net/http" @@ -75,6 +76,7 @@ import ( rolesrepository "github.com/khanzadimahdi/testproject/infrastructure/repository/mongodb/roles" userrepository "github.com/khanzadimahdi/testproject/infrastructure/repository/mongodb/users" "github.com/khanzadimahdi/testproject/infrastructure/storage/minio" + "github.com/khanzadimahdi/testproject/infrastructure/template" "github.com/khanzadimahdi/testproject/presentation/commands" articleAPI "github.com/khanzadimahdi/testproject/presentation/http/api/article" "github.com/khanzadimahdi/testproject/presentation/http/api/auth" @@ -95,13 +97,16 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" ) +//go:embed resources/view +var files embed.FS + func main() { ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) defer cancel() - console := console.NewConsole(path.Base(os.Args[0]), "Application description", os.Stderr) - console.Register(commands.NewServeCommand(httpHandler())) - code := console.Run(ctx, os.Args) + c := console.NewConsole(path.Base(os.Args[0]), "Application description", os.Stderr) + c.Register(commands.NewServeCommand(httpHandler())) + code := c.Run(ctx, os.Args) cancel() os.Exit(code) @@ -157,6 +162,8 @@ func httpHandler() http.Handler { j := jwt.NewJWT(privateKey, privateKey.Public()) hasher := argon2.NewArgon2id(2, 64*1024, 2, 64) + templateRenderer := template.NewRenderer(files, "tmpl") + mailFromAddress := os.Getenv("MAIL_SMTP_FROM") mailer := email.NewSMTP(email.Config{ Auth: email.Auth{ @@ -175,9 +182,9 @@ func httpHandler() http.Handler { log.SetFlags(log.LstdFlags | log.Llongfile) loginUseCase := login.NewUseCase(userRepository, j, hasher) refreshUseCase := refresh.NewUseCase(userRepository, j) - forgetPasswordUseCase := forgetpassword.NewUseCase(userRepository, j, mailer, mailFromAddress) + forgetPasswordUseCase := forgetpassword.NewUseCase(userRepository, j, mailer, mailFromAddress, templateRenderer) resetPasswordUseCase := resetpassword.NewUseCase(userRepository, hasher, j) - registerUseCase := register.NewUseCase(userRepository, j, mailer, mailFromAddress) + registerUseCase := register.NewUseCase(userRepository, j, mailer, mailFromAddress, templateRenderer) verifyUseCase := verify.NewUseCase(userRepository, hasher, j) getArticleUsecase := getArticle.NewUseCase(articlesRepository, elementsRepository) diff --git a/backend/presentation/http/api/auth/forgetpassword.go b/backend/presentation/http/api/auth/forgetpassword.go index c2a89001..8db4cca2 100644 --- a/backend/presentation/http/api/auth/forgetpassword.go +++ b/backend/presentation/http/api/auth/forgetpassword.go @@ -3,6 +3,7 @@ package auth import ( "encoding/json" "errors" + "log" "net/http" "github.com/khanzadimahdi/testproject/application/auth/forgetpassword" @@ -32,6 +33,8 @@ func (h *forgetPasswordHandler) ServeHTTP(rw http.ResponseWriter, r *http.Reques case errors.Is(err, domain.ErrNotExists): rw.WriteHeader(http.StatusNotFound) case err != nil: + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) case len(response.ValidationErrors) > 0: rw.WriteHeader(http.StatusBadRequest) diff --git a/backend/presentation/http/api/auth/register.go b/backend/presentation/http/api/auth/register.go index 1b660b1a..38a41118 100644 --- a/backend/presentation/http/api/auth/register.go +++ b/backend/presentation/http/api/auth/register.go @@ -3,6 +3,7 @@ package auth import ( "encoding/json" "errors" + "log" "net/http" "github.com/khanzadimahdi/testproject/application/auth/register" @@ -32,6 +33,7 @@ func (h *registerHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { case errors.Is(err, domain.ErrNotExists): rw.WriteHeader(http.StatusNotFound) case err != nil: + log.Println(err) rw.WriteHeader(http.StatusInternalServerError) case len(response.ValidationErrors) > 0: rw.WriteHeader(http.StatusBadRequest) diff --git a/backend/presentation/http/api/auth/resetpassword.go b/backend/presentation/http/api/auth/resetpassword.go index 8ed4dcc1..a78b33c4 100644 --- a/backend/presentation/http/api/auth/resetpassword.go +++ b/backend/presentation/http/api/auth/resetpassword.go @@ -3,6 +3,7 @@ package auth import ( "encoding/json" "errors" + "log" "net/http" "github.com/khanzadimahdi/testproject/application/auth/resetpassword" @@ -32,6 +33,7 @@ func (h *resetPasswordHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request case errors.Is(err, domain.ErrNotExists): rw.WriteHeader(http.StatusNotFound) case err != nil: + log.Println(err) rw.WriteHeader(http.StatusInternalServerError) case len(response.ValidationErrors) > 0: rw.WriteHeader(http.StatusBadRequest) diff --git a/backend/presentation/http/api/auth/verify.go b/backend/presentation/http/api/auth/verify.go index 46c747e2..4bd55f17 100644 --- a/backend/presentation/http/api/auth/verify.go +++ b/backend/presentation/http/api/auth/verify.go @@ -3,6 +3,7 @@ package auth import ( "encoding/json" "errors" + "log" "net/http" "github.com/khanzadimahdi/testproject/application/auth/verify" @@ -32,6 +33,7 @@ func (h *verifyHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { case errors.Is(err, domain.ErrNotExists): rw.WriteHeader(http.StatusNotFound) case err != nil: + log.Println(err) rw.WriteHeader(http.StatusInternalServerError) case len(response.ValidationErrors) > 0: rw.WriteHeader(http.StatusBadRequest) diff --git a/backend/resources/view/layout/base.layout.tmpl b/backend/resources/view/layout/base.layout.tmpl new file mode 100644 index 00000000..1712cd0a --- /dev/null +++ b/backend/resources/view/layout/base.layout.tmpl @@ -0,0 +1,13 @@ + + + + + + + {{block "head" .}}{{end}} + + + + {{block "body" .}}{{end}} + + diff --git a/backend/resources/view/mail/auth/register.tmpl b/backend/resources/view/mail/auth/register.tmpl new file mode 100644 index 00000000..5d51a625 --- /dev/null +++ b/backend/resources/view/mail/auth/register.tmpl @@ -0,0 +1,34 @@ +{{template "base.layout.tmpl" .}} + +{{define "head"}} + ثبت نام | طرح‌چه + + +{{end}} + +{{define "body"}} +
+
+
+

ثبت نام در طرح‌چه

+
+
+

دوست عزیز برای تکمیل ثبت نام لطفا بر روی دکمه زیر کلیک کنید

+ تکمیل ثبت نام +

اگر دلیل ارسال شدن این ایمیل را نمیدانید, نیاز نیست اقدام خاصی انجام دهید

+
+
+

© طرح‌چه

+
+
+
+{{end}} diff --git a/backend/resources/view/mail/auth/reset-password.tmpl b/backend/resources/view/mail/auth/reset-password.tmpl new file mode 100644 index 00000000..d5570e0f --- /dev/null +++ b/backend/resources/view/mail/auth/reset-password.tmpl @@ -0,0 +1,34 @@ +{{template "base.layout.tmpl" .}} + +{{define "head"}} + بازیابی کلمه عبور | طرح‌چه + + +{{end}} + +{{define "body"}} +
+
+
+

بازیابی کلمه عبور

+
+
+

برای بازیابی کلمه عبور بر روی دکمه زیر کلیک کنید

+ تکمیل ثبت نام +

اگر دلیل ارسال شدن این ایمیل را نمیدانید, نیاز نیست اقدام خاصی انجام دهید

+
+
+

© طرح‌چه

+
+
+
+{{end}} \ No newline at end of file