Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"files": "go.mod|go.sum|^.secrets.baseline$",
"lines": null
},
"generated_at": "2025-11-27T10:58:34Z",
"generated_at": "2026-04-21T09:42:22Z",
"plugins_used": [
{
"name": "AWSKeyDetector"
Expand Down Expand Up @@ -340,7 +340,7 @@
"hashed_secret": "6947818ac409551f11fbaa78f0ea6391960aa5b8",
"is_secret": false,
"is_verified": false,
"line_number": 115,
"line_number": 121,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down
8 changes: 8 additions & 0 deletions api/internal/pkg/pac-go-server/db/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package db

import (
"context"
"time"

"github.com/IBM/power-access-cloud/api/internal/pkg/pac-go-server/models"
)
Expand Down Expand Up @@ -47,4 +48,11 @@ type DB interface {
InsertFeedback(*models.Feedback) error
GetFeedbacks(models.FeedbacksFilter, int64, int64) ([]models.Feedback, int64, error)
FeedbackAllowed(context.Context, string) (bool, error)

// Maintenance window operations
GetAllMaintenanceWindows() ([]models.MaintenanceWindow, error)
GetMaintenanceWindowByID(id string) (*models.MaintenanceWindow, error)
CreateMaintenanceWindow(window *models.MaintenanceWindow) error
UpdateMaintenanceWindow(window *models.MaintenanceWindow) error
DeleteMaintenanceWindow(id string, deletedBy string, deletedAt *time.Time) error
}
73 changes: 73 additions & 0 deletions api/internal/pkg/pac-go-server/db/mock_db_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

143 changes: 143 additions & 0 deletions api/internal/pkg/pac-go-server/db/mongodb/maintenance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package mongodb

import (
"context"
"fmt"
"time"

"github.com/IBM/power-access-cloud/api/internal/pkg/pac-go-server/models"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)

// GetAllMaintenanceWindows retrieves all non-deleted maintenance windows from the database
func (db *MongoDB) GetAllMaintenanceWindows() ([]models.MaintenanceWindow, error) {
collection := db.Database.Collection("maintenance_windows")
ctx, cancel := context.WithTimeout(context.Background(), dbContextTimeout)
defer cancel()

// Filter out soft-deleted records
filter := bson.M{"deleted_at": bson.M{"$exists": false}}
cursor, err := collection.Find(ctx, filter)
if err != nil {
return nil, fmt.Errorf("error fetching maintenance windows from DB: %w", err)
}
defer cursor.Close(ctx)

var windows []models.MaintenanceWindow
if err := cursor.All(ctx, &windows); err != nil {
return nil, fmt.Errorf("error decoding maintenance windows: %w", err)
}

return windows, nil
}

// GetMaintenanceWindowByID retrieves a specific non-deleted maintenance window by ID
func (db *MongoDB) GetMaintenanceWindowByID(id string) (*models.MaintenanceWindow, error) {
collection := db.Database.Collection("maintenance_windows")
ctx, cancel := context.WithTimeout(context.Background(), dbContextTimeout)
defer cancel()

objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, fmt.Errorf("invalid maintenance window ID: %w", err)
}

var window models.MaintenanceWindow
// Filter out soft-deleted records
filter := bson.M{
"_id": objectID,
"deleted_at": bson.M{"$exists": false},
}

err = collection.FindOne(ctx, filter).Decode(&window)
if err != nil {
if err == mongo.ErrNoDocuments {
return nil, nil
}
return nil, fmt.Errorf("error fetching maintenance window from DB: %w", err)
}

return &window, nil
}

// CreateMaintenanceWindow creates a new maintenance window
func (db *MongoDB) CreateMaintenanceWindow(window *models.MaintenanceWindow) error {
collection := db.Database.Collection("maintenance_windows")
ctx, cancel := context.WithTimeout(context.Background(), dbContextTimeout)
defer cancel()

_, err := collection.InsertOne(ctx, window)
if err != nil {
return fmt.Errorf("error creating maintenance window: %w", err)
}

return nil
}

// UpdateMaintenanceWindow updates an existing maintenance window
func (db *MongoDB) UpdateMaintenanceWindow(window *models.MaintenanceWindow) error {
collection := db.Database.Collection("maintenance_windows")
ctx, cancel := context.WithTimeout(context.Background(), dbContextTimeout)
defer cancel()

filter := bson.M{"_id": window.ID}
update := bson.M{
"$set": bson.M{
"enabled": window.Enabled,
"start_time": window.StartTime,
"end_time": window.EndTime,
"message": window.Message,
"updated_by": window.UpdatedBy,
"updated_at": window.UpdatedAt,
},
}

result, err := collection.UpdateOne(ctx, filter, update)
if err != nil {
return fmt.Errorf("error updating maintenance window: %w", err)
}

if result.MatchedCount == 0 {
return fmt.Errorf("maintenance window not found")
}

return nil
}

// DeleteMaintenanceWindow performs soft delete on a maintenance window by ID
func (db *MongoDB) DeleteMaintenanceWindow(id string, deletedBy string, deletedAt *time.Time) error {
collection := db.Database.Collection("maintenance_windows")
ctx, cancel := context.WithTimeout(context.Background(), dbContextTimeout)
defer cancel()

objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return fmt.Errorf("invalid maintenance window ID: %w", err)
}

// Only soft delete non-deleted records
filter := bson.M{
"_id": objectID,
"deleted_at": bson.M{"$exists": false},
}

update := bson.M{
"$set": bson.M{
"deleted_by": deletedBy,
"deleted_at": deletedAt,
},
}

result, err := collection.UpdateOne(ctx, filter, update)
if err != nil {
return fmt.Errorf("error soft deleting maintenance window: %w", err)
}

if result.MatchedCount == 0 {
return fmt.Errorf("maintenance window not found or already deleted")
}

return nil
}
77 changes: 77 additions & 0 deletions api/internal/pkg/pac-go-server/models/maintenance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package models

