first
This commit is contained in:
parent
969362e4cc
commit
744a2fa40b
5
.cursor/worktrees.json
Normal file
5
.cursor/worktrees.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"setup-worktree": [
|
||||
"npm install"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="AdditionalModuleElements">
|
||||
<content url="file://$MODULE_DIR$/../../../vega-hrm-report/build/generated/sources/annotationProcessor/java/main">
|
||||
<sourceFolder url="file://$MODULE_DIR$/../../../vega-hrm-report/build/generated/sources/annotationProcessor/java/main" isTestSource="false" generated="true" />
|
||||
</content>
|
||||
</component>
|
||||
</module>
|
||||
BIN
logs/vega-hrm-auth/error/app-error-2025-11-15.log.gz
Normal file
BIN
logs/vega-hrm-auth/error/app-error-2025-11-15.log.gz
Normal file
Binary file not shown.
BIN
logs/vega-hrm-auth/error/app-error-2025-11-17.log.gz
Normal file
BIN
logs/vega-hrm-auth/error/app-error-2025-11-17.log.gz
Normal file
Binary file not shown.
BIN
logs/vega-hrm-auth/info/app-info-2025-11-15.log.gz
Normal file
BIN
logs/vega-hrm-auth/info/app-info-2025-11-15.log.gz
Normal file
Binary file not shown.
BIN
logs/vega-hrm-auth/info/app-info-2025-11-17.log.gz
Normal file
BIN
logs/vega-hrm-auth/info/app-info-2025-11-17.log.gz
Normal file
Binary file not shown.
BIN
logs/vega-hrm-report/error/app-error-2025-11-24.log.gz
Normal file
BIN
logs/vega-hrm-report/error/app-error-2025-11-24.log.gz
Normal file
Binary file not shown.
BIN
logs/vega-hrm-report/info/app-info-2025-11-15.log.gz
Normal file
BIN
logs/vega-hrm-report/info/app-info-2025-11-15.log.gz
Normal file
Binary file not shown.
BIN
logs/vega-hrm-report/info/app-info-2025-11-17.log.gz
Normal file
BIN
logs/vega-hrm-report/info/app-info-2025-11-17.log.gz
Normal file
Binary file not shown.
BIN
logs/vega-hrm-report/info/app-info-2025-11-24.log.gz
Normal file
BIN
logs/vega-hrm-report/info/app-info-2025-11-24.log.gz
Normal file
Binary file not shown.
|
|
@ -22,18 +22,16 @@ dependencies {
|
|||
implementation 'org.springframework.boot:spring-boot-starter-log4j2:3.4.0'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation:3.4.0'
|
||||
implementation 'de.mkammerer:argon2-jvm:2.1'
|
||||
// Google API Services
|
||||
implementation "com.google.apis:google-api-services-youtube:v3-rev182-1.22.0"
|
||||
implementation("com.google.collections:google-collections:1.0")
|
||||
implementation("com.google.guava:guava:31.1-jre")
|
||||
implementation("com.google.apis:google-api-services-youtubeAnalytics:v2-rev272-1.25.0")
|
||||
implementation "com.google.http-client:google-http-client-jackson2:1.20.0"
|
||||
|
||||
// OAuth Client
|
||||
implementation 'com.google.apis:google-api-services-oauth2:v2-rev157-1.25.0'
|
||||
|
||||
// Google API Client (phiên bản mới)
|
||||
implementation 'com.google.api-client:google-api-client:1.34.1'
|
||||
implementation 'com.google.oauth-client:google-oauth-client-jetty:1.34.1'
|
||||
implementation 'com.google.api-client:google-api-client:1.32.1'
|
||||
// Google Collections
|
||||
implementation "com.google.collections:google-collections:1.0"
|
||||
implementation "com.google.http-client:google-http-client-jackson2:1.43.3"
|
||||
implementation 'com.google.guava:guava:32.1.3-jre'
|
||||
implementation 'com.google.code.gson:gson:2.11.0'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.38'
|
||||
implementation project(":vega-hrm-core")
|
||||
|
|
@ -50,11 +48,14 @@ configurations {
|
|||
exclude group: 'ch.qos.logback', module: 'logback-classic'
|
||||
|
||||
exclude group: 'com.google.guava', module: 'guava-jdk5'
|
||||
exclude group: 'com.google.collections', module: 'google-collections' // very old
|
||||
exclude group: 'com.google.collections', module: 'google-collections'
|
||||
|
||||
resolutionStrategy {
|
||||
// bắt buộc dùng guava hiện đại
|
||||
force "com.google.guava:guava:32.1.3-jre"
|
||||
// Bắt buộc dùng Guava mới nhất
|
||||
force 'com.google.guava:guava:32.1.3-jre'
|
||||
force 'com.google.api-client:google-api-client:1.34.1'
|
||||
force 'com.google.oauth-client:google-oauth-client-jetty:1.34.1'
|
||||
force 'com.google.http-client:google-http-client-jackson2:1.43.3'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.vega.hrm.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import org.springdoc.core.models.GroupedOpenApi;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class OpenApiConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI authOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("Vega HRM Auth API")
|
||||
.version("v1")
|
||||
.description("Tài liệu mô tả các API xác thực, phân quyền và tích hợp Google cho Vega HRM.")
|
||||
.contact(new Contact()
|
||||
.name("Vega HRM Backend Team")
|
||||
.email("backend@vegahrm.local"))
|
||||
.license(new License().name("Proprietary")));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi authGroupedOpenApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("auth")
|
||||
.packagesToScan("com.vega.hrm.controller")
|
||||
.pathsToMatch("/api/**")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("api/google/user")
|
||||
@RequestMapping({"api/google/user", "api/auth/google"})
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "Auth - Google", description = "Luồng OAuth2 với Google phục vụ xác thực người dùng")
|
||||
public class GoogleController {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.google.api.client.auth.oauth2.AuthorizationCodeFlow;
|
|||
import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
|
||||
import com.google.api.client.auth.oauth2.BearerToken;
|
||||
import com.google.api.client.auth.oauth2.Credential;
|
||||
import com.google.api.client.auth.oauth2.TokenResponse;
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
|
||||
|
|
@ -21,14 +20,12 @@ import com.vega.hrm.core.models.responses.BaseResponse;
|
|||
import com.vega.hrm.core.dto.GoogleOAuthConfig;
|
||||
import com.google.api.services.oauth2.model.Userinfo;
|
||||
import com.vega.hrm.core.repositories.UserGoogleTokenRepository;
|
||||
import com.vega.hrm.dto.CustomTokenResponse;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
|
|
@ -100,26 +97,40 @@ public class GoogleService {
|
|||
|
||||
String email = userInfo.getEmail();
|
||||
var userGoogleToken = userGoogleTokenRepository.findUserGoogleTokenByEmail(email);
|
||||
|
||||
if (userGoogleToken == null) {
|
||||
userGoogleToken = new UserGoogleToken();
|
||||
userGoogleToken.setId(UUID.randomUUID());
|
||||
userGoogleToken.setEmail(email);
|
||||
userGoogleToken.setCreatedAt(Instant.now());
|
||||
}
|
||||
|
||||
Instant now = Instant.now();
|
||||
Long expiresInSeconds = tokenResponse.getExpiresInSeconds();
|
||||
Object refreshTokenExpiresInObj = tokenResponse.get("refresh_token_expires_in");
|
||||
Long refreshTokenExpiresInSeconds = refreshTokenExpiresInObj != null
|
||||
? Long.valueOf(refreshTokenExpiresInObj.toString())
|
||||
: null;
|
||||
|
||||
userGoogleToken.setAccessToken(tokenResponse.getAccessToken());
|
||||
userGoogleToken.setRefreshToken(tokenResponse.getRefreshToken());
|
||||
userGoogleToken.setScope(tokenResponse.getScope());
|
||||
userGoogleToken.setExpiresIn(tokenResponse.getExpiresInSeconds());
|
||||
userGoogleToken.setRefreshTokenExpiresIn(tokenResponse.getExpiresInSeconds());
|
||||
userGoogleToken.setExpiresAt(Instant.now().plusSeconds(tokenResponse.getExpiresInSeconds()));
|
||||
userGoogleToken.setExpiresIn(expiresInSeconds);
|
||||
userGoogleToken.setRefreshTokenExpiresIn(refreshTokenExpiresInSeconds);
|
||||
userGoogleToken.setTokenType(tokenResponse.getTokenType());
|
||||
userGoogleToken.setUpdatedAt(now);
|
||||
|
||||
userGoogleToken.setRefreshTokenExpiresAt(Instant.now().plusSeconds(tokenResponse.get("refresh_token_expires_in") != null
|
||||
? Long.valueOf(tokenResponse.get("refresh_token_expires_in").toString())
|
||||
: null));
|
||||
userGoogleToken.setCreatedAt(Instant.now());
|
||||
userGoogleTokenRepository.save(userGoogleToken);
|
||||
if (expiresInSeconds != null) {
|
||||
userGoogleToken.setExpiresAt(now.plusSeconds(expiresInSeconds));
|
||||
} else if (userGoogleToken.getExpiresAt() == null) {
|
||||
userGoogleToken.setExpiresAt(now);
|
||||
}
|
||||
if (refreshTokenExpiresInSeconds != null) {
|
||||
userGoogleToken.setRefreshTokenExpiresAt(now.plusSeconds(refreshTokenExpiresInSeconds));
|
||||
} else if (userGoogleToken.getRefreshTokenExpiresAt() == null) {
|
||||
userGoogleToken.setRefreshTokenExpiresAt(now);
|
||||
}
|
||||
|
||||
userGoogleTokenRepository.save(userGoogleToken);
|
||||
tokenStore.storeToken(email, tokenResponse);
|
||||
return BaseResponse.success("00",true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,17 +44,15 @@ dependencies {
|
|||
implementation 'org.bouncycastle:bcpkix-jdk18on:1.80'
|
||||
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
|
||||
|
||||
// Google API Services
|
||||
implementation "com.google.apis:google-api-services-youtube:v3-rev182-1.22.0"
|
||||
implementation("com.google.collections:google-collections:1.0")
|
||||
implementation("com.google.guava:guava:20.0")
|
||||
implementation("com.google.apis:google-api-services-youtubeAnalytics:v2-rev272-1.25.0")
|
||||
implementation "com.google.http-client:google-http-client-jackson2:1.20.0"
|
||||
|
||||
// OAuth Client
|
||||
implementation "com.google.oauth-client:google-oauth-client-jetty:1.20.0"
|
||||
|
||||
// Google Collections
|
||||
implementation "com.google.collections:google-collections:1.0"
|
||||
// Google API Client (phiên bản mới)
|
||||
implementation 'com.google.api-client:google-api-client:1.34.1'
|
||||
implementation 'com.google.oauth-client:google-oauth-client-jetty:1.34.1'
|
||||
implementation "com.google.http-client:google-http-client-jackson2:1.43.3"
|
||||
implementation 'com.google.guava:guava:32.1.3-jre'
|
||||
implementation 'com.google.code.gson:gson:2.11.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-orgjson
|
||||
|
|
@ -68,5 +66,17 @@ configurations {
|
|||
|
||||
// Loại bỏ logging mặc định
|
||||
exclude group: 'ch.qos.logback', module: 'logback-classic'
|
||||
|
||||
// Loại bỏ các thư viện Guava/Google Collections cũ
|
||||
exclude group: 'com.google.guava', module: 'guava-jdk5'
|
||||
exclude group: 'com.google.collections', module: 'google-collections'
|
||||
|
||||
resolutionStrategy {
|
||||
// Bắt buộc dùng Guava mới nhất
|
||||
force 'com.google.guava:guava:32.1.3-jre'
|
||||
force 'com.google.api-client:google-api-client:1.34.1'
|
||||
force 'com.google.oauth-client:google-oauth-client-jetty:1.34.1'
|
||||
force 'com.google.http-client:google-http-client-jackson2:1.43.3'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,26 +2,38 @@ package com.vega.hrm.core.component;
|
|||
|
||||
import com.google.api.client.auth.oauth2.Credential;
|
||||
import com.google.api.client.auth.oauth2.TokenResponse;
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
|
||||
import com.google.api.client.http.HttpTransport;
|
||||
import com.google.api.client.json.JsonFactory;
|
||||
import com.vega.hrm.core.entities.UserGoogleToken;
|
||||
import com.vega.hrm.core.repositories.UserGoogleTokenRepository;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class TokenStore {
|
||||
private final ConcurrentHashMap<String, TokenResponse> tokenMap = new ConcurrentHashMap<>();
|
||||
private final UserGoogleTokenRepository userGoogleTokenRepository;
|
||||
|
||||
public void storeToken(String userId, TokenResponse tokenResponse) {
|
||||
tokenMap.put(userId, tokenResponse);
|
||||
}
|
||||
|
||||
public TokenResponse getTokenResponse(String userId) {
|
||||
return tokenMap.get(userId);
|
||||
TokenResponse tokenResponse = tokenMap.get(userId);
|
||||
if (tokenResponse == null) {
|
||||
tokenResponse = loadTokenFromDatabase(userId);
|
||||
if (tokenResponse != null) {
|
||||
tokenMap.put(userId, tokenResponse);
|
||||
}
|
||||
}
|
||||
return tokenResponse;
|
||||
}
|
||||
|
||||
public Credential buildCredential(HttpTransport transport, JsonFactory jsonFactory, String userId, String clientId, String clientSecret) {
|
||||
TokenResponse tokenResponse = tokenMap.get(userId);
|
||||
TokenResponse tokenResponse = getTokenResponse(userId);
|
||||
if (tokenResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -33,4 +45,24 @@ public class TokenStore {
|
|||
.build()
|
||||
.setFromTokenResponse(tokenResponse);
|
||||
}
|
||||
|
||||
private TokenResponse loadTokenFromDatabase(String userId) {
|
||||
UserGoogleToken userGoogleToken = userGoogleTokenRepository.findUserGoogleTokenByEmail(userId);
|
||||
if (userGoogleToken == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TokenResponse tokenResponse = new TokenResponse()
|
||||
.setAccessToken(userGoogleToken.getAccessToken())
|
||||
.setRefreshToken(userGoogleToken.getRefreshToken())
|
||||
.setScope(userGoogleToken.getScope())
|
||||
.setTokenType(userGoogleToken.getTokenType())
|
||||
.setExpiresInSeconds(userGoogleToken.getExpiresIn());
|
||||
|
||||
if (userGoogleToken.getRefreshTokenExpiresIn() != null) {
|
||||
tokenResponse.set("refresh_token_expires_in", userGoogleToken.getRefreshTokenExpiresIn());
|
||||
}
|
||||
|
||||
return tokenResponse;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,28 +22,23 @@ dependencies {
|
|||
implementation 'org.springframework.boot:spring-boot-starter-log4j2:3.4.0'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation:3.4.0'
|
||||
implementation 'de.mkammerer:argon2-jvm:2.1'
|
||||
implementation "com.google.apis:google-api-services-youtube:v3-rev182-1.22.0"
|
||||
implementation("com.google.collections:google-collections:1.0")
|
||||
implementation("com.google.guava:guava:20.0")
|
||||
implementation("com.google.apis:google-api-services-youtubeAnalytics:v2-rev272-1.25.0")
|
||||
implementation "com.google.http-client:google-http-client-jackson2:1.20.0"
|
||||
|
||||
// OAuth Client
|
||||
implementation "com.google.oauth-client:google-oauth-client-jetty:1.20.0"
|
||||
// Google API Services
|
||||
implementation 'com.google.apis:google-api-services-youtube:v3-rev182-1.22.0'
|
||||
implementation 'com.google.apis:google-api-services-youtubeAnalytics:v2-rev272-1.25.0'
|
||||
implementation 'com.google.apis:google-api-services-youtubereporting:v1-rev10-1.22.0'
|
||||
implementation 'com.google.apis:google-api-services-oauth2:v2-rev157-1.25.0'
|
||||
implementation 'com.google.oauth-client:google-oauth-client-jetty:1.34.1'
|
||||
implementation 'com.google.api-client:google-api-client:2.3.0'
|
||||
|
||||
// Google Collections
|
||||
implementation "com.google.collections:google-collections:1.0"
|
||||
// Google API Client (phiên bản mới để tương thích với YouTube libs)
|
||||
implementation 'com.google.api-client:google-api-client:1.34.1'
|
||||
implementation 'com.google.oauth-client:google-oauth-client-jetty:1.34.1'
|
||||
implementation 'com.google.http-client:google-http-client-jackson2:1.43.3'
|
||||
implementation 'com.google.guava:guava:32.1.3-jre'
|
||||
implementation 'com.google.code.gson:gson:2.11.0'
|
||||
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.38'
|
||||
implementation project(":vega-hrm-core")
|
||||
implementation 'jakarta.annotation:jakarta.annotation-api:2.1.1'
|
||||
|
||||
// YouTube Reporting API
|
||||
implementation "com.google.apis:google-api-services-youtubereporting:v1-rev10-1.22.0"
|
||||
|
||||
}
|
||||
|
||||
configurations {
|
||||
|
|
@ -53,5 +48,17 @@ configurations {
|
|||
|
||||
// Loại bỏ logging mặc định
|
||||
exclude group: 'ch.qos.logback', module: 'logback-classic'
|
||||
|
||||
// Loại bỏ các thư viện Guava/Google Collections cũ
|
||||
exclude group: 'com.google.guava', module: 'guava-jdk5'
|
||||
exclude group: 'com.google.collections', module: 'google-collections'
|
||||
|
||||
resolutionStrategy {
|
||||
// Bắt buộc dùng Guava mới nhất
|
||||
force 'com.google.guava:guava:32.1.3-jre'
|
||||
force 'com.google.api-client:google-api-client:1.34.1'
|
||||
force 'com.google.oauth-client:google-oauth-client-jetty:1.34.1'
|
||||
force 'com.google.http-client:google-http-client-jackson2:1.43.3'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.vega.hrm.report.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import org.springdoc.core.models.GroupedOpenApi;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class OpenApiConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI reportOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("Vega HRM Report API")
|
||||
.version("v1")
|
||||
.description("Danh mục API phục vụ trích xuất báo cáo và đồng bộ dữ liệu YouTube/Google cho Vega HRM.")
|
||||
.contact(new Contact()
|
||||
.name("Vega HRM Backend Team")
|
||||
.email("backend@vegahrm.local"))
|
||||
.license(new License().name("Proprietary")));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi reportGroupedOpenApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("report")
|
||||
.packagesToScan("com.vega.hrm.report.controller")
|
||||
.pathsToMatch("/api/**")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6,15 +6,26 @@ import com.vega.hrm.core.component.TokenStore;
|
|||
import com.vega.hrm.core.dto.GoogleOAuthConfig;
|
||||
import com.vega.hrm.core.models.responses.BaseResponse;
|
||||
import com.vega.hrm.report.request.GetDragRevenueRequest;
|
||||
import com.vega.hrm.report.request.GetRevenueRequest;
|
||||
import com.vega.hrm.report.response.ReportTypeDto;
|
||||
import com.vega.hrm.report.response.RevenueDataDto;
|
||||
import com.vega.hrm.report.serivce.CreateReportingJobService;
|
||||
import com.vega.hrm.report.serivce.YouTubeReportService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
|
|
@ -24,19 +35,29 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
public class ReportGoogleController {
|
||||
|
||||
private final CreateReportingJobService createReportingJob;
|
||||
private final YouTubeReportService youTubeReportService;
|
||||
private final GoogleOAuthConfig googleOAuthConfig;
|
||||
private final TokenStore tokenStore;
|
||||
|
||||
@GetMapping("/youtube/demo")
|
||||
@Operation(summary = "Khởi tạo job demo YouTube", description = "Thực thi job báo cáo mẫu với cấu hình được lưu trữ trong hệ thống.")
|
||||
public ResponseEntity<BaseResponse<Boolean>> getRevenue(GetDragRevenueRequest getDragRevenueRequest)
|
||||
public ResponseEntity<BaseResponse<Boolean>> getRevenue(@RequestBody GetDragRevenueRequest getDragRevenueRequest)
|
||||
throws GeneralSecurityException, IOException {
|
||||
var tokenStore = new TokenStore();
|
||||
String email = getDragRevenueRequest != null ? getDragRevenueRequest.getEmail() : null;
|
||||
if (!StringUtils.hasText(email)) {
|
||||
return ResponseEntity.ok(BaseResponse.invalid("Email không được để trống"));
|
||||
}
|
||||
|
||||
if (tokenStore.getTokenResponse(email) == null) {
|
||||
return ResponseEntity.ok(BaseResponse.invalid("Không tìm thấy token Google cho email đã cung cấp"));
|
||||
}
|
||||
|
||||
createReportingJob.createJobWithStoredCredential(
|
||||
tokenStore,
|
||||
googleOAuthConfig,
|
||||
GoogleNetHttpTransport.newTrustedTransport(),
|
||||
JacksonFactory.getDefaultInstance(),
|
||||
"default",
|
||||
email,
|
||||
"channel_monetized_playback_a1", // report type
|
||||
"vega-monetization-daily" // job name
|
||||
);
|
||||
|
|
@ -48,4 +69,59 @@ public class ReportGoogleController {
|
|||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/report-types")
|
||||
@Operation(summary = "Lấy danh sách Report Types", description = "Lấy tất cả các loại báo cáo có sẵn từ YouTube Reporting API.")
|
||||
public ResponseEntity<BaseResponse<List<ReportTypeDto>>> getReportTypes(
|
||||
@Parameter(description = "Email của người dùng đã xác thực với Google")
|
||||
@RequestParam String email
|
||||
) throws GeneralSecurityException, IOException {
|
||||
if (!StringUtils.hasText(email)) {
|
||||
return ResponseEntity.ok(BaseResponse.invalid("Email không được để trống"));
|
||||
}
|
||||
|
||||
if (tokenStore.getTokenResponse(email) == null) {
|
||||
return ResponseEntity.ok(BaseResponse.invalid("Không tìm thấy token Google cho email đã cung cấp"));
|
||||
}
|
||||
|
||||
List<ReportTypeDto> reportTypes = youTubeReportService.getReportTypes(
|
||||
email,
|
||||
GoogleNetHttpTransport.newTrustedTransport(),
|
||||
JacksonFactory.getDefaultInstance()
|
||||
);
|
||||
|
||||
return ResponseEntity.ok(
|
||||
BaseResponse.<List<ReportTypeDto>>builder()
|
||||
.code("00")
|
||||
.message("Lấy danh sách report types thành công")
|
||||
.data(reportTypes)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@PostMapping("/revenue")
|
||||
@Operation(summary = "Lấy dữ liệu doanh thu", description = "Lấy dữ liệu doanh thu ước tính từ YouTube Analytics API theo khoảng thời gian.")
|
||||
public ResponseEntity<BaseResponse<List<RevenueDataDto>>> getRevenueData(
|
||||
@Valid @RequestBody GetRevenueRequest request
|
||||
) throws GeneralSecurityException, IOException {
|
||||
if (tokenStore.getTokenResponse(request.getEmail()) == null) {
|
||||
return ResponseEntity.ok(BaseResponse.invalid("Không tìm thấy token Google cho email đã cung cấp"));
|
||||
}
|
||||
|
||||
List<RevenueDataDto> revenueData = youTubeReportService.getRevenue(
|
||||
request.getEmail(),
|
||||
request.getStartDate(),
|
||||
request.getEndDate(),
|
||||
GoogleNetHttpTransport.newTrustedTransport(),
|
||||
JacksonFactory.getDefaultInstance()
|
||||
);
|
||||
|
||||
return ResponseEntity.ok(
|
||||
BaseResponse.<List<RevenueDataDto>>builder()
|
||||
.code("00")
|
||||
.message("Lấy dữ liệu doanh thu thành công")
|
||||
.data(revenueData)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.vega.hrm.report.request;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class GetRevenueRequest {
|
||||
@NotBlank(message = "Email không được để trống")
|
||||
private String email;
|
||||
|
||||
@NotBlank(message = "Ngày bắt đầu không được để trống")
|
||||
private String startDate; // format: YYYY-MM-DD
|
||||
|
||||
@NotBlank(message = "Ngày kết thúc không được để trống")
|
||||
private String endDate; // format: YYYY-MM-DD
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.vega.hrm.report.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ReportTypeDto {
|
||||
private String id;
|
||||
private String name;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.vega.hrm.report.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RevenueDataDto {
|
||||
private String date;
|
||||
private Double estimatedRevenue;
|
||||
}
|
||||
|
||||
|
|
@ -15,11 +15,9 @@ import com.google.api.services.youtubereporting.model.ReportType;
|
|||
import com.vega.hrm.core.component.TokenStore;
|
||||
import com.vega.hrm.core.dto.GoogleOAuthConfig;
|
||||
import com.vega.hrm.core.helpers.LogHelper;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
package com.vega.hrm.report.serivce;
|
||||
|
||||
import com.google.api.client.auth.oauth2.Credential;
|
||||
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
|
||||
import com.google.api.client.http.HttpTransport;
|
||||
import com.google.api.client.json.JsonFactory;
|
||||
import com.google.api.services.youtubeAnalytics.v2.YouTubeAnalytics;
|
||||
import com.google.api.services.youtubeAnalytics.v2.model.QueryResponse;
|
||||
import com.google.api.services.youtubereporting.YouTubeReporting;
|
||||
import com.google.api.services.youtubereporting.model.ListReportTypesResponse;
|
||||
import com.google.api.services.youtubereporting.model.ReportType;
|
||||
import com.vega.hrm.core.component.TokenStore;
|
||||
import com.vega.hrm.core.dto.GoogleOAuthConfig;
|
||||
import com.vega.hrm.core.helpers.LogHelper;
|
||||
import com.vega.hrm.report.response.ReportTypeDto;
|
||||
import com.vega.hrm.report.response.RevenueDataDto;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class YouTubeReportService {
|
||||
private static final String APPLICATION_NAME = "vega-report";
|
||||
private final TokenStore tokenStore;
|
||||
private final GoogleOAuthConfig googleOAuthConfig;
|
||||
|
||||
/**
|
||||
* Lấy danh sách tất cả report types từ YouTube Reporting API
|
||||
*/
|
||||
public List<ReportTypeDto> getReportTypes(String email, HttpTransport httpTransport, JsonFactory jsonFactory)
|
||||
throws IOException {
|
||||
Credential credential = tokenStore.buildCredential(
|
||||
httpTransport,
|
||||
jsonFactory,
|
||||
email,
|
||||
googleOAuthConfig.clientId,
|
||||
googleOAuthConfig.clientSecret
|
||||
);
|
||||
|
||||
if (credential == null) {
|
||||
throw new IllegalStateException("Không tìm thấy token Google cho email: " + email);
|
||||
}
|
||||
|
||||
YouTubeReporting youtubeReporting = new YouTubeReporting.Builder(httpTransport, jsonFactory, credential)
|
||||
.setApplicationName(APPLICATION_NAME)
|
||||
.build();
|
||||
|
||||
try {
|
||||
ListReportTypesResponse reportTypesListResponse = youtubeReporting.reportTypes().list().execute();
|
||||
List<ReportType> reportTypeList = reportTypesListResponse.getReportTypes();
|
||||
|
||||
if (reportTypeList == null || reportTypeList.isEmpty()) {
|
||||
LogHelper.info("Không tìm thấy report types nào.");
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
List<ReportTypeDto> result = new ArrayList<>();
|
||||
for (ReportType reportType : reportTypeList) {
|
||||
result.add(ReportTypeDto.builder()
|
||||
.id(reportType.getId())
|
||||
.name(reportType.getName())
|
||||
.build());
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (GoogleJsonResponseException e) {
|
||||
LogHelper.error("Lỗi khi lấy report types: " + e.getDetails().getMessage());
|
||||
throw new IOException("Không thể lấy danh sách report types: " + e.getDetails().getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy dữ liệu doanh thu từ YouTube Analytics API
|
||||
*/
|
||||
public List<RevenueDataDto> getRevenue(String email, String startDate, String endDate,
|
||||
HttpTransport httpTransport, JsonFactory jsonFactory)
|
||||
throws IOException {
|
||||
Credential credential = tokenStore.buildCredential(
|
||||
httpTransport,
|
||||
jsonFactory,
|
||||
email,
|
||||
googleOAuthConfig.clientId,
|
||||
googleOAuthConfig.clientSecret
|
||||
);
|
||||
|
||||
if (credential == null) {
|
||||
throw new IllegalStateException("Không tìm thấy token Google cho email: " + email);
|
||||
}
|
||||
|
||||
YouTubeAnalytics youtubeAnalytics = new YouTubeAnalytics.Builder(httpTransport, jsonFactory, credential)
|
||||
.setApplicationName(APPLICATION_NAME)
|
||||
.build();
|
||||
|
||||
try {
|
||||
QueryResponse queryResponse = youtubeAnalytics.reports().query()
|
||||
.setIds("channel==MINE")
|
||||
.setStartDate(startDate)
|
||||
.setEndDate(endDate)
|
||||
.setMetrics("estimatedRevenue")
|
||||
.setDimensions("day")
|
||||
.execute();
|
||||
|
||||
List<RevenueDataDto> result = new ArrayList<>();
|
||||
|
||||
if (queryResponse.getRows() != null) {
|
||||
for (List<Object> row : queryResponse.getRows()) {
|
||||
if (row.size() >= 2) {
|
||||
String date = row.get(0).toString();
|
||||
Double revenue = row.get(1) instanceof Number
|
||||
? ((Number) row.get(1)).doubleValue()
|
||||
: Double.parseDouble(row.get(1).toString());
|
||||
|
||||
result.add(RevenueDataDto.builder()
|
||||
.date(date)
|
||||
.estimatedRevenue(revenue)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.info("Đã lấy được " + result.size() + " dòng dữ liệu doanh thu.");
|
||||
return result;
|
||||
|
||||
} catch (GoogleJsonResponseException e) {
|
||||
LogHelper.error("Lỗi khi lấy dữ liệu revenue: " + e.getDetails().getMessage());
|
||||
throw new IOException("Không thể lấy dữ liệu doanh thu: " + e.getDetails().getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user