MukeshKapoor25 commited on
Commit
021424b
·
1 Parent(s): 1e41209

feat: Move address management endpoints under customers router and remove standalone addresses controller

Browse files
app/app.py CHANGED
@@ -4,7 +4,6 @@ from app.core.config import settings
4
  from app.core.logging import setup_logging
5
  from app.controllers.auth import router as auth_router
6
  from app.controllers.customers import router as customers_router
7
- from app.controllers.contacts import router as contacts_router
8
  from app.controllers.projects import router as projects_router
9
  from app.controllers.dashboard import router as dashboard_router
10
  from app.controllers.reports import router as reports_router
@@ -13,7 +12,7 @@ from app.controllers.reference import router as reference_router
13
  from app.controllers.bidders import router as bidders_router
14
  from app.controllers.distributors import router as distributors_router
15
  from app.controllers.notes import router as notes_router
16
- from app.controllers.addresses import router as addresses_router
17
 
18
  setup_logging(settings.LOG_LEVEL)
19
 
@@ -51,7 +50,6 @@ async def remove_content_type_for_no_content(request, call_next):
51
 
52
  app.include_router(auth_router)
53
  app.include_router(customers_router)
54
- app.include_router(contacts_router)
55
  app.include_router(projects_router)
56
  app.include_router(dashboard_router)
57
  app.include_router(reports_router)
@@ -59,4 +57,4 @@ app.include_router(employees_router)
59
  app.include_router(reference_router)
60
  app.include_router(bidders_router)
61
  app.include_router(notes_router)
62
- app.include_router(addresses_router)
 
4
  from app.core.logging import setup_logging
5
  from app.controllers.auth import router as auth_router
6
  from app.controllers.customers import router as customers_router
 
7
  from app.controllers.projects import router as projects_router
8
  from app.controllers.dashboard import router as dashboard_router
9
  from app.controllers.reports import router as reports_router
 
12
  from app.controllers.bidders import router as bidders_router
13
  from app.controllers.distributors import router as distributors_router
14
  from app.controllers.notes import router as notes_router
15
+ # addresses router removed; address endpoints now live under customers.py
16
 
17
  setup_logging(settings.LOG_LEVEL)
18
 
 
50
 
51
  app.include_router(auth_router)
52
  app.include_router(customers_router)
 
53
  app.include_router(projects_router)
54
  app.include_router(dashboard_router)
55
  app.include_router(reports_router)
 
57
  app.include_router(reference_router)
58
  app.include_router(bidders_router)
59
  app.include_router(notes_router)
60
+ # addresses router removed; address endpoints now included in customers_router
app/controllers/addresses.py DELETED
@@ -1,84 +0,0 @@
1
- from fastapi import APIRouter, Depends, status, Query, HTTPException
2
- from sqlalchemy.orm import Session
3
- from app.db.session import get_db
4
- from app.services.address_service import AddressService
5
- from app.schemas.address import AddressCreate, AddressOut
6
- from app.schemas.paginated_response import PaginatedResponse
7
- from typing import Optional
8
- import logging
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
- router = APIRouter(prefix="/api/v1/addresses", tags=["addresses"])
13
-
14
-
15
- @router.get(
16
- "/",
17
- response_model=PaginatedResponse[AddressOut],
18
- summary="List addresses",
19
- response_description="Paginated list of addresses"
20
- )
21
- def list_addresses(
22
- customer_id: Optional[int] = Query(None, description="Optional customer id to filter addresses"),
23
- page: int = Query(1, ge=1, description="Page number (1-indexed)"),
24
- page_size: int = Query(10, ge=1, le=100, description="Number of items per page"),
25
- order_by: Optional[str] = Query("Id", description="Field to order by"),
26
- order_dir: Optional[str] = Query("asc", description="Order direction (asc|desc)"),
27
- db: Session = Depends(get_db)
28
- ):
29
- try:
30
- service = AddressService(db)
31
- result = service.list(page=page, page_size=page_size, order_by=order_by, order_dir=order_dir, customer_id=customer_id)
32
- return result
33
- except Exception as e:
34
- logger.error(f"Error listing addresses: {e}")
35
- return PaginatedResponse[AddressOut](items=[], page=page, page_size=page_size, total=0)
36
-
37
-
38
- @router.get("/{address_id}", response_model=AddressOut, summary="Get address by id")
39
- def get_address(address_id: int, db: Session = Depends(get_db)):
40
- try:
41
- service = AddressService(db)
42
- return service.get(address_id)
43
- except Exception as e:
44
- logger.error(f"Error getting address {address_id}: {e}")
45
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Address {address_id} not found")
46
-
47
-
48
- @router.post("/", response_model=AddressOut, status_code=status.HTTP_201_CREATED)
49
- def create_address(address_in: AddressCreate, db: Session = Depends(get_db)):
50
- try:
51
- service = AddressService(db)
52
- result = service.create(address_in.dict())
53
- return result
54
- except ValueError as e:
55
- logger.error(f"Validation error creating address: {e}")
56
- raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
57
- except Exception as e:
58
- logger.error(f"Error creating address: {e}")
59
- raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create address")
60
-
61
-
62
- @router.put("/{address_id}", response_model=AddressOut)
63
- def update_address(address_id: int, address_in: AddressCreate, db: Session = Depends(get_db)):
64
- try:
65
- service = AddressService(db)
66
- result = service.update(address_id, address_in.dict(exclude_unset=True))
67
- return result
68
- except ValueError as e:
69
- logger.error(f"Validation error updating address {address_id}: {e}")
70
- raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
71
- except Exception as e:
72
- logger.error(f"Error updating address {address_id}: {e}")
73
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Address {address_id} not found")
74
-
75
-
76
- @router.delete("/{address_id}", status_code=status.HTTP_204_NO_CONTENT)
77
- def delete_address(address_id: int, db: Session = Depends(get_db)):
78
- try:
79
- service = AddressService(db)
80
- service.delete(address_id)
81
- return None
82
- except Exception as e:
83
- logger.error(f"Error deleting address {address_id}: {e}")
84
- raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to delete address")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/controllers/contacts.py DELETED
@@ -1,152 +0,0 @@
1
- from fastapi import APIRouter, Depends, status, Query, HTTPException
2
- from sqlalchemy.orm import Session
3
- from app.db.session import get_db
4
- from app.services.contact_service import ContactService
5
- from app.schemas.contact import ContactCreate, ContactOut
6
- from app.schemas.paginated_response import PaginatedResponse
7
- from typing import Optional
8
- import logging
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
- router = APIRouter(prefix="/api/v1/contacts", tags=["contacts"])
13
-
14
- @router.get(
15
- "/",
16
- response_model=PaginatedResponse[ContactOut],
17
- summary="List contacts by customer",
18
- response_description="Paginated list of contacts for a specific customer"
19
- )
20
- def list_contacts_by_customer(
21
- customer_id: int = Query(..., description="Customer ID (required)"),
22
- page: int = Query(1, ge=1, description="Page number (1-indexed)"),
23
- page_size: int = Query(10, ge=1, le=100, description="Number of items per page"),
24
- order_by: Optional[str] = Query("ContactID", description="Field to order by"),
25
- order_dir: Optional[str] = Query("asc", description="Order direction (asc|desc)"),
26
- db: Session = Depends(get_db)
27
- ):
28
- """Get all contacts for a specific customer with pagination and ordering"""
29
- try:
30
- logger.info(f"Listing contacts for customer {customer_id}: page={page}, page_size={page_size}")
31
- contact_service = ContactService(db)
32
- result = contact_service.get_by_customer_id(
33
- customer_id=customer_id,
34
- page=page,
35
- page_size=page_size,
36
- order_by=order_by,
37
- order_dir=order_dir.upper()
38
- )
39
- logger.info(f"Successfully retrieved {len(result.items)} contacts for customer {customer_id}")
40
- return result
41
- except Exception as e:
42
- logger.error(f"Error listing contacts for customer {customer_id}: {e}")
43
- return PaginatedResponse[ContactOut](
44
- items=[],
45
- page=page,
46
- page_size=page_size,
47
- total=0
48
- )
49
-
50
- @router.get(
51
- "/{contact_id}",
52
- response_model=ContactOut,
53
- summary="Get a contact by ID",
54
- response_description="Contact details"
55
- )
56
- def get_contact(contact_id: int, db: Session = Depends(get_db)):
57
- """Get a specific contact by ID"""
58
- try:
59
- contact_service = ContactService(db)
60
- return contact_service.get(contact_id)
61
- except Exception as e:
62
- logger.error(f"Error getting contact {contact_id}: {e}")
63
- raise HTTPException(
64
- status_code=status.HTTP_404_NOT_FOUND,
65
- detail=f"Contact {contact_id} not found"
66
- )
67
-
68
- @router.post(
69
- "/",
70
- response_model=ContactOut,
71
- status_code=status.HTTP_201_CREATED,
72
- summary="Create a new contact",
73
- response_description="Created contact details"
74
- )
75
- def create_contact(
76
- contact_in: ContactCreate,
77
- db: Session = Depends(get_db)
78
- ):
79
- """Create a new contact"""
80
- try:
81
- contact_service = ContactService(db)
82
- logger.info("Creating new contact")
83
- result = contact_service.create(contact_in)
84
- logger.info(f"Successfully created contact {result.contact_id}")
85
- return result
86
- except ValueError as e:
87
- logger.error(f"Validation error creating contact: {e}")
88
- raise HTTPException(
89
- status_code=status.HTTP_400_BAD_REQUEST,
90
- detail=str(e)
91
- )
92
- except Exception as e:
93
- logger.error(f"Error creating contact: {e}")
94
- raise HTTPException(
95
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
96
- detail="Failed to create contact"
97
- )
98
-
99
- @router.put(
100
- "/{contact_id}",
101
- response_model=ContactOut,
102
- summary="Update a contact",
103
- response_description="Updated contact details"
104
- )
105
- def update_contact(
106
- contact_id: int,
107
- contact_in: ContactCreate,
108
- db: Session = Depends(get_db)
109
- ):
110
- """Update an existing contact"""
111
- try:
112
- contact_service = ContactService(db)
113
- logger.info(f"Updating contact {contact_id}")
114
- result = contact_service.update(contact_id, contact_in)
115
- logger.info(f"Successfully updated contact {contact_id}")
116
- return result
117
- except ValueError as e:
118
- logger.error(f"Validation error updating contact {contact_id}: {e}")
119
- raise HTTPException(
120
- status_code=status.HTTP_400_BAD_REQUEST,
121
- detail=str(e)
122
- )
123
- except Exception as e:
124
- logger.error(f"Error updating contact {contact_id}: {e}")
125
- raise HTTPException(
126
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
127
- detail="Failed to update contact"
128
- )
129
-
130
- @router.delete(
131
- "/{contact_id}",
132
- status_code=status.HTTP_204_NO_CONTENT,
133
- summary="Delete a contact",
134
- response_description="Contact deleted successfully"
135
- )
136
- def delete_contact(
137
- contact_id: int,
138
- db: Session = Depends(get_db)
139
- ):
140
- """Delete a contact"""
141
- try:
142
- contact_service = ContactService(db)
143
- logger.info(f"Deleting contact {contact_id}")
144
- contact_service.delete(contact_id)
145
- logger.info(f"Successfully deleted contact {contact_id}")
146
- return None
147
- except Exception as e:
148
- logger.error(f"Error deleting contact {contact_id}: {e}")
149
- raise HTTPException(
150
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
151
- detail="Failed to delete contact"
152
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/controllers/customers.py CHANGED
@@ -4,11 +4,15 @@ from app.db.session import get_db
4
  from app.services.customer_service import CustomerService
5
  from app.services.customer_list_service import CustomerListService
6
  from app.services.contact_service import ContactService
 
7
  from app.schemas.customer import CustomerCreate, CustomerOut, CustomerUpdate
8
  from app.schemas.contact import ContactCreate, ContactOut
 
9
  from app.schemas.paginated_response import PaginatedResponse
10
  from typing import List, Optional
11
  import logging
 
 
12
 
13
  logger = logging.getLogger(__name__)
14
 
@@ -106,6 +110,101 @@ def delete_customer(customer_id: int, db: Session = Depends(get_db)):
106
  service.delete(customer_id)
107
  return None
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  # Contact-related endpoints
110
  @router.get(
111
  "/{customer_id}/contacts",
 
4
  from app.services.customer_service import CustomerService
5
  from app.services.customer_list_service import CustomerListService
6
  from app.services.contact_service import ContactService
7
+ from app.services.address_service import AddressService
8
  from app.schemas.customer import CustomerCreate, CustomerOut, CustomerUpdate
9
  from app.schemas.contact import ContactCreate, ContactOut
10
+ from app.schemas.address import AddressCreate, AddressOut
11
  from app.schemas.paginated_response import PaginatedResponse
12
  from typing import List, Optional
13
  import logging
14
+ import os
15
+ from app.db.models.address import Address
16
 
17
  logger = logging.getLogger(__name__)
18
 
 
110
  service.delete(customer_id)
111
  return None
112
 
113
+ # Address-related endpoints (moved from app/controllers/addresses.py)
114
+ @router.get(
115
+ "/{customer_id}/addresses",
116
+ response_model=PaginatedResponse[AddressOut],
117
+ summary="List addresses for a customer",
118
+ response_description="Paginated list of addresses for a customer"
119
+ )
120
+ def list_addresses(
121
+ customer_id: int,
122
+ page: int = Query(1, ge=1, description="Page number (1-indexed)"),
123
+ page_size: int = Query(10, ge=1, le=100, description="Number of items per page"),
124
+ order_by: Optional[str] = Query("Id", description="Field to order by"),
125
+ order_dir: Optional[str] = Query("asc", description="Order direction (asc|desc)"),
126
+ db: Session = Depends(get_db)
127
+ ):
128
+ try:
129
+ service = AddressService(db)
130
+ result = service.list(page=page, page_size=page_size, order_by=order_by, order_dir=order_dir, customer_id=customer_id)
131
+ return result
132
+ except Exception as e:
133
+ logger.error(f"Error listing addresses for customer {customer_id}: {e}")
134
+ return PaginatedResponse[AddressOut](items=[], page=page, page_size=page_size, total=0)
135
+
136
+
137
+
138
+
139
+ @router.get("/{customer_id}/addresses/{address_id}", response_model=AddressOut, summary="Get address by id")
140
+ def get_address(customer_id: int, address_id: int, db: Session = Depends(get_db)):
141
+ try:
142
+ service = AddressService(db)
143
+ addr = service.get(address_id)
144
+ if addr.customer_id != customer_id:
145
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Address {address_id} not found for customer {customer_id}")
146
+ return addr
147
+ except HTTPException:
148
+ raise
149
+ except Exception as e:
150
+ logger.error(f"Error getting address {address_id}: {e}")
151
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Address {address_id} not found")
152
+
153
+
154
+ @router.post("/{customer_id}/addresses", response_model=AddressOut, status_code=status.HTTP_201_CREATED)
155
+ def create_address(customer_id: int, address_in: AddressCreate, db: Session = Depends(get_db)):
156
+ try:
157
+ service = AddressService(db)
158
+ data = address_in.dict()
159
+ data['customer_id'] = customer_id
160
+ result = service.create(data)
161
+ return result
162
+ except ValueError as e:
163
+ logger.error(f"Validation error creating address for customer {customer_id}: {e}")
164
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
165
+ except Exception as e:
166
+ logger.error(f"Error creating address for customer {customer_id}: {e}")
167
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create address")
168
+
169
+
170
+ @router.put("/{customer_id}/addresses/{address_id}", response_model=AddressOut)
171
+ def update_address(customer_id: int, address_id: int, address_in: AddressCreate, db: Session = Depends(get_db)):
172
+ try:
173
+ service = AddressService(db)
174
+ existing = service.get(address_id)
175
+ if existing.customer_id != customer_id:
176
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Address {address_id} not found for customer {customer_id}")
177
+
178
+ update_data = address_in.dict(exclude_unset=True)
179
+ update_data['customer_id'] = customer_id
180
+ result = service.update(address_id, update_data)
181
+ return result
182
+ except HTTPException:
183
+ raise
184
+ except ValueError as e:
185
+ logger.error(f"Validation error updating address {address_id}: {e}")
186
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
187
+ except Exception as e:
188
+ logger.error(f"Error updating address {address_id}: {e}")
189
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Address {address_id} not found")
190
+
191
+
192
+ @router.delete("/{customer_id}/addresses/{address_id}", status_code=status.HTTP_204_NO_CONTENT)
193
+ def delete_address(customer_id: int, address_id: int, db: Session = Depends(get_db)):
194
+ try:
195
+ service = AddressService(db)
196
+ existing = service.get(address_id)
197
+ if existing.customer_id != customer_id:
198
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Address {address_id} not found for customer {customer_id}")
199
+
200
+ service.delete(address_id)
201
+ return None
202
+ except HTTPException:
203
+ raise
204
+ except Exception as e:
205
+ logger.error(f"Error deleting address {address_id}: {e}")
206
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to delete address")
207
+
208
  # Contact-related endpoints
209
  @router.get(
210
  "/{customer_id}/contacts",
app/db/repositories/address_repo.py CHANGED
@@ -38,6 +38,19 @@ class AddressRepository:
38
  query = query.order_by(order_col)
39
 
40
  total = query.count()
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  rows = query.offset((page - 1) * page_size).limit(page_size).all()
42
  items = [self._map_address(r.__dict__) for r in rows]
43
  return PaginatedResponse[AddressOut](items=items, page=page, page_size=page_size, total=total)
 
38
  query = query.order_by(order_col)
39
 
40
  total = query.count()
41
+
42
+ logger.info(f"Query: {str(query)}" )
43
+
44
+ logger.info(f"AddressRepository.list: customer_id={customer_id}, order_by={order_by}, order_direction={order_direction}, total={total}")
45
+
46
+ if total > 0:
47
+ # log a sample of the first row for debugging
48
+ sample = query.limit(1).first()
49
+ if sample is not None:
50
+ try:
51
+ logger.debug(f"Address sample row: {sample.__dict__}")
52
+ except Exception:
53
+ logger.debug("Address sample available but could not serialize row")
54
  rows = query.offset((page - 1) * page_size).limit(page_size).all()
55
  items = [self._map_address(r.__dict__) for r in rows]
56
  return PaginatedResponse[AddressOut](items=items, page=page, page_size=page_size, total=total)
tests/unit/test_addresses_routing.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ # Ensure the addresses endpoints were moved under the customers router
4
+ from app.controllers import customers
5
+
6
+
7
+ def test_addresses_routes_registered():
8
+ """Verify the customers router exposes the address-related paths.
9
+
10
+ This is a lightweight unit test that doesn't start the app or touch the DB.
11
+ It asserts the expected route paths are present on the customers APIRouter.
12
+ """
13
+ # Extract the list of registered route paths
14
+ registered_paths = {r.path for r in customers.router.routes}
15
+
16
+ expected = {
17
+ "/api/v1/customers/{customer_id}/addresses",
18
+ "/api/v1/customers/{customer_id}/addresses/debug/raw",
19
+ "/api/v1/customers/{customer_id}/addresses/{address_id}",
20
+ }
21
+
22
+ missing = expected - registered_paths
23
+ assert not missing, f"Missing address routes on customers router: {missing}"