Skip to content

๐ŸŒŠPARD 6๊ธฐ Longkathon MateCheck ์„œ๋ฒ„๋ ˆํฌ

Notifications You must be signed in to change notification settings

Club-PARD/Hamcheese_server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

30 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿค mate check

๋‚˜์—๊ฒŒ FITํ•œ ํŒ€์› ์ฐพ๊ธฐ

Deploy API Swagger

๋Œ€ํ•™์ƒ๋“ค์ด ์ˆ˜์—…, ํ”„๋กœ์ ํŠธ, ๋™์•„๋ฆฌ ๋“ฑ์—์„œ ์ž์‹ ์—๊ฒŒ ๋งž๋Š” ํŒ€์›์„ ์ฐพ๊ณ , ๋™๋ฃŒํ‰๊ฐ€๋ฅผ ํ†ตํ•ด ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ํ˜‘์—… ํŒŒํŠธ๋„ˆ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” ํ”Œ๋žซํผ

๐ŸŒ ์„œ๋น„์Šค ๋ฐ”๋กœ๊ฐ€๊ธฐ | ๐Ÿ“– API ๋ฌธ์„œ | ๐ŸŽจ Figma


๐Ÿ“Œ ์ž๊ธฐ์†Œ๊ฐœ ๋ฐ ์—ญํ•  ๋ถ„๋‹ด

ERD ๋ฐ API ๋ช…์„ธ์„œ๋Š” ๋‘ ์‚ฌ๋žŒ์ด ํ•จ๊ป˜ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์ด๋ฆ„ ์ฃผ์š” ๋‹ด๋‹น ๊ธฐ๋Šฅ
๐Ÿ”ฅ์ด์ƒํ—Œ - OAuth ๊ธฐ๋ฐ˜ ๋กœ๊ทธ์ธ
- ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ๊ด€๋ฆฌ ๊ด€๋ จ ๊ธฐ๋Šฅ
- AWS S3 ํŒŒ์ผ ์—…๋กœ๋“œ
- ๋™๋ฃŒํ‰๊ฐ€ ์‹œ์Šคํ…œ
๐Ÿฅท์กฐ๊ท€ํ˜ธ - mate check!(๋งค์นญ ์š”์ฒญ)
-ํŒ€์› ๋ชจ์ง‘ ๊ด€๋ จ ๊ธฐ๋Šฅ

๐Ÿ“Œ ํ”„๋กœ์ ํŠธ ์ •๋ณด

ํ•ญ๋ชฉ ๋‚ด์šฉ
๐Ÿš€ ๋ฐฐํฌ ์ฃผ์†Œ https://matecheck.vercel.app
๐Ÿ”— API Base URL https://matecheck.co.kr
โฑ๏ธ ๊ฐœ๋ฐœ ๊ธฐ๊ฐ„ 3์ฃผ

๐Ÿ› ๏ธ ๊ธฐ์ˆ  ์Šคํƒ

Backend

Spring Boot Java JPA Security OAuth2

Database

MySQL

Infrastructure

AWS EC2 AWS S3 Nginx

Authentication

Google OAuth

Tools

Swagger AWS SDK


๐ŸŒŠ ์ฃผ์š” ํ”Œ๋กœ์šฐ

1. ๋ฉ”์ธ ์†Œ๊ฐœ ํŽ˜์ด์ง€

2. ๋กœ๊ทธ์ธ / ํšŒ์›๊ฐ€์ž…

3. ๋ฉ”์ดํŠธ ๋‘˜๋Ÿฌ๋ณด๊ธฐ ๋ฐ ์ƒ์„ธ๋ณด๊ธฐ

4. ๋ชจ์ง‘ํ•˜๊ธฐ ๋‘˜๋Ÿฌ๋ณด๊ธฐ ๋ฐ ์ƒ์„ธ๋ณด๊ธฐ

5. ๋ชจ์ง‘ํ•˜๊ธฐ ์ž‘์„ฑ

6. mate check

7. mate check ์ˆ˜๋ฝ ์‹œ ์ฑ„ํŒ…
---

โœจ ํ•ต์‹ฌ ๊ธฐ๋Šฅ

๐Ÿ” ์ธ์ฆ ์‹œ์Šคํ…œ

  • Google OAuth 2.0 ๊ธฐ๋ฐ˜ ์†Œ์…œ ๋กœ๊ทธ์ธ
  • ID Token ๊ฒ€์ฆ ๋ฐ ์‚ฌ์šฉ์ž ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ
  • ํšŒ์›๊ฐ€์ž… ์‹œ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์ง€์›

๐Ÿ‘ค ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ๊ด€๋ฆฌ

  • ํ”„๋กœํ•„ CRUD (์ƒ์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ)
  • ํ•™์  ์ •๋ณด, ์ „๊ณต, ํ•™์ , ๊ฐ•์  ํ•ด์‹œํƒœ๊ทธ ๊ด€๋ฆฌ
  • ํ™œ๋™ ๋‚ด์—ญ ๊ธฐ๋ก
  • ํ•œ์ค„ ์†Œ๊ฐœ ์ž‘์„ฑ

