Skip to content

Commit

Permalink
Merge pull request #412 from bounswe/frontend/feature/364-Implement_P…
Browse files Browse the repository at this point in the history
…rofile_Page
  • Loading branch information
mmtftr authored Oct 20, 2024
2 parents da14c25 + 7d5a378 commit a0190e6
Show file tree
Hide file tree
Showing 174 changed files with 23,406 additions and 1,137 deletions.
10 changes: 2 additions & 8 deletions .github/workflows/backend_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,5 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Build the Docker image
run: docker build -t backend-test -f backend/Dockerfile.dev ./backend

- name: List Full Tree
run: ls -R

- name: Run the tests
run: docker run backend-test mvn test
- name: Test with Maven
run: docker compose -f dev.yml run --rm backend mvn test
33 changes: 33 additions & 0 deletions .github/workflows/mobile_deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
on:
push:
branches:
- main

jobs:
build:
name: Build Android APK
runs-on: ubuntu-latest
defaults:
run:
working-directory: mobile
steps:
- name: 🏗 Setup repo
uses: actions/checkout@v3

- name: 🏗 Setup Node
uses: actions/setup-node@v3
with:
node-version: 18.x
cache: yarn

- name: 🏗 Setup EAS
uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}

- name: 📦 Install dependencies
run: yarn install

- name: 🚀 Build app
run: eas build --non-interactive -p android
2 changes: 1 addition & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
nodeLinker: node-modules
nodeLinker: node-modules
36 changes: 18 additions & 18 deletions archive/frontend/src/routes/recipe.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import { Button } from "@/components/ui/button";
import LinkIcon from "@/assets/Icon/General/Link.svg?react";
import RatingInput from "@/components/RatingInput";
import Serving from "@/assets/Icon/General/Serving.svg?react";
import Clock from "@/assets/Icon/General/Clock.svg?react";
import Allergies from "@/assets/Icon/General/Allergies.svg?react";
import Clock from "@/assets/Icon/General/Clock.svg?react";
import Food from "@/assets/Icon/General/Food.svg?react";
import LinkIcon from "@/assets/Icon/General/Link.svg?react";
import Serving from "@/assets/Icon/General/Serving.svg?react";
import RatingInput from "@/components/RatingInput";
import { Button } from "@/components/ui/button";
// import MeatDish from "@/assets/Icon/Food/MeatDish.svg?react";
import { Flag, StarIcon, Trash } from "lucide-react";
import BookmarkButton from "@/components/BookmarkButton";
import { Bookmarkers } from "@/components/Bookmarkers";
import { AddComment } from "@/components/Comment";
import { Comments } from "@/components/CommentSection";
import ErrorAlert from "@/components/ErrorAlert";
import FollowButton from "@/components/FollowButton";
import { FullscreenLoading } from "@/components/FullscreenLoading";
import { toast } from "@/components/ui/use-toast";
import {
useDeleteRecipeById,
useGetRecipeById,
useRateRecipe,
} from "@/services/api/semanticBrowseComponents";
import { Link, useParams } from "react-router-dom";
import { FullscreenLoading } from "@/components/FullscreenLoading";
import { useState } from "react";
import ErrorAlert from "@/components/ErrorAlert";
import { Bookmarkers } from "@/components/Bookmarkers";
import useAuthStore from "@/services/auth";
import FollowButton from "@/components/FollowButton";
import BookmarkButton from "@/components/BookmarkButton";
import { toast } from "@/components/ui/use-toast";
import { AddComment } from "@/components/Comment";
import { Comments } from "@/components/CommentSection";
import { UserSummary } from "@/services/api/semanticBrowseSchemas";
import useAuthStore from "@/services/auth";
import { Flag, StarIcon, Trash } from "lucide-react";
import { useState } from "react";
import { Link, useParams } from "react-router-dom";

