89faa08b1c
continuous-integration/drone/push Build is passing
- Introduced the ability to append external links to company profiles through a new API endpoint. - Enhanced the `Company` entity to include a `Links` field for storing external resource links. - Created `AddLinksForm` for validating incoming link data and implemented corresponding logic in the service layer. - Added error handling for link validation, ensuring only valid URLs are accepted and limiting the number of links to 20. - Implemented unit tests for link management functions, including sanitization and merging of links. - Updated relevant API documentation to reflect the new functionality. This update significantly enhances the company management capabilities by allowing the addition of external links, improving the overall user experience and data richness in company profiles.
165 lines
3.7 KiB
Go
165 lines
3.7 KiB
Go
package company
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/asaskevich/govalidator"
|
|
)
|
|
|
|
const maxCompanyLinks = 20
|
|
|
|
// AddLinksResponse is returned after appending links to a company.
|
|
type AddLinksResponse struct {
|
|
Links []CompanyLink `json:"links"`
|
|
Added []CompanyLink `json:"added"`
|
|
}
|
|
|
|
// AddLinks appends validated links to a company.
|
|
func (s *companyService) AddLinks(ctx context.Context, companyID string, form *AddLinksForm) (*AddLinksResponse, error) {
|
|
if form == nil || len(form.Links) == 0 {
|
|
return nil, errors.New("no links provided")
|
|
}
|
|
if len(form.Links) > maxCompanyLinks {
|
|
return nil, errors.New("too many links in request")
|
|
}
|
|
|
|
company, err := s.repository.GetByID(ctx, companyID)
|
|
if err != nil {
|
|
return nil, errors.New("company not found")
|
|
}
|
|
|
|
newLinks, err := sanitizeCompanyLinks(convertCompanyLinkForms(form.Links))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(newLinks) == 0 {
|
|
return nil, errors.New("no valid links provided")
|
|
}
|
|
|
|
existing := sanitizeStoredCompanyLinks(company.Links)
|
|
merged, added := mergeCompanyLinks(existing, newLinks)
|
|
if len(merged) > maxCompanyLinks {
|
|
return nil, errors.New("company link limit exceeded")
|
|
}
|
|
|
|
company.Links = merged
|
|
if err := s.repository.Update(ctx, company); err != nil {
|
|
s.logger.Error("Failed to save company links", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"company_id": companyID,
|
|
})
|
|
return nil, errors.New("failed to update company links")
|
|
}
|
|
|
|
s.logger.Info("Company links added", map[string]interface{}{
|
|
"company_id": companyID,
|
|
"added_count": len(added),
|
|
"links_total": len(company.Links),
|
|
})
|
|
|
|
s.triggerAIOnboardingAsync(companyID)
|
|
|
|
return &AddLinksResponse{
|
|
Links: company.Links,
|
|
Added: added,
|
|
}, nil
|
|
}
|
|
|
|
func convertCompanyLinkForms(forms []CompanyLinkForm) []CompanyLink {
|
|
if len(forms) == 0 {
|
|
return nil
|
|
}
|
|
|
|
links := make([]CompanyLink, 0, len(forms))
|
|
for _, form := range forms {
|
|
links = append(links, CompanyLink{
|
|
URL: form.URL,
|
|
Title: form.Title,
|
|
})
|
|
}
|
|
return links
|
|
}
|
|
|
|
func sanitizeCompanyLinks(links []CompanyLink) ([]CompanyLink, error) {
|
|
if len(links) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
sanitized := make([]CompanyLink, 0, len(links))
|
|
for _, link := range links {
|
|
url := strings.TrimSpace(link.URL)
|
|
if url == "" {
|
|
continue
|
|
}
|
|
if !govalidator.IsURL(url) {
|
|
return nil, errors.New("invalid link URL")
|
|
}
|
|
|
|
var title *string
|
|
if link.Title != nil {
|
|
trimmed := strings.TrimSpace(*link.Title)
|
|
if trimmed != "" {
|
|
title = &trimmed
|
|
}
|
|
}
|
|
|
|
sanitized = append(sanitized, CompanyLink{
|
|
URL: url,
|
|
Title: title,
|
|
})
|
|
}
|
|
return sanitized, nil
|
|
}
|
|
|
|
func sanitizeStoredCompanyLinks(links []CompanyLink) []CompanyLink {
|
|
sanitized, err := sanitizeCompanyLinks(links)
|
|
if err != nil || len(sanitized) == 0 {
|
|
return nil
|
|
}
|
|
return sanitized
|
|
}
|
|
|
|
func mergeCompanyLinks(existing, additional []CompanyLink) ([]CompanyLink, []CompanyLink) {
|
|
seen := make(map[string]struct{}, len(existing)+len(additional))
|
|
merged := make([]CompanyLink, 0, len(existing)+len(additional))
|
|
added := make([]CompanyLink, 0, len(additional))
|
|
|
|
appendUnique := func(items []CompanyLink, trackAdded bool) {
|
|
for _, item := range items {
|
|
key := strings.ToLower(strings.TrimSpace(item.URL))
|
|
if key == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[key]; ok {
|
|
continue
|
|
}
|
|
seen[key] = struct{}{}
|
|
merged = append(merged, item)
|
|
if trackAdded {
|
|
added = append(added, item)
|
|
}
|
|
}
|
|
}
|
|
|
|
appendUnique(existing, false)
|
|
appendUnique(additional, true)
|
|
return merged, added
|
|
}
|
|
|
|
func companyLinkURLs(links []CompanyLink) []string {
|
|
if len(links) == 0 {
|
|
return nil
|
|
}
|
|
|
|
urls := make([]string, 0, len(links))
|
|
for _, link := range links {
|
|
url := strings.TrimSpace(link.URL)
|
|
if url != "" {
|
|
urls = append(urls, url)
|
|
}
|
|
}
|
|
return urls
|
|
}
|