feat(server): add project sharing access control
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"img_infinite_canvas/internal/domain/design"
|
||||
sharingmodule "img_infinite_canvas/internal/modules/sharing"
|
||||
"img_infinite_canvas/internal/svc"
|
||||
"img_infinite_canvas/internal/types"
|
||||
)
|
||||
|
||||
func ownedShareProject(ctx context.Context, svcCtx *svc.ServiceContext, projectID string) (design.Project, error) {
|
||||
return svcCtx.DesignService.GetProject(ctx, strings.TrimSpace(projectID))
|
||||
}
|
||||
|
||||
func verifiedShareOwner(ctx context.Context, svcCtx *svc.ServiceContext, project design.Project) (sharingmodule.Actor, error) {
|
||||
if svcCtx.AuthService == nil {
|
||||
return sharingmodule.Actor{}, errors.New("auth service is required for share management")
|
||||
}
|
||||
profile, err := svcCtx.AuthService.GetProfile(ctx, project.UserID)
|
||||
if err != nil {
|
||||
return sharingmodule.Actor{}, err
|
||||
}
|
||||
return sharingmodule.Actor{
|
||||
Authenticated: true,
|
||||
UserID: project.UserID,
|
||||
Email: profile.Email,
|
||||
CountryCode: profile.CountryCode,
|
||||
Phone: profile.Phone,
|
||||
Name: profile.Name,
|
||||
AvatarURL: profile.AvatarURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toShareSettingsResponse(ctx context.Context, svcCtx *svc.ServiceContext, project design.Project, settings sharingmodule.Settings) *types.ShareSettingsResponse {
|
||||
owner := types.SharePerson{
|
||||
Id: project.UserID,
|
||||
UserId: project.UserID,
|
||||
Name: "Owner",
|
||||
Permission: string(sharingmodule.PermissionOwner),
|
||||
}
|
||||
ownerActor, ownerErr := verifiedShareOwner(ctx, svcCtx, project)
|
||||
if ownerErr == nil {
|
||||
owner.Name = firstShareIdentifier(ownerActor.Name, owner.Name)
|
||||
owner.AvatarUrl = ownerActor.AvatarURL
|
||||
owner.Identifier = firstShareIdentifier(ownerActor.Email, displayPhone(ownerActor.CountryCode, ownerActor.Phone))
|
||||
}
|
||||
visibleMembers := filterShareMembers(settings.Members, ownerActor)
|
||||
members := make([]types.SharePerson, 0, len(visibleMembers))
|
||||
for _, member := range visibleMembers {
|
||||
members = append(members, types.SharePerson{
|
||||
Id: member.ID,
|
||||
Name: member.Identifier,
|
||||
Identifier: member.Identifier,
|
||||
Permission: string(member.Permission),
|
||||
CreatedAt: formatTime(member.CreatedAt),
|
||||
})
|
||||
}
|
||||
return &types.ShareSettingsResponse{Settings: types.ShareSettings{
|
||||
ProjectId: project.ID,
|
||||
ShareId: settings.ShareID,
|
||||
LinkPermission: string(settings.LinkPermission),
|
||||
Owner: owner,
|
||||
Members: members,
|
||||
VisitorCount: settings.VisitorCount,
|
||||
UpdatedAt: formatTime(settings.UpdatedAt),
|
||||
}}
|
||||
}
|
||||
|
||||
func filterShareMembers(members []sharingmodule.Member, owner sharingmodule.Actor) []sharingmodule.Member {
|
||||
if owner.UserID == "" {
|
||||
return members
|
||||
}
|
||||
filtered := make([]sharingmodule.Member, 0, len(members))
|
||||
for _, member := range members {
|
||||
if sharingmodule.ActorOwnsIdentifier(owner, member.Identifier) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, member)
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func toShareVisitorsResponse(ctx context.Context, svcCtx *svc.ServiceContext, visits []sharingmodule.VisitRecord) *types.ShareVisitorsResponse {
|
||||
items := make([]types.ShareVisitor, 0, len(visits))
|
||||
for _, visit := range visits {
|
||||
item := types.ShareVisitor{
|
||||
Id: visit.ID,
|
||||
UserId: visit.UserID,
|
||||
Name: "Anonymous visitor",
|
||||
VisitCount: visit.VisitCount,
|
||||
LastSeenAt: formatTime(visit.LastSeenAt),
|
||||
}
|
||||
if visit.UserID != "" && svcCtx.AuthService != nil {
|
||||
if profile, err := svcCtx.AuthService.GetProfile(ctx, visit.UserID); err == nil {
|
||||
item.Name = firstShareIdentifier(profile.Name, profile.Email, displayPhone(profile.CountryCode, profile.Phone), "Visitor")
|
||||
item.Identifier = firstShareIdentifier(profile.Email, displayPhone(profile.CountryCode, profile.Phone))
|
||||
item.AvatarUrl = profile.AvatarURL
|
||||
}
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return &types.ShareVisitorsResponse{Visitors: items}
|
||||
}
|
||||
|
||||
func toShareAccess(access sharingmodule.Access) types.ShareAccess {
|
||||
return types.ShareAccess{
|
||||
Permission: string(access.Permission),
|
||||
Source: access.Source,
|
||||
Authenticated: access.Authenticated,
|
||||
IsOwner: access.IsOwner,
|
||||
Capabilities: types.ShareCapabilities{
|
||||
CanViewCanvas: access.Capabilities.CanViewCanvas,
|
||||
CanViewChat: access.Capabilities.CanViewChat,
|
||||
CanEdit: access.Capabilities.CanEdit,
|
||||
CanManage: access.Capabilities.CanManage,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func filterSharedProject(project design.Project, access sharingmodule.Access) design.Project {
|
||||
project.UserID = ""
|
||||
if !access.Capabilities.CanViewChat {
|
||||
project.Brief = ""
|
||||
project.LastThreadID = ""
|
||||
project.Messages = []design.Message{}
|
||||
}
|
||||
return project
|
||||
}
|
||||
|
||||
func firstShareIdentifier(values ...string) string {
|
||||
for _, value := range values {
|
||||
if value = strings.TrimSpace(value); value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func displayPhone(countryCode string, phone string) string {
|
||||
phone = strings.TrimSpace(phone)
|
||||
if phone == "" {
|
||||
return ""
|
||||
}
|
||||
countryCode = strings.TrimPrefix(strings.TrimSpace(countryCode), "+")
|
||||
if countryCode == "" || strings.HasPrefix(phone, "+") {
|
||||
return phone
|
||||
}
|
||||
return "+" + countryCode + phone
|
||||
}
|
||||
Reference in New Issue
Block a user