Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/mobile-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,19 @@ jobs:
- name: Checkout Repository
uses: actions/checkout@v5

- name: Setup Java 17
- name: Setup Java 25
uses: actions/setup-java@v5
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin GitHub Action to a commit SHA.

Using actions/setup-java@v5 at Line 25 is mutable and weakens CI supply-chain guarantees. Pin this action to a full commit SHA.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 25-25: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/mobile-ci.yml at line 25, Replace the mutable reference
"uses: actions/setup-java@v5" with a pinned commit SHA for the
actions/setup-java action; locate the workflow step that contains the literal
string uses: actions/setup-java@v5 and update it to uses:
actions/setup-java@<full-commit-sha> (obtain the latest stable commit SHA from
the actions/setup-java repository and replace v5 with that full SHA).

with:
java-version: '17'
java-version: '25'
Comment on lines +24 to +27
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What Java versions are officially supported by the current Android Gradle Plugin and Gradle for Android builds, and is JDK 25 supported?

💡 Result:

Answer (as of 2026-05-25): 1) Officially supported Java versions for Android Gradle Plugin (AGP) and Gradle builds - AGP docs state that the minimum required JDK to run AGP depends on the AGP version, and provides a compatibility table in the AGP release notes (example given: AGP 8.x requires JDK 17 to run) [1]. - AGP 9.2.0 release notes explicitly list JDK 17 as both the minimum and default JDK for that AGP release [2]. - Therefore, for current AGP (9.2.0 and the contemporary AGP line), the supported JDK to run the build is JDK 17 (and higher, subject to the toolchain choosing behavior), with the exact minimum pinned per AGP version via release-note compatibility tables [1][2]. 2) What Gradle (for Android builds) is officially supported with (JDK side) - Gradle’s tested/compatibility matrix is about which Gradle versions work with which Android Gradle Plugin versions; it doesn’t enumerate JDK 25 as supported in the snippet we retrieved [3][4]. - For the purpose of “is JDK 25 supported?”, the key point is whether Gradle itself supports running/compiling with JDK 25. A Gradle issue discussing JDK 25 support notes that the current compatibility matrix shows there is no toolchain support for JDK 25 (at least at the time of that request) [5], and another notes that a warning about Kotlin/JDK 25 compatibility can be resolved on newer Gradle (e.g., 9.2.0) [6]. 3) Is JDK 25 supported? - For Android builds specifically: JDK 25 is not listed as an officially supported JDK in the Android/AGP JDK documentation we retrieved; the AGP 9.2.0 release notes show JDK 17 as the required/default JDK [2]. So, in the current official AGP guidance, JDK 25 is not presented as a supported “build JDK” the way JDK 17 is. - For Gradle itself: there are explicit reports that (at least in some versions of Gradle) the compatibility matrix indicated no toolchain support for JDK 25 [5], and later Gradle versions/workarounds address compilation issues [6]. Based on these sources alone, we cannot claim “JDK 25 is officially supported by current AGP+Gradle” as a definitive, fully sanctioned baseline; the authoritative AGP release-note baseline we have points to JDK 17 [2]. Practical conclusion: - Use JDK 17 for current Android Gradle Plugin (example: AGP 9.2.0 requires JDK 17) [2]. - I do not have authoritative evidence from the AGP/Android official JDK guidance that JDK 25 is officially supported as a build JDK today; Gradle community sources indicate that JDK 25 support varies by Gradle version and has had compatibility gaps [5][6].

Citations:


Pin CI to an AGP-supported JDK (JDK 17), or justify JDK 25 explicitly

File: .github/workflows/mobile-ci.yml (lines 24-27)

      - name: Setup Java 25
        uses: actions/setup-java@v5
        with:
          java-version: '25'

AGP 9.2.0’s official guidance specifies JDK 17 as the minimum/default JDK, and the AGP/Gradle JDK compatibility information does not show JDK 25 as an officially supported build JDK. Switching CI to Java 25 can therefore break the Android toolchain if Gradle/Kotlin/AGP reject newer JDKs.

  • Set java-version: '17' (matching the AGP compatibility baseline) or gate/upgrade only after confirming JDK 25 compatibility for the exact AGP + Gradle versions used.
🧰 Tools
🪛 zizmor (1.25.2)

[error] 25-25: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/mobile-ci.yml around lines 24 - 27, The CI workflow uses
actions/setup-java@v5 with java-version: '25' which is not an AGP-supported JDK;
change the java-version value to '17' (update the step named "Setup Java 25" to
set java-version: '17') so the workflow uses the AGP/Gradle-compatible JDK, or
alternatively gate this change with explicit compatibility checks for your AGP
and Gradle versions before keeping '25'.

