new flashcards
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
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
This commit is contained in:
260
Mindforge.API/Repositories/FlashcardRepository.cs
Normal file
260
Mindforge.API/Repositories/FlashcardRepository.cs
Normal file
@@ -0,0 +1,260 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user