forked from baron/baron-sso
feat(orgchart): Introduce standalone orgchart RP and shared link public API
This commit includes: - Added SharedLink data model and Keto-bypassed public API for orgchart view - Configured 'orgfront' as a new OAuth2 client in hydra - Applied MH Dashboard premium beige theme to OrgChart - Implemented user lookup fallback to company code
This commit is contained in:
@@ -20,9 +20,10 @@ type TenantHandler struct {
|
||||
Keto service.KetoService
|
||||
KetoOutbox repository.KetoOutboxRepository
|
||||
KratosAdmin service.KratosAdminService
|
||||
SharedLink service.SharedLinkService
|
||||
}
|
||||
|
||||
func NewTenantHandler(db *gorm.DB, svc service.TenantService, userRepo repository.UserRepository, keto service.KetoService, outbox repository.KetoOutboxRepository, kratos service.KratosAdminService) *TenantHandler {
|
||||
func NewTenantHandler(db *gorm.DB, svc service.TenantService, userRepo repository.UserRepository, keto service.KetoService, outbox repository.KetoOutboxRepository, kratos service.KratosAdminService, sharedLink service.SharedLinkService) *TenantHandler {
|
||||
return &TenantHandler{
|
||||
DB: db,
|
||||
Service: svc,
|
||||
@@ -30,6 +31,7 @@ func NewTenantHandler(db *gorm.DB, svc service.TenantService, userRepo repositor
|
||||
Keto: keto,
|
||||
KetoOutbox: outbox,
|
||||
KratosAdmin: kratos,
|
||||
SharedLink: sharedLink,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -865,3 +867,136 @@ func normalizeTenantType(value string) string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (h *TenantHandler) CreateShareLink(c *fiber.Ctx) error {
|
||||
tenantID := c.Params("id")
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
ExpiresAt *time.Time `json:"expiresAt"`
|
||||
}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errorJSON(c, fiber.StatusBadRequest, "invalid request body")
|
||||
}
|
||||
|
||||
link, err := h.SharedLink.CreateLink(c.Context(), tenantID, req.Name, req.Description, req.ExpiresAt)
|
||||
if err != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(link)
|
||||
}
|
||||
|
||||
func (h *TenantHandler) ListShareLinks(c *fiber.Ctx) error {
|
||||
tenantID := c.Params("id")
|
||||
links, err := h.SharedLink.GetLinksByTenant(c.Context(), tenantID)
|
||||
if err != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return c.JSON(links)
|
||||
}
|
||||
|
||||
func (h *TenantHandler) DeleteShareLink(c *fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
if err := h.SharedLink.DeactivateLink(c.Context(), id); err != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return c.JSON(fiber.Map{"message": "Share link deleted successfully"})
|
||||
}
|
||||
|
||||
func (h *TenantHandler) GetPublicOrgChart(c *fiber.Ctx) error {
|
||||
token := c.Query("token")
|
||||
if token == "" {
|
||||
return errorJSON(c, fiber.StatusUnauthorized, "share token is required")
|
||||
}
|
||||
|
||||
link, err := h.SharedLink.ValidateToken(c.Context(), token)
|
||||
if err != nil {
|
||||
return errorJSON(c, fiber.StatusUnauthorized, err.Error())
|
||||
}
|
||||
|
||||
allTenants, _, err := h.Service.ListTenants(c.Context(), 10000, 0, "")
|
||||
if err != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
parentMap := make(map[string]string)
|
||||
for _, t := range allTenants {
|
||||
if t.ParentID != nil {
|
||||
parentMap[t.ID] = *t.ParentID
|
||||
}
|
||||
}
|
||||
|
||||
findRoot := func(id string) string {
|
||||
curr := id
|
||||
for {
|
||||
p, exists := parentMap[curr]
|
||||
if !exists || p == "" { break }
|
||||
curr = p
|
||||
}
|
||||
return curr
|
||||
}
|
||||
|
||||
sharedRootID := findRoot(link.TenantID)
|
||||
var filteredTenants []domain.Tenant
|
||||
var tenantIDs []string
|
||||
var slugs []string
|
||||
|
||||
for _, t := range allTenants {
|
||||
if findRoot(t.ID) == sharedRootID {
|
||||
filteredTenants = append(filteredTenants, t)
|
||||
tenantIDs = append(tenantIDs, t.ID)
|
||||
slugs = append(slugs, t.Slug)
|
||||
}
|
||||
}
|
||||
|
||||
type publicUserSummary struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Position string `json:"position"`
|
||||
JobTitle string `json:"jobTitle"`
|
||||
CompanyCode string `json:"companyCode"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
var publicUsers []publicUserSummary
|
||||
seen := make(map[string]bool)
|
||||
|
||||
// Fetch users by IDs
|
||||
var usersByID []domain.User
|
||||
h.DB.Where("tenant_id IN ?", tenantIDs).Preload("Tenant").Find(&usersByID)
|
||||
for _, u := range usersByID {
|
||||
if u.Status != "active" || seen[u.ID] { continue }
|
||||
seen[u.ID] = true
|
||||
cc := u.CompanyCode
|
||||
if cc == "" && u.Tenant != nil { cc = u.Tenant.Slug }
|
||||
publicUsers = append(publicUsers, publicUserSummary{
|
||||
ID: u.ID, Name: u.Name, Position: u.Position, JobTitle: u.JobTitle, CompanyCode: cc, Status: u.Status,
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch users by Slugs
|
||||
var usersBySlug []domain.User
|
||||
h.DB.Where("company_code IN ?", slugs).Preload("Tenant").Find(&usersBySlug)
|
||||
for _, u := range usersBySlug {
|
||||
if u.Status != "active" || seen[u.ID] { continue }
|
||||
seen[u.ID] = true
|
||||
cc := u.CompanyCode
|
||||
if cc == "" && u.Tenant != nil { cc = u.Tenant.Slug }
|
||||
publicUsers = append(publicUsers, publicUserSummary{
|
||||
ID: u.ID, Name: u.Name, Position: u.Position, JobTitle: u.JobTitle, CompanyCode: cc, Status: u.Status,
|
||||
})
|
||||
}
|
||||
|
||||
tenantSummaries := make([]tenantSummary, 0, len(filteredTenants))
|
||||
for _, t := range filteredTenants {
|
||||
tenantSummaries = append(tenantSummaries, mapTenantSummary(t))
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"tenants": tenantSummaries,
|
||||
"users": publicUsers,
|
||||
"sharedWith": link.Name,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user