Skip to content
Merged

Dev #51

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
067f63c
setting: mysql update
bumstone Aug 11, 2025
f711399
refactor: FastApiControllerλ₯Ό ν†΅ν•œ mindmap 생성
bumstone Aug 25, 2025
594770a
refactor: Mindmap 생성 및 μƒˆλ‘œκ³ μΉ¨ jwt 헀더 전달 및 Repo μ—…λ°μ΄νŠΈ 둜직 제거
bumstone Aug 25, 2025
15cc15e
setting: github webhook μ„€μ •
bumstone Aug 26, 2025
7e3fc5f
feat: μœ μ € 개발 기술 도메인 κ΅¬ν˜„
bumstone Aug 31, 2025
066e26d
feat: μ΄ˆλŒ€ μƒνƒœλ³„ λͺ©λ‘ 쑰회
bumstone Aug 31, 2025
0bf2c9c
feat: SecurityPath inviataions, notifications μΆ”κ°€
bumstone Aug 31, 2025
ddba2f9
feat: owner λ³€κ²½ 적용
bumstone Sep 1, 2025
a93df4c
feat: mindmap fastapi arangodb 연동
bumstone Sep 1, 2025
efd44e7
feat: WebhookController μ„€μ •
bumstone Sep 1, 2025
9a53179
style: 주석 및 SSE μ—”λ“œν¬μΈνŠΈ μœ„μΉ˜ λ³€κ²½
bumstone Sep 1, 2025
3ce441a
feat: notification μ°Έμ‘° ID, 만료일 μΆ”κ°€, SSE - μ‹€μ‹œκ°„ μƒˆ μ•Œλ¦Ό λ‚΄μš© 전솑
bumstone Sep 2, 2025
9cef651
fix: webhook μ—”λ“œν¬μΈλ“œ μˆ˜μ • 및 requestHeader 인증 μˆ˜μ •
bumstone Sep 3, 2025
4c950a6
feat: λͺ¨μ§‘ 곡고 μ—”ν‹°ν‹° κ΅¬ν˜„
bumstone Sep 3, 2025
ed3208d
feat: λͺ¨μ§‘ μ‹ μ²­ μ—”ν‹°ν‹° κ΅¬ν˜„
bumstone Sep 3, 2025
87755f1
fix: λͺ¨μ§‘μž ν•„λ“œλͺ… μˆ˜μ •
bumstone Sep 3, 2025
e5f2222
fix: spring μ˜μ‘΄μ„± λ―Έμ£Όμž… λΆ€λΆ„ μˆ˜μ •(user_skill)
bumstone Sep 4, 2025
593ce66
Merge pull request #49 from Gitdeun/feat/#46-recruitment_application
bumstone Sep 4, 2025
c52b26b
refactor: application, recruitment, userSkill μ—”ν‹°ν‹° κ΄€λ ¨ κ°œμ„ 
bumstone Sep 4, 2025
75ed34a
Merge pull request #50 from Gitdeun/feat/#46-recruitment_application
bumstone Sep 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ dependencies {
// Email
implementation 'org.springframework.boot:spring-boot-starter-mail'

// Github Webhook
implementation 'org.kohsuke:github-api:1.327'

// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
Expand Down
114 changes: 57 additions & 57 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,60 +1,60 @@
services:
app:
build:
context: src
dockerfile: Dockerfile
image: gitdeun:latest
container_name: gitdeun
ports:
- "8080:8080"
restart: always
env_file:
- ./.env
environment:
SPRING_PROFILES_ACTIVE: dev,s3Bucket
TZ: Asia/Seoul
volumes:
- /data/logs/spring:/app/logs # 둜그 마운트
depends_on:
redis:
condition: service_healthy
networks:
- app-network

redis:
image: redis:latest
container_name: gitdeun-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data # 데이터 지속성을 μœ„ν•œ λ³Όλ₯¨ μΆ”κ°€
command: redis-server --appendonly yes
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
networks:
- app-network
services:
app:
build:
context: src
dockerfile: Dockerfile
image: gitdeun:latest
container_name: gitdeun
ports:
- "8080:8080"
restart: always
env_file:
- ./.env
environment:
SPRING_PROFILES_ACTIVE: dev,s3Bucket
TZ: Asia/Seoul
volumes:
- /data/logs/spring:/app/logs # 둜그 마운트
depends_on:
redis:
condition: service_healthy
networks:
- app-network

nginx:
image: nginx:latest
container_name: gitdeun-nginx
ports:
- "80:80"
- "443:443"
volumes:
- /data/nginx/conf.d:/etc/nginx/conf.d
- /data/nginx/ssl:/etc/nginx/ssl
- /data/logs/nginx:/var/log/nginx
depends_on:
- app
networks:
- app-network
redis:
image: redis:latest
container_name: gitdeun-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data # 데이터 지속성을 μœ„ν•œ λ³Όλ₯¨ μΆ”κ°€
command: redis-server --appendonly yes
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
networks:
- app-network

nginx:
image: nginx:latest
container_name: gitdeun-nginx
ports:
- "80:80"
- "443:443"
volumes:
- /data/nginx/conf.d:/etc/nginx/conf.d
- /data/nginx/ssl:/etc/nginx/ssl
- /data/logs/nginx:/var/log/nginx
depends_on:
- app
networks:
- app-network

networks:
app-network:
name: gitdeun-network

networks:
app-network:
name: gitdeun-network
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.teamEWSN.gitdeun.Application.entity;

import com.teamEWSN.gitdeun.Recruitment.entity.Recruitment;
import com.teamEWSN.gitdeun.Recruitment.entity.RecruitmentField;
import com.teamEWSN.gitdeun.common.util.AuditedEntity;
import com.teamEWSN.gitdeun.user.entity.User;
import jakarta.persistence.*;
import lombok.*;

@Entity
@Table(name = "application",
uniqueConstraints = {
// 동일 μ‚¬μš©μžΒ·κ³΅κ³ μ— λŒ€ν•΄ "ν™œμ„±" μ‹ μ²­ 쀑볡 λ°©μ§€μš© (WITHDRAWN μ œμ™Έ)
@UniqueConstraint(name = "uk_active_application",
columnNames = {"recruitment_id", "applicant_id", "active"})
},
indexes = {
@Index(name = "idx_application_recruitment", columnList = "recruitment_id"),
@Index(name = "idx_application_applicant", columnList = "applicant_id"),
@Index(name = "idx_application_status", columnList = "status")
})
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Application extends AuditedEntity {

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

/** λͺ¨μ§‘곡고 */
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "recruitment_id", nullable = false)
private Recruitment recruitment;

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(
name = "applicant_id", nullable = false,
foreignKey = @ForeignKey(name = "fk_applicant_user") // μ™Έλž˜ν‚€ κ°’ λͺ…μ‹œ
)
private User applicant;

/** 지원 λΆ„μ•Ό (μž‘μ„±μžμ˜ λͺ¨μ§‘ λΆ„μ•Ό μ€‘μ—μ„œ 선택) */
@Enumerated(EnumType.STRING)
@Column(name = "applied_field", length = 32)
private RecruitmentField appliedField;

/** 지원 λ©”μ„Έμ§€ */
@Column(name = "message", length = 1000)
private String message;

/** μ‹ μ²­ 거절 μ‚¬μœ (거절 μ‹œ 선택) */
@Column(name = "reject_reason", length = 500)
private String rejectReason;

/** μ‹ μ²­ μƒνƒœ (κ²€ν†  쀑 / 수락 / 거절 / 철회) */
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 24)
private ApplicationStatus status;

/** ν™œμ„± μ‹ μ²­ μ—¬λΆ€ (WITHDRAWN λ©΄ false) */
@Column(nullable = false)
private boolean active;


// μƒνƒœ 전이
public void accept() { this.status = ApplicationStatus.ACCEPTED; }
public void reject(String reason) { this.status = ApplicationStatus.REJECTED; this.rejectReason = reason; }
public void withdraw() { this.status = ApplicationStatus.WITHDRAWN; this.active = false; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.teamEWSN.gitdeun.Application.entity;

public enum ApplicationStatus {
PENDING, // 검토쀑
ACCEPTED, // 수락
REJECTED, // 거절
WITHDRAWN // 철회
}
112 changes: 112 additions & 0 deletions src/main/java/com/teamEWSN/gitdeun/Recruitment/entity/Recruitment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.teamEWSN.gitdeun.Recruitment.entity;

import com.teamEWSN.gitdeun.user.entity.User;
import com.teamEWSN.gitdeun.userskill.entity.DeveloperSkill;
import jakarta.persistence.*;
import lombok.*;

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

@Entity
@Table(name = "recruitment",
indexes = {
@Index(name = "idx_recruitment_status", columnList = "status"),
@Index(name = "idx_recruitment_deadline", columnList = "end_at"),
@Index(name = "idx_recruitment_recruiter", columnList = "recruiter_id")
})
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Recruitment {

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

// λͺ¨μ§‘μž
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(
name = "recruiter_id", nullable = false,
foreignKey = @ForeignKey(name = "fk_recruitment_user") // μ™Έλž˜ν‚€ κ°’ λͺ…μ‹œ
)
private User recruiter;

// λͺ¨μ§‘ 곡고 제λͺ© (μž…λ ₯ ν•„μš”)
@Column(nullable = false, length = 120)
private String title;

// 연락망: 이메일 μ£Όμ†Œ (μž…λ ₯ ν•„μš”)
@Column(length = 120)
private String contactEmail;

// λͺ¨μ§‘일 (μž…λ ₯ ν•„μš”)
@Column(name = "start_at", nullable = false)
private LocalDateTime startAt;

// λͺ¨μ§‘ 마감일 (μž…λ ₯ ν•„μš”)
@Column(name = "end_at", nullable = false)
private LocalDateTime endAt;

// λͺ¨μ§‘ λ‚΄μš© (μž…λ ₯ ν•„μš”)
@Lob // ν•΄λ‹Ή ν•„λ“œλ₯Ό DBμ—μ„œ λŒ€μš©λŸ‰ μ €μž₯ν•˜λ„λ‘ λ§€ν•‘
@Column(nullable = false)
private String content;

// νŒ€ 규λͺ¨(총인원) (μž…λ ₯ ν•„μš”)
@Column(name = "team_size_total", nullable = false)
private Integer teamSizeTotal;

// 남은 λͺ¨μ§‘ 인원(μž…λ ₯ ν•„μš”) – μ‹ μ²­ μ‹œ 1 κ°μ†Œ, 철회/거절 μ‹œ 1 증가(볡원)
@Column(name = "recruit_quota", nullable = false)
private Integer recruitQuota;

// 개발 λΆ„μ•Ό νƒœκ·Έ (선택 ν•„μš”) - BACKEND/FRONTEND/AI λ“±
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "recruitment_field_tags", joinColumns = @JoinColumn(name = "recruitment_id"))
@Column(name = "field", nullable = false, length = 32)
@Enumerated(EnumType.STRING)
@Builder.Default
private Set<RecruitmentField> fieldTags = new HashSet<>();

// 개발 μ–Έμ–΄ νƒœκ·Έ (선택 ν•„μš”) - ν™”λ©΄ ν•„ν„°/ν‘œμ‹œμš©
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "recruitment_language_tags", joinColumns = @JoinColumn(name = "recruitment_id"))
@Column(name = "language", nullable = false, length = 64)
@Enumerated(EnumType.STRING)
@Builder.Default
private Set<DeveloperSkill> languageTags = new HashSet<>();

// λͺ¨μ§‘ μƒνƒœ (λͺ¨μ§‘ μ˜ˆμ • / λͺ¨μ§‘ 쀑 / λͺ¨μ§‘ 마감)
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 24)
private RecruitmentStatus status;

// 쑰회수
@Column(name = "views", nullable = false)
@Builder.Default
private int views = 0;

// λͺ¨μ§‘ 곡고 이미지 (선택)
@Builder.Default
@OneToMany(mappedBy = "recruitment", cascade = CascadeType.ALL, orphanRemoval = true)
private List<RecruitmentImage> recruitmentImages = new ArrayList<>();

/*// 지원 μ‹ μ²­ 리슀트
@OneToMany(mappedBy = "recruitment", fetch = FetchType.LAZY)
@Builder.Default
private List<Application> applications = new ArrayList<>();*/

// μΆ”μ²œ κ°€μ€‘μΉ˜μš© μš”κ΅¬ 기술(μ–Έμ–΄/ν”„λ ˆμž„μ›Œν¬/툴 λ“±, 선택)
@OneToMany(mappedBy = "recruitment", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private Set<RecruitmentRequiredSkill> requiredSkills = new HashSet<>();



public void increaseView() { this.views++; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.teamEWSN.gitdeun.Recruitment.entity;

public enum RecruitmentField {
FRONTEND, // ν”„λ‘ νŠΈμ—”λ“œ
BACKEND, // λ°±μ—”λ“œ
FULLSTACK, // ν’€μŠ€νƒ
ANDROID, // μ•ˆλ“œλ‘œμ΄λ“œ
IOS, // iOS
DATA, // 데이터 뢄석
DEVOPS, // DevOps
AI, // 인곡지λŠ₯
EMBEDDED, // μž„λ² λ””λ“œ
GAME, // κ²Œμž„
SECURITY, // λ³΄μ•ˆ
ETC; // 기타
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.teamEWSN.gitdeun.Recruitment.entity;

import jakarta.persistence.*;
import lombok.*;

import java.time.LocalDateTime;

@Entity
@Getter
@Setter
@Builder
@Table(name = "recruitment_image", indexes = {
@Index(name = "idx_recruitment_image_recruitment_id_deleted_at", columnList = "recruitment_id, deleted_at")
})
@NoArgsConstructor
@AllArgsConstructor
public class RecruitmentImage {

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

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "recruitment_id")
private Recruitment recruitment;

@Column(name = "image_url", length = 255)
private String imageUrl;

@Column(name = "deleted_at")
private LocalDateTime deletedAt;

// Soft delete 처리 λ©”μ†Œλ“œ
public void softDelete() {
this.deletedAt = LocalDateTime.now();
}
}
Loading