Skip to content
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ application-secret.properties
application-local.yml
application-dev.yml
application-prod.yml
application-test.yml

### Java ###
# Compiled class file
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.pingle.pingleserver.config;

import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
@EnableConfigurationProperties
public class DataSourceConfiguration {

@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.jpa")
DataSource jpaDataSource() {
return DataSourceBuilder
.create()
.build();
}

@Bean
@Qualifier("lockDataSource")
@ConfigurationProperties(prefix = "spring.datasource.lock")
DataSource lockDataSource() {
return DataSourceBuilder
.create()
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.pingle.pingleserver.dto.response.ParticipantsResponse;
import org.pingle.pingleserver.dto.response.SearchResponse;
import org.pingle.pingleserver.dto.type.SuccessMessage;
import org.pingle.pingleserver.service.MeetingParticipateFacade;
import org.pingle.pingleserver.service.MeetingService;
import org.pingle.pingleserver.service.PinService;

Expand All @@ -28,6 +29,7 @@
public class MeetingController implements MeetingApi {

private final MeetingService meetingService;
private final MeetingParticipateFacade meetingParticipateFacade;
private final UserMeetingService userMeetingService;
private final PinService pinService;

Expand All @@ -43,7 +45,7 @@ public ApiResponse<?> createMeeting(@Valid @RequestBody MeetingRequest request,

@PostMapping("/{meetingId}/join")
public ApiResponse<?> participateMeeting (@UserId Long userId, @PathVariable("meetingId") Long meetingId) {
Long userMeetingId = userMeetingService.participateMeeting(userId, meetingId);
meetingParticipateFacade.participateWithLock(userId, meetingId);
return ApiResponse.success(SuccessMessage.CREATED);
}

Expand Down
21 changes: 15 additions & 6 deletions src/main/java/org/pingle/pingleserver/domain/Meeting.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
package org.pingle.pingleserver.domain;

import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import org.pingle.pingleserver.domain.enums.MCategory;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.pingle.pingleserver.domain.enums.MCategory;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/pingle/pingleserver/domain/UserMeeting.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@

@Entity
@Getter
@Table(
uniqueConstraints = {
@UniqueConstraint(name = "unique_user_meeting", columnNames = {"user_id", "meeting_id"})
}
)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserMeeting extends BaseTimeEntity implements Comparable<UserMeeting> {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.pingle.pingleserver.service;

public interface LockManager {
void executeWithLock(String key, Runnable runnable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.pingle.pingleserver.service;

import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class MeetingParticipateFacade {
private final UserMeetingService userMeetingService;
private final LockManager lockManager;

public void participateWithLock(Long userId, Long meetingId) {
lockManager.executeWithLock(meetingId.toString(),
() -> userMeetingService.participateMeeting(userId, meetingId));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package org.pingle.pingleserver.service;

import lombok.RequiredArgsConstructor;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.pingle.pingleserver.domain.Meeting;
import org.pingle.pingleserver.domain.Pin;
import org.pingle.pingleserver.domain.UserMeeting;
Expand All @@ -20,12 +24,7 @@
import org.pingle.pingleserver.utils.SlackUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.pingle.pingleserver.service;

import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.pingle.pingleserver.domain.Meeting;
import org.pingle.pingleserver.domain.Team;
Expand All @@ -9,13 +10,14 @@
import org.pingle.pingleserver.dto.type.ErrorMessage;
import org.pingle.pingleserver.exception.CustomException;
import org.pingle.pingleserver.repository.*;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;


@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserMeetingService {
private final UserRepository userRepository;
private final TeamRepository teamRepository;
Expand All @@ -34,23 +36,26 @@ public Long addOwnerToMeeting(Long userId, Meeting meeting) {
.build()).getId();
}

//유저가 그룹에 있는지
public void verifyUser(Long userId, Long groupId) {
User user = userRepository.findByIdOrThrow(userId);
Team team = teamRepository.findByIdOrThrow(groupId);
userTeamRepository.findByUserAndTeam(user, team)
.orElseThrow(() -> new CustomException(ErrorMessage.GROUP_PERMISSION_DENIED));
}

@Transactional
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Long participateMeeting(Long userId, Long meetingId) {
Meeting meeting = meetingRepository.findById(meetingId).orElseThrow(() -> new CustomException(ErrorMessage.MEETING_NOT_FOUND));
if(isParticipating(userId, meeting))
throw new CustomException(ErrorMessage.RESOURCE_CONFLICT);
if((getCurParticipants(meeting)) >= meeting.getMaxParticipants())
throw new CustomException(ErrorMessage.RESOURCE_CONFLICT);
User user = userRepository.findByIdOrThrow(userId);
return userMeetingRepository.save(new UserMeeting(user, meeting, MRole.PARTICIPANTS)).getId();
Long participateId;
try{
participateId = userMeetingRepository.save(new UserMeeting(user, meeting, MRole.PARTICIPANTS)).getId();
} catch (DataIntegrityViolationException e) {
throw new CustomException(ErrorMessage.RESOURCE_CONFLICT);
}
return participateId;
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.pingle.pingleserver.service.lock;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.pingle.pingleserver.dto.type.ErrorMessage;
import org.pingle.pingleserver.exception.CustomException;
import org.pingle.pingleserver.service.LockManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class DatabaseLockManager implements LockManager {

private final DataSource lockDataSource;

public DatabaseLockManager(@Qualifier("lockDataSource") DataSource lockDataSource) {
this.lockDataSource = lockDataSource;
}

public void executeWithLock(String key, Runnable runnable) {
try (Connection connection = lockDataSource.getConnection()) {
try {
getLock(connection, key);
runnable.run();
} finally {
releaseLock(connection, key);
}
} catch (SQLException e) {
throw new CustomException(ErrorMessage.INTERNAL_SERVER_ERROR);
}
}

private void getLock(final Connection connection, final String key) {
String sql = "SELECT get_lock(?, ?)";

try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, key);
stmt.setInt(2, 3000);
stmt.executeQuery();
} catch (SQLException e) {
throw new CustomException(ErrorMessage.INTERNAL_SERVER_ERROR);
}
}

private void releaseLock(final Connection connection, final String key) {
String sql = "SELECT RELEASE_LOCK(?)";

try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, key);
stmt.executeQuery();
} catch (SQLException e) {
throw new CustomException(ErrorMessage.INTERNAL_SERVER_ERROR);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

@SpringBootTest
@ActiveProfiles("test")
class PingleserverApplicationTests {

@Test
Expand Down
14 changes: 14 additions & 0 deletions src/test/java/org/pingle/pingleserver/ServiceSliceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.pingle.pingleserver;

import org.junit.jupiter.api.extension.ExtendWith;
import org.pingle.pingleserver.util.DatabaseCleanerExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.ActiveProfiles;

@ExtendWith(DatabaseCleanerExtension.class)
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@ActiveProfiles("test")
public abstract class ServiceSliceTest {
}

15 changes: 15 additions & 0 deletions src/test/java/org/pingle/pingleserver/fixture/MeetingFixture.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.pingle.pingleserver.fixture;

import java.time.LocalDateTime;
import org.pingle.pingleserver.domain.Meeting;
import org.pingle.pingleserver.domain.Pin;
import org.pingle.pingleserver.domain.enums.MCategory;

public abstract class MeetingFixture {
private MeetingFixture() {
}

public static Meeting create(Pin pin, int maxParticipants , LocalDateTime start, LocalDateTime end) {
return new Meeting(pin, MCategory.MULTI, "name", maxParticipants, "link", start, end);
}
}
15 changes: 15 additions & 0 deletions src/test/java/org/pingle/pingleserver/fixture/PinFixture.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.pingle.pingleserver.fixture;

import org.pingle.pingleserver.domain.Address;
import org.pingle.pingleserver.domain.Pin;
import org.pingle.pingleserver.domain.Point;
import org.pingle.pingleserver.domain.Team;

public abstract class PinFixture {
private PinFixture() {
}

public static Pin create(Team team) {
return new Pin(team, new Point(1.0, 1.0), new Address("add1", "add2"), "name");
}
}
13 changes: 13 additions & 0 deletions src/test/java/org/pingle/pingleserver/fixture/TeamFixture.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.pingle.pingleserver.fixture;

import org.pingle.pingleserver.domain.Team;
import org.pingle.pingleserver.domain.enums.TKeyword;

public abstract class TeamFixture {
private TeamFixture() {
}

public static Team create() {
return new Team("team1", "team1", "code", TKeyword.ETC);
}
}
14 changes: 14 additions & 0 deletions src/test/java/org/pingle/pingleserver/fixture/UserFixture.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.pingle.pingleserver.fixture;

import org.pingle.pingleserver.domain.User;
import org.pingle.pingleserver.domain.enums.Provider;
import org.pingle.pingleserver.domain.enums.URole;

public abstract class UserFixture {
private UserFixture() {
}

public static User create() {
return new User("serial", "name", "email", Provider.APPLE, URole.USER, "refreshToken");
}
}
Loading