Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/internal/model/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var (

type Alias struct {
BaseModel
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
Name string `gorm:"unique" json:"name"`
UserID string `json:"-"`
Enabled bool `json:"enabled"`
Expand Down
29 changes: 25 additions & 4 deletions api/internal/repository/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (d *Database) GetAlias(ctx context.Context, ID string, userID string) (mode
return alias, nil
}

func (d *Database) GetAliases(ctx context.Context, userID string, limit int, offset int, sortBy string, sortOrder string, catchAll string, search string) ([]model.Alias, error) {
func (d *Database) GetAliases(ctx context.Context, userID string, limit int, offset int, sortBy string, sortOrder string, catchAll string, search string, status string) ([]model.Alias, error) {
sortBy = "a." + sortBy

if catchAll == "true" {
Expand All @@ -48,6 +48,15 @@ func (d *Database) GetAliases(ctx context.Context, userID string, limit int, off
search = "AND (a.name LIKE '%" + search + "%' OR a.description LIKE '%" + search + "%')"
}

var statusFilter string
if status == "deleted" {
statusFilter = "AND a.deleted_at IS NOT NULL"
} else if status == "all" {
statusFilter = ""
} else {
statusFilter = "AND a.deleted_at IS NULL"
}

aliases := []model.Alias{}
query := `
SELECT a.*,
Expand All @@ -58,7 +67,7 @@ func (d *Database) GetAliases(ctx context.Context, userID string, limit int, off
FROM aliases a
LEFT JOIN messages m
ON a.id = m.alias_id
WHERE a.user_id = ? AND a.deleted_at IS NULL ` + catchAll + " " + search + `
WHERE a.user_id = ? ` + statusFilter + " " + catchAll + " " + search + `
GROUP BY a.id
ORDER BY ` + sortBy + " " + sortOrder

Expand Down Expand Up @@ -106,7 +115,7 @@ func (d *Database) GetAllAliases(ctx context.Context, userID string) ([]model.Al
return aliases, err
}

func (d *Database) GetAliasCount(ctx context.Context, userID string, catchAll string, search string) (int, error) {
func (d *Database) GetAliasCount(ctx context.Context, userID string, catchAll string, search string, status string) (int, error) {
if catchAll == "true" {
catchAll = " AND catch_all = true"
} else if catchAll == "false" {
Expand All @@ -120,7 +129,15 @@ func (d *Database) GetAliasCount(ctx context.Context, userID string, catchAll st
}

var count int64
err := d.Client.Model(&model.Alias{}).Where("user_id = ?"+catchAll+search, userID).Count(&count).Error
q := d.Client.Model(&model.Alias{})
if status == "deleted" {
q = q.Unscoped().Where("user_id = ? AND deleted_at IS NOT NULL"+catchAll+search, userID)
} else if status == "all" {
q = q.Unscoped().Where("user_id = ?"+catchAll+search, userID)
} else {
q = q.Where("user_id = ?"+catchAll+search, userID)
}
err := q.Count(&count).Error
return int(count), err
}

Expand Down Expand Up @@ -160,3 +177,7 @@ func (d *Database) DeleteAliasByUserID(ctx context.Context, userID string) error
func (d *Database) DeleteAliasByDomain(ctx context.Context, domain string, userID string) error {
return d.Client.Where("name LIKE ? AND user_id = ?", "%@"+domain, userID).Delete(&model.Alias{}).Error
}

func (d *Database) RestoreAlias(ctx context.Context, ID string, userID string) error {
return d.Client.Model(&model.Alias{}).Unscoped().Where("id = ? AND user_id = ?", ID, userID).Update("deleted_at", nil).Error
}
23 changes: 17 additions & 6 deletions api/internal/service/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,18 @@ var (

type AliasStore interface {
GetAlias(context.Context, string, string) (model.Alias, error)
GetAliases(context.Context, string, int, int, string, string, string, string) ([]model.Alias, error)
GetAliases(context.Context, string, int, int, string, string, string, string, string) ([]model.Alias, error)
GetAliasesByDomain(context.Context, string, string) ([]model.Alias, error)
GetAllAliases(context.Context, string) ([]model.Alias, error)
GetAliasCount(context.Context, string, string, string) (int, error)
GetAliasCount(context.Context, string, string, string, string) (int, error)
GetAliasDailyCount(context.Context, string) (int, error)
GetAliasByName(string) (model.Alias, error)
PostAlias(context.Context, model.Alias) (model.Alias, error)
UpdateAlias(context.Context, model.Alias) error
DeleteAlias(context.Context, string, string) error
DeleteAliasByUserID(context.Context, string) error
DeleteAliasByDomain(context.Context, string, string) error
RestoreAlias(context.Context, string, string) error
}

// aliasDomainPart returns the domain portion of an alias name (e.g. "user@example.com" → "example.com").
Expand Down Expand Up @@ -83,19 +84,19 @@ func (s *Service) GetAlias(ctx context.Context, ID string, userID string) (model
return alias, nil
}

func (s *Service) GetAliases(ctx context.Context, userID string, limit int, page int, sortBy string, sortOrder string, catchAll string, search string) (model.AliasList, error) {
func (s *Service) GetAliases(ctx context.Context, userID string, limit int, page int, sortBy string, sortOrder string, catchAll string, search string, status string) (model.AliasList, error) {
offset := (page - 1) * limit
if page < 1 {
offset = 0
}

aliases, err := s.Store.GetAliases(ctx, userID, limit, offset, sortBy, sortOrder, catchAll, search)
aliases, err := s.Store.GetAliases(ctx, userID, limit, offset, sortBy, sortOrder, catchAll, search, status)
if err != nil {
log.Printf("error fetching aliases: %s", err.Error())
return model.AliasList{}, ErrGetAliases
}

total, err := s.Store.GetAliasCount(ctx, userID, catchAll, search)
total, err := s.Store.GetAliasCount(ctx, userID, catchAll, search, status)
if err != nil {
log.Printf("error fetching alias count: %s", err.Error())
return model.AliasList{}, ErrGetAliases
Expand Down Expand Up @@ -180,7 +181,7 @@ func (s *Service) PostAlias(ctx context.Context, alias model.Alias, format strin

// Catch-all alias
if format == model.AliasFormatCatchAll {
userAliases, err := s.Store.GetAliases(ctx, alias.UserID, 0, 0, "created_at", "DESC", "true", "")
userAliases, err := s.Store.GetAliases(ctx, alias.UserID, 0, 0, "created_at", "DESC", "true", "", "active")
if err != nil {
log.Printf("error fetching user aliases: %s", err.Error())
return model.Alias{}, ErrPostAlias
Expand Down Expand Up @@ -276,3 +277,13 @@ func (s *Service) FindAlias(email string) (model.Alias, error) {

return alias, nil
}

func (s *Service) RestoreAlias(ctx context.Context, ID string, userID string) error {
err := s.Store.RestoreAlias(ctx, ID, userID)
if err != nil {
log.Printf("error restoring alias: %s", err.Error())
return ErrGetAlias
}

return nil
}
2 changes: 1 addition & 1 deletion api/internal/service/recipient.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ func (s *Service) DeleteRecipient(ctx context.Context, ID string, userID string,
}

// Get aliases
aliases, err := s.Store.GetAliases(ctx, userID, 0, 0, "created_at", "DESC", "", "")
aliases, err := s.Store.GetAliases(ctx, userID, 0, 0, "created_at", "DESC", "", "", "active")
if err != nil {
log.Printf("error deleting recipient, GetAliases: %s", err.Error())
return ErrDeleteRecipient
Expand Down
56 changes: 49 additions & 7 deletions api/internal/transport/api/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,22 @@ import (
)

var (
PostAliasSuccess = "Alias created successfully."
UpdateAliasSuccess = "Alias updated successfully."
DeleteAliasSuccess = "Alias deleted successfully."
ErrInvalidDomain = "Selected domain is invalid."
ErrUnverifiedRcp = "The recipient address has not been verified."
PostAliasSuccess = "Alias created successfully."
UpdateAliasSuccess = "Alias updated successfully."
DeleteAliasSuccess = "Alias deleted successfully."
ErrInvalidDomain = "Selected domain is invalid."
ErrUnverifiedRcp = "The recipient address has not been verified."
RestoreAliasSuccess = "Alias restored successfully."
)

type AliasService interface {
GetAlias(context.Context, string, string) (model.Alias, error)
GetAliases(context.Context, string, int, int, string, string, string, string) (model.AliasList, error)
GetAliases(context.Context, string, int, int, string, string, string, string, string) (model.AliasList, error)
GetAllAliases(context.Context, string) ([]model.Alias, error)
PostAlias(context.Context, model.Alias, string, string, string) (model.Alias, error)
UpdateAlias(context.Context, model.Alias) error
DeleteAlias(context.Context, string, string) error
RestoreAlias(context.Context, string, string) error
}

// @Summary Get alias
Expand Down Expand Up @@ -57,6 +59,7 @@ func (h *Handler) GetAlias(c *fiber.Ctx) error {
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param status query string false "Filter by alias status" Enums(active, deleted, all)
// @Success 200 {object} model.AliasList
// @Failure 400 {object} ErrorRes
// @Router /aliases [get]
Expand All @@ -78,6 +81,7 @@ func (h *Handler) GetAliases(c *fiber.Ctx) error {
sortOrder := strings.ToUpper(c.Query("sort_order"))
catchAll := c.Query("catch_all")
search := c.Query("search")
status := c.Query("status")

var allowSortBy = map[string]bool{
"created_at": true,
Expand All @@ -93,6 +97,12 @@ func (h *Handler) GetAliases(c *fiber.Ctx) error {
"false": true,
"": true,
}
var allowStatus = map[string]bool{
"active": true,
"deleted": true,
"all": true,
"": true,
}

if _, ok := allowSortBy[sortBy]; !ok {
sortBy = "created_at"
Expand All @@ -103,6 +113,12 @@ func (h *Handler) GetAliases(c *fiber.Ctx) error {
if _, ok := allowCatchAll[catchAll]; !ok {
catchAll = ""
}
if _, ok := allowStatus[status]; !ok {
status = ""
}
if status == "" {
status = "active"
}

err = h.Validator.Var(search, "omitempty,required,search")
if err != nil {
Expand All @@ -112,7 +128,7 @@ func (h *Handler) GetAliases(c *fiber.Ctx) error {
})
}

list, err := h.Service.GetAliases(c.Context(), userID, limit, page, sortBy, sortOrder, catchAll, search)
list, err := h.Service.GetAliases(c.Context(), userID, limit, page, sortBy, sortOrder, catchAll, search, status)
if err != nil {
return c.Status(400).JSON(fiber.Map{
"error": err.Error(),
Expand Down Expand Up @@ -331,3 +347,29 @@ func (h *Handler) DeleteAlias(c *fiber.Ctx) error {
"message": DeleteAliasSuccess,
})
}

// @Summary Restore alias
// @Description Restore alias
// @Tags alias
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param id path string true "Alias ID"
// @Success 200 {object} SuccessRes
// @Failure 400 {object} ErrorRes
// @Router /alias/restore/{id} [post]
// @Router /api/alias/restore/{id} [post]
func (h *Handler) RestoreAlias(c *fiber.Ctx) error {
userID := auth.GetUserID(c)
id := c.Params("id")
err := h.Service.RestoreAlias(c.Context(), id, userID)
if err != nil {
return c.Status(400).JSON(fiber.Map{
"error": err.Error(),
})
}

return c.Status(200).JSON(fiber.Map{
"message": RestoreAliasSuccess,
})
}
1 change: 1 addition & 0 deletions api/internal/transport/api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ func (h *Handler) SetupRoutes(cfg config.APIConfig) {
v1.Post("/alias", limiter.New(), h.PostAlias)
v1.Put("/alias/:id", h.UpdateAlias)
v1.Delete("/alias/:id", h.DeleteAlias)
v1.Post("/alias/restore/:id", h.RestoreAlias)

v1.Get("/logs", h.GetLogs)
v1.Delete("/logs", h.DeleteLogs)
Expand Down
1 change: 1 addition & 0 deletions app/src/api/alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export const aliasApi = {
create: (data: any) => api.post('/alias', data),
update: (id: string, data: any) => api.put('/alias/' + id, data),
delete: (id: string) => api.delete('/alias/' + id),
restore: (id: string) => api.post('/alias/restore/' + id),
}
Loading
Loading