Align document handlers/repo with contract updates
This commit is contained in:
@@ -1571,6 +1571,34 @@ paths:
|
|||||||
summary: List all documents accessible to the user
|
summary: List all documents accessible to the user
|
||||||
security:
|
security:
|
||||||
- tokenAuth: []
|
- tokenAuth: []
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: residence
|
||||||
|
description: Filter by residence ID
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: uint
|
||||||
|
- in: query
|
||||||
|
name: document_type
|
||||||
|
description: Filter by document type
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: is_active
|
||||||
|
description: Filter by active/inactive status (default true)
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
- in: query
|
||||||
|
name: expiring_soon
|
||||||
|
description: Return warranties expiring in N days
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
- in: query
|
||||||
|
name: search
|
||||||
|
description: Case-insensitive search over title/description
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: List of documents
|
description: List of documents
|
||||||
|
|||||||
@@ -36,16 +36,13 @@ func NewDocumentHandler(documentService *services.DocumentService, storageServic
|
|||||||
func (h *DocumentHandler) ListDocuments(c echo.Context) error {
|
func (h *DocumentHandler) ListDocuments(c echo.Context) error {
|
||||||
user := c.Get(middleware.AuthUserKey).(*models.User)
|
user := c.Get(middleware.AuthUserKey).(*models.User)
|
||||||
|
|
||||||
// Build filter from query params (matching KMP DocumentApi parameters)
|
// Build filter from supported query params.
|
||||||
var filter *repositories.DocumentFilter
|
var filter *repositories.DocumentFilter
|
||||||
if c.QueryParam("residence") != "" || c.QueryParam("document_type") != "" ||
|
if c.QueryParam("residence") != "" || c.QueryParam("document_type") != "" ||
|
||||||
c.QueryParam("category") != "" || c.QueryParam("contractor") != "" ||
|
|
||||||
c.QueryParam("is_active") != "" || c.QueryParam("expiring_soon") != "" ||
|
c.QueryParam("is_active") != "" || c.QueryParam("expiring_soon") != "" ||
|
||||||
c.QueryParam("tags") != "" || c.QueryParam("search") != "" {
|
c.QueryParam("search") != "" {
|
||||||
filter = &repositories.DocumentFilter{
|
filter = &repositories.DocumentFilter{
|
||||||
DocumentType: c.QueryParam("document_type"),
|
DocumentType: c.QueryParam("document_type"),
|
||||||
Category: c.QueryParam("category"),
|
|
||||||
Tags: c.QueryParam("tags"),
|
|
||||||
Search: c.QueryParam("search"),
|
Search: c.QueryParam("search"),
|
||||||
}
|
}
|
||||||
if rid := c.QueryParam("residence"); rid != "" {
|
if rid := c.QueryParam("residence"); rid != "" {
|
||||||
@@ -54,12 +51,6 @@ func (h *DocumentHandler) ListDocuments(c echo.Context) error {
|
|||||||
filter.ResidenceID = &residenceID
|
filter.ResidenceID = &residenceID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cid := c.QueryParam("contractor"); cid != "" {
|
|
||||||
if parsed, err := strconv.ParseUint(cid, 10, 32); err == nil {
|
|
||||||
contractorID := uint(parsed)
|
|
||||||
filter.ContractorID = &contractorID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ia := c.QueryParam("is_active"); ia != "" {
|
if ia := c.QueryParam("is_active"); ia != "" {
|
||||||
isActive := ia == "true" || ia == "1"
|
isActive := ia == "true" || ia == "1"
|
||||||
filter.IsActive = &isActive
|
filter.IsActive = &isActive
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ func TestDocumentHandler_ListDocuments(t *testing.T) {
|
|||||||
handler, e, db := setupDocumentHandler(t)
|
handler, e, db := setupDocumentHandler(t)
|
||||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||||
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Test Doc")
|
activeDoc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Test Doc")
|
||||||
|
inactiveDoc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Old Doc")
|
||||||
|
require.NoError(t, db.Model(&inactiveDoc).Update("is_active", false).Error)
|
||||||
|
|
||||||
authGroup := e.Group("/api/documents")
|
authGroup := e.Group("/api/documents")
|
||||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||||
@@ -45,7 +47,18 @@ func TestDocumentHandler_ListDocuments(t *testing.T) {
|
|||||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, response, 1)
|
assert.Len(t, response, 1)
|
||||||
assert.Equal(t, "Test Doc", response[0]["title"])
|
assert.Equal(t, activeDoc.Title, response[0]["title"])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can list inactive documents when requested", func(t *testing.T) {
|
||||||
|
w := testutil.MakeRequest(e, "GET", "/api/documents/?is_active=false", nil, "test-token")
|
||||||
|
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||||
|
|
||||||
|
var response []map[string]interface{}
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, response, 1)
|
||||||
|
assert.Equal(t, inactiveDoc.Title, response[0]["title"])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -237,6 +237,7 @@ var schemaToKMPClass = map[string]classMapping{
|
|||||||
|
|
||||||
// Subscription
|
// Subscription
|
||||||
"SubscriptionStatusResponse": {kmpClassName: "SubscriptionStatus"},
|
"SubscriptionStatusResponse": {kmpClassName: "SubscriptionStatus"},
|
||||||
|
"SubscriptionResponse": {kmpClassName: "VerificationSubscriptionInfo"},
|
||||||
"UsageResponse": {kmpClassName: "UsageStats"},
|
"UsageResponse": {kmpClassName: "UsageStats"},
|
||||||
"TierLimitsClientResponse": {kmpClassName: "TierLimits"},
|
"TierLimitsClientResponse": {kmpClassName: "TierLimits"},
|
||||||
"FeatureBenefit": {kmpClassName: "FeatureBenefit"},
|
"FeatureBenefit": {kmpClassName: "FeatureBenefit"},
|
||||||
@@ -262,7 +263,6 @@ var excludedSchemas = map[string]string{
|
|||||||
"SharePackageResponse": "Used for .casera file sharing, handled by SharedContractor",
|
"SharePackageResponse": "Used for .casera file sharing, handled by SharedContractor",
|
||||||
"UnregisterDeviceRequest": "Simple oneoff request",
|
"UnregisterDeviceRequest": "Simple oneoff request",
|
||||||
"UpdateTaskCompletionRequest": "Not yet used in KMP",
|
"UpdateTaskCompletionRequest": "Not yet used in KMP",
|
||||||
"SubscriptionResponse": "Inline in purchase/restore handler — KMP maps via VerificationResponse.subscription",
|
|
||||||
"UpgradeTriggerResponse": "Shape differs from KMP UpgradeTriggerData",
|
"UpgradeTriggerResponse": "Shape differs from KMP UpgradeTriggerData",
|
||||||
"UploadResult": "Handled inline in upload response parsing",
|
"UploadResult": "Handled inline in upload response parsing",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,8 @@ import (
|
|||||||
type DocumentFilter struct {
|
type DocumentFilter struct {
|
||||||
ResidenceID *uint
|
ResidenceID *uint
|
||||||
DocumentType string
|
DocumentType string
|
||||||
Category string
|
|
||||||
ContractorID *uint
|
|
||||||
IsActive *bool
|
IsActive *bool
|
||||||
ExpiringSoon *int
|
ExpiringSoon *int
|
||||||
Tags string
|
|
||||||
Search string
|
Search string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,18 +69,17 @@ func (r *DocumentRepository) FindByUserFiltered(residenceIDs []uint, filter *Doc
|
|||||||
query := r.db.Preload("CreatedBy").
|
query := r.db.Preload("CreatedBy").
|
||||||
Preload("Residence").
|
Preload("Residence").
|
||||||
Preload("Images").
|
Preload("Images").
|
||||||
Where("residence_id IN ? AND is_active = ?", residenceIDs, true)
|
Where("residence_id IN ?", residenceIDs)
|
||||||
|
|
||||||
|
// Default behavior is active-only unless explicitly overridden.
|
||||||
|
if filter == nil || filter.IsActive == nil {
|
||||||
|
query = query.Where("is_active = ?", true)
|
||||||
|
}
|
||||||
|
|
||||||
if filter != nil {
|
if filter != nil {
|
||||||
if filter.DocumentType != "" {
|
if filter.DocumentType != "" {
|
||||||
query = query.Where("document_type = ?", filter.DocumentType)
|
query = query.Where("document_type = ?", filter.DocumentType)
|
||||||
}
|
}
|
||||||
if filter.Category != "" {
|
|
||||||
query = query.Where("category = ?", filter.Category)
|
|
||||||
}
|
|
||||||
if filter.ContractorID != nil {
|
|
||||||
query = query.Where("contractor_id = ?", *filter.ContractorID)
|
|
||||||
}
|
|
||||||
if filter.IsActive != nil {
|
if filter.IsActive != nil {
|
||||||
query = query.Where("is_active = ?", *filter.IsActive)
|
query = query.Where("is_active = ?", *filter.IsActive)
|
||||||
}
|
}
|
||||||
@@ -92,9 +88,6 @@ func (r *DocumentRepository) FindByUserFiltered(residenceIDs []uint, filter *Doc
|
|||||||
threshold := now.AddDate(0, 0, *filter.ExpiringSoon)
|
threshold := now.AddDate(0, 0, *filter.ExpiringSoon)
|
||||||
query = query.Where("expiry_date IS NOT NULL AND expiry_date > ? AND expiry_date <= ?", now, threshold)
|
query = query.Where("expiry_date IS NOT NULL AND expiry_date > ? AND expiry_date <= ?", now, threshold)
|
||||||
}
|
}
|
||||||
if filter.Tags != "" {
|
|
||||||
query = query.Where("tags LIKE ?", "%"+filter.Tags+"%")
|
|
||||||
}
|
|
||||||
if filter.Search != "" {
|
if filter.Search != "" {
|
||||||
searchPattern := "%" + filter.Search + "%"
|
searchPattern := "%" + filter.Search + "%"
|
||||||
query = query.Where("(title ILIKE ? OR description ILIKE ?)", searchPattern, searchPattern)
|
query = query.Where("(title ILIKE ? OR description ILIKE ?)", searchPattern, searchPattern)
|
||||||
|
|||||||
Reference in New Issue
Block a user