Skip to content

Commit

Permalink
add img on dockerfile
Browse files Browse the repository at this point in the history
  • Loading branch information
adelemanga committed Jan 7, 2025
2 parents a4d74f9 + 51734ac commit 1f749e9
Show file tree
Hide file tree
Showing 34 changed files with 654 additions and 180 deletions.
8 changes: 0 additions & 8 deletions .github/workflows/frontend-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,3 @@ jobs:
context: "{{defaultContext}}:backend"
file: Dockerfile.prod
tags: ${{ secrets.DOCKERHUB_USERNAME }}/wildrent-backend:latest, ${{ secrets.DOCKERHUB_USERNAME }}/wildrent-backend:${{ github.sha }}
- name: Launch the security test
continue-on-error: true
run: npm run zap

- uses: actions/upload-artifact@v4
with:
name: zap-report
path: zap/wrk/index.html
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.env
img/node_modules
frontend/src/generated/graphql-types.ts
26 changes: 25 additions & 1 deletion backend/src/resolvers/ArticleResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ class NewArticleInput {
@Field(() => String)
productId: number;
}

@Resolver(Article)
class ArticleResolver {
@Query(() => [Article])
async getAllArticles() {
const article = await Article.find({ relations: { product: true, reservations: true } })
const article = await Article.find({ relations: { product: true, reservations: true }, order: { product: {name: "ASC" }}
})
return article
}

Expand Down Expand Up @@ -51,6 +53,28 @@ class ArticleResolver {
await Article.delete(idToDelete);
return `Product deleted successfully`;
}

@Mutation(() => Article)
async deleteArticleFromReservation(@Arg("articleId") articleId: string) {
const article = await Article.findOne({
where: { id: Number.parseInt(articleId) },
relations: { reservations: true },
});

if (!article) {
throw new Error("Article not found");
}

if (!article.reservations) {
throw new Error("Article is not part of any reservation");
}

article.reservations = [];

await article.save();

return article;
}
}

export default ArticleResolver;
23 changes: 19 additions & 4 deletions backend/src/resolvers/ReservationResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,19 @@ class ReservationResolver {
where: { user: { id: context.id } },
relations: ["user", "articles", "articles.product"],
order: {
createdAt: "DESC"
createdAt: "DESC",
},
});
return reservations.map(reservation => {

return reservations.map((reservation) => {
const totalPrice = calculateTotal(reservation.articles);
return { reservation, totalPrice };
});
} else {
return [];
}
}

@Mutation(() => Reservation)
async handleReservation(
@Ctx() context: Context,
Expand Down Expand Up @@ -174,6 +174,21 @@ class ReservationResolver {

return reservation;
}

@Mutation(() => Reservation)
async cancelReservation(@Arg("reservationId") reservationId: string) {
const reservation = await Reservation.findOne({
where: { id: Number.parseInt(reservationId) },
});

if (!reservation) {
throw new Error("Reservation not found");
}
reservation.status = ReservationStatus.Ended;
await reservation.save();

return reservation;
}
}

export default ReservationResolver;
13 changes: 6 additions & 7 deletions docker-compose.build.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,10 @@ services:
ports:
- 7000:80
img:
build:
context: ./img
dockerfile: Dockerfile.prod
build: ./img
dockerfile: Dockerfile.prod
healthcheck:
test: "curl --fail --request GET --url 'http://localhost:4000' || exit 1"
interval: 1s
timeout: 2s
retries: 100
test: "curl --fail --request GET --url 'http://localhost:4000' || exit 1"
interval: 1s
timeout: 2s
retries: 100
2 changes: 1 addition & 1 deletion docker-compose.e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@ services:
- ./e2e/playwright-report:/app/playwright-report
- ./e2e/test-results:/app/test-results
depends_on:
- apigateway
- apigateway
5 changes: 2 additions & 3 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,8 @@ services:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./logs:/var/log/nginx
img:
build:
context: ./img
dockerfile: Dockerfile.prod
build: ./img
dockerfile: Dockerfile.prod
healthcheck:
test: "curl --fail --request GET --url 'http://localhost:4000' || exit 1"
interval: 1s
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ services:
condition: service_healthy
ports:
- 7000:80
img:
img:
build: ./img
healthcheck:
test: "curl --fail --request GET --url 'http://localhost:4000' || exit 1"
Expand All @@ -58,4 +58,4 @@ services:
retries: 100

volumes:
pgdata:
pgdata:
2 changes: 1 addition & 1 deletion e2e/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/playwright:v1.48.0-jammy
FROM mcr.microsoft.com/playwright:v1.49.1-jammy

WORKDIR /app

Expand Down
6 changes: 5 additions & 1 deletion frontend/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ module.exports = {
"warn",
{ allowConstantExport: true },
],
quotes: ["error", "double"],
"@typescript-eslint/quotes": ["error", "double"],
eqeqeq: ["error", "always"],
indent: ["error", 2],
"no-unused-vars": "warn",
"no-console": "warn",
"no-trailing-spaces": "error",
},
};
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"dev": "graphql-codegen --config codegen.ts && vite --host",
"build": "tsc -b && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint": "eslint . --ext .ts,.tsx",
"preview": "vite preview",
"test": "vitest"
},
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,26 @@ import Login from "./pages/Login";
import SearchPage from "./pages/search/[searchKeywords]";
import Profile from "./pages/Profile";
import { Cart } from "./pages/Cart";
import AdminRouteProtection from "./components/AdminRouteProtection";

