feat(server): add project sharing access control
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
// Code scaffolded by goctl. Safe to edit.
|
||||
// goctl 1.10.1
|
||||
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"img_infinite_canvas/internal/svc"
|
||||
"img_infinite_canvas/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type DeleteShareMemberLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewDeleteShareMemberLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteShareMemberLogic {
|
||||
return &DeleteShareMemberLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DeleteShareMemberLogic) DeleteShareMember(req *types.DeleteShareMemberRequest) (resp *types.ShareSettingsResponse, err error) {
|
||||
project, err := ownedShareProject(l.ctx, l.svcCtx, req.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings, err := l.svcCtx.ShareService.DeleteMember(l.ctx, project.ID, project.UserID, req.MemberId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toShareSettingsResponse(l.ctx, l.svcCtx, project, settings), nil
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package logic
|
||||
import (
|
||||
"context"
|
||||
|
||||
sharingmodule "img_infinite_canvas/internal/modules/sharing"
|
||||
"img_infinite_canvas/internal/svc"
|
||||
"img_infinite_canvas/internal/types"
|
||||
|
||||
@@ -31,6 +32,9 @@ func (l *GetProjectLogic) GetProject(req *types.ProjectIdRequest) (resp *types.P
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if access, ok := sharingmodule.AccessFromContext(l.ctx); ok {
|
||||
project = filterSharedProject(project, access)
|
||||
}
|
||||
|
||||
return toProjectResponse(project), nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Code scaffolded by goctl. Safe to edit.
|
||||
// goctl 1.10.1
|
||||
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"img_infinite_canvas/internal/svc"
|
||||
"img_infinite_canvas/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetShareSettingsLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetShareSettingsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetShareSettingsLogic {
|
||||
return &GetShareSettingsLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetShareSettingsLogic) GetShareSettings(req *types.ShareSettingsRequest) (resp *types.ShareSettingsResponse, err error) {
|
||||
project, err := ownedShareProject(l.ctx, l.svcCtx, req.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings, err := l.svcCtx.ShareService.Settings(l.ctx, project.ID, project.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toShareSettingsResponse(l.ctx, l.svcCtx, project, settings), nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Code scaffolded by goctl. Safe to edit.
|
||||
// goctl 1.10.1
|
||||
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
sharingmodule "img_infinite_canvas/internal/modules/sharing"
|
||||
|
||||
"img_infinite_canvas/internal/svc"
|
||||
"img_infinite_canvas/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type InviteShareMemberLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewInviteShareMemberLogic(ctx context.Context, svcCtx *svc.ServiceContext) *InviteShareMemberLogic {
|
||||
return &InviteShareMemberLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *InviteShareMemberLogic) InviteShareMember(req *types.InviteShareMemberRequest) (resp *types.ShareSettingsResponse, err error) {
|
||||
permission, err := sharingmodule.ParseMemberPermission(req.Permission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
project, err := ownedShareProject(l.ctx, l.svcCtx, req.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
owner, err := verifiedShareOwner(l.ctx, l.svcCtx, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings, err := l.svcCtx.ShareService.Invite(l.ctx, project.ID, owner, req.Identifier, permission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toShareSettingsResponse(l.ctx, l.svcCtx, project, settings), nil
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Code scaffolded by goctl. Safe to edit.
|
||||
// goctl 1.10.1
|
||||
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"img_infinite_canvas/internal/svc"
|
||||
"img_infinite_canvas/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type ListShareVisitorsLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewListShareVisitorsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListShareVisitorsLogic {
|
||||
return &ListShareVisitorsLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ListShareVisitorsLogic) ListShareVisitors(req *types.ShareSettingsRequest) (resp *types.ShareVisitorsResponse, err error) {
|
||||
project, err := ownedShareProject(l.ctx, l.svcCtx, req.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
visitors, err := l.svcCtx.ShareService.Visitors(l.ctx, project.ID, project.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toShareVisitorsResponse(l.ctx, l.svcCtx, visitors), nil
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"img_infinite_canvas/internal/domain/design"
|
||||
sharingmodule "img_infinite_canvas/internal/modules/sharing"
|
||||
"img_infinite_canvas/internal/svc"
|
||||
"img_infinite_canvas/internal/types"
|
||||
|
||||
@@ -37,10 +38,17 @@ func (l *QueryProjectLogic) QueryProject(req *types.QueryProjectRequest) (resp *
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toQueryProjectResponse(project), nil
|
||||
permission := "OWNER"
|
||||
visibility := "PRIVATE"
|
||||
if access, ok := sharingmodule.AccessFromContext(l.ctx); ok {
|
||||
permission = strings.ToUpper(string(access.Permission))
|
||||
visibility = "SHARED"
|
||||
project = filterSharedProject(project, access)
|
||||
}
|
||||
return toQueryProjectResponse(project, permission, visibility), nil
|
||||
}
|
||||
|
||||
func toQueryProjectResponse(project design.Project) *types.QueryProjectResponse {
|
||||
func toQueryProjectResponse(project design.Project, permission string, visibility string) *types.QueryProjectResponse {
|
||||
covers := queryProjectCoverList(project)
|
||||
updatedAt := formatLovartTime(project.UpdatedAt)
|
||||
return &types.QueryProjectResponse{
|
||||
@@ -63,9 +71,9 @@ func toQueryProjectResponse(project design.Project) *types.QueryProjectResponse
|
||||
UniqId: project.SnapshotID,
|
||||
ParentUniqId: "",
|
||||
},
|
||||
EffectivePermission: "OWNER",
|
||||
EffectivePermission: permission,
|
||||
CopyTaskStatus: nil,
|
||||
Visibility: "PRIVATE",
|
||||
Visibility: visibility,
|
||||
CreateTime: updatedAt,
|
||||
UpdateTime: updatedAt,
|
||||
},
|
||||
@@ -78,7 +86,7 @@ func toQueryProjectResponse(project design.Project) *types.QueryProjectResponse
|
||||
Version: project.Version,
|
||||
SnapshotId: nil,
|
||||
ItemId: nil,
|
||||
Permission: "OWNER",
|
||||
Permission: permission,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Code scaffolded by goctl. Safe to edit.
|
||||
// goctl 1.10.1
|
||||
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"img_infinite_canvas/internal/domain/design"
|
||||
sharingmodule "img_infinite_canvas/internal/modules/sharing"
|
||||
"img_infinite_canvas/internal/svc"
|
||||
"img_infinite_canvas/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type ResolveShareLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewResolveShareLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResolveShareLogic {
|
||||
return &ResolveShareLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ResolveShareLogic) ResolveShare(req *types.ResolveShareRequest) (resp *types.ResolveShareResponse, err error) {
|
||||
actor := sharingmodule.ActorFromContext(l.ctx)
|
||||
access, err := l.svcCtx.ShareService.ResolveAccess(l.ctx, req.ShareId, actor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ownerCtx := design.ContextWithUserID(l.ctx, access.OwnerID)
|
||||
project, err := l.svcCtx.DesignService.GetProject(ownerCtx, access.ProjectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := l.svcCtx.ShareService.RecordVisit(l.ctx, access, actor, req.VisitorId); err != nil {
|
||||
l.Errorf("record share visit: %v", err)
|
||||
}
|
||||
project = filterSharedProject(project, access)
|
||||
updatedAt := project.UpdatedAt
|
||||
if updatedAt.IsZero() {
|
||||
updatedAt = time.Now().UTC()
|
||||
}
|
||||
return &types.ResolveShareResponse{
|
||||
ShareId: access.ShareID,
|
||||
Project: toAPIProject(project),
|
||||
Access: toShareAccess(access),
|
||||
UpdatedAt: formatTime(updatedAt),
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Code scaffolded by goctl. Safe to edit.
|
||||
// goctl 1.10.1
|
||||
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"img_infinite_canvas/internal/svc"
|
||||
"img_infinite_canvas/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type RevokeShareAccessLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewRevokeShareAccessLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RevokeShareAccessLogic {
|
||||
return &RevokeShareAccessLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *RevokeShareAccessLogic) RevokeShareAccess(req *types.ShareSettingsRequest) (resp *types.ShareSettingsResponse, err error) {
|
||||
project, err := ownedShareProject(l.ctx, l.svcCtx, req.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings, err := l.svcCtx.ShareService.RevokeAll(l.ctx, project.ID, project.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toShareSettingsResponse(l.ctx, l.svcCtx, project, settings), nil
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"img_infinite_canvas/internal/domain/design"
|
||||
sharingmodule "img_infinite_canvas/internal/modules/sharing"
|
||||
"img_infinite_canvas/internal/svc"
|
||||
"img_infinite_canvas/internal/types"
|
||||
)
|
||||
|
||||
func TestFilterSharedProjectRedactsCanvasOnlyData(t *testing.T) {
|
||||
project := design.Project{
|
||||
ID: "project-1",
|
||||
UserID: "91cd197b-8255-4172-9981-77391fd38f33",
|
||||
Brief: "private brief",
|
||||
LastThreadID: "thread-1",
|
||||
Messages: []design.Message{{
|
||||
ID: "message-1",
|
||||
Content: "private chat",
|
||||
CreatedAt: time.Now(),
|
||||
}},
|
||||
}
|
||||
canvasAccess := sharingmodule.Access{
|
||||
Permission: sharingmodule.PermissionCanvas,
|
||||
Capabilities: sharingmodule.PermissionCanvas.Capabilities(),
|
||||
}
|
||||
|
||||
filtered := filterSharedProject(project, canvasAccess)
|
||||
if filtered.UserID != "" || filtered.Brief != "" || filtered.LastThreadID != "" || len(filtered.Messages) != 0 {
|
||||
t.Fatalf("canvas-only response leaked private fields: %+v", filtered)
|
||||
}
|
||||
if project.UserID == "" || len(project.Messages) == 0 {
|
||||
t.Fatal("filter must not mutate the source project")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubmitGeneratorTaskRejectsShareProjectMismatch(t *testing.T) {
|
||||
ctx := sharingmodule.ContextWithAccess(context.Background(), sharingmodule.Access{ProjectID: "shared-project"})
|
||||
logic := NewSubmitGeneratorTaskLogic(ctx, &svc.ServiceContext{})
|
||||
_, err := logic.SubmitGeneratorTask(&types.GeneratorTaskSubmitRequest{ProjectId: "other-project"})
|
||||
if !errors.Is(err, sharingmodule.ErrNotFound) {
|
||||
t.Fatalf("expected share project mismatch to be rejected, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterSharedProjectKeepsViewerMessages(t *testing.T) {
|
||||
project := design.Project{
|
||||
UserID: "91cd197b-8255-4172-9981-77391fd38f33",
|
||||
Messages: []design.Message{{
|
||||
ID: "message-1",
|
||||
Content: "visible chat",
|
||||
}},
|
||||
}
|
||||
viewerAccess := sharingmodule.Access{
|
||||
Permission: sharingmodule.PermissionViewer,
|
||||
Capabilities: sharingmodule.PermissionViewer.Capabilities(),
|
||||
}
|
||||
|
||||
filtered := filterSharedProject(project, viewerAccess)
|
||||
if filtered.UserID != "" {
|
||||
t.Fatal("shared response must not expose the owner id")
|
||||
}
|
||||
if len(filtered.Messages) != 1 || filtered.Messages[0].Content != "visible chat" {
|
||||
t.Fatalf("viewer should retain chat messages: %+v", filtered.Messages)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterShareMembersRemovesOwnerIdentifiers(t *testing.T) {
|
||||
owner := sharingmodule.Actor{
|
||||
Authenticated: true,
|
||||
UserID: "91cd197b-8255-4172-9981-77391fd38f33",
|
||||
Email: "owner@example.com",
|
||||
CountryCode: "+86",
|
||||
Phone: "13800138000",
|
||||
}
|
||||
members := []sharingmodule.Member{
|
||||
{ID: "email", Identifier: "OWNER@EXAMPLE.COM", Permission: sharingmodule.PermissionEditor},
|
||||
{ID: "phone", Identifier: "+86 138-0013-8000", Permission: sharingmodule.PermissionViewer},
|
||||
{ID: "other", Identifier: "member@example.com", Permission: sharingmodule.PermissionViewer},
|
||||
}
|
||||
|
||||
filtered := filterShareMembers(members, owner)
|
||||
if len(filtered) != 1 || filtered[0].ID != "other" {
|
||||
t.Fatalf("expected only the non-owner member, got %+v", filtered)
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
|
||||
"img_infinite_canvas/internal/domain/design"
|
||||
sharingmodule "img_infinite_canvas/internal/modules/sharing"
|
||||
"img_infinite_canvas/internal/svc"
|
||||
"img_infinite_canvas/internal/types"
|
||||
|
||||
@@ -28,6 +29,9 @@ func NewSubmitGeneratorTaskLogic(ctx context.Context, svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func (l *SubmitGeneratorTaskLogic) SubmitGeneratorTask(req *types.GeneratorTaskSubmitRequest) (resp *types.GeneratorTaskSubmitResponse, err error) {
|
||||
if access, ok := sharingmodule.AccessFromContext(l.ctx); ok && req.ProjectId != access.ProjectID {
|
||||
return nil, sharingmodule.ErrNotFound
|
||||
}
|
||||
task, err := l.svcCtx.DesignService.SubmitGeneratorTask(l.ctx, design.GeneratorTaskSubmitRequest{
|
||||
CID: req.Cid,
|
||||
ProjectID: req.ProjectId,
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Code scaffolded by goctl. Safe to edit.
|
||||
// goctl 1.10.1
|
||||
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
sharingmodule "img_infinite_canvas/internal/modules/sharing"
|
||||
|
||||
"img_infinite_canvas/internal/svc"
|
||||
"img_infinite_canvas/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type UpdateShareLinkLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewUpdateShareLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateShareLinkLogic {
|
||||
return &UpdateShareLinkLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *UpdateShareLinkLogic) UpdateShareLink(req *types.UpdateShareLinkRequest) (resp *types.ShareSettingsResponse, err error) {
|
||||
permission, err := sharingmodule.ParseLinkPermission(req.Permission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
project, err := ownedShareProject(l.ctx, l.svcCtx, req.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings, err := l.svcCtx.ShareService.UpdateLink(l.ctx, project.ID, project.UserID, permission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toShareSettingsResponse(l.ctx, l.svcCtx, project, settings), nil
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Code scaffolded by goctl. Safe to edit.
|
||||
// goctl 1.10.1
|
||||
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
sharingmodule "img_infinite_canvas/internal/modules/sharing"
|
||||
|
||||
"img_infinite_canvas/internal/svc"
|
||||
"img_infinite_canvas/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type UpdateShareMemberLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewUpdateShareMemberLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateShareMemberLogic {
|
||||
return &UpdateShareMemberLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *UpdateShareMemberLogic) UpdateShareMember(req *types.UpdateShareMemberRequest) (resp *types.ShareSettingsResponse, err error) {
|
||||
permission, err := sharingmodule.ParseMemberPermission(req.Permission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
project, err := ownedShareProject(l.ctx, l.svcCtx, req.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings, err := l.svcCtx.ShareService.UpdateMember(l.ctx, project.ID, project.UserID, req.MemberId, permission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toShareSettingsResponse(l.ctx, l.svcCtx, project, settings), nil
|
||||
}
|
||||
Reference in New Issue
Block a user