This is an automated email from the ASF dual-hosted git repository.

DImuthuUpe pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git

commit f6b9e2a9fa578bcbdb712bfe6766551c66c19fd6
Author: DImuthuUpe <[email protected]>
AuthorDate: Sat May 16 21:39:26 2026 -0400

    Implemented the service layer for Compute Allocation Usage
---
 .../000009_compute_allocation_usages.down.sql      |  18 +++
 .../000009_compute_allocation_usages.up.sql        |  36 +++++
 internal/server/server.go                          |  84 +++++++++++
 internal/store/compute_allocation_usage_store.go   | 117 +++++++++++++++
 internal/store/store.go                            |  24 ++++
 pkg/models/allocation.go                           |  16 +--
 pkg/service/compute_allocation_usage.go            | 157 +++++++++++++++++++++
 pkg/service/service.go                             |   4 +
 8 files changed, 448 insertions(+), 8 deletions(-)

diff --git a/internal/db/migrations/000009_compute_allocation_usages.down.sql 
b/internal/db/migrations/000009_compute_allocation_usages.down.sql
new file mode 100644
index 000000000..8e089a51d
--- /dev/null
+++ b/internal/db/migrations/000009_compute_allocation_usages.down.sql
@@ -0,0 +1,18 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+--
+--   http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied.  See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+
+DROP TABLE IF EXISTS compute_allocation_usages;
diff --git a/internal/db/migrations/000009_compute_allocation_usages.up.sql 
b/internal/db/migrations/000009_compute_allocation_usages.up.sql
new file mode 100644
index 000000000..d1e875c4c
--- /dev/null
+++ b/internal/db/migrations/000009_compute_allocation_usages.up.sql
@@ -0,0 +1,36 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+--
+--   http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied.  See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+
+CREATE TABLE IF NOT EXISTS compute_allocation_usages
+(
+    id                             VARCHAR(255) NOT NULL,
+    compute_allocation_id          VARCHAR(255) NOT NULL,
+    used_raw_amount                BIGINT       NOT NULL DEFAULT 0,
+    used_su_amount                 BIGINT       NOT NULL DEFAULT 0,
+    calculated_time                TIMESTAMP(6) NOT NULL,
+    user_id                        VARCHAR(255) NOT NULL DEFAULT '',
+    job_id                         VARCHAR(255) NOT NULL DEFAULT '',
+    compute_allocation_resource_id VARCHAR(255) NOT NULL DEFAULT '',
+    created_at                     TIMESTAMP(6) NOT NULL DEFAULT 
CURRENT_TIMESTAMP(6),
+    PRIMARY KEY (id),
+    KEY idx_compute_allocation_usages_allocation (compute_allocation_id, 
calculated_time),
+    KEY idx_compute_allocation_usages_user (user_id),
+    KEY idx_compute_allocation_usages_job (job_id),
+    KEY idx_compute_allocation_usages_resource 
(compute_allocation_resource_id),
+    CONSTRAINT fk_compute_allocation_usages_allocation FOREIGN KEY 
(compute_allocation_id)
+        REFERENCES compute_allocations (id) ON DELETE CASCADE
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
diff --git a/internal/server/server.go b/internal/server/server.go
index bcf633a8c..fb98b7320 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -108,6 +108,14 @@ func (s *Server) routes() {
        s.mux.HandleFunc("DELETE /compute-allocation-memberships/{id}", 
s.deleteComputeAllocationMembership)
        s.mux.HandleFunc("GET /compute-allocations/{id}/memberships", 
s.listMembersForAllocation)
        s.mux.HandleFunc("GET /users/{id}/compute-allocation-memberships", 
s.listAllocationsForUser)
+
+       s.mux.HandleFunc("POST /compute-allocation-usages", 
s.createComputeAllocationUsage)
+       s.mux.HandleFunc("GET /compute-allocation-usages/{id}", 
s.getComputeAllocationUsage)
+       s.mux.HandleFunc("DELETE /compute-allocation-usages/{id}", 
s.deleteComputeAllocationUsage)
+       s.mux.HandleFunc("GET /compute-allocations/{id}/usages", 
s.listUsagesForAllocation)
+       s.mux.HandleFunc("GET /compute-allocations/{id}/usages/total", 
s.getTotalSUUsageForAllocation)
+       s.mux.HandleFunc("GET 
/compute-allocations/{id}/users/{userId}/usages/total", 
s.getTotalSUUsageForUserInAllocation)
+       s.mux.HandleFunc("GET /users/{id}/compute-allocation-usages", 
s.listUsagesByUser)
 }
 
 func (s *Server) healthz(w http.ResponseWriter, _ *http.Request) {
@@ -622,6 +630,82 @@ func (s *Server) listAllocationsForUser(w 
http.ResponseWriter, r *http.Request)
        writeJSON(w, http.StatusOK, rows)
 }
 
+func (s *Server) createComputeAllocationUsage(w http.ResponseWriter, r 
*http.Request) {
+       var u models.ComputeAllocationUsage
+       if err := decodeJSON(r, &u); err != nil {
+               writeError(w, http.StatusBadRequest, err)
+               return
+       }
+       created, err := s.svc.CreateComputeAllocationUsage(r.Context(), &u)
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusCreated, created)
+}
+
+func (s *Server) getComputeAllocationUsage(w http.ResponseWriter, r 
*http.Request) {
+       u, err := s.svc.GetComputeAllocationUsage(r.Context(), 
r.PathValue("id"))
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusOK, u)
+}
+
+func (s *Server) deleteComputeAllocationUsage(w http.ResponseWriter, r 
*http.Request) {
+       if err := s.svc.DeleteComputeAllocationUsage(r.Context(), 
r.PathValue("id")); err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       w.WriteHeader(http.StatusNoContent)
+}
+
+func (s *Server) listUsagesForAllocation(w http.ResponseWriter, r 
*http.Request) {
+       rows, err := s.svc.ListUsagesForAllocation(r.Context(), 
r.PathValue("id"))
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusOK, rows)
+}
+
+func (s *Server) listUsagesByUser(w http.ResponseWriter, r *http.Request) {
+       rows, err := s.svc.ListUsagesByUser(r.Context(), r.PathValue("id"))
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusOK, rows)
+}
+
+func (s *Server) getTotalSUUsageForAllocation(w http.ResponseWriter, r 
*http.Request) {
+       total, err := s.svc.GetTotalSUUsageForAllocation(r.Context(), 
r.PathValue("id"))
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusOK, map[string]any{
+               "compute_allocation_id": r.PathValue("id"),
+               "total_su_amount":       total,
+       })
+}
+
+func (s *Server) getTotalSUUsageForUserInAllocation(w http.ResponseWriter, r 
*http.Request) {
+       allocationID := r.PathValue("id")
+       userID := r.PathValue("userId")
+       total, err := s.svc.GetTotalSUUsageForUserInAllocation(r.Context(), 
allocationID, userID)
+       if err != nil {
+               writeServiceError(w, err)
+               return
+       }
+       writeJSON(w, http.StatusOK, map[string]any{
+               "compute_allocation_id": allocationID,
+               "user_id":               userID,
+               "total_su_amount":       total,
+       })
+}
+
 // LoggingMiddleware logs every request once it completes.
 func LoggingMiddleware(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
diff --git a/internal/store/compute_allocation_usage_store.go 
b/internal/store/compute_allocation_usage_store.go
new file mode 100644
index 000000000..a039b64cd
--- /dev/null
+++ b/internal/store/compute_allocation_usage_store.go
@@ -0,0 +1,117 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package store
+
+import (
+       "context"
+       "database/sql"
+       "errors"
+
+       "github.com/jmoiron/sqlx"
+
+       "github.com/apache/airavata-custos/pkg/models"
+)
+
+const computeAllocationUsageColumns = "id, compute_allocation_id, 
used_raw_amount, used_su_amount, calculated_time, user_id, job_id, 
compute_allocation_resource_id"
+
+type mysqlComputeAllocationUsageStore struct {
+       db *sqlx.DB
+}
+
+// NewComputeAllocationUsageStore returns a MySQL-backed
+// ComputeAllocationUsageStore.
+func NewComputeAllocationUsageStore(db *sqlx.DB) ComputeAllocationUsageStore {
+       return &mysqlComputeAllocationUsageStore{db: db}
+}
+
+func (s *mysqlComputeAllocationUsageStore) FindByID(ctx context.Context, id 
string) (*models.ComputeAllocationUsage, error) {
+       var u models.ComputeAllocationUsage
+       err := s.db.GetContext(ctx, &u,
+               `SELECT `+computeAllocationUsageColumns+` FROM 
compute_allocation_usages WHERE id = ?`, id)
+       if err != nil {
+               if errors.Is(err, sql.ErrNoRows) {
+                       return nil, nil
+               }
+               return nil, err
+       }
+       return &u, nil
+}
+
+func (s *mysqlComputeAllocationUsageStore) FindByAllocation(ctx 
context.Context, allocationID string) ([]models.ComputeAllocationUsage, error) {
+       var rows []models.ComputeAllocationUsage
+       err := s.db.SelectContext(ctx, &rows,
+               `SELECT `+computeAllocationUsageColumns+`
+                FROM compute_allocation_usages
+                WHERE compute_allocation_id = ?
+                ORDER BY calculated_time`, allocationID)
+       if err != nil {
+               return nil, err
+       }
+       return rows, nil
+}
+
+func (s *mysqlComputeAllocationUsageStore) FindByUser(ctx context.Context, 
userID string) ([]models.ComputeAllocationUsage, error) {
+       var rows []models.ComputeAllocationUsage
+       err := s.db.SelectContext(ctx, &rows,
+               `SELECT `+computeAllocationUsageColumns+`
+                FROM compute_allocation_usages
+                WHERE user_id = ?
+                ORDER BY calculated_time`, userID)
+       if err != nil {
+               return nil, err
+       }
+       return rows, nil
+}
+
+func (s *mysqlComputeAllocationUsageStore) SumSUForAllocation(ctx 
context.Context, allocationID string) (int64, error) {
+       var total sql.NullInt64
+       err := s.db.GetContext(ctx, &total,
+               `SELECT COALESCE(SUM(used_su_amount), 0)
+                FROM compute_allocation_usages
+                WHERE compute_allocation_id = ?`, allocationID)
+       if err != nil {
+               return 0, err
+       }
+       return total.Int64, nil
+}
+
+func (s *mysqlComputeAllocationUsageStore) SumSUForUserInAllocation(ctx 
context.Context, allocationID, userID string) (int64, error) {
+       var total sql.NullInt64
+       err := s.db.GetContext(ctx, &total,
+               `SELECT COALESCE(SUM(used_su_amount), 0)
+                FROM compute_allocation_usages
+                WHERE compute_allocation_id = ? AND user_id = ?`, 
allocationID, userID)
+       if err != nil {
+               return 0, err
+       }
+       return total.Int64, nil
+}
+
+func (s *mysqlComputeAllocationUsageStore) Create(ctx context.Context, tx 
*sql.Tx, u *models.ComputeAllocationUsage) error {
+       _, err := tx.ExecContext(ctx,
+               `INSERT INTO compute_allocation_usages
+                    (id, compute_allocation_id, used_raw_amount, 
used_su_amount, calculated_time, user_id, job_id, 
compute_allocation_resource_id)
+                VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
+               u.ID, u.ComputeAllocationID, u.UsedRawAmount, u.UsedSUAmount, 
u.CalculatedTime, u.UserID, u.JobID, u.ComputeAllocationResourceID)
+       return err
+}
+
+func (s *mysqlComputeAllocationUsageStore) Delete(ctx context.Context, tx 
*sql.Tx, id string) error {
+       _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_usages 
WHERE id = ?`, id)
+       return err
+}
diff --git a/internal/store/store.go b/internal/store/store.go
index 3498072a3..ba6faa9f0 100644
--- a/internal/store/store.go
+++ b/internal/store/store.go
@@ -230,3 +230,27 @@ type ComputeAllocationMembershipStore interface {
        // Delete removes a membership by ID within the provided transaction.
        Delete(ctx context.Context, tx *sql.Tx, id string) error
 }
+
+// ComputeAllocationUsageStore defines persistence operations for the
+// append-only log of resource consumption events charged against a compute
+// allocation.
+type ComputeAllocationUsageStore interface {
+       // FindByID returns the usage with the given ID, or nil if it does not 
exist.
+       FindByID(ctx context.Context, id string) 
(*models.ComputeAllocationUsage, error)
+       // FindByAllocation returns every usage event recorded against the given
+       // allocation, ordered by calculated_time ascending.
+       FindByAllocation(ctx context.Context, allocationID string) 
([]models.ComputeAllocationUsage, error)
+       // FindByUser returns every usage event attributed to the given user,
+       // ordered by calculated_time ascending.
+       FindByUser(ctx context.Context, userID string) 
([]models.ComputeAllocationUsage, error)
+       // SumSUForAllocation returns the total SUs consumed against the given
+       // allocation across all usage events.
+       SumSUForAllocation(ctx context.Context, allocationID string) (int64, 
error)
+       // SumSUForUserInAllocation returns the total SUs consumed by the given
+       // user against the given allocation.
+       SumSUForUserInAllocation(ctx context.Context, allocationID, userID 
string) (int64, error)
+       // Create inserts a new usage event within the provided transaction.
+       Create(ctx context.Context, tx *sql.Tx, u 
*models.ComputeAllocationUsage) error
+       // Delete removes a usage event by ID within the provided transaction.
+       Delete(ctx context.Context, tx *sql.Tx, id string) error
+}
diff --git a/pkg/models/allocation.go b/pkg/models/allocation.go
index 04bd11b4b..1037b1d7f 100644
--- a/pkg/models/allocation.go
+++ b/pkg/models/allocation.go
@@ -78,14 +78,14 @@ type ComputeAllocationChangeRequestEvent struct {
 }
 
 type ComputeAllocationUsage struct { // Represents the usage of a compute 
allocation, e.g., when a job consumes some of the allocated SUs, etc.
-       ID                          string    `json:"id"`
-       ComputeAllocationID         string    `json:"compute_allocation_id"`
-       UsedRawAmount               int64     `json:"used_raw_amount"`          
      // The raw amount of resource used, e.g., 20 CPU hours, 10 GPU hours, etc.
-       UsedSUAmount                int64     `json:"used_su_amount"`           
      // SUs used by the allocation, e.g., 200 SUs, etc.
-       CalculatedTime              time.Time `json:"last_updated"`             
      // The last time the usage was updated. SU should be calculated up to 
this point in time and charge rates should be applied based on the rates 
effective at this time.
-       UserID                      string    `json:"user_id"`                  
      // The ID of the user who used the allocation.
-       JobID                       string    `json:"job_id"`                   
      // The ID of the job that consumed the allocation.
-       ComputeAllocationResourceID string    
`json:"compute_allocation_resource_id"` // The specific resource consumed, 
e.g., 20 CPU hours, 10 GPU hours, etc.
+       ID                          string    `json:"id"                        
     db:"id"`
+       ComputeAllocationID         string    `json:"compute_allocation_id"     
     db:"compute_allocation_id"`
+       UsedRawAmount               int64     `json:"used_raw_amount"           
     db:"used_raw_amount"`                // The raw amount of resource used, 
e.g., 20 CPU hours, 10 GPU hours, etc.
+       UsedSUAmount                int64     `json:"used_su_amount"            
     db:"used_su_amount"`                 // SUs used by the allocation, e.g., 
200 SUs, etc.
+       CalculatedTime              time.Time `json:"last_updated"              
     db:"calculated_time"`                // The last time the usage was 
updated. SU should be calculated up to this point in time and charge rates 
should be applied based on the rates effective at this time.
+       UserID                      string    `json:"user_id"                   
     db:"user_id"`                        // The ID of the user who used the 
allocation.
+       JobID                       string    `json:"job_id"                    
     db:"job_id"`                         // The ID of the job that consumed 
the allocation.
+       ComputeAllocationResourceID string    
`json:"compute_allocation_resource_id" db:"compute_allocation_resource_id"` // 
The specific resource consumed, e.g., 20 CPU hours, 10 GPU hours, etc.
 }
 
 type ComputeAllocationMembership struct {
diff --git a/pkg/service/compute_allocation_usage.go 
b/pkg/service/compute_allocation_usage.go
new file mode 100644
index 000000000..38e9900bc
--- /dev/null
+++ b/pkg/service/compute_allocation_usage.go
@@ -0,0 +1,157 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package service
+
+import (
+       "context"
+       "database/sql"
+       "fmt"
+
+       "github.com/apache/airavata-custos/pkg/models"
+)
+
+// CreateComputeAllocationUsage records a new usage event against a compute
+// allocation. The referenced allocation must exist. CalculatedTime defaults
+// to the server's current UTC time when unset. Usage records are intended to
+// be append-only — there is no update operation.
+func (s *Service) CreateComputeAllocationUsage(ctx context.Context, u 
*models.ComputeAllocationUsage) (*models.ComputeAllocationUsage, error) {
+       if u == nil {
+               return nil, fmt.Errorf("%w: compute allocation usage is nil", 
ErrInvalidInput)
+       }
+       if u.ComputeAllocationID == "" {
+               return nil, fmt.Errorf("%w: compute_allocation_id is required", 
ErrInvalidInput)
+       }
+       if u.UsedRawAmount < 0 || u.UsedSUAmount < 0 {
+               return nil, fmt.Errorf("%w: used_raw_amount and used_su_amount 
must be non-negative", ErrInvalidInput)
+       }
+
+       if alloc, err := s.allocs.FindByID(ctx, u.ComputeAllocationID); err != 
nil {
+               return nil, fmt.Errorf("lookup compute allocation: %w", err)
+       } else if alloc == nil {
+               return nil, fmt.Errorf("%w: compute allocation %q not found", 
ErrInvalidInput, u.ComputeAllocationID)
+       }
+       if u.ComputeAllocationResourceID != "" {
+               if r, err := s.resources.FindByID(ctx, 
u.ComputeAllocationResourceID); err != nil {
+                       return nil, fmt.Errorf("lookup compute allocation 
resource: %w", err)
+               } else if r == nil {
+                       return nil, fmt.Errorf("%w: compute allocation resource 
%q not found", ErrInvalidInput, u.ComputeAllocationResourceID)
+               }
+       }
+
+       if u.ID == "" {
+               u.ID = newID()
+       }
+       if u.CalculatedTime.IsZero() {
+               u.CalculatedTime = nowUTC()
+       }
+
+       if err := s.inTx(ctx, func(tx *sql.Tx) error {
+               return s.usages.Create(ctx, tx, u)
+       }); err != nil {
+               return nil, fmt.Errorf("create compute allocation usage: %w", 
err)
+       }
+       return u, nil
+}
+
+// GetComputeAllocationUsage retrieves a usage event by its ID. Returns
+// ErrNotFound when no usage matches.
+func (s *Service) GetComputeAllocationUsage(ctx context.Context, id string) 
(*models.ComputeAllocationUsage, error) {
+       u, err := s.usages.FindByID(ctx, id)
+       if err != nil {
+               return nil, fmt.Errorf("get compute allocation usage: %w", err)
+       }
+       if u == nil {
+               return nil, ErrNotFound
+       }
+       return u, nil
+}
+
+// ListUsagesForAllocation returns every usage event recorded against the
+// given allocation, ordered by calculated_time ascending.
+func (s *Service) ListUsagesForAllocation(ctx context.Context, allocationID 
string) ([]models.ComputeAllocationUsage, error) {
+       if allocationID == "" {
+               return nil, fmt.Errorf("%w: compute_allocation_id is required", 
ErrInvalidInput)
+       }
+       rows, err := s.usages.FindByAllocation(ctx, allocationID)
+       if err != nil {
+               return nil, fmt.Errorf("list usages for allocation: %w", err)
+       }
+       return rows, nil
+}
+
+// ListUsagesByUser returns every usage event attributed to the given user,
+// ordered by calculated_time ascending.
+func (s *Service) ListUsagesByUser(ctx context.Context, userID string) 
([]models.ComputeAllocationUsage, error) {
+       if userID == "" {
+               return nil, fmt.Errorf("%w: user_id is required", 
ErrInvalidInput)
+       }
+       rows, err := s.usages.FindByUser(ctx, userID)
+       if err != nil {
+               return nil, fmt.Errorf("list usages by user: %w", err)
+       }
+       return rows, nil
+}
+
+// GetTotalSUUsageForAllocation returns the total SUs consumed against the
+// given allocation across all recorded usage events.
+func (s *Service) GetTotalSUUsageForAllocation(ctx context.Context, 
allocationID string) (int64, error) {
+       if allocationID == "" {
+               return 0, fmt.Errorf("%w: compute_allocation_id is required", 
ErrInvalidInput)
+       }
+       total, err := s.usages.SumSUForAllocation(ctx, allocationID)
+       if err != nil {
+               return 0, fmt.Errorf("sum su usage for allocation: %w", err)
+       }
+       return total, nil
+}
+
+// GetTotalSUUsageForUserInAllocation returns the total SUs consumed by the
+// given user against the given allocation.
+func (s *Service) GetTotalSUUsageForUserInAllocation(ctx context.Context, 
allocationID, userID string) (int64, error) {
+       if allocationID == "" {
+               return 0, fmt.Errorf("%w: compute_allocation_id is required", 
ErrInvalidInput)
+       }
+       if userID == "" {
+               return 0, fmt.Errorf("%w: user_id is required", ErrInvalidInput)
+       }
+       total, err := s.usages.SumSUForUserInAllocation(ctx, allocationID, 
userID)
+       if err != nil {
+               return 0, fmt.Errorf("sum su usage for user in allocation: %w", 
err)
+       }
+       return total, nil
+}
+
+// DeleteComputeAllocationUsage removes a usage event by ID.
+func (s *Service) DeleteComputeAllocationUsage(ctx context.Context, id string) 
error {
+       if id == "" {
+               return fmt.Errorf("%w: compute allocation usage id is 
required", ErrInvalidInput)
+       }
+       existing, err := s.usages.FindByID(ctx, id)
+       if err != nil {
+               return fmt.Errorf("lookup compute allocation usage: %w", err)
+       }
+       if existing == nil {
+               return ErrNotFound
+       }
+       if err := s.inTx(ctx, func(tx *sql.Tx) error {
+               return s.usages.Delete(ctx, tx, id)
+       }); err != nil {
+               return fmt.Errorf("delete compute allocation usage: %w", err)
+       }
+       return nil
+}
diff --git a/pkg/service/service.go b/pkg/service/service.go
index e8145c0d5..6fd70c88f 100644
--- a/pkg/service/service.go
+++ b/pkg/service/service.go
@@ -46,6 +46,7 @@ type Service struct {
        changeRequests   store.ComputeAllocationChangeRequestStore
        changeEvents     store.ComputeAllocationChangeRequestEventStore
        memberships      store.ComputeAllocationMembershipStore
+       usages           store.ComputeAllocationUsageStore
 }
 
 // New constructs a Service backed by the supplied database handle.
@@ -65,6 +66,7 @@ func New(database *sqlx.DB) *Service {
                changeRequests:   
store.NewComputeAllocationChangeRequestStore(database),
                changeEvents:     
store.NewComputeAllocationChangeRequestEventStore(database),
                memberships:      
store.NewComputeAllocationMembershipStore(database),
+               usages:           
store.NewComputeAllocationUsageStore(database),
        }
 }
 
@@ -85,6 +87,7 @@ func NewWithStores(
        changeRequests store.ComputeAllocationChangeRequestStore,
        changeEvents store.ComputeAllocationChangeRequestEventStore,
        memberships store.ComputeAllocationMembershipStore,
+       usages store.ComputeAllocationUsageStore,
 ) *Service {
        return &Service{
                db:               database,
@@ -100,6 +103,7 @@ func NewWithStores(
                changeRequests:   changeRequests,
                changeEvents:     changeEvents,
                memberships:      memberships,
+               usages:           usages,
        }
 }
 

Reply via email to