[ADD] CLAUDE AI, Y RETORNA ENLACE DE DRIVE

parent 7a71f8e5
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Build & Run
```bash
# Build WAR artifact
./mvnw clean package -DskipTests
# Run locally (port 8082)
./mvnw spring-boot:run
# Run a specific test
./mvnw test -Dtest=AppApplicationTests
```
The artifact name is `trismegisto-services.war`. It is deployed to WildFly/JBoss in production (see `src/main/WEB-INF/jboss-web.xml`).
## Architecture Overview
This is a Spring Boot 2.7.14 (Java 8) utility microservice that exposes several independent REST APIs. There is no persistent application database at startup — `DataSourceAutoConfiguration` is excluded. Database connections are established **per-request** via a special `Database` HTTP header.
### Request-scoped database connection
`JWTokenFilter` reads the `Database` header on every request. This header must contain a JWT token generated by `POST /token/database` that encodes `{db, url, user, password}`. The filter decodes it and stores a `JdbcTemplate` in `threadLocalSingleton` (a `ThreadLocal` wrapper). `RoutineSql` then picks up that `JdbcTemplate` to execute MySQL stored procedures or PostgreSQL functions via `POST /api/public/routine`.
### Security model
- Routes matching `*/public/*` are open.
- Routes matching `*/private/*` require `ADMIN` or `USER` authority.
- Routes matching `*/service/*` require `SERVICE` authority.
- `/security/**` requires `ADMIN`.
- Auth is stateless JWT (Bearer) or HTTP Basic. Both are validated in `JWTokenFilter`.
- Users are **hardcoded in-memory** in `UserRepository` (only `admin/admin` exists by default). There is no user table.
- Two JWT secrets in `application.properties`: `app.jwtSecret` (standard tokens) and `app.jwtSecret2` (one-life tokens). One-life tokens are tracked in `tokenSingleton` (an in-memory `JSONArray`) and can be consumed once; expired/consumed tokens are purged hourly via `@Scheduled`.
### REST endpoints
| Prefix | Class | Purpose |
|--------|-------|---------|
| `/token` | `tokenRest` | JWT auth, one-life tokens, data tokenization/detokenization |
| `/pdf/public/html` | `pdfRest` | HTML → PDF via iText 8 html2pdf; supports background image (URL or base64) |
| `/excel/public/generate` | `excelRest` | JSON → XLSX via Apache POI |
| `/gmail/public/generate` | `emailRest` | Send HTML email via Gmail API |
| `/drive` | `driveRest` | Upload/download files on Google Drive; supports year-based subfolder |
| `/api/public/routine` | `routineRest` | Call MySQL stored procedures or PostgreSQL functions |
| `/console/public/command` | `consoleRest` | Execute shell commands on the server (with optional sudo) |
| `/bbva` | `bbvaRest`, `bbvaWebHook` | BBVA payment gateway integration |
### Google integration
`GoogleConfig` handles OAuth2 token refresh for Drive and Gmail. Credentials are loaded from classpath resources:
- `src/main/resources/tokens/oAuth2_planilla.json` — Drive + Gmail for `trismegisto.planilla@sacooliveros.edu.pe`
- `src/main/resources/tokens/oAuth2_logistica.json` — Gmail for `trismegisto.logistica@sacooliveros.edu.pe`
Each JSON file must contain `client_id`, `client_secret`, `gmail_refresh_token`, and `drive_refresh_token`.
### PDF generation
`PDFService.generatePdf(html, backgroundUrl)` uses iText 8 `HtmlConverter`. The background is injected as a CSS `@page` rule appended to the HTML. Fonts are loaded from `src/main/resources/fonts/` (Roboto-Regular, Arial). The endpoint accepts JSON body or `multipart/form-data`.
### Singletons
- `tokenSingleton` — in-memory one-life token store, Spring `@Component`, scheduled cleanup every hour.
- `threadLocalSingleton` — per-thread `JdbcTemplate` holder; set by `JWTokenFilter`, consumed by `RoutineSql`.
- `bbvaSingleton` — BBVA session state.
...@@ -100,17 +100,21 @@ public class DriveService implements IDriveService { ...@@ -100,17 +100,21 @@ public class DriveService implements IDriveService {
@Override @Override
public ResponseEntity<?> getFile(String file_id, Boolean base64) { public ResponseEntity<?> getFile(String file_id, Boolean base64) {
try { try {
File file = googleConfig.getDrive().files().get(file_id).execute(); File file = googleConfig.getDrive().files().get(file_id).setFields("name,mimeType").execute();
InputStream inputStream = googleConfig.getDrive().files().get(file_id).executeMediaAsInputStream(); InputStream inputStream = googleConfig.getDrive().files().get(file_id).executeMediaAsInputStream();
String uudi = java.util.UUID.randomUUID().toString(); String uuid = java.util.UUID.randomUUID().toString();
String nameFile; String mimeType = file.getMimeType() != null ? file.getMimeType() : "";
boolean isHasExtension = false; String fileName = file.getName();
try {
nameFile = System.getProperty("java.io.tmpdir") + "/" + uudi + "." + file.getName().split("\\.")[1]; String extension;
isHasExtension = true; if (fileName.contains(".")) {
} catch (Exception e) { extension = fileName.substring(fileName.lastIndexOf('.') + 1);
nameFile = System.getProperty("java.io.tmpdir") + "/" + uudi + ".pdf"; } else {
extension = getExtensionFromMimeType(mimeType);
fileName = fileName + "." + extension;
} }
String nameFile = System.getProperty("java.io.tmpdir") + "/" + uuid + "." + extension;
java.io.File tempFile = new java.io.File(nameFile); java.io.File tempFile = new java.io.File(nameFile);
try (FileOutputStream fos = new FileOutputStream(tempFile)) { try (FileOutputStream fos = new FileOutputStream(tempFile)) {
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
...@@ -119,21 +123,29 @@ public class DriveService implements IDriveService { ...@@ -119,21 +123,29 @@ public class DriveService implements IDriveService {
fos.write(buffer, 0, bytesRead); fos.write(buffer, 0, bytesRead);
} }
} }
String viewUrl = getViewUrl(file_id, mimeType);
if (base64) { if (base64) {
String base64String = commonUtils.fileToBase64(tempFile); String base64String = commonUtils.fileToBase64(tempFile);
tempFile.delete(); // Eliminación inmediata en modo Base64 tempFile.delete();
return ResponseEntity.ok().body( JSONObject responseBody = new JSONObject()
new JSONObject() .put("base64", base64String)
.put("base64", base64String) .put("file_name", fileName)
.put("file_name", file.getName() + (isHasExtension ? "" : ".pdf")) .put("status", true)
.put("status", true) .put("message", "OK");
.put("message", "OK").toMap()); if (viewUrl != null) {
responseBody.put("view_url", viewUrl);
}
return ResponseEntity.ok().body(responseBody.toMap());
} else { } else {
Resource resource = commonUtils.fileToResource(tempFile); Resource resource = commonUtils.fileToResource(tempFile);
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"");
headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getName() + "\"");
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
if (viewUrl != null) {
headers.set("X-View-URL", viewUrl);
}
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
try { try {
...@@ -156,6 +168,47 @@ public class DriveService implements IDriveService { ...@@ -156,6 +168,47 @@ public class DriveService implements IDriveService {
} }
} }
private String getExtensionFromMimeType(String mimeType) {
switch (mimeType) {
case "application/pdf": return "pdf";
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document": return "docx";
case "application/msword": return "doc";
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": return "xlsx";
case "application/vnd.ms-excel": return "xls";
case "application/vnd.openxmlformats-officedocument.presentationml.presentation": return "pptx";
case "application/vnd.ms-powerpoint": return "ppt";
case "application/vnd.google-apps.document": return "docx";
case "application/vnd.google-apps.spreadsheet": return "xlsx";
case "application/vnd.google-apps.presentation": return "pptx";
case "image/jpeg": return "jpg";
case "image/png": return "png";
case "image/gif": return "gif";
case "text/plain": return "txt";
default: return "bin";
}
}
private String getViewUrl(String fileId, String mimeType) {
switch (mimeType) {
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
case "application/msword":
case "application/vnd.google-apps.document":
return "https://docs.google.com/document/d/" + fileId + "/edit";
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
case "application/vnd.ms-excel":
case "application/vnd.google-apps.spreadsheet":
return "https://docs.google.com/spreadsheets/d/" + fileId + "/edit";
case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
case "application/vnd.ms-powerpoint":
case "application/vnd.google-apps.presentation":
return "https://docs.google.com/presentation/d/" + fileId + "/edit";
case "application/pdf":
return "https://drive.google.com/file/d/" + fileId + "/view";
default:
return null;
}
}
@Override @Override
public byte[] getZip(JSONArray reqArrFiles) { public byte[] getZip(JSONArray reqArrFiles) {
String tempDirPath = System.getProperty("java.io.tmpdir") + "/" + java.util.UUID.randomUUID(); String tempDirPath = System.getProperty("java.io.tmpdir") + "/" + java.util.UUID.randomUUID();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment