Skip to content

Commit

Permalink
[BE] 해커톤 진행사항 병합 (#78)
Browse files Browse the repository at this point in the history
Co-authored-by: ehtjsv2 <[email protected]>
Co-authored-by: jimi567 <[email protected]>
Co-authored-by: 이충렬(트레) <[email protected]>
  • Loading branch information
4 people authored Jul 17, 2024
1 parent 52c4eab commit 621cc1f
Show file tree
Hide file tree
Showing 39 changed files with 1,065 additions and 28 deletions.
50 changes: 26 additions & 24 deletions backend/build.gradle
Original file line number Diff line number Diff line change
@@ -1,53 +1,55 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.1'
id 'io.spring.dependency-management' version '1.1.5'
id 'org.asciidoctor.jvm.convert' version '3.3.2'
id 'java'
id 'org.springframework.boot' version '3.3.1'
id 'io.spring.dependency-management' version '1.1.5'
id 'org.asciidoctor.jvm.convert' version '3.3.2'
}

group = 'com.woowacourse'
version = '0.0.1-SNAPSHOT'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
mavenCentral()
mavenCentral()
}

ext {
set('snippetsDir', file("build/generated-snippets"))
set('snippetsDir', file("build/generated-snippets"))
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
testImplementation 'io.rest-assured:rest-assured:5.4.0'

testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
outputs.dir snippetsDir
useJUnitPlatform()
outputs.dir snippetsDir
useJUnitPlatform()
}

