Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.smartjam.smartjamanalyzer.application;

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

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;
Expand All @@ -19,12 +22,21 @@ public class AudioAnalysisUseCase {
private final WorkspaceFactory workspaceFactory;
private final FeatureExtractor featureExtractor;

private final PerformanceEvaluator performanceEvaluator;
private final ReferenceRepository referenceRepository;
private final ResultRepository resultRepository;
private final DebugVisualizer debugVisualizer;

public void execute(String bucket, String fileKey) {
try (Workspace workspace = workspaceFactory.create()) {
UUID entityId = extractUuid(fileKey);

// TODO: Добавить обработку(проверку типа) входящего файла

// TODO: Добавить нормальный сбор метрик

// TODO: Добавить обработку(проверку типа) входящего файла
try (Workspace workspace = workspaceFactory.create()) {
updateStatus(bucket, entityId, AudioProcessingStatus.ANALYZING, null);
Comment on lines 30 to +38
Copy link

@coderabbitai coderabbitai bot Mar 22, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reject unknown buckets before starting the pipeline.

Lines 58-63 only handle "references" and "submissions", but Lines 80-86 route every other bucket to resultRepository. An unexpected bucket can still be marked ANALYZING, skip both save paths, and return without ever reaching COMPLETED. Validate the bucket once up front and keep updateStatus(...) aligned with that same two-value contract.

🛠️ Proposed fix
 public void execute(String bucket, String fileKey) {
+    if (!"references".equals(bucket) && !"submissions".equals(bucket)) {
+        throw new IllegalArgumentException("Unsupported bucket: " + bucket);
+    }
+
     UUID entityId = extractUuid(fileKey);

     // TODO: Добавить обработку(проверку типа) входящего файла
@@
     private void updateStatus(String bucket, UUID id, AudioProcessingStatus status, String error) {
         try {
             if ("references".equals(bucket)) {
                 referenceRepository.updateStatus(id, status, error);
-            } else {
+            } else if ("submissions".equals(bucket)) {
                 resultRepository.updateStatus(id, status, error);
             }
         } catch (Exception ex) {

Also applies to: 58-63, 80-86

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/application/AudioAnalysisUseCase.java`
around lines 30 - 38, In execute (AudioAnalysisUseCase.execute) validate the
incoming bucket string up front against the allowed set ("references",
"submissions"); if it's not one of those, log/mark an error and return before
creating a Workspace or calling updateStatus so we never set ANALYZING for
unknown buckets. Ensure the subsequent updateStatus(...) and downstream saves
only follow the same two-value contract (use referenceRepository for
"references" and resultRepository for "submissions") and do not attempt any save
or status transitions for invalid buckets.

Copy link
Owner Author

Choose a reason for hiding this comment

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

fixed

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
git log --oneline -5
cat backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/application/AudioAnalysisUseCase.java

Length of output: 5480


@Satlykovs, thanks for the update! However, looking at the latest commit (4a130f4), the fix appears to be only partially applied. Two issues from the original comment remain:

  1. No early bucket guard in execute — an unknown bucket still reaches updateStatus(ANALYZING, null) before any validation.
  2. updateStatus still uses a bare else — any bucket that isn't "references" (including unknown ones) still routes to resultRepository.updateStatus(...).

The downstream save block (else if ("submissions"...) is already correct, but the two items above still need to be addressed:

// In execute() — add this before extractUuid():
if (!"references".equals(bucket) && !"submissions".equals(bucket)) {
    throw new IllegalArgumentException("Unsupported bucket: " + bucket);
}

// In updateStatus() — change else → else if:
} else if ("submissions".equals(bucket)) {
    resultRepository.updateStatus(id, status, error);
}

Could you check if the changes were pushed correctly?


🧠 Learnings used
Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 5
File: backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/application/AudioAnalysisUseCase.java:23-39
Timestamp: 2026-03-09T16:58:58.342Z
Learning: In `backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/application/AudioAnalysisUseCase.java`, the `cleanWavFile` variable returned by `audioConverter.convertToStandardWav(...)` in the `execute(String bucket, String fileKey)` method is intentionally unused — it is a placeholder for downstream audio analysis logic (feature extraction / matching) that will be added in future PRs once the pipeline is complete.

Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 11
File: backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/application/AudioAnalysisUseCase.java:107-107
Timestamp: 2026-03-22T08:26:29.855Z
Learning: In `backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/application/AudioAnalysisUseCase.java`, the `debugVisualizer.generateHeatmap(...)` call inside `handleStudentSubmission` is intentionally debug/test-only and will be disabled (e.g., via a no-op bean or feature flag) in production. Failures from this call in non-production environments are considered acceptable.

Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 4
File: backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/service/StorageService.java:31-33
Timestamp: 2026-03-06T21:19:19.153Z
Learning: In `backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/service/StorageService.java`, S3 object keys are planned to be UUIDs generated by the API in future PRs, which eliminates filename-length concerns for temp file creation. Until that migration, arbitrary key names are still accepted by the current code.


// TODO: Добавить нормальный сбор метрик
StopWatch watch = new StopWatch(fileKey);

log.info("=== Начало обработки файла: {} из бакета {} ===", fileKey, bucket);
Expand All @@ -37,24 +49,84 @@ public void execute(String bucket, String fileKey) {
Path cleanWavFile = audioConverter.convertToStandardWav(localFile, workspace);
watch.stop();

watch.start("Business Logic (Math)");

watch.start("Feature Extraction");
FeatureSequence features = featureExtractor.extract(cleanWavFile);
watch.stop();

log.info("Extracted {} feature frames", features.frames().size());

watch.start("Evaluation & Persistence");
if ("references".equals(bucket)) {
log.info("Действие: Обработка ЭТАЛОНА учителя...");
handleTeacherReference(entityId, features);
} else if ("submissions".equals(bucket)) {
log.info("Действие: Обработка ПОПЫТКИ ученика...");
handleStudentSubmission(entityId, features);
}
watch.stop();

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

} catch (Exception e) {
log.error("Ошибка в UseCase для файла {}: {}", fileKey, e.getMessage());

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

log.error("Ошибка в UseCase для файла {}: {}", fileKey, errorMsg, e);
updateStatus(bucket, entityId, AudioProcessingStatus.FAILED, errorMsg);

throw new RuntimeException("Business logic failed", e);
}
}

private void updateStatus(String bucket, UUID id, AudioProcessingStatus status, String error) {
try {
if ("references".equals(bucket)) {
referenceRepository.updateStatus(id, status, error);
} else {
resultRepository.updateStatus(id, status, error);
}
} catch (Exception ex) {
log.error("Failed to update status in DB for {}: {}", id, ex.getMessage());
}
}

private void handleTeacherReference(UUID assignmentId, FeatureSequence teacherFeatures) {
log.info("Saving teacher reference features for assignment: {}", assignmentId);
referenceRepository.save(assignmentId, teacherFeatures);
}

private void handleStudentSubmission(UUID submissionId, FeatureSequence studentFeatures) {
log.info("Evaluating student submission: {}", submissionId);

UUID assignmentId = resultRepository
.findAssignmentIdBySubmissionId(submissionId)
.orElseThrow(() ->
new IllegalStateException("Submission " + submissionId + " is not linked to any assignment"));

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

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

resultRepository.save(submissionId, result);

try {
debugVisualizer.generateHeatmap(result, "debug_" + submissionId + ".png");
} catch (Exception e) {
log.error("Не удалось сгенерировать тепловую карту {}: {}", submissionId, e.getMessage());
}

log.info("Submission {} evaluation completed.", submissionId);
}

private UUID extractUuid(String fileKey) {
try {
String rawUuid = fileKey.contains(".") ? fileKey.substring(0, fileKey.lastIndexOf(".")) : fileKey;

return UUID.fromString(rawUuid);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid S3 file key. Expected UUID, got: " + fileKey);
}
Comment on lines +123 to +130
Copy link

@coderabbitai coderabbitai bot Mar 22, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
for name in StorageService.java S3StorageListener.java; do
  fd "^${name}$" backend -x sh -c '
    echo "--- $1 ---"
    sed -n "1,220p" "$1"
  ' sh {}
done

echo "--- key generation / upload sites ---"
rg -n --type java -g '!**/test/**' 'UUID\.randomUUID|putObject|fileKey|objectKey|upload.*(Audio|File)' backend/smartjam-api backend/smartjam-analyzer

Repository: Satlykovs/SmartJam

Length of output: 6019


🏁 Script executed:

#!/bin/bash
# Find StorageService in API (key generation)
fd "StorageService.java" backend/smartjam-api -x cat -n {}

# Check for UUID generation in API
echo "--- UUID generation in API ---"
rg -n --type java 'UUID\.randomUUID' backend/smartjam-api

# Find upload endpoints and key assignment logic
echo "--- Upload logic and key assignment ---"
rg -n --type java -A5 -B2 'putObject|fileKey|objectKey|@PostMapping.*upload|@PostMapping.*audio' backend/smartjam-api | head -100

Repository: Satlykovs/SmartJam

Length of output: 132


🏁 Script executed:

#!/bin/bash
# Find all modules/directories
echo "=== Repository structure ==="
ls -la backend/

echo -e "\n=== Java files in smartjam-api ==="
find backend/smartjam-api -name "*.java" -type f | head -30

echo -e "\n=== All classes with 'Upload' or 'Storage' in smartjam-api ==="
find backend/smartjam-api -name "*.java" -type f | xargs grep -l "Upload\|Storage" 2>/dev/null

echo -e "\n=== Key generation patterns across entire backend ==="
rg -n --type java 'UUID\.randomUUID|S3|putObject' backend --max-count=20

Repository: Satlykovs/SmartJam

Length of output: 8099


🏁 Script executed:

#!/bin/bash
echo "=== S3StorageListenerTest key examples ==="
cat -n backend/smartjam-analyzer/src/test/java/com/smartjam/smartjamanalyzer/api/listener/S3StorageListenerTest.java

echo -e "\n=== Check docker-compose for clues about upload source ==="
grep -n -A5 -B5 "upload\|minio\|s3" docker-compose.yaml

echo -e "\n=== Check if there's a frontend or external documentation ==="
find . -name "*.md" -o -name "*.txt" | xargs grep -l "upload\|S3 key\|fileKey" 2>/dev/null | head -5

Repository: Satlykovs/SmartJam

Length of output: 4477


🏁 Script executed:

#!/bin/bash
echo "=== AudioAnalysisUseCase tests ==="
find backend/smartjam-analyzer -name "*AudioAnalysisUseCase*Test*.java" -o -name "*Test*AudioAnalysisUseCase*.java" | xargs cat -n 2>/dev/null

echo -e "\n=== Full AudioAnalysisUseCase.execute() and extractUuid() ==="
sed -n '30,132p' backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/application/AudioAnalysisUseCase.java | cat -n

echo -e "\n=== Check if there are prefixed keys anywhere ==="
rg -n 'folder/|uploads/|audio/|prefix' backend --type java

Repository: Satlykovs/SmartJam

Length of output: 11339


UUID format enforcement happens too early, before producer-side migration to UUID keys is complete.

The extractUuid() method rejects any non-UUID object key immediately (line 31), throwing an exception before the database can record a persisted failure state. Additionally, prefixed keys like folder/<uuid>.wav are unsupported because the extension-stripping logic does not remove path segments.

Verify that the producer side (smartjam-api uploader) now guarantees all S3 object keys are bare UUIDs before this strict parsing is safe. If uploads still use arbitrary filenames or folder prefixes, this will silently drop uploads without proper error tracking.

private UUID extractUuid(String fileKey) {
    try {
        String rawUuid = fileKey.contains(".") ? fileKey.substring(0, fileKey.lastIndexOf(".")) : fileKey;
        return UUID.fromString(rawUuid);
    } catch (Exception e) {
        throw new IllegalArgumentException("Invalid S3 file key. Expected UUID, got: " + fileKey);
    }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/application/AudioAnalysisUseCase.java`
around lines 123 - 130, The extractUuid method currently fails on prefixed keys
and throws IllegalArgumentException on parse errors; change extractUuid to first
isolate the last path segment (e.g., take substring after the last '/' if
present), then strip the file extension (substring before last '.'), then
attempt UUID.fromString on that raw segment; on failure do NOT throw
IllegalArgumentException—either return Optional<UUID> (or null) or throw a
specific checked/handled exception (e.g., InvalidFileKeyException) so callers
(the surrounding processing flow) can record a persisted failure state in the
database instead of letting the upload be dropped silently; update all call
sites of extractUuid accordingly (or adapt to Optional) so parse failures are
handled and persisted.

Copy link
Owner Author

Choose a reason for hiding this comment

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

But if the key is wrong, we will be unable to find the record with that key, because it's invalid UUID

Copy link

Choose a reason for hiding this comment

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

@Satlykovs, that's a fair and correct point. If the file key isn't a valid UUID, there's no entity in the database keyed to that value — so there's nothing to update the status on. The entityId itself would never be assigned, making any updateStatus(...) call impossible regardless. Throwing IllegalArgumentException early and letting the S3 listener handle it at a higher level (e.g., logging, dead-lettering) is the right behavior here.

I'll withdraw this concern — the current implementation is correct.


🧠 Learnings used
Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 5
File: backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/application/AudioAnalysisUseCase.java:23-39
Timestamp: 2026-03-09T16:58:58.342Z
Learning: In `backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/application/AudioAnalysisUseCase.java`, the `cleanWavFile` variable returned by `audioConverter.convertToStandardWav(...)` in the `execute(String bucket, String fileKey)` method is intentionally unused — it is a placeholder for downstream audio analysis logic (feature extraction / matching) that will be added in future PRs once the pipeline is complete.

Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 11
File: backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/application/AudioAnalysisUseCase.java:107-107
Timestamp: 2026-03-22T08:26:29.855Z
Learning: In `backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/application/AudioAnalysisUseCase.java`, the `debugVisualizer.generateHeatmap(...)` call inside `handleStudentSubmission` is intentionally debug/test-only and will be disabled (e.g., via a no-op bean or feature flag) in production. Failures from this call in non-production environments are considered acceptable.

Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 4
File: backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/service/StorageService.java:31-33
Timestamp: 2026-03-06T21:19:19.153Z
Learning: In `backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/service/StorageService.java`, S3 object keys are planned to be UUIDs generated by the API in future PRs, which eliminates filename-length concerns for temp file creation. Until that migration, arbitrary key names are still accepted by the current code.

Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 0
File: :0-0
Timestamp: 2026-03-16T23:56:57.162Z
Learning: In SmartJam (Satlykovs/SmartJam), both UserRepository and RefreshTokenRepository incorrectly extend JpaRepository with Long as the ID type, but both UserEntity and RefreshTokenEntity use UUID as the primary key (GenerationType.UUID). The correct declarations are JpaRepository<UserEntity, UUID> and JpaRepository<RefreshTokenEntity, UUID>.

}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
package com.smartjam.smartjamanalyzer.domain.port;

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

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

/** Domain port for managing teacher reference features. */
public interface ReferenceRepository {
void save(UUID targetId, FeatureSequence features);

FeatureSequence findById(UUID targetId);
/**
* Saves the spectral features of a teacher's reference track.
*
* @param assignmentId Unique identifier of the assignment.
* @param features Extracted features to persist.
*/
void save(UUID assignmentId, FeatureSequence features);

/**
* Retrieves reference features for comparison.
*
* @param assignmentId Unique identifier of the assignment.
* @return The feature sequence if presented.
*/
Optional<FeatureSequence> findFeaturesById(UUID assignmentId);

/**
* Performs an optimized status update for an assignment, optionally recording an error message.
*
* @param assignmentId unique identifier of the submission.
* @param status the new processing state.
* @param errorMessage description of the failure, if applicable.
*/
void updateStatus(UUID assignmentId, AudioProcessingStatus status, String errorMessage);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@
package com.smartjam.smartjamanalyzer.domain.port;

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

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
* detailed feedback events.
*/
public interface ResultRepository {

/**
* Persists the final analysis result for a specific submission.
*
* @param submissionId unique identifier of the student submission.
* @param result calculated scores and feedback events.
*/
void save(UUID submissionId, AnalysisResult result);

/**
* Resolves the teacher's assignment ID associated with a given student submission.
*
* @param submissionId unique identifier of the submission.
* @return the UUID of the parent assignment.
* @throws RuntimeException if no linked assignment ID found.
*/
Optional<UUID> findAssignmentIdBySubmissionId(UUID submissionId);

/**
* Update status for a submission, optionally recording an error message.
*
* @param submissionId unique identifier of the submission.
* @param status the new processing state.
* @param errorMessage description of the failure, if applicable.
*/
void updateStatus(UUID submissionId, AudioProcessingStatus status, String errorMessage);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.smartjam.smartjamanalyzer.infrastructure.persistence.adapter;

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

import com.smartjam.common.model.AudioProcessingStatus;
import com.smartjam.smartjamanalyzer.domain.model.FeatureSequence;
import com.smartjam.smartjamanalyzer.domain.port.ReferenceRepository;
import com.smartjam.smartjamanalyzer.infrastructure.persistence.entity.AssignmentEntity;
import com.smartjam.smartjamanalyzer.infrastructure.persistence.repository.JpaAssignmentRepository;
import com.smartjam.smartjamanalyzer.infrastructure.utils.FeatureBinarySerializer;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
* JPA implementation of {@link ReferenceRepository}. Bridges the domain logic with the database using binary
* serialization for spectral data.
*/
@Component
@RequiredArgsConstructor
public class AssignmentPersistenceAdapter implements ReferenceRepository {
private final JpaAssignmentRepository repository;

/**
* Packs spectral features into a binary format and saves them to the assignment record. Transition the status to
* {@link AudioProcessingStatus#COMPLETED}.
*/
@Override
@Transactional
public void save(UUID assignmentId, FeatureSequence features) {
byte[] bytes = FeatureBinarySerializer.serialize(features);

AssignmentEntity entity = repository
.findById(assignmentId)
.orElseThrow(() -> new IllegalStateException("Assignment metadata missing for ID: " + assignmentId
+ ". It might have " + "been deleted or not created yet."));

entity.setReferenceSpectreCache(bytes);
entity.setStatus(AudioProcessingStatus.COMPLETED);
entity.setErrorMessage(null);

repository.save(entity);
}

/**
* Retrieves and unpacks binary features from the database.
*
* @return an Optional containing the FeatureSequence, or empty if not found.
*/
@Override
@Transactional(readOnly = true)
public Optional<FeatureSequence> findFeaturesById(UUID assignmentId) {
return repository
.findById(assignmentId)
.map(e -> FeatureBinarySerializer.deserialize(e.getReferenceSpectreCache()));
}

@Override
@Transactional
public void updateStatus(UUID id, AudioProcessingStatus status, String errorMessage) {
repository.updateStatus(id, status, errorMessage);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.smartjam.smartjamanalyzer.infrastructure.persistence.adapter;

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

import com.smartjam.common.model.AudioProcessingStatus;
import com.smartjam.smartjamanalyzer.domain.model.AnalysisResult;
import com.smartjam.smartjamanalyzer.domain.port.ResultRepository;
import com.smartjam.smartjamanalyzer.infrastructure.persistence.entity.SubmissionEntity;
import com.smartjam.smartjamanalyzer.infrastructure.persistence.repository.JpaSubmissionRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
* JPA implementation of {@link ResultRepository}. Manages storage of evaluation results and coordinates mapping between
* domain results and JSONB database columns.
*/
@Component
@RequiredArgsConstructor
public class SubmissionPersistenceAdapter implements ResultRepository {
private final JpaSubmissionRepository repository;

/**
* Updates the submission record with scores and feedback. Transition the status to
* {@link AudioProcessingStatus#COMPLETED}.
*/
@Override
@Transactional
public void save(UUID submissionId, AnalysisResult result) {
SubmissionEntity entity = repository
.findById(submissionId)
.orElseThrow(() -> new IllegalStateException("Submission record missing for ID: " + submissionId));

entity.setTotalScore(result.totalScore());
entity.setPitchScore(result.pitchScore());
entity.setRhythmScore(result.rhythmScore());
entity.setAnalysisFeedback(result.feedback());
entity.setStatus(AudioProcessingStatus.COMPLETED);
entity.setErrorMessage(null);

repository.save(entity);
}

@Override
@Transactional(readOnly = true)
public Optional<UUID> findAssignmentIdBySubmissionId(UUID submissionId) {
return repository.findById(submissionId).map(SubmissionEntity::getAssignmentId);
}

@Override
@Transactional
public void updateStatus(UUID submissionId, AudioProcessingStatus status, String errorMessage) {
repository.updateStatus(submissionId, status, errorMessage);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.smartjam.smartjamanalyzer.infrastructure.persistence.entity;

import java.util.UUID;

import jakarta.persistence.*;

import com.smartjam.common.model.AudioProcessingStatus;
import lombok.*;

/** Database model for teacher assignments. Stores heavy spectral data as raw bytes (BYTEA) to optimize performance. */
@Entity
@Table(name = "assignments")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AssignmentEntity {
@Id
private UUID id;

@Enumerated(EnumType.STRING)
private AudioProcessingStatus status;

@Column(name = "reference_spectre_cache")
private byte[] referenceSpectreCache;

@Column(name = "error_message", columnDefinition = "TEXT")
private String errorMessage;
}
Comment on lines +11 to +30
Copy link

@coderabbitai coderabbitai bot Mar 21, 2026

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add a versioned DB migration for these new columns/tables.

Relying on ddl-auto: update for persistence-schema changes is brittle across environments; add Flyway/Liquibase migration scripts for deterministic rollout.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/infrastructure/persistence/entity/AssignmentEntity.java`
around lines 10 - 29, Add a versioned DB migration that creates/updates the
assignments table to match AssignmentEntity: add the reference_spectre_cache
column (binary) and error_message column (text/varchar) and, if the table itself
is new in this PR, include full CREATE TABLE for assignments with id (UUID) and
status (string) columns; implement this as a Flyway (V###__describe.sql) or
Liquibase changeSet to ensure deterministic rollout rather than relying on
ddl-auto. Locate the schema differences by inspecting AssignmentEntity and
create an idempotent SQL migration that ALTERs the existing assignments table to
add reference_spectre_cache (bytea/BLOB) and error_message (text) and a
migration entry in your migrations folder so CI/production runs the versioned
migration during deployment.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Yes, I know about it, will fix later when Liquibase will be added

Copy link

Choose a reason for hiding this comment

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

@Satlykovs, understood! That's a reasonable approach — once Liquibase is integrated, the migration scripts for assignments and submissions tables (including the reference_spectre_cache, error_message, status columns, etc.) can be added as versioned changesets at that point.

Would you like me to open a GitHub issue to track the Liquibase migration work so it isn't forgotten? If so, I can create one now.


🧠 Learnings used
Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 0
File: :0-0
Timestamp: 2026-03-16T23:56:57.162Z
Learning: In SmartJam (Satlykovs/SmartJam), both UserRepository and RefreshTokenRepository incorrectly extend JpaRepository with Long as the ID type, but both UserEntity and RefreshTokenEntity use UUID as the primary key (GenerationType.UUID). The correct declarations are JpaRepository<UserEntity, UUID> and JpaRepository<RefreshTokenEntity, UUID>.

Learnt from: men229
Repo: Satlykovs/SmartJam PR: 2
File: backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java:25-26
Timestamp: 2026-03-16T23:31:36.243Z
Learning: In SmartJam (Satlykovs/SmartJam), UserEntity uses `email` as the unique login identifier (unique = true in Column). The `username`/`nickname` field is a non-unique display name and does not require a uniqueness constraint. Repository methods like `existsByUsername()` / `findUserEntitiesByUsername()` may have been renamed to reflect `nickname`. Do not flag missing uniqueness on the nickname/display-name field. Applies to backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java and related repository/service files.

Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 4
File: backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/service/StorageService.java:31-33
Timestamp: 2026-03-06T21:19:19.153Z
Learning: In `backend/smartjam-analyzer/src/main/java/com/smartjam/smartjamanalyzer/service/StorageService.java`, S3 object keys are planned to be UUIDs generated by the API in future PRs, which eliminates filename-length concerns for temp file creation. Until that migration, arbitrary key names are still accepted by the current code.

Loading