Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3247cb8
Issue #12: Added new use case UC-3 with functional requirements to th…
davidpasch1 Feb 3, 2026
8f34a13
Issue #15, #13: Created interfaces, reorganized code. CODE BROKEN.
davidpasch1 Feb 4, 2026
f539c4d
Prepared and fixed code.
davidpasch1 Feb 28, 2026
0a59e90
Issue #13: Added stored procedure that stores vocabulary entry with e…
davidpasch1 Mar 2, 2026
b4f1e3b
Added Migrations/ folder to .gitignore.
davidpasch1 Mar 2, 2026
b9e65c2
refactoring.
davidpasch1 Mar 3, 2026
80667eb
Adapted output of stored procedure to actual needs.
davidpasch1 Mar 3, 2026
58c0deb
Refactoring.
davidpasch1 Mar 3, 2026
7cdd542
Issue #13: Prepared code for implementing DAL.
davidpasch1 Mar 3, 2026
1e6506d
Issue #13: Implemented DAL with unit tests.
davidpasch1 Mar 3, 2026
de88402
Issue #13: Refactoring: Added extension method.
davidpasch1 Mar 3, 2026
908f6a2
Issue #13: Refactoring.
davidpasch1 Mar 4, 2026
2de1c2a
Issue #13: Prepared VocabularyService with unit test.
davidpasch1 Mar 4, 2026
8661005
Issue #13: Implemented VocabularyService.
davidpasch1 Mar 4, 2026
d2c2aa3
Refactoring.
davidpasch1 Mar 4, 2026
9b0f2b0
Added test for VocabularyService in the case of error.
davidpasch1 Mar 4, 2026
9aa12b0
Merge branch 'develop' into feature/CRUD
davidpasch1 Mar 5, 2026
c1dd481
Issue #18: Fix: appsettings.json.
davidpasch1 Mar 5, 2026
4353aa5
Issue #13: Implemented the VocabularyController.
davidpasch1 Mar 5, 2026
627260b
Refactoring.
davidpasch1 Mar 5, 2026
cbefb69
Issue #13: Implemented unit test for VocabularyController.
davidpasch1 Mar 6, 2026
784eecd
Spec: Added use case for user registration.
davidpasch1 Mar 6, 2026
b7e140e
database schema.
davidpasch1 Mar 6, 2026
6bf1b13
Refactoring: made VocabularyEntry and Example immutable.
davidpasch1 Mar 6, 2026
5b3c9f9
Refactoring: made VocabularyEntry and Example immutable.
davidpasch1 Mar 6, 2026
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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ It consists of the following projects:
SpanishByExample.sln
├─ SpanishByExample.Core
│ ├─ Model
│ ├─ Interfaces
│ ├─ Commands
│ ├─ Entities
│ ├─ Services
│ └─ Errors
├─ SpanishByExample.Api
│ ├─ Controllers
│ └─ DTO
│ └─ Dtos
├─ SpanishByExample.Business
├─ SpanishByExample.DataAccess
└─ SpanishByExample.Tests
Expand Down
10 changes: 10 additions & 0 deletions database/schema/create_UDTT.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
USE [spanish_ex]
GO

CREATE TYPE [dbo].[Examples_TT] AS TABLE(
[EXAMPLE_TEXT] [nvarchar](max) NOT NULL,
[ENGLISH] [nvarchar](max) NULL
)
GO


7 changes: 6 additions & 1 deletion database/schema/create_tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ CREATE TABLE VOCABULARY_T (
VOCABULARYID INT IDENTITY(1,1) NOT NULL,
WORDNORM NVARCHAR(100) NOT NULL,
ENGLISH NVARCHAR(MAX),
USERID NVARCHAR(450) NOT NULL, -- dbo.AspNetUsers.Id
CREATEDAT DATETIME2 NOT NULL
CONSTRAINT DF_VOCABULARY_CREATEDAT DEFAULT SYSUTCDATETIME(),
CONSTRAINT PK_VOCABULARY PRIMARY KEY(VOCABULARYID),
CONSTRAINT UQ_VOCABULARY_WORDNORM UNIQUE(WORDNORM)
CONSTRAINT UQ_VOCABULARY_WORDNORM UNIQUE(WORDNORM),
CONSTRAINT FK_VOCABULARY_AspNetUsers FOREIGN KEY (USERID) REFERENCES dbo.AspNetUsers(Id) ON DELETE NO ACTION
);
GO

Expand All @@ -30,12 +32,15 @@ CREATE TABLE EXAMPLES_T (
VOCABULARYID INT NOT NULL,
EXAMPLE_TEXT NVARCHAR(MAX) NOT NULL,
ENGLISH NVARCHAR(MAX),
USERID NVARCHAR(450) NOT NULL, -- dbo.AspNetUsers.Id
CREATEDAT DATETIME2 NOT NULL
);

