From 85b97e196ef7f035e0420e4e1c88c56c09d662ed Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Fri, 2 May 2025 16:41:30 +0900 Subject: [PATCH 1/9] =?UTF-8?q?[add]:=20root.module.css=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/root.module.css | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 frontend/src/root.module.css diff --git a/frontend/src/root.module.css b/frontend/src/root.module.css new file mode 100644 index 0000000..14354e4 --- /dev/null +++ b/frontend/src/root.module.css @@ -0,0 +1,27 @@ +:root { + .noto-sans-kr-context { + font-family: "Noto Sans KR", sans-serif; + font-optical-sizing: auto; + font-weight: 400; + font-style: normal; + } + @font-face { + font-family: "Cafe24Moyamoya-Regular-v1.0"; + src: url("https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_231029@1.1/Cafe24Moyamoya-Regular-v1.0.woff2") + format("woff2"); + font-weight: normal; + font-style: normal; + } + --main-green: #49ff24; + --card-toggle-green: #2aff00; + --card-detail-green: #2d791d; + --icon-top-green: #14ae5c; + --icon-detail-green: #14ae5c; + --icon-detail-yellow: #ffcd29; + --icon-detail-red: #ff2c2c; + --background-black: #000000; + --fill-gray: #d9d9d9; + --warn-red: #ff5858; + --text-white: #ffffff; + --border-gray: #c7c7c7; +} From 2626ddfc41521c2810e93fbf57b7d74dc03ffbf6 Mon Sep 17 00:00:00 2001 From: Suhjung Park <145967352+Imggaggu@users.noreply.github.com> Date: Fri, 2 May 2025 18:00:18 +0900 Subject: [PATCH 2/9] Create main.yml --- .github/workflows/main.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..cac5789 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,27 @@ +name: Notify Discord on Push + +on: + push: + +jobs: + notify-discord: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Get last commit info + id: commit + run: | + echo "msg=$(git log -1 --pretty=%s)" >> $GITHUB_OUTPUT + echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + echo "full_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + - name: Send Discord Notification + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + run: | + curl -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\": \"[\`${{ steps.commit.outputs.sha }}\`](https://github.com/${{ github.repository }}/commit/${{ steps.commit.outputs.full_sha }}) [Commit] **${{ steps.commit.outputs.msg }}** by @${{ github.actor }}\"}" \ + "$DISCORD_WEBHOOK_URL" From 342ceb3e08c19861d28bb97f7681305d9061627c Mon Sep 17 00:00:00 2001 From: Suhjung Park <145967352+Imggaggu@users.noreply.github.com> Date: Fri, 2 May 2025 18:17:25 +0900 Subject: [PATCH 3/9] Update main.yml --- .github/workflows/main.yml | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cac5789..1230d3a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,27 +1,18 @@ -name: Notify Discord on Push +name: Notify Discord on PR on: - push: + pull_request: + types: [opened, synchronize] # PR 생성 또는 커밋 추가 시 jobs: notify-discord: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Get last commit info - id: commit - run: | - echo "msg=$(git log -1 --pretty=%s)" >> $GITHUB_OUTPUT - echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - echo "full_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - - name: Send Discord Notification env: DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} run: | curl -H "Content-Type: application/json" \ -X POST \ - -d "{\"content\": \"[\`${{ steps.commit.outputs.sha }}\`](https://github.com/${{ github.repository }}/commit/${{ steps.commit.outputs.full_sha }}) [Commit] **${{ steps.commit.outputs.msg }}** by @${{ github.actor }}\"}" \ + -d "{\"content\": \"[PR #${{ github.event.pull_request.number }}](${{ github.event.pull_request.html_url }}) **${{ github.event.pull_request.title }}** by @${{ github.actor }}\"}" \ "$DISCORD_WEBHOOK_URL" From 36c85b4f8e03b2b5d1c2676834c5418503c1e3c7 Mon Sep 17 00:00:00 2001 From: Suhjung Park <145967352+Imggaggu@users.noreply.github.com> Date: Fri, 2 May 2025 18:19:14 +0900 Subject: [PATCH 4/9] Update main.yml --- .github/workflows/main.yml | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1230d3a..234cd69 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,18 +1,31 @@ -name: Notify Discord on PR +name: Notify Discord on PR events on: pull_request: - types: [opened, synchronize] # PR 생성 또는 커밋 추가 시 + types: [opened, closed, reopened] jobs: notify-discord: runs-on: ubuntu-latest steps: - - name: Send Discord Notification + - name: Determine PR event + id: msg + run: | + if [[ "${{ github.event.action }}" == "opened" ]]; then + echo "message= PR Opened: **${{ github.event.pull_request.title }}**" >> $GITHUB_OUTPUT + elif [[ "${{ github.event.action }}" == "reopened" ]]; then + echo "message= PR Reopened: **${{ github.event.pull_request.title }}**" >> $GITHUB_OUTPUT + elif [[ "${{ github.event.action }}" == "closed" && "${{ github.event.pull_request.merged }}" == "true" ]]; then + echo "message= PR Merged: **${{ github.event.pull_request.title }}**" >> $GITHUB_OUTPUT + else + echo "message= PR Closed without merge: **${{ github.event.pull_request.title }}**" >> $GITHUB_OUTPUT + fi + + - name: Send Discord notification env: DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} run: | curl -H "Content-Type: application/json" \ -X POST \ - -d "{\"content\": \"[PR #${{ github.event.pull_request.number }}](${{ github.event.pull_request.html_url }}) **${{ github.event.pull_request.title }}** by @${{ github.actor }}\"}" \ + -d "{\"content\": \"${{ steps.msg.outputs.message }}\\n[PR #${{ github.event.pull_request.number }}](${{ github.event.pull_request.html_url }}) by @${{ github.actor }}\"}" \ "$DISCORD_WEBHOOK_URL" From 0b3935885bc378b264610873fc43667c32120f61 Mon Sep 17 00:00:00 2001 From: Suhjung Park <145967352+Imggaggu@users.noreply.github.com> Date: Fri, 2 May 2025 18:42:55 +0900 Subject: [PATCH 5/9] Create deploy.yml --- .github/workflows/deploy.yml | 72 ++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..8b119a1 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,72 @@ +name: Deploy Frontend & Backend + +on: + push: + branches: [deploy] + +jobs: + frontend: + name: Deploy Frontend to S3 + runs-on: ubuntu-22.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '22' + + - name: Install dependencies + run: | + cd frontend + npm ci + + - name: Build frontend + run: | + cd frontend + npm run build + + - name: Deploy to S3 + uses: jakejarvis/s3-sync-action@master + with: + args: --delete + env: + AWS_S3_BUCKET: www.pirocheck.org + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + SOURCE_DIR: frontend/dist + + backend: + name: Deploy Backend to EC2 + needs: frontend + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + + - name: Build backend + run: | + cd backend + ./gradlew build --no-daemon + + - name: Restore PEM file + run: | + echo "${{ secrets.EC2_SSH_KEY }}" | base64 -d > pirocheck.pem + chmod 400 pirocheck.pem + + - name: Copy JAR to EC2 + run: | + scp -o StrictHostKeyChecking=no -i pirocheck.pem backend/build/libs/*.jar ubuntu@${{ secrets.EC2_HOST }}:/home/ubuntu/app.jar + + - name: Restart Spring Boot on EC2 + run: | + ssh -o StrictHostKeyChecking=no -i pirocheck.pem ubuntu@${{ secrets.EC2_HOST }} 'bash ~/restart.sh' From f14cd3d5b06bafdc3434dcbbcdd51f4d6ea7b2b6 Mon Sep 17 00:00:00 2001 From: Suhjung Park <145967352+Imggaggu@users.noreply.github.com> Date: Fri, 2 May 2025 19:06:25 +0900 Subject: [PATCH 6/9] Update deploy.yml --- .github/workflows/deploy.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8b119a1..f51066a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -70,3 +70,30 @@ jobs: - name: Restart Spring Boot on EC2 run: | ssh -o StrictHostKeyChecking=no -i pirocheck.pem ubuntu@${{ secrets.EC2_HOST }} 'bash ~/restart.sh' + + + - name: Send Discord notification (Success) + if: success() + run: | + curl -H "Content-Type: application/json" \ + -X POST \ + -d '{ + "embeds": [{ + "title": "🚀 Deploy 성공!", + "description": "**Branch**: `${{ github.ref }}`\n**Commit**: `${{ github.sha }}`\n🟢 서비스가 정상적으로 배포되었습니다!", + "color": 65353 + }] + }' ${{ secrets.DISCORD_WEBHOOK }} + + - name: Send Discord notification (Failure) + if: failure() + run: | + curl -H "Content-Type: application/json" \ + -X POST \ + -d '{ + "embeds": [{ + "title": "❌ Deploy 실패!", + "description": "**Branch**: `${{ github.ref }}`\n**Commit**: `${{ github.sha }}`\n🔴 배포 중 오류가 발생했습니다. 로그를 확인하세요.", + "color": 16711680 + }] + }' ${{ secrets.DISCORD_WEBHOOK }} From 2b7a3330c10a828c31c98e524304d643572d939b Mon Sep 17 00:00:00 2001 From: dietken1 Date: Mon, 5 May 2025 20:59:02 +0900 Subject: [PATCH 7/9] =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +++- backend/pirocheck/.gitignore | 4 +++- .../pirocheck/attendence/entity/Attendence.java | 16 ++++++++++++++++ .../attendence/entity/AttendenceCode.java | 17 +++++++++++++++++ 4 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendence.java create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/AttendenceCode.java diff --git a/.gitignore b/.gitignore index 01b6b1f..4d570a7 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,6 @@ # Common .DS_Store *.log -.env \ No newline at end of file +.env + +.idea/ \ No newline at end of file diff --git a/backend/pirocheck/.gitignore b/backend/pirocheck/.gitignore index c2065bc..49fee8b 100644 --- a/backend/pirocheck/.gitignore +++ b/backend/pirocheck/.gitignore @@ -5,6 +5,9 @@ build/ !**/src/main/**/build/ !**/src/test/**/build/ +.idea +.idea/ + ### STS ### .apt_generated .classpath @@ -18,7 +21,6 @@ bin/ !**/src/test/**/bin/ ### IntelliJ IDEA ### -.idea *.iws *.iml *.ipr diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendence.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendence.java new file mode 100644 index 0000000..d05267f --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendence.java @@ -0,0 +1,16 @@ +package backend.pirocheck.attendence.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity +@Table(name = "attendence") +public class Attendence { + @Id + private Long id; + + private Long userId; + private int week; + +} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/AttendenceCode.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/AttendenceCode.java new file mode 100644 index 0000000..59a5d8e --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/AttendenceCode.java @@ -0,0 +1,17 @@ +package backend.pirocheck.attendence.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Table(name = "attendence_code") +@Getter @Setter +public class AttendenceCode { + @Id + private Long id; + + private String code; +} From 56f9aeaa6048939ff38fd2a30617b2a65f47d5bf Mon Sep 17 00:00:00 2001 From: dietken1 Date: Mon, 5 May 2025 21:51:52 +0900 Subject: [PATCH 8/9] =?UTF-8?q?=EC=A4=91=EA=B0=84=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AttendanceController.java | 25 +++++++++ .../dto/request/GetAttendanceByDateReq.java | 11 ++++ .../response/GetAttendanceByUserIdRes.java | 13 +++++ .../attendence/entity/Attendance.java | 29 ++++++++++ ...ttendenceCode.java => AttendanceCode.java} | 4 +- .../attendence/entity/Attendence.java | 16 ------ .../repository/AttendanceCodeRepository.java | 9 ++++ .../repository/AttendanceRepository.java | 13 +++++ .../attendence/service/AttendanceService.java | 54 +++++++++++++++++++ 9 files changed, 156 insertions(+), 18 deletions(-) create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/attendence/controller/AttendanceController.java create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/request/GetAttendanceByDateReq.java create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/response/GetAttendanceByUserIdRes.java create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendance.java rename backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/{AttendenceCode.java => AttendanceCode.java} (81%) delete mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendence.java create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceCodeRepository.java create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceRepository.java create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/attendence/service/AttendanceService.java diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/controller/AttendanceController.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/controller/AttendanceController.java new file mode 100644 index 0000000..ddb4406 --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/controller/AttendanceController.java @@ -0,0 +1,25 @@ +package backend.pirocheck.attendence.controller; + +import backend.pirocheck.attendence.entity.AttendanceCode; +import backend.pirocheck.attendence.service.AttendanceService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/attendance") +public class AttendanceController { + private final AttendanceService attendanceService; + + // 특정 유저의 출석 정보 + @GetMapping("/{userId}") + public AttendanceCode getAttendance() { + + } + + // 특정 유저의 특정 일자 출석 정보 + @GetMapping("") + public +} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/request/GetAttendanceByDateReq.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/request/GetAttendanceByDateReq.java new file mode 100644 index 0000000..d731fb4 --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/request/GetAttendanceByDateReq.java @@ -0,0 +1,11 @@ +package backend.pirocheck.attendence.dto.request; + +import lombok.Getter; + +import java.time.LocalDate; + +@Getter +public class GetAttendanceByDateReq { + private Long userId; + private LocalDate date; +} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/response/GetAttendanceByUserIdRes.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/response/GetAttendanceByUserIdRes.java new file mode 100644 index 0000000..50fd6a4 --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/response/GetAttendanceByUserIdRes.java @@ -0,0 +1,13 @@ +package backend.pirocheck.attendence.dto.response; + +import lombok.Getter; + +import java.time.LocalDate; + +@Getter +public class GetAttendanceByUserIdRes { + private Long userId; + private LocalDate date; + private int order; + private boolean status; +} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendance.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendance.java new file mode 100644 index 0000000..8eedd37 --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendance.java @@ -0,0 +1,29 @@ +package backend.pirocheck.attendence.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; + +@Entity +@Table( + name = "attendance", + uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "date", "order"}) +) +@Getter @Setter +public class Attendance { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; + + private LocalDate date; + + private int order; + + private boolean status; +} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/AttendenceCode.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/AttendanceCode.java similarity index 81% rename from backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/AttendenceCode.java rename to backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/AttendanceCode.java index 59a5d8e..98c709d 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/AttendenceCode.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/AttendanceCode.java @@ -7,9 +7,9 @@ import lombok.Setter; @Entity -@Table(name = "attendence_code") +@Table(name = "attendance_code") @Getter @Setter -public class AttendenceCode { +public class AttendanceCode { @Id private Long id; diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendence.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendence.java deleted file mode 100644 index d05267f..0000000 --- a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendence.java +++ /dev/null @@ -1,16 +0,0 @@ -package backend.pirocheck.attendence.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; - -@Entity -@Table(name = "attendence") -public class Attendence { - @Id - private Long id; - - private Long userId; - private int week; - -} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceCodeRepository.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceCodeRepository.java new file mode 100644 index 0000000..b535f56 --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceCodeRepository.java @@ -0,0 +1,9 @@ +package backend.pirocheck.attendence.repository; + +import backend.pirocheck.attendence.entity.AttendanceCode; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AttendanceCodeRepository extends JpaRepository { +} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceRepository.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceRepository.java new file mode 100644 index 0000000..3e5786d --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceRepository.java @@ -0,0 +1,13 @@ +package backend.pirocheck.attendence.repository; + +import backend.pirocheck.attendence.entity.Attendance; +import backend.pirocheck.attendence.entity.AttendanceCode; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface AttendanceRepository extends JpaRepository { + List findByUserId(Long userId); +} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/service/AttendanceService.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/service/AttendanceService.java new file mode 100644 index 0000000..b9d1fd2 --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/service/AttendanceService.java @@ -0,0 +1,54 @@ +package backend.pirocheck.attendence.service; + +import backend.pirocheck.attendence.entity.Attendance; +import backend.pirocheck.attendence.entity.AttendanceCode; +import backend.pirocheck.attendence.repository.AttendanceCodeRepository; +import backend.pirocheck.attendence.repository.AttendanceRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.concurrent.ThreadLocalRandom; + +@Service +@RequiredArgsConstructor +public class AttendanceService { + + private final AttendanceRepository attendanceRepository; + private final AttendanceCodeRepository attendanceCodeRepository; + + // 출석코드 생성 함수 + @Transactional + public AttendanceCode generateCodeAndCreateAttendances(Long classId, int order) { + // 1. 출석 코드 생성 + String code = String.valueOf(ThreadLocalRandom.current().nextInt(1000, 10000)); + + AttendanceCode attendanceCode = new AttendanceCode(); + attendanceCode.setCode(code); + attendanceCodeRepository.save(attendanceCode); + + // 2. user 권한을 가진 학생 리스트 조회 + List users = userRepository.findAllByRole(Role.USER); + + // 3. 각 학생에 대해 출석 데이터 미리 생성 + for (User user : users) { + Attendance attendance = new Attendance(); + attendance.setUser(user); + attendance.setDate(LocalDate.now()); + attendance.setOrder(order); + attendance.setStatus(false); // 기본은 false + attendanceRepository.save(attendance); + } + + return attendanceCode; + } + + + // 출석코드 삭제 함수 + + // 유저 id로 출석 조회하는 함수 + public Attendance findByUserId(Long userId) { + + } +} From cbe7dfb5c06819893b581b4212aa2ba957666b94 Mon Sep 17 00:00:00 2001 From: dietken1 Date: Tue, 6 May 2025 01:39:29 +0900 Subject: [PATCH 9/9] =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=EC=84=B1=EC=99=84=EB=A3=8C=20(=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B3=B4=EC=99=84=ED=95=A0=20=EC=82=AC=ED=95=AD=20=EC=A1=B4?= =?UTF-8?q?=EC=9E=AC=ED=95=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../User/repository/UserRepository.java | 4 + .../controller/AttendanceController.java | 46 ++++++-- .../dto/request/MarkAttendanceReq.java | 9 ++ .../dto/response/AttendanceSlotRes.java | 14 +++ .../dto/response/AttendanceStatusRes.java | 15 +++ .../attendence/entity/Attendance.java | 4 +- .../attendence/entity/AttendanceCode.java | 15 ++- .../repository/AttendanceCodeRepository.java | 5 + .../repository/AttendanceRepository.java | 6 +- .../attendence/service/AttendanceService.java | 103 ++++++++++++++++-- 10 files changed, 200 insertions(+), 21 deletions(-) create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/request/MarkAttendanceReq.java create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/response/AttendanceSlotRes.java create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/response/AttendanceStatusRes.java diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/User/repository/UserRepository.java b/backend/pirocheck/src/main/java/backend/pirocheck/User/repository/UserRepository.java index 675aca1..9f80d25 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/User/repository/UserRepository.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/User/repository/UserRepository.java @@ -1,11 +1,15 @@ package backend.pirocheck.User.repository; +import backend.pirocheck.User.entity.Role; import backend.pirocheck.User.entity.User; import io.swagger.v3.oas.annotations.Operation; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; public interface UserRepository extends JpaRepository { Optional findByName(String name); + + List findByRole(Role role); } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/controller/AttendanceController.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/controller/AttendanceController.java index ddb4406..ac5847a 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/controller/AttendanceController.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/controller/AttendanceController.java @@ -1,25 +1,55 @@ package backend.pirocheck.attendence.controller; +import backend.pirocheck.attendence.dto.request.GetAttendanceByDateReq; +import backend.pirocheck.attendence.dto.request.MarkAttendanceReq; +import backend.pirocheck.attendence.dto.response.AttendanceSlotRes; +import backend.pirocheck.attendence.dto.response.AttendanceStatusRes; import backend.pirocheck.attendence.entity.AttendanceCode; import backend.pirocheck.attendence.service.AttendanceService; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.util.List; @RestController @RequiredArgsConstructor @RequestMapping("/api/attendance") public class AttendanceController { + private final AttendanceService attendanceService; // 특정 유저의 출석 정보 - @GetMapping("/{userId}") - public AttendanceCode getAttendance() { - + @GetMapping("/user") + public List getAttendanceByUserId(@RequestParam Long userId) { + return attendanceService.findByUserId(userId); } // 특정 유저의 특정 일자 출석 정보 - @GetMapping("") - public + @GetMapping("/user/date") + public List getAttendanceByUserIdAndDate( + @RequestParam Long userId, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date + ) { + return attendanceService.findByUserIdAndDate(userId, LocalDate.now()); + } + + // 출석체크 시작 + @PostMapping("/start") + public AttendanceCode postAttendance() { + return attendanceService.generateCodeAndCreateAttendances(); + } + + // 출석코드 비교 + @PostMapping("/mark") + public boolean markAttendance(@RequestBody MarkAttendanceReq req) { + return attendanceService.markAttendance(req.getUserId(), req.getCode()); + } + + // 출석체크 종료 + @PutMapping("/expire") + public boolean expireAttendance(@RequestParam String code) { + return attendanceService.exprireAttendanceCode(code); + } } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/request/MarkAttendanceReq.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/request/MarkAttendanceReq.java new file mode 100644 index 0000000..a6aad92 --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/request/MarkAttendanceReq.java @@ -0,0 +1,9 @@ +package backend.pirocheck.attendence.dto.request; + +import lombok.Getter; + +@Getter +public class MarkAttendanceReq { + private Long userId; + private String code; +} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/response/AttendanceSlotRes.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/response/AttendanceSlotRes.java new file mode 100644 index 0000000..81f0bbf --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/response/AttendanceSlotRes.java @@ -0,0 +1,14 @@ +package backend.pirocheck.attendence.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class AttendanceSlotRes { + private int order; + private boolean status; + +} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/response/AttendanceStatusRes.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/response/AttendanceStatusRes.java new file mode 100644 index 0000000..2a965ac --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/dto/response/AttendanceStatusRes.java @@ -0,0 +1,15 @@ +package backend.pirocheck.attendence.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; + +@Getter +@Setter +public class AttendanceStatusRes { + private LocalDate date; + private List slots; +} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendance.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendance.java index 8eedd37..af9ce56 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendance.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/Attendance.java @@ -1,5 +1,6 @@ package backend.pirocheck.attendence.entity; +import backend.pirocheck.User.entity.User; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; @@ -9,7 +10,7 @@ @Entity @Table( name = "attendance", - uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "date", "order"}) + uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "date", "order_number"}) ) @Getter @Setter public class Attendance { @@ -23,6 +24,7 @@ public class Attendance { private LocalDate date; + @Column(name = "order_number") private int order; private boolean status; diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/AttendanceCode.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/AttendanceCode.java index 98c709d..358bf29 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/AttendanceCode.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/entity/AttendanceCode.java @@ -1,17 +1,24 @@ package backend.pirocheck.attendence.entity; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; +import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; +import java.time.LocalDate; + @Entity @Table(name = "attendance_code") @Getter @Setter public class AttendanceCode { - @Id + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String code; + + private LocalDate date; + + @Column(name = "order_number") + private int order; + + private boolean isExpired = false; } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceCodeRepository.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceCodeRepository.java index b535f56..2c30b55 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceCodeRepository.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceCodeRepository.java @@ -4,6 +4,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.time.LocalDate; +import java.util.Optional; + @Repository public interface AttendanceCodeRepository extends JpaRepository { + int countByDate(LocalDate date); + Optional findByCodeAndDate(String code, LocalDate date); } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceRepository.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceRepository.java index 3e5786d..445f157 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceRepository.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/repository/AttendanceRepository.java @@ -5,9 +5,13 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.time.LocalDate; import java.util.List; +import java.util.Optional; @Repository -public interface AttendanceRepository extends JpaRepository { +public interface AttendanceRepository extends JpaRepository { List findByUserId(Long userId); + List findByUserIdAndDate(Long userId, LocalDate date); + Optional findByUserIdAndDateAndOrder(Long userId, LocalDate date, int order); } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/service/AttendanceService.java b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/service/AttendanceService.java index b9d1fd2..42ffe8f 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/attendence/service/AttendanceService.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/attendence/service/AttendanceService.java @@ -1,5 +1,10 @@ package backend.pirocheck.attendence.service; +import backend.pirocheck.User.entity.Role; +import backend.pirocheck.User.entity.User; +import backend.pirocheck.User.repository.UserRepository; +import backend.pirocheck.attendence.dto.response.AttendanceSlotRes; +import backend.pirocheck.attendence.dto.response.AttendanceStatusRes; import backend.pirocheck.attendence.entity.Attendance; import backend.pirocheck.attendence.entity.AttendanceCode; import backend.pirocheck.attendence.repository.AttendanceCodeRepository; @@ -9,7 +14,12 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -17,38 +27,117 @@ public class AttendanceService { private final AttendanceRepository attendanceRepository; private final AttendanceCodeRepository attendanceCodeRepository; + private final UserRepository userRepository; // 출석코드 생성 함수 @Transactional - public AttendanceCode generateCodeAndCreateAttendances(Long classId, int order) { + public AttendanceCode generateCodeAndCreateAttendances() { + LocalDate today = LocalDate.now(); + + // 오늘 생성된 출석코드 개수 = 현재까지 생성된 차시 수 + 1 (MAX=3) + int currentOrder = attendanceCodeRepository.countByDate(today) + 1; + // 1. 출석 코드 생성 String code = String.valueOf(ThreadLocalRandom.current().nextInt(1000, 10000)); AttendanceCode attendanceCode = new AttendanceCode(); attendanceCode.setCode(code); + attendanceCode.setDate(today); + attendanceCode.setOrder(currentOrder); attendanceCodeRepository.save(attendanceCode); // 2. user 권한을 가진 학생 리스트 조회 - List users = userRepository.findAllByRole(Role.USER); + List users = userRepository.findByRole(Role.MEMBER); // 3. 각 학생에 대해 출석 데이터 미리 생성 for (User user : users) { Attendance attendance = new Attendance(); attendance.setUser(user); attendance.setDate(LocalDate.now()); - attendance.setOrder(order); + attendance.setOrder(currentOrder); attendance.setStatus(false); // 기본은 false attendanceRepository.save(attendance); } - return attendanceCode; } + // 출석코드 만료처리 함수 + @Transactional + public boolean exprireAttendanceCode(String code) { + Optional codeOpt = attendanceCodeRepository.findByCodeAndDate(code, LocalDate.now()); + + if (codeOpt.isEmpty()) { + return false; + } + + AttendanceCode attendanceCode = codeOpt.get(); + + if (attendanceCode.isExpired()) { + return false; + } + + attendanceCode.setExpired(true); + attendanceCodeRepository.save(attendanceCode); + + return true; + } + + // 출석처리 함수 + @Transactional + public boolean markAttendance(Long userId, String inputCode) { + // 1. 출석코드 일치 비교 + Optional validCodeOpt = attendanceCodeRepository.findByCodeAndDate(inputCode, LocalDate.now()); + + if (validCodeOpt.isEmpty()) return false; + + AttendanceCode code = validCodeOpt.get(); + + // 2. 해당 유저의 출석 레코드 조회 + Optional attendanceOpt = attendanceRepository.findByUserIdAndDateAndOrder(userId, code.getDate(), code.getOrder()); + + if (attendanceOpt.isEmpty()) return false; + + // 3. 출석 처리 + Attendance attendance = attendanceOpt.get(); + attendance.setStatus(true); + attendanceRepository.save(attendance); - // 출석코드 삭제 함수 + return true; + } + + // 유저의 전체 출석 현황을 조회하는 함수 + public List findByUserId(Long userId) { + List attendances = attendanceRepository.findByUserId(userId); + + // 날짜별로 그룹화 + Map> grouped = attendances.stream() + .collect(Collectors.groupingBy(Attendance::getDate)); + + // 날짜별로 DTO 변환 + return grouped.entrySet().stream() + .map(entry -> { + LocalDate date = entry.getKey(); + List slots = entry.getValue().stream() + .map(a -> new AttendanceSlotRes(a.getOrder(), a.isStatus())) + .sorted(Comparator.comparingInt(AttendanceSlotRes::getOrder)) + .toList(); + + AttendanceStatusRes dto = new AttendanceStatusRes(); + dto.setDate(date); + dto.setSlots(slots); + return dto; + }) + .sorted(Comparator.comparing(AttendanceStatusRes::getDate).reversed()) + .toList(); + } - // 유저 id로 출석 조회하는 함수 - public Attendance findByUserId(Long userId) { + // 유저의 특정 날짜의 출석 현황을 조회하는 함수 + public List findByUserIdAndDate(Long userId, LocalDate date) { + List attendances = attendanceRepository.findByUserIdAndDate(userId, date); + return attendances.stream() + .map(a -> new AttendanceSlotRes(a.getOrder(), a.isStatus())) + .sorted(Comparator.comparingInt(AttendanceSlotRes::getOrder)) + .toList(); } }