Changes
4 changed files (+212/-15)
-
-
@@ -0,0 +1,108 @@// SPDX-FileCopyrightText: 2025 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only package workspace import ( "context" "connectrpc.com/connect" "google.golang.org/protobuf/proto" "pocka.jp/x/yamori/backend/core/event" "pocka.jp/x/yamori/backend/core/projection" errorV1 "pocka.jp/x/yamori/proto/go/error/v1" workspaceV2 "pocka.jp/x/yamori/proto/go/workspace/v2" ) func (s *Service) Get( ctx context.Context, req *connect.Request[workspaceV2.GetRequest], ) (*connect.Response[workspaceV2.GetResponse], error) { logger := s.core.Logger.With( "service", "yamori.workspace.v2.WorkspaceService", "method", "Get", ) header := req.Header() token, err := s.core.LoadTokenFromCookie(&header) if err != nil || token == nil { // TODO: AuthenticationError を proto に追加してこちらも切り替える return connect.NewResponse(&workspaceV2.GetResponse{ Result: &workspaceV2.GetResponse_SystemError{ SystemError: &errorV1.SystemError{ Message: proto.String("Unauthorized"), }, }, }), nil } tx, err := s.core.DB.Begin() if err != nil { logger.Error("Failed to begin transaction", "error", err) return connect.NewResponse(&workspaceV2.GetResponse{ Result: &workspaceV2.GetResponse_SystemError{ SystemError: &errorV1.SystemError{ Message: proto.String("Database error"), }, }, }), nil } defer tx.Rollback() secret, err := projection.GetLoginJwtSecret(tx) if err != nil { logger.Error("Failed to read login_jwt_secret projection", "error", err) return connect.NewResponse(&workspaceV2.GetResponse{ Result: &workspaceV2.GetResponse_SystemError{ SystemError: &errorV1.SystemError{ Message: proto.String("Database error"), }, }, }), nil } workspace, err := projection.GetWorkspace(tx) if err != nil { logger.Error("Failed to read workspace projection", "error", err) return connect.NewResponse(&workspaceV2.GetResponse{ Result: &workspaceV2.GetResponse_SystemError{ SystemError: &errorV1.SystemError{ Message: proto.String("Database error"), }, }, }), nil } if err := event.UpdateProjections(tx, workspace, secret); err != nil { logger.Error("Failed to update projections", "error", err) return connect.NewResponse(&workspaceV2.GetResponse{ Result: &workspaceV2.GetResponse_SystemError{ SystemError: &errorV1.SystemError{ Message: proto.String("Database error"), }, }, }), nil } if err := token.Validate(secret); err != nil { logger.Warn("Invalid token found", "error", err) // TODO: AuthenticationError を proto に追加してこちらも切り替える return connect.NewResponse(&workspaceV2.GetResponse{ Result: &workspaceV2.GetResponse_SystemError{ SystemError: &errorV1.SystemError{ Message: proto.String("Unauthorized"), }, }, }), nil } return connect.NewResponse(&workspaceV2.GetResponse{ Result: &workspaceV2.GetResponse_Ok{ Ok: &workspaceV2.Workspace{ DisplayName: workspace.Projection.DisplayName, HasAdmin: proto.Bool(workspace.Projection.GetNumberOfAdmins() > 0), }, }, }), nil }
-
-
-
@@ -40,21 +40,6 @@return connect.NewResponse(&res), nil } func (s *Service) Get( ctx context.Context, req *connect.Request[workspaceV2.GetRequest], ) (*connect.Response[workspaceV2.GetResponse], error) { res := workspaceV2.GetResponse{ Result: &workspaceV2.GetResponse_SystemError{ SystemError: &errorV1.SystemError{ Message: proto.String("Not Implemented"), }, }, } return connect.NewResponse(&res), nil } func (s *Service) Update( ctx context.Context, req *connect.Request[workspaceV2.UpdateRequest],
-
-
-
@@ -0,0 +1,64 @@// SPDX-FileCopyrightText: 2025 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only //go:build !js && !wasm package v2 import ( "context" "reflect" "testing" "connectrpc.com/connect" workspaceV2 "pocka.jp/x/yamori/proto/go/workspace/v2" "pocka.jp/x/yamori/proto/go/workspace/v2/v2connect" ) func TestGetOK(t *testing.T) { server, jar := setupLogin(t) httpClient := server.Client() httpClient.Jar = jar client := v2connect.NewWorkspaceServiceClient(httpClient, server.URL()) res, err := client.Get( context.Background(), connect.NewRequest(&workspaceV2.GetRequest{}), ) if err != nil { t.Fatal(err) } v, ok := res.Msg.Result.(*workspaceV2.GetResponse_Ok) if !ok { typeName := reflect.Indirect(reflect.ValueOf(res.Msg.Result)) t.Errorf("Expected ok, got %s", typeName.Type().Name()) } if v.Ok.GetDisplayName() != "名称未設定" { t.Errorf("Expected 名称未設定, got %s", v.Ok.GetDisplayName()) } } func TestGetRejectUnauthorizedRequest(t *testing.T) { server := setupInitialAdmin(t) client := v2connect.NewWorkspaceServiceClient(server.Client(), server.URL()) res, err := client.Get( context.Background(), connect.NewRequest(&workspaceV2.GetRequest{}), ) if err != nil { t.Fatal(err) } // TODO: proto を authentication_error (authorization?) にしてからここも変える if _, ok := res.Msg.Result.(*workspaceV2.GetResponse_SystemError); !ok { typeName := reflect.Indirect(reflect.ValueOf(res.Msg.Result)) t.Errorf("Expected system_error, got %s", typeName.Type().Name()) } }
-
-
-
@@ -10,11 +10,14 @@ "context""database/sql" "io" "log/slog" "net/http" "net/http/cookiejar" "reflect" "testing" "connectrpc.com/connect" "go.akshayshah.org/memhttp" "golang.org/x/net/publicsuffix" "google.golang.org/protobuf/proto" workspaceV2 "pocka.jp/x/yamori/proto/go/workspace/v2"
-
@@ -81,3 +84,40 @@ }return server } // setupLogin は初期管理者を作成し、作成したユーザでログインした状態までを // 設定し、接続可能なサーバとクライアント向けの Cookie を返す。 func setupLogin(t *testing.T) (*memhttp.Server, http.CookieJar) { server := setupInitialAdmin(t) jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) if err != nil { t.Fatal(err) } httpClient := server.Client() httpClient.Jar = jar client := v2connect.NewWorkspaceServiceClient( httpClient, server.URL(), ) res, err := client.Login( context.Background(), connect.NewRequest(&workspaceV2.LoginRequest{ Name: proto.String("alice"), Password: proto.String("alice_password"), }), ) if err != nil { t.Fatal(err) } if _, ok := res.Msg.Result.(*workspaceV2.LoginResponse_Ok); !ok { typeName := reflect.Indirect(reflect.ValueOf(res.Msg.Result)) t.Errorf("Expected ok, got %s", typeName.Type().Name()) } return server, jar }
-