const App = () => {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<HomePage />} />
<Route path="register" element={<Register />} />
<Route path="admin" element={<Admin />} />
<Route
path="admin"
element={
<AdminRouteProtection>
<Admin />
</AdminRouteProtection>
}
/>
<Route path="profile" element={<Profile />} />
<Route path="login" element={<Login />} />
<Route path="/product/:productId" element={<ProductDescription />} />
<Route path="search" element={<SearchPage />} />
<Route path="search" element={<SearchPage />} />
<Route path="/search/:keyword" element={<SearchPage />} />
<Route path="register" element={<Register />} />
<Route path="cart" element={<Cart />} />
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/components/AdminRouteProtection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useContext } from "react";
import { Navigate } from "react-router-dom";
import { UserContext } from "./Layout";
import { Role } from "../interface/types";

function AdminRouteProtection({ children }: { children: React.ReactNode }) {
const userInfo = useContext(UserContext);

if (!userInfo.isLoggedIn || userInfo.role !== Role.Admin) {
return <Navigate to="/" replace />;
}

return <>{children}</>;
}

export default AdminRouteProtection;
67 changes: 67 additions & 0 deletions frontend/src/components/ArticleReservationCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Card, Divider } from "antd";
import { ReservationData } from "../interface/types";
import ValidateReservationButton from "./ValidateReservationButton";
import CancelReservationButton from "./CancelReservationButton";
import DeleteArticleReservationButton from "./DeleteArticleReservationButton";

export const ArticleReservationCard = ({
reservationData,
}: {
reservationData: ReservationData;
}) => {
const articles = reservationData.reservation.articles;

return (
<>
{reservationData.reservation.status === "pending" ? (
<Card title={"Détails de votre réservation"} style={{ width: 500 }}>
{articles.map((article) => (
<Card style={{ margin: 20 }} key={article.product?.id}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<div style={{ display: "flex", alignItems: "center" }}>
<img
src={article.product?.imgUrl}
alt="product img"
style={{ height: 100, marginRight: 20 }}
/>
<div>
<p style={{ margin: 0, fontWeight: "bold" }}>
{article.product?.name}
</p>
<p style={{ margin: 0 }}>{article.product?.price}</p>
</div>
</div>
<DeleteArticleReservationButton
articleId={article.id}
reservationData={reservationData}
/>
</div>
</Card>
))}
<div style={{ display: "flex", justifyContent: "space-around" }}>
{reservationData.reservation.status === "pending" && (
<ValidateReservationButton
reservation={reservationData.reservation}
/>
)}
{reservationData.reservation.status === "pending" && (
<CancelReservationButton
reservation={reservationData.reservation}
/>
)}
</div>
</Card>
) : (
<h1>Aucune réservation en cours.</h1>
)}

<Divider dashed />
</>
);
};
45 changes: 45 additions & 0 deletions frontend/src/components/CancelReservationButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useMutation } from "@apollo/client";
import { Button, message, Popconfirm } from "antd";
import { CANCEL_RESERVATION } from "../graphql/mutations";
import { ReservationData } from "../interface/types";
import {
GetCurrentReservationByUserIdDocument,
GetReservationsByUserIdDocument,
} from "../generated/graphql-types";

function CancelReservationButton({ reservation }: ReservationData) {
const [cancelReservation] = useMutation(CANCEL_RESERVATION, {
onCompleted: () => {
message.success("La réservation a bien été annulée.");
},
onError: () => {
message.error("Une erreur est survenue lors de l'annulation.");
},
refetchQueries: [
GetReservationsByUserIdDocument,
GetCurrentReservationByUserIdDocument,
],
});

return (
<>
<Popconfirm
title="Annuler cette réservation ?"
description="La réservation sera définitivement annulée."
okText="Oui"
cancelText="Non"
onConfirm={() =>
cancelReservation({
variables: {
reservationId: reservation.id.toString(),
},
})
}
>
<Button danger>Annuler la réservation</Button>
</Popconfirm>
</>
);
}

export default CancelReservationButton;
12 changes: 7 additions & 5 deletions frontend/src/components/CurrentReservation.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { ReservationData } from "../interface/types";
import { ReservationCard } from "./ReservationCard";
import { ArticleReservationCard } from "./ArticleReservationCard";

export const CurrentReservation = ({ reservationData }: { reservationData: ReservationData }) => {
return (
<ReservationCard reservationData={reservationData} />
);
export const CurrentReservation = ({
reservationData,
}: {
reservationData: ReservationData;
}) => {
return <ArticleReservationCard reservationData={reservationData} />;
};

export default CurrentReservation;
1 change: 1 addition & 0 deletions frontend/src/components/DeleteArticleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function DeleteProductButton({ articleId }: { articleId: string }) {
title="Supprimer cet article ? "
description="Toutes les données le concernant seront perdues."
okText="Oui"
className="bg-red-500 text-white hover:bg-red-600 focus:outline-none"
cancelText="Non"
onConfirm={() =>
deleteArticle({
Expand Down
Loading

0 comments on commit 1f749e9

Please sign in to comment.