20 posts

15 / 20
·Tech·Java

Project

Full page →
Kotlin

Project List

Team Project

Personal Project

  • [Stock information collector using Gemini - TodayStock](/manage/2023/09/18/Project.html#Stock information-collector using Gemini---todaystock)
  • [Daily development learning worksheet - TOCO](/manage/2023/09/18/Project.html#daily-development learning-worksheet---toco)



Stock information collector using Gemini - TodayStock

GitHub
todaystock
Project category
Backend personal project
Project Period
2024.08 ~ In progress
Technology Stack

Kotlin Spring Boot 3.3.4 JPA Kotlin JDSL Gradle 8.8 PostgreSQL 15.3 Docker Flyway GCP Compute Engine, Cloud SQL, Artifact Registry, Vertex AI API (Gemini Pro 1.5) Spring Rest API
### Project introduction **TodayStock** had the experience of missing the appropriate buying and selling timing due to not obtaining and managing various information while trading American and domestic stocks. And I recently created a project to apply what I learned. - Use Gemini as GCP and Google Cloud Vertex AI model - Project applying Kotlin, Spring, and Kotlin DSL - All flows from development to CI/CD ### Key considerations #### 1. GCP and Gemini While doing Google Study Jam 2024, I only used Gemini on the GCP Console site, and I created a project considering how I could use it in my own project. (I have several experiences developing using AWS, but this was my first time applying GCP.) Functionally, the main purpose is to search and retrieve recent news from Naver that matches a specific keyword, and then have Gemini organize what you need to know in the news based on the keyword. During the creation process, there was a limit to the number of Vertex AI requests that could be used for free and without approval, so we limited it to one request and updated one of the latest information per search.

When testing Postman, we confirmed that it returns 10 of the latest news contents, including one piece of additional information, as shown below. Currently, the operation has been temporarily turned off.

2. Application of Kotlin JDSL query builder

I had a strong desire to apply Korea's open source with the DSL created by Line, and I liked the fact that I could develop in a more Kotlin-friendly way than QueryDSL. I chose it because there was no need to consider creating a separate metamodel and there were many reviews that it was compatible with JPA.

3. Other articles that record concerns and solutions that arose during the development process

  • [🚴🏽 How do I take environment variables?]({{site.baseurl}}/devops/2023/05/08/DevOps.html#-Environment variables-How do I take them?)



Region-based chat service for developers - DevMingle

GitHub
dev-mingle-server
Project category
Backend Team Project
Project Duration
2023.08 ~ 2023.11
Number of team members
Backend 4 people
Technology Stack

Alpine Linux 3.16 Java 17 Spring Boot 3.1.3 JPA Gradle 8.2.1 PostgreSQL 15.3 Redis MongoDB Flyway AWS EC2, S3
### Project introduction **DevMingle** is a bulletin board and chat platform service where developers can communicate. Bulletin boards and chats are operated on a regional basis, and you can check bulletin boards in two locations based on the subscriber's registered location and the current user's location. The chat implementation uses MongoDB with a sub/pub structure using STOMP. ### Project role I was in charge of the user section of the project. Responsible for user entity, member-related API, and Spring security authentication/authorization. We also used access tokens and refresh tokens based on token authentication through JWT, and are currently developing Oauth sign-up and login using Google and GitHub. ### Key considerations #### 1. GitHub Convention for Collaboration One of the big goals of this project was to develop the habit of creating and following proper conventions while using GitHub. The GitHub convention we decided on was similar for Commit, PR, and Issue registration. And I worked by categorizing the things that needed to be done in detail into Issues.

Additionally, development work was carried out by dividing the develop branch into API development units, using branches with feature, and then merging them back into the ````develop``` branch.

2. Token issuance and verification

Validation is verified using the access and refresh tokens entered as headers in the Token filter. At this time, if the access token has expired, the refresh token can be viewed and reissued without a separate refresh token issuance request. And the newly issued token is so that the new token is reflected in the response. setAuthenticationTokens was processed. TokenFilter.java

tokenProvider.validateToken(accessToken);
if(tokenProvider.expiredToken(accessToken) && !tokenProvider.expiredToken(refreshToken)){
tokenProvider.validateToken(refreshToken);
Map<String,String> map = tokenProvider.rebuildToken(refreshToken);
accessToken = map.get("accessToken");
refreshToken = map.get("refreshToken");
}
setSecurityContextHolder(accessToken);
tokenProvider.setAuthenticationTokens(response, accessToken, refreshToken);
filterChain.doFilter(request, response);

Based on the value contained in the token, we put it in LoginUser, an implementation of UserDetails````, so that @AuthenticationPrincipal```` can be used at the Controller level as a Security Context Holder. Even when a member's nickname or password is changed, the token will be reissued to reflect this and returned.

@PostMapping("/password/reset/confirm")
public ResponseEntity<ApiResponse> isRandomPassword(@AuthenticationPrincipal LoginUser loginUser){
boolean isRandomPassword = usersRepository.findIsRandomPasswordById(loginUser.getId());
return responseBuilder(isRandomPassword, HttpStatus.OK);
}

3. Processing of URLs that do not require authentication

It is complicated to list all URLs that do not require authentication in the security config, and the code is not clean. Furthermore, if separate verification in the token filter is not required, URL duplication will occur. Because unnecessary maintenance may occur in the process of updating the same URLs on both sides, I thought it would be better to manage them in one place, so I managed them separately in ``application-auth.yml. If you look at the detailed code, it is registered in application-auth.ymlas follows. URLs were classified into those withprefix(/api/v1)``` and those without.

url:
permit:
get:
- /
- /actuator/health
prefixGet:
- /categories
- /posts/**
prefixPost:
- /users/login
- /users
- /users/otp
- /users/nickname

Afterwards, these URLs were loaded into the Configuration list with priority over SecurityConfig. Each list of get and prefixGet is composed of a single list with a prefix attached. In other words, it was separated into URLs to be mapped to GET and POST.

@ConfigurationProperties(prefix = "url.permit")

And apply permitAll() to allow access without authentication in each SecurityConfig. TokenFilter processed it to proceed to another filter without token verification. Because the token validity is checked rather than the authentication process and the user information is entered into the ``Security Context Holder addFilterBefore allows the token verification process to precede ````UsernamePasswordAuthenticationFilter. SecurityConfig.java

http.httpBasic(HttpBasicConfigurer::disable)
.cors(Customizer.withDefaults())
.csrf(CsrfConfigurer::disable)
.sessionManagement(config -> config.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests(auth -> {
for (String url : urlProperties.getGet()) {
auth.requestMatchers("GET", url).permitAll();
}
for (String url : urlProperties.getPost()) {
auth.requestMatchers("POST", url).permitAll();
}
auth.anyRequest().authenticated();
})
.exceptionHandling(exception -> exception
.authenticationEntryPoint(customEntryPoint)
.accessDeniedHandler(customEntryPoint)
);

TokenFilter.java

private boolean doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
boolean b = false;
String method = request.getMethod();
String url = request.getRequestURI();
if (("GET".equals(method) && matchUrl(urlProperties.getGet(), request))
|| ("POST".equals(method) && matchUrl(urlProperties.getPost(), request))) {
filterChain.doFilter(request, response);
b=true;
}
return b;
}
private boolean matchUrl(List<String> list, HttpServletRequest request) {
AntPathRequestMatcher matcher;
for (String str : list) {
matcher = new AntPathRequestMatcher(str);
if (matcher.matches(new HttpServletRequestWrapper(request))) {
return true;
}
}
return false;
}



데일리 개발학습 학습지 - TOCO

GitHub
toco-java
프로젝트 구분
Fullstack 개인 프로젝트
프로젝트 기간
2023.06 ~ 2023.07
기술스택

Java 17 Spring Boot 3.0.1 JPA Gradle Querydsl MySQL JUnit5 JS
### 프로젝트 소개 **TOCO**는 개발자을 배우고 싶은 사람들에게 정한 날짜에 정기적으로 메일링 서비스로 주어진 강의를 스스로 학습할 수 있도록 가이드를 제공하는 서비스입니다. 지금은 Fullstack으로 되어 있지만 향후에는 프론트와 백단을 나눠서 배포하고 버전 관리할 예정입니다. ### 주요 고려사항 #### 1. Repository Custom 인터페이스의 분리 Querydsl을 공부하면서 처음 적용한 프로젝트였는데 여기서 Custom 인터페이스를 이용하는 방법과 그냥 Impl로 만들어버리는 방법에 대해 고민했습니다. 사실 인터페이스의 분리가 오히려 불편하고 코드가 복잡해보인다는 말도 있었습니다. Of course, I thought that this might be possible in a large-scale project, but I thought that if I manage the method for applying Querydsl separately, such as ``EducationContentCustom````, I think I can prevent any unfortunate events that may occur due to not implementing it in Impl when maintenance or functions are added in the future. I applied it using the Custom interface. ```java @RequiredArgsConstructor public class EducationContentRepositoryImpl implements EducationContentCustom { private final JPAQueryFactory jpaQueryFactory; @Override public EducationContent getNextUuid(int nextChapter, Education education) { return jpaQueryFactory .selectFrom(educationContent) .where(chapterEq(nextChapter),educationEq(education)) .fetchOne(); } } ``` #### 2. Concerns about relationships Association relationships are so simple that they can be said to be the flower of JPA, and various functions can be conveniently used through annotations. In this project, there was no ``OneToOne``` relationship, and there were many relationships established using ````ManyToOne``` and ````OneToMany```. So how far will this connection go? I don't think there is a right answer to this. In fact, the correlation can be adjusted to some extent as needed, but avoid lazy loading issues due to unnecessary correlation. For example, if the part corresponding to ```Many``` is ```null```, the value may not be loaded or there may be performance issues because the connected data is large. Here, while designing the ERD, we considered these aspects and did not establish a relationship between educational services and service types, which are relationships in areas that are thought to be infrequently used. This is because there are many entities related to educational services and when bringing in educational services, major and subcategories of service types are not necessary in most cases. (Already retrieves a list of educational services based on category)

//: # (

) //: # ( ) //: # (

)

Comments

No comments yet. Be the first!