diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..2687d48 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,10 @@ +## Documentation + +- **OpenAPI / Swagger document**: [here](/docs/openapi.yaml). +- **API Architecture & Data Models**: [here](/docs/api-overview.md) +- Overview of our **REST-compliant API routes**: [here](/docs/routes.md) +- **Currencies** (external API) data: [here](/docs/currencies.md) + +Non-technical (project management) + +- **Roadmap** of requirements: [here](/docs/roadmap.md) \ No newline at end of file diff --git a/docs/api-overview.md b/docs/api-overview.md new file mode 100644 index 0000000..1f1ffb6 --- /dev/null +++ b/docs/api-overview.md @@ -0,0 +1,60 @@ +## API Overview + +### API Architecture + +
+
+
+**Sneakers Collection:**
+
+
+
+**Reviews Collection:**
+
+
+
+(references Users and Sneakers documents)
+
+**Currencies Collection:**
+
+
+
+**Stores Collection:**
+
+
+
+**Users Collection:**
+
+
+
diff --git a/docs/assets/api-diagram.png b/docs/assets/api-diagram.png
new file mode 100644
index 0000000..d4b227c
Binary files /dev/null and b/docs/assets/api-diagram.png differ
diff --git a/docs/assets/data-models/currencies.png b/docs/assets/data-models/currencies.png
new file mode 100644
index 0000000..ad446d5
Binary files /dev/null and b/docs/assets/data-models/currencies.png differ
diff --git a/docs/assets/data-models/providers.png b/docs/assets/data-models/providers.png
new file mode 100644
index 0000000..8e61bab
Binary files /dev/null and b/docs/assets/data-models/providers.png differ
diff --git a/docs/assets/data-models/reviews.png b/docs/assets/data-models/reviews.png
new file mode 100644
index 0000000..803ae8d
Binary files /dev/null and b/docs/assets/data-models/reviews.png differ
diff --git a/docs/assets/data-models/sneakers.png b/docs/assets/data-models/sneakers.png
new file mode 100644
index 0000000..8bc67ec
Binary files /dev/null and b/docs/assets/data-models/sneakers.png differ
diff --git a/docs/assets/data-models/stores.png b/docs/assets/data-models/stores.png
new file mode 100644
index 0000000..fa8970b
Binary files /dev/null and b/docs/assets/data-models/stores.png differ
diff --git a/docs/assets/data-models/users.png b/docs/assets/data-models/users.png
new file mode 100644
index 0000000..68a2a01
Binary files /dev/null and b/docs/assets/data-models/users.png differ
diff --git a/docs/openapi.yaml b/docs/openapi.yaml
index fabb6aa..db57d80 100644
--- a/docs/openapi.yaml
+++ b/docs/openapi.yaml
@@ -199,6 +199,13 @@ paths:
required: true
schema:
type: string
+ - name: format
+ in: query
+ required: false
+ schema:
+ type: string
+ enum: [json, xml]
+ description: Set to 'xml' to receive the response in XML format. Defaults to JSON.
responses:
'200':
description: Successful response
diff --git a/docs/roadmap.md b/docs/roadmap.md
index ac4bf51..d3f2460 100644
--- a/docs/roadmap.md
+++ b/docs/roadmap.md
@@ -5,7 +5,7 @@
- [x] The API offers a REST interface and allows CRUD operations on the DB
- [x] The database is a MongoDB database.
- [x] The database is **automatically seeded** on launch if it's empty.
- - [ ] At least one message is in XML format and has an associated schema. (**optional**)
+ - [x] At least one message is in XML format and has an associated schema. (**optional**)
- [x] At least one response is in JSON format
- [x] There are at least 3 resources and they are related to each other (sneakers, users, reviews).
- [x] One of the collections has at least **1000 documents**
diff --git a/docs/routes.md b/docs/routes.md
index ab50666..40ae580 100644
--- a/docs/routes.md
+++ b/docs/routes.md
@@ -49,6 +49,21 @@ sneakers?currency=eur&limit=2&release_date_after=2021-10-22
**GET** /sneakers/{sneakerId}/reviews : Get all reviews for a specific sneaker
+Query Parameters:
+- `format`: Response format (optional, defaults to JSON)
+ - Type: string
+ Allowed values: **JSON, XML**
+
+Examples:
+
+```
+# Get all reviews for sneaker with ID 682b89e24d2ea61f1a8ab86f (JSON format)
+/sneakers/682b89e24d2ea61f1a8ab86f/reviews
+
+# Get all reviews for sneaker with ID 682b89e24d2ea61f1a8ab86f (XML format)
+/sneakers/682b89e24d2ea61f1a8ab86f/reviews?format=xml
+```
+
**POST** /sneakers/{sneakerId}/reviews : Create review for a sneaker
**GET** /reviews/{reviewId} : Get a specific review
diff --git a/package-lock.json b/package-lock.json
index 17633f3..a53bd7f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"cors": "^2.8.5",
"dotenv": "^16.5.0",
"express": "^4.21.2",
+ "js2xmlparser": "^5.0.0",
"mongoose": "^8.15.0",
"ts-node": "^10.9.2"
},
@@ -1100,6 +1101,15 @@
"node": ">=0.12.0"
}
},
+ "node_modules/js2xmlparser": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-5.0.0.tgz",
+ "integrity": "sha512-ckXs0Fzd6icWurbeAXuqo+3Mhq2m8pOPygsQjTPh8K5UWgKaUgDSHrdDxAfexmT11xvBKOQ6sgYwPkYc5RW/bg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "xmlcreate": "^2.0.4"
+ }
+ },
"node_modules/kareem": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
@@ -2047,6 +2057,12 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/xmlcreate": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz",
+ "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==",
+ "license": "Apache-2.0"
+ },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
diff --git a/package.json b/package.json
index 2d120c6..1c38fe0 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"cors": "^2.8.5",
"dotenv": "^16.5.0",
"express": "^4.21.2",
+ "js2xmlparser": "^5.0.0",
"mongoose": "^8.15.0",
"ts-node": "^10.9.2"
}
diff --git a/src/controllers/sneakers.ts b/src/controllers/sneakers.ts
index 24355d2..778a2ac 100644
--- a/src/controllers/sneakers.ts
+++ b/src/controllers/sneakers.ts
@@ -1,7 +1,11 @@
+import { parse } from "js2xmlparser";
+
import { Review } from "../schemas/review";
import { Sneaker } from "../schemas/sneaker";
import { getCurrencyRate } from "./utils/currency";
+import { cleanReviewForXml } from "./utils/xml";
+
export const getSneakers = async (req, res) => {
try {
const { release_date_after, limit = 20, offset = 0, currency } = req.query;
@@ -219,39 +223,63 @@ export const deleteSneakerById = async (req, res) => {
export const getSneakerReviews = async (req, res) => {
const { sneakerId } = req.params;
+ const { format } = req.query;
try {
-
const existingSneaker = await Sneaker.findOne({ _id: sneakerId });
if (!existingSneaker) {
- return res.status(404).json({
+ const errorObj = {
message: 'Sneaker not found',
status: 'failure'
- });
+ };
+
+ if (format === 'xml') {
+ res.set('Content-Type', 'application/xml');
+ return res.status(404).send(parse("error", errorObj));
+ }
+
+ return res.status(404).json(errorObj);
}
- const reviews = await Review.find({ sneakerId }).select('-__v').sort({ date: -1 });
+ const reviews = await Review.find({ sneakerId }).sort({ date: -1 });
if (!reviews || reviews.length === 0) {
- return res.status(404).json({
+ const errorObj = {
message: 'No reviews available for this sneaker',
status: 'failure'
- });
+ };
+
+ if (format === 'xml') {
+ res.set('Content-Type', 'application/xml');
+ return res.status(404).send(parse("error", errorObj));
+ }
+
+ return res.status(404).json(errorObj);
}
- return res.status(200).json({
- items: reviews,
- message: 'Reviews data fetched successfully',
- status: 'success'
- });
+ if (format === 'xml') {
+ res.set('Content-Type', 'application/xml');
+ const cleanedReviews = reviews.map(r => cleanReviewForXml(r.toObject()));
- } catch (error) {
+ return res.status(200)
+ .send(parse("reviews", { review: cleanedReviews }));
+ }
- return res.status(500).json({
+ return res.status(200).json(reviews);
+
+ } catch (error) {
+ const errorObj = {
message: 'Error fetching reviews',
status: 'failure'
- });
+ };
+
+ if (req.query.format === 'xml') {
+ res.set('Content-Type', 'application/xml');
+ return res.status(500).send(parse("error", errorObj));
+ }
+
+ return res.status(500).json(errorObj);
}
};
diff --git a/src/controllers/utils/xml.ts b/src/controllers/utils/xml.ts
new file mode 100644
index 0000000..1ee3f47
--- /dev/null
+++ b/src/controllers/utils/xml.ts
@@ -0,0 +1,12 @@
+
+export const cleanReviewForXml = (review) => {
+ return {
+ _id: review._id?.toString(),
+ sneakerId: review.sneakerId?.toString(),
+ userId: review.userId?.toString(),
+ rating: review.rating,
+ comment: review.comment,
+ date: review.date instanceof Date ? review.date.toISOString() : review.date
+ };
+}
+