export default function RecipePage() {
const { recipeId } = useParams();
Expand All @@ -43,7 +43,7 @@ export default function RecipePage() {
const [optimisticRating, setOptimisticRating] = useState<number | null>(null);

const { mutateAsync } = useRateRecipe({
onMutate: async (rating) => {
onMutate: async (rating: { body: { rating: number } }) => {
setOptimisticRating(rating.body?.rating || 0);
},
onSuccess: () => {
Expand Down
3 changes: 3 additions & 0 deletions archive/swagger/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1502,6 +1502,7 @@ components:
field:
type: string
description: If empty, indicates an error not related to any field.

SuccessResponseObject:
description: OK
type: object
Expand All @@ -1521,6 +1522,7 @@ components:
oneOf:
- type: object
- type: array

ErrorResponseObject:
description: Response with errors
type: object
Expand All @@ -1543,6 +1545,7 @@ components:
type: array
items:
$ref: "#/components/schemas/ApiError"

responses:
OkResponse:
description: OK
Expand Down
44 changes: 44 additions & 0 deletions backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,47 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.2.1</version> <!-- Use the latest version -->
</dependency>


<!-- For database & JPA dependencies -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- For database & JPA dependencies -->

<!-- For JWT dependencies -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
</dependency>
<!-- For JWT dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
Expand All @@ -58,6 +93,15 @@
<version>1.18.32</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.group1.programminglanguagesforum.Config;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

@Component
public class CorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:*", // Allows any port from localhost
"https://*.ondigitalocean.app")); // Allows the specific DigitalOcean URL;
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.group1.programminglanguagesforum.Config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.group1.programminglanguagesforum.DTOs.Responses.ErrorResponse;
import com.group1.programminglanguagesforum.DTOs.Responses.GenericApiResponse;
import com.group1.programminglanguagesforum.Repositories.UserRepository;
import com.group1.programminglanguagesforum.Services.CustomUserDetailsService;
import com.group1.programminglanguagesforum.Services.JwtService;
import io.jsonwebtoken.ExpiredJwtException;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Arrays;

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final UserRepository userRepository;
private final JwtService jwtAuthenticationService;
private final CustomUserDetailsService customUserDetailsService;
private final ObjectMapper objectMapper;

@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
try {
final String authorizationHeader = request.getHeader("Authorization");
final String jwt;
final String username;
if (StringUtils.isEmpty(authorizationHeader) || !org.springframework.util.StringUtils.startsWithIgnoreCase(authorizationHeader, "Bearer ")) {
logger.info("No JWT token found in request headers");
System.out.println("Request: " + request.getMethod() + " " + request.getRequestURI());
filterChain.doFilter(request, response);
System.out.println("Response Status: " + response.getStatus());
return;
}
jwt = authorizationHeader.substring(7);
username = jwtAuthenticationService.extractUsername(jwt);
if (StringUtils.isNotEmpty(username)) {
UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);

// Authenticate the user if the token is valid
if (jwtAuthenticationService.isTokenValid(jwt, userDetails)) {
SecurityContext context =
SecurityContextHolder.createEmptyContext();
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
context.setAuthentication(authToken);
SecurityContextHolder.setContext(context);
}
}


} catch (ExpiredJwtException e) {
GenericApiResponse<Void> genericApiResponse = GenericApiResponse.<Void>builder()
.status(HttpServletResponse.SC_UNAUTHORIZED)
.message("Token has expired")
.error(
ErrorResponse.builder()
.errorMessage("Token has expired")
.stackTrace(Arrays.toString(e.getStackTrace()))
.build()
)
.build();

response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(objectMapper.writeValueAsString(genericApiResponse));
return;
}
catch (Exception e){
GenericApiResponse<Void> genericApiResponse = GenericApiResponse.<Void>builder()
.status(HttpServletResponse.SC_UNAUTHORIZED)
.message("Invalid token")
.error(
ErrorResponse.builder()
.errorMessage("Invalid token")
.stackTrace(Arrays.toString(e.getStackTrace()))
.build()
)
.build();

response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(objectMapper.writeValueAsString(genericApiResponse));
return;
}
filterChain.doFilter(request, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.group1.programminglanguagesforum.Config;

import com.group1.programminglanguagesforum.DTOs.Responses.SelfProfileResponseDto;
import com.group1.programminglanguagesforum.DTOs.Responses.UserProfileResponseDto;
import com.group1.programminglanguagesforum.Entities.User;
import org.modelmapper.ModelMapper;
import org.modelmapper.PropertyMap;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ModelMapperConfig {
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();

// Define a PropertyMap to skip certain fields
modelMapper.addMappings(new PropertyMap<SelfProfileResponseDto, User>() {
@Override
protected void configure() {
skip(destination.getPassword());
skip(destination.getFollowers());
skip(destination.getFollowing());
}
});
modelMapper.addMappings(new PropertyMap <UserProfileResponseDto,User>() {
@Override
protected void configure() {
skip(destination.getPassword());
skip(destination.getFollowers());
skip(destination.getFollowing());

}
});

return modelMapper;
}
}
Loading

0 comments on commit a0190e6

Please sign in to comment.