From bc77487d4f34a7af34f4cd7ff70dd228f8710851 Mon Sep 17 00:00:00 2001
From: devsjc <47188100+devsjc@users.noreply.github.com>
Date: Thu, 11 Jun 2026 15:27:16 +0100
Subject: [PATCH 1/2] feat(backend): Simplify location ownership
---
README.md | 24 +-
cmd/main.go | 8 +-
internal/server/dummy/adminserverimpl.go | 175 -----
internal/server/dummy/dataserverimpl.go | 11 +
internal/server/postgres/adminserverimpl.go | 572 ----------------
.../server/postgres/adminserverimpl_test.go | 631 ------------------
internal/server/postgres/dataserverimpl.go | 83 ++-
.../server/postgres/dataserverimpl_test.go | 93 +++
internal/server/postgres/package_test.go | 4 -
.../sql/migrations/00008_simple_iam.sql | 93 +++
internal/server/postgres/sql/queries/iam.sql | 196 ------
.../server/postgres/sql/queries/locations.sql | 207 +++---
proto/ocf/dp/dp-admin.messages.proto | 213 ------
proto/ocf/dp/dp-admin.service.proto | 28 -
proto/ocf/dp/dp-data.messages.proto | 23 +-
proto/ocf/dp/dp-data.service.proto | 3 +
proto/ocf/dp/dp.rules.proto | 4 +-
17 files changed, 422 insertions(+), 1946 deletions(-)
delete mode 100644 internal/server/dummy/adminserverimpl.go
delete mode 100644 internal/server/postgres/adminserverimpl.go
delete mode 100644 internal/server/postgres/adminserverimpl_test.go
create mode 100644 internal/server/postgres/sql/migrations/00008_simple_iam.sql
delete mode 100644 internal/server/postgres/sql/queries/iam.sql
delete mode 100644 proto/ocf/dp/dp-admin.messages.proto
delete mode 100644 proto/ocf/dp/dp-admin.service.proto
diff --git a/README.md b/README.md
index db73286..9e5a699 100644
--- a/README.md
+++ b/README.md
@@ -574,7 +574,7 @@ Forecaster represents a generative source of predicted values.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
-| energy_source_filter | [EnergySource](#ocf-dp-EnergySource) | optional | Optional filter to only return locations of a specific energy source. || location_type_filter | [LocationType](#ocf-dp-LocationType) | optional | Optional filter to only return locations of a specific location type. || location_uuids_filter | [string](#string) | repeated | Optional filter to only return locations from a given set. || user_oauth_id_filter | [string](#string) | optional | Optional filter to only return locations belonging to a specific user. || permission_filter | [Permission](#ocf-dp-Permission) | optional | Optional filter to only return locations for which the user has a specific permission. || enclosing_location_uuid_filter | [string](#string) | optional | Optional filter to only return locations enclosed within a specific location. || enclosed_location_uuid_filter | [string](#string) | optional | Optional filter to only return locations that enclose a specific location. || location_names_filter | [string](#string) | repeated | Optional filter to only return locations with a specific name. |
+| energy_source_filter | [EnergySource](#ocf-dp-EnergySource) | optional | Optional filter to only return locations of a specific energy source. || location_type_filter | [LocationType](#ocf-dp-LocationType) | optional | Optional filter to only return locations of a specific location type. || location_uuids_filter | [string](#string) | repeated | Optional filter to only return locations from a given set. || organisation_id_filter | [string](#string) | optional | Optional filter to only return locations belonging to a specific organisation. || enclosing_location_uuid_filter | [string](#string) | optional | Optional filter to only return locations enclosed within a specific location. || enclosed_location_uuid_filter | [string](#string) | optional | Optional filter to only return locations that enclose a specific location. || location_names_filter | [string](#string) | repeated | Optional filter to only return locations with a specific name. |
ListLocationsResponse
@@ -645,6 +645,20 @@ Forecaster represents a generative source of predicted values.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| forecaster | [Forecaster](#ocf-dp-Forecaster) | | |
+
+UpdateLocationOwnerRequest
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| location_uuid | [string](#string) | | || new_organisation_id | [string](#string) | | |
+
+UpdateLocationOwnerResponse
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| location_uuid | [string](#string) | | || organisation_id | [string](#string) | | |
UpdateLocationRequest
@@ -740,6 +754,14 @@ UpdateLocation modifies various attributes associated with a given location.
_[UpdateLocationRequest](#ocf-dp-UpdateLocationRequest) / [UpdateLocationResponse](#ocf-dp-UpdateLocationResponse)_
+
+
+#### UpdateLocationOwner
+
+UpdateLocationOwner changes the ownership of a location.
+
+_[UpdateLocationOwnerRequest](#ocf-dp-UpdateLocationOwnerRequest) / [UpdateLocationOwnerResponse](#ocf-dp-UpdateLocationOwnerResponse)_
+
#### ListLocations
diff --git a/cmd/main.go b/cmd/main.go
index 2ef043e..1e2f916 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -56,9 +56,8 @@ func main() {
databaseUrl := strings.Trim(conf.GetString("dburl"), "\"")
var (
- dataServerImpl pb.DataPlatformDataServiceServer
- adminServerImpl pb.DataPlatformAdministrationServiceServer
- s *grpc.Server
+ dataServerImpl pb.DataPlatformDataServiceServer
+ s *grpc.Server
)
// Create a validator to use with protovalidate interceptor
@@ -71,7 +70,6 @@ func main() {
log.Warn().Msg("running in test mode with fake data. Not for production use")
dataServerImpl = dbdy.NewDataPlatformDataServerImpl()
- adminServerImpl = dbdy.NewDataPlatformAdministrationServiceServerImpl()
// For a dummy-backed server, just validate requests
s = grpc.NewServer(
@@ -85,7 +83,6 @@ func main() {
logInterceptor := ix.NewLoggingInterceptor()
txInterceptor := ix.NewTransactionInterceptor(databaseUrl, dbpg.Migrations)
dataServerImpl = dbpg.NewDataPlatformDataServiceServerImpl()
- adminServerImpl = dbpg.NewDataPlatformAdministrationServiceServerImpl()
// For a postgres-backed server, validate requests and manage database transactions
s = grpc.NewServer(
@@ -109,7 +106,6 @@ func main() {
// Create the GRPC server
// * Add an interceptor for request validation
pb.RegisterDataPlatformDataServiceServer(s, dataServerImpl)
- pb.RegisterDataPlatformAdministrationServiceServer(s, adminServerImpl)
grpc_health_v1.RegisterHealthServer(s, health.NewServer())
reflection.Register(s)
diff --git a/internal/server/dummy/adminserverimpl.go b/internal/server/dummy/adminserverimpl.go
deleted file mode 100644
index a2e605a..0000000
--- a/internal/server/dummy/adminserverimpl.go
+++ /dev/null
@@ -1,175 +0,0 @@
-// Package postgres defines a server implementation for the DataPlatformServiceServer.
-// This implementation is backed by a PostgreSQL database.
-//
-// Functions and structs for connecting to the database are generated from SQL using
-// the sqlc library, whilst the Server interface that is being implemented comes from
-// the top-level proto definitions.
-package dummy
-
-import (
- "context"
- "time"
-
- "github.com/google/uuid"
- "google.golang.org/protobuf/types/known/structpb"
- "google.golang.org/protobuf/types/known/timestamppb"
-
- pb "github.com/openclimatefix/data-platform/internal/gen/ocf/dp"
-)
-
-// --- Server Implementation ----------------------------------------------------------------------
-
-func NewDataPlatformAdministrationServiceServerImpl() *DataPlatformAdministrationServiceServerImpl {
- return &DataPlatformAdministrationServiceServerImpl{}
-}
-
-type DataPlatformAdministrationServiceServerImpl struct{}
-
-func (d *DataPlatformAdministrationServiceServerImpl) DeleteOrganisation(
- ctx context.Context,
- req *pb.DeleteOrganisationRequest,
-) (*pb.DeleteOrganisationResponse, error) {
- return &pb.DeleteOrganisationResponse{}, nil
-}
-
-// CreateLocationPolicyGroup implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) CreateLocationPolicyGroup(
- ctx context.Context,
- req *pb.CreateLocationPolicyGroupRequest,
-) (*pb.CreateLocationPolicyGroupResponse, error) {
- return &pb.CreateLocationPolicyGroupResponse{
- LocationPolicyGroupId: uuid.New().String(),
- Name: req.Name,
- }, nil
-}
-
-// GetLocationPolicyGroup implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) GetLocationPolicyGroup(
- ctx context.Context,
- req *pb.GetLocationPolicyGroupRequest,
-) (*pb.GetLocationPolicyGroupResponse, error) {
- return &pb.GetLocationPolicyGroupResponse{
- LocationPolicyGroupId: uuid.New().String(),
- Name: "Dummy Location Policy Group",
- LocationPolicies: []*pb.LocationPolicy{
- {
- LocationId: uuid.New().String(),
- EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR,
- Permission: pb.Permission_PERMISSION_READ,
- },
- },
- }, nil
-}
-
-// AddLocationPoliciesToGroup implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) AddLocationPoliciesToGroup(
- context.Context,
- *pb.AddLocationPoliciesToGroupRequest,
-) (*pb.AddLocationPoliciesToGroupResponse, error) {
- return &pb.AddLocationPoliciesToGroupResponse{}, nil
-}
-
-// RemoveLocationPoliciesFromGroup implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) RemoveLocationPoliciesFromGroup(
- context.Context,
- *pb.RemoveLocationPoliciesFromGroupRequest,
-) (*pb.RemoveLocationPoliciesFromGroupResponse, error) {
- return &pb.RemoveLocationPoliciesFromGroupResponse{}, nil
-}
-
-// GetUser implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) GetUser(
- ctx context.Context,
- req *pb.GetUserRequest,
-) (*pb.GetUserResponse, error) {
- return &pb.GetUserResponse{
- UserId: uuid.New().String(),
- OauthId: req.OauthId,
- Organisation: "Dummy Organisation",
- LocationPolicyGroups: []string{"DUMMY_POLICY_GROUP_ID_1", "DUMMY_POLICY_GROUP_ID_2"},
- CreatedAt: timestamppb.New(time.Now().UTC()),
- Metadata: &structpb.Struct{},
- }, nil
-}
-
-// CreateUser implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) CreateUser(
- ctx context.Context,
- req *pb.CreateUserRequest,
-) (*pb.CreateUserResponse, error) {
- return &pb.CreateUserResponse{
- UserId: uuid.New().String(),
- OauthId: req.OauthId,
- }, nil
-}
-
-// DeleteUser implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) DeleteUser(
- context.Context,
- *pb.DeleteUserRequest,
-) (*pb.DeleteUserResponse, error) {
- return &pb.DeleteUserResponse{}, nil
-}
-
-// CreateOrganisation implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) CreateOrganisation(
- ctx context.Context,
- req *pb.CreateOrganisationRequest,
-) (*pb.CreateOrganisationResponse, error) {
- return &pb.CreateOrganisationResponse{
- OrgId: uuid.New().String(),
- OrgName: req.OrgName,
- }, nil
-}
-
-// GetOrganisation implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) GetOrganisation(
- ctx context.Context,
- req *pb.GetOrganisationRequest,
-) (*pb.GetOrganisationResponse, error) {
- return &pb.GetOrganisationResponse{
- OrgId: uuid.New().String(),
- OrgName: req.OrgName,
- Metadata: &structpb.Struct{},
- CreatedAt: timestamppb.New(time.Now().UTC()),
- LocationPolicyGroups: []string{"DUMMY_POLICY_GROUP_ID_1", "DUMMY_POLICY_GROUP_ID_2"},
- UserOauthIds: []string{uuid.New().String()},
- }, nil
-}
-
-// AddLocationPolicyGroupToOrganisation implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) AddLocationPolicyGroupToOrganisation(
- context.Context,
- *pb.AddLocationPolicyGroupToOrganisationRequest,
-) (*pb.AddLocationPolicyGroupToOrganisationResponse, error) {
- return &pb.AddLocationPolicyGroupToOrganisationResponse{}, nil
-}
-
-// AddUserToOrganisation implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) AddUserToOrganisation(
- context.Context,
- *pb.AddUserToOrganisationRequest,
-) (*pb.AddUserToOrganisationResponse, error) {
- return &pb.AddUserToOrganisationResponse{}, nil
-}
-
-// RemoveLocationPolicyGroupFromOrganisation implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) RemoveLocationPolicyGroupFromOrganisation(
- context.Context,
- *pb.RemoveLocationPolicyGroupFromOrganisationRequest,
-) (*pb.RemoveLocationPolicyGroupFromOrganisationResponse, error) {
- return &pb.RemoveLocationPolicyGroupFromOrganisationResponse{}, nil
-}
-
-// RemoveUserFromOrganisation implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) RemoveUserFromOrganisation(
- context.Context,
- *pb.RemoveUserFromOrganisationRequest,
-) (*pb.RemoveUserFromOrganisationResponse, error) {
- return &pb.RemoveUserFromOrganisationResponse{}, nil
-}
-
-// Compile-time check to ensure the interface is implemented fully.
-var _ pb.DataPlatformAdministrationServiceServer = (*DataPlatformAdministrationServiceServerImpl)(
- nil,
-)
diff --git a/internal/server/dummy/dataserverimpl.go b/internal/server/dummy/dataserverimpl.go
index 646271a..c93146e 100644
--- a/internal/server/dummy/dataserverimpl.go
+++ b/internal/server/dummy/dataserverimpl.go
@@ -314,6 +314,17 @@ func (d *DataPlatformDataServiceServerImpl) UpdateLocation(
}, nil
}
+// UpdateLocationOwner implements [dp.DataPlatformDataServiceServer].
+func (d *DataPlatformDataServiceServerImpl) UpdateLocationOwner(
+ ctx context.Context,
+ req *pb.UpdateLocationOwnerRequest,
+) (*pb.UpdateLocationOwnerResponse, error) {
+ return &pb.UpdateLocationOwnerResponse{
+ LocationUuid: uuid.New().String(),
+ OrganisationId: req.NewOrganisationId,
+ }, nil
+}
+
// CreateObservations implements dp.DataPlatformDataServiceServer.
func (d *DataPlatformDataServiceServerImpl) CreateObservations(
ctx context.Context,
diff --git a/internal/server/postgres/adminserverimpl.go b/internal/server/postgres/adminserverimpl.go
deleted file mode 100644
index 97fa5eb..0000000
--- a/internal/server/postgres/adminserverimpl.go
+++ /dev/null
@@ -1,572 +0,0 @@
-// Package postgres defines server implementation for the DataPlatformServiceServer.
-// This implementation is backed by a PostgreSQL database.
-//
-// Functions and structs for connecting to the database are generated from SQL using
-// the sqlc library, whilst the Server interface that is being implemented comes from
-// the top-level proto definitions.
-//
-// NOTE: I am happy to use MustParse for the UUID handling here as the validation middleware
-// is handling uuid checks upstream.
-package postgres
-
-import (
- "context"
-
- "github.com/google/uuid"
- "github.com/rs/zerolog"
- "google.golang.org/grpc/codes"
- "google.golang.org/grpc/status"
- "google.golang.org/protobuf/types/known/timestamppb"
-
- pb "github.com/openclimatefix/data-platform/internal/gen/ocf/dp"
- ix "github.com/openclimatefix/data-platform/internal/interceptors"
- db "github.com/openclimatefix/data-platform/internal/server/postgres/gen"
-)
-
-// --- Server Implementation ----------------------------------------------------------------------
-
-func NewDataPlatformAdministrationServiceServerImpl() *DataPlatformAdministrationServiceServerImpl {
- return &DataPlatformAdministrationServiceServerImpl{}
-}
-
-// DataPlatformAdministrationServiceServerImpl implements the pb.DataPlatformDataServiceServer interface.
-// It requires the database transaction for the request to be set in the context.
-// It also expects a zerolog logger to be set in the context.
-type DataPlatformAdministrationServiceServerImpl struct{}
-
-func (d *DataPlatformAdministrationServiceServerImpl) DeleteOrganisation(
- ctx context.Context,
- req *pb.DeleteOrganisationRequest,
-) (*pb.DeleteOrganisationResponse, error) {
- l := zerolog.Ctx(ctx)
- querier := db.New(ix.GetTxFromContext(ctx))
-
- dobnParams := db.DeleteOrgByNameParams{
- OrgName: req.OrgName,
- }
-
- err := querier.DeleteOrgByName(ctx, dobnParams)
- if err != nil {
- l.Error().Err(err).Msgf("querier.DeleteOrgByName(%+v)", dobnParams)
-
- return nil, status.Error(
- codes.Internal,
- "Encountered database error. Check application logs.",
- )
- }
-
- return &pb.DeleteOrganisationResponse{}, nil
-}
-
-func (d *DataPlatformAdministrationServiceServerImpl) CreateLocationPolicyGroup(
- ctx context.Context,
- req *pb.CreateLocationPolicyGroupRequest,
-) (*pb.CreateLocationPolicyGroupResponse, error) {
- l := zerolog.Ctx(ctx)
- querier := db.New(ix.GetTxFromContext(ctx))
-
- clpgParams := db.CreateLocationPolicyGroupParams{
- LocationPolicyGroupName: req.Name,
- }
-
- dbGroup, err := querier.CreateLocationPolicyGroup(ctx, clpgParams)
- if err != nil {
- l.Error().Err(err).Msg("Error creating location policy group")
-
- return nil, status.Error(
- codes.Internal,
- "Encountered database error",
- )
- }
-
- l.Debug().
- Str("dp.loc_policygroup.name", dbGroup.LocationPolicyGroupName).
- Str("dp.loc_policygroup.uuid", dbGroup.LocationPolicyGroupUuid.String()).
- Msg("created location policy group")
-
- return &pb.CreateLocationPolicyGroupResponse{
- LocationPolicyGroupId: dbGroup.LocationPolicyGroupUuid.String(),
- Name: dbGroup.LocationPolicyGroupName,
- }, nil
-}
-
-// CreateOrganisation implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) CreateOrganisation(
- ctx context.Context,
- req *pb.CreateOrganisationRequest,
-) (*pb.CreateOrganisationResponse, error) {
- l := zerolog.Ctx(ctx)
- querier := db.New(ix.GetTxFromContext(ctx))
-
- coprms := db.CreateOrgParams{
- OrgName: req.OrgName,
- Metadata: req.Metadata,
- }
-
- dbOrg, err := querier.CreateOrg(ctx, coprms)
- if err != nil {
- l.Error().Err(err).Msgf("querier.CreateOrg(%+v)", coprms)
-
- return nil, status.Error(
- codes.InvalidArgument,
- "Invalid Organisation request. Ensure name is unique and metadata is valid JSON.",
- )
- }
-
- l.Debug().
- Str("dp.organisation.name", dbOrg.OrgName).
- Str("dp.organisation.uuid", dbOrg.OrgUuid.String()).
- Msg("created organisation")
-
- return &pb.CreateOrganisationResponse{
- OrgId: dbOrg.OrgUuid.String(),
- OrgName: dbOrg.OrgName,
- }, nil
-}
-
-// CreateUser implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) CreateUser(
- ctx context.Context,
- req *pb.CreateUserRequest,
-) (*pb.CreateUserResponse, error) {
- l := zerolog.Ctx(ctx)
-
- querier := db.New(ix.GetTxFromContext(ctx))
-
- goParams := db.GetOrgByNameParams{
- OrgName: req.Organisation,
- }
-
- dbOrg, err := querier.GetOrgByName(ctx, goParams)
- if err != nil {
- l.Error().Err(err).Msgf("querier.GetOrgByName(%+v)", goParams)
-
- return nil, status.Errorf(
- codes.NotFound,
- "Organisation with name '%s' not found. Choose an existing organisation, "+
- "or create a new organisation before adding users to it.",
- req.Organisation,
- )
- }
-
- cuParams := db.CreateUserParams{
- OrgUuid: dbOrg.OrgUuid,
- OauthID: req.OauthId,
- Metadata: req.Metadata,
- }
-
- dbUser, err := querier.CreateUser(ctx, cuParams)
- if err != nil {
- l.Error().Err(err).Msgf("querier.CreateUser(%+v)", cuParams)
-
- return nil, status.Error(
- codes.InvalidArgument,
- "Invalid User request. Ensure OAuth ID is of the correct format and metadata is valid JSON.",
- )
- }
-
- l.Debug().
- Str("dp.user.oauthid", dbUser.OauthID).
- Str("dp.user.uuid", dbUser.UserUuid.String()).
- Str("dp.user.org_name", dbOrg.OrgName).
- Msg("created user")
-
- return &pb.CreateUserResponse{
- UserId: dbUser.UserUuid.String(),
- OauthId: dbUser.OauthID,
- }, nil
-}
-
-// DeleteUser implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) DeleteUser(
- ctx context.Context,
- req *pb.DeleteUserRequest,
-) (*pb.DeleteUserResponse, error) {
- l := zerolog.Ctx(ctx)
- querier := db.New(ix.GetTxFromContext(ctx))
-
- userUuid := uuid.MustParse(req.UserId)
- duParams := db.DeleteUserParams{
- UserUuid: userUuid,
- }
-
- err := querier.DeleteUser(ctx, duParams)
- if err != nil {
- l.Error().Err(err).Msgf("querier.DeleteUser(%+v)", duParams)
-
- return nil, status.Errorf(
- codes.NotFound,
- "User with ID '%s' not found",
- req.UserId,
- )
- }
-
- return &pb.DeleteUserResponse{}, nil
-}
-
-// GetLocationPolicyGroup implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) GetLocationPolicyGroup(
- ctx context.Context,
- req *pb.GetLocationPolicyGroupRequest,
-) (*pb.GetLocationPolicyGroupResponse, error) {
- l := zerolog.Ctx(ctx)
- querier := db.New(ix.GetTxFromContext(ctx))
-
- gprms := db.GetLocationPolicyGroupByNameParams{
- LocationPolicyGroupName: req.LocationPolicyGroupName,
- }
-
- dbGroup, err := querier.GetLocationPolicyGroupByName(ctx, gprms)
- if err != nil {
- l.Error().Err(err).Msgf("querier.GetLocationPolicyGroupByName(%+v)", gprms)
-
- return nil, status.Errorf(
- codes.NotFound,
- "No location policy group found with name '%s'",
- req.LocationPolicyGroupName,
- )
- }
-
- llpParams := db.ListLocationPoliciesByGroupParams{
- LocationPolicyGroupUuid: dbGroup.LocationPolicyGroupUuid,
- }
-
- dbPolicies, err := querier.ListLocationPoliciesByGroup(ctx, llpParams)
- if err != nil {
- l.Error().Err(err).Msgf("querier.ListLocationPoliciesByGroup(%+v)", llpParams)
-
- return nil, status.Errorf(
- codes.NotFound,
- "No location policy group found with ID '%s'",
- dbGroup.LocationPolicyGroupUuid,
- )
- }
-
- policies := make([]*pb.LocationPolicy, len(dbPolicies))
- for i, p := range dbPolicies {
- policies[i] = &pb.LocationPolicy{
- LocationId: p.GeometryUuid.String(),
- EnergySource: pb.EnergySource(p.SourceTypeID),
- Permission: pb.Permission(p.PermissionID),
- }
- }
-
- l.Debug().
- Str("dp.loc_policygroup.name", dbGroup.LocationPolicyGroupName).
- Str("dp.loc_policygroup.uuid", dbGroup.LocationPolicyGroupUuid.String()).
- Int("dp.loc_policygroup.count", len(policies)).
- Msg("found location policy group")
-
- return &pb.GetLocationPolicyGroupResponse{
- LocationPolicyGroupId: dbGroup.LocationPolicyGroupUuid.String(),
- Name: dbGroup.LocationPolicyGroupName,
- LocationPolicies: policies,
- }, nil
-}
-
-// AddLocationPoliciesToGroup implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) AddLocationPoliciesToGroup(
- ctx context.Context,
- req *pb.AddLocationPoliciesToGroupRequest,
-) (*pb.AddLocationPoliciesToGroupResponse, error) {
- l := zerolog.Ctx(ctx)
- querier := db.New(ix.GetTxFromContext(ctx))
-
- for _, p := range req.LocationPolicies {
- locUuid := uuid.MustParse(p.LocationId)
- apParams := db.AddLocationPolicesToGroupParams{
- PermissionID: int16(p.Permission),
- SourceTypeID: int16(p.EnergySource),
- LocationPolicyGroupName: req.LocationPolicyGroupName,
- GeometryUuids: []uuid.UUID{locUuid},
- }
-
- err := querier.AddLocationPolicesToGroup(ctx, apParams)
- if err != nil {
- l.Error().Err(err).Msgf("querier.AddLocationPolicesToGroup(%+v)", apParams)
-
- return nil, status.Errorf(
- codes.Internal,
- "No location policy group found with name '%s'",
- req.LocationPolicyGroupName,
- )
- }
- }
-
- l.Debug().
- Str("dp.loc_policygroup.name", req.LocationPolicyGroupName).
- Int("dp.loc_policygroup.added_count", len(req.LocationPolicies)).
- Msg("added location policies to group")
-
- return &pb.AddLocationPoliciesToGroupResponse{}, nil
-}
-
-// RemoveLocationPoliciesFromGroup implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) RemoveLocationPoliciesFromGroup(
- ctx context.Context,
- req *pb.RemoveLocationPoliciesFromGroupRequest,
-) (*pb.RemoveLocationPoliciesFromGroupResponse, error) {
- l := zerolog.Ctx(ctx)
- querier := db.New(ix.GetTxFromContext(ctx))
-
- for _, p := range req.LocationPolicies {
- locUuid := uuid.MustParse(p.LocationId)
- rpParams := db.RemoveLocationPoliciesFromGroupParams{
- PermissionID: int16(p.Permission),
- SourceTypeID: int16(p.EnergySource),
- LocationPolicyGroupName: req.LocationPolicyGroupName,
- GeometryUuid: locUuid,
- }
-
- err := querier.RemoveLocationPoliciesFromGroup(ctx, rpParams)
- if err != nil {
- l.Error().Err(err).Msgf("querier.RemoveLocationPoliciesFromGroup(%+v)", rpParams)
-
- return nil, status.Errorf(
- codes.Internal,
- "No location policy group found with name '%s'",
- req.LocationPolicyGroupName,
- )
- }
- }
-
- l.Debug().
- Str("dp.loc_policygroup.name", req.LocationPolicyGroupName).
- Int("dp.loc_policygroup.removed_count", len(req.LocationPolicies)).
- Msg("removed location policies from group")
-
- return &pb.RemoveLocationPoliciesFromGroupResponse{}, nil
-}
-
-// GetOrganisation implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) GetOrganisation(
- ctx context.Context,
- req *pb.GetOrganisationRequest,
-) (*pb.GetOrganisationResponse, error) {
- l := zerolog.Ctx(ctx)
- querier := db.New(ix.GetTxFromContext(ctx))
-
- goParams := db.GetOrgByNameParams{
- OrgName: req.OrgName,
- }
-
- dbOrg, err := querier.GetOrgByName(ctx, goParams)
- if err != nil {
- l.Error().Err(err).Msgf("querier.GetOrgByName(%+v)", goParams)
-
- return nil, status.Errorf(
- codes.NotFound,
- "Organisation with name '%s' not found",
- req.OrgName,
- )
- }
-
- l.Debug().
- Str("dp.organisation.name", dbOrg.OrgName).
- Str("dp.organisation.uuid", dbOrg.OrgUuid.String()).
- Msg("found organisation")
-
- return &pb.GetOrganisationResponse{
- OrgId: dbOrg.OrgUuid.String(),
- OrgName: dbOrg.OrgName,
- Metadata: dbOrg.Metadata,
- CreatedAt: timestamppb.New(dbOrg.CreatedAtUtc.Time),
- LocationPolicyGroups: dbOrg.LocationPolicyGroupNames,
- UserOauthIds: dbOrg.OauthIds,
- }, nil
-}
-
-// GetUser implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) GetUser(
- ctx context.Context,
- req *pb.GetUserRequest,
-) (*pb.GetUserResponse, error) {
- l := zerolog.Ctx(ctx)
- querier := db.New(ix.GetTxFromContext(ctx))
-
- guParams := db.GetUserByOAuthIDParams{
- OauthID: req.OauthId,
- }
-
- dbUser, err := querier.GetUserByOAuthID(ctx, guParams)
- if err != nil {
- l.Error().Err(err).Msgf("querier.GetUserByOAuthID(%+v)", guParams)
-
- return nil, status.Errorf(
- codes.NotFound,
- "User with OAuth ID '%s' not found",
- req.OauthId,
- )
- }
-
- goParams := db.GetOrgByNameParams{
- OrgName: dbUser.OrgName,
- }
-
- dbOrg, err := querier.GetOrgByName(ctx, goParams)
- if err != nil {
- l.Error().Err(err).Msgf("querier.GetOrgByName(%+v)", goParams)
-
- return nil, status.Errorf(
- codes.Internal,
- "Error fetching organisation for user with OAuth ID '%s'",
- req.OauthId,
- )
- }
-
- l.Debug().
- Str("dp.user.oauthid", dbUser.OauthID).
- Str("dp.user.uuid", dbUser.UserUuid.String()).
- Str("dp.user.org_name", dbUser.OrgName).
- Msg("found user")
-
- return &pb.GetUserResponse{
- UserId: dbUser.UserUuid.String(),
- OauthId: dbUser.OauthID,
- Organisation: dbUser.OrgName,
- LocationPolicyGroups: dbOrg.LocationPolicyGroupNames,
- CreatedAt: ×tamppb.Timestamp{},
- Metadata: dbUser.Metadata,
- }, nil
-}
-
-// AddLocationPolicyGroupToOrganisation implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) AddLocationPolicyGroupToOrganisation(
- ctx context.Context,
- req *pb.AddLocationPolicyGroupToOrganisationRequest,
-) (*pb.AddLocationPolicyGroupToOrganisationResponse, error) {
- l := zerolog.Ctx(ctx)
- querier := db.New(ix.GetTxFromContext(ctx))
-
- agprms := db.AddLocationPolicyGroupToOrgByNamesParams{
- OrgName: req.OrgName,
- LocationPolicyGroupName: req.LocationPolicyGroupName,
- }
-
- err := querier.AddLocationPolicyGroupToOrgByNames(ctx, agprms)
- if err != nil {
- l.Error().Err(err).Msgf("querier.AddLocationPolicyGroupToOrgByNames(%+v)", agprms)
-
- return nil, status.Errorf(
- codes.Internal,
- "Error adding location policy group '%s' to organisation '%s'. "+
- "Ensure organisation and location policy group exist.",
- req.LocationPolicyGroupName,
- req.OrgName,
- )
- }
-
- l.Debug().
- Str("dp.organisation.name", req.OrgName).
- Str("dp.loc_policygroup.name", req.LocationPolicyGroupName).
- Msg("added location policy group to organisation")
-
- return &pb.AddLocationPolicyGroupToOrganisationResponse{}, nil
-}
-
-// RemoveLocationPolicyGroupFromOrganisation implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) RemoveLocationPolicyGroupFromOrganisation(
- ctx context.Context,
- req *pb.RemoveLocationPolicyGroupFromOrganisationRequest,
-) (*pb.RemoveLocationPolicyGroupFromOrganisationResponse, error) {
- l := zerolog.Ctx(ctx)
- querier := db.New(ix.GetTxFromContext(ctx))
-
- rgprms := db.RemoveLocationPolicyGroupFromOrgByNamesParams{
- OrgName: req.OrgName,
- LocationPolicyGroupName: req.LocationPolicyGroupName,
- }
-
- err := querier.RemoveLocationPolicyGroupFromOrgByNames(ctx, rgprms)
- if err != nil {
- l.Error().Err(err).Msgf("querier.RemoveLocationPolicyGroupFromOrgByNames(%+v)", rgprms)
-
- return nil, status.Errorf(
- codes.Internal,
- "Error removing location policy group '%s' from organisation '%s'. "+
- "Ensure organisation and location policy group exist.",
- req.LocationPolicyGroupName,
- req.OrgName,
- )
- }
-
- l.Debug().
- Str("dp.organisation.name", req.OrgName).
- Str("dp.loc_policygroup.name", req.LocationPolicyGroupName).
- Msg("removed location policy group from organisation")
-
- return &pb.RemoveLocationPolicyGroupFromOrganisationResponse{}, nil
-}
-
-// AddUserToOrganisation implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) AddUserToOrganisation(
- ctx context.Context,
- req *pb.AddUserToOrganisationRequest,
-) (*pb.AddUserToOrganisationResponse, error) {
- l := zerolog.Ctx(ctx)
- querier := db.New(ix.GetTxFromContext(ctx))
-
- auprms := db.AddUserToOrgByOAuthIDAndNameParams{
- OrgName: req.OrgName,
- OauthID: req.UserOauthId,
- }
-
- err := querier.AddUserToOrgByOAuthIDAndName(ctx, auprms)
- if err != nil {
- l.Error().Err(err).Msgf("querier.AddUserToOrgByOAuthIDAndName(%+v)", auprms)
-
- return nil, status.Errorf(
- codes.Internal,
- "Error adding user with OAuth ID '%s' to organisation '%s'. "+
- "Ensure organisation and user exist.",
- req.UserOauthId,
- req.OrgName,
- )
- }
-
- l.Debug().
- Str("dp.organisation.name", req.OrgName).
- Str("dp.user.oauthid", req.UserOauthId).
- Msg("added user to organisation")
-
- return &pb.AddUserToOrganisationResponse{}, nil
-}
-
-// RemoveUserFromOrganisation implements dp.DataPlatformAdministrationServiceServer.
-func (d *DataPlatformAdministrationServiceServerImpl) RemoveUserFromOrganisation(
- ctx context.Context,
- req *pb.RemoveUserFromOrganisationRequest,
-) (*pb.RemoveUserFromOrganisationResponse, error) {
- l := zerolog.Ctx(ctx)
- querier := db.New(ix.GetTxFromContext(ctx))
-
- ruprms := db.RemoveUserFromOrgByOAuthIDAndNameParams{
- OrgName: req.OrgName,
- OauthID: req.UserOauthId,
- }
-
- err := querier.RemoveUserFromOrgByOAuthIDAndName(ctx, ruprms)
- if err != nil {
- l.Error().Err(err).Msgf("querier.RemoveUserFromOrgByOAuthIDAndName(%+v)", ruprms)
-
- return nil, status.Errorf(
- codes.Internal,
- "Error removing user with OAuth ID '%s' from organisation '%s'. "+
- "Ensure organisation and user exist.",
- req.UserOauthId,
- req.OrgName,
- )
- }
-
- l.Debug().
- Str("dp.organisation.name", req.OrgName).
- Str("dp.user.oauthid", req.UserOauthId).
- Msg("removed user from organisation")
-
- return &pb.RemoveUserFromOrganisationResponse{}, nil
-}
-
-// Compile-time check to ensure the interface is implemented fully.
-var _ pb.DataPlatformAdministrationServiceServer = (*DataPlatformAdministrationServiceServerImpl)(
- nil,
-)
diff --git a/internal/server/postgres/adminserverimpl_test.go b/internal/server/postgres/adminserverimpl_test.go
deleted file mode 100644
index 9cff6b9..0000000
--- a/internal/server/postgres/adminserverimpl_test.go
+++ /dev/null
@@ -1,631 +0,0 @@
-package postgres
-
-import (
- "fmt"
- "strings"
- "testing"
- "time"
-
- "github.com/google/uuid"
- "github.com/stretchr/testify/require"
- "google.golang.org/protobuf/types/known/structpb"
- "google.golang.org/protobuf/types/known/timestamppb"
-
- pb "github.com/openclimatefix/data-platform/internal/gen/ocf/dp"
-)
-
-func TestCreateOrganisation(t *testing.T) {
- metadata, err := structpb.NewStruct(map[string]any{"source": "test"})
- require.NoError(t, err)
-
- testCases := []struct {
- name string
- createReq *pb.CreateOrganisationRequest
- }{
- {
- name: "Should create organisation",
- createReq: &pb.CreateOrganisationRequest{
- OrgName: "test_create_organisation_1",
- Metadata: metadata,
- },
- },
- {
- name: "Shouldn't create organisation with duplicate name",
- createReq: &pb.CreateOrganisationRequest{
- OrgName: "test_create_organisation_1",
- Metadata: metadata,
- },
- },
- {
- name: "Shouldn't create organisation with empty name",
- createReq: &pb.CreateOrganisationRequest{
- OrgName: "",
- Metadata: metadata,
- },
- },
- {
- name: "Should create another organisation",
- createReq: &pb.CreateOrganisationRequest{
- OrgName: "test_create_organisation_2",
- Metadata: metadata,
- },
- },
- {
- name: "Should create organisation with empty metadata",
- createReq: &pb.CreateOrganisationRequest{
- OrgName: "test_create_organisation_3",
- Metadata: nil,
- },
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- resp, err := ac.CreateOrganisation(t.Context(), tc.createReq)
- if strings.Split(tc.name, " ")[0] == "Shouldn't" {
- require.Error(t, err)
- } else {
- require.NoError(t, err)
-
- // Read back the organisation
- dbOrg, err := ac.GetOrganisation(t.Context(), &pb.GetOrganisationRequest{
- OrgName: resp.OrgName,
- })
- require.NoError(t, err)
-
- require.Equal(t, tc.createReq.OrgName, dbOrg.OrgName)
- require.Equal(t, tc.createReq.Metadata.AsMap(), dbOrg.Metadata.AsMap())
- }
- })
- }
-}
-
-func TestDeleteOrganisation(t *testing.T) {
- metadata, err := structpb.NewStruct(map[string]any{"source": "test"})
- require.NoError(t, err)
-
- orgResp, err := ac.CreateOrganisation(t.Context(), &pb.CreateOrganisationRequest{
- OrgName: "test_delete_organisation_1",
- Metadata: metadata,
- })
-
- require.NoError(t, err)
-
- testCases := []struct {
- name string
- deleteReq *pb.DeleteOrganisationRequest
- }{
- {
- name: "Should delete existing organisation",
- deleteReq: &pb.DeleteOrganisationRequest{
- OrgName: orgResp.OrgName,
- },
- },
- {
- name: "Should handle deleting a non-existent organisation",
- deleteReq: &pb.DeleteOrganisationRequest{
- OrgName: "non_existent_delete_organisation",
- },
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- _, err := ac.DeleteOrganisation(t.Context(), tc.deleteReq)
- if strings.Contains(tc.name, "Shouldn't") {
- require.Error(t, err)
- } else {
- require.NoError(t, err)
- _, err := ac.GetOrganisation(t.Context(), &pb.GetOrganisationRequest{
- OrgName: tc.deleteReq.OrgName,
- })
- require.Error(t, err)
- }
- })
- }
-}
-
-func TestAddRemoveLocationPolicyGroupToOrganisation(t *testing.T) {
- orgResp, err := ac.CreateOrganisation(t.Context(), &pb.CreateOrganisationRequest{
- OrgName: "test_add_remove_location_policy_group_organisation",
- })
- require.NoError(t, err)
- lpResp, err := ac.CreateLocationPolicyGroup(t.Context(), &pb.CreateLocationPolicyGroupRequest{
- Name: "test_add_remove_location_policy_group_policy_group",
- })
- require.NoError(t, err)
-
- testCases := []struct {
- name string
- addRequest *pb.AddLocationPolicyGroupToOrganisationRequest
- removeRequest *pb.RemoveLocationPolicyGroupFromOrganisationRequest
- expectedPolicyGroupCount int
- }{
- {
- name: "Should add location policy group to organisation",
- addRequest: &pb.AddLocationPolicyGroupToOrganisationRequest{
- OrgName: orgResp.OrgName,
- LocationPolicyGroupName: lpResp.Name,
- },
- expectedPolicyGroupCount: 1,
- },
- {
- name: "Should handle adding duplicate location policy groups",
- addRequest: &pb.AddLocationPolicyGroupToOrganisationRequest{
- OrgName: orgResp.OrgName,
- LocationPolicyGroupName: lpResp.Name,
- },
- expectedPolicyGroupCount: 1,
- },
- {
- name: "Should remove location policy group from organisation",
- removeRequest: &pb.RemoveLocationPolicyGroupFromOrganisationRequest{
- OrgName: orgResp.OrgName,
- LocationPolicyGroupName: lpResp.Name,
- },
- expectedPolicyGroupCount: 0,
- },
- {
- name: "Should handle non-existent location policy group removal",
- removeRequest: &pb.RemoveLocationPolicyGroupFromOrganisationRequest{
- OrgName: orgResp.OrgName,
- LocationPolicyGroupName: "non_existent_policy_group",
- },
- expectedPolicyGroupCount: 0,
- },
- {
- name: "Shouldn't add non-existent location policy group to organisation",
- addRequest: &pb.AddLocationPolicyGroupToOrganisationRequest{
- OrgName: orgResp.OrgName,
- LocationPolicyGroupName: "non_existent_policy_group",
- },
- expectedPolicyGroupCount: 0,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if tc.addRequest != nil {
- _, err = ac.AddLocationPolicyGroupToOrganisation(t.Context(), tc.addRequest)
- }
-
- if tc.removeRequest != nil {
- _, err = ac.RemoveLocationPolicyGroupFromOrganisation(t.Context(), tc.removeRequest)
- }
-
- if strings.Contains(tc.name, "Shouldn't") {
- require.Error(t, err)
- } else {
- require.NoError(t, err)
- // Read back the organisation
- dbOrg, err := ac.GetOrganisation(t.Context(), &pb.GetOrganisationRequest{
- OrgName: orgResp.OrgName,
- })
- require.NoError(t, err)
- require.Equal(t, tc.expectedPolicyGroupCount, len(dbOrg.LocationPolicyGroups))
- }
- })
- }
-}
-
-func TestAddRemoveLocationPoliciesFromGroup(t *testing.T) {
- lResp, err := dc.CreateLocation(t.Context(), &pb.CreateLocationRequest{
- LocationName: "test_add_remove_location_policies_location",
- EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR,
- GeometryWkt: "POINT(8.3 33.44)",
- EffectiveCapacityWatts: 5000,
- LocationType: pb.LocationType_LOCATION_TYPE_SITE,
- Metadata: &structpb.Struct{},
- ValidFromUtc: timestamppb.New(time.Now().UTC().Add(-1 * time.Hour)),
- })
- require.NoError(t, err)
- lpResp, err := ac.CreateLocationPolicyGroup(t.Context(), &pb.CreateLocationPolicyGroupRequest{
- Name: "test_add_remove_location_policies_policy_group",
- })
- require.NoError(t, err)
-
- testCases := []struct {
- name string
- addRequest *pb.AddLocationPoliciesToGroupRequest
- removeRequest *pb.RemoveLocationPoliciesFromGroupRequest
- expectedPolicyCount int
- }{
- {
- name: "Should add location policies to group",
- addRequest: &pb.AddLocationPoliciesToGroupRequest{
- LocationPolicyGroupName: lpResp.Name,
- LocationPolicies: []*pb.LocationPolicy{
- {
- LocationId: lResp.LocationUuid,
- EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR,
- Permission: pb.Permission_PERMISSION_READ,
- },
- },
- },
- expectedPolicyCount: 1,
- },
- {
- name: "Should handle adding duplicate location policies",
- addRequest: &pb.AddLocationPoliciesToGroupRequest{
- LocationPolicyGroupName: lpResp.Name,
- LocationPolicies: []*pb.LocationPolicy{
- {
- LocationId: lResp.LocationUuid,
- EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR,
- Permission: pb.Permission_PERMISSION_READ,
- },
- },
- },
- expectedPolicyCount: 1,
- },
- // {
- // name: "Shouldn't add location policy referencing non-existent source",
- // addRequest: &pb.AddLocationPoliciesToGroupRequest{
- // LocationPolicyGroupName: lpResp.Name,
- // LocationPolicies: []*pb.LocationPolicy{
- // {
- // LocationId: lResp.LocationUuid,
- // EnergySource: pb.EnergySource_ENERGY_SOURCE_WIND,
- // Permission: pb.Permission_PERMISSION_READ,
- // },
- // },
- // },
- // expectedPolicyCount: 1,
- // },
- {
- name: "Shouldn't add location policy referencing non-existent location",
- addRequest: &pb.AddLocationPoliciesToGroupRequest{
- LocationPolicyGroupName: lpResp.Name,
- LocationPolicies: []*pb.LocationPolicy{
- {
- LocationId: uuid.New().String(),
- EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR,
- Permission: pb.Permission_PERMISSION_READ,
- },
- },
- },
- expectedPolicyCount: 1,
- },
- {
- name: "Should remove location policies from group",
- removeRequest: &pb.RemoveLocationPoliciesFromGroupRequest{
- LocationPolicyGroupName: lpResp.Name,
- LocationPolicies: []*pb.LocationPolicy{
- {
- LocationId: lResp.LocationUuid,
- EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR,
- Permission: pb.Permission_PERMISSION_READ,
- },
- },
- },
- expectedPolicyCount: 0,
- },
- {
- name: "Should handle non-existent location policy removal",
- removeRequest: &pb.RemoveLocationPoliciesFromGroupRequest{
- LocationPolicyGroupName: lpResp.Name,
- LocationPolicies: []*pb.LocationPolicy{
- {
- LocationId: uuid.New().String(),
- EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR,
- Permission: pb.Permission_PERMISSION_READ,
- },
- },
- },
- expectedPolicyCount: 0,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if tc.addRequest != nil {
- _, err = ac.AddLocationPoliciesToGroup(t.Context(), tc.addRequest)
- }
-
- if tc.removeRequest != nil {
- _, err = ac.RemoveLocationPoliciesFromGroup(t.Context(), tc.removeRequest)
- }
-
- if strings.Contains(tc.name, "Shouldn't") {
- require.Error(t, err)
- } else {
- require.NoError(t, err)
- // Read back the location policy group
- dbLPG, err := ac.GetLocationPolicyGroup(
- t.Context(),
- &pb.GetLocationPolicyGroupRequest{
- LocationPolicyGroupName: lpResp.Name,
- },
- )
- require.NoError(t, err)
- require.Equal(t, tc.expectedPolicyCount, len(dbLPG.LocationPolicies))
- }
- })
- }
-}
-
-func TestCreateUser(t *testing.T) {
- metadata, err := structpb.NewStruct(map[string]any{"source": "test"})
- require.NoError(t, err)
-
- orgResp, err := ac.CreateOrganisation(t.Context(), &pb.CreateOrganisationRequest{
- OrgName: "test_create_user_organisation",
- Metadata: metadata,
- })
- require.NoError(t, err)
-
- testCases := []struct {
- name string
- createReq *pb.CreateUserRequest
- }{
- {
- name: "Should create user",
- createReq: &pb.CreateUserRequest{
- OauthId: "TEST_CREATE_USER_1",
- Organisation: orgResp.OrgName,
- Metadata: metadata,
- },
- },
- {
- name: "Shouldn't create user with duplicate oauth ID",
- createReq: &pb.CreateUserRequest{
- OauthId: "TEST_CREATE_USER_1",
- Organisation: orgResp.OrgName,
- Metadata: metadata,
- },
- },
- {
- name: "Shouldn't create user with empty oauth ID",
- createReq: &pb.CreateUserRequest{
- OauthId: "",
- Organisation: orgResp.OrgName,
- Metadata: metadata,
- },
- },
- {
- name: "Should create another user",
- createReq: &pb.CreateUserRequest{
- OauthId: "TEST_CREATE_USER_2",
- Organisation: orgResp.OrgName,
- Metadata: metadata,
- },
- },
- {
- name: "Should create user with empty metadata",
- createReq: &pb.CreateUserRequest{
- OauthId: "TEST_CREATE_USER_3",
- Organisation: orgResp.OrgName,
- Metadata: nil,
- },
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- _, err := ac.CreateUser(t.Context(), tc.createReq)
- if strings.Split(tc.name, " ")[0] == "Shouldn't" {
- require.Error(t, err)
- } else {
- require.NoError(t, err)
-
- // Read back the user
- dbUser, err := ac.GetUser(t.Context(), &pb.GetUserRequest{
- OauthId: tc.createReq.OauthId,
- })
- require.NoError(t, err)
-
- require.Equal(t, tc.createReq.OauthId, dbUser.OauthId)
- require.Equal(t, tc.createReq.Organisation, dbUser.Organisation)
- require.Equal(t, tc.createReq.Metadata.AsMap(), dbUser.Metadata.AsMap())
- }
- })
- }
-}
-
-func DeleteUser(t *testing.T) {
- metadata, err := structpb.NewStruct(map[string]any{"source": "test"})
- require.NoError(t, err)
-
- orgResp, err := ac.CreateOrganisation(t.Context(), &pb.CreateOrganisationRequest{
- OrgName: "test_delete_user_organisation",
- Metadata: metadata,
- })
- require.NoError(t, err)
-
- createResp, err := ac.CreateUser(t.Context(), &pb.CreateUserRequest{
- OauthId: "TEST_DELETE_USER",
- Organisation: orgResp.OrgName,
- Metadata: metadata,
- })
- require.NoError(t, err)
-
- testCases := []struct {
- name string
- deleteReq *pb.DeleteUserRequest
- }{
- {
- name: "Should delete existing user",
- deleteReq: &pb.DeleteUserRequest{
- UserId: createResp.UserId,
- },
- },
- {
- name: "Shouldn't delete non-existent user",
- deleteReq: &pb.DeleteUserRequest{
- UserId: "non_existent_user_id",
- },
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- _, err := ac.DeleteUser(t.Context(), tc.deleteReq)
- if strings.Split(tc.name, " ")[0] == "Shouldn't" {
- require.Error(t, err)
- } else {
- require.NoError(t, err)
-
- // Try to read back the user
- _, err := ac.GetUser(t.Context(), &pb.GetUserRequest{
- OauthId: "TEST_DELETE_USER",
- })
- // Obviously should error here
- require.Error(t, err)
- }
- })
- }
-}
-
-func TestListLocationsIamFilters(t *testing.T) {
- pivotTime := time.Now().Truncate(time.Minute)
-
- // Create a bunch of locations
- var (
- locationUuids []string
- locationPolicies []*pb.LocationPolicy
- )
-
- for i := range 5 {
- resp, err := dc.CreateLocation(t.Context(), &pb.CreateLocationRequest{
- LocationName: fmt.Sprintf(
- "test_list_locations_site_%02d",
- i,
- ),
- GeometryWkt: fmt.Sprintf("POINT(-5.%d 51.%d)", i, i),
- EffectiveCapacityWatts: uint64(1000000 + i*100),
- EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR,
- LocationType: pb.LocationType_LOCATION_TYPE_GSP,
- ValidFromUtc: timestamppb.New(pivotTime.Add(-time.Hour * 4)),
- Metadata: &structpb.Struct{},
- })
- require.NoError(t, err)
-
- locationUuids = append(locationUuids, resp.LocationUuid)
- locationPolicies = append(locationPolicies, &pb.LocationPolicy{
- LocationId: resp.LocationUuid,
- EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR,
- Permission: pb.Permission_PERMISSION_WRITE,
- })
- }
-
- // Create an organisation with a user and location policies that have write access on the locations
- orgResp, err := ac.CreateOrganisation(t.Context(), &pb.CreateOrganisationRequest{
- OrgName: "test_list_locations_organisation",
- })
- require.NoError(t, err)
- user1Resp, err := ac.CreateUser(t.Context(), &pb.CreateUserRequest{
- OauthId: "TEST_LIST_LOCATIONS_USER",
- Organisation: orgResp.OrgName,
- })
- require.NoError(t, err)
- lpResp, err := ac.CreateLocationPolicyGroup(t.Context(), &pb.CreateLocationPolicyGroupRequest{
- Name: "test_list_locations_policy_group_1",
- })
- require.NoError(t, err)
- _, err = ac.AddLocationPolicyGroupToOrganisation(
- t.Context(),
- &pb.AddLocationPolicyGroupToOrganisationRequest{
- OrgName: orgResp.OrgName,
- LocationPolicyGroupName: lpResp.Name,
- },
- )
- require.NoError(t, err)
- _, err = ac.AddLocationPoliciesToGroup(t.Context(), &pb.AddLocationPoliciesToGroupRequest{
- LocationPolicyGroupName: lpResp.Name,
- LocationPolicies: locationPolicies,
- })
- require.NoError(t, err)
- // Create an organisation with a user and one read policy
- orgResp, err = ac.CreateOrganisation(t.Context(), &pb.CreateOrganisationRequest{
- OrgName: "test_list_locations_organisation_2",
- })
- require.NoError(t, err)
- user2Resp, err := ac.CreateUser(t.Context(), &pb.CreateUserRequest{
- OauthId: "TEST_LIST_LOCATIONS_USER_2",
- Organisation: orgResp.OrgName,
- })
- require.NoError(t, err)
- lpResp, err = ac.CreateLocationPolicyGroup(t.Context(), &pb.CreateLocationPolicyGroupRequest{
- Name: "test_list_locations_policy_group_2",
- })
- require.NoError(t, err)
- _, err = ac.AddLocationPolicyGroupToOrganisation(
- t.Context(),
- &pb.AddLocationPolicyGroupToOrganisationRequest{
- OrgName: orgResp.OrgName,
- LocationPolicyGroupName: lpResp.Name,
- },
- )
- require.NoError(t, err)
- _, err = ac.AddLocationPoliciesToGroup(t.Context(), &pb.AddLocationPoliciesToGroupRequest{
- LocationPolicyGroupName: lpResp.Name,
- LocationPolicies: []*pb.LocationPolicy{
- {
- LocationId: locationUuids[0],
- EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR,
- Permission: pb.Permission_PERMISSION_READ,
- },
- },
- })
- require.NoError(t, err)
-
- permissionFilter := new(pb.Permission)
- *permissionFilter = pb.Permission_PERMISSION_READ
-
- // All tests in the table need to filter by the location uuids just created as the postgres
- // container the tests are run against is reused across the tests for speed of unit testing.
- // As such, it may contain more than just the locations created here, depending on the number of
- // tests being run.
- // TODO: This is a fairly minimal test suite, and I imagine there are plenty of edge cases that
- // are not covered here. This purely covers the basic filtering functionality, and should by
- // improved upon in future.
- tests := []struct {
- name string
- req *pb.ListLocationsRequest
- expectedCount int
- }{
- {
- name: "Should filter locations by user 1",
- req: &pb.ListLocationsRequest{
- UserOauthIdFilter: &user1Resp.OauthId,
- LocationUuidsFilter: locationUuids,
- },
- expectedCount: 5,
- },
- {
- name: "Should filter locations by user 2",
- req: &pb.ListLocationsRequest{
- UserOauthIdFilter: &user2Resp.OauthId,
- LocationUuidsFilter: locationUuids,
- },
- expectedCount: 1,
- },
- {
- name: "Should filter locations by permission",
- req: &pb.ListLocationsRequest{
- PermissionFilter: permissionFilter,
- LocationUuidsFilter: locationUuids,
- },
- expectedCount: 1,
- },
- {
- name: "Should filter locations by user and permission",
- req: &pb.ListLocationsRequest{
- UserOauthIdFilter: &user2Resp.OauthId,
- PermissionFilter: permissionFilter,
- LocationUuidsFilter: locationUuids,
- },
- expectedCount: 1,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- resp, err := dc.ListLocations(t.Context(), tt.req)
- if strings.Contains(tt.name, "Shouldn't") {
- require.Error(t, err)
- } else {
- require.NoError(t, err)
- require.Equal(t, tt.expectedCount, len(resp.Locations))
- }
- })
- }
-}
diff --git a/internal/server/postgres/dataserverimpl.go b/internal/server/postgres/dataserverimpl.go
index 7bb89b8..e8544fb 100644
--- a/internal/server/postgres/dataserverimpl.go
+++ b/internal/server/postgres/dataserverimpl.go
@@ -1505,6 +1505,44 @@ func (s *DataPlatformDataServiceServerImpl) UpdateLocation(
}, nil
}
+func (s *DataPlatformDataServiceServerImpl) UpdateLocationOwner(
+ ctx context.Context,
+ req *pb.UpdateLocationOwnerRequest,
+) (resp *pb.UpdateLocationOwnerResponse, err error) {
+ l := zerolog.Ctx(ctx)
+
+ querier := db.New(ix.GetTxFromContext(ctx))
+
+ params := db.ReownGeometryParams{
+ GeometryUuid: uuid.MustParse(req.LocationUuid),
+ NewOwningEntityExternalID: req.NewOrganisationId,
+ }
+
+ dbGeom, err := querier.ReownGeometry(ctx, params)
+ if err != nil {
+ l.Err(err).Msgf("querier.ReownGeometry(%+v)", params)
+ return nil, status.Errorf(codes.InvalidArgument, "Invalid location UUID: %v", err)
+ }
+
+ l.Debug().
+ Str("dp.geometry.uuid", dbGeom.GeometryUuid.String()).
+ Str("dp.geometry.new_owner_org_id", req.NewOrganisationId).
+ Msg("updated location owner")
+
+ err = querier.RefreshSourcesMaterializedView(ctx)
+ if err != nil {
+ l.Err(err).Msg("querier.RefreshSourcesMaterializedView()")
+ return nil, status.Error(codes.Internal, "Failed to update sources materialised view")
+ }
+
+ l.Debug().Msg("refreshed sources materialised view")
+
+ return &pb.UpdateLocationOwnerResponse{
+ LocationUuid: dbGeom.GeometryUuid.String(),
+ OrganisationId: req.NewOrganisationId,
+ }, nil
+}
+
func (s *DataPlatformDataServiceServerImpl) GetLocationsAsGeoJSON(
ctx context.Context,
req *pb.GetLocationsAsGeoJSONRequest,
@@ -1744,12 +1782,6 @@ func (s *DataPlatformDataServiceServerImpl) ListLocations(
parsedUuids[i] = uuid.MustParse(id)
}
- var permissionId *int16
- if req.PermissionFilter != nil {
- pid := int16(req.PermissionFilter.Number())
- permissionId = &pid
- }
-
var sourceTypeId *int16
if req.EnergySourceFilter != nil {
stid := int16(req.EnergySourceFilter.Number())
@@ -1766,13 +1798,12 @@ func (s *DataPlatformDataServiceServerImpl) ListLocations(
if req.EnclosingLocationUuidFilter != nil {
llprms := db.ListSourcesAtTimestampWithinParams{
- OuterGeometryUuid: uuid.MustParse(*req.EnclosingLocationUuidFilter),
- AtTimestampUtc: pgtype.Timestamp{Time: time.Now().UTC(), Valid: true},
- OauthID: req.UserOauthIdFilter,
- GeometryUuids: parsedUuids,
- PermissionID: permissionId,
- SourceTypeID: sourceTypeId,
- GeometryTypeID: locationTypeId,
+ OuterGeometryUuid: uuid.MustParse(*req.EnclosingLocationUuidFilter),
+ AtTimestampUtc: pgtype.Timestamp{Time: time.Now().UTC(), Valid: true},
+ OwningEntityExternalID: req.OrganisationIdFilter,
+ GeometryUuids: parsedUuids,
+ SourceTypeID: sourceTypeId,
+ GeometryTypeID: locationTypeId,
}
glResp, err := querier.ListSourcesAtTimestampWithin(ctx, llprms)
@@ -1796,13 +1827,12 @@ func (s *DataPlatformDataServiceServerImpl) ListLocations(
}
} else if req.EnclosedLocationUuidFilter != nil {
llprms := db.ListSourcesAtTimestampWithoutParams{
- InnerGeometryUuid: uuid.MustParse(*req.EnclosedLocationUuidFilter),
- AtTimestampUtc: pgtype.Timestamp{Time: time.Now().UTC(), Valid: true},
- OauthID: req.UserOauthIdFilter,
- GeometryUuids: parsedUuids,
- PermissionID: permissionId,
- SourceTypeID: sourceTypeId,
- GeometryTypeID: locationTypeId,
+ InnerGeometryUuid: uuid.MustParse(*req.EnclosedLocationUuidFilter),
+ AtTimestampUtc: pgtype.Timestamp{Time: time.Now().UTC(), Valid: true},
+ OwningEntityExternalID: req.OrganisationIdFilter,
+ GeometryUuids: parsedUuids,
+ SourceTypeID: sourceTypeId,
+ GeometryTypeID: locationTypeId,
}
glResp, err := querier.ListSourcesAtTimestampWithout(ctx, llprms)
@@ -1826,13 +1856,12 @@ func (s *DataPlatformDataServiceServerImpl) ListLocations(
}
} else {
lsprms := db.ListSourcesAtTimestampParams{
- OauthID: req.UserOauthIdFilter,
- GeometryUuids: parsedUuids,
- AtTimestampUtc: pgtype.Timestamp{Time: time.Now().UTC(), Valid: true},
- PermissionID: permissionId,
- SourceTypeID: sourceTypeId,
- GeometryTypeID: locationTypeId,
- GeometryNames: req.LocationNamesFilter,
+ OwningEntityExternalID: req.OrganisationIdFilter,
+ GeometryUuids: parsedUuids,
+ AtTimestampUtc: pgtype.Timestamp{Time: time.Now().UTC(), Valid: true},
+ SourceTypeID: sourceTypeId,
+ GeometryTypeID: locationTypeId,
+ GeometryNames: req.LocationNamesFilter,
}
glResp, err := querier.ListSourcesAtTimestamp(ctx, lsprms)
diff --git a/internal/server/postgres/dataserverimpl_test.go b/internal/server/postgres/dataserverimpl_test.go
index 29c4f7c..98ac857 100644
--- a/internal/server/postgres/dataserverimpl_test.go
+++ b/internal/server/postgres/dataserverimpl_test.go
@@ -91,6 +91,21 @@ func TestCreateLocation(t *testing.T) {
Metadata: metadata,
},
},
+ {
+ name: "Should create location with pipe in the name",
+ req: &pb.CreateLocationRequest{
+ LocationName: "location|with|pipe",
+ EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR,
+ GeometryWkt: "POINT(0.0 51.5)",
+ EffectiveCapacityWatts: 1230,
+ LocationType: pb.LocationType_LOCATION_TYPE_SITE,
+ Metadata: metadata,
+ },
+ expectedLatLng: &pb.LatLng{
+ Latitude: 51.5,
+ Longitude: 0.0,
+ },
+ },
{
name: "Should create location with large capacity",
req: &pb.CreateLocationRequest{
@@ -317,6 +332,84 @@ func TestUpdateLocation(t *testing.T) {
}
}
+func TestUpdateLocationOwner(t *testing.T) {
+ metadata, err := structpb.NewStruct(map[string]any{"source": "test"})
+ require.NoError(t, err)
+ createResp, err := dc.CreateLocation(t.Context(), &pb.CreateLocationRequest{
+ LocationName: "test_update_location_owner_site",
+ GeometryWkt: "POINT(-0.1 51.5)",
+ EffectiveCapacityWatts: 1234e6,
+ Metadata: metadata,
+ EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR,
+ LocationType: pb.LocationType_LOCATION_TYPE_SITE,
+ ValidFromUtc: timestamppb.New(time.Date(2019, 5, 6, 6, 0, 0, 0, time.UTC)),
+ })
+ require.NoError(t, err)
+
+ createResp, err = dc.CreateLocation(t.Context(), &pb.CreateLocationRequest{
+ LocationName: "test_update_location_owner_site_2",
+ GeometryWkt: "POINT(-0.2 51.6)",
+ EffectiveCapacityWatts: 1000e6,
+ Metadata: metadata,
+ EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR,
+ LocationType: pb.LocationType_LOCATION_TYPE_SITE,
+ ValidFromUtc: timestamppb.New(time.Date(2019, 5, 6, 6, 0, 0, 0, time.UTC)),
+ })
+ require.NoError(t, err)
+
+ testcases := []struct {
+ name string
+ newOwner string
+ shouldError bool
+ }{
+ {
+ name: "Should update owner to a new value",
+ newOwner: "first_owner",
+ shouldError: false,
+ },
+ {
+ name: "Should update owner to the same value",
+ newOwner: "first_owner",
+ shouldError: false,
+ },
+ {
+ name: "Should update owner to a different value",
+ newOwner: "second_owner",
+ shouldError: false,
+ },
+ {
+ name: "Should remove owner with empty string",
+ newOwner: "",
+ shouldError: false,
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ _, err := dc.UpdateLocationOwner(t.Context(), &pb.UpdateLocationOwnerRequest{
+ LocationUuid: createResp.LocationUuid,
+ NewOrganisationId: tc.newOwner,
+ })
+ if tc.shouldError {
+ require.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ listResp, err := dc.ListLocations(t.Context(), &pb.ListLocationsRequest{
+ LocationUuidsFilter: []string{createResp.LocationUuid},
+ OrganisationIdFilter: &tc.newOwner,
+ })
+ require.NoError(t, err)
+
+ if tc.newOwner == "" {
+ require.Len(t, listResp.Locations, 0)
+ } else {
+ require.Len(t, listResp.Locations, 1)
+ }
+ }
+ })
+ }
+}
+
func TestCreateUpdateForecaster(t *testing.T) {
testcases := []struct {
name string
diff --git a/internal/server/postgres/package_test.go b/internal/server/postgres/package_test.go
index 295877f..067798a 100644
--- a/internal/server/postgres/package_test.go
+++ b/internal/server/postgres/package_test.go
@@ -26,7 +26,6 @@ import (
var (
// Global gRPC clients for use in tests.
- ac pb.DataPlatformAdministrationServiceClient
dc pb.DataPlatformDataServiceClient
pgConnString string
)
@@ -131,10 +130,8 @@ func setupTestMain(ctx context.Context, m *testing.M) (code int, err error) {
defer lis.Close()
dataServerImpl := NewDataPlatformDataServiceServerImpl()
- adminServerImpl := NewDataPlatformAdministrationServiceServerImpl()
pb.RegisterDataPlatformDataServiceServer(s, dataServerImpl)
- pb.RegisterDataPlatformAdministrationServiceServer(s, adminServerImpl)
go func() {
if err := s.Serve(lis); err != nil {
@@ -160,7 +157,6 @@ func setupTestMain(ctx context.Context, m *testing.M) (code int, err error) {
defer cc.Close()
dc = pb.NewDataPlatformDataServiceClient(cc)
- ac = pb.NewDataPlatformAdministrationServiceClient(cc)
// m.Run() executes the regular, user-defined test functions.
// Any defer statements that have been made will be run after m.Run()
diff --git a/internal/server/postgres/sql/migrations/00008_simple_iam.sql b/internal/server/postgres/sql/migrations/00008_simple_iam.sql
new file mode 100644
index 0000000..65da778
--- /dev/null
+++ b/internal/server/postgres/sql/migrations/00008_simple_iam.sql
@@ -0,0 +1,93 @@
+-- +goose Up
+
+/*
+ * Removes most of the functionality of the IAM setup.
+ *
+ * This is due to a new relience on an external system for user management. The only remaining
+ * element relevant to the data platform is the organistion object.
+ *
+ * Note that the dropping of the IAM schema is permanent and not restored by the migration down.
+ * It is empty in all deployed version of this application and so can and should be safely removed.
+ */
+
+DROP SCHEMA iam CASCADE;
+
+CREATE TABLE IF NOT EXISTS loc.entities (
+ entity_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
+ external_id TEXT NOT NULL
+ CONSTRAINT external_id_format_check CHECK (
+ external_id IS NOT NULL
+ AND LENGTH(external_id) > 0
+ AND LENGTH(external_id) <= 128
+ ),
+ UNIQUE (external_id)
+);
+
+ALTER TABLE loc.geometries
+ ADD COLUMN owning_entity_id INTEGER DEFAULT NULL
+ REFERENCES loc.entities(entity_id)
+ ON UPDATE CASCADE
+ ON DELETE SET NULL;
+
+CREATE INDEX idx_owning_entity_id ON loc.geometries (owning_entity_id);
+
+DROP MATERIALIZED VIEW IF EXISTS loc.sources_mv;
+CREATE MATERIALIZED VIEW loc.sources_mv AS
+SELECT
+ sh.geometry_uuid,
+ sh.source_type_id,
+ sh.capacity_watts,
+ sh.capacity_limit_sip,
+ sh.metadata,
+ COALESCE(sh.metadata || g.metadata, sh.metadata, g.metadata)::JSONB AS metadata_jsonb,
+ g.geometry_name,
+ g.geometry_type_id,
+ g.owning_entity_id,
+ ST_X(g.associated_point)::REAL AS longitude,
+ ST_Y(g.associated_point)::REAL AS latitude,
+ TSRANGE(
+ sh.valid_from_utc,
+ LEAD(sh.valid_from_utc, 1) OVER (
+ PARTITION BY sh.geometry_uuid, sh.source_type_id
+ ORDER BY sh.valid_from_utc
+ )
+ ) AS sys_period
+FROM loc.sources_history AS sh
+INNER JOIN loc.geometries AS g USING (geometry_uuid);
+-- Prevent overlapping records. Required for concurrent refreshes.
+CREATE UNIQUE INDEX ON loc.sources_mv (geometry_uuid, source_type_id, sys_period);
+CREATE INDEX idx_sources_mv_gist_sys_period ON loc.sources_mv USING gist (sys_period);
+CREATE INDEX idx_sources_mv_owning_entity_id ON loc.sources_mv (owning_entity_id);
+
+
+-- +goose Down
+DROP MATERIALIZED VIEW IF EXISTS loc.sources_mv;
+
+ALTER TABLE loc.geometries
+ DROP COLUMN owning_entity_id;
+
+DROP TABLE IF EXISTS loc.entities;
+
+CREATE MATERIALIZED VIEW loc.sources_mv AS
+SELECT
+ sh.geometry_uuid,
+ sh.source_type_id,
+ sh.capacity_watts,
+ sh.capacity_limit_sip,
+ sh.metadata,
+ g.geometry_name,
+ g.geometry_type_id,
+ ST_X(g.associated_point)::REAL AS longitude,
+ ST_Y(g.associated_point)::REAL AS latitude,
+ TSRANGE(
+ sh.valid_from_utc,
+ LEAD(sh.valid_from_utc, 1) OVER (
+ PARTITION BY sh.geometry_uuid, sh.source_type_id
+ ORDER BY sh.valid_from_utc
+ )
+ ) AS sys_period
+FROM loc.sources_history AS sh
+INNER JOIN loc.geometries AS g USING (geometry_uuid);
+CREATE UNIQUE INDEX ON loc.sources_mv (geometry_uuid, source_type_id, sys_period);
+CREATE INDEX ON loc.sources_mv USING gist (sys_period);
+
diff --git a/internal/server/postgres/sql/queries/iam.sql b/internal/server/postgres/sql/queries/iam.sql
deleted file mode 100644
index 3d56a1f..0000000
--- a/internal/server/postgres/sql/queries/iam.sql
+++ /dev/null
@@ -1,196 +0,0 @@
-/*= Queries for the IAM schema ================================================================= */
-
-/*- Org Table -----------------------------------------------------------------------------------*/
-
--- name: CreateOrg :one
-INSERT INTO iam.orgs (org_name, metadata)
-VALUES (
- LOWER(sqlc.arg(org_name)::TEXT),
- CASE WHEN sqlc.arg(metadata)::JSONB = '{}'::JSONB THEN NULL ELSE sqlc.arg(metadata)::JSONB END
-)
-RETURNING org_uuid, org_name, metadata;
-
--- name: UpdateOrg :one
-UPDATE iam.orgs
-SET
- org_name = LOWER(sqlc.arg(new_org_name)::TEXT),
- metadata = CASE WHEN sqlc.arg(metadata)::JSONB = '{}'::JSONB THEN NULL ELSE sqlc.arg(metadata)::JSONB END
-WHERE org_uuid = $1
-RETURNING org_uuid, org_name, metadata;
-
--- name: GetOrgByName :one
-SELECT
- od.org_uuid,
- od.org_name,
- od.created_at_utc,
- od.user_uuids,
- od.oauth_ids,
- od.location_policy_group_uuids,
- od.location_policy_group_names,
- od.metadata
-FROM iam.org_details_v AS od
-WHERE org_name = LOWER(sqlc.arg(org_name)::TEXT);
-
--- name: ListOrgs :many
-SELECT
- org_uuid,
- org_name,
- metadata,
- UUIDV7_EXTRACT_TIMESTAMP(org_uuid)::TIMESTAMP AS created_at_utc
-FROM iam.orgs
-ORDER BY org_name;
-
--- name: DeleteOrgByName :exec
-DELETE FROM iam.orgs
-WHERE org_name = LOWER(sqlc.arg(org_name)::TEXT);
-
--- name: AddUserToOrgByOAuthIDAndName :exec
-INSERT INTO iam.users (org_uuid, oauth_id)
-VALUES (
- (
- SELECT o.org_uuid FROM iam.orgs AS o
- WHERE o.org_name = LOWER(sqlc.arg(org_name)::TEXT)
- ),
- sqlc.arg(oauth_id)::TEXT
-)
-ON CONFLICT DO NOTHING;
-
--- name: RemoveUserFromOrgByOAuthIDAndName :exec
-DELETE FROM iam.users
-WHERE org_uuid = (
- SELECT o.org_uuid FROM iam.orgs AS o
- WHERE o.org_name = LOWER(sqlc.arg(org_name)::TEXT)
- )
- AND oauth_id = sqlc.arg(oauth_id)::TEXT;
-
-/*- Users Table ---------------------------------------------------------------------------------*/
-
--- name: CreateUser :one
-INSERT INTO iam.users (org_uuid, oauth_id, metadata)
-VALUES (
- $1,
- $2,
- CASE WHEN sqlc.arg(metadata)::JSONB = '{}'::JSONB THEN NULL ELSE sqlc.arg(metadata)::JSONB END
-)
-RETURNING user_uuid, org_uuid, oauth_id, metadata;
-
--- name: GetUserByOAuthID :one
-SELECT
- u.user_uuid,
- u.org_uuid,
- o.org_name,
- u.oauth_id,
- u.metadata,
- UUIDV7_EXTRACT_TIMESTAMP(u.user_uuid)::TIMESTAMP AS created_at_utc
-FROM iam.orgs AS o
- INNER JOIN iam.users AS u USING (org_uuid)
-WHERE u.oauth_id = $1;
-
--- name: DeleteUser :exec
-DELETE FROM iam.users
-WHERE user_uuid = $1;
-
-/*- Location Policy Groups ----------------------------------------------------------------------*/
-
--- name: CreateLocationPolicyGroup :one
-INSERT INTO iam.location_policy_groups (location_policy_group_name)
-VALUES ($1)
-RETURNING location_policy_group_uuid, location_policy_group_name;
-
--- name: UpdateLocationPolicyGroup :one
-UPDATE iam.location_policy_groups
-SET location_policy_group_name = $2
-WHERE location_policy_group_uuid = $1
-RETURNING location_policy_group_uuid, location_policy_group_name;
-
--- name: GetLocationPolicyGroupByUUID :one
-SELECT
- location_policy_group_uuid,
- location_policy_group_name
-FROM iam.location_policy_groups
-WHERE location_policy_group_uuid = $1;
-
--- name: GetLocationPolicyGroupByName :one
-SELECT
- location_policy_group_uuid,
- location_policy_group_name
-FROM iam.location_policy_groups
-WHERE location_policy_group_name = LOWER(sqlc.arg(location_policy_group_name)::TEXT);
-
--- name: ListLocationPolicyGroups :many
-SELECT
- location_policy_group_uuid,
- location_policy_group_name
-FROM iam.location_policy_groups
-ORDER BY location_policy_group_name;
-
--- name: DeleteLocationPolicyGroup :exec
-DELETE FROM iam.location_policy_groups
-WHERE location_policy_group_uuid = $1;
-
--- name: AddLocationPolicyGroupToOrgByNames :exec
-INSERT INTO iam.org_location_policy_groups (org_uuid, location_policy_group_uuid)
-VALUES (
- (
- SELECT o.org_uuid FROM iam.orgs AS o
- WHERE o.org_name = LOWER(sqlc.arg(org_name)::TEXT)
- ),
- (
- SELECT lpg.location_policy_group_uuid FROM iam.location_policy_groups AS lpg
- WHERE lpg.location_policy_group_name = LOWER(sqlc.arg(location_policy_group_name)::TEXT)
- )
-)
-ON CONFLICT DO NOTHING;
-
--- name: RemoveLocationPolicyGroupFromOrgByNames :exec
-DELETE FROM iam.org_location_policy_groups
-WHERE org_uuid = (
- SELECT o.org_uuid FROM iam.orgs AS o
- WHERE o.org_name = LOWER(sqlc.arg(org_name)::TEXT)
- )
- AND location_policy_group_uuid = (
- SELECT lpg.location_policy_group_uuid FROM iam.location_policy_groups AS lpg
- WHERE lpg.location_policy_group_name = LOWER(sqlc.arg(location_policy_group_name)::TEXT)
- );
-
-/*- Location Policies ---------------------------------------------------------------------------*/
-
--- name: ListLocationPoliciesByGroup :many
-SELECT
- lp.permission_id,
- r.permission_name,
- lp.source_type_id,
- st.source_type_name,
- lp.geometry_uuid,
- lp.location_policy_group_uuid
-FROM iam.location_policies AS lp
- INNER JOIN iam.permissions AS r USING (permission_id)
- INNER JOIN loc.source_types AS st USING (source_type_id)
-WHERE lp.location_policy_group_uuid = $1;
-
--- name: AddLocationPolicesToGroup :exec
-INSERT INTO iam.location_policies (
- permission_id,
- source_type_id,
- geometry_uuid,
- location_policy_group_uuid
-) SELECT
- $1,
- $2,
- loc_uuid,
- (
- SELECT lpg.location_policy_group_uuid FROM iam.location_policy_groups AS lpg
- WHERE lpg.location_policy_group_name = sqlc.arg(location_policy_group_name)::TEXT
- )
-FROM UNNEST(ARRAY[sqlc.arg(geometry_uuids)::UUID []]) AS t (loc_uuid)
-ON CONFLICT DO NOTHING;
-
--- name: RemoveLocationPoliciesFromGroup :exec
-DELETE FROM iam.location_policies
-WHERE location_policy_group_uuid = (
- SELECT lpg.location_policy_group_uuid FROM iam.location_policy_groups AS lpg
- WHERE lpg.location_policy_group_name = sqlc.arg(location_policy_group_name)::TEXT
- )
- AND geometry_uuid = $1
- AND source_type_id = $2
- AND permission_id = $3;
diff --git a/internal/server/postgres/sql/queries/locations.sql b/internal/server/postgres/sql/queries/locations.sql
index 7dd9c47..41e34a4 100644
--- a/internal/server/postgres/sql/queries/locations.sql
+++ b/internal/server/postgres/sql/queries/locations.sql
@@ -21,6 +21,36 @@ WHERE l.geometry_uuid = $1
RETURNING
l.geometry_uuid, l.geometry_name, ST_X(l.associated_point)::REAL AS longitude, ST_Y(l.associated_point)::REAL AS latitude;
+-- name: ReownGeometry :one
+/* ReownGeometry assigns a new owning_entity_id to a geometry.
+ * To keep maintenance down to a minimum, it creates any missing entities on the fly.
+* If the input external_id is an empty string, the owning_entity_id is set to NULL.
+ */
+WITH input AS (
+ SELECT NULLIF(sqlc.arg(new_owning_entity_external_id)::TEXT, '') AS ext_id
+),
+target_entity AS (
+ INSERT INTO loc.entities (external_id)
+ SELECT ext_id FROM input
+ WHERE ext_id IS NOT NULL
+ ON CONFLICT (external_id) DO NOTHING
+ RETURNING entity_id
+),
+selected_entity AS (
+ SELECT entity_id FROM target_entity
+ UNION ALL
+ SELECT e.entity_id
+ FROM loc.entities AS e
+ INNER JOIN input AS i ON e.external_id = i.ext_id
+ LIMIT 1
+)
+UPDATE loc.geometries AS l
+SET owning_entity_id = (SELECT entity_id FROM selected_entity)
+WHERE l.geometry_uuid = $1
+RETURNING
+ l.geometry_uuid,
+ l.owning_entity_id;
+
-- name: GetGeometryWKB :one
/* GetGeometryWKB returns the geometries in WKB format for the given geometry UUIDs. */
SELECT
@@ -63,15 +93,15 @@ SELECT
s.capacity_limit_sip,
s.source_type_id,
s.geometry_uuid,
- l.geometry_name,
+ s.geometry_name,
s.sys_period,
- ST_X(l.associated_point)::REAL AS longitude,
- ST_Y(l.associated_point)::REAL AS latitude,
- COALESCE(s.metadata || l.metadata, s.metadata, l.metadata)::JSONB AS metadata_jsonb
+ s.longitude,
+ s.latitude,
+ s.owning_entity_id,
+ s.metadata_jsonb
FROM loc.sources_mv AS s
- INNER JOIN loc.geometries AS l USING (geometry_uuid)
WHERE
- l.geometry_uuid = $1
+ s.geometry_uuid = $1
AND s.source_type_id = $2
AND s.sys_period @> sqlc.arg(at_timestamp_utc)::TIMESTAMP;
@@ -161,33 +191,29 @@ ORDER BY valid_from_utc ASC;
-- name: ListSourcesAtTimestamp :many
/* ListSourcesAtTimestamp returns all sources that match the given filters.
- * It uses left joins to include geometries even if there are no associated policies, to allow the
- * caller to not include permission-based filtering.
*/
WITH unfiltered_sources AS (
SELECT
- u.oauth_id,
- lp.permission_id,
ls.source_type_id,
ls.geometry_uuid,
ls.capacity_watts,
ls.capacity_limit_sip,
- l.geometry_name,
- l.geometry_type_id,
- ST_X(l.associated_point)::REAL AS longitude,
- ST_Y(l.associated_point)::REAL AS latitude,
- COALESCE(l.metadata || ls.metadata, l.metadata, ls.metadata)::JSONB AS metadata_jsonb
+ ls.geometry_name,
+ ls.geometry_type_id,
+ ls.owning_entity_id,
+ ls.longitude,
+ ls.latitude,
+ ls.metadata_jsonb
FROM loc.sources_mv AS ls
- INNER JOIN loc.geometries AS l USING (geometry_uuid)
- LEFT OUTER JOIN iam.location_policies AS lp USING (geometry_uuid, source_type_id)
- LEFT OUTER JOIN iam.org_location_policy_groups USING (location_policy_group_uuid)
- LEFT OUTER JOIN iam.users AS u USING (org_uuid)
WHERE ls.sys_period @> sqlc.arg(at_timestamp_utc)::TIMESTAMP
)
SELECT *
FROM unfiltered_sources AS us
WHERE
- (sqlc.narg(source_type_id)::SMALLINT IS NULL OR us.source_type_id = sqlc.narg(source_type_id)::SMALLINT)
+ (
+ sqlc.narg(source_type_id)::SMALLINT IS NULL
+ OR us.source_type_id = sqlc.narg(source_type_id)::SMALLINT
+ )
AND (
ARRAY_LENGTH(sqlc.arg(geometry_uuids)::UUID [], 1) IS NULL
OR us.geometry_uuid = ANY(sqlc.arg(geometry_uuids)::UUID [])
@@ -196,112 +222,121 @@ WHERE
ARRAY_LENGTH(sqlc.arg(geometry_names)::TEXT [], 1) IS NULL
OR us.geometry_name = ANY(sqlc.arg(geometry_names)::TEXT [])
)
- AND (sqlc.narg(geometry_type_id)::SMALLINT IS NULL OR us.geometry_type_id = sqlc.narg(geometry_type_id)::SMALLINT)
- AND (sqlc.narg(oauth_id)::TEXT IS NULL OR us.oauth_id = sqlc.arg(oauth_id)::TEXT)
- AND (sqlc.narg(permission_id)::SMALLINT IS NULL OR us.permission_id = sqlc.narg(permission_id)::SMALLINT);
+ AND (
+ sqlc.narg(geometry_type_id)::SMALLINT IS NULL
+ OR us.geometry_type_id = sqlc.narg(geometry_type_id)::SMALLINT
+ )
+ AND (
+ sqlc.narg(owning_entity_external_id)::TEXT IS NULL
+ OR us.owning_entity_id = (
+ SELECT entity_id
+ FROM loc.entities
+ WHERE external_id = sqlc.narg(owning_entity_external_id)::TEXT
+ )
+ );
-- name: ListSourcesAtTimestampWithin :many
/* ListSourcesAtTimestampWithin returns all sources that match the given filters
* and are within a given geometry.
- * This has to be seperated from the ListSourcesAtTimestamp query due to the spatial join.
*/
-WITH contained_geometries AS (
- SELECT
- l.geometry_uuid,
- l.geometry_name,
- l.geometry_type_id,
- l.geom,
- l.associated_point,
- l.metadata
- FROM loc.geometries AS l
- INNER JOIN
- loc.geometries AS l_outer ON ST_WITHIN(
- l.geom,
- l_outer.geom
- ) AND l_outer.geometry_uuid = sqlc.arg(outer_geometry_uuid)::UUID
- AND l.geometry_uuid <> sqlc.arg(outer_geometry_uuid)::UUID
+WITH target_outer AS (
+ SELECT geom
+ FROM loc.geometries
+ WHERE geometry_uuid = sqlc.arg(outer_geometry_uuid)::UUID
),
unfiltered_sources AS (
SELECT
- u.oauth_id,
- lp.permission_id,
ls.source_type_id,
ls.geometry_uuid,
ls.capacity_watts,
ls.capacity_limit_sip,
- l.geometry_name,
- l.geometry_type_id,
- ST_X(l.associated_point)::REAL AS longitude,
- ST_Y(l.associated_point)::REAL AS latitude,
- COALESCE(l.metadata || ls.metadata, l.metadata, ls.metadata)::JSONB AS metadata_jsonb
+ ls.geometry_name,
+ ls.geometry_type_id,
+ ls.longitude,
+ ls.latitude,
+ ls.owning_entity_id,
+ ls.metadata_jsonb
FROM loc.sources_mv AS ls
- INNER JOIN contained_geometries AS l USING (geometry_uuid)
- LEFT OUTER JOIN iam.location_policies AS lp USING (geometry_uuid, source_type_id)
- LEFT OUTER JOIN iam.org_location_policy_groups USING (location_policy_group_uuid)
- LEFT OUTER JOIN iam.users AS u USING (org_uuid)
+ INNER JOIN loc.geometries AS l USING (geometry_uuid)
WHERE ls.sys_period @> sqlc.arg(at_timestamp_utc)::TIMESTAMP
+ AND ls.geometry_uuid <> sqlc.arg(outer_geometry_uuid)::UUID
+ AND ST_WITHIN(l.geom, (SELECT geom FROM target_outer))
)
SELECT *
FROM unfiltered_sources AS us
WHERE
- (sqlc.narg(source_type_id)::SMALLINT IS NULL OR us.source_type_id = sqlc.narg(source_type_id)::SMALLINT)
+ (
+ sqlc.narg(source_type_id)::SMALLINT IS NULL
+ OR us.source_type_id = sqlc.narg(source_type_id)::SMALLINT
+ )
AND (
ARRAY_LENGTH(sqlc.arg(geometry_uuids)::UUID [], 1) IS NULL
OR us.geometry_uuid = ANY(sqlc.arg(geometry_uuids)::UUID [])
)
- AND (sqlc.narg(geometry_type_id)::SMALLINT IS NULL OR us.geometry_type_id = sqlc.narg(geometry_type_id)::SMALLINT)
- AND (sqlc.narg(oauth_id)::TEXT IS NULL OR us.oauth_id = sqlc.arg(oauth_id)::TEXT)
- AND (sqlc.narg(permission_id)::SMALLINT IS NULL OR us.permission_id = sqlc.narg(permission_id)::SMALLINT);
+ AND (
+ ARRAY_LENGTH(sqlc.arg(geometry_names)::TEXT [], 1) IS NULL
+ OR us.geometry_name = ANY(sqlc.arg(geometry_names)::TEXT [])
+ )
+ AND (
+ sqlc.narg(geometry_type_id)::SMALLINT IS NULL
+ OR us.geometry_type_id = sqlc.narg(geometry_type_id)::SMALLINT
+ )
+ AND (
+ sqlc.narg(owning_entity_external_id)::TEXT IS NULL
+ OR us.owning_entity_id = (
+ SELECT entity_id
+ FROM loc.entities
+ WHERE external_id = sqlc.narg(owning_entity_external_id)::TEXT
+ )
+ );
-- name: ListSourcesAtTimestampWithout :many
/* ListSourcesAtTimestampWithout returns all sources that match the given filters
- * and contain a given geometry.
- * This has to be seperated from the ListSourcesAtTimestamp query due to the spatial join.
+ * and contain a given geometry's associated point.
*/
-WITH containing_geometries AS (
- SELECT
- l.geometry_uuid,
- l.geometry_name,
- l.geometry_type_id,
- l.geom,
- l.associated_point,
- l.metadata
- FROM loc.geometries AS l
- INNER JOIN
- loc.geometries AS l_inner ON ST_WITHIN(
- l_inner.associated_point,
- l.geom
- ) AND l_inner.geometry_uuid = sqlc.arg(inner_geometry_uuid)::UUID
- AND l.geometry_uuid <> sqlc.arg(inner_geometry_uuid)::UUID
+WITH target_inner AS (
+ SELECT associated_point
+ FROM loc.geometries
+ WHERE geometry_uuid = sqlc.arg(inner_geometry_uuid)::UUID
),
unfiltered_sources AS (
SELECT
- u.oauth_id,
- lp.permission_id,
ls.source_type_id,
ls.geometry_uuid,
ls.capacity_watts,
ls.capacity_limit_sip,
- l.geometry_name,
- l.geometry_type_id,
- ST_X(l.associated_point)::REAL AS longitude,
- ST_Y(l.associated_point)::REAL AS latitude,
- COALESCE(l.metadata || ls.metadata, l.metadata, ls.metadata)::JSONB AS metadata_jsonb
+ ls.geometry_name,
+ ls.geometry_type_id,
+ ls.longitude,
+ ls.latitude,
+ ls.owning_entity_id,
+ ls.metadata_jsonb
FROM loc.sources_mv AS ls
- INNER JOIN containing_geometries AS l USING (geometry_uuid)
- LEFT OUTER JOIN iam.location_policies AS lp USING (geometry_uuid, source_type_id)
- LEFT OUTER JOIN iam.org_location_policy_groups USING (location_policy_group_uuid)
- LEFT OUTER JOIN iam.users AS u USING (org_uuid)
+ INNER JOIN loc.geometries AS l USING (geometry_uuid)
WHERE ls.sys_period @> sqlc.arg(at_timestamp_utc)::TIMESTAMP
+ AND ls.geometry_uuid <> sqlc.arg(inner_geometry_uuid)::UUID
+ AND ST_CONTAINS(l.geom, (SELECT associated_point FROM target_inner))
)
SELECT *
FROM unfiltered_sources AS us
WHERE
- (sqlc.narg(source_type_id)::SMALLINT IS NULL OR us.source_type_id = sqlc.narg(source_type_id)::SMALLINT)
+ (
+ sqlc.narg(source_type_id)::SMALLINT IS NULL
+ OR us.source_type_id = sqlc.narg(source_type_id)::SMALLINT
+ )
AND (
ARRAY_LENGTH(sqlc.arg(geometry_uuids)::UUID [], 1) IS NULL
OR us.geometry_uuid = ANY(sqlc.arg(geometry_uuids)::UUID [])
)
- AND (sqlc.narg(geometry_type_id)::SMALLINT IS NULL OR us.geometry_type_id = sqlc.narg(geometry_type_id)::SMALLINT)
- AND (sqlc.narg(oauth_id)::TEXT IS NULL OR us.oauth_id = sqlc.arg(oauth_id)::TEXT)
- AND (sqlc.narg(permission_id)::SMALLINT IS NULL OR us.permission_id = sqlc.narg(permission_id)::SMALLINT);
+ AND (
+ sqlc.narg(geometry_type_id)::SMALLINT IS NULL
+ OR us.geometry_type_id = sqlc.narg(geometry_type_id)::SMALLINT
+ )
+ AND (
+ sqlc.narg(owning_entity_external_id)::TEXT IS NULL
+ OR us.owning_entity_id = (
+ SELECT entity_id
+ FROM loc.entities
+ WHERE external_id = sqlc.narg(owning_entity_external_id)::TEXT
+ )
+ );
diff --git a/proto/ocf/dp/dp-admin.messages.proto b/proto/ocf/dp/dp-admin.messages.proto
deleted file mode 100644
index 286bc46..0000000
--- a/proto/ocf/dp/dp-admin.messages.proto
+++ /dev/null
@@ -1,213 +0,0 @@
-syntax = "proto3";
-
-package ocf.dp;
-
-import "google/protobuf/timestamp.proto";
-import "google/protobuf/struct.proto";
-import "buf/validate/validate.proto";
-import "ocf/dp/dp.common.proto";
-
-option go_package = "github.com/openclimatefix/data-platform/internal/protogen/ocf/dp;dp";
-
-
-message LocationPolicy {
- string location_id = 1 [
- (buf.validate.field).string.uuid = true
- ];
- EnergySource energy_source = 2;
- Permission permission = 3;
-}
-
-message CreateUserRequest {
- string oauth_id = 1 [
- (buf.validate.field).string.min_len = 5,
- (buf.validate.field).string.max_len = 128
- ];
- string organisation = 2 [
- (buf.validate.field).string.min_len = 2,
- (buf.validate.field).string.max_len = 128
- ];
- google.protobuf.Struct metadata = 3;
-}
-
-message CreateUserResponse {
- string user_id = 1 [
- (buf.validate.field).string.uuid = true
- ];
- string oauth_id = 2;
-}
-
-
-message GetUserRequest {
- string oauth_id = 1 [
- (buf.validate.field).string.min_len = 5,
- (buf.validate.field).string.max_len = 128
- ];
-}
-
-message GetUserResponse {
- string user_id = 1 [
- (buf.validate.field).string.uuid = true
- ];
- string oauth_id = 2;
- string organisation = 3;
- repeated string location_policy_groups = 4;
- google.protobuf.Timestamp created_at = 5;
- google.protobuf.Struct metadata = 6;
-}
-
-
-message DeleteUserRequest {
- string user_id = 1 [
- (buf.validate.field).string.uuid = true
- ];
-}
-
-message DeleteUserResponse {}
-
-message CreateOrganisationRequest {
- string org_name = 1 [
- (buf.validate.field).string.min_len = 2,
- (buf.validate.field).string.max_len = 128
- ];
- google.protobuf.Struct metadata = 2;
-}
-
-message CreateOrganisationResponse {
- string org_id = 1 [
- (buf.validate.field).string.uuid = true
- ];
- string org_name = 2;
-}
-
-
-message GetOrganisationRequest {
- string org_name = 1 [
- (buf.validate.field).string.min_len = 2,
- (buf.validate.field).string.max_len = 128
- ];
-}
-
-message GetOrganisationResponse {
- string org_id = 1 [
- (buf.validate.field).string.uuid = true
- ];
- string org_name = 2;
- google.protobuf.Struct metadata = 3;
- google.protobuf.Timestamp created_at = 4;
- repeated string location_policy_groups = 5;
- repeated string user_oauth_ids = 6;
-}
-
-message DeleteOrganisationRequest {
- string org_name = 1 [
- (buf.validate.field).string.min_len = 2,
- (buf.validate.field).string.max_len = 128
- ];
-}
-
-message DeleteOrganisationResponse {}
-
-message AddLocationPolicyGroupToOrganisationRequest {
- string org_name = 1 [
- (buf.validate.field).string.min_len = 2,
- (buf.validate.field).string.max_len = 128
- ];
- string location_policy_group_name = 2 [
- (buf.validate.field).string.min_len = 2,
- (buf.validate.field).string.max_len = 128
- ];
-}
-
-message AddLocationPolicyGroupToOrganisationResponse {}
-
-
-message RemoveLocationPolicyGroupFromOrganisationRequest {
- string org_name = 1 [
- (buf.validate.field).string.min_len = 2,
- (buf.validate.field).string.max_len = 128
- ];
- string location_policy_group_name = 2 [
- (buf.validate.field).string.min_len = 2,
- (buf.validate.field).string.max_len = 128
- ];
-}
-
-message RemoveLocationPolicyGroupFromOrganisationResponse {}
-
-
-message AddUserToOrganisationRequest {
- string org_name = 1 [
- (buf.validate.field).string.min_len = 2,
- (buf.validate.field).string.max_len = 128
- ];
- string user_oauth_id = 2 [
- (buf.validate.field).string.min_len = 5,
- (buf.validate.field).string.max_len = 128
- ];
-}
-
-message AddUserToOrganisationResponse {}
-
-message RemoveUserFromOrganisationRequest {
- string org_name = 1 [
- (buf.validate.field).string.min_len = 2,
- (buf.validate.field).string.max_len = 128
- ];
- string user_oauth_id = 2 [
- (buf.validate.field).string.min_len = 5,
- (buf.validate.field).string.max_len = 128
- ];
-}
-
-message RemoveUserFromOrganisationResponse {}
-
-
-message CreateLocationPolicyGroupRequest {
- string name = 1 [
- (buf.validate.field).string.min_len = 2,
- (buf.validate.field).string.max_len = 128
- ];
-}
-
-message CreateLocationPolicyGroupResponse {
- string location_policy_group_id = 1;
- string name = 2;
-}
-
-message GetLocationPolicyGroupRequest {
- string location_policy_group_name = 1 [
- (buf.validate.field).string.min_len = 2,
- (buf.validate.field).string.max_len = 128
- ];
-}
-
-message GetLocationPolicyGroupResponse {
- string location_policy_group_id = 1;
- string name = 2;
- repeated LocationPolicy location_policies = 3;
-}
-
-message AddLocationPoliciesToGroupRequest {
- string location_policy_group_name = 1 [
- (buf.validate.field).string.min_len = 2,
- (buf.validate.field).string.max_len = 128
- ];
- repeated LocationPolicy location_policies = 2 [
- (buf.validate.field).repeated.max_items = 10000
- ];
-}
-
-message AddLocationPoliciesToGroupResponse {}
-
-message RemoveLocationPoliciesFromGroupRequest {
- string location_policy_group_name = 1 [
- (buf.validate.field).string.min_len = 2,
- (buf.validate.field).string.max_len = 128
- ];
- repeated LocationPolicy location_policies = 2 [
- (buf.validate.field).repeated.max_items = 10000
- ];
-}
-
-message RemoveLocationPoliciesFromGroupResponse {}
diff --git a/proto/ocf/dp/dp-admin.service.proto b/proto/ocf/dp/dp-admin.service.proto
deleted file mode 100644
index 08e126f..0000000
--- a/proto/ocf/dp/dp-admin.service.proto
+++ /dev/null
@@ -1,28 +0,0 @@
-syntax = "proto3";
-
-package ocf.dp;
-
-import "ocf/dp/dp-admin.messages.proto";
-
-option go_package = "github.com/openclimatefix/data-platform/internal/protogen/ocf/dp;dp";
-
-service DataPlatformAdministrationService {
-
- rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {}
- rpc GetUser(GetUserRequest) returns (GetUserResponse) {}
- rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse) {}
-
- rpc CreateOrganisation(CreateOrganisationRequest) returns (CreateOrganisationResponse) {}
- rpc GetOrganisation(GetOrganisationRequest) returns (GetOrganisationResponse) {}
- rpc DeleteOrganisation(DeleteOrganisationRequest) returns (DeleteOrganisationResponse) {}
- rpc AddUserToOrganisation(AddUserToOrganisationRequest) returns (AddUserToOrganisationResponse) {}
- rpc RemoveUserFromOrganisation(RemoveUserFromOrganisationRequest) returns (RemoveUserFromOrganisationResponse) {}
- rpc AddLocationPolicyGroupToOrganisation(AddLocationPolicyGroupToOrganisationRequest) returns (AddLocationPolicyGroupToOrganisationResponse) {}
- rpc RemoveLocationPolicyGroupFromOrganisation(RemoveLocationPolicyGroupFromOrganisationRequest) returns (RemoveLocationPolicyGroupFromOrganisationResponse) {}
-
- rpc CreateLocationPolicyGroup(CreateLocationPolicyGroupRequest) returns (CreateLocationPolicyGroupResponse) {}
- rpc GetLocationPolicyGroup(GetLocationPolicyGroupRequest) returns (GetLocationPolicyGroupResponse) {}
- rpc AddLocationPoliciesToGroup(AddLocationPoliciesToGroupRequest) returns (AddLocationPoliciesToGroupResponse) {}
- rpc RemoveLocationPoliciesFromGroup(RemoveLocationPoliciesFromGroupRequest) returns (RemoveLocationPoliciesFromGroupResponse) {}
-
-}
diff --git a/proto/ocf/dp/dp-data.messages.proto b/proto/ocf/dp/dp-data.messages.proto
index 914b66a..edcf2ba 100644
--- a/proto/ocf/dp/dp-data.messages.proto
+++ b/proto/ocf/dp/dp-data.messages.proto
@@ -558,6 +558,22 @@ message UpdateLocationResponse {
uint64 effective_capacity_watts = 3;
}
+
+message UpdateLocationOwnerRequest {
+ string location_uuid = 1 [
+ (buf.validate.field).required = true,
+ (buf.validate.field).string.uuid = true
+ ];
+ string new_organisation_id = 3 [
+ (buf.validate.field).string.max_len = 128
+ ];
+}
+
+message UpdateLocationOwnerResponse {
+ string location_uuid = 1;
+ string organisation_id = 2;
+}
+
message GetLocationsAsGeoJSONRequest {
repeated string location_uuids = 1 [
(buf.validate.field).repeated.min_items = 1,
@@ -734,12 +750,10 @@ message ListLocationsRequest {
string: {uuid: true}
}
];
- // Optional filter to only return locations belonging to a specific user.
- optional string user_oauth_id_filter = 4 [
+ // Optional filter to only return locations belonging to a specific organisation.
+ optional string organisation_id_filter = 4 [
(buf.validate.field).string.max_len = 128
];
- // Optional filter to only return locations for which the user has a specific permission.
- optional Permission permission_filter = 5;
// Optional filter to only return locations enclosed within a specific location.
optional string enclosing_location_uuid_filter = 6 [
(buf.validate.field).string.uuid = true
@@ -836,4 +850,3 @@ message ForecastDatum {
map metadata = 9;
google.protobuf.Timestamp target_timestamp_utc = 10;
}
-
diff --git a/proto/ocf/dp/dp-data.service.proto b/proto/ocf/dp/dp-data.service.proto
index 244efcf..a3bc40e 100644
--- a/proto/ocf/dp/dp-data.service.proto
+++ b/proto/ocf/dp/dp-data.service.proto
@@ -36,6 +36,9 @@ service DataPlatformDataService {
rpc CreateLocation(CreateLocationRequest) returns (CreateLocationResponse) {}
/* UpdateLocation modifies various attributes associated with a given location. */
rpc UpdateLocation(UpdateLocationRequest) returns (UpdateLocationResponse) {}
+ /* UpdateLocationOwner changes the ownership of a location. */
+ rpc UpdateLocationOwner(UpdateLocationOwnerRequest) returns (UpdateLocationOwnerResponse) {}
+
/* ListLocations fetches a list of registered locations that match the supplied filters. */
rpc ListLocations(ListLocationsRequest) returns (ListLocationsResponse) {}
/* CreateForecaster registers a new forecaster.
diff --git a/proto/ocf/dp/dp.rules.proto b/proto/ocf/dp/dp.rules.proto
index 2bc812f..08f61ba 100644
--- a/proto/ocf/dp/dp.rules.proto
+++ b/proto/ocf/dp/dp.rules.proto
@@ -9,8 +9,8 @@ option go_package = "github.com/openclimatefix/data-platform/internal/protogen/o
extend buf.validate.StringRules {
optional bool valid_location_name = 4457818 [(buf.validate.predefined).cel = {
id: "string.valid.locationname",
- message: "Required field, 2-100 chars long; lowercase, alphanumeric and underscores only",
- expression: "this.size() >= 2 && this.size() <= 100 && this.matches(\'^[a-z0-9_]+$\')"
+ message: "Required field, 2-100 chars long; lowercase, alphanumeric and underscores and pipes only",
+ expression: "this.size() >= 2 && this.size() <= 100 && this.matches(\'^[a-z0-9_|]+$\')"
}];
}
From 2e6c4b0d19975036876a992e8835b6d0af073f7c Mon Sep 17 00:00:00 2001
From: devsjc <47188100+devsjc@users.noreply.github.com>
Date: Thu, 11 Jun 2026 16:10:19 +0100
Subject: [PATCH 2/2] fix(fox): fix
---
Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 256cef8..a3548a9 100644
--- a/Makefile
+++ b/Makefile
@@ -283,7 +283,7 @@ gen.proto.docs: ${PROTOC} ${PROTOC_GEN_DOC}
-I=proto \
-I=$(PROTOC_INCLUDE) \
--doc_out=gen/docs \
- --doc_opt=gen/docs/markdown.tmpl,docs.md:=buf/*,google/*,ocf/dp/dp.rules.proto,ocf/dp/dp-admin.service.proto,ocf/dp/dp-admin.messages.proto
+ --doc_opt=gen/docs/markdown.tmpl,docs.md:=buf/*,google/*,ocf/dp/dp.rules.proto
@sed -n '1,//p' README.md > README.tmp
@echo "" >> README.tmp
@cat gen/docs/docs.md >> README.tmp