Skip to content

Commit 9113596

Browse files
committed
Internal API for Viz
1 parent a5a6577 commit 9113596

File tree

23 files changed

+2047
-55
lines changed

23 files changed

+2047
-55
lines changed

server/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ REEARTH_COGNITO_CLIENTID=
2828
# Local Mock Auth
2929
REEARTH_MOCKAUTH=
3030

31+
REEARTH_INTERNALAPI_ACTIVE=true
32+
REEARTH_INTERNALAPI_PORT=50051
33+
REEARTH_INTERNALAPI_TOKEN=token
34+
3135
# Auth client
3236
#REEARTH_AUTH_ISS=https://hoge.com
3337
#REEARTH_AUTH_AUD=https://api.reearth.example.com

server/Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ schematyper:
111111
deep-copy:
112112
(cd pkg/property && deep-copy --type Initializer --pointer-receiver -o initializer_gen.go .)
113113

114+
grpc:
115+
protoc --go_out=./internal/adapter/internalapi/ --go_opt=paths=source_relative \
116+
--go-grpc_out=./internal/adapter/internalapi/ --go-grpc_opt=paths=source_relative \
117+
./schemas/internalapi/v1/schema.proto
118+
114119
# =======================
115120
# Tools
116121
# =======================

server/e2e/common.go

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,26 @@ import (
3333
)
3434

3535
var (
36-
fr *gateway.File
36+
fr *gateway.File
37+
3738
disabledAuthConfig = &config.Config{
3839
Origins: []string{"https://example.com"},
3940
AuthSrv: config.AuthSrvConfig{
4041
Disabled: true,
4142
},
4243
}
44+
45+
internalApiConfig = &config.Config{
46+
Origins: []string{"https://example.com"},
47+
AuthSrv: config.AuthSrvConfig{
48+
Disabled: true,
49+
},
50+
InternalApi: config.InternalApiConfig{
51+
Active: true,
52+
Port: "50051",
53+
Token: "test",
54+
},
55+
}
4356
)
4457

4558
type Seeder func(ctx context.Context, r *repo.Container, f gateway.File) error
@@ -109,20 +122,38 @@ func StartGQLServerWithRepos(t *testing.T, cfg *config.Config, repos *repo.Conta
109122
}
110123

111124
ctx := context.Background()
112-
l, err := net.Listen("tcp", ":0")
113-
if err != nil {
114-
t.Fatalf("server failed to listen: %v", err)
115-
}
116125

117126
srv, gateways, accountGateway := initServerWithAccountGateway(cfg, repos, ctx)
118127

