Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev #56

Closed
wants to merge 16 commits into from
Closed

Dev #56

Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
feat: Zode validations added for scanner and product endpoints
  • Loading branch information
Axeloooo committed Feb 4, 2024
commit e4634fe9b2fccff3e52ec0f4c4fedf1944d0e7f6
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
- [Anfaal]() - Backend Developer
- [Ryan]() - Backend Developer
- [Alison]() - Backend Developer

## 👨‍💻 Tech Stack

- Frontend
Expand All @@ -51,12 +51,12 @@
![Node.js](https://img.shields.io/badge/Node.js-339933.svg?style=for-the-badge&logo=nodedotjs&logoColor=white)
![Prisma](https://img.shields.io/badge/Prisma-5a67d8.svg?style=for-the-badge&logo=Prisma&logoColor=white)
![MySQL](https://img.shields.io/badge/MySQL-3e6e93.svg?style=for-the-badge&logo=MySQL&logoColor=white)
![Zod](https://img.shields.io/badge/Zod-3E67B1.svg?style=for-the-badge&logo=Zod&logoColor=white)

- Cloud

![PlanetScale](https://img.shields.io/badge/PlanetScale-000000.svg?style=for-the-badge&logo=PlanetScale&logoColor=white)


## 🚀 Backend Documentation

All the code is located in the `backend/src` directory. The backend is written using [Node.js](https://nodejs.org/en/) and [Express](https://expressjs.com/).
Expand Down Expand Up @@ -187,39 +187,40 @@ datasource db {
npx prisma db push
```


# 🌟 Frontend Documentation

The frontend is crafted for iOS platforms, utilizing Swift and SwiftUI. The code is primarily housed in the `Rethread` directory. This section details the setup, development practices, and testing for the frontend environment.

## 🏃 Quickstart

1. **Clone the Repository**:
```bash
git clone git@github.com:techstartucalgary/fashion.git
```

```bash
git clone git@github.com:techstartucalgary/fashion.git
```

2. **Navigate to the Frontend Directory**:
```bash
cd Rethread
```

```bash
cd Rethread
```

3. **Open the Project in Xcode**:
Open the project file `.xcodeproj` in Xcode.
Open the project file `.xcodeproj` in Xcode.

4. **Run the Application**:
Select an iOS simulator or connected device in Xcode and click 'Run'.
Select an iOS simulator or connected device in Xcode and click 'Run'.

## 🛠️ Setup and Installation

1. **Install Xcode**:
Ensure you have Xcode installed on your macOS, available through the Mac App Store.
Ensure you have Xcode installed on your macOS, available through the Mac App Store.

2. **Update Swift and SwiftUI**:
Ensure you have the latest version of Swift and SwiftUI installed, as they are crucial for frontend development.
Ensure you have the latest version of Swift and SwiftUI installed, as they are crucial for frontend development.

3. **Verify the Installation**:
Open Xcode and check for Swift and SwiftUI updates in the preferences.
Open Xcode and check for Swift and SwiftUI updates in the preferences.

4. **Minimum iOS Version**: This app is built for `iOS 16` and above.

Expand Down
23 changes: 9 additions & 14 deletions backend/src/abstracts/product.abstract.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
import { PrismaProduct, PrismaProducts } from "../../types";
import {
PrismaProduct,
PrismaProducts,
CreateProduct,
GetProduct,
} from "../types";

abstract class ProductProvider {
abstract getProducts(): Promise<PrismaProducts>;

abstract getProductById(id: string): Promise<PrismaProduct>;
abstract getProductById(getProduct: GetProduct): Promise<PrismaProduct>;

abstract createProduct(
title: string,
size: string,
color: string,
description: string,
gender: string,
category: string,
price: number,
imageUrl: string,
url: string
): Promise<PrismaProduct>;
abstract createProduct(createProduct: CreateProduct): Promise<PrismaProduct>;

abstract deleteProduct(id: string): Promise<PrismaProduct>;
abstract deleteProduct(getProduct: GetProduct): Promise<PrismaProduct>;
}

export default ProductProvider;
4 changes: 2 additions & 2 deletions backend/src/abstracts/scanner.abstract.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Tag } from "../../types";
import { ScannerRequest, Tag } from "../types";

abstract class ScannerProvider {
abstract getMaterials(text: string): Tag[];

abstract getTextFromImage(imagePath: string): Promise<string>;
abstract getTextFromImage(scannerRequest: ScannerRequest): Promise<string>;
}

export default ScannerProvider;
24 changes: 7 additions & 17 deletions backend/src/controllers/product.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PrismaProduct } from "../../types";
import { CreateProduct, GetProduct, PrismaProduct } from "../types";
import ProductProvider from "../abstracts/product.abstract";
import { Request, Response, NextFunction } from "express";

Expand All @@ -21,13 +21,13 @@ class ProductController {
};

public getProductById = async (
req: Request,
req: Request<GetProduct>,
res: Response,
next: NextFunction
): Promise<Response<any, Record<string, any>> | void> => {
try {
const product: PrismaProduct = await this.service.getProductById(
req.params.id
req.params
);
return res.status(200).json(product);
} catch (e) {
Expand All @@ -36,35 +36,25 @@ class ProductController {
};

public postProduct = async (
req: Request,
req: Request<unknown, unknown, CreateProduct>,
res: Response,
next: NextFunction
): Promise<Response<any, Record<string, any>> | void> => {
try {
const newProduct = await this.service.createProduct(
req.body.title,
req.body.size,
req.body.color,
req.body.description,
req.body.gender,
req.body.category,
req.body.price,
req.body.imageUrl,
req.body.url
);
const newProduct = await this.service.createProduct(req.body);
return res.status(201).json(newProduct);
} catch (e) {
next(e);
}
};

public deleteProduct = async (
req: Request,
req: Request<GetProduct>,
res: Response,
next: NextFunction
): Promise<Response<any, Record<string, any>> | void> => {
try {
const product = await this.service.deleteProduct(req.params.id);
const product = await this.service.deleteProduct(req.params);
return res.status(200).json(product);
} catch (e) {
next(e);
Expand Down
6 changes: 3 additions & 3 deletions backend/src/controllers/scanner.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Tag } from "../../types.js";
import { ScannerRequest, Tag } from "../types.js";
import ScannerProvider from "../abstracts/scanner.abstract.js";
import { Request, Response, NextFunction } from "express";

Expand All @@ -8,12 +8,12 @@ class ScannerController {
}

public postMaterials = async (
req: Request,
req: Request<unknown, unknown, ScannerRequest>,
res: Response,
next: NextFunction
): Promise<Response<any, Record<string, any>> | void> => {
try {
const text: string = await this.service.getTextFromImage(req.body.image);
const text: string = await this.service.getTextFromImage(req.body);
const materials: Tag[] = this.service.getMaterials(text);
return res.status(201).json(materials);
} catch (e) {
Expand Down
8 changes: 5 additions & 3 deletions backend/src/middlewares/error.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import {
import { ProductNotFoundError } from "../errors/product.error.js";
import { TesseractServiceError } from "../errors/tesseract.error.js";

export default function errorHandler(
const errorHandler = (
e: Error,
_: Request,
res: Response,
next: NextFunction
) {
) => {
if (e instanceof HttpBadRequestError) {
res.status(400).json({ error: "Bad Request Error" });
} else if (e instanceof HttpUnauthorizedError) {
Expand All @@ -46,4 +46,6 @@ export default function errorHandler(
} else {
res.status(500).json({ error: "Unexpected error" });
}
}
};

export default errorHandler;
21 changes: 21 additions & 0 deletions backend/src/middlewares/schemaValidation.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NextFunction, Request, Response } from "express";
import { AnyZodObject, ZodError } from "zod";

const schemaValidation =
(schemaValidation: AnyZodObject) =>
(req: Request, res: Response, next: NextFunction) => {
try {
schemaValidation.parse(req);
next();
} catch (e) {
if (e instanceof ZodError) {
res.status(400).json({
error: e.errors.map((error) => error.message),
});
} else {
next(e);
}
}
};

export default schemaValidation;
44 changes: 23 additions & 21 deletions backend/src/repositories/product.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import ProductProvider from "../abstracts/product.abstract.js";
import { prisma } from "../index.js";
import { Prisma } from "@prisma/client";
import { ProductNotFoundError } from "../errors/product.error.js";
import { PrismaProduct, PrismaProducts } from "../../types.js";
import {
CreateProduct,
GetProduct,
PrismaProduct,
PrismaProducts,
} from "../types.js";
import {
PrismaClientInitializationError,
PrismaClientRustPanicError,
Expand Down Expand Up @@ -33,10 +38,12 @@ class ProductRepository implements ProductProvider {
}
};

public getProductById = async (id: string): Promise<PrismaProduct> => {
public getProductById = async (
getProduct: GetProduct
): Promise<PrismaProduct> => {
try {
const product: PrismaProduct | null = await prisma.product.findUnique({
where: { id: id },
where: { id: getProduct.params.id },
});
if (product === null) {
throw new ProductNotFoundError();
Expand All @@ -62,26 +69,19 @@ class ProductRepository implements ProductProvider {
};

public createProduct = async (
title: string,
size: string,
color: string,
description: string,
gender: string,
category: string,
price: number,
imageUrl: string
createProduct: CreateProduct
): Promise<PrismaProduct> => {
try {
const newProduct: PrismaProduct = await prisma.product.create({
data: {
title: title,
size: size,
color: color,
description: description,
gender: gender,
category: category,
price: price,
imageUrl: imageUrl,
title: createProduct.body.title,
size: createProduct.body.size,
color: createProduct.body.color,
description: createProduct.body.description,
gender: createProduct.body.gender,
category: createProduct.body.category,
price: createProduct.body.price,
imageUrl: createProduct.body.imageUrl,
},
});
return newProduct;
Expand All @@ -102,10 +102,12 @@ class ProductRepository implements ProductProvider {
}
};

public deleteProduct = async (id: string): Promise<PrismaProduct> => {
public deleteProduct = async (
getProduct: GetProduct
): Promise<PrismaProduct> => {
try {
const product: PrismaProduct | null = await prisma.product.delete({
where: { id: id },
where: { id: getProduct.params.id },
});
if (product === null) {
throw new ProductNotFoundError();
Expand Down
14 changes: 9 additions & 5 deletions backend/src/repositories/scanner.repository.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { createWorker } from "tesseract.js";
import { Tag } from "../../types.js";
import ScannerProvider from "../abstracts/scanner.abstract.js";
import { createWorker } from "tesseract.js";
import { ScannerRequest, Tag } from "../types.js";
import { TesseractServiceError } from "../errors/tesseract.error.js";

class ScannerRepository implements ScannerProvider {
public getMaterials = (_: string): Tag[] => {
public getMaterials = (text: string): Tag[] => {
throw new Error("Method not implemented.");
};

public getTextFromImage = async (imagePath: string): Promise<string> => {
public getTextFromImage = async (
scannerRequest: ScannerRequest
): Promise<string> => {
try {
const worker: Tesseract.Worker = await createWorker("eng");
const ret: Tesseract.RecognizeResult = await worker.recognize(imagePath);
const ret: Tesseract.RecognizeResult = await worker.recognize(
scannerRequest.body.imageUrl
);
await worker.terminate();
return ret.data.text;
} catch (e) {
Expand Down
26 changes: 23 additions & 3 deletions backend/src/routes/product.routes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import ProductController from "../controllers/product.controller.js";
import ProductService from "../services/product.service.js";
import ProductRepository from "../repositories/product.repository.js";
import schemaValidation from "../middlewares/schemaValidation.middleware.js";
import {
GetProductSchema,
CreateProductSchema,
} from "../schemas/product.schema.js";
import { Router } from "express";

const productRouter = Router();
Expand All @@ -9,8 +14,23 @@ const productController = new ProductController(
);

productRouter.get("/", productController.getProducts);
productRouter.get("/:id", productController.getProductById);
productRouter.post("/", productController.postProduct);
productRouter.delete("/:id", productController.deleteProduct);

productRouter.get(
"/:id",
schemaValidation(GetProductSchema),
productController.getProductById
);

productRouter.post(
"/",
schemaValidation(CreateProductSchema),
productController.postProduct
);

productRouter.delete(
"/:id",
schemaValidation(GetProductSchema),
productController.deleteProduct
);

export default productRouter;
Loading