diff --git a/.gitignore b/.gitignore index c2065bc..cdfa4e1 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,172 @@ out/ ### VS Code ### .vscode/ +db/data/\#ib_16384_0.dblwr +db/data/\#ib_16384_1.dblwr +db/data/\#innodb_redo/\#ib_redo10_tmp +db/data/\#innodb_redo/\#ib_redo11_tmp +db/data/\#innodb_redo/\#ib_redo12_tmp +db/data/\#innodb_redo/\#ib_redo13_tmp +db/data/\#innodb_redo/\#ib_redo14_tmp +db/data/\#innodb_redo/\#ib_redo15_tmp +db/data/\#innodb_redo/\#ib_redo16_tmp +db/data/\#innodb_redo/\#ib_redo17_tmp +db/data/\#innodb_redo/\#ib_redo18_tmp +db/data/\#innodb_redo/\#ib_redo19_tmp +db/data/\#innodb_redo/\#ib_redo20_tmp +db/data/\#innodb_redo/\#ib_redo21_tmp +db/data/\#innodb_redo/\#ib_redo22_tmp +db/data/\#innodb_redo/\#ib_redo23_tmp +db/data/\#innodb_redo/\#ib_redo24_tmp +db/data/\#innodb_redo/\#ib_redo25_tmp +db/data/\#innodb_redo/\#ib_redo26_tmp +db/data/\#innodb_redo/\#ib_redo27_tmp +db/data/\#innodb_redo/\#ib_redo28_tmp +db/data/\#innodb_redo/\#ib_redo29_tmp +db/data/\#innodb_redo/\#ib_redo30_tmp +db/data/\#innodb_redo/\#ib_redo31_tmp +db/data/\#innodb_redo/\#ib_redo32_tmp +db/data/\#innodb_redo/\#ib_redo33_tmp +db/data/\#innodb_redo/\#ib_redo34_tmp +db/data/\#innodb_redo/\#ib_redo35_tmp +db/data/\#innodb_redo/\#ib_redo36_tmp +db/data/\#innodb_redo/\#ib_redo37_tmp +db/data/\#innodb_redo/\#ib_redo38_tmp +db/data/\#innodb_redo/\#ib_redo39_tmp +db/data/\#innodb_redo/\#ib_redo40_tmp +db/data/\#innodb_redo/\#ib_redo9 +db/data/auto.cnf +db/data/binlog.000001 +db/data/binlog.000002 +db/data/binlog.index +db/data/ca-key.pem +db/data/ca.pem +db/data/client-cert.pem +db/data/client-key.pem +db/data/ib_buffer_pool +db/data/ibdata1 +db/data/mysql.ibd +db/data/mysql.sock +db/data/mysql/general_log.CSM +db/data/mysql/general_log.CSV +db/data/mysql/general_log_213.sdi +db/data/mysql/slow_log.CSM +db/data/mysql/slow_log.CSV +db/data/mysql/slow_log_214.sdi +db/data/performance_schema/accounts_145.sdi +db/data/performance_schema/binary_log_trans_189.sdi +db/data/performance_schema/cond_instances_82.sdi +db/data/performance_schema/data_lock_waits_161.sdi +db/data/performance_schema/data_locks_160.sdi +db/data/performance_schema/error_log_83.sdi +db/data/performance_schema/events_errors_su_139.sdi +db/data/performance_schema/events_errors_su_140.sdi +db/data/performance_schema/events_errors_su_141.sdi +db/data/performance_schema/events_errors_su_142.sdi +db/data/performance_schema/events_errors_su_143.sdi +db/data/performance_schema/events_stages_cu_111.sdi +db/data/performance_schema/events_stages_hi_112.sdi +db/data/performance_schema/events_stages_hi_113.sdi +db/data/performance_schema/events_stages_su_114.sdi +db/data/performance_schema/events_stages_su_115.sdi +db/data/performance_schema/events_stages_su_116.sdi +db/data/performance_schema/events_stages_su_117.sdi +db/data/performance_schema/events_stages_su_118.sdi +db/data/performance_schema/events_statement_119.sdi +db/data/performance_schema/events_statement_120.sdi +db/data/performance_schema/events_statement_121.sdi +db/data/performance_schema/events_statement_122.sdi +db/data/performance_schema/events_statement_123.sdi +db/data/performance_schema/events_statement_124.sdi +db/data/performance_schema/events_statement_125.sdi +db/data/performance_schema/events_statement_126.sdi +db/data/performance_schema/events_statement_127.sdi +db/data/performance_schema/events_statement_128.sdi +db/data/performance_schema/events_statement_129.sdi +db/data/performance_schema/events_statement_130.sdi +db/data/performance_schema/events_transacti_131.sdi +db/data/performance_schema/events_transacti_132.sdi +db/data/performance_schema/events_transacti_133.sdi +db/data/performance_schema/events_transacti_134.sdi +db/data/performance_schema/events_transacti_135.sdi +db/data/performance_schema/events_transacti_136.sdi +db/data/performance_schema/events_transacti_137.sdi +db/data/performance_schema/events_transacti_138.sdi +db/data/performance_schema/events_waits_cur_84.sdi +db/data/performance_schema/events_waits_his_85.sdi +db/data/performance_schema/events_waits_his_86.sdi +db/data/performance_schema/events_waits_sum_87.sdi +db/data/performance_schema/events_waits_sum_88.sdi +db/data/performance_schema/events_waits_sum_89.sdi +db/data/performance_schema/events_waits_sum_90.sdi +db/data/performance_schema/events_waits_sum_91.sdi +db/data/performance_schema/events_waits_sum_92.sdi +db/data/performance_schema/file_instances_93.sdi +db/data/performance_schema/file_summary_by__94.sdi +db/data/performance_schema/file_summary_by__95.sdi +db/data/performance_schema/global_status_181.sdi +db/data/performance_schema/global_variables_184.sdi +db/data/performance_schema/host_cache_96.sdi +db/data/performance_schema/hosts_146.sdi +db/data/performance_schema/keyring_componen_191.sdi +db/data/performance_schema/keyring_keys_152.sdi +db/data/performance_schema/log_status_174.sdi +db/data/performance_schema/memory_summary_b_154.sdi +db/data/performance_schema/memory_summary_b_155.sdi +db/data/performance_schema/memory_summary_b_156.sdi +db/data/performance_schema/memory_summary_b_157.sdi +db/data/performance_schema/memory_summary_g_153.sdi +db/data/performance_schema/metadata_locks_159.sdi +db/data/performance_schema/mutex_instances_97.sdi +db/data/performance_schema/objects_summary__98.sdi +db/data/performance_schema/performance_time_99.sdi +db/data/performance_schema/persisted_variab_187.sdi +db/data/performance_schema/prepared_stateme_175.sdi +db/data/performance_schema/processlist_100.sdi +db/data/performance_schema/replication_appl_165.sdi +db/data/performance_schema/replication_appl_166.sdi +db/data/performance_schema/replication_appl_167.sdi +db/data/performance_schema/replication_appl_168.sdi +db/data/performance_schema/replication_appl_170.sdi +db/data/performance_schema/replication_appl_171.sdi +db/data/performance_schema/replication_asyn_172.sdi +db/data/performance_schema/replication_asyn_173.sdi +db/data/performance_schema/replication_conn_162.sdi +db/data/performance_schema/replication_conn_164.sdi +db/data/performance_schema/replication_grou_163.sdi +db/data/performance_schema/replication_grou_169.sdi +db/data/performance_schema/rwlock_instances_101.sdi +db/data/performance_schema/session_account__151.sdi +db/data/performance_schema/session_connect__150.sdi +db/data/performance_schema/session_status_182.sdi +db/data/performance_schema/session_variable_185.sdi +db/data/performance_schema/setup_actors_102.sdi +db/data/performance_schema/setup_consumers_103.sdi +db/data/performance_schema/setup_instrument_104.sdi +db/data/performance_schema/setup_objects_105.sdi +db/data/performance_schema/setup_threads_106.sdi +db/data/performance_schema/socket_instances_147.sdi +db/data/performance_schema/socket_summary_b_148.sdi +db/data/performance_schema/socket_summary_b_149.sdi +db/data/performance_schema/status_by_accoun_177.sdi +db/data/performance_schema/status_by_host_178.sdi +db/data/performance_schema/status_by_thread_179.sdi +db/data/performance_schema/status_by_user_180.sdi +db/data/performance_schema/table_handles_158.sdi +db/data/performance_schema/table_io_waits_s_107.sdi +db/data/performance_schema/table_io_waits_s_108.sdi +db/data/performance_schema/table_lock_waits_109.sdi +db/data/performance_schema/threads_110.sdi +db/data/performance_schema/tls_channel_stat_190.sdi +db/data/performance_schema/user_defined_fun_188.sdi +db/data/performance_schema/user_variables_b_176.sdi +db/data/performance_schema/users_144.sdi +db/data/performance_schema/variables_by_thr_183.sdi +db/data/performance_schema/variables_info_186.sdi +db/data/private_key.pem +db/data/public_key.pem +db/data/server-cert.pem +db/data/server-key.pem +db/data/sys/sys_config.ibd +db/data/undo_001 +db/data/undo_002 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..47c7fd8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM openjdk:17-slim +EXPOSE 8080 +ARG JAR_FILE +COPY ${JAR_FILE} app.jar +LABEL authors="jks83" + +ENTRYPOINT ["java", "-jar", "/app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index b805b0d..067aa93 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,18 @@ +buildscript { + ext { + queryDslVersion = "5.0.0" + } +} + plugins { id 'java' id 'org.springframework.boot' version '3.4.0' id 'io.spring.dependency-management' version '1.1.6' + id 'com.palantir.docker' version '0.35.0' } group = 'com.ns' -version = '0.0.1-SNAPSHOT' +version = '0.0.1' java { toolchain { @@ -29,12 +36,63 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' + + //json + implementation 'com.googlecode.json-simple:json-simple:1.1.1' + + //Swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + + implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '4.4.1.201607150455-r' + implementation 'software.amazon.awssdk:ecr:2.20.5' + compileOnly 'org.projectlombok:lombok' runtimeOnly 'org.postgresql:postgresql' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + //QueryDSL + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + + implementation "com.querydsl:querydsl-core" + implementation "com.querydsl:querydsl-collections" +} + +// QueryDSL +// Querydsl 설정부 +def generated = 'src/main/generated' + +// querydsl QClass 파일 생성 위치를 지정 +tasks.withType(JavaCompile) { + options.getGeneratedSourceOutputDirectory().set(file(generated)) +} + +// java source set 에 querydsl QClass 위치 추가 +sourceSets { + main.java.srcDirs += [ generated ] +} + +// gradle clean 시에 QClass 디렉토리 삭제 +clean { + delete file(generated) +} + + +tasks.named('bootBuildImage') { + builder = 'paketobuildpacks/builder-jammy-base:latest' +} + +docker { + println(tasks.bootJar.outputs.files) + name project.name+":"+version + dockerfile file('./Dockerfile') + files tasks.bootJar.outputs.files + buildArgs(['JAR_FILE':tasks.bootJar.outputs.files.singleFile.name]) } tasks.named('test') { diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..1929866 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,45 @@ +version: '3' +services: + + mysql: + image: mysql:8.0 + networks: + - solve_network + volumes: + - ./db/conf.d:/etc/mysql/conf.d + - ./db/data:/var/lib/mysql + - ./db/initdb.d:/docker-entrypoint-initdb.d + ports: + - "3306:3306" + environment: + - TZ=Asia/Seoul + - MYSQL_ROOT_PASSWORD=rootpw + - MYSQL_USER=mysqluser + - MYSQL_PASSWORD=mysqlpw + - MYSQL_HOST=localhost + - MYSQL_PORT=3306 + - MYSQL_DATABASE=solve + + solve: + image: solve:0.0.1 + networks: + - solve_network + ports: + - "8080:8080" + depends_on: + - mysql + environment: + - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/solve?useSSL=false&allowPublicKeyRetrieval=true + - SPRING_DATASOURCE_USERNAME=mysqluser + - SPRING_DATASOURCE_PASSWORD=mysqlpw + - SPRING_JPA_HIBERNATE_DDL_AUTO=update + - KAKAO_CLIENT_ID= + - KAKAO_CLIENT_SECRET= + - KAKAO_REDIRECT_URL=http://localhost/kakao/callback + - NAVER_CLIENT_ID= + - NAVER_CLIENT_SECRET= + - NAVER_REDIRECT_URL=naver/callback + +networks: + solve_network: + driver: bridge \ No newline at end of file diff --git a/src/main/generated/com/ns/solve/Domain/QAssignment.java b/src/main/generated/com/ns/solve/Domain/QAssignment.java new file mode 100644 index 0000000..67817d2 --- /dev/null +++ b/src/main/generated/com/ns/solve/Domain/QAssignment.java @@ -0,0 +1,47 @@ +package com.ns.solve.Domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QAssignment is a Querydsl query type for Assignment + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QAssignment extends EntityPathBase { + + private static final long serialVersionUID = 1428616620L; + + public static final QAssignment assignment = new QAssignment("assignment"); + + public final NumberPath assignmentId = createNumber("assignmentId", Long.class); + + public final MapPath caseAccuracy = this.createMap("caseAccuracy", String.class, String.class, StringPath.class); + + public final StringPath detail = createString("detail"); + + public final StringPath gitRepository = createString("gitRepository"); + + public final NumberPath membershipId = createNumber("membershipId", Long.class); + + public final NumberPath problemId = createNumber("problemId", Long.class); + + public QAssignment(String variable) { + super(Assignment.class, forVariable(variable)); + } + + public QAssignment(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QAssignment(PathMetadata metadata) { + super(Assignment.class, metadata); + } + +} + diff --git a/src/main/generated/com/ns/solve/Domain/QMembership.java b/src/main/generated/com/ns/solve/Domain/QMembership.java new file mode 100644 index 0000000..e869c87 --- /dev/null +++ b/src/main/generated/com/ns/solve/Domain/QMembership.java @@ -0,0 +1,63 @@ +package com.ns.solve.Domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QMembership is a Querydsl query type for Membership + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QMembership extends EntityPathBase { + + private static final long serialVersionUID = -937888075L; + + public static final QMembership membership = new QMembership("membership"); + + public final StringPath address = createString("address"); + + public final DateTimePath createdAt = createDateTime("createdAt", java.sql.Timestamp.class); + + public final StringPath curProductRegion = createString("curProductRegion"); + + public final StringPath email = createString("email"); + + public final NumberPath exp = createNumber("exp", Integer.class); + + public final BooleanPath isValid = createBoolean("isValid"); + + public final NumberPath level = createNumber("level", Integer.class); + + public final NumberPath membershipId = createNumber("membershipId", Long.class); + + public final StringPath name = createString("name"); + + public final StringPath nickname = createString("nickname"); + + public final StringPath refreshToken = createString("refreshToken"); + + public final StringPath region = createString("region"); + + public final EnumPath role = createEnum("role", Membership.ROLE.class); + + public final StringPath type = createString("type"); + + public QMembership(String variable) { + super(Membership.class, forVariable(variable)); + } + + public QMembership(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QMembership(PathMetadata metadata) { + super(Membership.class, metadata); + } + +} + diff --git a/src/main/generated/com/ns/solve/Domain/QProblem.java b/src/main/generated/com/ns/solve/Domain/QProblem.java new file mode 100644 index 0000000..320ef5d --- /dev/null +++ b/src/main/generated/com/ns/solve/Domain/QProblem.java @@ -0,0 +1,48 @@ +package com.ns.solve.Domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QProblem is a Querydsl query type for Problem + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QProblem extends EntityPathBase { + + private static final long serialVersionUID = -1820862432L; + + public static final QProblem problem = new QProblem("problem"); + + public final ListPath> caseList = this.>createList("caseList", Case.class, SimplePath.class, PathInits.DIRECT2); + + public final DateTimePath deadline = createDateTime("deadline", java.time.LocalDateTime.class); + + public final StringPath detail = createString("detail"); + + public final NumberPath membershipId = createNumber("membershipId", Long.class); + + public final NumberPath problemId = createNumber("problemId", Long.class); + + public final EnumPath status = createEnum("status", Problem.ProblemStatus.class); + + public QProblem(String variable) { + super(Problem.class, forVariable(variable)); + } + + public QProblem(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QProblem(PathMetadata metadata) { + super(Problem.class, metadata); + } + +} + diff --git a/src/main/java/com/ns/solve/Auth/kakao/KakaoController.java b/src/main/java/com/ns/solve/Auth/kakao/KakaoController.java new file mode 100644 index 0000000..09d9ef3 --- /dev/null +++ b/src/main/java/com/ns/solve/Auth/kakao/KakaoController.java @@ -0,0 +1,56 @@ +package com.ns.solve.Auth.kakao; + +import com.ns.solve.Auth.naver.NaverService; +import com.ns.solve.Domain.dto.MessageEntity; +import com.ns.solve.Service.MembershipService; +import com.ns.solve.Utils.JwtToken; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.view.RedirectView; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("kakao") +public class KakaoController { + + private final KakaoService kakaoService; + private final NaverService naverService; + private final MembershipService membershipService; + + @RequestMapping(value="/", method= RequestMethod.GET) + public String login(Model model) { + model.addAttribute("kakaoUrl", kakaoService.getKakaoLogin()); + model.addAttribute("naverUrl", naverService.getNaverLogin()); + return "index"; + } + + @GetMapping("/login") + public RedirectView login(){ + return new RedirectView(kakaoService.getKakaoLogin()); + } + + @GetMapping("/callback") + public ResponseEntity callback(HttpServletRequest request) throws Exception { + KakaoDTO kakaoInfo = kakaoService.getKakaoInfo(request.getParameter("code")); + + Long id = kakaoInfo.getId(); + JwtToken token = membershipService.LoginMembership(id); + + if (token!=null) + return ResponseEntity.ok() + .body(new MessageEntity("Success ", token)); + else return ResponseEntity.ok() + .body(new MessageEntity("Fail ","token is empty.")); // empty시 register시켜야함. + } + + + +} diff --git a/src/main/java/com/ns/solve/Auth/kakao/KakaoDTO.java b/src/main/java/com/ns/solve/Auth/kakao/KakaoDTO.java new file mode 100644 index 0000000..537c209 --- /dev/null +++ b/src/main/java/com/ns/solve/Auth/kakao/KakaoDTO.java @@ -0,0 +1,14 @@ +package com.ns.solve.Auth.kakao; + +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class KakaoDTO { + + private long id; + private String email; + private String nickname; + +} \ No newline at end of file diff --git a/src/main/java/com/ns/solve/Auth/kakao/KakaoService.java b/src/main/java/com/ns/solve/Auth/kakao/KakaoService.java new file mode 100644 index 0000000..77e1fa0 --- /dev/null +++ b/src/main/java/com/ns/solve/Auth/kakao/KakaoService.java @@ -0,0 +1,116 @@ +package com.ns.solve.Auth.kakao; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + + +@Service +@Slf4j +@RequiredArgsConstructor +public class KakaoService { + + @Value("${KAKAO_CLIENT_ID}") + private String KAKAO_CLIENT_ID; + + @Value("${KAKAO_CLIENT_SECRET}") + private String KAKAO_CLIENT_SECRET; + + @Value("${KAKAO_REDIRECT_URL}") + private String KAKAO_REDIRECT_URL; + + private final static String KAKAO_AUTH_URI = "https://kauth.kakao.com"; + private final static String KAKAO_API_URI = "https://kapi.kakao.com"; + + public String getKakaoLogin() { + return KAKAO_AUTH_URI + "/oauth/authorize" + + "?client_id=" + KAKAO_CLIENT_ID + + "&redirect_uri=" + KAKAO_REDIRECT_URL + + "&response_type=code"; + } + + public KakaoDTO getKakaoInfo(String code) throws Exception { + if (code == null) throw new Exception("Failed get authorization code"); + + String accessToken = ""; + String refreshToken = ""; + + try { + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-type", "application/x-www-form-urlencoded"); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type" , "authorization_code"); + params.add("client_id" , KAKAO_CLIENT_ID); + params.add("client_secret", KAKAO_CLIENT_SECRET); + params.add("code" , code); + params.add("redirect_uri" , KAKAO_REDIRECT_URL); + + RestTemplate restTemplate = new RestTemplate(); + HttpEntity> httpEntity = new HttpEntity<>(params, headers); + + ResponseEntity response = restTemplate.exchange( + KAKAO_AUTH_URI + "/oauth/token", + HttpMethod.POST, + httpEntity, + String.class + ); + + JSONParser jsonParser = new JSONParser(); + JSONObject jsonObj = (JSONObject) jsonParser.parse(response.getBody()); + + accessToken = (String) jsonObj.get("access_token"); + refreshToken = (String) jsonObj.get("refresh_token"); + } catch (Exception e) { + throw new Exception("API call failed"); + } + + return getUserInfoWithToken(accessToken); + } + + private KakaoDTO getUserInfoWithToken(String accessToken) throws Exception { + //HttpHeader 생성 + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Bearer " + accessToken); + headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8"); + + //HttpHeader 담기 + RestTemplate rt = new RestTemplate(); + HttpEntity> httpEntity = new HttpEntity<>(headers); + ResponseEntity response = rt.exchange( + KAKAO_API_URI + "/v2/user/me", + HttpMethod.POST, + httpEntity, + String.class + ); + + log.info("Received json : "+ response.getBody()); + //Response 데이터 파싱 + JSONParser jsonParser = new JSONParser(); + JSONObject jsonObj = (JSONObject) jsonParser.parse(response.getBody()); + JSONObject account = (JSONObject) jsonObj.get("kakao_account"); + JSONObject profile = (JSONObject) account.get("profile"); + + long id = (long) jsonObj.get("id"); + String email = String.valueOf(account.get("email")); + String nickname = String.valueOf(profile.get("nickname")); + + return KakaoDTO.builder() + .id(id) + .email(email) + .nickname(nickname).build(); + } + + + +} \ No newline at end of file diff --git a/src/main/java/com/ns/solve/Auth/naver/NaverController.java b/src/main/java/com/ns/solve/Auth/naver/NaverController.java new file mode 100644 index 0000000..43196a1 --- /dev/null +++ b/src/main/java/com/ns/solve/Auth/naver/NaverController.java @@ -0,0 +1,26 @@ +package com.ns.solve.Auth.naver; + +import com.ns.solve.Domain.dto.MessageEntity; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("naver") +public class NaverController { + + private final NaverService naverService; + + @GetMapping("/callback") + public ResponseEntity callback(HttpServletRequest request) throws Exception { + NaverDTO naverInfo = naverService.getNaverInfo(request.getParameter("code")); + + return ResponseEntity.ok() + .body(new MessageEntity("Success", naverInfo)); + } + +} \ No newline at end of file diff --git a/src/main/java/com/ns/solve/Auth/naver/NaverDTO.java b/src/main/java/com/ns/solve/Auth/naver/NaverDTO.java new file mode 100644 index 0000000..a2e285b --- /dev/null +++ b/src/main/java/com/ns/solve/Auth/naver/NaverDTO.java @@ -0,0 +1,13 @@ +package com.ns.solve.Auth.naver; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class NaverDTO { + + private String id; + private String email; + private String name; + +} diff --git a/src/main/java/com/ns/solve/Auth/naver/NaverService.java b/src/main/java/com/ns/solve/Auth/naver/NaverService.java new file mode 100644 index 0000000..75b4c28 --- /dev/null +++ b/src/main/java/com/ns/solve/Auth/naver/NaverService.java @@ -0,0 +1,107 @@ +package com.ns.solve.Auth.naver; + +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +@Service +public class NaverService { + + @Value("${NAVER_CLIENT_ID}") + private String NAVER_CLIENT_ID; + + @Value("${NAVER_CLIENT_SECRET}") + private String NAVER_CLIENT_SECRET; + + @Value("${NAVER_REDIRECT_URL}") + private String NAVER_REDIRECT_URL; + + private final static String NAVER_AUTH_URI = "https://nid.naver.com"; + private final static String NAVER_API_URI = "https://openapi.naver.com"; + + public String getNaverLogin() { + return NAVER_AUTH_URI + "/oauth2.0/authorize" + + "?client_id=" + NAVER_CLIENT_ID + + "&redirect_uri=" + NAVER_REDIRECT_URL + + "&response_type=code"; + } + + public NaverDTO getNaverInfo(String code) throws Exception { + if (code == null) throw new Exception("Failed get authorization code"); + + String accessToken = ""; + String refreshToken = ""; + + try { + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-type", "application/x-www-form-urlencoded"); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type" , "authorization_code"); + params.add("client_id" , NAVER_CLIENT_ID); + params.add("client_secret", NAVER_CLIENT_SECRET); + params.add("code" , code); + params.add("redirect_uri" , NAVER_REDIRECT_URL); + + RestTemplate restTemplate = new RestTemplate(); + HttpEntity> httpEntity = new HttpEntity<>(params, headers); + + ResponseEntity response = restTemplate.exchange( + NAVER_AUTH_URI + "/oauth2.0/token", + HttpMethod.POST, + httpEntity, + String.class + ); + + JSONParser jsonParser = new JSONParser(); + JSONObject jsonObj = (JSONObject) jsonParser.parse(response.getBody()); + + accessToken = (String) jsonObj.get("access_token"); + refreshToken = (String) jsonObj.get("refresh_token"); + } catch (Exception e) { + throw new Exception("API call failed"); + } + + return getUserInfoWithToken(accessToken); + } + + private NaverDTO getUserInfoWithToken(String accessToken) throws Exception { + //HttpHeader 생성 + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Bearer " + accessToken); + headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8"); + + //HttpHeader 담기 + RestTemplate rt = new RestTemplate(); + HttpEntity> httpEntity = new HttpEntity<>(headers); + ResponseEntity response = rt.exchange( + NAVER_API_URI + "/v1/nid/me", + HttpMethod.POST, + httpEntity, + String.class + ); + + //Response 데이터 파싱 + JSONParser jsonParser = new JSONParser(); + JSONObject jsonObj = (JSONObject) jsonParser.parse(response.getBody()); + JSONObject account = (JSONObject) jsonObj.get("response"); + + String id = String.valueOf(account.get("id")); + String email = String.valueOf(account.get("email")); + String name = String.valueOf(account.get("name")); + + return NaverDTO.builder() + .id(id) + .email(email) + .name(name).build(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/ns/solve/Config/QuerydslConfig.java b/src/main/java/com/ns/solve/Config/QuerydslConfig.java new file mode 100644 index 0000000..01aa2cb --- /dev/null +++ b/src/main/java/com/ns/solve/Config/QuerydslConfig.java @@ -0,0 +1,19 @@ +package com.ns.solve.Config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QuerydslConfig { + + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} diff --git a/src/main/java/com/ns/solve/Config/RestTemplateConfig.java b/src/main/java/com/ns/solve/Config/RestTemplateConfig.java new file mode 100644 index 0000000..3b92374 --- /dev/null +++ b/src/main/java/com/ns/solve/Config/RestTemplateConfig.java @@ -0,0 +1,46 @@ +package com.ns.solve.Config; + +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) throws NoSuchAlgorithmException, KeyManagementException { + + TrustManager[] trustAllCerts = new TrustManager[] { + new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + public void checkClientTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + public void checkServerTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + } + }; + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + CloseableHttpClient httpClient = HttpClients.custom() + .setSslcontext(sslContext) + .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE) + .build(); + HttpComponentsClientHttpRequestFactory customRequestFactory = new HttpComponentsClientHttpRequestFactory(); + customRequestFactory.setHttpClient(httpClient); + return builder.requestFactory(() -> customRequestFactory).build(); + } +} diff --git a/src/main/java/com/ns/solve/Config/SecurityConfig.java b/src/main/java/com/ns/solve/Config/SecurityConfig.java new file mode 100644 index 0000000..1fc3de5 --- /dev/null +++ b/src/main/java/com/ns/solve/Config/SecurityConfig.java @@ -0,0 +1,98 @@ +package com.ns.solve.Config; + +import com.ns.solve.Repository.MembershipRepository; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebFluxSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.authorizeHttpRequests(auth -> auth + .requestMatchers("/login").permitAll() + .requestMatchers("/admin/**").hasRole("ADMIN") + .requestMatchers("/problem/**").hasRole("MANAGER") + .requestMatchers("/**").hasRole("USER") + .anyRequest().authenticated() + ) + .formLogin(form -> form + .loginPage("/login") + .defaultSuccessUrl("/", true) + .permitAll() + ) + .logout(logout -> logout + .logoutUrl("/logout") + .logoutSuccessUrl("/login?logout") + .permitAll() + ) + .csrf(csrf -> csrf.disable()) + .build(); + } + + + @Bean + public UserDetailsService inMemoryUserDetailsService() { + UserDetails user = User.withUsername("user") + .password(passwordEncoder().encode("password")) + .roles("USER") + .build(); + + UserDetails admin = User.withUsername("admin") + .password(passwordEncoder().encode("admin")) + .roles("ADMIN") + .build(); + + return new InMemoryUserDetailsManager(user, admin); + } + + @Bean + public UserDetailsService dbUserDetailsService(MembershipRepository membershipRepository) { + return account -> membershipRepository.findByAccount(account) + .map(member -> User.withUsername(member.getName()) + .password(member.getPassword()) + .roles(member.getRoleName()) + .build() + ).orElseThrow(() -> new UsernameNotFoundException("Account not found: " + account)); + } + + @Bean + public AuthenticationManager authenticationManager(UserDetailsService inMemoryUserDetailsService, UserDetailsService dbUserDetailsService, PasswordEncoder passwordEncoder) { + // 다중 인증 처리를 위한 AuthenticationManager + + DaoAuthenticationProvider inMemoryAuthProvider = new DaoAuthenticationProvider(); + inMemoryAuthProvider.setUserDetailsService(inMemoryUserDetailsService); + inMemoryAuthProvider.setPasswordEncoder(passwordEncoder); + + DaoAuthenticationProvider dbAuthProvider = new DaoAuthenticationProvider(); + dbAuthProvider.setUserDetailsService(dbUserDetailsService); + dbAuthProvider.setPasswordEncoder(passwordEncoder); + + ProviderManager authManager = new ProviderManager(inMemoryAuthProvider, dbAuthProvider); + return authManager; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + +} + + + + diff --git a/src/main/java/com/ns/solve/Config/SwaggerConfig.java b/src/main/java/com/ns/solve/Config/SwaggerConfig.java new file mode 100644 index 0000000..51ee632 --- /dev/null +++ b/src/main/java/com/ns/solve/Config/SwaggerConfig.java @@ -0,0 +1,24 @@ +package com.ns.solve.Config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI openAPI() { + Info info = new Info() + .title("solve-proejct") + .description("hello world") + .version("1.0.0"); + + return new OpenAPI() + .info(info); + + } + + +} diff --git a/src/main/java/com/ns/solve/Controller/AssignmentController.java b/src/main/java/com/ns/solve/Controller/AssignmentController.java new file mode 100644 index 0000000..692f162 --- /dev/null +++ b/src/main/java/com/ns/solve/Controller/AssignmentController.java @@ -0,0 +1,42 @@ +package com.ns.solve.Controller; + +import com.ns.solve.Domain.dto.AssignmentDto; +import com.ns.solve.Service.AssignmentService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/assignment") +@RequiredArgsConstructor +public class AssignmentController { + + private final AssignmentService assignmentService; + + @PostMapping("/register") + public void registerAssignment(@RequestBody AssignmentDto assignmentDto){ + assignmentService.registerAssignment(assignmentDto); + } + + @PutMapping("/update/{AssignmentId}") + public void updateAssignment(@PathVariable Long assignmentId){ + assignmentService.updateAssignment(assignmentId); + } + + @DeleteMapping("/delete/{AssignmentId}") + public void deleteAssignment(@PathVariable Long assignmentId){ + assignmentService.deleteAssignment(assignmentId); + } + + @GetMapping("/{AssignmentId}") + public void findAssignment(@PathVariable Long assignmentId){ + assignmentService.findAssignment(assignmentId); + } +} + diff --git a/src/main/java/com/ns/solve/Controller/MembershipController.java b/src/main/java/com/ns/solve/Controller/MembershipController.java new file mode 100644 index 0000000..139bed4 --- /dev/null +++ b/src/main/java/com/ns/solve/Controller/MembershipController.java @@ -0,0 +1,15 @@ +package com.ns.solve.Controller; + +import com.ns.solve.Service.MembershipService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/membership") +public class MembershipController { + + private final MembershipService membershipService; + +} diff --git a/src/main/java/com/ns/solve/Controller/ProblemController.java b/src/main/java/com/ns/solve/Controller/ProblemController.java new file mode 100644 index 0000000..ffb4a02 --- /dev/null +++ b/src/main/java/com/ns/solve/Controller/ProblemController.java @@ -0,0 +1,51 @@ +package com.ns.solve.Controller; + +import com.ns.solve.Domain.dto.ProblemDto; +import com.ns.solve.Service.ProblemService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/problem") +@RequiredArgsConstructor +public class ProblemController { + private final ProblemService problemService; + + @PostMapping("/register/public") + public void registerPublicProblem(@RequestBody ProblemDto problemDto){ + problemService.registerProblem(true, problemDto); + } + @PostMapping("/register/private") + public void registerPrivateProblem(@RequestBody ProblemDto problemDto){ + problemService.registerProblem(false, problemDto); + } + + @GetMapping("/manage/{problemId}") + public void monitorProblem(@PathVariable Long problemId){ + problemService.monitorProblem(problemId); + } + + @PutMapping("/update/{problemId}") + public void updateProblem(@PathVariable Long problemId){ + problemService.updateProblem(problemId); + } + + @DeleteMapping("/delete/{problemId}") + public void deleteProblem(@PathVariable Long problemId){ + problemService.deleteProblem(problemId); + } + + @GetMapping("/{problemId}") + public void findProblem(@PathVariable Long problemId){ + problemService.findProblem(problemId); + } + + +} diff --git a/src/main/java/com/ns/solve/Domain/Assignment.java b/src/main/java/com/ns/solve/Domain/Assignment.java new file mode 100644 index 0000000..348a096 --- /dev/null +++ b/src/main/java/com/ns/solve/Domain/Assignment.java @@ -0,0 +1,28 @@ +package com.ns.solve.Domain; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Assignment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long assignmentId; + + private Long problemId; + private Long membershipId; + + private String detail; + private String gitRepository; + + private Map caseAccuracy; +} diff --git a/src/main/java/com/ns/solve/Domain/Case.java b/src/main/java/com/ns/solve/Domain/Case.java new file mode 100644 index 0000000..a7bb363 --- /dev/null +++ b/src/main/java/com/ns/solve/Domain/Case.java @@ -0,0 +1,13 @@ +package com.ns.solve.Domain; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class Case { + private final Long problemId; + private final CaseKind caseKind; + private final String input; + private final String expectedOutput; +} diff --git a/src/main/java/com/ns/solve/Domain/CaseKind.java b/src/main/java/com/ns/solve/Domain/CaseKind.java new file mode 100644 index 0000000..b7f2ad9 --- /dev/null +++ b/src/main/java/com/ns/solve/Domain/CaseKind.java @@ -0,0 +1,5 @@ +package com.ns.solve.Domain; + +public enum CaseKind { + HIDDNE, EVUALATION, EXAMPLE +} diff --git a/src/main/java/com/ns/solve/Domain/Membership.java b/src/main/java/com/ns/solve/Domain/Membership.java new file mode 100644 index 0000000..d6901e2 --- /dev/null +++ b/src/main/java/com/ns/solve/Domain/Membership.java @@ -0,0 +1,50 @@ +package com.ns.solve.Domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.sql.Timestamp; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity +@Table(name ="membership") +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Membership { + + @Id + @GeneratedValue + @Column(name="id") + private Long membershipId; + + private String name; + private String nickname; + private String address; + private String email; + private String password; + + public enum ROLE{ + ADMIN, MANAGER, USER + } + private ROLE role; + + private String refreshToken; + + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "Asia/Seoul") + @Column(name = "created_at") + private Timestamp createdAt; + + + public String getRoleName() { + return "ROLE_" + this.role.name(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/ns/solve/Domain/Problem.java b/src/main/java/com/ns/solve/Domain/Problem.java new file mode 100644 index 0000000..d1e5bcd --- /dev/null +++ b/src/main/java/com/ns/solve/Domain/Problem.java @@ -0,0 +1,36 @@ +package com.ns.solve.Domain; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import java.time.LocalDateTime; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity +@AllArgsConstructor +@NoArgsConstructor +@Data +public class Problem { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long problemId; + + public enum ProblemStatus{ + ALGORITHM, ASSIGNMENT, CTF + } + + private ProblemStatus status; + + private Long membershipId; + private LocalDateTime deadline; + private String detail; + + + private List caseList; + + +} diff --git a/src/main/java/com/ns/solve/Domain/ProblemFilter.java b/src/main/java/com/ns/solve/Domain/ProblemFilter.java new file mode 100644 index 0000000..ec026a0 --- /dev/null +++ b/src/main/java/com/ns/solve/Domain/ProblemFilter.java @@ -0,0 +1,16 @@ +package com.ns.solve.Domain; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ProblemFilter{ + private List problemStatus; + private List regions; +} diff --git a/src/main/java/com/ns/solve/Domain/dto/AssignmentDto.java b/src/main/java/com/ns/solve/Domain/dto/AssignmentDto.java new file mode 100644 index 0000000..f0c9b8f --- /dev/null +++ b/src/main/java/com/ns/solve/Domain/dto/AssignmentDto.java @@ -0,0 +1,15 @@ +package com.ns.solve.Domain.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Data +public class AssignmentDto { + private Long membershipId; + private Long problemId; + private String comment; // 회고록, 느낀 점 + private String gitDirectory; +} diff --git a/src/main/java/com/ns/solve/Domain/dto/KakaoProfile.java b/src/main/java/com/ns/solve/Domain/dto/KakaoProfile.java new file mode 100644 index 0000000..3bd1872 --- /dev/null +++ b/src/main/java/com/ns/solve/Domain/dto/KakaoProfile.java @@ -0,0 +1,42 @@ +package com.ns.solve.Domain.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +public class KakaoProfile { + public Long id; + public String connected_at; + public Properties properties; + public Kakao_Account kakao_account; + + @Data + @JsonIgnoreProperties(ignoreUnknown=true) + public class Properties { + + public String nickname; + + } + @Data + @JsonIgnoreProperties(ignoreUnknown=true) + public class Kakao_Account { + + public Boolean profile_nickname_needs_agreement; + public Profile profile; + public Boolean has_email; + public Boolean email_Needs_Agreement; + public Boolean is_Email_Valid; + public Boolean is_Email_Verified; + public String email; + + @Data + @JsonIgnoreProperties(ignoreUnknown=true) + public class Profile { + + public String nickname; + + } + + } + +} diff --git a/src/main/java/com/ns/solve/Domain/dto/KakaoRequest.java b/src/main/java/com/ns/solve/Domain/dto/KakaoRequest.java new file mode 100644 index 0000000..720f452 --- /dev/null +++ b/src/main/java/com/ns/solve/Domain/dto/KakaoRequest.java @@ -0,0 +1,7 @@ +package com.ns.solve.Domain.dto; + +import lombok.Data; +@Data +public class KakaoRequest { + private String access_token; +} \ No newline at end of file diff --git a/src/main/java/com/ns/solve/Domain/dto/KakaoToken.java b/src/main/java/com/ns/solve/Domain/dto/KakaoToken.java new file mode 100644 index 0000000..1039dd0 --- /dev/null +++ b/src/main/java/com/ns/solve/Domain/dto/KakaoToken.java @@ -0,0 +1,13 @@ +package com.ns.solve.Domain.dto; + +import lombok.Data; +@Data +public class KakaoToken { + private String access_token; + private String token_type; + private String refresh_token; + private String id_token; + private int expires_in; + private String scope; + private int refresh_token_expires_in; +} diff --git a/src/main/java/com/ns/solve/Domain/dto/MessageEntity.java b/src/main/java/com/ns/solve/Domain/dto/MessageEntity.java new file mode 100644 index 0000000..29acc44 --- /dev/null +++ b/src/main/java/com/ns/solve/Domain/dto/MessageEntity.java @@ -0,0 +1,14 @@ +package com.ns.solve.Domain.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +public class MessageEntity { + + private String message; + private Object result; +} \ No newline at end of file diff --git a/src/main/java/com/ns/solve/Domain/dto/ProblemDto.java b/src/main/java/com/ns/solve/Domain/dto/ProblemDto.java new file mode 100644 index 0000000..bcb1ca8 --- /dev/null +++ b/src/main/java/com/ns/solve/Domain/dto/ProblemDto.java @@ -0,0 +1,21 @@ +package com.ns.solve.Domain.dto; + +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ProblemDto { + private Long membershipId; + private String title; + private String detail; + private LocalDateTime deadline; + + private String input; + private String expectedOutput; +} diff --git a/src/main/java/com/ns/solve/Repository/AssignmentCustomRepository.java b/src/main/java/com/ns/solve/Repository/AssignmentCustomRepository.java new file mode 100644 index 0000000..4a87040 --- /dev/null +++ b/src/main/java/com/ns/solve/Repository/AssignmentCustomRepository.java @@ -0,0 +1,8 @@ +package com.ns.solve.Repository; + +import com.ns.solve.Domain.Assignment; +import java.util.Optional; + +public interface AssignmentCustomRepository { + Optional findByAssignmentId(Long assignmentId); +} diff --git a/src/main/java/com/ns/solve/Repository/AssignmentCustomRepositoryImpl.java b/src/main/java/com/ns/solve/Repository/AssignmentCustomRepositoryImpl.java new file mode 100644 index 0000000..3910b2b --- /dev/null +++ b/src/main/java/com/ns/solve/Repository/AssignmentCustomRepositoryImpl.java @@ -0,0 +1,22 @@ +package com.ns.solve.Repository; + +import com.ns.solve.Domain.Assignment; +import com.ns.solve.Domain.QAssignment; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.Optional; + +public class AssignmentCustomRepositoryImpl implements AssignmentCustomRepository{ + private final JPAQueryFactory jpaQueryFactory; + private final QAssignment qAssignment; + + public AssignmentCustomRepositoryImpl(JPAQueryFactory jpaQueryFactory, QAssignment qAssignment){ + this.jpaQueryFactory = jpaQueryFactory; + this.qAssignment = qAssignment; + } + + + @Override + public Optional findByAssignmentId(Long assignmentId) { + return Optional.empty(); + } +} diff --git a/src/main/java/com/ns/solve/Repository/AssignmentRepository.java b/src/main/java/com/ns/solve/Repository/AssignmentRepository.java new file mode 100644 index 0000000..f559b69 --- /dev/null +++ b/src/main/java/com/ns/solve/Repository/AssignmentRepository.java @@ -0,0 +1,9 @@ +package com.ns.solve.Repository; + +import com.ns.solve.Domain.Assignment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AssignmentRepository extends JpaRepository, AssignmentCustomRepository { +} diff --git a/src/main/java/com/ns/solve/Repository/MembershipCustomRepository.java b/src/main/java/com/ns/solve/Repository/MembershipCustomRepository.java new file mode 100644 index 0000000..afddd7a --- /dev/null +++ b/src/main/java/com/ns/solve/Repository/MembershipCustomRepository.java @@ -0,0 +1,9 @@ +package com.ns.solve.Repository; + +import com.ns.solve.Domain.Membership; +import java.util.Optional; + +public interface MembershipCustomRepository { + Optional findByMembershipId(Long membershipId); + Optional findByAccount(String membershipAccount); +} diff --git a/src/main/java/com/ns/solve/Repository/MembershipCustomRepositoryImpl.java b/src/main/java/com/ns/solve/Repository/MembershipCustomRepositoryImpl.java new file mode 100644 index 0000000..027ece9 --- /dev/null +++ b/src/main/java/com/ns/solve/Repository/MembershipCustomRepositoryImpl.java @@ -0,0 +1,28 @@ +package com.ns.solve.Repository; + +import com.ns.solve.Domain.Membership; +import com.ns.solve.Domain.QMembership; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.Optional; + +public class MembershipCustomRepositoryImpl implements MembershipCustomRepository { + private final JPAQueryFactory jpaQueryFactory; + private final QMembership qMembership; + + + public MembershipCustomRepositoryImpl(JPAQueryFactory jpaQueryFactory, QMembership qMembership){ + this.jpaQueryFactory = jpaQueryFactory; + this.qMembership = qMembership; + } + + + @Override + public Optional findByMembershipId(Long membershipId) { + return Optional.empty(); + } + + @Override + public Optional findByAccount(String membershipAccount) { + return null; + } +} diff --git a/src/main/java/com/ns/solve/Repository/MembershipRepository.java b/src/main/java/com/ns/solve/Repository/MembershipRepository.java new file mode 100644 index 0000000..ae97fda --- /dev/null +++ b/src/main/java/com/ns/solve/Repository/MembershipRepository.java @@ -0,0 +1,9 @@ +package com.ns.solve.Repository; + +import com.ns.solve.Domain.Membership; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MembershipRepository extends JpaRepository, MembershipCustomRepository{ +} diff --git a/src/main/java/com/ns/solve/Repository/ProblemCustomRepository.java b/src/main/java/com/ns/solve/Repository/ProblemCustomRepository.java new file mode 100644 index 0000000..af8a138 --- /dev/null +++ b/src/main/java/com/ns/solve/Repository/ProblemCustomRepository.java @@ -0,0 +1,11 @@ +package com.ns.solve.Repository; + +import com.ns.solve.Domain.Case; +import com.ns.solve.Domain.Problem; +import com.ns.solve.Domain.ProblemFilter; +import java.util.List; + +public interface ProblemCustomRepository { + List findByProblemId(Long problemId, ProblemFilter filter); + List findCaseByProblemId(Long problemId); +} diff --git a/src/main/java/com/ns/solve/Repository/ProblemCustomRepositoryImpl.java b/src/main/java/com/ns/solve/Repository/ProblemCustomRepositoryImpl.java new file mode 100644 index 0000000..df74ac0 --- /dev/null +++ b/src/main/java/com/ns/solve/Repository/ProblemCustomRepositoryImpl.java @@ -0,0 +1,29 @@ +package com.ns.solve.Repository; + +import com.ns.solve.Domain.Case; +import com.ns.solve.Domain.Problem; +import com.ns.solve.Domain.ProblemFilter; +import com.ns.solve.Domain.QProblem; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; + +public class ProblemCustomRepositoryImpl implements ProblemCustomRepository{ + + private final JPAQueryFactory jpaQueryFactory; + private final QProblem qProblem; + + public ProblemCustomRepositoryImpl(JPAQueryFactory jpaQueryFactory, QProblem qProblem){ + this.jpaQueryFactory = jpaQueryFactory; + this.qProblem = qProblem; + } + + @Override + public List findByProblemId(Long problemId, ProblemFilter filter) { + return null; + } + + @Override + public List findCaseByProblemId(Long problemId) { + return null; + } +} diff --git a/src/main/java/com/ns/solve/Repository/ProblemRepository.java b/src/main/java/com/ns/solve/Repository/ProblemRepository.java new file mode 100644 index 0000000..77e0e71 --- /dev/null +++ b/src/main/java/com/ns/solve/Repository/ProblemRepository.java @@ -0,0 +1,10 @@ +package com.ns.solve.Repository; + +import com.ns.solve.Domain.Problem; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProblemRepository extends JpaRepository, ProblemCustomRepository { + +} diff --git a/src/main/java/com/ns/solve/Service/AssignmentService.java b/src/main/java/com/ns/solve/Service/AssignmentService.java new file mode 100644 index 0000000..7191f6e --- /dev/null +++ b/src/main/java/com/ns/solve/Service/AssignmentService.java @@ -0,0 +1,37 @@ +package com.ns.solve.Service; + +import com.ns.solve.Domain.Assignment; +import com.ns.solve.Domain.dto.AssignmentDto; +import com.ns.solve.Repository.AssignmentRepository; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AssignmentService { + private final ImageService imageService; + private final AssignmentRepository assignmentRepository; + + public void registerAssignment(AssignmentDto assignmentDto) { + System.out.println("Assignment 등록: " + assignmentDto); + + } + + public void updateAssignment(Long assignmentId) { + System.out.println("Assignment 업데이트: ID = " + assignmentId); + } + + public void deleteAssignment(Long assignmentId) { + System.out.println("Assignment 삭제: ID = " + assignmentId); + } + + public void findAssignment(Long assignmentId) { + System.out.println("Assignment 검색: ID = " + assignmentId); + } + + private Optional findByAssignmentId(Long assignmentId){ + return assignmentRepository.findByAssignmentId(assignmentId); + } + +} diff --git a/src/main/java/com/ns/solve/Service/ContainerService.java b/src/main/java/com/ns/solve/Service/ContainerService.java new file mode 100644 index 0000000..7bd56a4 --- /dev/null +++ b/src/main/java/com/ns/solve/Service/ContainerService.java @@ -0,0 +1,7 @@ +package com.ns.solve.Service; + +import org.springframework.stereotype.Service; + +@Service +public class ContainerService { +} diff --git a/src/main/java/com/ns/solve/Service/ImageService.java b/src/main/java/com/ns/solve/Service/ImageService.java new file mode 100644 index 0000000..de92804 --- /dev/null +++ b/src/main/java/com/ns/solve/Service/ImageService.java @@ -0,0 +1,130 @@ +package com.ns.solve.Service; + +import com.ns.solve.Domain.Case; +import com.ns.solve.Domain.dto.AssignmentDto; +import com.ns.solve.Repository.ProblemRepository; +import java.io.File; +import java.io.IOException; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.springframework.stereotype.Service; +import software.amazon.awssdk.services.ecr.EcrClient; +import software.amazon.awssdk.services.ecr.model.BatchDeleteImageRequest; +import software.amazon.awssdk.services.ecr.model.EcrException; +import software.amazon.awssdk.services.ecr.model.ImageIdentifier; + +@Service +@RequiredArgsConstructor +public class ImageService { + private final ProblemRepository problemRepository; + + public void process(AssignmentDto assignmentDto){ + + // 해당 problemId에 맞는 케이스를 DB에서 가져온다. + Long problemId = assignmentDto.getProblemId(); + List problemCases = problemRepository.findCaseByProblemId(problemId); + + try { + // Git Repository Clone + String localPath = "/tmp/" + assignmentDto.getMembershipId(); + cloneRepository(assignmentDto.getGitDirectory(), localPath); + + // ECS Image Push + String imageName = "assignment-" + assignmentDto.getMembershipId(); + String ecrUri = "your-ecr-uri"; + buildImage(localPath, imageName, ecrUri); + + // Image Execute + String clusterName = "your-cluster-name"; + String taskDefinition = "your-task-definition"; + String containerName = "your-container-name"; + + // runTask(clusterName, taskDefinition, containerName, ecrUri, testInput); + + for(Case problemCase : problemCases) { + String expectedOutput = problemCase.getExpectedOutput(); + String result = null; // InputCaseTask(problemCase.getInput()); + + if (result == null) { + System.out.println("result is null"); + return; + } + + if (result.equals(expectedOutput)) { + System.out.println("Test Passed! Output: " + result); + } else { + System.out.println("Test Failed. Expected: " + expectedOutput + ", but got: " + result); + } + } + + } catch (Exception e) { + e.printStackTrace(); + System.out.println("Error processing assignment: " + e.getMessage()); + } + } + + public void cloneRepository(String repoUrl, String localPath) throws GitAPIException { + File repoDir = new File(localPath); + if (!repoDir.exists()) { + Git.cloneRepository() + .setURI(repoUrl) + .setDirectory(repoDir) + .call(); + + System.out.println("Repository cloned successfully."); + } else { + System.out.println("Repository already exists at " + localPath); + } + } + + public void buildImage(String repoPath, String imageName, String ecrUri) { + try { + ProcessBuilder builder = new ProcessBuilder("docker", "build", "-t", imageName, repoPath); + builder.inheritIO().start().waitFor(); + + ProcessBuilder loginBuilder = new ProcessBuilder( + "aws", "ecr", "get-login-password", "--region", "your-region", + "|", "docker", "login", "--username", "AWS", "--password-stdin", ecrUri + ); + loginBuilder.inheritIO().start().waitFor(); + + ProcessBuilder pushBuilder = new ProcessBuilder( + "docker", "tag", imageName, ecrUri + "/" + imageName, + "&&", "docker", "push", ecrUri + "/" + imageName + ); + pushBuilder.inheritIO().start().waitFor(); + + deleteDirectory(new File(repoPath)); + + System.out.println("Docker image pushed to ECR successfully."); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + + private void deleteDirectory(File file) { + if (file.isDirectory()) { + for (File subFile : file.listFiles()) { + deleteDirectory(subFile); + } + } + file.delete(); + } + + private void deleteEcrImage(String imageName) { + try (EcrClient ecrClient = EcrClient.create()) { + BatchDeleteImageRequest deleteRequest = BatchDeleteImageRequest.builder() + .repositoryName(imageName) + .imageIds(ImageIdentifier.builder().imageTag("latest").build()) + .build(); + + ecrClient.batchDeleteImage(deleteRequest); + System.out.println("ECR image deleted successfully."); + } catch (EcrException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/com/ns/solve/Service/MembershipService.java b/src/main/java/com/ns/solve/Service/MembershipService.java new file mode 100644 index 0000000..94515ad --- /dev/null +++ b/src/main/java/com/ns/solve/Service/MembershipService.java @@ -0,0 +1,22 @@ +package com.ns.solve.Service; + +import com.ns.solve.Domain.Membership; +import com.ns.solve.Repository.MembershipRepository; +import com.ns.solve.Utils.JwtToken; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class MembershipService { + private final MembershipRepository membershipRepository; + + public JwtToken LoginMembership(Long membershipId){ + return null; + } + + private Optional findByMembershipId(Long membershipId){ + return membershipRepository.findByMembershipId(membershipId); + } +} diff --git a/src/main/java/com/ns/solve/Service/ProblemService.java b/src/main/java/com/ns/solve/Service/ProblemService.java new file mode 100644 index 0000000..f29bc62 --- /dev/null +++ b/src/main/java/com/ns/solve/Service/ProblemService.java @@ -0,0 +1,48 @@ +package com.ns.solve.Service; + +import com.ns.solve.Domain.Problem; +import com.ns.solve.Domain.ProblemFilter; +import com.ns.solve.Domain.dto.ProblemDto; +import com.ns.solve.Repository.ProblemRepository; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ProblemService { + private final ProblemRepository problemRepository; + + public void registerProblem(boolean isPublic, ProblemDto problemDto) { + if (isPublic) { + System.out.println("공개 문제 등록: " + problemDto); + } else { + System.out.println("비공개 문제 등록: " + problemDto); + } + } + + public void monitorProblem(Long problemId) { + System.out.println("문제 모니터링: ID = " + problemId); + } + + public void updateProblem(Long problemId) { + System.out.println("문제 업데이트: ID = " + problemId); + } + + public void deleteProblem(Long problemId) { + System.out.println("문제 삭제: ID = " + problemId); + } + + public void findProblem(Long problemId) { + System.out.println("문제 조회: ID = " + problemId); + } + + private List findByProblemId(Long problemId){ + ProblemFilter filter = ProblemFilter.builder() + .problemStatus(null) + .regions(null) + .build(); + return problemRepository.findByProblemId(problemId, filter); + } +} diff --git a/src/main/java/com/ns/solve/Utils/JwtToken.java b/src/main/java/com/ns/solve/Utils/JwtToken.java new file mode 100644 index 0000000..bf46842 --- /dev/null +++ b/src/main/java/com/ns/solve/Utils/JwtToken.java @@ -0,0 +1,27 @@ +package com.ns.solve.Utils; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class JwtToken { + + @Getter + private final Long membershipId; + @Getter + private final String jwtToken; + @Getter + private final String refreshToken; + + + public static JwtToken generateJwtToken(Long membershipId, String membershipJwtToken, + String membershipRefreshToken) { + + return new JwtToken( + membershipId, membershipJwtToken, membershipRefreshToken + ); + } + + +} diff --git a/src/main/java/com/ns/solve/Utils/JwtTokenProvider.java b/src/main/java/com/ns/solve/Utils/JwtTokenProvider.java new file mode 100644 index 0000000..926b0cc --- /dev/null +++ b/src/main/java/com/ns/solve/Utils/JwtTokenProvider.java @@ -0,0 +1,4 @@ +package com.ns.solve.Utils; + +public class JwtTokenProvider { +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index dd17c5a..be7fa03 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,4 @@ spring.application.name=solve + +cloud.aws.credentials.access-key=KEY +cloud.aws.credentials.secret-key=SECRET \ No newline at end of file