119128
ch := make(chan error)
120-
go func() {
121-
if err := srv.Serve(l); err != http.ErrServerClosed {
122-
ch <- err
129+
130+
var l net.Listener
131+
var err error
132+
133+
if cfg.InternalApi.Active {
134+
l, err = net.Listen("tcp", ":"+cfg.InternalApi.Port)
135+
if err != nil {
136+
t.Fatalf("server failed to listen: %v", err)
123137
}
124-
close(ch)
125-
}()
138+
go func() {
139+
if err := srv.ServeGRPC(l); err != http.ErrServerClosed {
140+
ch <- err
141+
}
142+
close(ch)
143+
}()
144+
} else {
145+
l, err = net.Listen("tcp", ":0")
146+
if err != nil {
147+
t.Fatalf("server failed to listen: %v", err)
148+
}
149+
go func() {
150+
if err := srv.Serve(l); err != http.ErrServerClosed {
151+
ch <- err
152+
}
153+
close(ch)
154+
}()
155+
}
156+
126157
t.Cleanup(func() {
127158
if err := srv.Shutdown(context.Background()); err != nil {
128159
t.Fatalf("server shutdown: %v", err)
@@ -132,6 +163,7 @@ func StartGQLServerWithRepos(t *testing.T, cfg *config.Config, repos *repo.Conta
132163
t.Fatalf("server serve: %v", err)
133164
}
134165
})
166+
135167
return httpexpect.Default(t, "http://"+l.Addr().String()), gateways, accountGateway
136168
}
137169

@@ -151,6 +183,10 @@ func ServerAndRepos(t *testing.T, seeder Seeder) (*httpexpect.Expect, *repo.Cont
151183
return startServer(t, disabledAuthConfig, true, seeder)
152184
}
153185

186+
func GRPCServer(t *testing.T, seeder Seeder) (*httpexpect.Expect, *repo.Container, *gateway.Container) {
187+
return startServer(t, internalApiConfig, true, seeder)
188+
}
189+
154190
func Server(t *testing.T, seeder Seeder) *httpexpect.Expect {
155191
e, _, _ := startServer(t, disabledAuthConfig, true, seeder)
156192
return e

server/e2e/proto_project_test.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package e2e
2+
3+
import (
4+
"context"
5+
"io"
6+
"log"
7+
"testing"
8+
"time"
9+
10+
"github.com/samber/lo"
11+
"github.com/stretchr/testify/assert"
12+
"google.golang.org/grpc"
13+
"google.golang.org/grpc/credentials/insecure"
14+
"google.golang.org/grpc/metadata"
15+
16+
pb "github.com/reearth/reearth/server/internal/adapter/internalapi/schemas/internalapi/v1"
17+
)
18+
19+
func SafeClose(c io.Closer) {
20+
if err := c.Close(); err != nil {
21+
log.Printf("warning: failed to close: %v", err)
22+
}
23+
}
24+
25+
func TestInternalAPI_private(t *testing.T) {
26+
GRPCServer(t, baseSeeder)
27+
28+
runTestWithUser(t, uID.String(), func(client pb.ReEarthVisualizerClient, ctx context.Context) {
29+
30+
// -------- default Project
31+
res, err := client.CreateProject(ctx, &pb.CreateProjectRequest{
32+
TeamId: wID.String(),
33+
Visualizer: pb.Visualizer_VISUALIZER_CESIUM,
34+
Name: lo.ToPtr("Test Project1"),
35+
Description: lo.ToPtr("Test Description1"),
36+
CoreSupport: lo.ToPtr(true),
37+
// Visibility: lo.ToPtr("private"),
38+
})
39+
assert.Nil(t, err)
40+
41+
// -------- private
42+
res2, err := client.GetProject(ctx, &pb.GetProjectRequest{
43+
ProjectId: res.Project.Id,
44+
})
45+
assert.Nil(t, err)
46+
assert.Equal(t, "private", res2.Project.Visibility)
47+
48+
// -------- private Project
49+
res, err = client.CreateProject(ctx, &pb.CreateProjectRequest{
50+
TeamId: wID.String(),
51+
Visualizer: pb.Visualizer_VISUALIZER_CESIUM,
52+
Name: lo.ToPtr("Test Project1"),
53+
Description: lo.ToPtr("Test Description1"),
54+
CoreSupport: lo.ToPtr(true),
55+
Visibility: lo.ToPtr("private"),
56+
})
57+
assert.Nil(t, err)
58+
59+
// -------- private
60+
res2, err = client.GetProject(ctx, &pb.GetProjectRequest{
61+
ProjectId: res.Project.Id,
62+
})
63+
assert.Nil(t, err)
64+
assert.Equal(t, "private", res2.Project.Visibility)
65+
66+
// -------- public list size 3
67+
// 0: creante seeder => null
68+
// 1: creante default => private
69+
// 2: creante private => private
70+
res3, err := client.GetProjectList(ctx, &pb.GetProjectListRequest{
71+
TeamId: wID.String(),
72+
})
73+
assert.Nil(t, err)
74+
assert.Equal(t, 3, len(res3.Projects))
75+
assert.Equal(t, "", res3.Projects[0].Visibility)
76+
assert.Equal(t, "private", res3.Projects[1].Visibility)
77+
assert.Equal(t, "private", res3.Projects[2].Visibility)
78+
})
79+
80+
// User2 test
81+
runTestWithUser(t, uID2.String(), func(client pb.ReEarthVisualizerClient, ctx context.Context) {
82+
// -------- public list size 0 (Not authenticated)
83+
res3, err := client.GetProjectList(ctx, &pb.GetProjectListRequest{
84+
TeamId: wID.String(),
85+
})
86+
assert.Nil(t, err)
87+
assert.Equal(t, 0, len(res3.Projects))
88+
})
89+
}
90+
91+
func TestInternalAPI_public(t *testing.T) {
92+
GRPCServer(t, baseSeeder)
93+
94+
runTestWithUser(t, uID.String(), func(client pb.ReEarthVisualizerClient, ctx context.Context) {
95+
// -------- public Project
96+
res, err := client.CreateProject(ctx, &pb.CreateProjectRequest{
97+
TeamId: wID.String(),
98+
Visualizer: pb.Visualizer_VISUALIZER_CESIUM,
99+
Name: lo.ToPtr("Test Project1"),
100+
Description: lo.ToPtr("Test Description1"),
101+
CoreSupport: lo.ToPtr(true),
102+
Visibility: lo.ToPtr("public"),
103+
})
104+
assert.Nil(t, err)
105+
106+
// -------- private
107+
res2, err := client.GetProject(ctx, &pb.GetProjectRequest{
108+
ProjectId: res.Project.Id,
109+
})
110+
assert.Nil(t, err)
111+
assert.Equal(t, "public", res2.Project.Visibility)
112+
113+
// -------- private Project
114+
res, err = client.CreateProject(ctx, &pb.CreateProjectRequest{
115+
TeamId: wID.String(),
116+
Visualizer: pb.Visualizer_VISUALIZER_CESIUM,
117+
Name: lo.ToPtr("Test Project1"),
118+
Description: lo.ToPtr("Test Description1"),
119+
CoreSupport: lo.ToPtr(true),
120+
Visibility: lo.ToPtr("private"),
121+
})
122+
assert.Nil(t, err)
123+
124+
// -------- private
125+
res2, err = client.GetProject(ctx, &pb.GetProjectRequest{
126+
ProjectId: res.Project.Id,
127+
})
128+
assert.Nil(t, err)
129+
assert.Equal(t, "private", res2.Project.Visibility)
130+
131+
// -------- list size 3
132+
// 0: creante seeder => null
133+
// 1: creante public => public
134+
// 2: creante private => private
135+
res3, err := client.GetProjectList(ctx, &pb.GetProjectListRequest{
136+
TeamId: wID.String(),
137+
})
138+
assert.Nil(t, err)
139+
assert.Equal(t, 3, len(res3.Projects))
140+
assert.Equal(t, "", res3.Projects[0].Visibility)
141+
assert.Equal(t, "public", res3.Projects[1].Visibility)
142+
assert.Equal(t, "private", res3.Projects[2].Visibility)
143+
})
144+
145+
// User2 test
146+
runTestWithUser(t, uID2.String(), func(client pb.ReEarthVisualizerClient, ctx context.Context) {
147+
// -------- public list size 1 (Not authenticated)
148+
res3, err := client.GetProjectList(ctx, &pb.GetProjectListRequest{
149+
TeamId: wID.String(),
150+
})
151+
assert.Nil(t, err)
152+
assert.Equal(t, 1, len(res3.Projects))
153+
})
154+
}
155+
156+
func runTestWithUser(t *testing.T, userID string, testFunc func(client pb.ReEarthVisualizerClient, ctx context.Context)) {
157+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
158+
defer cancel()
159+
160+
ctx = metadata.NewOutgoingContext(ctx, metadata.New(map[string]string{
161+
"authorization": "Bearer test",
162+
"user-id": userID,
163+
}))
164+
165+
conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
166+
if err != nil {
167+
t.Fatalf("failed to connect: %v", err)
168+
}
169+
defer SafeClose(conn)
170+
171+
client := pb.NewReEarthVisualizerClient(conn)
172+
testFunc(client, ctx)
173+
}

server/e2e/seeder.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,20 @@ import (
3434
)
3535

3636
var (
37-
uID = user.NewID()
38-
uEmail = "[email protected]"
39-
uName = "e2e"
40-
wID = accountdomain.NewWorkspaceID()
41-
pID = id.NewProjectID()
42-
pName = "p1"
43-
pDesc = pName + " desc"
44-
pAlias = "PROJECT_ALIAS"
45-
sID = id.NewSceneID()
46-
now = time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)
37+
uID = user.NewID()
38+
uID2 = user.NewID()
39+
uEmail = "[email protected]"
40+
uName = "e2e"
41+
wID = accountdomain.NewWorkspaceID()
42+
wID2 = accountdomain.NewWorkspaceID()
43+
uEmail2 = "[email protected]"
44+
uName2 = "e3e"
45+
pID = id.NewProjectID()
46+
pName = "p1"
47+
pDesc = pName + " desc"
48+
pAlias = "PROJECT_ALIAS"
49+
sID = id.NewSceneID()
50+
now = time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)
4751

4852
nlsLayerId = id.NewNLSLayerID()
4953
storyID = id.NewStoryID()
@@ -63,6 +67,15 @@ func baseSeeder(ctx context.Context, r *repo.Container, f gateway.File) error {
6367
if err := r.User.Save(ctx, u); err != nil {
6468
return err
6569
}
70+
u2 := user.New().
71+
ID(uID2).
72+
Workspace(wID2).
73+
Name(uName2).
74+
Email(uEmail2).
75+
MustBuild()
76+
if err := r.User.Save(ctx, u2); err != nil {
77+
return err
78+
}
6679
return baseSetup(ctx, r, u, f)
6780
}
6881

server/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ require (
4949
golang.org/x/text v0.21.0
5050
golang.org/x/tools v0.29.0
5151
google.golang.org/api v0.219.0
52+
google.golang.org/grpc v1.70.0
53+
google.golang.org/protobuf v1.36.4
5254
gopkg.in/h2non/gock.v1 v1.1.2
5355
gopkg.in/square/go-jose.v2 v2.6.0
5456
)
@@ -178,8 +180,6 @@ require (
178180
google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422 // indirect
179181
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
180182
google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 // indirect
181-
google.golang.org/grpc v1.70.0 // indirect
182-
google.golang.org/protobuf v1.36.4 // indirect
183183
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
184184
gopkg.in/fsnotify.v1 v1.4.7 // indirect
185185
gopkg.in/go-jose/go-jose.v2 v2.6.2 // indirect

server/internal/adapter/gql/loader_project.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,28 @@ func (c *ProjectLoader) FindDeletedByWorkspace(ctx context.Context, wsID gqlmode
123123
}, nil
124124
}
125125

126+
func (c *ProjectLoader) VisibilityByWorkspace(ctx context.Context, wsID gqlmodel.ID) (*gqlmodel.ProjectConnection, error) {
127+
tid, err := gqlmodel.ToID[accountdomain.Workspace](wsID)
128+
if err != nil {
129+
return nil, err
130+
}
131+
132+
res, err := c.usecase.FindVisibilityByWorkspace(ctx, tid, getOperator(ctx))
133+
if err != nil {
134+
return nil, err
135+
}
136+
137+
nodes := make([]*gqlmodel.Project, 0, len(res))
138+
for _, p := range res {
139+
nodes = append(nodes, gqlmodel.ToProject(p))
140+
}
141+
142+
return &gqlmodel.ProjectConnection{
143+
Nodes: nodes,
144+
TotalCount: len(nodes),
145+
}, nil
146+
}
147+
126148
// data loaders
127149

128150
type ProjectDataLoader interface {

server/internal/adapter/gql/resolver_query.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,7 @@ func (r *queryResolver) StarredProjects(ctx context.Context, teamId gqlmodel.ID)
182182
func (r *queryResolver) DeletedProjects(ctx context.Context, teamId gqlmodel.ID) (*gqlmodel.ProjectConnection, error) {
183183
return loaders(ctx).Project.FindDeletedByWorkspace(ctx, teamId)
184184
}
185+
186+
func (r *queryResolver) VisibilityProjects(ctx context.Context, teamId gqlmodel.ID) (*gqlmodel.ProjectConnection, error) {
187+
return loaders(ctx).Project.VisibilityByWorkspace(ctx, teamId)
188+
}

0 commit comments

Comments
 (0)