diff --git a/.devcontainer/devcontainer.json b/.devcontainer.disabled/devcontainer.json similarity index 100% rename from .devcontainer/devcontainer.json rename to .devcontainer.disabled/devcontainer.json diff --git a/.devcontainer/on-create.sh b/.devcontainer.disabled/on-create.sh similarity index 100% rename from .devcontainer/on-create.sh rename to .devcontainer.disabled/on-create.sh diff --git a/java/socialapp/.gradle/9.2.1/checksums/checksums.lock b/java/socialapp/.gradle/9.2.1/checksums/checksums.lock new file mode 100644 index 0000000..eee6918 Binary files /dev/null and b/java/socialapp/.gradle/9.2.1/checksums/checksums.lock differ diff --git a/java/socialapp/.gradle/9.2.1/checksums/md5-checksums.bin b/java/socialapp/.gradle/9.2.1/checksums/md5-checksums.bin new file mode 100644 index 0000000..e8f7004 Binary files /dev/null and b/java/socialapp/.gradle/9.2.1/checksums/md5-checksums.bin differ diff --git a/java/socialapp/.gradle/9.2.1/checksums/sha1-checksums.bin b/java/socialapp/.gradle/9.2.1/checksums/sha1-checksums.bin new file mode 100644 index 0000000..615420c Binary files /dev/null and b/java/socialapp/.gradle/9.2.1/checksums/sha1-checksums.bin differ diff --git a/java/socialapp/.gradle/9.2.1/executionHistory/executionHistory.bin b/java/socialapp/.gradle/9.2.1/executionHistory/executionHistory.bin new file mode 100644 index 0000000..e068498 Binary files /dev/null and b/java/socialapp/.gradle/9.2.1/executionHistory/executionHistory.bin differ diff --git a/java/socialapp/.gradle/9.2.1/executionHistory/executionHistory.lock b/java/socialapp/.gradle/9.2.1/executionHistory/executionHistory.lock new file mode 100644 index 0000000..9c2b085 Binary files /dev/null and b/java/socialapp/.gradle/9.2.1/executionHistory/executionHistory.lock differ diff --git a/java/socialapp/.gradle/9.2.1/fileChanges/last-build.bin b/java/socialapp/.gradle/9.2.1/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/java/socialapp/.gradle/9.2.1/fileChanges/last-build.bin differ diff --git a/java/socialapp/.gradle/9.2.1/fileHashes/fileHashes.bin b/java/socialapp/.gradle/9.2.1/fileHashes/fileHashes.bin new file mode 100644 index 0000000..d5d3ce4 Binary files /dev/null and b/java/socialapp/.gradle/9.2.1/fileHashes/fileHashes.bin differ diff --git a/java/socialapp/.gradle/9.2.1/fileHashes/fileHashes.lock b/java/socialapp/.gradle/9.2.1/fileHashes/fileHashes.lock new file mode 100644 index 0000000..6a4103c Binary files /dev/null and b/java/socialapp/.gradle/9.2.1/fileHashes/fileHashes.lock differ diff --git a/java/socialapp/.gradle/9.2.1/fileHashes/resourceHashesCache.bin b/java/socialapp/.gradle/9.2.1/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000..b9eaf06 Binary files /dev/null and b/java/socialapp/.gradle/9.2.1/fileHashes/resourceHashesCache.bin differ diff --git a/java/socialapp/.gradle/9.2.1/gc.properties b/java/socialapp/.gradle/9.2.1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/java/socialapp/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/java/socialapp/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000..1ab90a5 Binary files /dev/null and b/java/socialapp/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/java/socialapp/.gradle/buildOutputCleanup/cache.properties b/java/socialapp/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..2991605 --- /dev/null +++ b/java/socialapp/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Thu Jan 29 22:05:10 UTC 2026 +gradle.version=9.2.1 diff --git a/java/socialapp/.gradle/buildOutputCleanup/outputFiles.bin b/java/socialapp/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000..9f0bc5e Binary files /dev/null and b/java/socialapp/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/java/socialapp/.gradle/vcs-1/gc.properties b/java/socialapp/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/java/socialapp/build.gradle b/java/socialapp/build.gradle new file mode 100644 index 0000000..8b43a44 --- /dev/null +++ b/java/socialapp/build.gradle @@ -0,0 +1,38 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.2.0' +} + +group = 'com.contoso' +version = '0.0.1-SNAPSHOT' +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +repositories { + mavenCentral() +} + +dependencies { + implementation platform('org.springframework.boot:spring-boot-dependencies:3.2.0') + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' + + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-jdbc' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2' + implementation 'org.xerial:sqlite-jdbc:3.42.0.0' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.2' + + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +bootJar { + launchScript() +} diff --git a/java/socialapp/pom.xml b/java/socialapp/pom.xml new file mode 100644 index 0000000..fd310f3 --- /dev/null +++ b/java/socialapp/pom.xml @@ -0,0 +1,68 @@ + + 4.0.0 + com.contoso + socialapp + 0.0.1-SNAPSHOT + jar + + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + + + 21 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.xerial + sqlite-jdbc + 3.42.0.0 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.1.0 + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/java/socialapp/settings.gradle b/java/socialapp/settings.gradle new file mode 100644 index 0000000..87c36e0 --- /dev/null +++ b/java/socialapp/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'socialapp' \ No newline at end of file diff --git a/java/socialapp/sns_api.db b/java/socialapp/sns_api.db new file mode 100644 index 0000000..9963bf9 Binary files /dev/null and b/java/socialapp/sns_api.db differ diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/Application.java b/java/socialapp/src/main/java/com/contoso/socialapp/Application.java new file mode 100644 index 0000000..3f71399 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/Application.java @@ -0,0 +1,11 @@ +package com.contoso.socialapp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/config/DatabaseInitializer.java b/java/socialapp/src/main/java/com/contoso/socialapp/config/DatabaseInitializer.java new file mode 100644 index 0000000..9d0df14 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/config/DatabaseInitializer.java @@ -0,0 +1,52 @@ +package com.contoso.socialapp.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; + +@Component +public class DatabaseInitializer { + + private final JdbcTemplate jdbc; + + @Autowired + public DatabaseInitializer(JdbcTemplate jdbc) { + this.jdbc = jdbc; + } + + @PostConstruct + public void init() { + // Drop and recreate tables on every startup to mirror FastAPI init_database behavior + jdbc.execute("DROP TABLE IF EXISTS likes"); + jdbc.execute("DROP TABLE IF EXISTS comments"); + jdbc.execute("DROP TABLE IF EXISTS posts"); + + jdbc.execute("CREATE TABLE posts (" + + "id TEXT PRIMARY KEY," + + "username TEXT NOT NULL," + + "content TEXT NOT NULL," + + "created_at TEXT NOT NULL," + + "updated_at TEXT NOT NULL," + + "likes INTEGER NOT NULL," + + "likes_by TEXT NOT NULL" + + ")"); + + jdbc.execute("CREATE TABLE comments (" + + "id TEXT PRIMARY KEY," + + "post_id TEXT NOT NULL," + + "username TEXT NOT NULL," + + "content TEXT NOT NULL," + + "created_at TEXT NOT NULL," + + "updated_at TEXT NOT NULL," + + "likes INTEGER NOT NULL" + + ")"); + + jdbc.execute("CREATE TABLE likes (" + + "like_id TEXT PRIMARY KEY," + + "post_id TEXT NOT NULL," + + "username TEXT NOT NULL" + + ")"); + } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/controller/CommentController.java b/java/socialapp/src/main/java/com/contoso/socialapp/controller/CommentController.java new file mode 100644 index 0000000..44fa926 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/controller/CommentController.java @@ -0,0 +1,8 @@ +package com.contoso.socialapp.controller; + +import org.springframework.web.bind.annotation.RestController; + +// Note: Comments and comment endpoints are handled within PostController to match path structure from the OpenAPI contract. +@RestController +public class CommentController { +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/controller/HealthController.java b/java/socialapp/src/main/java/com/contoso/socialapp/controller/HealthController.java new file mode 100644 index 0000000..df5f857 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/controller/HealthController.java @@ -0,0 +1,16 @@ +package com.contoso.socialapp.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +public class HealthController { + + @GetMapping("/health") + public ResponseEntity> health() { + return ResponseEntity.ok(Map.of("status", "healthy", "message", "API is running successfully")); + } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/controller/OpenApiController.java b/java/socialapp/src/main/java/com/contoso/socialapp/controller/OpenApiController.java new file mode 100644 index 0000000..0a9fe89 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/controller/OpenApiController.java @@ -0,0 +1,26 @@ +package com.contoso.socialapp.controller; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.InputStream; + +@RestController +public class OpenApiController { + + @GetMapping(value = "/v3/api-docs", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity apiDocs() throws Exception { + ClassPathResource r = new ClassPathResource("static/openapi.yaml"); + try (InputStream in = r.getInputStream()) { + ObjectMapper yamlReader = new YAMLMapper(); + JsonNode obj = yamlReader.readTree(in); + return ResponseEntity.ok(obj); + } + } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/controller/PostController.java b/java/socialapp/src/main/java/com/contoso/socialapp/controller/PostController.java new file mode 100644 index 0000000..572fb44 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/controller/PostController.java @@ -0,0 +1,116 @@ +package com.contoso.socialapp.controller; + +import com.contoso.socialapp.model.dto.*; +import com.contoso.socialapp.service.CommentService; +import com.contoso.socialapp.service.LikeService; +import com.contoso.socialapp.service.PostService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.Valid; +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api") +@Validated +public class PostController { + private final PostService postService; + private final CommentService commentService; + private final LikeService likeService; + + @Autowired + public PostController(PostService postService, CommentService commentService, LikeService likeService) { + this.postService = postService; + this.commentService = commentService; + this.likeService = likeService; + } + + @GetMapping("/posts") + public ResponseEntity> listPosts() { + return ResponseEntity.ok(postService.listPosts()); + } + + @PostMapping("/posts") + public ResponseEntity createPost(@Valid @RequestBody NewPostRequest req) { + PostDTO p = postService.createPost(req.getUsername(), req.getContent()); + return ResponseEntity.status(HttpStatus.CREATED).body(p); + } + + @GetMapping("/posts/{postId}") + public ResponseEntity getPost(@PathVariable("postId") String postId) { + Optional p = postService.getPostById(postId); + if (p.isEmpty()) throw new com.contoso.socialapp.exception.ResourceNotFoundException("Post with ID '" + postId + "' not found"); + return ResponseEntity.ok(p.get()); + } + + @PatchMapping("/posts/{postId}") + public ResponseEntity updatePost(@PathVariable("postId") String postId, @Valid @RequestBody UpdatePostRequest req) { + Optional p = postService.updatePost(postId, req.getUsername(), req.getContent()); + if (p.isEmpty()) throw new com.contoso.socialapp.exception.ResourceNotFoundException("Post not found or you do not have permission to update it"); + return ResponseEntity.ok(p.get()); + } + + @DeleteMapping("/posts/{postId}") + public ResponseEntity deletePost(@PathVariable("postId") String postId) { + boolean deleted = postService.deletePost(postId); + if (!deleted) throw new com.contoso.socialapp.exception.ResourceNotFoundException("Post with ID '" + postId + "' not found"); + return ResponseEntity.noContent().build(); + } + + // Comments + @GetMapping("/posts/{postId}/comments") + public ResponseEntity> listComments(@PathVariable("postId") String postId) { + // Verify post exists + Optional p = postService.getPostById(postId); + if (p.isEmpty()) throw new com.contoso.socialapp.exception.ResourceNotFoundException("Post with ID '" + postId + "' not found"); + return ResponseEntity.ok(commentService.listCommentsByPostId(postId)); + } + + @PostMapping("/posts/{postId}/comments") + public ResponseEntity createComment(@PathVariable("postId") String postId, @Valid @RequestBody NewCommentRequest req) { + var c = commentService.createComment(postId, req.getUsername(), req.getContent()); + if (c.isEmpty()) throw new com.contoso.socialapp.exception.ResourceNotFoundException("Post with ID '" + postId + "' not found"); + return ResponseEntity.status(HttpStatus.CREATED).body(c.get()); + } + + @GetMapping("/posts/{postId}/comments/{commentId}") + public ResponseEntity getComment(@PathVariable("postId") String postId, @PathVariable("commentId") String commentId) { + var c = commentService.getComment(postId, commentId); + if (c.isEmpty()) throw new com.contoso.socialapp.exception.ResourceNotFoundException("Comment with ID '" + commentId + "' not found on post '" + postId + "'"); + return ResponseEntity.ok(c.get()); + } + + @PatchMapping("/posts/{postId}/comments/{commentId}") + public ResponseEntity updateComment(@PathVariable("postId") String postId, @PathVariable("commentId") String commentId, @Valid @RequestBody UpdateCommentRequest req) { + var c = commentService.updateComment(postId, commentId, req.getUsername(), req.getContent()); + if (c.isEmpty()) throw new com.contoso.socialapp.exception.ResourceNotFoundException("Comment not found or you do not have permission to update it"); + return ResponseEntity.ok(c.get()); + } + + @DeleteMapping("/posts/{postId}/comments/{commentId}") + public ResponseEntity deleteComment(@PathVariable("postId") String postId, @PathVariable("commentId") String commentId) { + boolean deleted = commentService.deleteComment(postId, commentId); + if (!deleted) throw new com.contoso.socialapp.exception.ResourceNotFoundException("Comment with ID '" + commentId + "' not found"); + return ResponseEntity.noContent().build(); + } + + // Likes + @PostMapping("/posts/{postId}/likes") + public ResponseEntity likePost(@PathVariable("postId") String postId, @Valid @RequestBody LikeRequest req) { + var r = likeService.addLike(postId, req.getUsername()); + if (r == null) throw new com.contoso.socialapp.exception.ResourceNotFoundException("Post with ID '" + postId + "' not found"); + return ResponseEntity.status(HttpStatus.CREATED).body(r); + } + + @DeleteMapping("/posts/{postId}/likes") + public ResponseEntity unlikePost(@PathVariable("postId") String postId, @Valid @RequestBody LikeRequest req) { + boolean existsPost = postService.getPostById(postId).isPresent(); + if (!existsPost) throw new com.contoso.socialapp.exception.ResourceNotFoundException("Post with ID '" + postId + "' not found"); + likeService.removeLike(postId, req.getUsername()); + return ResponseEntity.noContent().build(); + } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/exception/ApiExceptionHandler.java b/java/socialapp/src/main/java/com/contoso/socialapp/exception/ApiExceptionHandler.java new file mode 100644 index 0000000..f15b287 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/exception/ApiExceptionHandler.java @@ -0,0 +1,61 @@ +package com.contoso.socialapp.exception; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.context.request.WebRequest; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@ControllerAdvice +public class ApiExceptionHandler { + + private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(ApiExceptionHandler.class); + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidation(MethodArgumentNotValidException ex) { + List details = new ArrayList<>(); + for (FieldError err : ex.getBindingResult().getFieldErrors()) { + details.add(err.getField() + " " + err.getDefaultMessage()); + } + Map body = new HashMap<>(); + body.put("error", "VALIDATION_ERROR"); + body.put("message", "The request body is invalid"); + body.put("details", details); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body); + } + + @ExceptionHandler(org.springframework.web.servlet.resource.NoResourceFoundException.class) + public ResponseEntity handleNoResource(org.springframework.web.servlet.resource.NoResourceFoundException ex) { + Map body = new HashMap<>(); + body.put("error", "NOT_FOUND"); + body.put("message", "The requested resource was not found"); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleAll(Exception ex) { + // Log the exception so we can see stack traces in the server log when an unexpected error occurs + logger.error("Unhandled exception caught by ApiExceptionHandler", ex); + Map body = new HashMap<>(); + body.put("error", "INTERNAL_ERROR"); + body.put("message", "An unexpected error occurred"); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body); + } + + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleNotFound(ResourceNotFoundException ex) { + Map body = new HashMap<>(); + body.put("error", "NOT_FOUND"); + body.put("message", ex.getMessage() != null ? ex.getMessage() : "The requested resource was not found"); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body); + } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/exception/ResourceNotFoundException.java b/java/socialapp/src/main/java/com/contoso/socialapp/exception/ResourceNotFoundException.java new file mode 100644 index 0000000..45af360 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/exception/ResourceNotFoundException.java @@ -0,0 +1,5 @@ +package com.contoso.socialapp.exception; + +public class ResourceNotFoundException extends RuntimeException { + public ResourceNotFoundException(String message) { super(message); } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/CommentDTO.java b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/CommentDTO.java new file mode 100644 index 0000000..68c4fcb --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/CommentDTO.java @@ -0,0 +1,39 @@ +package com.contoso.socialapp.model.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CommentDTO { + private String id; + + @JsonProperty("postId") + private String postId; + + private String username; + private String content; + + @JsonProperty("createdAt") + private String createdAt; + + @JsonProperty("updatedAt") + private String updatedAt; + + public CommentDTO() {} + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + + public String getPostId() { return postId; } + public void setPostId(String postId) { this.postId = postId; } + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getContent() { return content; } + public void setContent(String content) { this.content = content; } + + public String getCreatedAt() { return createdAt; } + public void setCreatedAt(String createdAt) { this.createdAt = createdAt; } + + public String getUpdatedAt() { return updatedAt; } + public void setUpdatedAt(String updatedAt) { this.updatedAt = updatedAt; } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/LikeRequest.java b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/LikeRequest.java new file mode 100644 index 0000000..43a371c --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/LikeRequest.java @@ -0,0 +1,13 @@ +package com.contoso.socialapp.model.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class LikeRequest { + @NotBlank + @Size(min = 1, max = 100) + private String username; + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/LikeResponse.java b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/LikeResponse.java new file mode 100644 index 0000000..4d35f16 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/LikeResponse.java @@ -0,0 +1,24 @@ +package com.contoso.socialapp.model.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class LikeResponse { + @JsonProperty("postId") + private String postId; + + @JsonProperty("username") + private String username; + + @JsonProperty("likedAt") + private String likedAt; + + public LikeResponse(String postId, String username, String likedAt) { + this.postId = postId; + this.username = username; + this.likedAt = likedAt; + } + + public String getPostId() { return postId; } + public String getUsername() { return username; } + public String getLikedAt() { return likedAt; } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/NewCommentRequest.java b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/NewCommentRequest.java new file mode 100644 index 0000000..30994fb --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/NewCommentRequest.java @@ -0,0 +1,20 @@ +package com.contoso.socialapp.model.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class NewCommentRequest { + @NotBlank + @Size(min = 1, max = 100) + private String username; + + @NotBlank + @Size(min = 1, max = 300) + private String content; + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getContent() { return content; } + public void setContent(String content) { this.content = content; } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/NewPostRequest.java b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/NewPostRequest.java new file mode 100644 index 0000000..7fe2ab9 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/NewPostRequest.java @@ -0,0 +1,20 @@ +package com.contoso.socialapp.model.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class NewPostRequest { + @NotBlank + @Size(min = 1, max = 100) + private String username; + + @NotBlank + @Size(min = 1, max = 500) + private String content; + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getContent() { return content; } + public void setContent(String content) { this.content = content; } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/PostDTO.java b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/PostDTO.java new file mode 100644 index 0000000..d63993d --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/PostDTO.java @@ -0,0 +1,44 @@ +package com.contoso.socialapp.model.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class PostDTO { + private String id; + private String username; + private String content; + + @JsonProperty("createdAt") + private String createdAt; + + @JsonProperty("updatedAt") + private String updatedAt; + + @JsonProperty("likesCount") + private int likesCount; + + @JsonProperty("commentsCount") + private int commentsCount; + + public PostDTO() {} + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getContent() { return content; } + public void setContent(String content) { this.content = content; } + + public String getCreatedAt() { return createdAt; } + public void setCreatedAt(String createdAt) { this.createdAt = createdAt; } + + public String getUpdatedAt() { return updatedAt; } + public void setUpdatedAt(String updatedAt) { this.updatedAt = updatedAt; } + + public int getLikesCount() { return likesCount; } + public void setLikesCount(int likesCount) { this.likesCount = likesCount; } + + public int getCommentsCount() { return commentsCount; } + public void setCommentsCount(int commentsCount) { this.commentsCount = commentsCount; } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/UpdateCommentRequest.java b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/UpdateCommentRequest.java new file mode 100644 index 0000000..1e9a644 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/UpdateCommentRequest.java @@ -0,0 +1,20 @@ +package com.contoso.socialapp.model.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class UpdateCommentRequest { + @NotBlank + @Size(min = 1, max = 100) + private String username; + + @NotBlank + @Size(min = 1, max = 300) + private String content; + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getContent() { return content; } + public void setContent(String content) { this.content = content; } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/UpdatePostRequest.java b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/UpdatePostRequest.java new file mode 100644 index 0000000..4e8411b --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/UpdatePostRequest.java @@ -0,0 +1,20 @@ +package com.contoso.socialapp.model.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class UpdatePostRequest { + @NotBlank + @Size(min = 1, max = 100) + private String username; + + @NotBlank + @Size(min = 1, max = 500) + private String content; + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getContent() { return content; } + public void setContent(String content) { this.content = content; } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/repository/CommentRepository.java b/java/socialapp/src/main/java/com/contoso/socialapp/repository/CommentRepository.java new file mode 100644 index 0000000..38fc136 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/repository/CommentRepository.java @@ -0,0 +1,75 @@ +package com.contoso.socialapp.repository; + +import com.contoso.socialapp.model.dto.CommentDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.stereotype.Repository; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +@Repository +public class CommentRepository { + private final JdbcTemplate jdbc; + + @Autowired + public CommentRepository(JdbcTemplate jdbc) { + this.jdbc = jdbc; + } + + public void insertComment(String id, String postId, String username, String content, String createdAt, String updatedAt) { + jdbc.update("INSERT INTO comments (id, post_id, username, content, created_at, updated_at, likes) VALUES (?,?,?,?,?,?,?)", + id, postId, username, content, createdAt, updatedAt, 0); + } + + public List findByPostId(String postId) { + return jdbc.query("SELECT * FROM comments WHERE post_id = ?", new Object[]{postId}, new RowMapper() { + @Override + public CommentDTO mapRow(ResultSet rs, int rowNum) throws SQLException { + CommentDTO c = new CommentDTO(); + c.setId(rs.getString("id")); + c.setPostId(rs.getString("post_id")); + c.setUsername(rs.getString("username")); + c.setContent(rs.getString("content")); + c.setCreatedAt(rs.getString("created_at")); + c.setUpdatedAt(rs.getString("updated_at")); + return c; + } + }); + } + + public Optional findByPostAndId(String postId, String commentId) { + SqlRowSet rs = jdbc.queryForRowSet("SELECT * FROM comments WHERE id = ?", commentId); + if (rs.next() && rs.getString("post_id").equals(postId)) { + CommentDTO c = new CommentDTO(); + c.setId(rs.getString("id")); + c.setPostId(rs.getString("post_id")); + c.setUsername(rs.getString("username")); + c.setContent(rs.getString("content")); + c.setCreatedAt(rs.getString("created_at")); + c.setUpdatedAt(rs.getString("updated_at")); + return Optional.of(c); + } + return Optional.empty(); + } + + public void updateCommentContent(String commentId, String content, String updatedAt) { + jdbc.update("UPDATE comments SET content = ?, updated_at = ? WHERE id = ?", content, updatedAt, commentId); + } + + public void deleteComment(String commentId) { + jdbc.update("DELETE FROM comments WHERE id = ?", commentId); + } + + public int countByPostId(String postId) { + Integer count = jdbc.queryForObject("SELECT COUNT(*) FROM comments WHERE post_id = ?", Integer.class, postId); + return count == null ? 0 : count; + } + + public void deleteByPostId(String postId) { + jdbc.update("DELETE FROM comments WHERE post_id = ?", postId); + } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/repository/LikeRepository.java b/java/socialapp/src/main/java/com/contoso/socialapp/repository/LikeRepository.java new file mode 100644 index 0000000..de3abd0 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/repository/LikeRepository.java @@ -0,0 +1,44 @@ +package com.contoso.socialapp.repository; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.stereotype.Repository; + +import java.util.ArrayList; +import java.util.List; + +@Repository +public class LikeRepository { + private final JdbcTemplate jdbc; + + @Autowired + public LikeRepository(JdbcTemplate jdbc) { + this.jdbc = jdbc; + } + + public boolean exists(String likeId) { + SqlRowSet rs = jdbc.queryForRowSet("SELECT like_id FROM likes WHERE like_id = ?", likeId); + return rs.next(); + } + + public void insertLike(String likeId, String postId, String username) { + jdbc.update("INSERT INTO likes (like_id, post_id, username) VALUES (?,?,?)", likeId, postId, username); + } + + public void deleteLike(String likeId) { + jdbc.update("DELETE FROM likes WHERE like_id = ?", likeId); + } + + public List findUsernamesByPostId(String postId) { + List list = new ArrayList<>(); + jdbc.query("SELECT username FROM likes WHERE post_id = ?", new Object[]{postId}, (rs) -> { + list.add(rs.getString("username")); + }); + return list; + } + + public void deleteByPostId(String postId) { + jdbc.update("DELETE FROM likes WHERE post_id = ?", postId); + } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/repository/PostRepository.java b/java/socialapp/src/main/java/com/contoso/socialapp/repository/PostRepository.java new file mode 100644 index 0000000..e2351d6 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/repository/PostRepository.java @@ -0,0 +1,86 @@ +package com.contoso.socialapp.repository; + +import com.contoso.socialapp.model.dto.PostDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.stereotype.Repository; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Instant; +import java.util.*; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@Repository +public class PostRepository { + private final JdbcTemplate jdbc; + private final ObjectMapper objectMapper; + + @Autowired + public PostRepository(JdbcTemplate jdbc, ObjectMapper objectMapper) { + this.jdbc = jdbc; + this.objectMapper = objectMapper; + } + + public void insertPost(String id, String username, String content, String createdAt, String updatedAt) { + jdbc.update("INSERT INTO posts (id, username, content, created_at, updated_at, likes, likes_by) VALUES (?,?,?,?,?,?,?)", + id, username, content, createdAt, updatedAt, 0, "[]"); + } + + public List findAll() { + List list = jdbc.query("SELECT * FROM posts", new RowMapper() { + @Override + public PostDTO mapRow(ResultSet rs, int rowNum) throws SQLException { + PostDTO p = new PostDTO(); + p.setId(rs.getString("id")); + p.setUsername(rs.getString("username")); + p.setContent(rs.getString("content")); + p.setCreatedAt(rs.getString("created_at")); + p.setUpdatedAt(rs.getString("updated_at")); + p.setLikesCount(rs.getInt("likes")); + // commentsCount computed separately + p.setCommentsCount(0); + return p; + } + }); + // sort by createdAt desc + list.sort(Comparator.comparing(PostDTO::getCreatedAt).reversed()); + return list; + } + + public Optional> findPostRow(String id) { + SqlRowSet rs = jdbc.queryForRowSet("SELECT * FROM posts WHERE id = ?", id); + if (rs.next()) { + Map m = new HashMap<>(); + m.put("id", rs.getString("id")); + m.put("username", rs.getString("username")); + m.put("content", rs.getString("content")); + m.put("created_at", rs.getString("created_at")); + m.put("updated_at", rs.getString("updated_at")); + m.put("likes", rs.getInt("likes")); + m.put("likes_by", rs.getString("likes_by")); + return Optional.of(m); + } + return Optional.empty(); + } + + public void updatePostContent(String id, String content, String updatedAt) { + jdbc.update("UPDATE posts SET content = ?, updated_at = ? WHERE id = ?", content, updatedAt, id); + } + + public void deletePost(String id) { + jdbc.update("DELETE FROM posts WHERE id = ?", id); + } + + public void setLikesByAndCount(String postId, List likesBy) { + try { + String json = objectMapper.writeValueAsString(likesBy); + jdbc.update("UPDATE posts SET likes = ?, likes_by = ? WHERE id = ?", likesBy.size(), json, postId); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/service/CommentService.java b/java/socialapp/src/main/java/com/contoso/socialapp/service/CommentService.java new file mode 100644 index 0000000..86b39b3 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/service/CommentService.java @@ -0,0 +1,57 @@ +package com.contoso.socialapp.service; + +import com.contoso.socialapp.model.dto.CommentDTO; +import com.contoso.socialapp.repository.CommentRepository; +import com.contoso.socialapp.repository.PostRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; + +@Service +public class CommentService { + private final CommentRepository commentRepo; + private final PostRepository postRepo; + + @Autowired + public CommentService(CommentRepository commentRepo, PostRepository postRepo) { + this.commentRepo = commentRepo; + this.postRepo = postRepo; + } + + public Optional createComment(String postId, String username, String content) { + if (postRepo.findPostRow(postId).isEmpty()) return Optional.empty(); + String id = UUID.randomUUID().toString(); + String now = Instant.now().toString(); + commentRepo.insertComment(id, postId, username, content, now, now); + Optional c = commentRepo.findByPostAndId(postId, id); + return c; + } + + public Optional getComment(String postId, String commentId) { + return commentRepo.findByPostAndId(postId, commentId); + } + + public java.util.List listCommentsByPostId(String postId) { + return commentRepo.findByPostId(postId); + } + + public Optional updateComment(String postId, String commentId, String username, String content) { + Optional existing = commentRepo.findByPostAndId(postId, commentId); + if (existing.isEmpty()) return Optional.empty(); + CommentDTO c = existing.get(); + if (!c.getUsername().equals(username)) return Optional.empty(); + String now = Instant.now().toString(); + commentRepo.updateCommentContent(commentId, content, now); + return commentRepo.findByPostAndId(postId, commentId); + } + + public boolean deleteComment(String postId, String commentId) { + Optional c = commentRepo.findByPostAndId(postId, commentId); + if (c.isEmpty()) return false; + commentRepo.deleteComment(commentId); + return true; + } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/service/LikeService.java b/java/socialapp/src/main/java/com/contoso/socialapp/service/LikeService.java new file mode 100644 index 0000000..29dd435 --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/service/LikeService.java @@ -0,0 +1,66 @@ +package com.contoso.socialapp.service; + +import com.contoso.socialapp.repository.LikeRepository; +import com.contoso.socialapp.repository.PostRepository; +import com.contoso.socialapp.model.dto.LikeResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +@Service +public class LikeService { + private final LikeRepository likeRepo; + private final PostRepository postRepo; + private final ObjectMapper objectMapper; + + @Autowired + public LikeService(LikeRepository likeRepo, PostRepository postRepo, ObjectMapper objectMapper) { + this.likeRepo = likeRepo; + this.postRepo = postRepo; + this.objectMapper = objectMapper; + } + + public LikeResponse addLike(String postId, String username) { + var postRow = postRepo.findPostRow(postId); + if (postRow.isEmpty()) return null; + String likeId = postId + "#" + username; + if (!likeRepo.exists(likeId)) { + likeRepo.insertLike(likeId, postId, username); + // update posts.likes_by json and likes count + String likesByJson = (String) postRow.get().get("likes_by"); + try { + List likesBy = objectMapper.readValue(likesByJson, new TypeReference>(){}); + likesBy.add(username); + postRepo.setLikesByAndCount(postId, likesBy); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + String likedAt = Instant.now().toString(); + return new LikeResponse(postId, username, likedAt); + } + + public boolean removeLike(String postId, String username) { + var postRow = postRepo.findPostRow(postId); + if (postRow.isEmpty()) return false; + String likeId = postId + "#" + username; + if (likeRepo.exists(likeId)) { + likeRepo.deleteLike(likeId); + String likesByJson = (String) postRow.get().get("likes_by"); + try { + List likesBy = objectMapper.readValue(likesByJson, new TypeReference>(){}); + likesBy.removeIf(u -> u.equals(username)); + postRepo.setLikesByAndCount(postId, likesBy); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return true; + } +} diff --git a/java/socialapp/src/main/java/com/contoso/socialapp/service/PostService.java b/java/socialapp/src/main/java/com/contoso/socialapp/service/PostService.java new file mode 100644 index 0000000..59f218e --- /dev/null +++ b/java/socialapp/src/main/java/com/contoso/socialapp/service/PostService.java @@ -0,0 +1,86 @@ +package com.contoso.socialapp.service; + +import com.contoso.socialapp.model.dto.PostDTO; +import com.contoso.socialapp.repository.PostRepository; +import com.contoso.socialapp.repository.CommentRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +@Service +public class PostService { + private final PostRepository postRepo; + private final CommentRepository commentRepo; + private final com.contoso.socialapp.repository.LikeRepository likeRepo; + + @Autowired + public PostService(PostRepository postRepo, CommentRepository commentRepo, com.contoso.socialapp.repository.LikeRepository likeRepo) { + this.postRepo = postRepo; + this.commentRepo = commentRepo; + this.likeRepo = likeRepo; + } + + public List listPosts() { + List posts = postRepo.findAll(); + for (PostDTO p : posts) { + int comments = commentRepo.countByPostId(p.getId()); + p.setCommentsCount(comments); + } + return posts; + } + + public PostDTO createPost(String username, String content) { + String id = UUID.randomUUID().toString(); + String now = Instant.now().toString(); + postRepo.insertPost(id, username, content, now, now); + PostDTO p = new PostDTO(); + p.setId(id); + p.setUsername(username); + p.setContent(content); + p.setCreatedAt(now); + p.setUpdatedAt(now); + p.setLikesCount(0); + p.setCommentsCount(0); + return p; + } + + public Optional getPostById(String id) { + Optional> row = postRepo.findPostRow(id); + if (row.isEmpty()) return Optional.empty(); + Map m = row.get(); + PostDTO p = new PostDTO(); + p.setId((String)m.get("id")); + p.setUsername((String)m.get("username")); + p.setContent((String)m.get("content")); + p.setCreatedAt((String)m.get("created_at")); + p.setUpdatedAt((String)m.get("updated_at")); + p.setLikesCount((Integer)m.get("likes")); + p.setCommentsCount(commentRepo.countByPostId(id)); + return Optional.of(p); + } + + public Optional updatePost(String id, String username, String content) { + Optional> row = postRepo.findPostRow(id); + if (row.isEmpty()) return Optional.empty(); + Map m = row.get(); + if (!((String)m.get("username")).equals(username)) return Optional.empty(); + String updatedAt = Instant.now().toString(); + postRepo.updatePostContent(id, content, updatedAt); + return getPostById(id); + } + + public boolean deletePost(String id) { + Optional> row = postRepo.findPostRow(id); + if (row.isEmpty()) return false; + // Delete post and cascade delete comments and likes via repositories + postRepo.deletePost(id); + commentRepo.deleteByPostId(id); + likeRepo.deleteByPostId(id); + return true; + } +} diff --git a/java/socialapp/src/main/resources/application.properties b/java/socialapp/src/main/resources/application.properties new file mode 100644 index 0000000..55ab60f --- /dev/null +++ b/java/socialapp/src/main/resources/application.properties @@ -0,0 +1,7 @@ +server.port=8080 +spring.datasource.url=jdbc:sqlite:sns_api.db +spring.datasource.driver-class-name=org.sqlite.JDBC +spring.datasource.initialization-mode=never +# Point Swagger UI to our static OpenAPI document +springdoc.swagger-ui.url=/openapi.yaml +springdoc.api-docs.enabled=false diff --git a/java/socialapp/src/main/resources/static/openapi.yaml b/java/socialapp/src/main/resources/static/openapi.yaml new file mode 100644 index 0000000..d058b78 --- /dev/null +++ b/java/socialapp/src/main/resources/static/openapi.yaml @@ -0,0 +1,574 @@ +openapi: 3.0.1 +info: + title: Simple Social Media API + description: A basic Social Networking Service (SNS) API that allows users to create, retrieve, update, and delete posts; add comments; and like/unlike posts. + version: 1.0.0 + contact: + name: Contoso Product Team + email: support@contoso.com + license: + name: MIT + url: https://opensource.org/licenses/MIT + +servers: + - url: http://localhost:8080/api + description: Local development server + +paths: + /posts: + get: + summary: List all posts + description: Retrieve all recent posts to browse what others are sharing + operationId: getPosts + tags: + - Posts + responses: + '200': + description: Successfully retrieved posts + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Post' + '500': + $ref: '#/components/responses/InternalServerError' + + post: + summary: Create a new post + description: Create a new post to share something with others + operationId: createPost + tags: + - Posts + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPostRequest' + responses: + '201': + description: Post created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Post' + '400': + $ref: '#/components/responses/BadRequest' + '500': + $ref: '#/components/responses/InternalServerError' + + /posts/{postId}: + get: + summary: Get a specific post + description: Retrieve a specific post by its ID to read in detail + operationId: getPostById + tags: + - Posts + parameters: + - $ref: '#/components/parameters/PostIdPath' + responses: + '200': + description: Successfully retrieved the post + content: + application/json: + schema: + $ref: '#/components/schemas/Post' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + patch: + summary: Update a post + description: Update an existing post if you made a mistake or have something to add + operationId: updatePost + tags: + - Posts + parameters: + - $ref: '#/components/parameters/PostIdPath' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatePostRequest' + responses: + '200': + description: Post updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Post' + '400': + $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + delete: + summary: Delete a post + description: Delete a post if you no longer want it shared + operationId: deletePost + tags: + - Posts + parameters: + - $ref: '#/components/parameters/PostIdPath' + responses: + '204': + description: Post deleted successfully + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /posts/{postId}/comments: + get: + summary: List comments for a post + description: Retrieve all comments on a specific post + operationId: getCommentsByPostId + tags: + - Comments + parameters: + - $ref: '#/components/parameters/PostIdPath' + responses: + '200': + description: Successfully retrieved comments + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Comment' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + post: + summary: Create a comment + description: Add a comment to a post to share your thoughts + operationId: createComment + tags: + - Comments + parameters: + - $ref: '#/components/parameters/PostIdPath' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewCommentRequest' + responses: + '201': + description: Comment created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /posts/{postId}/comments/{commentId}: + get: + summary: Get a specific comment + description: Retrieve a specific comment by its ID + operationId: getCommentById + tags: + - Comments + parameters: + - $ref: '#/components/parameters/PostIdPath' + - $ref: '#/components/parameters/CommentIdPath' + responses: + '200': + description: Successfully retrieved the comment + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + patch: + summary: Update a comment + description: Update an existing comment to correct or revise it + operationId: updateComment + tags: + - Comments + parameters: + - $ref: '#/components/parameters/PostIdPath' + - $ref: '#/components/parameters/CommentIdPath' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateCommentRequest' + responses: + '200': + description: Comment updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + delete: + summary: Delete a comment + description: Delete a comment if necessary + operationId: deleteComment + tags: + - Comments + parameters: + - $ref: '#/components/parameters/PostIdPath' + - $ref: '#/components/parameters/CommentIdPath' + responses: + '204': + description: Comment deleted successfully + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /posts/{postId}/likes: + post: + summary: Like a post + description: Like a post to show appreciation + operationId: likePost + tags: + - Likes + parameters: + - $ref: '#/components/parameters/PostIdPath' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LikeRequest' + responses: + '201': + description: Post liked successfully + content: + application/json: + schema: + $ref: '#/components/schemas/LikeResponse' + '400': + $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + delete: + summary: Unlike a post + description: Remove your like from a post if you change your mind + operationId: unlikePost + tags: + - Likes + parameters: + - $ref: '#/components/parameters/PostIdPath' + responses: + '204': + description: Like removed successfully + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + +components: + parameters: + PostIdPath: + name: postId + in: path + required: true + description: Unique identifier of the post + schema: + type: string + format: uuid + example: "123e4567-e89b-12d3-a456-426614174000" + + CommentIdPath: + name: commentId + in: path + required: true + description: Unique identifier of the comment + schema: + type: string + format: uuid + example: "987fcdeb-51a2-43d1-9f6b-123456789abc" + + schemas: + Post: + type: object + required: + - id + - username + - content + - createdAt + - updatedAt + - likesCount + - commentsCount + properties: + id: + type: string + format: uuid + description: Unique identifier for the post + example: "123e4567-e89b-12d3-a456-426614174000" + username: + type: string + minLength: 1 + maxLength: 50 + description: Username of the post author + example: "john_doe" + content: + type: string + minLength: 1 + maxLength: 2000 + description: Content of the post + example: "Just had an amazing hike in the mountains! #outdoorlife" + createdAt: + type: string + format: date-time + description: Timestamp when the post was created + example: "2025-06-01T10:30:00Z" + updatedAt: + type: string + format: date-time + description: Timestamp when the post was last updated + example: "2025-06-01T10:30:00Z" + likesCount: + type: integer + minimum: 0 + description: Number of likes on the post + example: 15 + commentsCount: + type: integer + minimum: 0 + description: Number of comments on the post + example: 3 + + Comment: + type: object + required: + - id + - postId + - username + - content + - createdAt + - updatedAt + properties: + id: + type: string + format: uuid + description: Unique identifier for the comment + example: "987fcdeb-51a2-43d1-9f6b-123456789abc" + postId: + type: string + format: uuid + description: ID of the post this comment belongs to + example: "123e4567-e89b-12d3-a456-426614174000" + username: + type: string + minLength: 1 + maxLength: 50 + description: Username of the comment author + example: "jane_smith" + content: + type: string + minLength: 1 + maxLength: 1000 + description: Content of the comment + example: "Great photo! Where was this taken?" + createdAt: + type: string + format: date-time + description: Timestamp when the comment was created + example: "2025-06-01T11:15:00Z" + updatedAt: + type: string + format: date-time + description: Timestamp when the comment was last updated + example: "2025-06-01T11:15:00Z" + + NewPostRequest: + type: object + required: + - username + - content + properties: + username: + type: string + minLength: 1 + maxLength: 50 + description: Username of the post author + example: "john_doe" + content: + type: string + minLength: 1 + maxLength: 2000 + description: Content of the post + example: "Just had an amazing hike in the mountains! #outdoorlife" + + UpdatePostRequest: + type: object + required: + - username + - content + properties: + username: + type: string + minLength: 1 + maxLength: 50 + description: Username of the post author (for validation) + example: "john_doe" + content: + type: string + minLength: 1 + maxLength: 2000 + description: Updated content of the post + example: "Just had an amazing hike in the mountains! The view was breathtaking. #outdoorlife #hiking" + + NewCommentRequest: + type: object + required: + - username + - content + properties: + username: + type: string + minLength: 1 + maxLength: 50 + description: Username of the comment author + example: "jane_smith" + content: + type: string + minLength: 1 + maxLength: 1000 + description: Content of the comment + example: "Great photo! Where was this taken?" + + UpdateCommentRequest: + type: object + required: + - username + - content + properties: + username: + type: string + minLength: 1 + maxLength: 50 + description: Username of the comment author (for validation) + example: "jane_smith" + content: + type: string + minLength: 1 + maxLength: 1000 + description: Updated content of the comment + example: "Great photo! Where was this taken? The scenery looks amazing!" + + LikeRequest: + type: object + required: + - username + properties: + username: + type: string + minLength: 1 + maxLength: 50 + description: Username of the user liking the post + example: "mike_wilson" + + LikeResponse: + type: object + required: + - postId + - username + - likedAt + properties: + postId: + type: string + format: uuid + description: ID of the liked post + example: "123e4567-e89b-12d3-a456-426614174000" + username: + type: string + description: Username who liked the post + example: "mike_wilson" + likedAt: + type: string + format: date-time + description: Timestamp when the post was liked + example: "2025-06-01T12:00:00Z" + + Error: + type: object + required: + - error + - message + properties: + error: + type: string + description: Error code or type + example: "VALIDATION_ERROR" + message: + type: string + description: Human-readable error message + example: "The request body is invalid" + details: + type: array + description: Additional error details (optional) + items: + type: string + example: ["username is required", "content must not be empty"] + + responses: + BadRequest: + description: Bad request - invalid input or validation error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: "VALIDATION_ERROR" + message: "The request body is invalid" + details: ["username is required", "content must not be empty"] + + NotFound: + description: Resource not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: "NOT_FOUND" + message: "The requested resource was not found" + + InternalServerError: + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: "INTERNAL_ERROR" + message: "An unexpected error occurred" + +tags: + - name: Posts + description: Operations related to posts management + - name: Comments + description: Operations related to comments management + - name: Likes + description: Operations related to likes management diff --git a/java/socialapp/src/main/resources/static/swagger-ui/index.html b/java/socialapp/src/main/resources/static/swagger-ui/index.html new file mode 100644 index 0000000..bd300b0 --- /dev/null +++ b/java/socialapp/src/main/resources/static/swagger-ui/index.html @@ -0,0 +1,26 @@ + + + + + + Swagger UI + + + + +
+ + + + \ No newline at end of file diff --git a/java/socialapp/target/classes/application.properties b/java/socialapp/target/classes/application.properties new file mode 100644 index 0000000..55ab60f --- /dev/null +++ b/java/socialapp/target/classes/application.properties @@ -0,0 +1,7 @@ +server.port=8080 +spring.datasource.url=jdbc:sqlite:sns_api.db +spring.datasource.driver-class-name=org.sqlite.JDBC +spring.datasource.initialization-mode=never +# Point Swagger UI to our static OpenAPI document +springdoc.swagger-ui.url=/openapi.yaml +springdoc.api-docs.enabled=false diff --git a/java/socialapp/target/classes/static/openapi.yaml b/java/socialapp/target/classes/static/openapi.yaml new file mode 100644 index 0000000..d058b78 --- /dev/null +++ b/java/socialapp/target/classes/static/openapi.yaml @@ -0,0 +1,574 @@ +openapi: 3.0.1 +info: + title: Simple Social Media API + description: A basic Social Networking Service (SNS) API that allows users to create, retrieve, update, and delete posts; add comments; and like/unlike posts. + version: 1.0.0 + contact: + name: Contoso Product Team + email: support@contoso.com + license: + name: MIT + url: https://opensource.org/licenses/MIT + +servers: + - url: http://localhost:8080/api + description: Local development server + +paths: + /posts: + get: + summary: List all posts + description: Retrieve all recent posts to browse what others are sharing + operationId: getPosts + tags: + - Posts + responses: + '200': + description: Successfully retrieved posts + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Post' + '500': + $ref: '#/components/responses/InternalServerError' + + post: + summary: Create a new post + description: Create a new post to share something with others + operationId: createPost + tags: + - Posts + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPostRequest' + responses: + '201': + description: Post created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Post' + '400': + $ref: '#/components/responses/BadRequest' + '500': + $ref: '#/components/responses/InternalServerError' + + /posts/{postId}: + get: + summary: Get a specific post + description: Retrieve a specific post by its ID to read in detail + operationId: getPostById + tags: + - Posts + parameters: + - $ref: '#/components/parameters/PostIdPath' + responses: + '200': + description: Successfully retrieved the post + content: + application/json: + schema: + $ref: '#/components/schemas/Post' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + patch: + summary: Update a post + description: Update an existing post if you made a mistake or have something to add + operationId: updatePost + tags: + - Posts + parameters: + - $ref: '#/components/parameters/PostIdPath' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatePostRequest' + responses: + '200': + description: Post updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Post' + '400': + $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + delete: + summary: Delete a post + description: Delete a post if you no longer want it shared + operationId: deletePost + tags: + - Posts + parameters: + - $ref: '#/components/parameters/PostIdPath' + responses: + '204': + description: Post deleted successfully + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /posts/{postId}/comments: + get: + summary: List comments for a post + description: Retrieve all comments on a specific post + operationId: getCommentsByPostId + tags: + - Comments + parameters: + - $ref: '#/components/parameters/PostIdPath' + responses: + '200': + description: Successfully retrieved comments + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Comment' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + post: + summary: Create a comment + description: Add a comment to a post to share your thoughts + operationId: createComment + tags: + - Comments + parameters: + - $ref: '#/components/parameters/PostIdPath' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewCommentRequest' + responses: + '201': + description: Comment created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /posts/{postId}/comments/{commentId}: + get: + summary: Get a specific comment + description: Retrieve a specific comment by its ID + operationId: getCommentById + tags: + - Comments + parameters: + - $ref: '#/components/parameters/PostIdPath' + - $ref: '#/components/parameters/CommentIdPath' + responses: + '200': + description: Successfully retrieved the comment + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + patch: + summary: Update a comment + description: Update an existing comment to correct or revise it + operationId: updateComment + tags: + - Comments + parameters: + - $ref: '#/components/parameters/PostIdPath' + - $ref: '#/components/parameters/CommentIdPath' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateCommentRequest' + responses: + '200': + description: Comment updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + delete: + summary: Delete a comment + description: Delete a comment if necessary + operationId: deleteComment + tags: + - Comments + parameters: + - $ref: '#/components/parameters/PostIdPath' + - $ref: '#/components/parameters/CommentIdPath' + responses: + '204': + description: Comment deleted successfully + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /posts/{postId}/likes: + post: + summary: Like a post + description: Like a post to show appreciation + operationId: likePost + tags: + - Likes + parameters: + - $ref: '#/components/parameters/PostIdPath' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LikeRequest' + responses: + '201': + description: Post liked successfully + content: + application/json: + schema: + $ref: '#/components/schemas/LikeResponse' + '400': + $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + delete: + summary: Unlike a post + description: Remove your like from a post if you change your mind + operationId: unlikePost + tags: + - Likes + parameters: + - $ref: '#/components/parameters/PostIdPath' + responses: + '204': + description: Like removed successfully + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + +components: + parameters: + PostIdPath: + name: postId + in: path + required: true + description: Unique identifier of the post + schema: + type: string + format: uuid + example: "123e4567-e89b-12d3-a456-426614174000" + + CommentIdPath: + name: commentId + in: path + required: true + description: Unique identifier of the comment + schema: + type: string + format: uuid + example: "987fcdeb-51a2-43d1-9f6b-123456789abc" + + schemas: + Post: + type: object + required: + - id + - username + - content + - createdAt + - updatedAt + - likesCount + - commentsCount + properties: + id: + type: string + format: uuid + description: Unique identifier for the post + example: "123e4567-e89b-12d3-a456-426614174000" + username: + type: string + minLength: 1 + maxLength: 50 + description: Username of the post author + example: "john_doe" + content: + type: string + minLength: 1 + maxLength: 2000 + description: Content of the post + example: "Just had an amazing hike in the mountains! #outdoorlife" + createdAt: + type: string + format: date-time + description: Timestamp when the post was created + example: "2025-06-01T10:30:00Z" + updatedAt: + type: string + format: date-time + description: Timestamp when the post was last updated + example: "2025-06-01T10:30:00Z" + likesCount: + type: integer + minimum: 0 + description: Number of likes on the post + example: 15 + commentsCount: + type: integer + minimum: 0 + description: Number of comments on the post + example: 3 + + Comment: + type: object + required: + - id + - postId + - username + - content + - createdAt + - updatedAt + properties: + id: + type: string + format: uuid + description: Unique identifier for the comment + example: "987fcdeb-51a2-43d1-9f6b-123456789abc" + postId: + type: string + format: uuid + description: ID of the post this comment belongs to + example: "123e4567-e89b-12d3-a456-426614174000" + username: + type: string + minLength: 1 + maxLength: 50 + description: Username of the comment author + example: "jane_smith" + content: + type: string + minLength: 1 + maxLength: 1000 + description: Content of the comment + example: "Great photo! Where was this taken?" + createdAt: + type: string + format: date-time + description: Timestamp when the comment was created + example: "2025-06-01T11:15:00Z" + updatedAt: + type: string + format: date-time + description: Timestamp when the comment was last updated + example: "2025-06-01T11:15:00Z" + + NewPostRequest: + type: object + required: + - username + - content + properties: + username: + type: string + minLength: 1 + maxLength: 50 + description: Username of the post author + example: "john_doe" + content: + type: string + minLength: 1 + maxLength: 2000 + description: Content of the post + example: "Just had an amazing hike in the mountains! #outdoorlife" + + UpdatePostRequest: + type: object + required: + - username + - content + properties: + username: + type: string + minLength: 1 + maxLength: 50 + description: Username of the post author (for validation) + example: "john_doe" + content: + type: string + minLength: 1 + maxLength: 2000 + description: Updated content of the post + example: "Just had an amazing hike in the mountains! The view was breathtaking. #outdoorlife #hiking" + + NewCommentRequest: + type: object + required: + - username + - content + properties: + username: + type: string + minLength: 1 + maxLength: 50 + description: Username of the comment author + example: "jane_smith" + content: + type: string + minLength: 1 + maxLength: 1000 + description: Content of the comment + example: "Great photo! Where was this taken?" + + UpdateCommentRequest: + type: object + required: + - username + - content + properties: + username: + type: string + minLength: 1 + maxLength: 50 + description: Username of the comment author (for validation) + example: "jane_smith" + content: + type: string + minLength: 1 + maxLength: 1000 + description: Updated content of the comment + example: "Great photo! Where was this taken? The scenery looks amazing!" + + LikeRequest: + type: object + required: + - username + properties: + username: + type: string + minLength: 1 + maxLength: 50 + description: Username of the user liking the post + example: "mike_wilson" + + LikeResponse: + type: object + required: + - postId + - username + - likedAt + properties: + postId: + type: string + format: uuid + description: ID of the liked post + example: "123e4567-e89b-12d3-a456-426614174000" + username: + type: string + description: Username who liked the post + example: "mike_wilson" + likedAt: + type: string + format: date-time + description: Timestamp when the post was liked + example: "2025-06-01T12:00:00Z" + + Error: + type: object + required: + - error + - message + properties: + error: + type: string + description: Error code or type + example: "VALIDATION_ERROR" + message: + type: string + description: Human-readable error message + example: "The request body is invalid" + details: + type: array + description: Additional error details (optional) + items: + type: string + example: ["username is required", "content must not be empty"] + + responses: + BadRequest: + description: Bad request - invalid input or validation error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: "VALIDATION_ERROR" + message: "The request body is invalid" + details: ["username is required", "content must not be empty"] + + NotFound: + description: Resource not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: "NOT_FOUND" + message: "The requested resource was not found" + + InternalServerError: + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: "INTERNAL_ERROR" + message: "An unexpected error occurred" + +tags: + - name: Posts + description: Operations related to posts management + - name: Comments + description: Operations related to comments management + - name: Likes + description: Operations related to likes management diff --git a/java/socialapp/target/maven-archiver/pom.properties b/java/socialapp/target/maven-archiver/pom.properties new file mode 100644 index 0000000..4c46523 --- /dev/null +++ b/java/socialapp/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=socialapp +groupId=com.contoso +version=0.0.1-SNAPSHOT diff --git a/java/socialapp/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/java/socialapp/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..aa51179 --- /dev/null +++ b/java/socialapp/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,25 @@ +com/contoso/socialapp/service/CommentService.class +com/contoso/socialapp/controller/PostController.class +com/contoso/socialapp/service/PostService.class +com/contoso/socialapp/repository/PostRepository$1.class +com/contoso/socialapp/repository/CommentRepository.class +com/contoso/socialapp/config/DatabaseInitializer.class +com/contoso/socialapp/model/dto/LikeResponse.class +com/contoso/socialapp/repository/PostRepository.class +com/contoso/socialapp/repository/LikeRepository.class +com/contoso/socialapp/service/LikeService$1.class +com/contoso/socialapp/model/dto/LikeRequest.class +com/contoso/socialapp/model/dto/UpdateCommentRequest.class +com/contoso/socialapp/exception/ApiExceptionHandler.class +com/contoso/socialapp/Application.class +com/contoso/socialapp/model/dto/NewPostRequest.class +com/contoso/socialapp/controller/HealthController.class +com/contoso/socialapp/model/dto/PostDTO.class +com/contoso/socialapp/repository/CommentRepository$1.class +com/contoso/socialapp/service/LikeService.class +com/contoso/socialapp/model/dto/CommentDTO.class +com/contoso/socialapp/service/LikeService$2.class +com/contoso/socialapp/model/dto/UpdatePostRequest.class +com/contoso/socialapp/exception/ResourceNotFoundException.class +com/contoso/socialapp/model/dto/NewCommentRequest.class +com/contoso/socialapp/controller/CommentController.class diff --git a/java/socialapp/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/java/socialapp/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..9809999 --- /dev/null +++ b/java/socialapp/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,21 @@ +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/controller/PostController.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/config/DatabaseInitializer.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/NewPostRequest.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/Application.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/CommentDTO.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/UpdatePostRequest.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/controller/HealthController.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/service/CommentService.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/service/PostService.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/PostDTO.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/exception/ResourceNotFoundException.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/UpdateCommentRequest.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/NewCommentRequest.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/controller/CommentController.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/repository/CommentRepository.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/service/LikeService.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/LikeResponse.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/repository/LikeRepository.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/repository/PostRepository.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/model/dto/LikeRequest.java +/workspaces/github-copilot-vibe-coding-workshop/java/socialapp/src/main/java/com/contoso/socialapp/exception/ApiExceptionHandler.java diff --git a/java/socialapp/target/project-local-repo/com.contoso/socialapp/0.0.1-SNAPSHOT/socialapp-0.0.1-SNAPSHOT-consumer.pom b/java/socialapp/target/project-local-repo/com.contoso/socialapp/0.0.1-SNAPSHOT/socialapp-0.0.1-SNAPSHOT-consumer.pom new file mode 100644 index 0000000..2813643 --- /dev/null +++ b/java/socialapp/target/project-local-repo/com.contoso/socialapp/0.0.1-SNAPSHOT/socialapp-0.0.1-SNAPSHOT-consumer.pom @@ -0,0 +1,61 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + com.contoso + socialapp + 0.0.1-SNAPSHOT + + 21 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.xerial + sqlite-jdbc + 3.42.0.0 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.1.0 + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/java/socialapp/target/project-local-repo/com.contoso/socialapp/0.0.1-SNAPSHOT/socialapp-0.0.1-SNAPSHOT.pom b/java/socialapp/target/project-local-repo/com.contoso/socialapp/0.0.1-SNAPSHOT/socialapp-0.0.1-SNAPSHOT.pom new file mode 100644 index 0000000..fd310f3 --- /dev/null +++ b/java/socialapp/target/project-local-repo/com.contoso/socialapp/0.0.1-SNAPSHOT/socialapp-0.0.1-SNAPSHOT.pom @@ -0,0 +1,68 @@ + + 4.0.0 + com.contoso + socialapp + 0.0.1-SNAPSHOT + jar + + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + + + 21 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.xerial + sqlite-jdbc + 3.42.0.0 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.1.0 + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/java/socialapp/target/socialapp-0.0.1-SNAPSHOT.jar.original b/java/socialapp/target/socialapp-0.0.1-SNAPSHOT.jar.original new file mode 100644 index 0000000..cdc74b5 Binary files /dev/null and b/java/socialapp/target/socialapp-0.0.1-SNAPSHOT.jar.original differ diff --git a/javascript/SimpleSocialMediaApplication/.gitignore b/javascript/SimpleSocialMediaApplication/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/javascript/SimpleSocialMediaApplication/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/javascript/SimpleSocialMediaApplication/README.md b/javascript/SimpleSocialMediaApplication/README.md new file mode 100644 index 0000000..93e23a8 --- /dev/null +++ b/javascript/SimpleSocialMediaApplication/README.md @@ -0,0 +1,149 @@ +# Simple Social Media Application + +A React web application built with Vite that demonstrates a simple social media platform with posts, comments, and likes functionality. + +## Features + +- **Post Management** + - View all posts in a feed + - Create new posts + - View post details + - Like posts + - Delete posts + +- **Comments** + - View comments on posts + - Create comments on posts + - Delete comments + - Update comments + +- **User Management** + - Set and change username + - All posts and comments are attributed to the current username + +- **API Status Monitoring** + - Visual indicator when backend API is unavailable + - Automatic health check every 5 seconds + - Graceful fallback UI when offline + +## Project Structure + +``` +src/ +├── components/ +│ ├── APIStatusIndicator.jsx # Shows API availability status +│ ├── CommentList.jsx # Displays comments for a post +│ ├── CreateCommentModal.jsx # Modal to add new comment +│ ├── CreatePostModal.jsx # Modal to create new post +│ ├── PostDetail.jsx # Detailed view of a single post +│ ├── PostList.jsx # Feed of all posts +│ └── UsernameModal.jsx # Modal for username input +├── context/ +│ └── UsernameContext.jsx # React context for managing current username +├── services/ +│ └── api.js # API client service +├── App.jsx # Main application component +├── App.css # Component styles (Tailwind) +├── index.css # Global styles with Tailwind +└── main.jsx # Application entry point +``` + +## Requirements + +- Node.js 16+ +- npm or yarn +- Backend API running at `http://localhost:8000` + +## Installation + +```bash +cd SimpleSocialMediaApplication +npm install +``` + +## Development + +Start the development server: + +```bash +npm run dev +``` + +The application will be available at `http://localhost:3000` + +## Building + +Create a production build: + +```bash +npm run build +``` + +Preview the production build: + +```bash +npm run preview +``` + +## API Integration + +The application connects to a backend API at `http://localhost:8000/api` with the following endpoints: + +### Posts +- `GET /posts` - List all posts +- `POST /posts` - Create a new post +- `GET /posts/{postId}` - Get a specific post +- `PATCH /posts/{postId}` - Update a post +- `DELETE /posts/{postId}` - Delete a post + +### Comments +- `GET /posts/{postId}/comments` - List comments for a post +- `POST /posts/{postId}/comments` - Create a comment +- `GET /posts/{postId}/comments/{commentId}` - Get a specific comment +- `PATCH /posts/{postId}/comments/{commentId}` - Update a comment +- `DELETE /posts/{postId}/comments/{commentId}` - Delete a comment + +### Likes +- `POST /posts/{postId}/likes` - Like a post +- `DELETE /posts/{postId}/likes` - Unlike a post + +## Styling + +This project uses **Tailwind CSS** for styling. All component styles are defined using Tailwind utility classes. + +### Configuration Files +- `tailwind.config.js` - Tailwind CSS configuration +- `postcss.config.js` - PostCSS configuration with Tailwind and autoprefixer + +## Technologies Used + +- **React 19** - UI framework +- **Vite** - Frontend build tool +- **Tailwind CSS** - Utility-first CSS framework +- **JavaScript (ES6+)** - Programming language + +## User Workflow + +1. **Initial Setup**: Enter a username when the app loads +2. **Browse Posts**: View all posts in the main feed +3. **View Details**: Click on a post to see full details and comments +4. **Create Post**: Click "Create Post" button to open the post creation modal +5. **Comment**: Add comments to posts from the post detail view +6. **Change Username**: Click "Change Username" to update your username + +## Error Handling + +- Network errors are displayed with retry options +- API unavailability is clearly indicated with a prominent notification +- Form validation prevents invalid submissions +- Confirmation dialogs appear before deleting content + +## License + +MIT + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/javascript/SimpleSocialMediaApplication/eslint.config.js b/javascript/SimpleSocialMediaApplication/eslint.config.js new file mode 100644 index 0000000..4fa125d --- /dev/null +++ b/javascript/SimpleSocialMediaApplication/eslint.config.js @@ -0,0 +1,29 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + rules: { + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + }, + }, +]) diff --git a/javascript/SimpleSocialMediaApplication/index.html b/javascript/SimpleSocialMediaApplication/index.html new file mode 100644 index 0000000..59de7e1 --- /dev/null +++ b/javascript/SimpleSocialMediaApplication/index.html @@ -0,0 +1,13 @@ + + + + + + + doan + + +
+ + + diff --git a/javascript/SimpleSocialMediaApplication/package-lock.json b/javascript/SimpleSocialMediaApplication/package-lock.json new file mode 100644 index 0000000..36719c8 --- /dev/null +++ b/javascript/SimpleSocialMediaApplication/package-lock.json @@ -0,0 +1,3358 @@ +{ + "name": "doan", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "doan", + "version": "0.0.0", + "dependencies": { + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@tailwindcss/postcss": "^4.1.18", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.23", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.18", + "vite": "npm:rolldown-vite@7.2.5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@oxc-project/runtime": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.97.0.tgz", + "integrity": "sha512-yH0zw7z+jEws4dZ4IUKoix5Lh3yhqIJWF9Dc8PWvhpo7U7O+lJrv7ZZL4BeRO0la8LBQFwcCewtLBnVV7hPe/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.97.0.tgz", + "integrity": "sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.50.tgz", + "integrity": "sha512-XlEkrOIHLyGT3avOgzfTFSjG+f+dZMw+/qd+Y3HLN86wlndrB/gSimrJCk4gOhr1XtRtEKfszpadI3Md4Z4/Ag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.50.tgz", + "integrity": "sha512-+JRqKJhoFlt5r9q+DecAGPLZ5PxeLva+wCMtAuoFMWPoZzgcYrr599KQ+Ix0jwll4B4HGP43avu9My8KtSOR+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.50.tgz", + "integrity": "sha512-fFXDjXnuX7/gQZQm/1FoivVtRcyAzdjSik7Eo+9iwPQ9EgtA5/nB2+jmbzaKtMGG3q+BnZbdKHCtOacmNrkIDA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.50.tgz", + "integrity": "sha512-F1b6vARy49tjmT/hbloplzgJS7GIvwWZqt+tAHEstCh0JIh9sa8FAMVqEmYxDviqKBaAI8iVvUREm/Kh/PD26Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.50.tgz", + "integrity": "sha512-U6cR76N8T8M6lHj7EZrQ3xunLPxSvYYxA8vJsBKZiFZkT8YV4kjgCO3KwMJL0NOjQCPGKyiXO07U+KmJzdPGRw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.50.tgz", + "integrity": "sha512-ONgyjofCrrE3bnh5GZb8EINSFyR/hmwTzZ7oVuyUB170lboza1VMCnb8jgE6MsyyRgHYmN8Lb59i3NKGrxrYjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.50.tgz", + "integrity": "sha512-L0zRdH2oDPkmB+wvuTl+dJbXCsx62SkqcEqdM+79LOcB+PxbAxxjzHU14BuZIQdXcAVDzfpMfaHWzZuwhhBTcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.50.tgz", + "integrity": "sha512-gyoI8o/TGpQd3OzkJnh1M2kxy1Bisg8qJ5Gci0sXm9yLFzEXIFdtc4EAzepxGvrT2ri99ar5rdsmNG0zP0SbIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.50.tgz", + "integrity": "sha512-zti8A7M+xFDpKlghpcCAzyOi+e5nfUl3QhU023ce5NCgUxRG5zGP2GR9LTydQ1rnIPwZUVBWd4o7NjZDaQxaXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.50.tgz", + "integrity": "sha512-eZUssog7qljrrRU9Mi0eqYEPm3Ch0UwB+qlWPMKSUXHNqhm3TvDZarJQdTevGEfu3EHAXJvBIe0YFYr0TPVaMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.50.tgz", + "integrity": "sha512-nmCN0nIdeUnmgeDXiQ+2HU6FT162o+rxnF7WMkBm4M5Ds8qTU7Dzv2Wrf22bo4ftnlrb2hKK6FSwAJSAe2FWLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.50.tgz", + "integrity": "sha512-7kcNLi7Ua59JTTLvbe1dYb028QEPaJPJQHqkmSZ5q3tJueUeb6yjRtx8mw4uIqgWZcnQHAR3PrLN4XRJxvgIkA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-ia32-msvc": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.50.tgz", + "integrity": "sha512-lL70VTNvSCdSZkDPPVMwWn/M2yQiYvSoXw9hTLgdIWdUfC3g72UaruezusR6ceRuwHCY1Ayu2LtKqXkBO5LIwg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.50.tgz", + "integrity": "sha512-4qU4x5DXWB4JPjyTne/wBNPqkbQU8J45bl21geERBKtEittleonioACBL1R0PsBu0Aq21SwMK5a9zdBkWSlQtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", + "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "postcss": "^8.4.41", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", + "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/autoprefixer": { + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.279", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz", + "integrity": "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.50.tgz", + "integrity": "sha512-JFULvCNl/anKn99eKjOSEubi0lLmNqQDAjyEMME2T4CwezUDL0i6t1O9xZsu2OMehPnV2caNefWpGF+8TnzB6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.97.0", + "@rolldown/pluginutils": "1.0.0-beta.50" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-beta.50", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.50", + "@rolldown/binding-darwin-x64": "1.0.0-beta.50", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.50", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.50", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.50", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.50", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.50", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.50", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.50", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.50", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.50", + "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.50", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.50" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.50.tgz", + "integrity": "sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==", + "dev": true, + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "name": "rolldown-vite", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.2.5.tgz", + "integrity": "sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@oxc-project/runtime": "0.97.0", + "fdir": "^6.5.0", + "lightningcss": "^1.30.2", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rolldown": "1.0.0-beta.50", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "esbuild": "^0.25.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/javascript/SimpleSocialMediaApplication/package.json b/javascript/SimpleSocialMediaApplication/package.json new file mode 100644 index 0000000..c63bcae --- /dev/null +++ b/javascript/SimpleSocialMediaApplication/package.json @@ -0,0 +1,34 @@ +{ + "name": "doan", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@tailwindcss/postcss": "^4.1.18", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.23", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.18", + "vite": "npm:rolldown-vite@7.2.5" + }, + "overrides": { + "vite": "npm:rolldown-vite@7.2.5" + } +} diff --git a/javascript/SimpleSocialMediaApplication/postcss.config.js b/javascript/SimpleSocialMediaApplication/postcss.config.js new file mode 100644 index 0000000..a7f73a2 --- /dev/null +++ b/javascript/SimpleSocialMediaApplication/postcss.config.js @@ -0,0 +1,5 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + }, +} diff --git a/javascript/SimpleSocialMediaApplication/public/vite.svg b/javascript/SimpleSocialMediaApplication/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/javascript/SimpleSocialMediaApplication/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/javascript/SimpleSocialMediaApplication/src/App.css b/javascript/SimpleSocialMediaApplication/src/App.css new file mode 100644 index 0000000..743b0e2 --- /dev/null +++ b/javascript/SimpleSocialMediaApplication/src/App.css @@ -0,0 +1,2 @@ +/* App styling handled by Tailwind CSS */ + diff --git a/javascript/SimpleSocialMediaApplication/src/App.jsx b/javascript/SimpleSocialMediaApplication/src/App.jsx new file mode 100644 index 0000000..b315310 --- /dev/null +++ b/javascript/SimpleSocialMediaApplication/src/App.jsx @@ -0,0 +1,84 @@ +import { useState } from 'react'; +import { useUsername } from './context/UsernameContext'; +import PostList from './components/PostList'; +import PostDetail from './components/PostDetail'; +import CreatePostModal from './components/CreatePostModal'; +import UsernameModal from './components/UsernameModal'; +import APIStatusIndicator from './components/APIStatusIndicator'; +import './App.css'; + +function App() { + const { username } = useUsername(); + const [selectedPost, setSelectedPost] = useState(null); + const [showCreatePost, setShowCreatePost] = useState(false); + const [apiAvailable, setApiAvailable] = useState(true); + const [showUsernameModal, setShowUsernameModal] = useState(!username); + + const handlePostCreated = () => { + setShowCreatePost(false); + window.location.reload(); + }; + + return ( +
+ + +
+
+

Social Media

+ {username && ( +
+ @{username} + +
+ )} +
+ + {apiAvailable && username && ( + <> + setShowCreatePost(true)} + apiAvailable={apiAvailable} + /> + + {selectedPost && ( + setSelectedPost(null)} + apiAvailable={apiAvailable} + /> + )} + + {showCreatePost && ( + setShowCreatePost(false)} + onPostCreated={handlePostCreated} + /> + )} + + )} + + {!apiAvailable && ( +
+

Backend API Unavailable

+

+ The application cannot connect to the backend API at http://localhost:8000. Please ensure the + API server is running and try again. +

+
+ )} +
+ + setShowUsernameModal(false)} /> +
+ ); +} + +export default App; diff --git a/javascript/SimpleSocialMediaApplication/src/assets/react.svg b/javascript/SimpleSocialMediaApplication/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/javascript/SimpleSocialMediaApplication/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/javascript/SimpleSocialMediaApplication/src/components/APIStatusIndicator.jsx b/javascript/SimpleSocialMediaApplication/src/components/APIStatusIndicator.jsx new file mode 100644 index 0000000..73e3a66 --- /dev/null +++ b/javascript/SimpleSocialMediaApplication/src/components/APIStatusIndicator.jsx @@ -0,0 +1,32 @@ +import React, { useEffect, useState } from 'react'; +import { api } from '../services/api'; + +const APIStatusIndicator = ({ onStatusChange }) => { + const [isAvailable, setIsAvailable] = useState(true); + + useEffect(() => { + const checkStatus = async () => { + const available = await api.isAvailable(); + setIsAvailable(available); + onStatusChange(available); + }; + + checkStatus(); + const interval = setInterval(checkStatus, 5000); + + return () => clearInterval(interval); + }, [onStatusChange]); + + if (isAvailable) { + return null; + } + + return ( +
+
+ Backend API is unavailable. Please check your connection. +
+ ); +}; + +export default APIStatusIndicator; diff --git a/javascript/SimpleSocialMediaApplication/src/components/CommentList.jsx b/javascript/SimpleSocialMediaApplication/src/components/CommentList.jsx new file mode 100644 index 0000000..21ab78e --- /dev/null +++ b/javascript/SimpleSocialMediaApplication/src/components/CommentList.jsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { api } from '../services/api'; + +const CommentList = ({ comments, postId, onCommentDeleted }) => { + const handleDeleteComment = async (commentId) => { + if (confirm('Are you sure you want to delete this comment?')) { + try { + await api.deleteComment(postId, commentId); + onCommentDeleted(commentId); + } catch (err) { + alert('Failed to delete comment: ' + err.message); + } + } + }; + + if (comments.length === 0) { + return
No comments yet
; + } + + return ( +
+ {comments.map((comment) => ( +
+
+
+

{comment.username}

+

+ {new Date(comment.createdAt).toLocaleDateString()}{' '} + {new Date(comment.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} +

+
+ +
+

{comment.content}

+
+ ))} +
+ ); +}; + +export default CommentList; diff --git a/javascript/SimpleSocialMediaApplication/src/components/CreateCommentModal.jsx b/javascript/SimpleSocialMediaApplication/src/components/CreateCommentModal.jsx new file mode 100644 index 0000000..df82014 --- /dev/null +++ b/javascript/SimpleSocialMediaApplication/src/components/CreateCommentModal.jsx @@ -0,0 +1,78 @@ +import React, { useState } from 'react'; +import { api } from '../services/api'; +import { useUsername } from '../context/UsernameContext'; + +const CreateCommentModal = ({ isOpen, postId, onClose, onCommentCreated }) => { + const [content, setContent] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const { username } = useUsername(); + + const handleSubmit = async (e) => { + e.preventDefault(); + if (!content.trim()) return; + + setLoading(true); + setError(null); + + try { + const newComment = await api.createComment(postId, username, content.trim()); + onCommentCreated(newComment); + setContent(''); + onClose(); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + if (!isOpen) return null; + + return ( +
+
+

Add Comment

+ + {error && ( +
+ {error} +
+ )} + +
+