๐Ÿ“ ํŒ€์› ๋ชจ์ง‘

  • ๋ชจ์ง‘ํ•˜๊ธฐ ์ž‘์„ฑ ๋ฐ ๊ด€๋ฆฌ
  • ํ”„๋กœ์ ํŠธ ํƒ€์ž…๋ณ„ ํ•„ํ„ฐ๋ง (์ˆ˜์—…, ์กธ์ž‘, ๋™์•„๋ฆฌ, ํ•™ํšŒ, ๋Œ€ํšŒ)
  • ํ•™๋ถ€ ๋ฐ ์ด๋ฆ„ ๊ธฐ๋ฐ˜ ๊ฒ€์ƒ‰
  • ๊ฐ€์žฅ ์ž˜ํ•  ์ˆ˜ ์žˆ๋Š” ํ‚ค์›Œ๋“œ ํƒœ๊ทธ ์‹œ์Šคํ…œ (์ตœ๋Œ€ 10๊ฐœ)

โญ ๋™๋ฃŒํ‰๊ฐ€ ์‹œ์Šคํ…œ

  • ๊ฐ•์ /์•ฝ์  ํ‚ค์›Œ๋“œ ๊ธฐ๋ฐ˜ ํ‰๊ฐ€
  • ํ‚ค์›Œ๋“œ๋ณ„ ๋ˆ„์  ์ง‘๊ณ„
  • ์ตœ์‹ ์ˆœ ๋™๋ฃŒํ‰๊ฐ€ ๋ชฉ๋ก ์กฐํšŒ
  • Top 3 ํ‚ค์›Œ๋“œ ์ž๋™ ์‚ฐ์ถœ

๐Ÿ’Œ mate check! (๋งค์นญ ์š”์ฒญ)

  • ๋ชจ์ง‘ํ•˜๊ธฐ ๋˜๋Š” ํ”„๋กœํ•„์—์„œ mate check!
  • ์ค‘๋ณต mate check! ๋ฐฉ์ง€
  • mate check! ์ˆ˜์‹  ๋ชฉ๋ก ๊ด€๋ฆฌ
  • ์ˆ˜๋ฝ/๊ฑฐ์ ˆ ์ฒ˜๋ฆฌ ์‹œ ์„œ๋ฒ„ ๋‚ด๋ถ€ ์•Œ๋ฆผ ์ƒ์„ฑ
  • ์•Œ๋ฆผ ๋ชฉ๋ก ์กฐํšŒ ๋ฐ ํ™•์ธ(์‚ญ์ œ)

๐Ÿ“ ํŒŒ์ผ ๊ด€๋ฆฌ

  • S3 ๊ธฐ๋ฐ˜ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ/์‚ญ์ œ
  • UUID ๊ธฐ๋ฐ˜ ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€
  • ์ด๋ฏธ์ง€ URL ์ž๋™ ์ƒ์„ฑ

๐Ÿ—๏ธ ์•„ํ‚คํ…์ฒ˜

๋ฐฐํฌ ๊ตฌ์กฐ

GitHub Repository
    โ†“ (push)
EC2 Server
    โ†“ (pull & build)
Spring Boot Application (Port 8080)
    โ†“
Nginx (Port 80/443)
    โ†“ (reverse proxy)
Client (matecheck.vercel.app)

๋ฐฐํฌ ํ”„๋กœ์„ธ์Šค

  1. ๋กœ์ปฌ์—์„œ GitHub๋กœ push
  2. EC2 ์„œ๋ฒ„์—์„œ ์ˆ˜๋™์œผ๋กœ pull ํ›„ ๋นŒ๋“œ
  3. Nginx๋ฅผ ํ†ตํ•œ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ ๋ฐ HTTPS ์ ์šฉ
  4. S3๋ฅผ ํ†ตํ•œ ์ •์  ํŒŒ์ผ(์ด๋ฏธ์ง€) ๊ด€๋ฆฌ

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ (DDD ์ง€ํ–ฅ)

๐Ÿ“ฆ src/main/java/pard/server/com/longkathon/
โ”œโ”€โ”€ ๐Ÿ“‚ config/                    # ์„ค์ • ํŒŒ์ผ
โ”‚   โ”œโ”€โ”€ CorsConfig.java
โ”‚   โ”œโ”€โ”€ SecurityConfig.java
โ”‚   โ”œโ”€โ”€ SwaggerConfig.java
โ”‚   โ””โ”€โ”€ WebMvcConfig.java
โ”œโ”€โ”€ ๐Ÿ“‚ googleLogin/               # OAuth ์ธ์ฆ
โ”‚   โ”œโ”€โ”€ AuthController.java
โ”‚   โ”œโ”€โ”€ GoogleTokenParser.java
โ”‚   โ””โ”€โ”€ GoogleUserInfo.java
โ”œโ”€โ”€ ๐Ÿ“‚ MyPage/                    # ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ๋„๋ฉ”์ธ
โ”‚   โ”œโ”€โ”€ user/
โ”‚   โ”œโ”€โ”€ userFile/
โ”‚   โ”œโ”€โ”€ introduction/
โ”‚   โ”œโ”€โ”€ activity/
โ”‚   โ”œโ”€โ”€ skillStackList/
โ”‚   โ”œโ”€โ”€ peerReview/
โ”‚   โ”œโ”€โ”€ peerGoodKeyword/
โ”‚   โ””โ”€โ”€ peerBadKeyword/
โ”œโ”€โ”€ ๐Ÿ“‚ posting/                   # ๋ชจ์ง‘ํ•˜๊ธฐ ๋„๋ฉ”์ธ
โ”‚   โ”œโ”€โ”€ recruiting/
โ”‚   โ””โ”€โ”€ myKeyword/
โ”œโ”€โ”€ ๐Ÿ“‚ poking/                    # mate check! ๋„๋ฉ”์ธ
โ”œโ”€โ”€ ๐Ÿ“‚ alarm/                     # ์•Œ๋ฆผ ๋„๋ฉ”์ธ
โ””โ”€โ”€ ๐Ÿ“‚ s3/                        # AWS S3 ์—ฐ๋™

