Skip to content

Commit 3015683

Browse files
committed
feat(email): add new email service protocol
Signed-off-by: Chayan Das <[email protected]>
1 parent e15319a commit 3015683

File tree

13 files changed

+755
-14
lines changed

13 files changed

+755
-14
lines changed

cmd/laas/main.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-FileCopyrightText: 2023 Siemens AG
33
// SPDX-FileContributor: Gaurav Mishra <[email protected]>
44
// SPDX-FileContributor: Dearsh Oberoi <[email protected]>
5+
// SPDX-FileContributor: 2025 Chayan Das <[email protected]>
56
//
67
// SPDX-License-Identifier: GPL-2.0-only
78

@@ -12,16 +13,21 @@ import (
1213
"flag"
1314
"log"
1415
"os"
16+
"strconv"
1517

1618
"github.com/joho/godotenv"
1719
"github.com/lestrrat-go/httprc/v3"
1820
"github.com/lestrrat-go/jwx/v3/jwk"
21+
"go.uber.org/zap"
1922

2023
_ "github.com/dave/jennifer/jen"
2124
_ "github.com/fossology/LicenseDb/cmd/laas/docs"
2225
"github.com/fossology/LicenseDb/pkg/api"
2326
"github.com/fossology/LicenseDb/pkg/auth"
2427
"github.com/fossology/LicenseDb/pkg/db"
28+
"github.com/fossology/LicenseDb/pkg/email"
29+
logger "github.com/fossology/LicenseDb/pkg/log"
30+
2531
"github.com/fossology/LicenseDb/pkg/utils"
2632
"github.com/fossology/LicenseDb/pkg/validations"
2733
)
@@ -39,21 +45,28 @@ func main() {
3945
if err != nil {
4046
log.Fatalf("Error loading .env file")
4147
}
42-
4348
flag.Parse()
4449

50+
// Start the email service
51+
EnableSMTP, _ := strconv.ParseBool(os.Getenv("ENABLE_SMTP"))
52+
if EnableSMTP {
53+
if err := email.Init(); err != nil {
54+
logger.LogFatal("Failed to initialize email service", zap.Error(err))
55+
}
56+
}
57+
4558
if os.Getenv("TOKEN_HOUR_LIFESPAN") == "" || os.Getenv("API_SECRET") == "" || os.Getenv("DEFAULT_ISSUER") == "" {
46-
log.Fatal("Mandatory environment variables not configured")
59+
logger.LogFatal("Mandatory environment variables not configured")
4760
}
4861

4962
if os.Getenv("JWKS_URI") != "" {
5063
cache, err := jwk.NewCache(context.Background(), httprc.NewClient())
5164
if err != nil {
52-
log.Fatalf("Failed to create a jwk.Cache from the oidc provider's URL: %s", err)
65+
logger.LogFatal("Failed to create a jwk.Cache from the oidc provider's URL:", zap.Error(err))
5366
}
5467

5568
if err := cache.Register(context.Background(), os.Getenv("JWKS_URI")); err != nil {
56-
log.Fatalf("Failed to create a jwk.Cache from the oidc provider's URL: %s", err)
69+
logger.LogFatal("Failed to create a jwk.Cache from the oidc provider's URL:", zap.Error(err))
5770
}
5871

5972
auth.Jwks = cache
@@ -76,8 +89,7 @@ func main() {
7689
}
7790

7891
r := api.Router()
79-
8092
if err := r.Run(); err != nil {
81-
log.Fatalf("Error while running the server: %v", err)
93+
logger.LogFatal("Error while running the server:", zap.Error(err))
8294
}
8395
}

configs/.env.dev.example

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# SPDX-License-Identifier: GPL-2.0-only
22
# SPDX-FileCopyrightText: FOSSology contributors
3+
#SPDX-FileContributor: 2025 Chayan Das <[email protected]>
34

45
# How long the token can be valid
56
TOKEN_HOUR_LIFESPAN=24
@@ -49,4 +50,15 @@ DB_HOST=localhost
4950
# This value can be adjusted based on the requirements of the similarity search
5051
# A lower value will result in more matches, while a higher value will be more strict
5152
# Default is set to 0.7, but can be changed to a higher value like 0.8 or 0.9 for stricter matching
52-
SIMILARITY_THRESHOLD = 0.8
53+
SIMILARITY_THRESHOLD = 0.8
54+
55+
56+
# SMTP Configuration
57+
ENABLE_SMTP=false
58+
SMTP_HOST=smtp.gmail.com
59+
SMTP_PORT=587
60+
61+
SMTP_PASSWORD=your_password
62+
63+
64+

configs/.env.test.example

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,17 @@ DB_HOST=localhost
4545
# This value can be adjusted based on the requirements of the similarity search
4646
# A lower value will result in more matches, while a higher value will be more strict
4747
# Default is set to 0.7, but can be changed to a higher value like 0.8 or 0.9 for stricter matching
48-
SIMILARITY_THRESHOLD = 0.8
48+
SIMILARITY_THRESHOLD = 0.8
49+
50+
51+
52+
53+
# SMTP Configuration
54+
ENABLE_SMTP=false
55+
SMTP_HOST=smtp.gmail.com
56+
SMTP_PORT=587
57+
58+
SMTP_PASSWORD=your_password
59+
60+
61+

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ require (
3636
github.com/lib/pq v1.10.9 // indirect
3737
github.com/segmentio/asm v1.2.0 // indirect
3838
go.uber.org/atomic v1.7.0 // indirect
39+
go.uber.org/multierr v1.10.0 // indirect
40+
go.uber.org/zap v1.27.0 // indirect
3941
golang.org/x/sync v0.12.0 // indirect
4042
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
4143
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
153153
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
154154
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
155155
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
156+
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
157+
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
158+
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
159+
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
156160
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
157161
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
158162
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

pkg/api/licenses.go

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
package api
1010

1111
import (
12+
"context"
1213
"encoding/json"
1314
"errors"
1415
"fmt"
@@ -20,12 +21,14 @@ import (
2021
"time"
2122

2223
"github.com/fossology/LicenseDb/pkg/db"
24+
email "github.com/fossology/LicenseDb/pkg/email"
25+
template "github.com/fossology/LicenseDb/pkg/email/templetes"
26+
logger "github.com/fossology/LicenseDb/pkg/log"
2327
"github.com/fossology/LicenseDb/pkg/models"
2428
"github.com/fossology/LicenseDb/pkg/utils"
2529
"github.com/fossology/LicenseDb/pkg/validations"
2630
"github.com/gin-gonic/gin"
2731
"github.com/go-playground/validator/v10"
28-
2932
"gorm.io/gorm"
3033
)
3134

@@ -308,7 +311,15 @@ func CreateLicense(c *gin.Context) {
308311
c.JSON(http.StatusInternalServerError, er)
309312
return err
310313
}
311-
314+
// send email
315+
if email.Email != nil && email.Email.IsRunning() {
316+
userName := *lic.User.UserName
317+
userEmail := *lic.User.UserEmail
318+
shortName := *lic.Shortname
319+
email.NotifyLicenseCreated(userEmail, userName, shortName)
320+
} else {
321+
logger.LogInfo("SMTP disabled or not reachable. Skipping email.")
322+
}
312323
res := models.LicenseResponse{
313324
Data: []models.LicenseResponseDTO{lic.ConvertToLicenseResponseDTO()},
314325
Status: http.StatusCreated,
@@ -321,6 +332,7 @@ func CreateLicense(c *gin.Context) {
321332

322333
return nil
323334
})
335+
324336
}
325337

326338
// UpdateLicense Update license with given shortname and create audit and changelog entries.
@@ -451,6 +463,14 @@ func UpdateLicense(c *gin.Context) {
451463
c.JSON(http.StatusInternalServerError, er)
452464
return err
453465
}
466+
if email.Email != nil && email.Email.IsRunning() {
467+
userName := *newLicense.User.UserName
468+
userEmail := *newLicense.User.UserEmail
469+
shortName := *newLicense.Shortname
470+
email.NotifyLicenseUpdated(userEmail, userName, shortName)
471+
} else {
472+
logger.LogInfo("SMTP disabled or not reachable. Skipping email.")
473+
}
454474

455475
res := models.LicenseResponse{
456476
Data: []models.LicenseResponseDTO{newLicense.ConvertToLicenseResponseDTO()},
@@ -573,6 +593,7 @@ func SearchInLicense(c *gin.Context) {
573593
// @Router /licenses/import [post]
574594
func ImportLicenses(c *gin.Context) {
575595
userId := c.MustGet("userId").(int64)
596+
var user models.User
576597
file, header, err := c.Request.FormFile("file")
577598
if err != nil {
578599
er := models.LicenseError{
@@ -641,8 +662,21 @@ func ImportLicenses(c *gin.Context) {
641662
res := models.ImportLicensesResponse{
642663
Status: http.StatusOK,
643664
}
644-
665+
var total, success, failed int
666+
err = db.DB.Where(models.User{Id: userId}).First(&user).Error
667+
if err != nil {
668+
er := models.LicenseError{
669+
Status: http.StatusNotFound,
670+
Message: fmt.Sprintf("no with userId '%d' exists", userId),
671+
Error: err.Error(),
672+
Path: c.Request.URL.Path,
673+
Timestamp: time.Now().Format(time.RFC3339),
674+
}
675+
c.JSON(http.StatusNotFound, er)
676+
return
677+
}
645678
for i := range licenses {
679+
total++
646680
lic, err := licenses[i].ConvertToLicenseDB()
647681
if err != nil {
648682
res.Data = append(res.Data, models.LicenseError{
@@ -683,6 +717,37 @@ func ImportLicenses(c *gin.Context) {
683717
})
684718
// error is not returned here as it will rollback the transaction
685719
}
720+
if importStatus == utils.IMPORT_LICENSE_CREATED ||
721+
importStatus == utils.IMPORT_LICENSE_UPDATED ||
722+
importStatus == utils.IMPORT_LICENSE_UPDATED_EXCEPT_TEXT {
723+
success++
724+
} else {
725+
failed++
726+
}
727+
}
728+
if email.Email != nil && email.Email.IsRunning() {
729+
userName := *user.UserName
730+
userEmail := *user.UserEmail
731+
subject, html := template.ImportSummaryEmailTemplate(
732+
userName,
733+
"Licenses",
734+
total,
735+
success,
736+
failed,
737+
time.Now(),
738+
)
739+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
740+
defer cancel()
741+
742+
if err := email.Email.Queue(ctx, email.EmailData{
743+
To: []string{userEmail},
744+
Subject: subject,
745+
HTML: html,
746+
}); err != nil {
747+
logger.LogError("Failed to enqueue email")
748+
}
749+
} else {
750+
logger.LogInfo("SMTP disabled or not reachable. Skipping email.")
686751
}
687752

688753
c.JSON(http.StatusOK, res)

pkg/email/admin.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-FileCopyrightText: 2025 Chayan Das <[email protected]>
2+
// SPDX-License-Identifier: GPL-2.0-only
3+
4+
package email
5+
6+
import (
7+
"github.com/fossology/LicenseDb/pkg/db"
8+
"github.com/fossology/LicenseDb/pkg/models"
9+
)
10+
11+
func FetchAdminEmails() ([]string, error) {
12+
var emails []string
13+
admin := "ADMIN"
14+
err := db.DB.
15+
Model(&models.User{}).
16+
Where(&models.User{UserLevel: &admin}). // can add super_admin too
17+
Pluck("user_email", &emails).Error
18+
if err != nil {
19+
return nil, err
20+
}
21+
22+
return emails, nil
23+
}

0 commit comments

Comments
 (0)