Project

Β· TechΒ· Java
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!

    164 posts in ν…Œν¬

    1–5 / 164