distribution: 'temurin'
cache: 'gradle'

- name: Make gradlew executable
run: chmod +x gradlew

- name: Create google-services.json
run: echo "${{ secrets.GOOGLE_SERVICES_JSON }}" | base64 --decode > app/google-services.json


- name: Build Android App
run: ./gradlew assembleDebug lintDebug testDebugUnitTest --no-daemon
run: ./gradlew spotlessCheck assembleDebug lintDebug testDebugUnitTest --no-daemon
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
*.iml
*.iws

.vscode/
.vscode/

firebase-adminsdk.json
google-services.json
6 changes: 5 additions & 1 deletion backend/compose-messaging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,12 @@ services:
echo "Broker not ready, retrying in 2s..."
sleep 2
done

/opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka:9092 --create --if-not-exists --topic s3-events --partitions 3 --replication-factor 1
echo "Topic s3-events created."

/opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka:9092 --create --if-not-exists --topic analysis-results --partitions 3 --replication-factor 1

echo "Topics s3-events and analysis-results created."



Expand Down
3 changes: 2 additions & 1 deletion backend/settings.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
rootProject.name = 'backend'
include 'smartjam-api'
include 'smartjam-analyzer'
include 'smartjam-common'
include 'smartjam-common'
include 'smartjam-notification'
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.smartjam.smartjamanalyzer;
package com.smartjam.analyzer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.smartjam.smartjamanalyzer.api.kafka;
package com.smartjam.analyzer.api.kafka;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

import com.smartjam.common.dto.s3.S3EventDto;
import com.smartjam.smartjamanalyzer.application.AudioAnalysisUseCase;
import com.smartjam.analyzer.application.AudioAnalysisUseCase;
import com.smartjam.common.dto.s3.S3Event;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.annotation.KafkaListener;
Expand Down Expand Up @@ -32,14 +32,14 @@ public class S3StorageListener {
topics = "s3-events",
groupId = "smartjam-analyzer-group",
concurrency = "3",
properties = {"spring.json.value.default.type=com.smartjam.common.dto.s3.S3EventDto"})
public void onFileUploaded(S3EventDto event, Acknowledgment ack) {
properties = {"spring.json.value.default.type=com.smartjam.common.dto.s3.S3Event"})
public void onFileUploaded(S3Event event, Acknowledgment ack) {
if (event == null || event.records() == null || event.records().isEmpty()) {
if (ack != null) ack.acknowledge();
return;
}

for (S3EventDto.S3Record s3Record : event.records()) {
for (S3Event.S3Record s3Record : event.records()) {

try {

Expand All @@ -51,17 +51,17 @@ public void onFileUploaded(S3EventDto event, Acknowledgment ack) {
analysisUseCase.execute(bucket, fileKey);

} catch (Exception e) {
log.error("Ошибка при разборе события S3: {}", e.getMessage());
log.error("Ошибка при разборе события S3: {}", e.getMessage(), e);

throw new RuntimeException(e);
throw e;
}
}
if (ack != null) {
ack.acknowledge();
}
}

private boolean isValid(S3EventDto.S3Record r) {
private boolean isValid(S3Event.S3Record r) {
return r != null
&& r.s3() != null
&& r.s3().bucket() != null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.smartjam.smartjamanalyzer.application;
package com.smartjam.analyzer.application;

import java.nio.file.Path;
import java.util.UUID;

import com.smartjam.analyzer.domain.exception.AnalysisFatalException;
import com.smartjam.analyzer.domain.model.AnalysisResult;
import com.smartjam.analyzer.domain.model.FeatureSequence;
import com.smartjam.analyzer.domain.port.*;
import com.smartjam.common.dto.analysis.AnalysisFinishedEvent;
import com.smartjam.common.dto.analysis.AnalysisType;
import com.smartjam.common.model.AudioProcessingStatus;
import com.smartjam.smartjamanalyzer.domain.model.AnalysisResult;
import com.smartjam.smartjamanalyzer.domain.model.FeatureSequence;
import com.smartjam.smartjamanalyzer.domain.port.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand All @@ -30,6 +33,8 @@ public class AudioAnalysisUseCase {
private final ResultRepository resultRepository;
private final DebugVisualizer debugVisualizer;

private final AnalysisEventPublisher eventPublisher;

public void execute(String bucket, String fileKey) {

if (!BUCKET_REFERENCES.equals(bucket) && !BUCKET_SUBMISSIONS.equals(bucket)) {
Expand Down Expand Up @@ -74,15 +79,27 @@ public void execute(String bucket, String fileKey) {

log.info("Результаты обработки {}: \n{}", fileKey, watch.prettyPrint());

} catch (AnalysisFatalException e) {
log.error("Fatal analysis error for file {}: {}", fileKey, e.getMessage(), e);

updateStatus(bucket, entityId, AudioProcessingStatus.FAILED, e.getMessage());
eventPublisher.publish(AnalysisFinishedEvent.builder()
.targetId(entityId)
.type(BUCKET_REFERENCES.equals(bucket) ? AnalysisType.REFERENCE : AnalysisType.SUBMISSION)
.status(AudioProcessingStatus.FAILED)
.errorMessage(e.getMessage())
.build());
} catch (Exception e) {

String errorMsg =
e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName();

log.error("Ошибка в UseCase для файла {}: {}", fileKey, errorMsg, e);
log.error("Technical error for file {}: {}\n Retrying...", fileKey, errorMsg, e);

updateStatus(bucket, entityId, AudioProcessingStatus.FAILED, errorMsg);

throw new RuntimeException("Business logic failed", e);
throw new RuntimeException("Technical failure, retrying...", e); // Хотелось бы сюда
// DLT и не ходить в базу при каждом ретрае
}
}

Expand All @@ -101,6 +118,12 @@ private void updateStatus(String bucket, UUID id, AudioProcessingStatus status,
private void handleTeacherReference(UUID assignmentId, FeatureSequence teacherFeatures) {
log.info("Сохраняем извлеченные признаки учителя для задания: {}", assignmentId);
referenceRepository.save(assignmentId, teacherFeatures);

eventPublisher.publish(AnalysisFinishedEvent.builder()
.targetId(assignmentId)
.type(AnalysisType.REFERENCE)
.status(AudioProcessingStatus.COMPLETED)
.build());
}

private void handleStudentSubmission(UUID submissionId, FeatureSequence studentFeatures) {
Expand All @@ -109,17 +132,24 @@ private void handleStudentSubmission(UUID submissionId, FeatureSequence studentF
UUID assignmentId = resultRepository
.findAssignmentIdBySubmissionId(submissionId)
.orElseThrow(() ->
new IllegalStateException("Submission " + submissionId + " is not linked to any assignment"));
new AnalysisFatalException("Submission " + submissionId + " is not linked to any assignment"));

FeatureSequence teacherFeatures = referenceRepository
.findFeaturesById(assignmentId)
.orElseThrow(() -> new IllegalStateException(
.orElseThrow(() -> new AnalysisFatalException(
"Teacher reference features not found for assignment: " + assignmentId));

AnalysisResult result = performanceEvaluator.evaluate(teacherFeatures, studentFeatures);

resultRepository.save(submissionId, result);

eventPublisher.publish(AnalysisFinishedEvent.builder()
.targetId(submissionId)
.type(AnalysisType.SUBMISSION)
.status(AudioProcessingStatus.COMPLETED)
.totalScore(result.totalScore())
.build());

try {
debugVisualizer.generateHeatmap(result, "debug_" + submissionId + ".png");
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.smartjam.analyzer.domain.exception;

/**
* Exception thrown when a non-recoverable error occurs during audio analysis. Indicates that the process cannot be
* successfully retried (e.g., missing metadata in DB).
*/
public class AnalysisFatalException extends RuntimeException {
public AnalysisFatalException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.smartjam.smartjamanalyzer.domain.model;
package com.smartjam.analyzer.domain.model;

import java.util.List;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.smartjam.smartjamanalyzer.domain.model;
package com.smartjam.analyzer.domain.model;

import java.util.List;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.smartjam.analyzer.domain.port;

import com.smartjam.common.dto.analysis.AnalysisFinishedEvent;

public interface AnalysisEventPublisher {
void publish(AnalysisFinishedEvent event);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.smartjam.smartjamanalyzer.domain.port;
package com.smartjam.analyzer.domain.port;

import java.nio.file.Path;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.smartjam.smartjamanalyzer.domain.port;
package com.smartjam.analyzer.domain.port;

import java.nio.file.Path;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.smartjam.smartjamanalyzer.domain.port;
package com.smartjam.analyzer.domain.port;

import com.smartjam.smartjamanalyzer.domain.model.AnalysisResult;
import com.smartjam.analyzer.domain.model.AnalysisResult;

/**
* Port for generating visual artifacts of the analysis process. Typically used for debugging and fine-tuning DTW
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.smartjam.smartjamanalyzer.domain.port;
package com.smartjam.analyzer.domain.port;

import java.nio.file.Path;

import com.smartjam.smartjamanalyzer.domain.model.FeatureSequence;
import com.smartjam.analyzer.domain.model.FeatureSequence;

/** Port for extracting musical features from an audio file. */
public interface FeatureExtractor {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.smartjam.analyzer.domain.port;

import com.smartjam.analyzer.domain.model.AnalysisResult;
import com.smartjam.analyzer.domain.model.FeatureSequence;

public interface PerformanceEvaluator {
AnalysisResult evaluate(FeatureSequence reference, FeatureSequence student);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.smartjam.smartjamanalyzer.domain.port;
package com.smartjam.analyzer.domain.port;

import java.util.Optional;
import java.util.UUID;

import com.smartjam.analyzer.domain.model.FeatureSequence;
import com.smartjam.common.model.AudioProcessingStatus;
import com.smartjam.smartjamanalyzer.domain.model.FeatureSequence;

/** Domain port for managing teacher reference features. */
public interface ReferenceRepository {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.smartjam.smartjamanalyzer.domain.port;
package com.smartjam.analyzer.domain.port;

import java.util.Optional;
import java.util.UUID;

import com.smartjam.analyzer.domain.model.AnalysisResult;
import com.smartjam.common.model.AudioProcessingStatus;
import com.smartjam.smartjamanalyzer.domain.model.AnalysisResult;

/**
* Port for managing student submissions and their analysis results. Handles the persistence of evaluation scores and
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.smartjam.smartjamanalyzer.domain.port;
package com.smartjam.analyzer.domain.port;

import java.io.IOException;
import java.nio.file.Path;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.smartjam.smartjamanalyzer.domain.port;
package com.smartjam.analyzer.domain.port;

/**
* Factory port for creating isolated {@link Workspace} instances. This abstraction allows business logic to acquire
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.smartjam.smartjamanalyzer.domain.service;
package com.smartjam.analyzer.domain.service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.smartjam.analyzer.domain.model.AnalysisResult;
import com.smartjam.analyzer.domain.model.FeatureSequence;
import com.smartjam.analyzer.domain.port.PerformanceEvaluator;
import com.smartjam.common.model.FeedbackEvent;
import com.smartjam.common.model.FeedbackType;
import com.smartjam.smartjamanalyzer.domain.model.AnalysisResult;
import com.smartjam.smartjamanalyzer.domain.model.FeatureSequence;
import com.smartjam.smartjamanalyzer.domain.port.PerformanceEvaluator;

/**
* Performance evaluator using Dynamic Time Warping (DTW) and Cosine Similarity. Provides granular scoring for pitch and
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.smartjam.smartjamanalyzer.infrastructure.analysis;
package com.smartjam.analyzer.infrastructure.analysis;

import jakarta.validation.constraints.Min;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.smartjam.smartjamanalyzer.infrastructure.analysis;
package com.smartjam.analyzer.infrastructure.analysis;

import com.smartjam.smartjamanalyzer.domain.port.PerformanceEvaluator;
import com.smartjam.smartjamanalyzer.domain.service.DtwPerformanceEvaluator;
import com.smartjam.analyzer.domain.port.PerformanceEvaluator;
import com.smartjam.analyzer.domain.service.DtwPerformanceEvaluator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.smartjam.smartjamanalyzer.infrastructure.analysis;
package com.smartjam.analyzer.infrastructure.analysis;

import java.io.File;
import java.nio.file.Path;
Expand All @@ -10,8 +10,8 @@
import be.tarsos.dsp.AudioProcessor;
import be.tarsos.dsp.ConstantQ;
import be.tarsos.dsp.io.jvm.AudioDispatcherFactory;
import com.smartjam.smartjamanalyzer.domain.model.FeatureSequence;
import com.smartjam.smartjamanalyzer.domain.port.FeatureExtractor;
import com.smartjam.analyzer.domain.model.FeatureSequence;
import com.smartjam.analyzer.domain.port.FeatureExtractor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.smartjam.smartjamanalyzer.infrastructure.converter;
package com.smartjam.analyzer.infrastructure.converter;

import java.io.IOException;

Expand Down
Loading
Loading