๐Ÿš€ ๋กœ์ปฌ ์‹คํ–‰ ๋ฐฉ๋ฒ•

1๏ธโƒฃ ์‚ฌ์ „ ์š”๊ตฌ์‚ฌํ•ญ

  • Java 17 ์ด์ƒ
  • MySQL 8.0 ์ด์ƒ
  • Gradle

2๏ธโƒฃ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ •

src/main/resources/application.yml ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ์•„๋ž˜ ๋‚ด์šฉ์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/{DB๋ช…}?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
    username: {DB ์‚ฌ์šฉ์ž๋ช…}
    password: {DB ๋น„๋ฐ€๋ฒˆํ˜ธ}
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: update  # ๊ฐœ๋ฐœ: update, ์šด์˜: validate ๊ถŒ์žฅ
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        dialect: org.hibernate.dialect.MySQL8Dialect

  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB

cloud:
  aws:
    credentials:
      access-key: {AWS Access Key}
      secret-key: {AWS Secret Key}
    region:
      static: ap-northeast-2
    s3:
      bucket: {S3 ๋ฒ„ํ‚ท๋ช…}
    stack:
      auto: false

logging:
  level:
    pard.server.com.longkathon: DEBUG

3๏ธโƒฃ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒ์„ฑ

CREATE DATABASE matecheck CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

4๏ธโƒฃ ์‹คํ–‰

# Gradle๋กœ ๋นŒ๋“œ
./gradlew build

# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰
./gradlew bootRun

# ๋˜๋Š” JAR ํŒŒ์ผ ์‹คํ–‰
java -jar build/libs/Longkathon-0.0.1-SNAPSHOT.jar

5๏ธโƒฃ ํ™•์ธ


๐Ÿ“– API ๋ช…์„ธ์„œ

Swagger UI๋กœ ์ „์ฒด API ํ™•์ธ


๐Ÿ“‹ ์ƒ์„ธ API ๋ฌธ์„œ

1. ์„œ๋น„์Šค ์†Œ๊ฐœ ํŽ˜์ด์ง€ API

1.1 ์ฒซ ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ ์กฐํšŒ

Endpoint: GET /user/firstPage

Response:

{
  "profileFeedList": [
    {
      "userId": "long",
      "name": "string",
      "firstMajor": "string",
      "secondMajor": "string",
      "studentId": "string",
      "introduction": "string",
      "skillList": ["string"],
      "peerGoodKeywords": ["string"],
      "imageUrl": "string"
    }
  ],
  "recruitingFeedList": [
    {
      "recruitingId": "long",
      "name": "string",
      "projectType": "string",
      "projectSpecific": "string",
      "classes": "string",
      "topic": "string",
      "totalPeople": "integer",
      "recruitPeople": "integer",
      "title": "string",
      "myKeyword": ["string"]
    }
  ]
}
  • profileFeedList: ์ตœ๊ทผ ํ”„๋กœํ•„ ํ”ผ๋“œ 3๊ฐœ
  • recruitingFeedList: ์ตœ๊ทผ ํŒ€์› ๊ตฌํ•˜๊ธฐ ๊ฒŒ์‹œ๊ธ€ 3๊ฐœ
2. ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ API

Endpoint: POST /auth/google/exists

ํ”„๋ก ํŠธ์—์„œ ๊ตฌ๊ธ€๋กœ๊ทธ์ธ์„ ํ†ตํ•œ idToken์„ ๋„˜๊ฒจ์ค€๋‹ค.

Response:

{
  "exists": "boolean",
  "email": "string",
  "socialId": "string",
  "myId": "Long"
}
  • exists: DB์— ํ•ด๋‹น ์œ ์ €๊ฐ€ ์กด์žฌํ•˜๋ฉด true โ†’ ํšŒ์›๊ฐ€์ž…ํŽ˜์ด์ง€ ์Šคํ‚ตํ•˜๊ณ  ๋ฐ”๋กœ ๋ฉ”์ธํŽ˜์ด์ง€๋กœ
  • myId: DB์— ํ•ด๋‹น ์œ ์ €๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด, ์œ ์ €์˜ id๊ฐ’. ์•ž์œผ๋กœ ํ”„๋ก ํŠธ๋Š” ํ•ด๋‹น ์œ ์ €์— ๋Œ€ํ•œ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ• ๋•Œ myId๋กœ ์š”์ฒญํ•œ๋‹ค.
3. ํšŒ์›๊ฐ€์ž… (User) API

3.1 ํšŒ์›๊ฐ€์ž…

Endpoint: POST /user/create

Request Body: profileImage์™€ data (JSON)์„ ๋‘˜๋‹ค ๋ณด๋‚ด์•ผํ•จ

const formData = new FormData();
formData.append("profileImage", file);
formData.append("data", JSON.stringify(payload));

await axios.post("http://localhost:8080/user/create", formData, {
  headers: {
    "Content-Type": "multipart/form-data",
  },
});
{
  "name": "string",
  "studentId": "string",
  "grade": "string",
  "semester": "string",
  "department": "string",
  "firstMajor": "string",
  "secondMajor": "string",
  "phoneNumber": "string (optional)",
  "gpa": "string (optional)",
  "email": "string",
  "socialId": "string"
}

Response:

{
  "myId": "long",
  "name": "string"
}

3.2 ์ „์ฒด ๋ฉ”์ดํŠธ ์กฐํšŒ (๋ฉ”์ดํŠธ ๋‘˜๋Ÿฌ๋ณด๊ธฐ)

Endpoint: GET /user/findAll

Response: List of

{
  "userId": "long",
  "name": "string",
  "firstMajor": "string",
  "secondMajor": "string",
  "studentId": "string",
  "introduction": "string",
  "skillList": ["string"],
  "peerGoodKeywords": ["string"],
  "imageUrl": "string"
}

3.3 ๋ฉ”์ดํŠธ ํ•„ํ„ฐ๋ง ๊ฒ€์ƒ‰

Endpoint: GET /user/filter

Query Parameters:

  • departments: string (comma-separated, ์˜ˆ: "์ปดํ“จํ„ฐ๊ณตํ•™๊ณผ,์ „์ž๊ณตํ•™๊ณผ")
  • name: string (์˜ˆ: "ํ™๊ธธ๋™")

Example:

GET /user/filter?departments=์ปดํ“จํ„ฐ๊ณตํ•™๊ณผ,์ „์ž๊ณตํ•™๊ณผ&name=ํ™๊ธธ๋™

Response: List of

{
  "userId": "long",
  "name": "string",
  "firstMajor": "string",
  "secondMajor": "string",
  "studentId": "string",
  "introduction": "string",
  "skillList": ["string"],
  "peerGoodKeywords": ["string"],
  "imageUrl": "string",
  "goodKeywordCount": "int"
}

3.4.1 ๋ฉ”์ดํŠธ & ๋งˆ์ด ํ”„๋กœํ•„ ์กฐํšŒ

Endpoint: GET /user/equal/{myId}/{userId}

๋‘ ์‚ฌ์šฉ์ž ID๊ฐ€ ๋™์ผํ•œ์ง€ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.

Response: boolean

true  // ๋™์ผํ•œ ๊ฒฝ์šฐ
false // ๋‹ค๋ฅธ ๊ฒฝ์šฐ

ํ๋ฆ„:

  1. GET /user/equal/{myId}/{userId} ๋จผ์ € ์š”์ฒญ
  2. false์ผ๋•Œ๋Š” GET /user/mateProfile/{userId} ์š”์ฒญ
  3. true์ผ๋•Œ๋Š” GET /user/myProfile/{myId} ์š”์ฒญ

๋ฉ”์ดํŠธ ํ”„๋กœํ•„ ๋„์šฐ๊ธฐ

Endpoint: GET /user/mateProfile/{userId}

Response:

{
  "name": "string",
  "email": "string",
  "department": "string",
  "firstMajor": "string",
  "secondMajor": "string",
  "gpa": "string",
  "grade": "string",
  "studentId": "string",
  "semester": "integer",
  "imageUrl": "string",
  "introduction": "string",
  "skillList": ["string"],
  "activity": [
    {
      "year": "integer",
      "title": "string",
      "link": "string"
    }
  ],
  "peerGoodKeyword": {
    "keyword1": "integer",
    "keyword2": "integer",
    "keyword3": "integer"
  },
  "goodKeywordCount": "integer",
  "peerBadKeyword": {
    "keyword1": "integer",
    "keyword2": "integer",
    "keyword3": "integer"
  },
  "badKeywordCount": "integer",
  "peerReviewRecent": [
    {
      "startDate": "string",
      "meetSpecific": "string",
      "goodKeywordList": ["string"],
      "badKeywordList": ["string"]
    }
  ]
}

๋งˆ์ดํ”„๋กœํ•„ ๋„์šฐ๊ธฐ

Endpoint: GET /user/myProfile/{myId}

Response:

{
  "name": "string",
  "email": "string",
  "department": "string",
  "firstMajor": "string",
  "secondMajor": "string",
  "gpa": "string",
  "grade": "string",
  "studentId": "string",
  "semester": "integer",
  "imageUrl": "string",
  "introduction": "string",
  "skillList": ["string"],
  "activity": [
    {
      "year": "integer",
      "title": "string",
      "link": "string"
    }
  ]
}

3.5.1 ํ”„๋กœํ•„ ์ˆ˜์ • - ์‚ฌ์ง„๋งŒ ์ˆ˜์ •

Endpoint: POST /user/updateImage/{myId}

Request Body:

POST /user/updateImage/123
Content-Type: multipart/form-data

profileImage: [image file]

Response: 200 OK


3.5.2 ํ”„๋กœํ•„ ์‚ฌ์ง„ ์‚ญ์ œ

Endpoint: DELETE /user/myProfile/{myId}

Response: 200 OK


3.5.3 ํ”„๋กœํ•„ ์ˆ˜์ • - ์ธ์ ์‚ฌํ•ญ๋งŒ ์ˆ˜์ •

Endpoint: PATCH /user/update/{myId}

Request Body:

{
  "name": "string",
  "email": "string",
  "department": "string",
  "firstMajor": "string",
  "secondMajor": "string",
  "gpa": "string",
  "studentId": "string",
  "grade": "string",
  "semester": "string",
  "imageUrl": "string",
  "introduction": "string",
  "skillList": ["string", "string"],
  "activity": [
    {
      "year": "string",
      "title": "string",
      "link": "string"
    }
  ]
}

Response: 200 OK


3.5.4 ๋‚˜์˜ ๋™๋ฃŒํ‰๊ฐ€ ํƒญ

Endpoint: GET /user/myPeerReview/{myId}

Response:

{
  "peerGoodKeyword": {
    "keyword1": "integer",
    "keyword2": "integer",
    "keyword3": "integer"
  },
  "goodKeywordCount": "integer",
  "peerBadKeyword": {
    "keyword1": "integer",
    "keyword2": "integer",
    "keyword3": "integer"
  },
  "badKeywordCount": "integer",
  "peerReviewRecent": [
    {
      "startDate": "string",
      "meetSpecific": "string",
      "goodKeywordList": ["string"],
      "badKeywordList": ["string"]
    }
  ]
}
4. ๋ชจ์ง‘ (Recruiting) API

4.1 ์ „์ฒด ๋ชจ์ง‘ํ•˜๊ธฐ ์กฐํšŒ

Endpoint: GET /recruiting/findAll

Response: List of

{
  "recruitingId": "long",
  "name": "string",
  "projectType": "string",
  "projectSpecific": "string",
  "classes": "string",
  "topic": "string",
  "totalPeople": "integer",
  "recruitPeople": "integer",
  "title": "string",
  "myKeyword": ["string"],
  "date": "string"
}

4.2 ๋ชจ์ง‘ํ•˜๊ธฐ ํ•„ํ„ฐ๋ง ๊ฒ€์ƒ‰

Endpoint: GET /recruiting/filter

Query Parameters:

  • type: string (์˜ˆ: "์ˆ˜์—…", "์กธ์ž‘", "๋™์•„๋ฆฌ", "ํ•™ํšŒ", "๋Œ€ํšŒ")
  • departments: string (comma-separated, ์˜ˆ: "์ปดํ“จํ„ฐ๊ณตํ•™๊ณผ,์ „์ž๊ณตํ•™๊ณผ")
  • name: string (์˜ˆ: "ํ™๊ธธ๋™")

Response: List of

{
  "recruitingId": "long",
  "name": "string",
  "projectType": "string",
  "projectSpecific": "string",
  "classes": "integer",
  "topic": "string",
  "totalPeople": "integer",
  "recruitPeople": "integer",
  "title": "string",
  "myKeyword": ["string"],
  "date": "string"
}

4.3 ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๋ชจ์ง‘ํ•˜๊ธฐ ์กฐํšŒ (๋‚ด๊ฐ€ ์“ด ๊ธ€ ํƒญ)

Endpoint: GET /recruiting/{myId}

Response: List of

{
  "recruiting": "long",
  "name": "string",
  "projectType": "string",
  "projectSpecific": "string",
  "classes": "string",
  "topic": "string",
  "totalPeople": "integer",
  "recruitPeople": "integer",
  "title": "string",
  "myKeyword": ["string"],
  "date": "string"
}

4.4.1 ๋ชจ์ง‘ํ•˜๊ธฐ ์ƒ์„ธ ์กฐํšŒ

Endpoint: GET /recruiting/detail/{recruitingId}/{myId}

Response:

{
  "name": "string",
  "projectType": "string",
  "projectSpecific": "string",
  "classes": "string",
  "topic": "string",
  "totalPeople": "integer",
  "recruitPeople": "integer",
  "title": "string",
  "myKeyword": ["string"],
  "date": "string",
  "context": "string",
  "studentId": "string",
  "firstMajor": "string",
  "secondMajor": "string",
  "imageUrl": "string",
  "postingList": [
    {
      "recruitingId": "long",
      "name": "string",
      "projectType": "string",
      "totalPeople": "integer",
      "recruitPeople": "integer",
      "title": "string",
      "date": "string"
    }
  ],
  "canEdit": "Boolean"
}

Notes: ์ž‘์„ฑ์ž์™€ ๋กœ๊ทธ์ธ ๊ณ„์ •์ด ๋™์ผํ•˜๋ฉด ์ˆ˜์ • ๊ฐ€๋Šฅํ•œ UI, ๋‹ค๋ฅด๋ฉด ์ˆ˜์ • ๋ถˆ๊ฐ€ํ•œ UI ์ œ๊ณต


4.4.2 ๋ชจ์ง‘ํ•˜๊ธฐ ์ˆ˜์ •

Endpoint: PATCH /recruiting/{recruitingId}/{myId}

Request Body:

{
  "projectType": "string",
  "projectSpecific": "string",
  "classes": "string",
  "topic": "string",
  "totalPeople": 0,
  "recruitPeople": 0,
  "title": "string",
  "context": "string",
  "keyword": ["string", "string", "..."]
}

Response: 200 OK


4.4.3 ๋ชจ์ง‘ํ•˜๊ธฐ ์‚ญ์ œ

Endpoint: DELETE /recruiting/{recruitingId}/{myId}

Response: 200 OK


4.5 ๋ชจ์ง‘ํ•˜๊ธฐ ์ƒ์„ฑ

Endpoint: POST /recruiting/createPost/{userId}

Request Body:

{
  "projectType": "string",
  "projectSpecific": "string",
  "classes": "string",
  "topic": "string",
  "totalPeople": "integer",
  "recruitPeople": "integer",
  "title": "string",
  "context": "string",
  "myKeyword": ["string"]
}

Response: 200 OK

5. ๋™๋ฃŒํ‰๊ฐ€ (Peer Review) API

5.1 ๋™๋ฃŒํ‰๊ฐ€ ์ž‘์„ฑ

Endpoint: POST /peerReview/{myId}/{userId}

Path Parameters:

  • myId: ํ‰๊ฐ€ ์ž‘์„ฑ์ž ID
  • userId: ํ‰๊ฐ€ ๋Œ€์ƒ์ž ID

Request Body:

{
  "startDate": "string",
  "meetSpecific": "string",
  "goodKeywordList": ["string"],
  "badKeywordList": ["string"]
}

Response: 200 OK (ํ‰๊ฐ€ ์ƒ์„ฑ ์™„๋ฃŒ)

6. mate check! (Poking) API

6.1 ๋ชจ์ง‘ํ•˜๊ธฐ์—์„œ mate check! ์ƒ์„ฑ

Endpoint: POST /poking/{recruitingId}/{myId}

Response: 200 OK (์ƒ์„ฑ ์™„๋ฃŒ)


6.2 ์œ ์ €์—๊ฒŒ mate check! ์ƒ์„ฑ

Endpoint: POST /poking/user/{userId}/{myId}

Path Parameters:

  • userId: mate check!๋ฅผ ๋ฐ›๋Š” ์‚ฌ๋žŒ(๋ฉ”์ดํŠธ)
  • myId: mate check!๋ฅผ ๋ณด๋‚ด๋Š” ์‚ฌ๋žŒ(๋กœ๊ทธ์ธ ์œ ์ €)

Response: 200 OK (์ƒ์„ฑ ์™„๋ฃŒ)


6.3 ๋ชจ์ง‘ํ•˜๊ธฐ์—์„œ mate check! ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ์กฐํšŒ

Endpoint: GET /poking/canInRecruiting/{recruitingId}/{myId}

Response:

{
  "canPoke": "boolean",
  "reason": "string"
}

6.4 ์œ ์ € โ†’ ์œ ์ € mate check! ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ์กฐํšŒ

Endpoint: GET /poking/canInProfile/{userId}/{myId}

Response:

{
  "canPoke": "boolean",
  "reason": "string"
}

6.5 mate check! ์กฐํšŒ (๋‚ด๊ฐ€ ๋ฐ›์€ mate check! ๋ชฉ๋ก)

Endpoint: GET /poking/received/{myId}

Response: List of

[
  {
    "pokingId": "long",
    "recruitingId": "long",
    "senderId": "long",
    "senderName": "string",
    "projectSpecific": "string",
    "date": "string",
    "imageUrl": "string"
  }
]

6.6 mate check! ์‚ญ์ œ (์ˆ˜๋ฝ/๊ฑฐ์ ˆ ์ฒ˜๋ฆฌ)

mate check!๋ฅผ ์‚ญ์ œํ•˜๋ฉฐ, ์ˆ˜๋ฝ(true) / ๊ฑฐ์ ˆ(false) ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์„œ๋ฒ„ ๋‚ด๋ถ€์—์„œ ์•Œ๋ฆผ์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

Endpoint: DELETE /poking/{pokingId}

Request Body:

{
  "ok": "boolean"
}

6.7 ์•Œ๋ฆผ ํ™•์ธ (ํ•ด๋‹น ์œ ์ €๊ฐ€ ๋ฐ›์€ ๋ชจ๋“  ์•Œ๋ฆผ ์กฐํšŒ)

Endpoint: GET /alarm/{userId}

Response: List of

[
  {
    "alarmId": 1,
    "senderName": "ํ™๊ธธ๋™",
    "ok": true
  },
  {
    "alarmId": 2,
    "senderName": "๊น€์ฒ ์ˆ˜",
    "ok": false
  }
]

6.8 ์•Œ๋ฆผ ์‚ญ์ œ (ํ™•์ธ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ)

Endpoint: DELETE /alarm/{alarmId}

Response: 200 OK


6.9 ์ฑ„ํŒ… ์ƒ์„ฑ (์ˆ˜๋ฝ ์ฒ˜๋ฆฌ)

mate check! ์ˆ˜๋ฝ(ok=true) ์ฒ˜๋ฆฌ ์‹œ ์ฑ„ํŒ… ์ƒ์„ฑ ๋กœ์ง์ด ํ•จ๊ป˜ ์ˆ˜ํ–‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Endpoint: DELETE /poking/{pokingId}

Response: 200 OK


๐Ÿ—„๏ธ ERD

์ฃผ์š” ํ…Œ์ด๋ธ” ๊ตฌ์กฐ

mate check logo

ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

  • Google ๋กœ๊ทธ์ธ ๋ฐฉ์‹ ์ •๋ฆฌ

    • ๋ฌธ์ œ: โ€œ์ผ๋ฐ˜ ๋กœ๊ทธ์ธ(JWT/์„ธ์…˜)โ€๊ณผ ๋‹ฌ๋ฆฌ idToken ๊ธฐ๋ฐ˜ ํ๋ฆ„์ด๋ผ ํŒ€ ๋‚ด์—์„œ ์ธ์ฆ/์ธ๊ฐ€ ๋ฒ”์œ„๊ฐ€ ํ—ท๊ฐˆ๋ฆผ
    • ๋Œ€์‘: POST /auth/google/exists์—์„œ idToken โ†’ (email/socialId) ์ถ”์ถœ โ†’ exists/myId ๋ฐ˜ํ™˜ ํ๋ฆ„์œผ๋กœ ๋ฌธ์„œํ™”
  • EC2 ์ˆ˜๋™ ๋ฐฐํฌ๋กœ ์ธํ•œ ์„ค์ • ๋ถˆ์ผ์น˜

    • ๋ฌธ์ œ: ๋กœ์ปฌ๊ณผ EC2 ํ™˜๊ฒฝ๋ณ€์ˆ˜/์„ค์ • ํŒŒ์ผ ์ฐจ์ด๋กœ ์‹คํ–‰ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์‰ฌ์›€
    • ๋Œ€์‘: application.yml ๋ถ„๋ฆฌ + ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋ชฉ๋ก์„ ์ •๋ฆฌํ•˜๊ณ , ๋ฐฐํฌ ์‹œ ์ฒดํฌ๋ฆฌ์ŠคํŠธ(ํ•„์ˆ˜ env, DB ์—ฐ๊ฒฐ, S3 ๊ถŒํ•œ)๋ฅผ ๋งŒ๋“ค์–ด ๊ณต์œ 
  • ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ(๋ฉ€ํ‹ฐํŒŒํŠธ) ๋””๋ฒ„๊น…

    • ๋ฌธ์ œ: multipart/form-data์—์„œ profileImage + data(JSON string)๋ฅผ ํ•จ๊ป˜ ์ „์†กํ•  ๋•Œ ํ‚ค ์ด๋ฆ„/Content-Type ์‹ค์ˆ˜๋กœ 400/415 ๋ฐœ์ƒ
    • ๋Œ€์‘: ํ”„๋ก ํŠธ ์š”์ฒญ ์˜ˆ์‹œ(FormData)์™€ ์„œ๋ฒ„ ์š”๊ตฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„์„ ๋ช…์„ธ์— ๊ณ ์ •, Postman์œผ๋กœ ๋จผ์ € ๊ฒ€์ฆ ํ›„ ํ”„๋ก ํŠธ ์ ์šฉ
  • ERD/๋„๋ฉ”์ธ ์„ค๊ณ„ ๋ณ€๊ฒฝ ๋น„์šฉ

    • ๋ฌธ์ œ: ํ™”๋ฉด/API ๋ณ€๊ฒฝ์ด ์ƒ๊ธธ ๋•Œ ERD๊ฐ€ ๊ฐ™์ด ํ”๋“ค๋ฆฌ๋ฉฐ ์ˆ˜์ • ๋น„์šฉ์ด ์ปค์ง
    • ๋Œ€์‘: ํ‚ค์›Œ๋“œ/๋ฆฌ๋ทฐ์ฒ˜๋Ÿผ ๋ณ€ํ™”๊ฐ€ ์žฆ์€ ์˜์—ญ์€ โ€œ์ •๊ทœํ™” vs ์ง‘๊ณ„ ํ…Œ์ด๋ธ”โ€ ๊ธฐ์ค€์„ ์„ธ์šฐ๊ณ , ๋ˆ„์  ์ง‘๊ณ„(GOOD/BAD) ํ…Œ์ด๋ธ”๋กœ ์กฐํšŒ ์„ฑ๋Šฅ์„ ํ™•๋ณด
  • ์„œ๋ฒ„ ์‹œ๊ฐ„๋Œ€(UTC)๋กœ ์ธํ•ด ์ƒ์„ฑ ์‹œ๊ฐ„์ด ํ•œ๊ตญ ์‹œ๊ฐ„๊ณผ ๋‹ค๋ฅด๊ฒŒ ์ €์žฅ๋จ

    • ๋ฌธ์ œ: AWS ์„œ๋ฒ„ ๋ฆฌ์ „/๊ธฐ๋ณธ ํƒ€์ž„์กด ์„ค์ • ์˜ํ–ฅ์œผ๋กœ LocalDateTime.now() ๊ธฐ์ค€ ์‹œ๊ฐ„์ด ํ•œ๊ตญ ์‹œ๊ฐ„(KST)๊ณผ ์–ด๊ธ‹๋‚˜, ๋ชจ์ง‘๊ธ€/์ฐŒ๋ฅด๊ธฐ ์ƒ์„ฑ ์‹œ๊ฐ„์ด ํ”„๋ก ํŠธ์—์„œ ๊ธฐ๋Œ€ํ•œ ์‹œ๊ฐ„๊ณผ ๋‹ค๋ฅด๊ฒŒ ๋ณด์ž„
    • ์›์ธ: ์„œ๋ฒ„ ํ™˜๊ฒฝ(์˜ˆ: UTC ๋˜๋Š” ๋‹ค๋ฅธ ํƒ€์ž„์กด) ๊ธฐ์ค€์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ๊ฐ„์ด ์ƒ์„ฑ๋จ
    • ๋Œ€์‘: Recruiting, Poking ์—”ํ‹ฐํ‹ฐ์— @PrePersist๋ฅผ ์ถ”๊ฐ€ํ•ด ์ €์žฅ ์ง์ „์— KST(Asia/Seoul) ๊ธฐ์ค€์œผ๋กœ date๋ฅผ ์„ธํŒ…

    Recruiting

    import java.time.ZoneId;
    
    @PrePersist
    public void prePersist() {
        if (this.date == null) {
            this.date = LocalDateTime.now(ZoneId.of("Asia/Seoul"))
                    .truncatedTo(java.time.temporal.ChronoUnit.MINUTES);
        }
    }

    Poking

    import java.time.ZoneId;
    
    @PrePersist
    public void prePersist() {
        if (this.date == null) {
            this.date = LocalDateTime.now(ZoneId.of("Asia/Seoul"));
        }
    }

ํšŒ๊ณ  (์ž˜ํ•œ ์ )

  • 3์ฃผ ๋‚ด โ€œ๊ธฐ๋Šฅ ์™„์„ฑ + ๋ฐฐํฌโ€๊นŒ์ง€ ๋„๋‹ฌ

    • OAuth ๊ธฐ๋ฐ˜ ์ธ์ฆ ํ๋ฆ„์„ ๊ตฌํ˜„ํ•˜๊ณ , Nginx/HTTPS๋ฅผ ํฌํ•จํ•œ ์‹ค์„œ๋น„์Šค ํ˜•ํƒœ๋กœ ๋๊นŒ์ง€ ์—ฐ๊ฒฐ
  • ์กฐํšŒ ์„ฑ๋Šฅ์„ ๊ณ ๋ คํ•œ ์„ค๊ณ„ ์‹œ๋„

    • ๋™๋ฃŒํ‰๊ฐ€ ํ‚ค์›Œ๋“œ Top3/Count ์š”๊ตฌ์‚ฌํ•ญ์„ ๋ˆ„์  ์ง‘๊ณ„ ํ…Œ์ด๋ธ”๋กœ ๋ถ„๋ฆฌํ•ด ์กฐํšŒ๋ฅผ ๋‹จ์ˆœํ™”
  • API ๋ช…์„ธ ์ค‘์‹ฌ ํ˜‘์—…

    • ํ™”๋ฉด ํ๋ฆ„(๋™์ผ ์œ ์ € ์—ฌ๋ถ€ ํŒ๋ณ„, canEdit ๋“ฑ)์„ ๋ช…์„ธ์— ๋ฐ˜์˜ํ•ด ํ”„๋ก ํŠธ์™€ ํ•ฉ์˜์ ์„ ๋งŒ๋“ค๊ณ  ๊ฐœ๋ฐœ ์ง„ํ–‰

๊ฐœ์„  ๊ณ„ํš (๋‹ค์Œ ๋‹จ๊ณ„)

  • ์ธ์ฆ ๊ณ ๋„ํ™”

    • idToken์€ โ€œ์ตœ์ดˆ ๋กœ๊ทธ์ธ ๊ฒ€์ฆโ€์—๋งŒ ์‚ฌ์šฉ
    • ์„œ๋ฒ„๊ฐ€ access/refresh JWT๋ฅผ ๋ฐœ๊ธ‰ํ•˜๊ณ , ๊ถŒํ•œ/๋งŒ๋ฃŒ/์žฌ๋ฐœ๊ธ‰ ํ๋ฆ„์„ ํ‘œ์ค€ํ™”
  • Docker ๊ธฐ๋ฐ˜ ๋ฐฐํฌ ์ „ํ™˜

    • Spring + MySQL(+Nginx) ์ปจํ…Œ์ด๋„ˆํ™”๋กœ ์‹คํ–‰ ํ™˜๊ฒฝ์„ ๊ณ ์ •
    • docker compose๋กœ ๋กœ์ปฌ/์„œ๋ฒ„ ํ™˜๊ฒฝ์„ ๋™์ผํ•˜๊ฒŒ ๋งž์ถ”๊ธฐ
  • CI/CD ๋„์ž…

    • GitHub Actions๋กœ ๋นŒ๋“œ/ํ…Œ์ŠคํŠธ ํ›„ EC2 ๋ฐฐํฌ ์ž๋™ํ™”
    • ๋ฐฐํฌ ์‹คํŒจ ์‹œ ๋กค๋ฐฑ ๋˜๋Š” ์ด์ „ ๋ฒ„์ „ ์œ ์ง€ ์ „๋žต ์ˆ˜๋ฆฝ
  • ์šด์˜ ๊ธฐ๋ณธ๊ธฐ ์ถ”๊ฐ€

    • ๋กœ๊ทธ(๊ตฌ์กฐํ™” ๋กœ๊ทธ), ๋ชจ๋‹ˆํ„ฐ๋ง(ํ—ฌ์Šค์ฒดํฌ/๋ฉ”ํŠธ๋ฆญ) ์ ์šฉ
    • ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €(systemd) ๋˜๋Š” ์ปจํ…Œ์ด๋„ˆ ์žฌ์‹œ์ž‘ ์ •์ฑ…์œผ๋กœ ์•ˆ์ •์„ฑ ํ™•๋ณด
  • DB/๋„๋ฉ”์ธ ๋ฆฌํŒฉํ† ๋ง

    • ๋ณ€ํ™”๊ฐ€ ์žฆ์€ ๋„๋ฉ”์ธ(ํ‚ค์›Œ๋“œ/๋ฆฌ๋ทฐ/ํ”„๋กœํ•„ ํ™•์žฅ)์— ๋Œ€ํ•ด ์Šคํ‚ค๋งˆ ์ •์ฑ… ์ •๋ฆฌ
    • ์ธ๋ฑ์Šค/์ฟผ๋ฆฌ ํŠœ๋‹ ๋ฐ ์กฐํšŒ API์˜ ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋„์ž…

About

๐ŸŒŠPARD 6๊ธฐ Longkathon MateCheck ์„œ๋ฒ„๋ ˆํฌ

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages