-
Notifications
You must be signed in to change notification settings - Fork 0
add initial project structure and configuration files #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| --- | ||
| description: Structure of ApiResponse class. | ||
| globs: **/src/main/java/com/study/bitly/ApiResponse.java | ||
| --- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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). |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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. | ||||||||||||||||||||||
|
Comment on lines
+6
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid Lombok @DaTa on JPA entities; tighten guidance. @DaTa can generate problematic equals/hashCode/toString for entities and relationships. - Must annotate entity classes with @Entity.
-- Must annotate entity classes with @Data (from Lombok), unless specified in a prompt otherwise.
+- Prefer Lombok @Getter/@Setter and explicitly manage @EqualsAndHashCode(onlyExplicitlyIncluded = true) and @ToString(exclude = {"relations"}).
- 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.
+- Provide a protected no-args constructor for JPA; consider auditing fields (@CreatedDate, @LastModifiedDate) with @EntityListeners.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| --- | ||
| description: Structure of GlobalExceptionHandler class. | ||
| globs: **/src/main/java/com/study/bitly/GlobalExceptionHandler.java | ||
| --- |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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. | ||||||||||
|
Comment on lines
+8
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Make query guidance practical (JPQL vs native; N+1 mitigation). Hard “must”s here are too rigid; prefer guidance with options. Apply: - 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.
+ Prefer JPQL for @Query methods; use native queries only when required (e.g., DB-specific features or performance).
+ To mitigate N+1, use @EntityGraph, fetch joins, or batching (hibernate.default_batch_fetch_size) as appropriate and verified by query plans.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| - Must use a DTO as The data container for multi-join queries with @Query. | ||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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 | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Fix glob path to match package (controllers under com.study.bitly) Current glob misses the actual package and nested packages. -globs: **/src/main/java/com/study/controllers/*.java
+globs: **/src/main/java/com/study/bitly/controllers/**/*.java📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| --- | ||||||||||||||
| - 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. | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Constructor injection over field/method injection Do not instruct @Autowired on “class methods”; use constructor injection for dependencies. -- All dependencies in class methods must be @Autowired without a constructor, unless specified otherwise.
+- Prefer constructor injection for controller dependencies; avoid field or method injection.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| - Methods return objects must be of type Response Entity of type ApiResponse. | ||||||||||||||
|
Comment on lines
+7
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Correct annotations, paths, and return type
-- Class methods must use best practice HTTP method annotations, e.g, create = @postMapping("/create"), etc.
-+ Use @PostMapping, @GetMapping, @PutMapping, @DeleteMapping with resource-based paths (e.g., @PostMapping("/users")).
-
-- Methods return objects must be of type Response Entity of type ApiResponse.
-+ Methods should return ResponseEntity<ApiResponse<?>>.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| - 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. | ||||||||||||||
|
Comment on lines
+10
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Don’t require try/catch in every handler; use @RestControllerAdvice Let controllers be thin; centralize error handling. -- 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.
+- Do not wrap controller methods in try/catch. Throw exceptions and handle them in GlobalExceptionHandler (@RestControllerAdvice).📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -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 | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Fix glob path to match current package structure The project packages are under com.study.bitly. Update the glob so the rule actually applies. -globs: **/src/main/java/com/study/services/*.java
+globs: **/src/main/java/com/study/bitly/services/**/*.java📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| --- | ||||||
| - 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. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Prefer constructor injection; avoid field injection mandate Field injection (@Autowired on fields) harms testability and immutability. Spring Boot 3 favors constructor injection (no annotation needed for single ctor). -- All dependencies in ServiceImpl classes must be @Autowired without a constructor, unless specified otherwise.
+- Prefer constructor injection for dependencies (no field injection). Use @Autowired on the constructor only when multiple constructors exist.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| - 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. | ||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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 | ||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rule set mismatches the project stack (Maven/PostgreSQL vs Gradle/MySQL). Align the rule description with this repository (Gradle + MySQL). Apply: -- Framework: Java Spring Boot 3 Maven with Java 17 Dependencies: Spring Web, Spring Data JPA, Thymeleaf, Lombok, PostgreSQL driver
+- Framework: Spring Boot 3 with Gradle (Java 17). Dependencies: Spring Web, Spring Data JPA, Lombok, MySQL driver.
+ - Add Thymeleaf only if server‑side rendering is required.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| - 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. | ||||||||||||||||||||||||
|
Comment on lines
+6
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Tighten and correct architectural guidance (remove ambiguity, fix terminology). Current wording introduces exceptions that undermine the rules and uses “ServiceImpl” rigidly. Apply: - 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.
+ All HTTP request/response handling must reside in @RestController classes.
+ Business logic and DB operations belong to the Service layer (interfaces + implementations). Services must use Repository methods (no direct EntityManager access except in rare, justified cases).
+ RestControllers must not depend on Repository types; depend on Service interfaces only.
+ Use DTOs at the controller–service boundary (both directions). Do not expose entities in API contracts.
+ Entities represent persisted domain objects for both reads and writes. Avoid using entities as external API payloads.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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 | ||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Tooling mismatch: project uses Gradle, not Maven Align the rule text with the build system to avoid confusion. -Framework: Java Spring Boot 3 Maven with Java 17 Dependencies: Spring Web, Spring Data JPA, Lombok, MySQL driver, Redis
+Framework: Java Spring Boot 3 with Java 17 (Gradle). Dependencies: Spring Web, Spring Data JPA, Lombok, MySQL driver, Redis📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| 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. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+24
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid Lombok @DaTa on JPA entities @DaTa generates equals/hashCode/toString that can traverse lazy relations and cause bugs. Prefer granular annotations. -2. Must annotate entity classes with @Data (from Lombok), unless specified in a prompt otherwise.
+2. Prefer @Getter/@Setter and define @EqualsAndHashCode(onlyExplicitlyIncluded = true) on identifiers; avoid @Data on entities.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| 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. | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+42
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Adopt constructor injection and drop blanket try/catch requirement Promote testability and centralized error handling. -4. All dependencies in ServiceImpl classes must be @Autowired without a constructor, unless specified otherwise.
+4. Use constructor injection in ServiceImpl classes (avoid field injection).
-4. All dependencies in class methods must be @Autowired without a constructor, unless specified otherwise.
-6. All class method logic must be implemented in a try..catch block(s).
+4. Use constructor injection in controllers (avoid field/method injection).
+6. Do not wrap handlers in try/catch; handle exceptions via GlobalExceptionHandler.Also applies to: 57-60 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| 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<T> { | ||||||||||||||||||||||||||||||||||||||||||||||
| private String result; // SUCCESS or ERROR | ||||||||||||||||||||||||||||||||||||||||||||||
| private String message; // success or error message | ||||||||||||||||||||||||||||||||||||||||||||||
| private T data; // return object from service class, if successful | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+64
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ApiResponse snippet lacks factory methods used elsewhere GlobalExceptionHandler calls ApiResponse.error(...), which doesn’t exist. Add success/error factories. @Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
private String result; // SUCCESS or ERROR
private String message; // success or error message
private T data; // return object from service class, if successful
+
+ public static <T> ApiResponse<T> success(String message, T data) {
+ return new ApiResponse<>("SUCCESS", message, data);
+ }
+
+ public static <T> ApiResponse<T> error(String message) {
+ return new ApiResponse<>("ERROR", message, null);
+ }
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| GlobalExceptionHandler Class (/GlobalExceptionHandler.java) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @RestControllerAdvice | ||||||||||||||||||||||||||||||||||||||||||||||
| public class GlobalExceptionHandler { | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| public static ResponseEntity<ApiResponse<?>> errorResponseEntity(String message, HttpStatus status) { | ||||||||||||||||||||||||||||||||||||||||||||||
| ApiResponse<?> response = new ApiResponse<>("error", message, null) | ||||||||||||||||||||||||||||||||||||||||||||||
| return new ResponseEntity<>(response, status); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @ExceptionHandler(IllegalArgumentException.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| public ResponseEntity<ApiResponse<?>> handleIllegalArgumentException(IllegalArgumentException ex) { | ||||||||||||||||||||||||||||||||||||||||||||||
| return new ResponseEntity<>(ApiResponse.error(400, ex.getMessage()), HttpStatus.BAD_REQUEST); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+78
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GlobalExceptionHandler snippet won’t compile (missing semicolon) and mismatched ApiResponse API
@RestControllerAdvice
public class GlobalExceptionHandler {
public static ResponseEntity<ApiResponse<?>> errorResponseEntity(String message, HttpStatus status) {
- ApiResponse<?> response = new ApiResponse<>("error", message, null)
+ ApiResponse<?> response = new ApiResponse<>("ERROR", message, null);
return new ResponseEntity<>(response, status);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ApiResponse<?>> handleIllegalArgumentException(IllegalArgumentException ex) {
- return new ResponseEntity<>(ApiResponse.error(400, ex.getMessage()), HttpStatus.BAD_REQUEST);
+ return new ResponseEntity<>(ApiResponse.error(ex.getMessage()), HttpStatus.BAD_REQUEST);
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .gradle | ||||||||||||||||||||||||||||||||||||||||||||||||||
| build/ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| out/ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| *.log | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .DS_Store | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .idea/ | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add .env and editor artifacts to prevent secret leaks and noise. Current rules miss common local secrets and editor files. .gradle
build/
out/
*.log
.DS_Store
.idea/
+
+# env/secrets
+.env
+.env.*
+*.env
+
+# editors
+.vscode/
+*.iml
+
+# logs dir
+logs/📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -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 | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. COPY may match multiple jars and fail the build. Spring Boot often produces both app.jar and app-plain.jar; copying “*.jar” to a single filename is unsafe. Apply (paired with build.gradle change to name the artifact app.jar): -COPY --from=build /home/gradle/src/build/libs/*.jar app.jar
+COPY --from=build /home/gradle/src/build/libs/app.jar app.jar📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| ENTRYPOINT ["java","-jar","app.jar"] | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() | ||
| } | ||
|
Comment on lines
+24
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Ensure a single, predictable Boot jar for containerization. Avoid producing both boot and plain jars; also fix the Docker COPY to a stable filename. Apply: tasks.named('test') {
useJUnitPlatform()
}
+
+// Produce a single Boot executable jar with a stable name for Docker
+bootJar {
+ archiveFileName = 'app.jar'
+}
+tasks.named('jar') {
+ enabled = false
+}Follow-up: adjust Dockerfile COPY to build/libs/app.jar (see comment there). 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid using root credentials; move secrets to .env and create an app user. Using root/root in-compose is risky even for dev and couples secrets to VCS. services:
db:
image: mysql:8.0
container_name: mysql_boot
restart: always
- ports:
- - "3306:3306"
- environment:
- MYSQL_DATABASE: 'bitly_db'
- MYSQL_ROOT_PASSWORD: 'root'
+ env_file:
+ - .env
+ environment:
+ MYSQL_DATABASE: ${MYSQL_DATABASE:-bitly_db}
+ MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root}
+ MYSQL_USER: ${MYSQL_USER:-app_user}
+ MYSQL_PASSWORD: ${MYSQL_PASSWORD:-app_pass}
+ ports:
+ - "${HOST_MYSQL_PORT:-3306}:3306"
+ volumes:
+ - mysql_data:/var/lib/mysql
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
+ healthcheck:
+ test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p${MYSQL_ROOT_PASSWORD:-root}"]
+ interval: 5s
+ timeout: 3s
+ retries: 30
+
+volumes:
+ mysql_data:Follow-up: add .env to .gitignore (see separate comment). 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| command: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - --character-set-server=utf8mb4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - --collation-server=utf8mb4_unicode_ci | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | 클릭 수 (분석 기능을 위한 선택 컬럼) | | ||
|
|
||
|
Comment on lines
+58
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Tight short_key length; add constraints and indexes Base62 of a 64‑bit id can exceed 10 chars. Define explicit constraints and uniqueness. -| `short_key` | VARCHAR(10) | 생성된 고유한 짧은 키 (Index, Unique) |
+| `short_key` | VARCHAR(16) | 생성된 고유한 짧은 키 (UNIQUE INDEX) |Example DDL: CREATE TABLE urls (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
original_url TEXT NOT NULL,
short_key VARCHAR(16) NOT NULL,
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
expires_at TIMESTAMP(3) NULL,
click_count BIGINT NOT NULL DEFAULT 0,
CONSTRAINT uq_urls_short_key UNIQUE (short_key)
);🤖 Prompt for AI Agents |
||
| `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) | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Fix glob to match this project’s package.
The current glob points to com.example; align to com.study.bitly.
📝 Committable suggestion
🤖 Prompt for AI Agents