auto mail generation
This commit is contained in:
@@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"nearle/middleware"
|
||||
"nearle/models"
|
||||
"nearle/services"
|
||||
"net/http"
|
||||
@@ -346,7 +347,8 @@ func (ctl *TenantController) CreateStaff(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
if err := ctl.tenantService.CreateStaff(data); err != nil {
|
||||
invite, err := ctl.tenantService.CreateStaff(data)
|
||||
if err != nil {
|
||||
// A rejected PIN, a missing name, a role nobody set — these are things
|
||||
// the person filling in the form can fix, so they come back as 400 with
|
||||
// the reason. This answered 500 with a body claiming 409, which told a
|
||||
@@ -358,10 +360,17 @@ func (ctl *TenantController) CreateStaff(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// The person was hired either way. Whether they were emailed their
|
||||
// first-password link is reported beside that rather than folded into
|
||||
// `status`: this account is created with no password and the link is the only
|
||||
// way in, so an operator who is not told cannot know they have added somebody
|
||||
// who cannot sign in.
|
||||
return c.JSON(fiber.Map{
|
||||
"code": http.StatusCreated,
|
||||
"message": "Staff created successfully",
|
||||
"status": true,
|
||||
"code": http.StatusCreated,
|
||||
"message": "Staff created successfully",
|
||||
"status": true,
|
||||
"invited": invite.Sent,
|
||||
"invitereason": invite.Reason,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -436,7 +445,7 @@ func (ctl *TenantController) CreateTenantUser(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
result, err := ctl.tenantService.CreateTenantUser(data)
|
||||
result, invite, err := ctl.tenantService.CreateTenantUser(data)
|
||||
if err != nil {
|
||||
if err.Error() == "Tenant Already Exists" {
|
||||
return c.Status(http.StatusConflict).JSON(fiber.Map{
|
||||
@@ -453,11 +462,21 @@ func (ctl *TenantController) CreateTenantUser(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// The tenant was created either way. The invitation is reported beside it
|
||||
// rather than folded into `status`, because a merchant who exists and has
|
||||
// not been emailed is a task for the operator — resend, or correct the
|
||||
// address — and not a failed onboarding to be retried.
|
||||
//
|
||||
// `invited: false` with a reason is the state the platform console shows on
|
||||
// the tenant, so it never has to guess whether the email went.
|
||||
return c.Status(http.StatusCreated).JSON(fiber.Map{
|
||||
"code": 201,
|
||||
"status": true,
|
||||
"message": "Successfully Created",
|
||||
"details": result,
|
||||
"invited": invite.Sent,
|
||||
// Omitted when it sent, so a successful onboarding carries no apology.
|
||||
"invitereason": invite.Reason,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -776,3 +795,79 @@ func (ctl *TenantController) AssignPartner(c *fiber.Ctx) error {
|
||||
"code": http.StatusOK, "status": true, "message": "Successfully Updated",
|
||||
})
|
||||
}
|
||||
|
||||
// ResendInvite re-issues a merchant's first-password link.
|
||||
//
|
||||
// ── Why this is platform staff only ─────────────────────────────────────────
|
||||
//
|
||||
// It mints a credential. `middleware.WebAuth` already pins a merchant's session
|
||||
// to their own tenant, so a shop could at most re-invite itself — but the
|
||||
// account it would be inviting is the one signing in to ask, which can only
|
||||
// happen if that account already has a password, and the service refuses that
|
||||
// case outright.
|
||||
//
|
||||
// So the only caller this is for is Nearle's own staff, chasing a merchant who
|
||||
// never received the mail. Saying so explicitly is better than relying on two
|
||||
// other checks to make the wrong case impossible.
|
||||
func (ctl *TenantController) ResendInvite(c *fiber.Ctx) error {
|
||||
claims, ok := middleware.WebClaimsFrom(c)
|
||||
if !ok || !claims.IsPlatformAccount() {
|
||||
return c.Status(http.StatusForbidden).JSON(fiber.Map{
|
||||
"code": http.StatusForbidden, "status": false,
|
||||
"message": "Only Nearle staff can resend an invitation.",
|
||||
})
|
||||
}
|
||||
|
||||
// Either a tenant — meaning its owner, the one account onboarding created —
|
||||
// or one named person. Staff added later and the login every branch spawns
|
||||
// are created with no password too, and a business has many of them, so
|
||||
// "the tenant's invitation" cannot reach them.
|
||||
var req struct {
|
||||
Tenantid int `json:"tenantid"`
|
||||
Userid int `json:"userid"`
|
||||
}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
||||
"code": http.StatusBadRequest, "status": false, "message": "Invalid request body",
|
||||
})
|
||||
}
|
||||
if req.Tenantid <= 0 && req.Userid <= 0 {
|
||||
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
||||
"code": http.StatusBadRequest, "status": false,
|
||||
"message": "Send a tenantid to re-invite the owner, or a userid to re-invite one person.",
|
||||
})
|
||||
}
|
||||
|
||||
// `userid` wins when both arrive. It is the more specific of the two, and a
|
||||
// caller that sent a person's id meant that person — silently emailing the
|
||||
// owner instead would be the wrong mailbox with no sign anything was off.
|
||||
var (
|
||||
outcome services.InviteOutcome
|
||||
err error
|
||||
)
|
||||
if req.Userid > 0 {
|
||||
outcome, err = ctl.tenantService.ResendInviteToUser(req.Userid)
|
||||
} else {
|
||||
outcome, err = ctl.tenantService.ResendInvite(req.Tenantid)
|
||||
}
|
||||
if err != nil {
|
||||
// 409, not 500. Every failure here is a business fact the operator can
|
||||
// act on — no such tenant, an address that matches no login, a merchant
|
||||
// already set up — rather than a fault in the server.
|
||||
return c.Status(http.StatusConflict).JSON(fiber.Map{
|
||||
"code": http.StatusConflict, "status": false, "message": err.Error(),
|
||||
})
|
||||
}
|
||||
if !outcome.Sent {
|
||||
// The tenant is fine and the mail did not go. Reported as a failure
|
||||
// because the operator pressed a button expecting an email to leave,
|
||||
// and the reason names what to fix.
|
||||
return c.Status(http.StatusConflict).JSON(fiber.Map{
|
||||
"code": http.StatusConflict, "status": false, "message": outcome.Reason,
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"code": http.StatusOK, "status": true, "message": "Invitation sent.",
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user