ALTER TABLE dbo.EXAMPLES_T ADD CONSTRAINT PK_EXAMPLES PRIMARY KEY(EXAMPLEID);
ALTER TABLE dbo.EXAMPLES_T ADD CONSTRAINT FK_EXAMPLES_VOCABULARY FOREIGN KEY(VOCABULARYID) REFERENCES dbo.VOCABULARY_T(VOCABULARYID) ON DELETE CASCADE;
ALTER TABLE dbo.EXAMPLES_T ADD CONSTRAINT DF_EXAMPLES_CREATEDAT DEFAULT SYSUTCDATETIME() FOR CREATEDAT;
-- NOTE: Keep examples even if user got deleted.
ALTER TABLE dbo.EXAMPLES_T ADD CONSTRAINT FK_EXAMPLES_AspNetUsers FOREIGN KEY (USERID) REFERENCES dbo.AspNetUsers(Id) ON DELETE NO ACTION;

GO

Expand Down
69 changes: 69 additions & 0 deletions database/schema/create_usp_AddVocabularyEntryWithExamples.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
USE [spanish_ex]
GO

/****** Object: StoredProcedure [dbo].[usp_AddVocabularyEntryWithExamples] Script Date: 06.03.2026 10:14:22 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO



-- =============================================
-- Author: David Pasch
-- Create date: 2026-03-02
-- Description: Adds a new vocabulary entry with examples.
-- =============================================
CREATE PROCEDURE [dbo].[usp_AddVocabularyEntryWithExamples]
@wordNorm NVARCHAR(100),
@english NVARCHAR(MAX) = NULL,
@examplesTT dbo.Examples_TT READONLY,
@userId NVARCHAR(450)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;

SET XACT_ABORT ON;

BEGIN TRAN

INSERT INTO [dbo].[VOCABULARY_T]
([WORDNORM]
,[ENGLISH]
,[USERID])
VALUES
(@wordNorm
,@english
,@userId);

DECLARE @vocId INT = SCOPE_IDENTITY();

INSERT INTO [dbo].[EXAMPLES_T]
([VOCABULARYID]
,[EXAMPLE_TEXT]
,[ENGLISH]
,[USERID])
SELECT @vocId, EXAMPLE_TEXT, ENGLISH, @userId
FROM @examplesTT;

COMMIT TRAN

SELECT [VOCABULARYID]
,[WORDNORM]
,[ENGLISH]
FROM [dbo].[VOCABULARY_T]
WHERE VOCABULARYID = @vocId;

SELECT [EXAMPLEID]
,[EXAMPLE_TEXT]
,[ENGLISH]
FROM [dbo].[EXAMPLES_T]
WHERE VOCABULARYID = @vocId;

END
GO


18 changes: 18 additions & 0 deletions database/tests/test-usp_AddVocabularyEntryWithExamples.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
USE spanish_ex;
GO

DECLARE @wordNorm NVARCHAR(100) = N'comer',
@english NVARCHAR(MAX) = NULL,
@examplesTT dbo.Examples_TT,
@userId NVARCHAR(450) = N'f401d78e-3dda-4bce-9f84-55ee158c57f9';

INSERT INTO @examplesTT(EXAMPLE_TEXT, ENGLISH)
VALUES
(N'Como carne.', NULL),
(N'No como queso.', N'I don''t eat cheese.');

BEGIN TRAN

EXEC dbo.usp_AddVocabularyEntryWithExamples @wordNorm, @english, @examplesTT, @userId

ROLLBACK
87 changes: 65 additions & 22 deletions docs/spec/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The reason why we limit ourselves to verbs is that verbs are generally considere
The system distinguishes between the following user roles:

- *Anonymous*: Any unauthenticated user.
- *Authorized User*: An authenticated user.
- *Author*: An authenticated user who edits the vocabulary.
- *Admin*: A system administrator.


Expand All @@ -50,23 +50,23 @@ The different user roles are engaged in the following use cases.
All Users:

- **UC-1**: Search Vocabulary
- **UC-99**: Register New User


Authorized User:
Author:

- UC-02 Browse One's Own Vocabulary Entries
- UC-03 Create New Vocabulary Entry
- UC-04 Edit One's Own Vocabulary Entry
- UC-05 Delete One's Own Vocabulary Entry
- UC-2: Browse One's Own Vocabulary Entries
- **UC-3**: Create New Vocabulary Entry
- **UC-4**: Add Example to Existing Vocabulary Entry
- UC-5: Delete One's Own Vocabulary Entry


In the following sub-sections, selected use cases are detailled.


## UC-1 Search Vocabulary
## UC-1: Search Vocabulary


- **Actors**: Anonymous, Authorized User, Admin
- **Actors**: Anonymous, Author, Admin
- **Input**:
- Verb possibly in conjugated form or non-existent in database.
- **Preconditions**: None.
Expand All @@ -79,38 +79,81 @@ In the following sub-sections, selected use cases are detailled.
- No data is modified.


## UC-2 Browse One's Own Vocabulary Entries
## UC-3: Create New Vocabulary Entry

- **Actors**: Author (authenticated user)
- **Input**:
- Verb in infinitive or conjugated form
- One or more example sentences (optional translation)
- **Preconditions**:
- Author is authenticated.
- Author account is active.
- Verb does not exist in the system.
- **Main Flow**:
1. Author submits a verb and one or more usage examples.
2. System verifies that the author account is active.
3. System validates and normalizes verb by using an external dictionary.
4. If the verb is valid, the system stores the normalized verb and associated examples.
- **Alternative Flow A**:
- If the verb is not recognized by the external dictionary, the system rejects the request.
- **Postconditions**:
- The normalized verb and its example sentences are stored in the database.
- The author is recorded as the creator of the stored data.


## UC-3 Create New Example Sentence(s)
## UC-4: Add Example to Existing Vocabulary Entry

- **Actors**: Author (authenticated user)
- **Input**:
- Vocabulary entry
- One or more example sentences (optional translation)
- **Preconditions**:
- The author is authenticated.
- The same example sentences may already be associated with another user.
- **Main Flow**:
1. Author selects an existing vocabulary entry.
2. Author submits one or more usage examples.
3. System persists the examples.
- **Postconditions**:
- The example sentences are associated with the vocabulary entry.
- The author is recorded as the creator of the stored data.


- **Actor**: Authorized User (Bob)
## UC-99: Register New User

- **Actors**: Anonymous, Author, Admin
- **Input**:
- Verb possibly in conjugated form or non-existent in database.
- At least one example sentence optionally with translation.
- Username
- Email
- Password
- **Preconditions**:
- Bob is authenticated.
- Bob is not banned.
- The user is not authenticated.
- Username is not yet registered.
- **Main Flow**:
1. Bob submits a verb and at least one example sentence.
1. If the verb does not yet exist in the database, the system verifies, normalizes, and translates it and creates a new vocabulary entry.
1. The provided example sentence(s) and translation(s) are associated with the vocabulary entry and Bob as the author.
1. User registers by providing username, email, password.
2. System validates input.
3. System checks that username is unique.
4. System creates a new user account.
- **Postconditions**:
- The verb together with the example(s) exist in the system.
- A new user account exists in the system.
- The user can authenticate using the provided credentials.


# Requirements


## Functional Requirements


- **FR-1**: The system shall allow any user to search for example sentences associated with a given Spanish verb. (Related use cases: UC-1)
- **FR-2**: The system shall accept both infinitive and conjugated verb forms as search input. (Related use cases: UC-1)
- **FR-2**: The system shall accept both infinitive and conjugated verb forms as search input. (Related use cases: UC-1, UC-3)
- **FR-3**: The system returns the queried verb in its infinitive form together with an English translation. (Related use cases: UC-1)
- **FR-4**: The system returns example sentences with their translation, where available. (Related use cases: UC-1)
- **FR-5**: The system shall enable an author to create a new vocabulary entry with one or more usage examples, optionally with translation. (Related use cases: UC-3)
- **FR-6**: The system shall enable an author to add one or more usage examples, optionally with translation, to an existing vocabulary entry. (Related use cases: UC-4)
- **FR-7**: The system shall record the author who has created a new vocabulary entry or example. (Related use cases: UC-3, UC-4)
- **FR-99**: The system shall allow any user to register a new account. (Related use cases: UC-99)
- **FR-100**: The system shall store passwords securely using hashing. (Related use cases: UC-99)
- **FR-101**: The system shall prevent duplicate usernames or emails. (Related use cases: UC-99)


## Non-Functional Requirements
Expand Down
31 changes: 31 additions & 0 deletions src/SpanishByExample.Api/ApiUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using SpanishByExample.Api.Dtos;
using SpanishByExample.Core.Commands;

namespace SpanishByExample.Api;

/// <summary>
/// Utilities for Api.
/// </summary>
public static class ApiUtils
{
/// <summary>
/// Converts <c>VocabularyEntryRequestDto</c> to <c>CreateVocabularyCommand</c>.
/// </summary>
/// <param name="vocDto">DTO to create vocabulary entry.</param>
/// <returns><c>CreateVocabularyCommand</c> populated with data from DTO.</returns>
public static CreateVocabularyCommand ToCommand(this VocabularyEntryRequestDto vocDto)
{
CreateVocabularyCommand vocabularyCommand = new()
{
RawWord = vocDto.RawWord,
EnglishTranslation = vocDto.EnglishTranslation,
CreateExampleCommands = vocDto.Examples.Select(exDto => new CreateExampleCommand()
{
ExampleText = exDto.ExampleText,
EnglishTranslation = exDto.EnglishTranslation
}).ToList().AsReadOnly(),
};

return vocabularyCommand;
}
}
9 changes: 4 additions & 5 deletions src/SpanishByExample.Api/Controllers/ExamplesController.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using SpanishByExample.Core.Errors;
using SpanishByExample.Core.Interfaces;
using SpanishByExample.Core.Model;
using SpanishByExample.Core.Services;
using SpanishByExample.Core.Entities;

namespace SpanishByExample.Api.Controllers;

/// <summary>
/// Handles requests for the example sentences.
/// </summary>
/// <param name="log">Injected logger.</param>
/// <param name="examplesService">Injected Examples service.</param>
[Route("api/[controller]")]
[ApiController]
Expand Down
Loading