tasks.named('asciidoctor') {
inputs.dir snippetsDir
dependsOn test
inputs.dir snippetsDir
dependsOn test
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
@SpringBootApplication
public class FriendoglyApplication {

public static void main(String[] args) {
SpringApplication.run(FriendoglyApplication.class, args);
}
public static void main(String[] args) {
SpringApplication.run(FriendoglyApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.woowacourse.friendogly;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handle(RuntimeException exception) {
return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<String> handle(HttpMessageNotReadableException exception) {
return new ResponseEntity<>("읽을 수 없는 HTTP 메세지입니다.", HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<String> handle(HttpRequestMethodNotSupportedException exception) {
return new ResponseEntity<>("지원하지 않는 HTTP 메서드입니다.", HttpStatus.METHOD_NOT_ALLOWED);
}

@ExceptionHandler(NoResourceFoundException.class)
public ResponseEntity<String> handle(NoResourceFoundException exception) {
return new ResponseEntity<>("존재하지 않는 요청 경로입니다.", HttpStatus.NOT_FOUND);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<String> handle(Exception exception) {
return new ResponseEntity<>("예기치 못한 예외가 발생하였습니다. 다시 실행해주세요.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.woowacourse.friendogly.footprint.controller;

import com.woowacourse.friendogly.footprint.dto.request.FindNearFootprintRequest;
import com.woowacourse.friendogly.footprint.dto.request.SaveFootprintRequest;
import com.woowacourse.friendogly.footprint.dto.response.FindNearFootprintResponse;
import com.woowacourse.friendogly.footprint.service.FootprintCommandService;
import com.woowacourse.friendogly.footprint.service.FootprintQueryService;
import java.net.URI;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/footprints")
public class FootprintController {

private final FootprintCommandService footprintCommandService;
private final FootprintQueryService footprintQueryService;

@PostMapping
public ResponseEntity<Void> save(@RequestBody SaveFootprintRequest request) {
Long id = footprintCommandService.save(request);
return ResponseEntity.created(URI.create("/footprints/" + id))
.build();
}

@GetMapping("/near")
public List<FindNearFootprintResponse> findNear(
@RequestParam double lat,
@RequestParam double lng
) {
return footprintQueryService.findNear(new FindNearFootprintRequest(lat, lng));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.woowacourse.friendogly.footprint.domain;

import com.woowacourse.friendogly.member.domain.Member;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import java.time.LocalDateTime;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
public class Footprint {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne
private Member member;

@Embedded
private Location location;

private LocalDateTime createdAt;

private boolean isDeleted;

@Builder
public Footprint(Member member, Location location) {
this.member = member;
this.location = location;
this.createdAt = LocalDateTime.now();
this.isDeleted = false;
}

public boolean isNear(Location location) {
return this.location.isNear(location);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.woowacourse.friendogly.footprint.domain;

import jakarta.persistence.Embeddable;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Embeddable
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Location {

private static final int BOUNDARY_RADIUS_METER = 1000;

private double latitude;
private double longitude;

public boolean isNear(Location other) {
return distance(this.latitude, this.longitude, other.latitude, other.longitude) <= BOUNDARY_RADIUS_METER;
}

private double distance(double lat1, double lon1, double lat2, double lon2) {
double theta = lon1 - lon2;
double dist = Math.sin(degToRad(lat1)) * Math.sin(degToRad(lat2)) +
Math.cos(degToRad(lat1)) * Math.cos(degToRad(lat2)) * Math.cos(degToRad(theta));

dist = Math.acos(dist);
dist = radToDeg(dist);
dist = dist * 60 * 1.1515 * 1609.344;

return Math.abs(dist);
}

private double degToRad(double deg) {
return (deg * Math.PI / 180.0);
}

private double radToDeg(double rad) {
return (rad * 180 / Math.PI);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.woowacourse.friendogly.footprint.dto.request;

public record FindNearFootprintRequest(double latitude, double longitude) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.woowacourse.friendogly.footprint.dto.request;

public record SaveFootprintRequest(Long memberId, double latitude, double longitude) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.woowacourse.friendogly.footprint.dto.response;

import com.woowacourse.friendogly.footprint.domain.Footprint;
import java.time.LocalDateTime;

public record FindNearFootprintResponse(
Long memberId,
double latitude,
double longitude,
LocalDateTime createdAt
) {

public static FindNearFootprintResponse from(Footprint footprint) {
return new FindNearFootprintResponse(
footprint.getMember().getId(),
footprint.getLocation().getLatitude(),
footprint.getLocation().getLongitude(),
footprint.getCreatedAt()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.woowacourse.friendogly.footprint.repository;

import com.woowacourse.friendogly.footprint.domain.Footprint;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

public interface FootprintRepository extends JpaRepository<Footprint, Long> {

List<Footprint> findByCreatedAtAfter(LocalDateTime since);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.woowacourse.friendogly.footprint.service;

import com.woowacourse.friendogly.footprint.domain.Footprint;
import com.woowacourse.friendogly.footprint.domain.Location;
import com.woowacourse.friendogly.footprint.dto.request.SaveFootprintRequest;
import com.woowacourse.friendogly.footprint.repository.FootprintRepository;
import com.woowacourse.friendogly.member.domain.Member;
import com.woowacourse.friendogly.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class FootprintCommandService {

private final FootprintRepository footprintRepository;
private final MemberRepository memberRepository;

public Long save(SaveFootprintRequest request) {
Member member = memberRepository.findById(request.memberId())
.orElseThrow(() -> new IllegalArgumentException("멤버 없음"));

Footprint footprint = footprintRepository.save(
Footprint.builder()
.member(member)
.location(new Location(request.latitude(), request.longitude()))
.build()
);

return footprint.getId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.woowacourse.friendogly.footprint.service;

import com.woowacourse.friendogly.footprint.domain.Footprint;
import com.woowacourse.friendogly.footprint.domain.Location;
import com.woowacourse.friendogly.footprint.dto.request.FindNearFootprintRequest;
import com.woowacourse.friendogly.footprint.dto.response.FindNearFootprintResponse;
import com.woowacourse.friendogly.footprint.repository.FootprintRepository;
import java.time.LocalDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class FootprintQueryService {

private static final int HOURS_AGO = 24;

private final FootprintRepository footprintRepository;

public List<FindNearFootprintResponse> findNear(FindNearFootprintRequest request) {
LocalDateTime since = LocalDateTime.now().minusHours(HOURS_AGO);
List<Footprint> recentFootprints = footprintRepository.findByCreatedAtAfter(since);

Location currentLocation = new Location(request.latitude(), request.longitude());

return recentFootprints.stream()
.filter(footprint -> footprint.isNear(currentLocation))
.map(FindNearFootprintResponse::from)
.toList();
}
}
Loading

0 comments on commit 621cc1f

Please sign in to comment.