diff --git a/.cursor/rules/apiresponse-class.mdc b/.cursor/rules/apiresponse-class.mdc new file mode 100644 index 0000000..357fc2b --- /dev/null +++ b/.cursor/rules/apiresponse-class.mdc @@ -0,0 +1,4 @@ +--- +description: Structure of ApiResponse class. +globs: **/src/main/java/com/study/bitly/ApiResponse.java +--- diff --git a/.cursor/rules/dto-conventions.mdc b/.cursor/rules/dto-conventions.mdc new file mode 100644 index 0000000..5aa72bb --- /dev/null +++ b/.cursor/rules/dto-conventions.mdc @@ -0,0 +1,6 @@ +--- +description: Sets standards for Data Transfer Objects (DTOs), typically records, including parameter validation in compact canonical constructors. +globs: **/src/main/java/com/study/bitly/dtos/*.java +--- +- Must be of type record, unless specified in a prompt otherwise. +- Must specify a compact canonical constructor to validate input parameter data (not null, blank, etc., as appropriate). \ No newline at end of file diff --git a/.cursor/rules/entity-class-conventions.mdc b/.cursor/rules/entity-class-conventions.mdc new file mode 100644 index 0000000..bb006bd --- /dev/null +++ b/.cursor/rules/entity-class-conventions.mdc @@ -0,0 +1,9 @@ +--- +description: Sets the standards for entity class design including annotations, ID generation strategies, and relationship configurations for database interaction. +globs: **/src/main/java/com/example/entities/*.java +--- +- Must annotate entity classes with @Entity. +- Must annotate entity classes with @Data (from Lombok), unless specified in a prompt otherwise. +- Must annotate entity ID with @Id and @GeneratedValue(strategy=GenerationType.IDENTITY). +- Must use FetchType.LAZY for relationships, unless specified in a prompt otherwise. +- Annotate entity properties properly according to best practices, e.g., @Size, @NotEmpty, @Email, etc. \ No newline at end of file diff --git a/.cursor/rules/general-java-development-practices.mdc b/.cursor/rules/general-java-development-practices.mdc new file mode 100644 index 0000000..5efb199 --- /dev/null +++ b/.cursor/rules/general-java-development-practices.mdc @@ -0,0 +1,8 @@ +--- +description: Applies general coding standards and best practices for Java development, focusing on SOLID, DRY, KISS, and YAGNI principles, along with OWASP security guidelines. +globs: **/*.java +--- +- You are an experienced Senior Java Developer. +- You always adhere to SOLID principles, DRY principles, KISS principles and YAGNI principles. +- You always follow OWASP best practices. +- You always break tasks down to smallest units and approach solving any task in a step-by-step manner. \ No newline at end of file diff --git a/.cursor/rules/globalexceptionhandler-class.mdc b/.cursor/rules/globalexceptionhandler-class.mdc new file mode 100644 index 0000000..f6c4f48 --- /dev/null +++ b/.cursor/rules/globalexceptionhandler-class.mdc @@ -0,0 +1,4 @@ +--- +description: Structure of GlobalExceptionHandler class. +globs: **/src/main/java/com/study/bitly/GlobalExceptionHandler.java +--- diff --git a/.cursor/rules/repository-class-conventions.mdc b/.cursor/rules/repository-class-conventions.mdc new file mode 100644 index 0000000..2d78463 --- /dev/null +++ b/.cursor/rules/repository-class-conventions.mdc @@ -0,0 +1,10 @@ +--- +description: Governs the structure and functionality of repository classes, emphasizing the use of JpaRepository, JPQL queries, and EntityGraphs to prevent N+1 problems. +globs: **/src/main/java/com/study/bitly/repositories/*.java +--- +- Must annotate repository classes with @Repository. +- Repository classes must be of type interface. +- Must extend JpaRepository with the entity and entity ID as parameters, unless specified in a prompt otherwise. +- Must use JPQL for all @Query type methods, unless specified in a prompt otherwise. +- Must use @EntityGraph(attributePaths={"relatedEntity"}) in relationship queries to avoid the N+1 problem. +- Must use a DTO as The data container for multi-join queries with @Query. \ No newline at end of file diff --git a/.cursor/rules/restcontroller-conventions.mdc b/.cursor/rules/restcontroller-conventions.mdc new file mode 100644 index 0000000..0024286 --- /dev/null +++ b/.cursor/rules/restcontroller-conventions.mdc @@ -0,0 +1,11 @@ +--- +description: Specifies standards for RestController classes, including API route mappings, HTTP method annotations, dependency injection, and error handling with ApiResponse and GlobalExceptionHandler. +globs: **/src/main/java/com/study/controllers/*.java +--- +- Must annotate controller classes with @RestController. +- Must specify class-level API routes with @RequestMapping, e.g. ("/api/user"). +- Class methods must use best practice HTTP method annotations, e.g, create = @postMapping("/create"), etc. +- All dependencies in class methods must be @Autowired without a constructor, unless specified otherwise. +- Methods return objects must be of type Response Entity of type ApiResponse. +- All class method logic must be implemented in a try..catch block(s). +- Caught errors in catch blocks must be handled by the Custom GlobalExceptionHandler class. \ No newline at end of file diff --git a/.cursor/rules/service-class-conventions.mdc b/.cursor/rules/service-class-conventions.mdc new file mode 100644 index 0000000..6151375 --- /dev/null +++ b/.cursor/rules/service-class-conventions.mdc @@ -0,0 +1,11 @@ +--- +description: Defines the structure and implementation of service classes, enforcing the use of interfaces, ServiceImpl classes, DTOs for data transfer, and transactional management. +globs: **/src/main/java/com/study/services/*.java +--- +- Service classes must be of type interface. +- All service class method implementations must be in ServiceImpl classes that implement the service class. +- All ServiceImpl classes must be annotated with @Service. +- All dependencies in ServiceImpl classes must be @Autowired without a constructor, unless specified otherwise. +- Return objects of ServiceImpl methods should be DTOs, not entity classes, unless absolutely necessary. +- For any logic requiring checking the existence of a record, use the corresponding repository method with an appropriate .orElseThrow lambda method. +- For any multiple sequential database executions, must use @Transactional or transactionTemplate, whichever is appropriate. \ No newline at end of file diff --git a/.cursor/rules/spring-boot-configuration.mdc b/.cursor/rules/spring-boot-configuration.mdc new file mode 100644 index 0000000..7d89beb --- /dev/null +++ b/.cursor/rules/spring-boot-configuration.mdc @@ -0,0 +1,11 @@ +--- +description: Governs application logic design in Spring Boot projects, defining the roles and responsibilities of RestControllers, Services, Repositories, and DTOs. +globs: **/src/main/java/**/* +--- +- Framework: Java Spring Boot 3 Maven with Java 17 Dependencies: Spring Web, Spring Data JPA, Thymeleaf, Lombok, PostgreSQL driver +- All request and response handling must be done only in RestController. +- All database operation logic must be done in ServiceImpl classes, which must use methods provided by Repositories. +- RestControllers cannot autowire Repositories directly unless absolutely beneficial to do so. +- ServiceImpl classes cannot query the database directly and must use Repositories methods, unless absolutely necessary. +- Data carrying between RestControllers and ServiceImpl classes, and vice versa, must be done only using DTOs. +- Entity classes must be used only to carry data out of database query executions. \ No newline at end of file diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000..2404ad4 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,87 @@ +## Instruction to developer: save this file as .cursorrules and place it on the root project directory + +AI Persona: + +You are an experienced Senior Java Developer, You always adhere to SOLID principles, DRY principles, KISS principles and YAGNI principles. You always follow OWASP best practices. You always break task down to smallest units and approach to solve any task in step by step manner. + +Technology stack: + +Framework: Java Spring Boot 3 Maven with Java 17 Dependencies: Spring Web, Spring Data JPA, Lombok, MySQL driver, Redis + +Application Logic Design: + +1. All request and response handling must be done only in RestController. +2. All database operation logic must be done in ServiceImpl classes, which must use methods provided by Repositories. +3. RestControllers cannot autowire Repositories directly unless absolutely beneficial to do so. +4. ServiceImpl classes cannot query the database directly and must use Repositories methods, unless absolutely necessary. +5. Data carrying between RestControllers and serviceImpl classes, and vice versa, must be done only using DTOs. +6. Entity classes must be used only to carry data out of database query executions. + +Entities + +1. Must annotate entity classes with @Entity. +2. Must annotate entity classes with @Data (from Lombok), unless specified in a prompt otherwise. +3. Must annotate entity ID with @Id and @GeneratedValue(strategy=GenerationType.IDENTITY). +4. Must use FetchType.LAZY for relationships, unless specified in a prompt otherwise. +5. Annotate entity properties properly according to best practices, e.g., @Size, @NotEmpty, @Email, etc. + +Repository (DAO): + +1. Must annotate repository classes with @Repository. +2. Repository classes must be of type interface. +3. Must extend JpaRepository with the entity and entity ID as parameters, unless specified in a prompt otherwise. +4. Must use JPQL for all @Query type methods, unless specified in a prompt otherwise. +5. Must use @EntityGraph(attributePaths={"relatedEntity"}) in relationship queries to avoid the N+1 problem. +6. Must use a DTO as The data container for multi-join queries with @Query. + +Service: + +1. Service classes must be of type interface. +2. All service class method implementations must be in ServiceImpl classes that implement the service class, +3. All ServiceImpl classes must be annotated with @Service. +4. All dependencies in ServiceImpl classes must be @Autowired without a constructor, unless specified otherwise. +5. Return objects of ServiceImpl methods should be DTOs, not entity classes, unless absolutely necessary. +6. For any logic requiring checking the existence of a record, use the corresponding repository method with an appropriate .orElseThrow lambda method. +7. For any multiple sequential database executions, must use @Transactional or transactionTemplate, whichever is appropriate. + +Data Transfer object (DTo): + +1. Must be of type record, unless specified in a prompt otherwise. +2. Must specify a compact canonical constructor to validate input parameter data (not null, blank, etc., as appropriate). + +RestController: + +1. Must annotate controller classes with @RestController. +2. Must specify class-level API routes with @RequestMapping, e.g. ("/api/user"). +3. Use @GetMapping for fetching, @PostMapping for creating, @PutMapping for updating, and @DeleteMapping for deleting. Keep paths resource-based (e.g., '/users/{id}'), avoiding verbs like '/create', '/update', '/delete', '/get', or '/edit' +4. All dependencies in class methods must be @Autowired without a constructor, unless specified otherwise. +5. Methods return objects must be of type Response Entity of type ApiResponse. +6. All class method logic must be implemented in a try..catch block(s). +7. Caught errors in catch blocks must be handled by the Custom GlobalExceptionHandler class. + +ApiResponse Class (/ApiResponse.java): + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApiResponse { + private String result; // SUCCESS or ERROR + private String message; // success or error message + private T data; // return object from service class, if successful +} + +GlobalExceptionHandler Class (/GlobalExceptionHandler.java) + +@RestControllerAdvice +public class GlobalExceptionHandler { + + public static ResponseEntity> errorResponseEntity(String message, HttpStatus status) { + ApiResponse response = new ApiResponse<>("error", message, null) + return new ResponseEntity<>(response, status); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity> handleIllegalArgumentException(IllegalArgumentException ex) { + return new ResponseEntity<>(ApiResponse.error(400, ex.getMessage()), HttpStatus.BAD_REQUEST); + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9cf151c --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.gradle +build/ +out/ +*.log +.DS_Store +.idea/ + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ed8b586 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +# Build stage +FROM gradle:8.5.0-jdk17-jammy AS build +WORKDIR /home/gradle/src +COPY build.gradle settings.gradle ./ +COPY src ./src +RUN gradle build --no-daemon + +# Package stage +FROM openjdk:17-jdk-slim +WORKDIR /app +COPY --from=build /home/gradle/src/build/libs/*.jar app.jar +ENTRYPOINT ["java","-jar","app.jar"] diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..2464b48 --- /dev/null +++ b/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.3.1' + id 'io.spring.dependency-management' version '1.1.5' +} + +group = 'com.study' +version = '0.0.1-SNAPSHOT' +sourceCompatibility = '17' + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + runtimeOnly 'com.mysql:mysql-connector-j' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6b7aca2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ + +version: '3.8' +services: + db: + image: mysql:8.0 + container_name: mysql_boot + restart: always + ports: + - "3306:3306" + environment: + MYSQL_DATABASE: 'bitly_db' + MYSQL_ROOT_PASSWORD: 'root' + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci diff --git a/gemini.md b/gemini.md new file mode 100644 index 0000000..95a6da3 --- /dev/null +++ b/gemini.md @@ -0,0 +1,108 @@ +# gemini.md: URL 단축기 만들기 + +## 1. 소개 (Introduction) + +URL 단축기(URL Shortener)는 긴 URL을 짧고 기억하기 쉬운 URL로 변환해 주는 서비스입니다. 이렇게 생성된 짧은 URL을 클릭하면 원래의 긴 URL로 리디렉션됩니다. 소셜 미디어, 이메일, 문자 메시지 등 글자 수 제한이 있는 플랫폼에서 유용하게 사용되며, 링크 추적 및 분석에도 활용될 수 있습니다. 💻 + +본 문서는 URL 단축기의 핵심 기능, 시스템 아키텍처, 데이터베이스 설계, 그리고 구현 단계에 대한 개요를 제공합니다. + +--- + +## 2. 핵심 기능 (Core Features) + +기본적인 URL 단축기는 다음과 같은 핵심 기능을 포함해야 합니다. + +* **URL 단축:** 사용자가 긴 URL을 입력하면, 고유하고 짧은 URL을 생성하여 반환합니다. +* **URL 리디렉션:** 짧은 URL에 접속하면, 원래의 긴 URL로 리디렉션합니다. +* **(선택) 사용자 정의 URL:** 사용자가 원하는 문자열로 짧은 URL을 직접 지정할 수 있는 기능을 제공할 수 있습니다. +* **(선택) 링크 만료:** 생성된 짧은 URL이 특정 시간이 지나면 만료되도록 설정할 수 있습니다. +* **(선택) 분석:** 짧은 URL의 클릭 수, 접속 지역 등 통계 정보를 제공할 수 있습니다. + +--- + +## 3. 시스템 아키텍처 (System Architecture) + +URL 단축기의 시스템 아키텍처는 일반적으로 다음과 같은 구성 요소를 포함합니다. + + + +* **웹 서버 (Web Server):** 사용자의 요청을 받아 처리하는 역할을 합니다. (예: Nginx, Apache) +* **애플리케이션 서버 (Application Server):** URL 단축 및 리디렉션 로직을 수행합니다. (예: Node.js, Python/Django, Java/Spring) +* **데이터베이스 (Database):** 원본 URL과 단축된 URL의 매핑 정보를 저장합니다. 대규모 트래픽을 처리하기 위해 NoSQL 데이터베이스가 유리할 수 있습니다. (예: Redis, Cassandra, MongoDB) +* **캐시 (Cache):** 자주 요청되는 URL의 리디렉션 속도를 높이기 위해 사용됩니다. 사용자가 짧은 URL을 요청하면, 먼저 캐시에서 찾아보고 없는 경우에만 데이터베이스에 접근합니다. (예: Redis, Memcached) + +### 작업 흐름 + +1. **URL 단축 요청:** + * 사용자가 긴 URL을 입력하고 단축을 요청합니다. + * 애플리케이션 서버는 이 URL에 대한 고유한 짧은 키를 생성합니다. + * 생성된 짧은 키와 원본 URL을 데이터베이스에 저장합니다. + * 사용자에게 단축된 전체 URL을 반환합니다. + +2. **URL 리디렉션 요청:** + * 사용자가 단축된 URL을 클릭합니다. + * 웹 서버는 이 요청을 애플리케이션 서버로 전달합니다. + * 애플리케이션 서버는 먼저 캐시에서 해당 짧은 키에 매핑된 원본 URL을 찾습니다. + * 캐시에 없는 경우, 데이터베이스에서 조회합니다. + * 조회된 원본 URL로 사용자를 301 Moved Permanently 리디렉션합니다. + * 데이터베이스에서 조회한 경우, 해당 정보를 캐시에 저장하여 다음 요청에 빠르게 응답할 수 있도록 합니다. + +--- + +## 4. 데이터베이스 설계 (Database Design) + +URL 단축기를 위한 데이터베이스 스키마는 비교적 간단하게 설계할 수 있습니다. 관계형 데이터베이스(RDBMS)나 NoSQL 데이터베이스 모두 사용 가능하며, 대규모 서비스를 고려한다면 읽기 성능이 뛰어난 NoSQL이 더 적합할 수 있습니다. + +**기본 테이블 스키마 예시 (`urls` 테이블):** + +| 컬럼명 (Column Name) | 데이터 타입 (Data Type) | 설명 (Description) | +| -------------------- | ----------------------- | -------------------------------------------------- | +| `id` | BIGINT (Auto Increment) | 고유 식별자 (Primary Key) | +| `original_url` | TEXT | 사용자가 입력한 원본 URL | +| `short_key` | VARCHAR(10) | 생성된 고유한 짧은 키 (Index, Unique) | +| `created_at` | TIMESTAMP | 생성 일시 | +| `expires_at` | TIMESTAMP (Nullable) | 만료 일시 (선택 기능) | +| `click_count` | INT (Default: 0) | 클릭 수 (분석 기능을 위한 선택 컬럼) | + +`short_key`는 고유해야 하며, 빠른 조회를 위해 인덱스를 생성하는 것이 중요합니다. + +--- + +## 5. 구현 단계 (Implementation Steps) + +### 1단계: 짧은 키 생성 전략 결정 + +고유하고 충돌 없는 짧은 키를 생성하는 것이 핵심입니다. 몇 가지 전략이 있습니다. + +* **해싱 (Hashing):** 원본 URL을 MD5나 SHA-256과 같은 해시 함수로 해싱한 후, 결과값의 일부를 잘라내어 사용합니다. 해시 충돌(collision)이 발생할 수 있으므로, 충돌 시 다른 문자열을 덧붙여 다시 해싱하는 등의 처리 로직이 필요합니다. +* **Base62 인코딩:** 데이터베이스의 Auto Increment 되는 `id` 값을 62진법(0-9, a-z, A-Z)으로 변환하는 방법입니다. 이 방법은 생성된 키의 길이가 예측 가능하고 충돌이 발생하지 않는다는 장점이 있습니다. + * 예시: `id`가 `1000`이라면, Base62로 인코딩하면 `g8`이 됩니다. + +### 2단계: API 엔드포인트 설계 + +* `POST /api/v1/shorten`: 긴 URL을 받아 짧은 URL을 생성하는 엔드포인트. + * **Request Body:** `{ "originalUrl": "https://www.example.com/very/long/url" }` + * **Response Body:** `{ "shortUrl": "https://your.domain/g8" }` +* `GET /{shortKey}`: 짧은 URL을 받아 원래 URL로 리디렉션하는 엔드포인트. + +### 3단계: 리디렉션 처리 + +사용자가 `https://your.domain/{shortKey}`로 접속했을 때, 데이터베이스에서 `{shortKey}`에 해당하는 `original_url`을 찾아 301 리디렉션을 수행합니다. 301 리디렉션은 영구 이동을 의미하며, 검색 엔진 최적화(SEO)에도 도움이 됩니다. + +--- + +## 6. 확장성 및 고려사항 (Scalability and Considerations) + +* **데이터베이스 확장성:** + * **샤딩 (Sharding):** 데이터베이스를 여러 서버에 분산하여 저장함으로써 부하를 분산시킬 수 있습니다. +* **고가용성 (High Availability):** + * **로드 밸런서 (Load Balancer):** 여러 대의 애플리케이션 서버에 트래픽을 분산하여 하나의 서버에 장애가 발생하더라도 서비스가 중단되지 않도록 합니다. + * **데이터베이스 복제 (Replication):** 데이터베이스를 여러 개 복제하여 원본에 문제가 생겨도 복제본을 통해 서비스를 계속할 수 있습니다. +* **보안 (Security):** + * **악성 URL 차단:** Google Safe Browsing API 등을 이용하여 사용자가 악성 사이트로 리디렉션되는 것을 방지할 수 있습니다. + * **Rate Limiting:** 특정 IP 주소에서 비정상적으로 많은 요청이 오는 경우, 요청을 제한하여 서비스 공격(DoS)을 방지합니다. +* **분석 (Analytics):** + * 클릭 이벤트가 발생할 때마다 관련 정보를 비동기적으로 처리하여 데이터베이스에 저장하면, 리디렉션 성능에 영향을 주지 않으면서 통계 데이터를 수집할 수 있습니다. 메시지 큐(Message Queue)를 활용하는 것이 좋은 방법입니다. (예: RabbitMQ, Kafka) + + + \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..9bbc975 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..37f853b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..faf9300 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..68ad58f --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'bitly-mingi' diff --git a/src/main/java/com/study/bitly/BitlyApplication.java b/src/main/java/com/study/bitly/BitlyApplication.java new file mode 100644 index 0000000..be86ccc --- /dev/null +++ b/src/main/java/com/study/bitly/BitlyApplication.java @@ -0,0 +1,12 @@ +package com.study.bitly; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class BitlyApplication { + public static void main(String[] args) { + SpringApplication.run(BitlyApplication.class, args); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..277a15f --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,14 @@ +spring: + datasource: + url: jdbc:mysql://db:3306/bitly_db?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8 + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + jpa: + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + format_sql: true + dialect: org.hibernate.dialect.MySQLDialect \ No newline at end of file