import (
"time"

"go.mongodb.org/mongo-driver/bson/primitive"
)

// MaintenanceWindow represents a single maintenance notification window
type MaintenanceWindow struct {
ID primitive.ObjectID `bson:"_id" json:"id"`
Enabled bool `bson:"enabled" json:"enabled"`
StartTime time.Time `bson:"start_time" json:"start_time"`
EndTime time.Time `bson:"end_time" json:"end_time"`
Message string `bson:"message" json:"message"`
CreatedBy string `bson:"created_by" json:"created_by"`
CreatedAt time.Time `bson:"created_at" json:"created_at"`
UpdatedBy string `bson:"updated_by" json:"updated_by"`
UpdatedAt time.Time `bson:"updated_at" json:"updated_at"`
DeletedBy string `bson:"deleted_by,omitempty" json:"deleted_by,omitempty"`
DeletedAt *time.Time `bson:"deleted_at,omitempty" json:"deleted_at,omitempty"`
}

// MaintenanceResponse is the public response for a single maintenance window
type MaintenanceResponse struct {
ID string `json:"id"`
Enabled bool `json:"enabled"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
Message string `json:"message"`
IsActive bool `json:"is_active"`
}

// MaintenanceAdminResponse is the admin response with audit fields
type MaintenanceAdminResponse struct {
ID string `json:"id"`
Enabled bool `json:"enabled"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
Message string `json:"message"`
IsActive bool `json:"is_active"`
CreatedBy string `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedBy string `json:"updated_by,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
DeletedBy string `json:"deleted_by,omitempty"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
}

// MaintenanceListResponse is the public response for listing all maintenance windows
type MaintenanceListResponse struct {
Maintenances []MaintenanceResponse `json:"maintenances"`
}
Comment on lines +50 to +53
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List of all maintenances is not required for public, this would be required for admin only for auditing or some other purposes


// MaintenanceAdminListResponse is the admin response for listing all maintenance windows with pagination
type MaintenanceAdminListResponse struct {
TotalPages int64 `json:"total_pages"`
TotalItems int64 `json:"total_items"`
Maintenances []MaintenanceAdminResponse `json:"maintenances"`
Links Links `json:"links"`
}

// MaintenanceCreateRequest is the request body for creating a new maintenance window
type MaintenanceCreateRequest struct {
Enabled bool `json:"enabled"`
StartTime time.Time `json:"start_time" binding:"required"`
EndTime time.Time `json:"end_time" binding:"required"`
Message string `json:"message" binding:"required"`
}

// MaintenanceUpdateRequest is the request body for updating an existing maintenance window
type MaintenanceUpdateRequest struct {
Enabled bool `json:"enabled"`
StartTime time.Time `json:"start_time,omitempty"`
EndTime time.Time `json:"end_time,omitempty"`
Message string `json:"message,omitempty"`
}
7 changes: 7 additions & 0 deletions api/internal/pkg/pac-go-server/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ func CreateRouter() *gin.Engine {
authorizedAdmin.GET("/users/:id", services.GetUser)

authorizedAdmin.GET("/feedbacks", services.GetFeedback)

authorizedAdmin.POST("/maintenance", services.CreateMaintenanceWindow)
authorizedAdmin.PUT("/maintenance/:id", services.UpdateMaintenanceWindow)
authorizedAdmin.DELETE("/maintenance/:id", services.DeleteMaintenanceWindow)
}

// user related endpoints
Expand Down Expand Up @@ -124,5 +128,8 @@ func CreateRouter() *gin.Engine {
// feedback related endpoints
authorized.POST("/feedbacks", services.CreateFeedback)

// maintenance notification related endpoints
authorized.GET("/maintenance", services.GetMaintenanceWindows)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldnt GetAllMaintenances be only admin route?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, We need this call to get the maintenance windows and show the active one to the users


return router
}
Loading
Loading