Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
96ef701
Added group creation logic and improved security in the sandbox
IrminDev Jul 19, 2026
bc2d4aa
Redesigned the scope check, added validation and improved the
IrminDev Jul 22, 2026
2cbebc0
Added edit assignment, feedback and grading features, and notification
IrminDev Jul 26, 2026
529a38e
Added missing files
IrminDev Jul 26, 2026
780e2d2
Updatex migrations definitions and fixed some warnings
IrminDev Jul 26, 2026
4939214
Minor fixes, performance improved and added more examples to test
IrminDev Jul 26, 2026
3e1e141
Added graphify
IrminDev Jul 27, 2026
446473b
Added graphify
IrminDev Jul 29, 2026
c646d00
Updated unexpected behavior for some business rules
IrminDev Jul 29, 2026
3902882
docs(metrics): add teacher metrics business rules and API contract
JohannTF Jul 31, 2026
4792eb6
feat(metrics): add aggregation queries and projection DTOs
JohannTF Jul 31, 2026
1598e72
feat(metrics): expose teacher metrics endpoints for groups and assign…
JohannTF Jul 31, 2026
e5c0491
test(groups): cover lifecycle rules for rejoin, restore, archive and …
JohannTF Jul 31, 2026
27e230d
fix(groups): limit roster response to non-sensitive student fields
JohannTF Jul 31, 2026
a686d2c
fix(groups): ratify and apply pending group lifecycle rules.
JohannTF Aug 4, 2026
3367916
fix(security): extend group-owner authorization to assignments and gr…
JohannTF Aug 4, 2026
64c7d2c
Minor fixes to fir better with the requirements
IrminDev Aug 4, 2026
d60b49c
Merge branch 'feat/teacher-metrics' into feature/groups
JohannTF Aug 4, 2026
dc3ffec
chore(graphify): update knowledge graph after groups/metrics changes
JohannTF Aug 4, 2026
e4eae6a
Feature/frontend redesign (#63)
IrminDev Aug 11, 2026
ec131dc
Added connection between frontend and backend for teacher's features.
IrminDev Aug 13, 2026
6d2aaed
Updated wrong constraints and added data cleanup for MinIO
IrminDev Aug 17, 2026
6ac7f36
Feature/frontend redesign (#64)
IrminDev Aug 18, 2026
a7fd8c1
Fixed issues in the frontend connection
IrminDev Aug 19, 2026
261d166
Improved and implemented an initial functional version for the student
IrminDev Aug 22, 2026
a201c67
Added admin controls
IrminDev Aug 24, 2026
cce42be
Revert "Added admin controls"
IrminDev Sep 2, 2026
92330ca
FIxed minor issues
IrminDev Sep 2, 2026
de8f1ba
Feat/student metrics (#87)
IrminDev Sep 4, 2026
bbcdd23
Fixed minor issues
IrminDev Sep 4, 2026
4e38beb
Added admin controls again, student metrics in frontend and improved
IrminDev Sep 5, 2026
5354b39
Fixed test issues
IrminDev Sep 6, 2026
47be0f6
Wrap StudentDashboardPage in ProtectedRoute
IrminDev Sep 6, 2026
f73815b
Potential fix for pull request finding 'CodeQL / Implicit narrowing c…
IrminDev Sep 6, 2026
8f9748a
Revise container security settings and limits
IrminDev Sep 6, 2026
45d5792
Change validation from NotNull to NotBlank
IrminDev Sep 6, 2026
4aa28e7
feat(frontend): separate managed groups from enrolled group
JohannTF Sep 13, 2026
7bb9826
feat(admin): restore revoked groups when CREATE_GROUP is regained
JohannTF Sep 14, 2026
92476f4
fix(teacher): keep archived groups gradable and mark their state
JohannTF Sep 19, 2026
ea92d65
feat(groups): keep deleted groups consultable for their owner
JohannTF Sep 19, 2026
09fa7bf
fix(teacher): stop panels from clipping row and hero action menus
JohannTF Sep 19, 2026
244b6d9
revert(teacher): make group and assignment deletion terminal in the UI
JohannTF Sep 27, 2026
f03f68c
fix(assignments): replace examples in place to respect the order uniq…
JohannTF Sep 27, 2026
f1df115
Added codebase for AI assistant
IrminDev Sep 27, 2026
b0e7d27
feat(teacher): open assignment status drawer from the card and after …
JohannTF Sep 27, 2026
60f1edc
Added AI assistant functionality
IrminDev Sep 28, 2026
7117bf8
Added gemini 3.8 flash as default
IrminDev Sep 28, 2026
6dded11
fix(worker): support Windows hosts via DOCKER_HOST and optional POSIX…
rodolfo-rgb Sep 28, 2026
f196dea
feat(admin): open user detail by clicking anywhere on the row
rodolfo-rgb Sep 28, 2026
85209e5
fix(teacher): open action menus upward when they would overflow the v…
rodolfo-rgb Sep 28, 2026
e38a56a
feat(student): keep editor drafts and restore delivered code in the w…
rodolfo-rgb Sep 28, 2026
fc64140
fix(assignments): keep grades when a resubmitted test suite is identical
rodolfo-rgb Sep 28, 2026
1a39c3e
fix(admin): record user creation and CSV uploads in the audit log
rodolfo-rgb Sep 28, 2026
10a680e
fix(api): answer 404 instead of 500 for unknown routes
rodolfo-rgb Sep 28, 2026
b0a13fb
fix(auth,admin): accurate reset link expiry, field-level errors, role…
rodolfo-rgb Sep 28, 2026
3e96bc5
revert(worker): keep Windows-only Docker host and permission tweaks o…
rodolfo-rgb Sep 28, 2026
0d1710e
Fixes and added diagrams
IrminDev Oct 4, 2026
b1422a3
Added AI usage analytics
IrminDev Oct 5, 2026
f184b00
Improved AI usage metrics
IrminDev Oct 8, 2026
6485621
Updated UI and AI provider calls
IrminDev Oct 11, 2026
e3f4e7e
Removed unused tests
IrminDev Oct 11, 2026
68a9367
feat(auth): secure CSV progress WebSocket with ticketed handshake
JohannTF Oct 12, 2026
272b164
fix(assignments): avoid duplicate example order on update
JohannTF Oct 13, 2026
1212791
feat(frontend): dock AI assistant and rework teacher analytics
JohannTF Oct 13, 2026
8a734a2
feat(frontend): clarify submission status and replacement flow, anima…
JohannTF Oct 13, 2026
c575d85
Added tests for ai assistant
IrminDev Oct 18, 2026
747391f
Minor UI changes
IrminDev Oct 18, 2026
f9618a3
Minor fixes for assistant behavior
IrminDev Oct 19, 2026
c00dbb9
chore: merge origin/main into AI assistant
IrminDev Oct 19, 2026
cbbfc46
Removed frontend tests
IrminDev Oct 19, 2026
1a1e351
Fixed java TLE errors
IrminDev Oct 20, 2026
b7ccf1b
Potential fix for pull request finding 'CodeQL / Implicit narrowing c…
IrminDev Oct 25, 2026
f54f84e
Fixed llm guardrails
IrminDev Oct 29, 2026
056a7d1
Merge backup/no-student-stats to recover work from main
JohannTF Nov 1, 2026
4b8a0a0
chore(deploy): add Google Cloud deployment setup
JohannTF Nov 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
20 changes: 20 additions & 0 deletions .gcloudignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
*
!deploy/
!deploy/**
!codehive-backend/
!codehive-backend/**
!codehive-worker/
!codehive-worker/**

.env
.env.*
**/.env
**/.env.*
**/.DS_Store
**/build/
**/bin/
**/.gradle/
**/graphify-out/
**/*.drawio
**/*.graphml
**/__pycache__/
8 changes: 8 additions & 0 deletions codehive-backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*
!gradlew
!settings.gradle.kts
!build.gradle.kts
!gradle/
!src/
src/test/
**/.DS_Store
16 changes: 16 additions & 0 deletions codehive-backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM eclipse-temurin:21-jdk AS build
WORKDIR /workspace
COPY gradlew settings.gradle.kts build.gradle.kts ./
COPY gradle gradle
COPY src src
RUN sh ./gradlew --no-daemon bootJar

FROM eclipse-temurin:21-jre
RUN useradd --uid 10001 --create-home codehive
WORKDIR /app
COPY --from=build /workspace/build/libs/*-SNAPSHOT.jar app.jar
# Entities store LocalDateTime and the browser reads those values as local time.
ENV TZ=America/Mexico_City
USER 10001
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "app.jar"]
53 changes: 52 additions & 1 deletion codehive-backend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-websocket")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.ai:spring-ai-starter-model-google-genai:1.1.8")
// Public custom HTTP transport support is needed to prevent hidden POST retries.
implementation("com.google.genai:google-genai:1.76.0")
developmentOnly("org.springframework.boot:spring-boot-devtools")
developmentOnly("org.springframework.boot:spring-boot-docker-compose")

Expand Down Expand Up @@ -67,7 +70,6 @@ dependencies {

tasks.withType<Test> {
useJUnitPlatform()
finalizedBy(tasks.jacocoTestReport) // Generate coverage report after tests

// Show test results in console
testLogging {
Expand All @@ -94,6 +96,55 @@ tasks.withType<Test> {
}))
}

val postgresAssistantTest by tasks.registering(Test::class) {
description = "Runs assistant integration checks against an isolated PostgreSQL database"
group = "verification"
testClassesDirs = sourceSets.test.get().output.classesDirs
classpath = sourceSets.test.get().runtimeClasspath
filter {
includeTestsMatching("*AssistantTransactionServiceIntegrationTest")
includeTestsMatching("*AssistantControllerIntegrationTest")
includeTestsMatching("*AssistantPostgresSchemaIntegrationTest")
includeTestsMatching("*AssistantUsagePostgresIntegrationTest")
}
val databaseUrl = providers.environmentVariable("CODEHIVE_ASSISTANT_TEST_DATABASE_URL")
val databasePassword = providers.environmentVariable("CODEHIVE_ASSISTANT_TEST_DATABASE_PASSWORD")
doFirst {
require(databaseUrl.isPresent && databasePassword.isPresent) {
"Set CODEHIVE_ASSISTANT_TEST_DATABASE_URL and CODEHIVE_ASSISTANT_TEST_DATABASE_PASSWORD for a disposable PostgreSQL database"
}
}
systemProperty("spring.datasource.url", databaseUrl.orNull ?: "")
systemProperty("spring.datasource.username", providers.environmentVariable("CODEHIVE_ASSISTANT_TEST_DATABASE_USERNAME").orNull ?: "postgres")
systemProperty("spring.datasource.password", databasePassword.orNull ?: "")
systemProperty("spring.datasource.driver-class-name", "org.postgresql.Driver")
systemProperty("spring.jpa.database-platform", "org.hibernate.dialect.PostgreSQLDialect")
systemProperty("spring.jpa.hibernate.ddl-auto", "create-drop")
systemProperty("spring.flyway.enabled", "false")
systemProperty("spring.ai.model.chat", "none")
}

tasks.test {
finalizedBy(tasks.jacocoTestReport) // Generate coverage report after standard tests only
useJUnitPlatform {
excludeTags("postgres-assistant", "live-assistant")
}
}

val liveAssistantTest by tasks.registering(Test::class) {
description = "Runs synthetic assistant safety fixtures against explicitly configured Gemini"
group = "verification"
testClassesDirs = sourceSets.test.get().output.classesDirs
classpath = sourceSets.test.get().runtimeClasspath
filter.includeTestsMatching("*LiveAssistantQualificationTest")
systemProperty("spring.ai.model.chat", "google-genai")
systemProperty("assistant.model-timeout-seconds",
providers.environmentVariable("ASSISTANT_MODEL_TIMEOUT_SECONDS").orNull ?: "20")
providers.environmentVariable("CODEHIVE_ASSISTANT_LIVE_MODEL").orNull?.let {
systemProperty("spring.ai.google.genai.chat.options.model", it)
}
}

tasks.jacocoTestReport {
dependsOn(tasks.test) // Tests are required to run before generating the report
reports {
Expand Down
5 changes: 3 additions & 2 deletions codehive-backend/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ services:
- "15672:15672"

minio:
image: "minio/minio:RELEASE.2025-09-07T16-13-09Z-cpuv1"
image: "cgr.dev/chainguard/minio:latest"
container_name: "minio"
user: root
command: 'server /data --console-address ":9001"'
ports:
- "9000:9000"
Expand All @@ -37,7 +38,7 @@ services:
retries: 5

minio-init:
image: "minio/mc:latest"
image: "cgr.dev/chainguard/minio-client:latest-dev"
container_name: "minio-init"
depends_on:
minio:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.github.codehive.config;

import java.time.Clock;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AssistantFoundationConfig {
@Bean("assistantClock")
Clock assistantClock() {
return Clock.systemUTC();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.github.codehive.config;

import java.io.IOException;
import java.time.Duration;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.ai.model.google.genai.autoconfigure.chat.GoogleGenAiConnectionProperties;
import org.springframework.util.StringUtils;
import org.springframework.retry.support.RetryTemplate;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.genai.Client;
import com.google.genai.types.HttpOptions;
import com.google.genai.types.HttpRetryOptions;
import com.google.genai.types.ClientOptions;

import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.MediaType;
import okio.BufferedSink;

/** Spring's retry setting does not control retries inside the Google SDK. */
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.ai.model.chat", havingValue = "google-genai")
public class AssistantGoogleGenAiConfiguration {
@Bean
public RetryTemplate assistantModelRetryTemplate() {
return RetryTemplate.builder().maxAttempts(1).build();
}

@Bean(destroyMethod = "close")
public Client assistantGoogleGenAiClient(Environment environment) throws IOException {
var properties = Binder.get(environment)
.bind(GoogleGenAiConnectionProperties.CONFIG_PREFIX, GoogleGenAiConnectionProperties.class)
.orElseGet(GoogleGenAiConnectionProperties::new);
String baseUrl = environment.getProperty("assistant.google-genai.base-url", "");
HttpOptions.Builder httpOptions = HttpOptions.builder()
.retryOptions(HttpRetryOptions.builder().attempts(1).build());
if (StringUtils.hasText(baseUrl)) httpOptions.baseUrl(baseUrl);
return clientBuilder(properties).httpOptions(httpOptions.build())
.clientOptions(ClientOptions.builder().customHttpClient(singleAttemptHttpClient()).build())
.build();
}

private OkHttpClient singleAttemptHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(Duration.ZERO).readTimeout(Duration.ZERO).writeTimeout(Duration.ZERO)
.retryOnConnectionFailure(false).followRedirects(false).followSslRedirects(false)
.addInterceptor(chain -> {
var request = chain.request();
var body = request.body();
if (body != null) {
// OkHttp can otherwise replay a POST after a 503 with Retry-After: 0.
RequestBody singleAttempt = new RequestBody() {
@Override public MediaType contentType() { return body.contentType(); }
@Override public long contentLength() throws IOException { return body.contentLength(); }
@Override public void writeTo(BufferedSink sink) throws IOException { body.writeTo(sink); }
@Override public boolean isOneShot() { return true; }
};
request = request.newBuilder().method(request.method(), singleAttempt).build();
}
return chain.proceed(request);
}).build();
}

private Client.Builder clientBuilder(GoogleGenAiConnectionProperties properties) throws IOException {
Client.Builder builder = Client.builder();
boolean hasVertexConfig = StringUtils.hasText(properties.getProjectId())
&& StringUtils.hasText(properties.getLocation());
if (!properties.isVertexAi() && StringUtils.hasText(properties.getApiKey())) {
return builder.apiKey(properties.getApiKey()).vertexAI(false);
}
if (!hasVertexConfig) {
throw new IllegalStateException("Google GenAI requires an API key or Vertex AI project and location");
}
builder.project(properties.getProjectId()).location(properties.getLocation()).vertexAI(true);
if (properties.getCredentialsUri() != null) {
try (var input = properties.getCredentialsUri().getInputStream()) {
builder.credentials(GoogleCredentials.fromStream(input));
}
}
return builder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

import io.minio.MinioClient;

Expand All @@ -17,13 +18,18 @@ public class MinioConfig {
@Value("${minio.secretKey}")
private String minioSecretKey;

@Value("${minio.region:}")
private String minioRegion;

@Bean
MinioClient minioClient() {
return MinioClient.builder()
MinioClient.Builder builder = MinioClient.builder()
.endpoint(minioUrl)
.credentials(
minioAccessKey,
minioSecretKey)
.build();
.credentials(minioAccessKey, minioSecretKey);
// Skips GetBucketLocation, which Cloud Storage denies without storage.buckets.get.
if (StringUtils.hasText(minioRegion)) {
builder.region(minioRegion);
}
return builder.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

import com.github.codehive.websocket.CsvProgressWebSocketHandler;
import com.github.codehive.websocket.WebSocketTicketHandshakeInterceptor;

@Configuration
@EnableWebSocket
Expand All @@ -16,14 +17,18 @@ public class WebSocketConfig implements WebSocketConfigurer {
private String allowedOrigins;

private final CsvProgressWebSocketHandler csvProgressHandler;
private final WebSocketTicketHandshakeInterceptor ticketInterceptor;

public WebSocketConfig(CsvProgressWebSocketHandler csvProgressHandler) {
public WebSocketConfig(CsvProgressWebSocketHandler csvProgressHandler,
WebSocketTicketHandshakeInterceptor ticketInterceptor) {
this.csvProgressHandler = csvProgressHandler;
this.ticketInterceptor = ticketInterceptor;
}

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(csvProgressHandler, "/ws/csv-progress")
.addInterceptors(ticketInterceptor)
.setAllowedOrigins(allowedOrigins.split("\\s*,\\s*"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.github.codehive.controller;
import java.util.UUID;
import org.springframework.web.bind.annotation.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.access.prepost.PreAuthorize;
import com.github.codehive.service.assistant.AssistantUsageService;
import com.github.codehive.service.assistant.AssistantUsageService.Audience;
import com.github.codehive.repository.AssistantUsageRepository.Dimension;
import com.github.codehive.model.request.assistant.AssistantUsageQuery;
import com.github.codehive.model.dto.assistant.usage.AssistantUsageDTO.*;
import com.github.codehive.model.response.SuccessResponse;
@io.swagger.v3.oas.annotations.tags.Tag(name = "AdminAssistantUsage", description = "Content-free assistant usage, historical ledger counts and measured provider work")
@io.swagger.v3.oas.annotations.responses.ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Usage retrieved"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid dates, page or filter"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Caller lacks ownership, role or required scope"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "Requested resource not found")
})
@RestController
@PreAuthorize("hasAuthority('ADMIN') and hasAuthority('CHECK_ANALYTICS')")
public class AdminAssistantUsageController {
private final AssistantUsageService usage;
public AdminAssistantUsageController(AssistantUsageService usage) { this.usage=usage; }
@GetMapping("/api/admin/assistant-usage")
@io.swagger.v3.oas.annotations.Operation(summary = "Get global AI usage")
public SuccessResponse<Summary> summary(@org.springdoc.core.annotations.ParameterObject @ModelAttribute AssistantUsageQuery query, Authentication auth) {
var access=usage.authorize(Audience.ADMIN,auth.getName(),null,null,null,query,false);
return new SuccessResponse<>("Assistant usage retrieved",usage.summary(access));
}
@GetMapping("/api/admin/assistant-usage/users")
@PreAuthorize("hasAuthority('ADMIN') and hasAuthority('CHECK_ANALYTICS') and hasAuthority('VIEW_USERS')")
@io.swagger.v3.oas.annotations.Operation(summary = "List AI usage by user")
public SuccessResponse<Breakdown> users(@org.springdoc.core.annotations.ParameterObject @ModelAttribute AssistantUsageQuery query, Authentication auth) {
var access=usage.authorize(Audience.ADMIN,auth.getName(),null,null,null,query,true);
return new SuccessResponse<>("Assistant usage retrieved",usage.rows(access,Dimension.USERS,query));
}
@GetMapping("/api/admin/users/{userId}/assistant-usage")
@PreAuthorize("hasAuthority('ADMIN') and hasAuthority('CHECK_ANALYTICS') and hasAuthority('VIEW_USERS')")
@io.swagger.v3.oas.annotations.Operation(summary = "Get user AI usage")
public SuccessResponse<Summary> userSummary(@PathVariable UUID userId, @org.springdoc.core.annotations.ParameterObject @ModelAttribute AssistantUsageQuery query, Authentication auth) {
var access=usage.authorize(Audience.ADMIN,auth.getName(),userId,null,null,query,true);
return new SuccessResponse<>("Assistant usage retrieved",usage.summary(access));
}
@GetMapping("/api/admin/users/{userId}/assistant-usage/groups")
@PreAuthorize("hasAuthority('ADMIN') and hasAuthority('CHECK_ANALYTICS') and hasAuthority('VIEW_USERS')")
@io.swagger.v3.oas.annotations.Operation(summary = "List user AI usage by group")
public SuccessResponse<Breakdown> userGroups(@PathVariable UUID userId, @org.springdoc.core.annotations.ParameterObject @ModelAttribute AssistantUsageQuery query, Authentication auth) {
var access=usage.authorize(Audience.ADMIN,auth.getName(),userId,null,null,query,true);
return new SuccessResponse<>("Assistant usage retrieved",usage.rows(access,Dimension.GROUPS,query));
}
@GetMapping("/api/admin/users/{userId}/assistant-usage/groups/{groupId}/assignments")
@PreAuthorize("hasAuthority('ADMIN') and hasAuthority('CHECK_ANALYTICS') and hasAuthority('VIEW_USERS')")
@io.swagger.v3.oas.annotations.Operation(summary = "List user AI usage and quota by assignment")
public SuccessResponse<Breakdown> userAssignments(@PathVariable UUID userId, @PathVariable UUID groupId, @org.springdoc.core.annotations.ParameterObject @ModelAttribute AssistantUsageQuery query, Authentication auth) {
var access=usage.authorize(Audience.ADMIN,auth.getName(),userId,groupId,null,query,true);
return new SuccessResponse<>("Assistant usage retrieved",usage.rows(access,Dimension.ASSIGNMENTS,query));
}
@GetMapping("/api/admin/assistant-usage/models")
@io.swagger.v3.oas.annotations.Operation(summary = "List measured provider model and stage usage")
public SuccessResponse<Models> models(@org.springdoc.core.annotations.ParameterObject @ModelAttribute AssistantUsageQuery query, Authentication auth) {
var access=usage.authorize(Audience.ADMIN,auth.getName(),null,null,null,query,false);
return new SuccessResponse<>("Assistant usage retrieved",usage.models(access));
}
}
Loading
Loading