create read and update customers (#67)
* create read and update customers * precommit fixes
taylor_socfortress committed
Jul 26, 2023 at 09:27 UTC
be4dea43157de010cdb144fae322c213e3aed56e
4 files changed
+574
backend/app/__init__.py
+2
@@ -50,6 +50,7 @@ migrate = Migrate(app, db)
50
from app.routes.agents import bp as agents_bp
51
from app.routes.alerts import bp as alerts_bp
52
from app.routes.connectors import bp as connectors_bp
53
+from app.routes.customers import bp as customers_bp
54
from app.routes.dfir_iris import bp as dfir_iris_bp
55
from app.routes.graylog import bp as graylog_bp
56
from app.routes.healthchecks import bp as healthchecks_bp
@@ -76,3 +77,4 @@ app.register_blueprint(influxdb_bp) # Register the influxdb blueprint
77
app.register_blueprint(smtp_bp) # Register the smtp blueprint
78
app.register_blueprint(healthchecks_bp) # Register the healthchecks blueprint
79
app.register_blueprint(threatintel_bp) # Register the threatintel blueprint
80
+app.register_blueprint(customers_bp) # Register the customers blueprint
backend/app/routes/customers.py
new
+146
@@ -0,0 +1,146 @@
1
+from datetime import datetime
2
+
3
+from flask import Blueprint
4
+from flask import jsonify
5
+from flask import request
6
+from loguru import logger
7
+
8
+from app.services.Customers.universal import UniversalCustomers
9
+
10
+bp = Blueprint("customers", __name__)
11
+
12
+
13
+@bp.route("/customers/create", methods=["POST"])
14
+def create_customer():
15
+ """
16
+ Endpoint to store a new customer into `customers` table.
17
+
18
+ Returns:
19
+ Tuple[jsonify, int]: A Tuple where the first element is a JSON response
20
+ indicating if the customer was stored successfully and the second element
21
+ is the HTTP status code.
22
+ """
23
+ logger.info(f"Received data to create a new customer: {request.json}")
24
+ if not request.is_json:
25
+ return jsonify({"message": "Missing JSON in request", "success": False}), 400
26
+
27
+ customerCode = request.json.get("customerCode", None)
28
+ customerName = request.json.get("customerName", None)
29
+ parentCustomerCode = request.json.get("parentCustomerCode", None)
30
+ contactLastName = request.json.get("contactLastName", None)
31
+ contactFirstName = request.json.get("contactFirstName", None)
32
+ phone = request.json.get("phone", None)
33
+ addressLine1 = request.json.get("addressLine1", None)
34
+ addressLine2 = request.json.get("addressLine2", None)
35
+ city = request.json.get("city", None)
36
+ state = request.json.get("state", None)
37
+ postalCode = request.json.get("postalCode", None)
38
+ country = request.json.get("country", None)
39
+ customerType = request.json.get("customerType", None)
40
+ logoFile = request.json.get("logoFile", None)
41
+ createdAt = request.json.get("createdAt", None)
42
+ if createdAt:
43
+ createdAt = datetime.fromisoformat(createdAt.replace("Z", "+00:00"))
44
+
45
+ new_customer = UniversalCustomers.create(
46
+ customerCode,
47
+ customerName,
48
+ parentCustomerCode,
49
+ contactLastName,
50
+ contactFirstName,
51
+ phone,
52
+ addressLine1,
53
+ addressLine2,
54
+ city,
55
+ state,
56
+ postalCode,
57
+ country,
58
+ customerType,
59
+ logoFile,
60
+ createdAt,
61
+ )
62
+
63
+ return jsonify(new_customer), 201
64
+
65
+
66
+@bp.route("/customers/read/all", methods=["GET"])
67
+def read_all_customers():
68
+ """
69
+ Endpoint to list all customers from the `customers` table.
70
+
71
+ Returns:
72
+ jsonify: A JSON response containing the list of all customers.
73
+ """
74
+ logger.info("Received request to get all customers")
75
+ customers = UniversalCustomers.read_all()
76
+ return jsonify(customers)
77
+
78
+
79
+@bp.route("/customers/read/<int:id>", methods=["GET"])
80
+def read_customer_by_id(id: int):
81
+ """
82
+ Endpoint to fetch a customer by their id.
83
+
84
+ Returns:
85
+ Tuple[jsonify, int]: A Tuple where the first element is a JSON response
86
+ containing the customer and the second element is the HTTP status code.
87
+ """
88
+ logger.info(f"Received request to get customer with id {id}")
89
+ customer = UniversalCustomers.read_by_id(id)
90
+ if customer:
91
+ return jsonify(customer), 200
92
+ return jsonify({"message": "Customer not found", "success": False}), 404
93
+
94
+
95
+@bp.route("/customers/update/<int:id>", methods=["PUT"])
96
+def update_customer(id: int):
97
+ """
98
+ Endpoint to update a customer into `customers` table.
99
+
100
+ Returns:
101
+ Tuple[jsonify, int]: A Tuple where the first element is a JSON response
102
+ indicating if the customer was updated successfully and the second element
103
+ is the HTTP status code.
104
+ """
105
+ logger.info(f"Received data to update a customer: {request.json}")
106
+ if not request.is_json:
107
+ return jsonify({"message": "Missing JSON in request", "success": False}), 400
108
+
109
+ customerCode = request.json.get("customerCode", None)
110
+ customerName = request.json.get("customerName", None)
111
+ parentCustomerCode = request.json.get("parentCustomerCode", None)
112
+ contactLastName = request.json.get("contactLastName", None)
113
+ contactFirstName = request.json.get("contactFirstName", None)
114
+ phone = request.json.get("phone", None)
115
+ addressLine1 = request.json.get("addressLine1", None)
116
+ addressLine2 = request.json.get("addressLine2", None)
117
+ city = request.json.get("city", None)
118
+ state = request.json.get("state", None)
119
+ postalCode = request.json.get("postalCode", None)
120
+ country = request.json.get("country", None)
121
+ customerType = request.json.get("customerType", None)
122
+ logoFile = request.json.get("logoFile", None)
123
+ createdAt = request.json.get("createdAt", None)
124
+ if createdAt:
125
+ createdAt = datetime.fromisoformat(createdAt.replace("Z", "+00:00"))
126
+
127
+ updated_customer = UniversalCustomers.update(
128
+ id,
129
+ customerCode,
130
+ customerName,
131
+ parentCustomerCode,
132
+ contactLastName,
133
+ contactFirstName,
134
+ phone,
135
+ addressLine1,
136
+ addressLine2,
137
+ city,
138
+ state,
139
+ postalCode,
140
+ country,
141
+ customerType,
142
+ logoFile,
143
+ createdAt,
144
+ )
145
+
146
+ return jsonify(updated_customer), 201
backend/app/services/Customers/universal.py
new
+206
@@ -0,0 +1,206 @@
1
+from datetime import datetime
2
+from typing import Dict
3
+from typing import List
4
+from typing import Optional
5
+from typing import Union
6
+
7
+from app import db
8
+from app.models.customers import Customers
9
+from app.models.customers import customer_schema
10
+from app.models.customers import customers_schema
11
+
12
+
13
+class UniversalCustomers:
14
+ @staticmethod
15
+ def create(
16
+ customerCode: str,
17
+ customerName: str,
18
+ parentCustomerCode: str = None,
19
+ contactLastName: str = None,
20
+ contactFirstName: str = None,
21
+ phone: str = None,
22
+ addressLine1: str = None,
23
+ addressLine2: str = None,
24
+ city: str = None,
25
+ state: str = None,
26
+ postalCode: str = None,
27
+ country: str = None,
28
+ customerType: str = None,
29
+ logoFile: str = None,
30
+ createdAt: datetime = None,
31
+ ) -> Dict[str, Union[int, str]]:
32
+ """
33
+ Create a new customer and add it to the database.
34
+
35
+ :param customerCode: The code of the customer used as the Wazuh-Agent Label.
36
+ :param customerName: The name of the customer.
37
+ :param parentCustomerCode: The code of the parent customer.
38
+ :param contactLastName: The last name of the contact.
39
+ :param contactFirstName: The first name of the contact.
40
+ :param phone: The phone number of the contact.
41
+ :param addressLine1: The first line of the customer's address.
42
+ :param addressLine2: The second line of the customer's address.
43
+ :param city: The city of the customer's address.
44
+ :param state: The state of the customer's address.
45
+ :param postalCode: The postal code of the customer's address.
46
+ :param country: The country of the customer's address.
47
+ :param customerType: The type of the customer.
48
+ :param logoFile: The file name of the customer's logo.
49
+ :param createdAt: The date the customer was created.
50
+ :return: Dictionary of the created customer.
51
+ """
52
+ new_customer = Customers(
53
+ customerCode,
54
+ customerName,
55
+ parentCustomerCode,
56
+ contactLastName,
57
+ contactFirstName,
58
+ phone,
59
+ addressLine1,
60
+ addressLine2,
61
+ city,
62
+ state,
63
+ postalCode,
64
+ country,
65
+ customerType,
66
+ logoFile,
67
+ createdAt,
68
+ )
69
+ db.session.add(new_customer)
70
+ db.session.commit()
71
+ return {
72
+ "message": "Customer created successfully.",
73
+ "success": True,
74
+ "customer": customer_schema.dump(new_customer),
75
+ }
76
+
77
+ @staticmethod
78
+ def read_by_id(id: int) -> Optional[Dict[str, Union[int, str]]]:
79
+ """
80
+ Fetch customer by their id.
81
+
82
+ :param id: ID of the customer.
83
+ :return: Dictionary of the customer or None if not found.
84
+ """
85
+ customer = Customers.query.get(id)
86
+ return customer_schema.dump(customer) if customer else None
87
+
88
+ @staticmethod
89
+ def read_by_customerCode(customerCode: str) -> Optional[Dict[str, Union[int, str]]]:
90
+ """
91
+ Fetch customer by customerCode.
92
+
93
+ :param customerCode: The code of the customer.
94
+ :return: Dictionary of the customer or None if not found.
95
+ """
96
+ customer = Customers.query.filter_by(customerCode=customerCode).first()
97
+ return customer_schema.dump(customer) if customer else None
98
+
99
+ @staticmethod
100
+ def read_all() -> List[Dict[str, Union[int, str]]]:
101
+ """
102
+ Fetch all customers.
103
+
104
+ :return: List of dictionaries of all customers.
105
+ """
106
+ customers = Customers.query.all()
107
+ return {
108
+ "message": "Customers retrieved successfully.",
109
+ "success": True,
110
+ "customers": customers_schema.dump(customers, many=True),
111
+ }
112
+
113
+ @staticmethod
114
+ def update(
115
+ id: int,
116
+ customerCode: Optional[str] = None,
117
+ customerName: Optional[str] = None,
118
+ parentCustomerCode: Optional[str] = None,
119
+ contactLastName: Optional[str] = None,
120
+ contactFirstName: Optional[str] = None,
121
+ phone: Optional[str] = None,
122
+ addressLine1: Optional[str] = None,
123
+ addressLine2: Optional[str] = None,
124
+ city: Optional[str] = None,
125
+ state: Optional[str] = None,
126
+ postalCode: Optional[str] = None,
127
+ country: Optional[str] = None,
128
+ customerType: Optional[str] = None,
129
+ logoFile: Optional[str] = None,
130
+ createdAt: Optional[datetime] = None,
131
+ ) -> Optional[Dict[str, Union[int, str]]]:
132
+ """
133
+ Update existing customer.
134
+
135
+ :param id: ID of the customer.
136
+ :param customerCode: The code of the customer used as the Wazuh-Agent Label.
137
+ :param customerName: The name of the customer.
138
+ :param parentCustomerCode: The code of the parent customer.
139
+ :param contactLastName: The last name of the contact.
140
+ :param contactFirstName: The first name of the contact.
141
+ :param phone: The phone number of the contact.
142
+ :param addressLine1: The first line of the customer's address.
143
+ :param addressLine2: The second line of the customer's address.
144
+ :param city: The city of the customer's address.
145
+ :param state: The state of the customer's address.
146
+ :param postalCode: The postal code of the customer's address.
147
+ :param country: The country of the customer's address.
148
+ :param customerType: The type of the customer.
149
+ :param logoFile: The file name of the customer's logo.
150
+ :param createdAt: The date the customer was created.
151
+ :return: Dictionary of the updated customer or None if not found.
152
+ """
153
+ customer = Customers.query.get(id)
154
+ if not customer:
155
+ return None
156
+ if customerCode is not None:
157
+ customer.customerCode = customerCode
158
+ if customerName is not None:
159
+ customer.customerName = customerName
160
+ if parentCustomerCode is not None:
161
+ customer.parentCustomerCode = parentCustomerCode
162
+ if contactLastName is not None:
163
+ customer.contactLastName = contactLastName
164
+ if contactFirstName is not None:
165
+ customer.contactFirstName = contactFirstName
166
+ if phone is not None:
167
+ customer.phone = phone
168
+ if addressLine1 is not None:
169
+ customer.addressLine1 = addressLine1
170
+ if addressLine2 is not None:
171
+ customer.addressLine2 = addressLine2
172
+ if city is not None:
173
+ customer.city = city
174
+ if state is not None:
175
+ customer.state = state
176
+ if postalCode is not None:
177
+ customer.postalCode = postalCode
178
+ if country is not None:
179
+ customer.country = country
180
+ if customerType is not None:
181
+ customer.customerType = customerType
182
+ if logoFile is not None:
183
+ customer.logoFile = logoFile
184
+ if createdAt is not None:
185
+ customer.createdAt = createdAt
186
+
187
+ db.session.commit()
188
+ return customer_schema.dump(customer)
189
+
190
+ @staticmethod
191
+ def delete_all():
192
+ """
193
+ Delete all customers from the table.
194
+ """
195
+ Customers.query.delete()
196
+ db.session.commit()
197
+
198
+ @staticmethod
199
+ def delete_by_id(id: int):
200
+ """
201
+ Delete a customer with the given id.
202
+
203
+ :param id: ID of the customer to delete.
204
+ """
205
+ Customers.query.filter(Customers.id == id).delete()
206
+ db.session.commit()
backend/app/static/swagger.json
+220
@@ -18,6 +18,14 @@
18
"url": "http://swagger.io"
19
}
20
},
21
+ {
22
+ "name": "Customers",
23
+ "description": "Everything about your Customers",
24
+ "externalDocs": {
25
+ "description": "Find out more",
26
+ "url": "http://swagger.io"
27
+ }
28
+ },
29
{
30
"name": "Agents",
31
"description": "Everything about your Agents",
@@ -346,6 +354,218 @@
354
}
355
}
356
},
357
+ "/customers/create": {
358
+ "post": {
359
+ "tags": ["Customers"],
360
+ "summary": "Create a new customer",
361
+ "description": "Endpoint to store a new customer into the `customers` table.",
362
+ "requestBody": {
363
+ "content": {
364
+ "application/json": {
365
+ "schema": {
366
+ "type": "object",
367
+ "properties": {
368
+ "customerCode": { "type": "string", "description": "Customer Code" },
369
+ "customerName": { "type": "string", "description": "Customer Name" },
370
+ "parentCustomerCode": { "type": "string", "description": "Parent Customer Code" },
371
+ "contactLastName": { "type": "string", "description": "Contact Last Name" },
372
+ "contactFirstName": { "type": "string", "description": "Contact First Name" },
373
+ "phone": { "type": "string", "description": "Phone Number" },
374
+ "addressLine1": { "type": "string", "description": "Address Line 1" },
375
+ "addressLine2": { "type": "string", "description": "Address Line 2" },
376
+ "city": { "type": "string", "description": "City" },
377
+ "state": { "type": "string", "description": "State" },
378
+ "postalCode": { "type": "string", "description": "Postal Code" },
379
+ "country": { "type": "string", "description": "Country" },
380
+ "customerType": { "type": "string", "description": "Customer Type" },
381
+ "logoFile": { "type": "string", "description": "Logo File" },
382
+ "createdAt": { "type": "string", "format": "date-time", "description": "Creation Time" }
383
+ },
384
+ "required": ["customerCode", "customerName"]
385
+ }
386
+ }
387
+ }
388
+ },
389
+ "responses": {
390
+ "201": {
391
+ "description": "Customer created",
392
+ "content": {
393
+ "application/json": {
394
+ "schema": {
395
+ "type": "object",
396
+ "properties": {
397
+ "message": { "type": "string", "example": "Customer created successfully." },
398
+ "success": { "type": "boolean", "example": true },
399
+ "customer": { "$ref": "#/definitions/Customer" }
400
+ }
401
+ }
402
+ }
403
+ }
404
+ },
405
+ "400": {
406
+ "description": "Invalid input",
407
+ "content": {
408
+ "application/json": {
409
+ "schema": {
410
+ "type": "object",
411
+ "properties": {
412
+ "message": { "type": "string", "example": "Invalid input" },
413
+ "success": { "type": "boolean", "example": false }
414
+ }
415
+ }
416
+ }
417
+ }
418
+ }
419
+ }
420
+ }
421
+ },
422
+ "/customers/read/all": {
423
+ "get": {
424
+ "tags": ["Customers"],
425
+ "summary": "Get all customers",
426
+ "description": "Endpoint to retrieve all customers from the `customers` table.",
427
+ "responses": {
428
+ "200": {
429
+ "description": "Successful operation",
430
+ "content": {
431
+ "application/json": {
432
+ "schema": {
433
+ "type": "object",
434
+ "properties": {
435
+ "message": { "type": "string", "example": "Customers retrieved successfully." },
436
+ "success": { "type": "boolean", "example": true },
437
+ "customers": {
438
+ "type": "array",
439
+ "items": { "$ref": "#/definitions/Customer" }
440
+ }
441
+ }
442
+ }
443
+ }
444
+ }
445
+ }
446
+ }
447
+ }
448
+ },
449
+ "/customers/read/{id}": {
450
+ "get": {
451
+ "tags": ["Customers"],
452
+ "summary": "Get a customer",
453
+ "description": "Endpoint to retrieve a customer from the `customers` table.",
454
+ "parameters": [
455
+ {
456
+ "name": "id",
457
+ "in": "path",
458
+ "description": "ID of the customer to retrieve",
459
+ "required": true,
460
+ "type": "integer"
461
+ }
462
+ ],
463
+ "responses": {
464
+ "200": {
465
+ "description": "Successful operation",
466
+ "content": {
467
+ "application/json": {
468
+ "schema": {
469
+ "type": "object",
470
+ "properties": {
471
+ "message": { "type": "string", "example": "Customer retrieved successfully." },
472
+ "success": { "type": "boolean", "example": true },
473
+ "customer": { "$ref": "#/definitions/Customer" }
474
+ }
475
+ }
476
+ }
477
+ }
478
+ },
479
+ "404": {
480
+ "description": "Customer not found",
481
+ "content": {
482
+ "application/json": {
483
+ "schema": {
484
+ "type": "object",
485
+ "properties": {
486
+ "message": { "type": "string", "example": "Customer not found." },
487
+ "success": { "type": "boolean", "example": false }
488
+ }
489
+ }
490
+ }
491
+ }
492
+ }
493
+ }
494
+ }
495
+ },
496
+ "/customers/update/{id}": {
497
+ "put": {
498
+ "tags": ["Customers"],
499
+ "summary": "Update an existing customer",
500
+ "description": "Endpoint to update a customer in the `customers` table.",
501
+ "parameters": [
502
+ {
503
+ "name": "id",
504
+ "in": "path",
505
+ "description": "ID of the customer to update",
506
+ "required": true,
507
+ "type": "integer"
508
+ }
509
+ ],
510
+ "requestBody": {
511
+ "content": {
512
+ "application/json": {
513
+ "schema": {
514
+ "type": "object",
515
+ "properties": {
516
+ "customerCode": { "type": "string", "description": "Customer Code" },
517
+ "customerName": { "type": "string", "description": "Customer Name" },
518
+ "parentCustomerCode": { "type": "string", "description": "Parent Customer Code" },
519
+ "contactLastName": { "type": "string", "description": "Contact Last Name" },
520
+ "contactFirstName": { "type": "string", "description": "Contact First Name" },
521
+ "phone": { "type": "string", "description": "Phone Number" },
522
+ "addressLine1": { "type": "string", "description": "Address Line 1" },
523
+ "addressLine2": { "type": "string", "description": "Address Line 2" },
524
+ "city": { "type": "string", "description": "City" },
525
+ "state": { "type": "string", "description": "State" },
526
+ "postalCode": { "type": "string", "description": "Postal Code" },
527
+ "country": { "type": "string", "description": "Country" },
528
+ "customerType": { "type": "string", "description": "Customer Type" },
529
+ "logoFile": { "type": "string", "description": "Logo File" },
530
+ "createdAt": { "type": "string", "format": "date-time", "description": "Creation Time" }
531
+ }
532
+ }
533
+ }
534
+ }
535
+ },
536
+ "responses": {
537
+ "201": {
538
+ "description": "Customer updated",
539
+ "content": {
540
+ "application/json": {
541
+ "schema": {
542
+ "type": "object",
543
+ "properties": {
544
+ "message": { "type": "string", "example": "Customer updated successfully." },
545
+ "success": { "type": "boolean", "example": true },
546
+ "customer": { "$ref": "#/definitions/Customer" }
547
+ }
548
+ }
549
+ }
550
+ }
551
+ },
552
+ "400": {
553
+ "description": "Invalid input",
554
+ "content": {
555
+ "application/json": {
556
+ "schema": {
557
+ "type": "object",
558
+ "properties": {
559
+ "message": { "type": "string", "example": "Invalid input" },
560
+ "success": { "type": "boolean", "example": false }
561
+ }
562
+ }
563
+ }
564
+ }
565
+ }
566
+ }
567
+ }
568
+ },
569
"/agents": {
570
"get": {
571
"tags": ["Agents"],