2 Commits

Author SHA1 Message Date
e63b254395 fix(sravni): update entities to match current sravni.ru API
dataSources changed from map[string]ReduxDataSource (objects) to
map[string]string (simple key-value pairs). Dictionaries block removed
from ReduxConfig entirely. ReduxConfigSorting now includes Name and
Direction fields alongside Property.
2026-06-28 17:09:26 +00:00
5c529ef060 fix: critical bugs from code review (data corruption, error contract, HTTP hardening) (#5)
Co-authored-by: hermes <hermes@noreply.localhost>
Co-committed-by: hermes <hermes@noreply.localhost>
2026-06-28 14:04:25 +00:00
15 changed files with 65 additions and 87 deletions

View File

@ -85,19 +85,6 @@ type Filters struct {
IsCourseProfeccion Flagged `json:"isCourseProfession"`
}
type DataSourceType string
const (
DataSourceTypeDictionaryConfig DataSourceType = "dictionaryConfig"
DataSourceTypeDictionary DataSourceType = "dictionary"
DataSourceTypeOrganization DataSourceType = "organization"
)
type ReduxDataSource struct {
Type DataSourceType `json:"type"`
Source string `json:"source"`
}
type ReduxConfigDictionaryBaseUnit struct {
Label string `json:"label"`
Value string `json:"value"`
@ -124,25 +111,18 @@ type ReduxConfigFilterUnit struct {
}
type ReduxConfigSorting struct {
Property string `json:"property"`
// 3 more fields
Name string `json:"name"`
Property string `json:"property"`
Direction string `json:"direction"`
}
type ReduxConfig struct {
Default struct {
SortingParameters []ReduxConfigSorting `json:"sortingParameters"`
} `json:"default"`
DataSources map[string]ReduxDataSource `json:"dataSources"`
Dictionaries struct {
PriceFilter []ReduxConfigDictionaryRangeUnit `json:"dictionaryPriceFilter"`
GraphicFilter []ReduxConfigDictionaryBaseUnit `json:"dictionaryGraphicFilterNew"`
FormatFilter []ReduxConfigDictionaryBaseUnit `json:"dictionaryFormatFilterNew"`
LevelFilter []ReduxConfigDictionaryBaseUnit `json:"dictionaryLevelFilterNew"`
TimeFilter []ReduxConfigDictionaryRangeUnit `json:"dictionaryTimeFilter"`
InstallmentFilter []ReduxConfigDictionaryRangeUnit `json:"dictionaryInstallmentFilter"`
}
Filters []ReduxConfigFilterUnit `json:"filters"`
Sorting []ReduxConfigSorting `json:"sorting"`
DataSources map[string]string `json:"dataSources"`
Filters []ReduxConfigFilterUnit `json:"filters"`
Sorting []ReduxConfigSorting `json:"sorting"`
}
type InitialReduxState struct {

View File

@ -5,7 +5,7 @@ import (
"testing"
"git.loyso.art/frx/kurious/internal/kurious/domain"
mockrepo "git.loyso.art/frx/kurious/internal/kurious/domain/mocks"
mockrepo "git.loyso.art/frx/kurious/internal/kurious/adapters/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"

View File

@ -0,0 +1,42 @@
package adapters
import (
"context"
cerrors "git.loyso.art/frx/kurious/internal/common/errors"
"git.loyso.art/frx/kurious/internal/kurious/domain"
)
type NotImplementedOrganizationRepository struct{}
func (NotImplementedOrganizationRepository) ListStats(
context.Context,
domain.ListOrganizationsParams,
) ([]domain.OrganizationStat, error) {
return nil, cerrors.ErrNotImplemented
}
func (NotImplementedOrganizationRepository) List(context.Context, domain.ListOrganizationsParams) ([]domain.Organization, error) {
return nil, cerrors.ErrNotImplemented
}
func (NotImplementedOrganizationRepository) Get(context.Context, domain.GetOrganizationParams) (domain.Organization, error) {
return domain.Organization{}, cerrors.ErrNotImplemented
}
func (NotImplementedOrganizationRepository) Create(context.Context, domain.CreateOrganizationParams) (domain.Organization, error) {
return domain.Organization{}, cerrors.ErrNotImplemented
}
func (NotImplementedOrganizationRepository) Delete(ctx context.Context, id string) error {
return cerrors.ErrNotImplemented
}
type NotImplementedLearningCategory struct{}
func (NotImplementedLearningCategory) Upsert(context.Context, domain.LearningCategory) error {
return cerrors.ErrNotImplemented
}
func (NotImplementedLearningCategory) List(context.Context) ([]domain.LearningCategory, error) {
return nil, cerrors.ErrNotImplemented
}
func (NotImplementedLearningCategory) Get(context.Context, string) (domain.LearningCategory, error) {
return domain.LearningCategory{}, cerrors.ErrNotImplemented
}

View File

@ -8,6 +8,7 @@ import (
"log/slog"
"strings"
cerrors "git.loyso.art/frx/kurious/internal/common/errors"
"git.loyso.art/frx/kurious/internal/common/xslices"
"git.loyso.art/frx/kurious/internal/kurious/domain"
@ -156,7 +157,7 @@ func (r *sqliteLearingCategoryRepository) Get(ctx context.Context, id string) (c
err = r.db.GetContext(ctx, &cdb, query, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return domain.LearningCategory{}, domain.ErrNotFound
return domain.LearningCategory{}, cerrors.ErrNotFound
}
return domain.LearningCategory{}, fmt.Errorf("executing query: %w", err)
}

View File

@ -3,6 +3,7 @@ package adapters
import (
"testing"
"git.loyso.art/frx/kurious/internal/common/errors"
"git.loyso.art/frx/kurious/internal/common/nullable"
"git.loyso.art/frx/kurious/internal/kurious/domain"
@ -75,7 +76,7 @@ func (s *sqliteLearningCategoriesRepositorySuite) TestUpsert() {
const categoryID = "test-id-1"
repo := s.connection.LearningCategory()
gotCategory, err := repo.Get(s.ctx, categoryID)
s.ErrorIs(err, domain.ErrNotFound)
s.ErrorIs(err, errors.ErrNotFound)
s.Empty(gotCategory)
createdCategory := domain.LearningCategory{

View File

@ -9,6 +9,7 @@ import (
"strings"
"time"
cerrors "git.loyso.art/frx/kurious/internal/common/errors"
"git.loyso.art/frx/kurious/internal/common/xslices"
"git.loyso.art/frx/kurious/internal/kurious/domain"
"go.opentelemetry.io/otel/attribute"
@ -228,7 +229,7 @@ func (r *sqliteOrganizationRepository) Get(ctx context.Context, params domain.Ge
err = r.db.GetContext(ctx, &orgdb, query, args...)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return out, domain.ErrNotFound
return out, cerrors.ErrNotFound
}
return out, fmt.Errorf("executing query: %w", err)
}
@ -306,14 +307,14 @@ func (r *sqliteOrganizationRepository) Delete(ctx context.Context, id string) (e
result, err := r.db.ExecContext(ctx, query, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return domain.ErrNotFound
return cerrors.ErrNotFound
}
return fmt.Errorf("executing query: %w", err)
}
affected, _ := result.RowsAffected()
if affected == 0 {
return domain.ErrNotFound
return cerrors.ErrNotFound
}
return nil

View File

@ -110,11 +110,11 @@ func (conn *YDBConnection) Close() error {
}
func (conn *YDBConnection) Organization() domain.OrganizationRepository {
return domain.NotImplementedOrganizationRepository{}
return NotImplementedOrganizationRepository{}
}
func (conn *YDBConnection) LearningCategory() domain.LearningCategoryRepository {
return domain.NotImplementedLearningCategory{}
return NotImplementedLearningCategory{}
}
func (conn *YDBConnection) CourseRepository() *ydbCourseRepository {

View File

@ -9,7 +9,7 @@ import (
"git.loyso.art/frx/kurious/internal/common/nullable"
"git.loyso.art/frx/kurious/internal/kurious/domain"
mockrepo "git.loyso.art/frx/kurious/internal/kurious/domain/mocks"
mockrepo "git.loyso.art/frx/kurious/internal/kurious/adapters/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"

View File

@ -8,7 +8,7 @@ import (
"testing"
"git.loyso.art/frx/kurious/internal/kurious/domain"
mockrepo "git.loyso.art/frx/kurious/internal/kurious/domain/mocks"
mockrepo "git.loyso.art/frx/kurious/internal/kurious/adapters/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"

View File

@ -1,12 +0,0 @@
package domain
const (
ErrNotFound PlainError = "not found"
ErrNotImplemented PlainError = "not implemented"
)
type PlainError string
func (err PlainError) Error() string {
return string(err)
}

View File

@ -80,7 +80,7 @@ type ListStatisticsResult struct {
LearningTypeStatistics []StatisticUnit
}
//go:generate mockery --name CourseRepository
//go:generate mockery --name CourseRepository --output ../adapters/mocks
type CourseRepository interface {
// List courses by specifid parameters.
List(context.Context, ListCoursesParams) (ListCoursesResult, error)
@ -126,7 +126,7 @@ type ListOrganizationsParams struct {
IDs []string
}
//go:generate mockery --name OrganizationRepository
//go:generate mockery --name OrganizationRepository --output ../adapters/mocks
type OrganizationRepository interface {
ListStats(context.Context, ListOrganizationsParams) ([]OrganizationStat, error)
List(context.Context, ListOrganizationsParams) ([]Organization, error)
@ -135,44 +135,10 @@ type OrganizationRepository interface {
Delete(ctx context.Context, id string) error
}
type NotImplementedOrganizationRepository struct{}
func (NotImplementedOrganizationRepository) ListStats(
context.Context,
ListOrganizationsParams,
) ([]OrganizationStat, error) {
return nil, ErrNotImplemented
}
func (NotImplementedOrganizationRepository) List(context.Context, ListOrganizationsParams) ([]Organization, error) {
return nil, ErrNotImplemented
}
func (NotImplementedOrganizationRepository) Get(context.Context, GetOrganizationParams) (Organization, error) {
return Organization{}, ErrNotImplemented
}
func (NotImplementedOrganizationRepository) Create(context.Context, CreateOrganizationParams) (Organization, error) {
return Organization{}, ErrNotImplemented
}
func (NotImplementedOrganizationRepository) Delete(ctx context.Context, id string) error {
return ErrNotImplemented
}
//go:generate mockery --name LearningCategoryRepository
//go:generate mockery --name LearningCategoryRepository --output ../adapters/mocks
type LearningCategoryRepository interface {
Upsert(context.Context, LearningCategory) error
List(context.Context) ([]LearningCategory, error)
Get(context.Context, string) (LearningCategory, error)
}
type NotImplementedLearningCategory struct{}
func (NotImplementedLearningCategory) Upsert(context.Context, LearningCategory) error {
return ErrNotImplemented
}
func (NotImplementedLearningCategory) List(context.Context) ([]LearningCategory, error) {
return nil, ErrNotImplemented
}
func (NotImplementedLearningCategory) Get(context.Context, string) (LearningCategory, error) {
return LearningCategory{}, ErrNotImplemented
}

View File

@ -9,7 +9,6 @@ import (
"git.loyso.art/frx/kurious/internal/common/errors"
"git.loyso.art/frx/kurious/internal/common/xcontext"
"git.loyso.art/frx/kurious/internal/kurious/domain"
"git.loyso.art/frx/kurious/internal/kurious/service"
"git.loyso.art/frx/kurious/pkg/xdefault"
@ -50,7 +49,7 @@ func handleError(ctx context.Context, err error, w http.ResponseWriter, log *slo
case stderrors.As(err, &valErr):
errorString = valErr.Error()
code = http.StatusBadRequest
case stderrors.Is(err, errors.ErrNotFound), stderrors.Is(err, domain.ErrNotFound):
case stderrors.Is(err, errors.ErrNotFound):
errorString = err.Error()
code = http.StatusNotFound
default: