diff --git a/build.gradle b/build.gradle index a08b8be..1be7389 100644 --- a/build.gradle +++ b/build.gradle @@ -25,8 +25,9 @@ dependencies { // Database & JPA implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - runtimeOnly 'com.mysql:mysql-connector-j' - runtimeOnly 'com.h2database:h2' + runtimeOnly 'org.postgresql:postgresql' + testRuntimeOnly 'com.h2database:h2' + implementation 'org.hibernate.orm:hibernate-vector:6.4.1.Final' // Apache Tika 텍스트 추출 implementation 'org.apache.tika:tika-core:2.9.1' @@ -83,9 +84,6 @@ dependencies { implementation "software.amazon.awssdk:s3" implementation "software.amazon.awssdk:auth" - // AWS S3 Presigner 의존성 - implementation 'software.amazon.awssdk:s3:2.20.162' - implementation 'io.awspring.cloud:spring-cloud-aws-starter-sqs:3.1.0' } diff --git a/compose.yml b/compose.yml index 8ce8a72..ebeac1c 100644 --- a/compose.yml +++ b/compose.yml @@ -1,21 +1,20 @@ services: - mysql: - image: mysql:8.0 - container_name: aibe-mysql + db: + image: pgvector/pgvector:pg16 + container_name: aibe-db restart: unless-stopped environment: - MYSQL_DATABASE: ${MYSQL_DATABASE} - MYSQL_USER: ${MYSQL_USER} - MYSQL_PASSWORD: ${MYSQL_PASSWORD} - MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} TZ: Asia/Seoul ports: - - "${MYSQL_PORT:-3307}:3306" + - "${POSTGRES_PORT:-5432}:5432" volumes: - - mysql_data:/var/lib/mysql + - pgdata:/var/lib/postgresql/data - ./db/init:/docker-entrypoint-initdb.d:ro healthcheck: - test: [ "CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD}" ] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 20 @@ -41,12 +40,13 @@ services: ports: - "4566:4566" environment: - - SERVICES=sqs,s3 - - DEFAULT_REGION=ap-northeast-2 + SERVICES: sqs,s3 + DEFAULT_REGION: ${AWS_REGION:-ap-northeast-2} volumes: - "./localstack:/var/lib/localstack" + - "./localstack/init:/etc/localstack/init/ready.d" healthcheck: - test: [ "CMD", "curl", "-s", "http://localhost:4566/_localstack/health" ] + test: ["CMD", "curl", "-s", "http://localhost:4566/_localstack/health"] interval: 10s timeout: 5s retries: 20 @@ -62,31 +62,28 @@ services: environment: SPRING_PROFILES_ACTIVE: docker - MYSQL_HOST: mysql - MYSQL_PORT: 3306 - MYSQL_DATABASE: ${MYSQL_DATABASE} - MYSQL_USER: ${MYSQL_USER} - MYSQL_PASSWORD: ${MYSQL_PASSWORD} + POSTGRES_HOST: db + POSTGRES_PORT: 5432 + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + + DOCKER_REDIS_HOST: redis + DOCKER_REDIS_PORT: 6379 LOCALSTACK_HOST: localstack LOCALSTACK_PORT: 4566 - AWS_REGION: ${AWS_REGION:-ap-northeast-2} - AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-test} - AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-test} + AWS_REGION: ${AWS_REGION:-ap-northeast-2} - GEMINI_API_KEY: ${OPENAI_API_KEY:-dummy_key_for_now} - RETELL_API_KEY: ${RETELL_API_KEY:-dummy_key_for_now} - RETELL_AGENT_ID: ${RETELL_AGENT_ID:-dummy_agent_id} depends_on: - mysql: + db: condition: service_healthy redis: condition: service_healthy localstack: condition: service_healthy - command: ["sh", "-lc", "sleep 8 && java -jar /app/app.jar"] volumes: - mysql_data: + pgdata: redis_data: \ No newline at end of file diff --git a/db/init/001_init.sql b/db/init/001_init.sql index 41a29e8..1eb8473 100644 --- a/db/init/001_init.sql +++ b/db/init/001_init.sql @@ -1,125 +1,130 @@ -- db/init/001_init.sql --- 목적: Docker MySQL 최초 기동 시 스키마를 확정해서 생성 - -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; +-- 목적: Docker PostgreSQL 최초 기동 시 스키마를 확정해서 생성 -- 1) member CREATE TABLE member ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, email VARCHAR(255) NOT NULL UNIQUE, password VARCHAR(255), nickname VARCHAR(50) NOT NULL, - role ENUM('MEMBER', 'ADMIN') DEFAULT 'MEMBER', + role VARCHAR(20) NOT NULL DEFAULT 'MEMBER' + CHECK (role IN ('MEMBER', 'ADMIN')), desired_job VARCHAR(100), preferred_location VARCHAR(100), - subscription_plan ENUM('FREE', 'PRO', 'ENTERPRISE') DEFAULT 'FREE', + subscription_plan VARCHAR(20) NOT NULL DEFAULT 'FREE' + CHECK (subscription_plan IN ('FREE', 'PRO', 'ENTERPRISE')), credit_balance INT DEFAULT 0, - auth_provider ENUM('LOCAL', 'GITHUB', 'GOOGLE', 'KAKAO') DEFAULT 'LOCAL', + auth_provider VARCHAR(20) NOT NULL DEFAULT 'LOCAL' + CHECK (auth_provider IN ('LOCAL', 'GITHUB', 'GOOGLE', 'KAKAO')), profile_image_url VARCHAR(500), - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - deleted_at DATETIME DEFAULT NULL, - status ENUM('ACTIVE', 'DORMANCY', 'DELETED') NOT NULL DEFAULT 'ACTIVE' -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP DEFAULT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' + CHECK (status IN ('ACTIVE', 'DORMANCY', 'DELETED')) +); -- 2) social_auth CREATE TABLE social_auth ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, member_id BIGINT NOT NULL, provider_member_id VARCHAR(255) NOT NULL, provider_type VARCHAR(50), - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - deleted_at DATETIME DEFAULT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP DEFAULT NULL, CONSTRAINT fk_social_member FOREIGN KEY (member_id) REFERENCES member(id) ON DELETE CASCADE, - UNIQUE KEY unique_provider_member (provider_type, provider_member_id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + CONSTRAINT unique_provider_member UNIQUE (provider_type, provider_member_id) +); -- 3) resume CREATE TABLE resume ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, member_id BIGINT NOT NULL, title VARCHAR(255) NOT NULL, s3_file_url VARCHAR(500), content TEXT, is_analyzed BOOLEAN DEFAULT FALSE, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_resume_member FOREIGN KEY (member_id) REFERENCES member(id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +); -- 4) job_posting CREATE TABLE job_posting ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, member_id BIGINT NOT NULL, company_name VARCHAR(100) NOT NULL DEFAULT 'Self-Input', job_title VARCHAR(100) NOT NULL, job_description TEXT, posting_url TEXT, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_job_member FOREIGN KEY (member_id) REFERENCES member(id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +); -- 4-1) job_skill CREATE TABLE job_skill ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, job_posting_id BIGINT NOT NULL, skill_name VARCHAR(50) NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_skill_job FOREIGN KEY (job_posting_id) REFERENCES job_posting(id) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +); CREATE INDEX idx_skill_name ON job_skill(skill_name); -- 5) analysis_report CREATE TABLE analysis_report ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, resume_id BIGINT NOT NULL, job_posting_id BIGINT NOT NULL, match_score INT, - keyword_analysis JSON, - sentence_correction JSON, - generated_subtitle JSON, + keyword_analysis JSONB, + sentence_correction JSONB, + generated_subtitle JSONB, revised_full_content TEXT, - status ENUM('PENDING', 'PROCESSING', 'DELAYED', 'COMPLETED', 'FAILED') DEFAULT 'PENDING', - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + status VARCHAR(20) NOT NULL DEFAULT 'PENDING' + CHECK (status IN ('PENDING', 'PROCESSING', 'DELAYED', 'COMPLETED', 'FAILED')), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_report_resume FOREIGN KEY (resume_id) REFERENCES resume(id) ON DELETE CASCADE, CONSTRAINT fk_report_job FOREIGN KEY (job_posting_id) REFERENCES job_posting(id), - UNIQUE KEY unique_analysis (resume_id, job_posting_id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + CONSTRAINT unique_analysis UNIQUE (resume_id, job_posting_id) +); -- 6) interview_session CREATE TABLE interview_session ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, member_id BIGINT NOT NULL, resume_id BIGINT NOT NULL, job_posting_id BIGINT NOT NULL, - interview_mode ENUM('GENERAL', 'TAIL_BITING', 'PRESSURE') NOT NULL DEFAULT 'GENERAL', - interview_type ENUM('TEXT', 'VOICE') NOT NULL DEFAULT 'TEXT', - status ENUM('CREATED', 'IN_PROGRESS', 'DONE', 'ABORTED') DEFAULT 'IN_PROGRESS', + interview_mode VARCHAR(20) NOT NULL DEFAULT 'GENERAL' + CHECK (interview_mode IN ('GENERAL', 'TAIL_BITING', 'PRESSURE')), + interview_type VARCHAR(20) NOT NULL DEFAULT 'TEXT' + CHECK (interview_type IN ('TEXT', 'VOICE')), + status VARCHAR(20) NOT NULL DEFAULT 'IN_PROGRESS' + CHECK (status IN ('CREATED', 'IN_PROGRESS', 'DONE', 'ABORTED')), final_score INT DEFAULT 0, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_session_member FOREIGN KEY (member_id) REFERENCES member(id), CONSTRAINT fk_session_resume FOREIGN KEY (resume_id) REFERENCES resume(id), CONSTRAINT fk_session_job FOREIGN KEY (job_posting_id) REFERENCES job_posting(id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +); -- 7) interview_record CREATE TABLE interview_record ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, interview_session_id BIGINT NOT NULL, question_text TEXT NOT NULL, question_intent TEXT, @@ -127,49 +132,49 @@ CREATE TABLE interview_record ( follow_up_depth INT DEFAULT 0, s3_file_url VARCHAR(500), wpm INT, - stt_accuracy FLOAT, + stt_accuracy REAL, silence_count INT, - emotion_analysis JSON, + emotion_analysis JSONB, feedback_text TEXT, - evaluation_score FLOAT DEFAULT 0.0, + evaluation_score REAL DEFAULT 0.0, response_time_ms INT, turn_sequence INT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_record_session FOREIGN KEY (interview_session_id) REFERENCES interview_session(id) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +); -- 8) question_scrap CREATE TABLE question_scrap ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, member_id BIGINT NOT NULL, interview_record_id BIGINT NOT NULL, memo TEXT, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_scrap_member FOREIGN KEY (member_id) REFERENCES member(id), CONSTRAINT fk_scrap_record FOREIGN KEY (interview_record_id) REFERENCES interview_record(id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +); -- 9) notification CREATE TABLE notification ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, member_id BIGINT NOT NULL, message VARCHAR(255) NOT NULL, notification_type VARCHAR(50), is_read BOOLEAN DEFAULT FALSE, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_noti_member FOREIGN KEY (member_id) REFERENCES member(id) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +); -- 10) usage_log CREATE TABLE usage_log ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, member_id BIGINT NOT NULL, request_trace_id VARCHAR(100) NOT NULL, service_type VARCHAR(50) NOT NULL, @@ -179,11 +184,11 @@ CREATE TABLE usage_log ( target_type VARCHAR(50), target_id BIGINT, description VARCHAR(255), - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_usage_member FOREIGN KEY (member_id) REFERENCES member(id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +); CREATE INDEX idx_usage_member ON usage_log(member_id); CREATE INDEX idx_usage_created ON usage_log(created_at); @@ -191,35 +196,34 @@ CREATE INDEX idx_usage_service ON usage_log(service_type); -- 11) audit_log CREATE TABLE audit_log ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, admin_id BIGINT NOT NULL, target_id BIGINT, target_type VARCHAR(50), action_type VARCHAR(50) NOT NULL, - action_detail JSON, + action_detail JSONB, ip_address VARCHAR(50), member_agent VARCHAR(255), - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_audit_admin FOREIGN KEY (admin_id) REFERENCES member(id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +); -- 12) attachment CREATE TABLE attachment ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, owner_member_id BIGINT NOT NULL, s3_key VARCHAR(500) NOT NULL, - file_type ENUM('RESUME_ORIGINAL', 'RESUME_REVISED', 'INTERVIEW_AUDIO') NOT NULL, + file_type VARCHAR(30) NOT NULL + CHECK (file_type IN ('RESUME_ORIGINAL', 'RESUME_REVISED', 'INTERVIEW_AUDIO')), target_type VARCHAR(50) NOT NULL, target_id BIGINT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_attachment_owner FOREIGN KEY (owner_member_id) REFERENCES member(id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +); CREATE INDEX idx_attachment_owner ON attachment(owner_member_id); -CREATE INDEX idx_attachment_target ON attachment(target_type, target_id); - -SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file +CREATE INDEX idx_attachment_target ON attachment(target_type, target_id); \ No newline at end of file