Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ dependencies {

// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.14'

// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

}

tasks.named('test') {
Expand Down
7 changes: 5 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ services:
- SPRING_DATASOURCE_USERNAME=${POSTGRES_USER}
- SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD}
- SPRING_PROFILES_ACTIVE=docker # Dockerfile의 ENV와 일치
- JWT_SECRET=${JWT_SECRET}
- JWT_ACCESS_TOKEN_VALIDITY_IN_SECONDS=${JWT_ACCESS_TOKEN_VALIDITY_IN_SECONDS}
depends_on:
db:
condition: service_healthy
- db
# db:
# condition: service_healthy

# 2. PostgreSQL 서비스
db:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.mobility.api.domain.auth.controller;

import com.mobility.api.domain.auth.dto.response.TokenDto;
import com.mobility.api.domain.auth.service.AuthService;
import com.mobility.api.domain.office.dto.request.DispatchSearchDto;
import com.mobility.api.domain.office.dto.request.OfficeLoginReq;
import com.mobility.api.domain.office.dto.request.OfficeSignupReq;
import com.mobility.api.domain.office.dto.response.GetAllDispatchRes;
import com.mobility.api.global.annotation.SwaggerPageable;
import com.mobility.api.global.response.CommonResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;

@Tag(name = "사무실 인증 관련 요청(/api/v1/auth/office/...)")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/auth/office")
public class AuthOfficeV1Controller {

private final AuthService authService;

@Operation(summary = "사무실 회원가입")
@PostMapping("/signup")
public CommonResponse<String> signupOffice(
@RequestBody OfficeSignupReq req
) {
authService.signupOffice(req);
return CommonResponse.success(null);
}

/**
* <pre>
* 사무실 - 로그인
* </pre>
*/
@Operation(summary = "사무실 로그인 요청", description = "ID와 비밀번호를 입력받아 Access Token을 발급합니다.")
@PostMapping("/login") // RequestMapping(method=POST)와 같습니다.
public CommonResponse<TokenDto> login(@RequestBody OfficeLoginReq req) {

// 1. 서비스 호출 (로그인 로직 수행)
TokenDto tokenDto = authService.officeLogin(req);

// 2. 결과 반환
return CommonResponse.success(tokenDto);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.mobility.api.domain.auth.dto.response;

public record TokenDto(
String accessToken,
String grantType
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.mobility.api.domain.auth.service;

import com.mobility.api.domain.auth.dto.response.TokenDto;
import com.mobility.api.domain.office.dto.request.OfficeLoginReq;
import com.mobility.api.domain.office.dto.request.OfficeSignupReq;
import com.mobility.api.domain.office.entity.Manager;
import com.mobility.api.domain.office.entity.Office;
import com.mobility.api.domain.office.enums.ManagerRole;
import com.mobility.api.domain.office.repository.ManagerRepository;
import com.mobility.api.domain.office.repository.OfficeRepository;
import com.mobility.api.global.exception.GlobalException;
import com.mobility.api.global.jwt.JwtProvider;
import com.mobility.api.global.response.ResultCode;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class AuthService {

private final ManagerRepository managerRepository;
private final OfficeRepository officeRepository;

private final JwtProvider jwtProvider; // 토큰 발급기
private final PasswordEncoder passwordEncoder; // 비밀번호 검사기

/**
* 사무실 회원가입 (사무실 생성 + 사장님 계정 생성)
*/
@Transactional
public void signupOffice(OfficeSignupReq req) {

// 1. 아이디 중복 검사
if (managerRepository.existsByLoginId(req.loginId())) {
// throw new GlobalException(ResultCode.DUPLICATE_USER_ID); // 에러 코드 추가 필요
throw new GlobalException(ResultCode.FIXME_FAIL);
}

// 2. 사무실 정보 저장
Office office = Office.builder()
.officeName(req.officeName())
.officeRegistrationNumber(req.officeRegistrationNumber())
.officeAddress(req.officeAddress())
.officeTelNumber(req.officeTelNumber())
.build();

officeRepository.save(office); // DB에 사무실 Insert (이때 ID 생성됨)

// 3. 사장님(Manager) 정보 저장
Manager manager = Manager.builder()
.loginId(req.loginId())
.password(passwordEncoder.encode(req.password())) // 비밀번호 암호화
// .password(req.password()) // 비밀번호 암호화
.name(req.managerName())
.phone(req.managerPhone())
.email(req.managerEmail())
.role(ManagerRole.OWNER) // 가입 시점엔 무조건 사장님(OWNER)
.office(office) // 위에서 만든 사무실 연결
.build();

managerRepository.save(manager);
}

/**
* 사무실 관리자 (Manager) 로그인
*/
@Transactional
public TokenDto officeLogin(OfficeLoginReq req) {
// 1. 아이디(loginId)로 매니저 찾기
Manager manager = managerRepository.findByLoginId(req.loginId())
.orElseThrow(() -> new GlobalException(ResultCode.NOT_FOUND_USER));

// 2. 비밀번호 검증
if (!passwordEncoder.matches(req.password(), manager.getPassword())) {
// throw new GlobalException(ResultCode.PASSWORD_NOT_MATCH);
throw new GlobalException(ResultCode.FIXME_FAIL);
}

// 3. 토큰 생성
// Subject: loginId (나중에 이걸로 DB 조회함)
// Role: "ROLE_OFFICE" (일단 고정, 필요하면 manager.getRole().name() 사용)
String accessToken = jwtProvider.createToken(manager.getLoginId(), "ROLE_OFFICE");

return new TokenDto(accessToken, "Bearer");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.mobility.api.domain.office.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;

public record OfficeLoginReq(
@Schema(description = "관리자 웹 아이디", example = "tak123")
String loginId,

@Schema(description = "비밀번호", example = "tak123!")
String password
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.mobility.api.domain.office.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;

public record OfficeSignupReq(

// --- 사무실 정보 ---
@Schema(description = "사업장 이름", example = "mobi 탁송")
String officeName,

@Schema(description = "사업자 등록번호", example = "123-45-67890")
String officeRegistrationNumber,

@Schema(description = "사업장 주소", example = "전북 전주시 완산구 용머리로 29")
String officeAddress,

@Schema(description = "사무실 전화번호", example = "063-123-4567")
String officeTelNumber,

// --- 사장님(관리자) 정보 ---
@Schema(description = "관리자 웹 아이디", example = "tak123")
String loginId,

@Schema(description = "비밀번호", example = "tak123!")
String password,

@Schema(description = "관리자 이름", example = "김규원")
String managerName,

@Schema(description = "관리자 휴대폰 번호", example = "010-5244-4070")
String managerPhone,

@Schema(description = "관리자 이메일", example = "rlarbdnjs0630@gmail.com")
String managerEmail
) {}
61 changes: 61 additions & 0 deletions src/main/java/com/mobility/api/domain/office/entity/Manager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.mobility.api.domain.office.entity;

import com.mobility.api.domain.office.enums.ManagerRole;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "manager") // DB 테이블명
public class Manager {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "manager_id")
private Long id;

// 1. 로그인 ID (새로 추가! - 예: tak123)
@Column(nullable = false, unique = true, length = 50)
private String loginId;

// 2. 비밀번호
@Column(nullable = false)
private String password;

// 3. 사용자 이름 (예: 김담당)
@Column(nullable = false, length = 20)
private String name;

// 휴대폰 번호 (예: 010-5244-4070)
@Column(nullable = false, length = 20)
private String phone;

// 이메일 (로그인용 아님, 연락용)
@Column(nullable = false, length = 100)
private String email;

// 4. 권한 (OWNER: 사무실 대표, ??: 일반 직원) - 선택사항
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private ManagerRole role;

// 5. 소속 사무실 (어느 사무실 사람인지?)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "office_id")
private Office office;

@Builder
public Manager(String loginId, String password, String name, String phone, String email, ManagerRole role, Office office) {
this.loginId = loginId;
this.password = password;
this.name = name;
this.phone = phone;
this.email = email;
this.role = role;
this.office = office;
}
}
23 changes: 19 additions & 4 deletions src/main/java/com/mobility/api/domain/office/entity/Office.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.mobility.api.domain.office.entity;

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

@Entity
@Getter
Expand All @@ -17,9 +14,27 @@ public class Office {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// 1. 사업장 이름 (예: mobi 탁송)
@Column(nullable = false, length = 50)
private String officeName;

// 사업자 등록 번호
@Column(name = "office_registration_number")
private String officeRegistrationNumber;

// 3. 사업장 주소
@Column(nullable = false)
private String officeAddress;

// 사업장 전화번호
@Column(name = "office_tel_number")
private String officeTelNumber;

@Builder
public Office(String officeName, String officeRegistrationNumber, String officeAddress, String officeTelNumber) {
this.officeName = officeName;
this.officeRegistrationNumber = officeRegistrationNumber;
this.officeAddress = officeAddress;
this.officeTelNumber = officeTelNumber;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.mobility.api.domain.office.enums;

/**
* <pre>
* FIXME 일단 OWNER만 사용
* </pre>
*/
public enum ManagerRole {
OWNER, // 사무실 생성/삭제, 직원 관리 가능
// STAFF // 배차 등록/수정만 가능
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.mobility.api.domain.office.repository;

import com.mobility.api.domain.dispatch.entity.Dispatch;
import com.mobility.api.domain.office.entity.Manager;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface ManagerRepository extends JpaRepository<Manager, Long> {

Optional<Manager> findByLoginId(String loginId);

boolean existsByLoginId(String loginId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.mobility.api.domain.office.repository;

import com.mobility.api.domain.office.entity.Office;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OfficeRepository extends JpaRepository<Office, Long> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;

public interface TransporterRepository extends JpaRepository<Transporter, Long> {

Expand Down Expand Up @@ -57,4 +58,6 @@ List<TransporterDistanceProjection> findEligibleDriversForAutoDispatch(
@Param("lon") double lon
);

Optional<Transporter> findByPhone(String username);

}
Loading
Loading