Files
mindforge/Mindforge.API/Repositories/FlashcardRepository.cs
Jose Henrique b80d28f671
All checks were successful
Mindforge API Build and Deploy / Build Mindforge API Image (push) Successful in 4m4s
Mindforge Web Build and Deploy (internal) / Build Mindforge Web Image (push) Successful in 5m29s
Mindforge Web Build and Deploy (internal) / Deploy Mindforge Web (internal) (push) Successful in 9s
Mindforge API Build and Deploy / Deploy Mindforge API (internal) (push) Successful in 8s
new flashcards
2026-05-30 11:59:19 -03:00

261 lines
9.6 KiB
C#

using Dapper;
using Mindforge.API.Models.Flashcards;
using Mindforge.API.Services.Interfaces;
using Npgsql;
namespace Mindforge.API.Repositories
{
public class FlashcardRepository : IFlashcardRepository
{
private const string DefaultConnectionString = "Host=iris.haven;Database=mindforge;Username=root;Password=root";
private readonly string _connectionString;
public FlashcardRepository(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("MindforgeDb")
?? configuration["MINDFORGE_DB_CONNECTION"]
?? DefaultConnectionString;
}
public async Task EnsureSchemaAsync()
{
const string sql = """
CREATE TABLE IF NOT EXISTS flashcard_libraries (
id BIGSERIAL PRIMARY KEY,
file_path TEXT NOT NULL UNIQUE,
file_name TEXT NOT NULL,
subject TEXT NOT NULL,
difficulty TEXT NOT NULL,
card_count INTEGER NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS flashcards (
id BIGSERIAL PRIMARY KEY,
library_id BIGINT NOT NULL REFERENCES flashcard_libraries(id) ON DELETE CASCADE,
front TEXT NOT NULL,
back TEXT NOT NULL,
position INTEGER NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS ix_flashcard_libraries_subject ON flashcard_libraries(subject);
CREATE INDEX IF NOT EXISTS ix_flashcards_library_id_position ON flashcards(library_id, position);
""";
await using var connection = CreateConnection();
await connection.OpenAsync();
await connection.ExecuteAsync(sql);
}
public async Task<FlashcardLibraryDetails> UpsertLibraryAsync(
string filePath,
string fileName,
string subject,
string difficulty,
IReadOnlyList<FlashcardDraftCard> cards)
{
await using var connection = CreateConnection();
await connection.OpenAsync();
await using var transaction = await connection.BeginTransactionAsync();
try
{
var existingId = await connection.ExecuteScalarAsync<long?>(
"SELECT id FROM flashcard_libraries WHERE file_path = @FilePath FOR UPDATE;",
new { FilePath = filePath },
transaction);
long libraryId;
if (existingId.HasValue)
{
libraryId = existingId.Value;
await connection.ExecuteAsync(
"""
UPDATE flashcard_libraries
SET file_name = @FileName,
subject = @Subject,
difficulty = @Difficulty,
card_count = @CardCount,
updated_at = NOW()
WHERE id = @LibraryId;
""",
new
{
FileName = fileName,
Subject = subject,
Difficulty = difficulty,
CardCount = cards.Count,
LibraryId = libraryId
},
transaction);
await connection.ExecuteAsync(
"DELETE FROM flashcards WHERE library_id = @LibraryId;",
new { LibraryId = libraryId },
transaction);
}
else
{
libraryId = await connection.ExecuteScalarAsync<long>(
"""
INSERT INTO flashcard_libraries (file_path, file_name, subject, difficulty, card_count)
VALUES (@FilePath, @FileName, @Subject, @Difficulty, @CardCount)
RETURNING id;
""",
new
{
FilePath = filePath,
FileName = fileName,
Subject = subject,
Difficulty = difficulty,
CardCount = cards.Count
},
transaction);
}
if (cards.Count > 0)
{
await connection.ExecuteAsync(
"""
INSERT INTO flashcards (library_id, front, back, position)
VALUES (@LibraryId, @Front, @Back, @Position);
""",
cards.Select(card => new
{
LibraryId = libraryId,
card.Front,
card.Back,
card.Position
}),
transaction);
}
await transaction.CommitAsync();
var details = await GetLibraryByIdAsync(libraryId);
if (details is null)
{
throw new InvalidOperationException("Biblioteca gerada nao encontrada apos persistencia.");
}
return details;
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
public async Task<IReadOnlyList<FlashcardLibrarySummary>> GetLibrariesAsync()
{
const string sql = """
SELECT
id AS Id,
file_path AS FilePath,
file_name AS FileName,
subject AS Subject,
difficulty AS Difficulty,
card_count AS CardCount,
created_at AS CreatedAt,
updated_at AS UpdatedAt
FROM flashcard_libraries
ORDER BY subject, file_name;
""";
await using var connection = CreateConnection();
await connection.OpenAsync();
var rows = await connection.QueryAsync<FlashcardLibrarySummary>(sql);
return rows.ToList();
}
public async Task<FlashcardLibraryDetails?> GetLibraryByIdAsync(long libraryId)
{
const string librarySql = """
SELECT
id AS Id,
file_path AS FilePath,
file_name AS FileName,
subject AS Subject,
difficulty AS Difficulty,
card_count AS CardCount,
created_at AS CreatedAt,
updated_at AS UpdatedAt
FROM flashcard_libraries
WHERE id = @LibraryId;
""";
const string cardsSql = """
SELECT
id AS Id,
library_id AS LibraryId,
front AS Front,
back AS Back,
position AS Position,
created_at AS CreatedAt
FROM flashcards
WHERE library_id = @LibraryId
ORDER BY position;
""";
await using var connection = CreateConnection();
await connection.OpenAsync();
var library = await connection.QuerySingleOrDefaultAsync<FlashcardLibrarySummary>(
librarySql,
new { LibraryId = libraryId });
if (library is null)
{
return null;
}
var cards = await connection.QueryAsync<FlashcardCard>(cardsSql, new { LibraryId = libraryId });
return new FlashcardLibraryDetails
{
Id = library.Id,
FilePath = library.FilePath,
FileName = library.FileName,
Subject = library.Subject,
Difficulty = library.Difficulty,
CardCount = library.CardCount,
CreatedAt = library.CreatedAt,
UpdatedAt = library.UpdatedAt,
Cards = cards.ToList()
};
}
public async Task<IReadOnlyList<FlashcardCard>> GetCardsForLibrariesAsync(IReadOnlyList<long> libraryIds)
{
if (libraryIds.Count == 0)
{
return [];
}
const string sql = """
SELECT
id AS Id,
library_id AS LibraryId,
front AS Front,
back AS Back,
position AS Position,
created_at AS CreatedAt
FROM flashcards
WHERE library_id = ANY(@LibraryIds)
ORDER BY library_id, position;
""";
await using var connection = CreateConnection();
await connection.OpenAsync();
var cards = await connection.QueryAsync<FlashcardCard>(sql, new { LibraryIds = libraryIds.ToArray() });
return cards.ToList();
}
private NpgsqlConnection CreateConnection()
{
return new NpgsqlConnection(_connectionString);
}
}
}