diff --git a/back/.gitignore b/back/.gitignore
new file mode 100644
index 0000000..104b544
--- /dev/null
+++ b/back/.gitignore
@@ -0,0 +1,484 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from `dotnet new gitignore`
+
+# dotenv files
+.env
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+.idea
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# Vim temporary swap files
+*.swp
diff --git a/back/Api.sln b/back/Api.sln
new file mode 100644
index 0000000..c0cdbbb
--- /dev/null
+++ b/back/Api.sln
@@ -0,0 +1,40 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Controllers", "Controllers\Controllers.csproj", "{BEA05282-6DE5-4D67-983A-EF13AAF8EECE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services", "Services\Services.csproj", "{B937E273-1D6E-42DF-BD34-14DA5E71FC71}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "Contracts\Contracts.csproj", "{B9246DBE-67B0-4B0F-8648-E9ED2EB7172C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "Infrastructure\Infrastructure.csproj", "{A35121D4-7D41-4266-8DA4-87135E8ABF89}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {BEA05282-6DE5-4D67-983A-EF13AAF8EECE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BEA05282-6DE5-4D67-983A-EF13AAF8EECE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BEA05282-6DE5-4D67-983A-EF13AAF8EECE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BEA05282-6DE5-4D67-983A-EF13AAF8EECE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B937E273-1D6E-42DF-BD34-14DA5E71FC71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B937E273-1D6E-42DF-BD34-14DA5E71FC71}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B937E273-1D6E-42DF-BD34-14DA5E71FC71}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B937E273-1D6E-42DF-BD34-14DA5E71FC71}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B9246DBE-67B0-4B0F-8648-E9ED2EB7172C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B9246DBE-67B0-4B0F-8648-E9ED2EB7172C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B9246DBE-67B0-4B0F-8648-E9ED2EB7172C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B9246DBE-67B0-4B0F-8648-E9ED2EB7172C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A35121D4-7D41-4266-8DA4-87135E8ABF89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A35121D4-7D41-4266-8DA4-87135E8ABF89}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A35121D4-7D41-4266-8DA4-87135E8ABF89}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A35121D4-7D41-4266-8DA4-87135E8ABF89}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/back/Contracts/Contracts.csproj b/back/Contracts/Contracts.csproj
new file mode 100644
index 0000000..fa71b7a
--- /dev/null
+++ b/back/Contracts/Contracts.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/back/Contracts/DTOs/ChangeRecordDto.cs b/back/Contracts/DTOs/ChangeRecordDto.cs
new file mode 100644
index 0000000..21d140c
--- /dev/null
+++ b/back/Contracts/DTOs/ChangeRecordDto.cs
@@ -0,0 +1,10 @@
+namespace Contracts.DTO;
+
+public class ChangeRecordDto
+{
+ public Guid Id { get; set; }
+ public Guid UserId { get; set; }
+ public Guid? SpendingGroupId { get; set; }
+ public decimal Sum { get; set; }
+ public DateTime ChangedAt { get; set; }
+}
\ No newline at end of file
diff --git a/back/Contracts/DTOs/SpendingGroupDto.cs b/back/Contracts/DTOs/SpendingGroupDto.cs
new file mode 100644
index 0000000..dd001fd
--- /dev/null
+++ b/back/Contracts/DTOs/SpendingGroupDto.cs
@@ -0,0 +1,10 @@
+namespace Contracts.DTO;
+
+public class SpendingGroupDto
+{
+ public Guid Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+ public Guid UserId { get; set; }
+ public List ChangeRecords { get; set; } = new();
+ public List SpendingPlans { get; set; } = new();
+}
\ No newline at end of file
diff --git a/back/Contracts/DTOs/SpendingPlanDto.cs b/back/Contracts/DTOs/SpendingPlanDto.cs
new file mode 100644
index 0000000..fc88578
--- /dev/null
+++ b/back/Contracts/DTOs/SpendingPlanDto.cs
@@ -0,0 +1,10 @@
+namespace Contracts.DTO;
+
+public class SpendingPlanDto
+{
+ public Guid Id { get; set; }
+ public Guid SpendingGroupId { get; set; }
+ public decimal Sum { get; set; }
+ public DateTime StartAt { get; set; }
+ public DateTime EndAt { get; set; }
+}
\ No newline at end of file
diff --git a/back/Contracts/DTOs/UserDTO.cs b/back/Contracts/DTOs/UserDTO.cs
new file mode 100644
index 0000000..e312313
--- /dev/null
+++ b/back/Contracts/DTOs/UserDTO.cs
@@ -0,0 +1,9 @@
+namespace Contracts.DTO;
+
+public class UserDto
+{
+ public Guid Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+ public string Password { get; set; } = string.Empty;
+ public decimal Balance { get; set; }
+}
\ No newline at end of file
diff --git a/back/Contracts/DTOs/UserLoginDTO.cs b/back/Contracts/DTOs/UserLoginDTO.cs
new file mode 100644
index 0000000..73210e9
--- /dev/null
+++ b/back/Contracts/DTOs/UserLoginDTO.cs
@@ -0,0 +1,7 @@
+namespace Contracts.DTO;
+
+public class UserLoginDTO
+{
+ public string Name { get; set; } = string.Empty;
+ public string Password { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/back/Contracts/Mappers/ChangeRecordMapper.cs b/back/Contracts/Mappers/ChangeRecordMapper.cs
new file mode 100644
index 0000000..606344d
--- /dev/null
+++ b/back/Contracts/Mappers/ChangeRecordMapper.cs
@@ -0,0 +1,15 @@
+using Contracts.DTO;
+using Contracts.ViewModels;
+
+namespace Contracts.Mappers;
+
+public static class ChangeRecordMapper
+{
+ public static ChangeRecordViewModel ToView(this ChangeRecordDto dto)
+ => new()
+ {
+ Id = dto.Id,
+ Sum = dto.Sum,
+ ChangedAt = dto.ChangedAt
+ };
+}
\ No newline at end of file
diff --git a/back/Contracts/Mappers/SpendingGroupMapper.cs b/back/Contracts/Mappers/SpendingGroupMapper.cs
new file mode 100644
index 0000000..fd6a987
--- /dev/null
+++ b/back/Contracts/Mappers/SpendingGroupMapper.cs
@@ -0,0 +1,16 @@
+using Contracts.DTO;
+using Contracts.ViewModels;
+
+namespace Contracts.Mappers;
+
+public static class SpendingGroupMapper
+{
+ public static SpendingGroupViewModel ToView(this SpendingGroupDto dto)
+ => new()
+ {
+ Id = dto.Id,
+ Name = dto.Name,
+ ChangeRecords = dto.ChangeRecords.Select(x => x.ToView()).ToList(),
+ SpendingPlans = dto.SpendingPlans.Select(x => x.ToView()).ToList()
+ };
+}
\ No newline at end of file
diff --git a/back/Contracts/Mappers/SpendingPlanMapper.cs b/back/Contracts/Mappers/SpendingPlanMapper.cs
new file mode 100644
index 0000000..84504b9
--- /dev/null
+++ b/back/Contracts/Mappers/SpendingPlanMapper.cs
@@ -0,0 +1,16 @@
+using Contracts.DTO;
+using Contracts.ViewModels;
+
+namespace Contracts.Mappers;
+
+public static class SpendingPlanMapper
+{
+ public static SpendingPlanViewModel ToView(this SpendingPlanDto dto)
+ => new()
+ {
+ Id = dto.Id,
+ StartAt = dto.StartAt,
+ EndAt = dto.EndAt,
+ Sum = dto.Sum
+ };
+}
\ No newline at end of file
diff --git a/back/Contracts/Mappers/UserMapper.cs b/back/Contracts/Mappers/UserMapper.cs
new file mode 100644
index 0000000..a32afed
--- /dev/null
+++ b/back/Contracts/Mappers/UserMapper.cs
@@ -0,0 +1,16 @@
+using Contracts.DTO;
+using Contracts.ViewModels;
+
+namespace Contracts.Mappers;
+
+public static class UserMapper
+{
+ public static UserViewModel ToView(this UserDto user)
+ => new()
+ {
+ Id = user.Id,
+ Name = user.Name,
+ Balance = user.Balance
+ };
+
+}
\ No newline at end of file
diff --git a/back/Contracts/Repositories/IChangeRecordRepo.cs b/back/Contracts/Repositories/IChangeRecordRepo.cs
new file mode 100644
index 0000000..d8e835e
--- /dev/null
+++ b/back/Contracts/Repositories/IChangeRecordRepo.cs
@@ -0,0 +1,13 @@
+using Contracts.DTO;
+using Contracts.SearchModels;
+
+namespace Contracts.Repositories;
+
+public interface IChangeRecordRepo
+{
+ Task Create(ChangeRecordDto changeRecord);
+ Task Get(ChangeRecordSearch search);
+ Task> GetList(ChangeRecordSearch? search = null);
+ Task Update(ChangeRecordDto changeRecord);
+ Task Delete(ChangeRecordSearch search);
+}
\ No newline at end of file
diff --git a/back/Contracts/Repositories/ISpendingGroupRepo.cs b/back/Contracts/Repositories/ISpendingGroupRepo.cs
new file mode 100644
index 0000000..f8f7999
--- /dev/null
+++ b/back/Contracts/Repositories/ISpendingGroupRepo.cs
@@ -0,0 +1,13 @@
+using Contracts.DTO;
+using Contracts.SearchModels;
+
+namespace Contracts.Repositories;
+
+public interface ISpendingGroupRepo
+{
+ Task Get(SpendingGroupSearch search);
+ Task> GetList(SpendingGroupSearch? search = null);
+ Task Create(SpendingGroupDto spendingGroup);
+ Task Delete(SpendingGroupSearch search);
+ Task Update(SpendingGroupDto spendingGroup);
+}
diff --git a/back/Contracts/Repositories/ISpendingPlanRepo.cs b/back/Contracts/Repositories/ISpendingPlanRepo.cs
new file mode 100644
index 0000000..8dcfd4b
--- /dev/null
+++ b/back/Contracts/Repositories/ISpendingPlanRepo.cs
@@ -0,0 +1,13 @@
+using Contracts.DTO;
+using Contracts.SearchModels;
+
+namespace Contracts.Repositories;
+
+public interface ISpendingPlanRepo
+{
+ Task Create(SpendingPlanDto dto);
+ Task Update(SpendingPlanDto dto);
+ Task Delete(SpendingPlanSearch search);
+ Task Get(SpendingPlanSearch search);
+ Task> GetList(SpendingPlanSearch? search = null);
+}
\ No newline at end of file
diff --git a/back/Contracts/Repositories/IUserRepo.cs b/back/Contracts/Repositories/IUserRepo.cs
new file mode 100644
index 0000000..dab5cd0
--- /dev/null
+++ b/back/Contracts/Repositories/IUserRepo.cs
@@ -0,0 +1,13 @@
+using Contracts.DTO;
+using Contracts.SearchModels;
+
+namespace Contracts.Repositories;
+
+public interface IUserRepo
+{
+ public Task Get(UserSearch search);
+ public Task Create(UserDto user);
+ public Task Update(UserDto user);
+ public Task ChangeBalance(UserSearch search, decimal amount);
+ public Task Delete(UserSearch search);
+}
\ No newline at end of file
diff --git a/back/Contracts/SearchModels/ChangeRecordSearch.cs b/back/Contracts/SearchModels/ChangeRecordSearch.cs
new file mode 100644
index 0000000..65a5094
--- /dev/null
+++ b/back/Contracts/SearchModels/ChangeRecordSearch.cs
@@ -0,0 +1,9 @@
+namespace Contracts.SearchModels;
+
+public class ChangeRecordSearch
+{
+ public Guid? Id { get; set; }
+ public Guid? SpendingGroupId { get; set; }
+ public DateTime? From { get; set; }
+ public DateTime? To { get; set; }
+}
\ No newline at end of file
diff --git a/back/Contracts/SearchModels/SpendingGroupSearch.cs b/back/Contracts/SearchModels/SpendingGroupSearch.cs
new file mode 100644
index 0000000..927a206
--- /dev/null
+++ b/back/Contracts/SearchModels/SpendingGroupSearch.cs
@@ -0,0 +1,7 @@
+namespace Contracts.SearchModels;
+
+public class SpendingGroupSearch
+{
+ public Guid? Id { get; set; }
+ public string? Name { get; set; }
+}
\ No newline at end of file
diff --git a/back/Contracts/SearchModels/SpendingPlanSearch.cs b/back/Contracts/SearchModels/SpendingPlanSearch.cs
new file mode 100644
index 0000000..0b53198
--- /dev/null
+++ b/back/Contracts/SearchModels/SpendingPlanSearch.cs
@@ -0,0 +1,6 @@
+namespace Contracts.SearchModels;
+
+public class SpendingPlanSearch
+{
+ public Guid? Id { get; set; }
+}
\ No newline at end of file
diff --git a/back/Contracts/SearchModels/UserSearch.cs b/back/Contracts/SearchModels/UserSearch.cs
new file mode 100644
index 0000000..7ea4178
--- /dev/null
+++ b/back/Contracts/SearchModels/UserSearch.cs
@@ -0,0 +1,7 @@
+namespace Contracts.SearchModels;
+
+public class UserSearch
+{
+ public Guid? Id { get; set; }
+ public string? Name { get; set; }
+}
\ No newline at end of file
diff --git a/back/Contracts/Services/IAuthService.cs b/back/Contracts/Services/IAuthService.cs
new file mode 100644
index 0000000..03548f7
--- /dev/null
+++ b/back/Contracts/Services/IAuthService.cs
@@ -0,0 +1,11 @@
+using Contracts.DTO;
+using Contracts.SearchModels;
+using Contracts.ViewModels;
+
+namespace Contracts.Services;
+
+public interface IAuthService
+{
+ public Task Login(UserLoginDTO loginData);
+ public Task Register(UserDto user);
+}
\ No newline at end of file
diff --git a/back/Contracts/Services/IChangeRecordService.cs b/back/Contracts/Services/IChangeRecordService.cs
new file mode 100644
index 0000000..b9939e0
--- /dev/null
+++ b/back/Contracts/Services/IChangeRecordService.cs
@@ -0,0 +1,13 @@
+using Contracts.DTO;
+using Contracts.SearchModels;
+using Contracts.ViewModels;
+
+namespace Contracts.Services;
+
+public interface IChangeRecordService
+{
+ Task Create(ChangeRecordDto model);
+ Task Update(ChangeRecordDto model);
+ Task Delete(ChangeRecordSearch search);
+ Task> GetList(ChangeRecordSearch? search = null);
+}
\ No newline at end of file
diff --git a/back/Contracts/Services/ISpendingGroupService.cs b/back/Contracts/Services/ISpendingGroupService.cs
new file mode 100644
index 0000000..938806f
--- /dev/null
+++ b/back/Contracts/Services/ISpendingGroupService.cs
@@ -0,0 +1,14 @@
+using Contracts.DTO;
+using Contracts.SearchModels;
+using Contracts.ViewModels;
+
+namespace Contracts.Services;
+
+public interface ISpendingGroupService
+{
+ Task GetDetails(SpendingGroupSearch search);
+ Task> GetList(SpendingGroupSearch? search = null);
+ Task Create(SpendingGroupDto model);
+ Task Update(SpendingGroupDto model);
+ Task Delete(SpendingGroupSearch search);
+}
\ No newline at end of file
diff --git a/back/Contracts/Services/ISpendingPlanService.cs b/back/Contracts/Services/ISpendingPlanService.cs
new file mode 100644
index 0000000..086ec5b
--- /dev/null
+++ b/back/Contracts/Services/ISpendingPlanService.cs
@@ -0,0 +1,14 @@
+using Contracts.DTO;
+using Contracts.SearchModels;
+using Contracts.ViewModels;
+
+namespace Contracts.Services;
+
+public interface ISpendingPlanService
+{
+ Task GetDetails(SpendingPlanSearch search);
+ Task> GetList(SpendingPlanSearch? search = null);
+ Task Create(SpendingPlanDto spendingPlan);
+ Task Delete(SpendingPlanSearch search);
+ Task Update(SpendingPlanDto spendingPlan);
+}
\ No newline at end of file
diff --git a/back/Contracts/Services/IUserService.cs b/back/Contracts/Services/IUserService.cs
new file mode 100644
index 0000000..736d3bf
--- /dev/null
+++ b/back/Contracts/Services/IUserService.cs
@@ -0,0 +1,12 @@
+using Contracts.DTO;
+using Contracts.SearchModels;
+using Contracts.ViewModels;
+
+namespace Contracts.Services;
+
+public interface IUserService
+{
+ public Task GetDetails(UserSearch search);
+ public Task UpdateUserData(UserDto user);
+ public Task Delete(UserSearch search);
+}
\ No newline at end of file
diff --git a/back/Contracts/ViewModels/ChangeRecordViewModel.cs b/back/Contracts/ViewModels/ChangeRecordViewModel.cs
new file mode 100644
index 0000000..3a75eb3
--- /dev/null
+++ b/back/Contracts/ViewModels/ChangeRecordViewModel.cs
@@ -0,0 +1,8 @@
+namespace Contracts.ViewModels;
+
+public class ChangeRecordViewModel
+{
+ public Guid Id { get; set; }
+ public decimal Sum { get; set; }
+ public DateTime ChangedAt { get; set; }
+}
\ No newline at end of file
diff --git a/back/Contracts/ViewModels/SpendingGroupViewModel.cs b/back/Contracts/ViewModels/SpendingGroupViewModel.cs
new file mode 100644
index 0000000..1a94317
--- /dev/null
+++ b/back/Contracts/ViewModels/SpendingGroupViewModel.cs
@@ -0,0 +1,9 @@
+namespace Contracts.ViewModels;
+
+public class SpendingGroupViewModel
+{
+ public Guid Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+ public List ChangeRecords { get; set; } = new();
+ public List SpendingPlans { get; set; } = new();
+}
\ No newline at end of file
diff --git a/back/Contracts/ViewModels/SpendingPlanViewModel.cs b/back/Contracts/ViewModels/SpendingPlanViewModel.cs
new file mode 100644
index 0000000..285cdd8
--- /dev/null
+++ b/back/Contracts/ViewModels/SpendingPlanViewModel.cs
@@ -0,0 +1,9 @@
+namespace Contracts.ViewModels;
+
+public class SpendingPlanViewModel
+{
+ public Guid Id { get; set; }
+ public DateTime StartAt { get; set; }
+ public DateTime EndAt { get; set; }
+ public decimal Sum { get; set; }
+}
\ No newline at end of file
diff --git a/back/Contracts/ViewModels/UserViewModel.cs b/back/Contracts/ViewModels/UserViewModel.cs
new file mode 100644
index 0000000..9bf54dc
--- /dev/null
+++ b/back/Contracts/ViewModels/UserViewModel.cs
@@ -0,0 +1,8 @@
+namespace Contracts.ViewModels;
+
+public class UserViewModel
+{
+ public Guid Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+ public decimal Balance { get; set; }
+}
\ No newline at end of file
diff --git a/back/Controllers/Controllers.csproj b/back/Controllers/Controllers.csproj
new file mode 100644
index 0000000..03a20aa
--- /dev/null
+++ b/back/Controllers/Controllers.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
diff --git a/back/Controllers/Controllers.http b/back/Controllers/Controllers.http
new file mode 100644
index 0000000..f5c450d
--- /dev/null
+++ b/back/Controllers/Controllers.http
@@ -0,0 +1,5 @@
+@Controllers_HostAddress = http://localhost:5125
+
+Accept: application/json
+
+###
diff --git a/back/Controllers/Controllers/AuthController.cs b/back/Controllers/Controllers/AuthController.cs
new file mode 100644
index 0000000..15eb928
--- /dev/null
+++ b/back/Controllers/Controllers/AuthController.cs
@@ -0,0 +1,63 @@
+using Contracts.DTO;
+using Contracts.Services;
+using Contracts.ViewModels;
+using Microsoft.AspNetCore.Mvc;
+using Services.Support.Exceptions;
+
+namespace Controllers.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+public class AuthController : ControllerBase
+{
+ private readonly IAuthService _authService;
+
+ public AuthController(IAuthService authService)
+ {
+ _authService = authService;
+ }
+
+ [HttpPost]
+ public async Task> Login([FromBody] UserLoginDTO loginData)
+ {
+ try
+ {
+ var user = await _authService.Login(loginData);
+ return Ok(user);
+ }
+ catch (ArgumentException ex)
+ {
+ return BadRequest(ex.Message);
+ }
+ catch (UserNotFoundException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpPost("register")]
+ public async Task> Register([FromBody] UserDto user)
+ {
+ try
+ {
+ var createdUser = await _authService.Register(user);
+ return CreatedAtAction(nameof(Login), new { name = createdUser.Name }, createdUser);
+ }
+ catch (ArgumentException ex)
+ {
+ return BadRequest(ex.Message);
+ }
+ catch (AlreadyExistsException ex)
+ {
+ return Conflict(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/back/Controllers/Controllers/ChangeRecordController.cs b/back/Controllers/Controllers/ChangeRecordController.cs
new file mode 100644
index 0000000..c823a99
--- /dev/null
+++ b/back/Controllers/Controllers/ChangeRecordController.cs
@@ -0,0 +1,100 @@
+using Contracts.DTO;
+using Contracts.SearchModels;
+using Contracts.Services;
+using Contracts.ViewModels;
+using Microsoft.AspNetCore.Mvc;
+using Services.Support.Exceptions;
+
+namespace Controllers.Controllers;
+
+[Route("api/[controller]")]
+[ApiController]
+public class ChangeRecordController : ControllerBase
+{
+ private readonly IChangeRecordService _changeRecordService;
+
+ public ChangeRecordController(IChangeRecordService changeRecordService)
+ {
+ _changeRecordService = changeRecordService;
+ }
+
+ [HttpPost]
+ public async Task> CreateChangeRecord(
+ [FromBody] ChangeRecordDto dto)
+ {
+ try
+ {
+ var record = await _changeRecordService.Create(dto);
+ return CreatedAtAction(nameof(GetChangeRecords), record);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpGet]
+ public async Task>> GetChangeRecords()
+ {
+ try
+ {
+ var records = await _changeRecordService.GetList();
+ return Ok(records);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpGet("filter")]
+ public async Task>> GetChangeRecords(
+ [FromQuery] ChangeRecordSearch search)
+ {
+ try
+ {
+ var records = await _changeRecordService.GetList(search);
+ return Ok(records);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpPatch]
+ public async Task> UpdateChangeRecord([FromBody] ChangeRecordDto dto)
+ {
+ try
+ {
+ var record = await _changeRecordService.Update(dto);
+ return Ok(record);
+ }
+ catch (EntityNotFoundException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpDelete]
+ public async Task DeleteChangeRecord([FromQuery] ChangeRecordSearch search)
+ {
+ try
+ {
+ var record = await _changeRecordService.Delete(search);
+ return Ok(record);
+ }
+ catch (EntityNotFoundException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/back/Controllers/Controllers/SpendingGroupController.cs b/back/Controllers/Controllers/SpendingGroupController.cs
new file mode 100644
index 0000000..635c06a
--- /dev/null
+++ b/back/Controllers/Controllers/SpendingGroupController.cs
@@ -0,0 +1,126 @@
+namespace Controllers.Extensions;
+
+using Contracts.DTO;
+using Contracts.SearchModels;
+using Contracts.Services;
+using Contracts.ViewModels;
+using Microsoft.AspNetCore.Mvc;
+using Services.Support.Exceptions;
+
+[ApiController]
+[Route("api/[controller]")]
+public class SpendingGroupController : ControllerBase
+{
+ private readonly ISpendingGroupService _spendingGroupService;
+
+ public SpendingGroupController(ISpendingGroupService spendingGroupService)
+ {
+ _spendingGroupService = spendingGroupService;
+ }
+ [HttpGet("{id}")]
+ public async Task> GetSpendingGroup(
+ Guid id,
+ [FromQuery] SpendingGroupSearch search)
+ {
+ try
+ {
+ search ??= new();
+ search.Id = id;
+
+ var group = await _spendingGroupService.GetDetails(search);
+ return Ok(group);
+ }
+ catch (EntityNotFoundException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpGet]
+ public async Task>> GetSpendingGroups()
+ {
+ try
+ {
+ var groups = await _spendingGroupService.GetList();
+ return Ok(groups);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpGet("filter")]
+ public async Task>> GetSpendingGroups(
+ [FromQuery] SpendingGroupSearch search)
+ {
+ try
+ {
+ var groups = await _spendingGroupService.GetList(search);
+ return Ok(groups);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpPost]
+ public async Task> CreateSpendingGroup(
+ [FromBody] SpendingGroupDto dto)
+ {
+ try
+ {
+ var group = await _spendingGroupService.Create(dto);
+ return CreatedAtAction(nameof(GetSpendingGroup), new { id = group.Id }, group);
+ }
+ catch (EntityNotFoundException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpPatch]
+ public async Task> UpdateSpendingGroup([FromBody] SpendingGroupDto dto)
+ {
+ try
+ {
+ var group = await _spendingGroupService.Update(dto);
+ return Ok(group);
+ }
+ catch (EntityNotFoundException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpDelete]
+ public async Task DeleteSpendingGroup([FromQuery] SpendingGroupSearch search)
+ {
+ try
+ {
+ var group = await _spendingGroupService.Delete(search);
+ return Ok(group);
+ }
+ catch (EntityNotFoundException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/back/Controllers/Controllers/SpendingPlanController.cs b/back/Controllers/Controllers/SpendingPlanController.cs
new file mode 100644
index 0000000..5f457b1
--- /dev/null
+++ b/back/Controllers/Controllers/SpendingPlanController.cs
@@ -0,0 +1,128 @@
+using Contracts.DTO;
+using Contracts.SearchModels;
+using Contracts.Services;
+using Contracts.ViewModels;
+using Microsoft.AspNetCore.Mvc;
+using Services.Support.Exceptions;
+
+namespace Controllers.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+public class SpendingPlanController : ControllerBase
+{
+ private readonly ISpendingPlanService _spendingPlanService;
+
+ public SpendingPlanController(ISpendingPlanService spendingPlanService)
+ {
+ _spendingPlanService = spendingPlanService;
+ }
+
+ [HttpGet("{id}")]
+ public async Task> GetSpendingPlan(
+ Guid id,
+ [FromQuery] SpendingPlanSearch search)
+ {
+ try
+ {
+ search ??= new();
+ search.Id = id;
+
+ var plan = await _spendingPlanService.GetDetails(search);
+ return Ok(plan);
+ }
+ catch (EntityNotFoundException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpGet]
+ public async Task>> GetSpendingPlans()
+ {
+ try
+ {
+ var plans = await _spendingPlanService.GetList();
+ return Ok(plans);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpGet("filter")]
+ public async Task>> GetSpendingPlans(
+ [FromQuery] SpendingPlanSearch search)
+ {
+ try
+ {
+ var plans = await _spendingPlanService.GetList(search);
+ return Ok(plans);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpPost]
+ public async Task> CreateSpendingPlan(
+ [FromBody] SpendingPlanDto dto)
+ {
+ try
+ {
+ var plan = await _spendingPlanService.Create(dto);
+ return CreatedAtAction(nameof(GetSpendingPlan), new { id = plan.Id }, plan);
+ }
+ catch (EntityNotFoundException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpPatch]
+ public async Task> UpdateSpendingPlan(
+ [FromBody] SpendingPlanDto dto)
+ {
+ try
+ {
+ var plan = await _spendingPlanService.Update(dto);
+ return Ok(plan);
+ }
+ catch (EntityNotFoundException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpDelete]
+ public async Task DeleteSpendingPlan([FromQuery] SpendingPlanSearch search)
+ {
+ try
+ {
+ var plan = await _spendingPlanService.Delete(search);
+ return Ok(plan);
+ }
+ catch (EntityNotFoundException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/back/Controllers/Controllers/UserController.cs b/back/Controllers/Controllers/UserController.cs
new file mode 100644
index 0000000..c93c276
--- /dev/null
+++ b/back/Controllers/Controllers/UserController.cs
@@ -0,0 +1,73 @@
+using Contracts.DTO;
+using Contracts.SearchModels;
+using Contracts.Services;
+using Contracts.ViewModels;
+using Microsoft.AspNetCore.Mvc;
+using Services.Support.Exceptions;
+
+namespace Controllers.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+public class UserController : ControllerBase
+{
+ private readonly IUserService _userService;
+
+ public UserController(IUserService userService)
+ {
+ _userService = userService;
+ }
+ [HttpGet]
+ public async Task> GetUser([FromQuery] UserSearch search)
+ {
+ try
+ {
+ var user = await _userService.GetDetails(search);
+ return Ok(user);
+ }
+ catch (UserNotFoundException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpPatch]
+ public async Task> UpdateUser([FromBody] UserDto user)
+ {
+ try
+ {
+ var updatedUser = await _userService.UpdateUserData(user);
+ return Ok(updatedUser);
+ }
+ catch (UserNotFoundException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+
+ [HttpDelete]
+ public async Task> DeleteUser([FromQuery] UserSearch search)
+ {
+ try
+ {
+ var deletedUser = await _userService.Delete(search);
+ return Ok(deletedUser);
+ }
+ catch (UserNotFoundException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, ex.Message);
+ }
+ }
+}
diff --git a/back/Controllers/Extensions/AddDomainServicesExt.cs b/back/Controllers/Extensions/AddDomainServicesExt.cs
new file mode 100644
index 0000000..7d6cc0a
--- /dev/null
+++ b/back/Controllers/Extensions/AddDomainServicesExt.cs
@@ -0,0 +1,19 @@
+using Contracts.Services;
+using Services.Domain;
+
+namespace Controllers.Extensions;
+
+public static class AddDomainServicesExtension
+{
+ public static void AddDomainServices(this IServiceCollection services)
+ {
+ services.AddTransient();
+ services.AddTransient();
+
+ services.AddTransient();
+
+ services.AddTransient();
+
+ services.AddTransient();
+ }
+}
\ No newline at end of file
diff --git a/back/Controllers/Extensions/AddReposExt.cs b/back/Controllers/Extensions/AddReposExt.cs
new file mode 100644
index 0000000..5da333a
--- /dev/null
+++ b/back/Controllers/Extensions/AddReposExt.cs
@@ -0,0 +1,17 @@
+using Contracts.Repositories;
+using Contracts.Services;
+using Infrastructure.Repositories;
+using Services.Domain;
+
+namespace Controllers.Extensions;
+
+public static class AddReposExtension
+{
+ public static void AddRepos(this IServiceCollection services)
+ {
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ }
+}
\ No newline at end of file
diff --git a/back/Controllers/Extensions/DatabaseSetupExt.cs b/back/Controllers/Extensions/DatabaseSetupExt.cs
new file mode 100644
index 0000000..0f72055
--- /dev/null
+++ b/back/Controllers/Extensions/DatabaseSetupExt.cs
@@ -0,0 +1,30 @@
+using Infrastructure;
+using Microsoft.EntityFrameworkCore;
+
+namespace Controllers.Extensions;
+
+public static class DatabaseSetupExtension
+{
+ public static void AddDbConnectionService(this IServiceCollection services, IConfiguration config)
+ {
+ var connectionString = config.GetConnectionString("DefaultConnection")
+ ?? throw new ArgumentException("Нет строки подключения");
+ services.AddDbContext(options => options.UseNpgsql(connectionString));
+ services.AddSingleton, DbContextFactory>();
+ }
+
+ public static void MigrateDb(this IApplicationBuilder app)
+ {
+ try
+ {
+ using var scope = app.ApplicationServices.CreateScope();
+ var context = scope.ServiceProvider.GetRequiredService>();
+ using var db = context.CreateDbContext();
+ db.Database.Migrate();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/back/Controllers/Program.cs b/back/Controllers/Program.cs
new file mode 100644
index 0000000..2e20712
--- /dev/null
+++ b/back/Controllers/Program.cs
@@ -0,0 +1,32 @@
+using Controllers.Extensions;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+builder.Services.AddDbConnectionService(builder.Configuration);
+builder.Services.AddRepos();
+builder.Services.AddDomainServices();
+
+builder.Services.AddControllers();
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.MigrateDb();
+
+app.UseHttpsRedirection();
+
+app.UseAuthorization();
+
+app.MapControllers();
+
+await app.RunAsync();
diff --git a/back/Controllers/Properties/launchSettings.json b/back/Controllers/Properties/launchSettings.json
new file mode 100644
index 0000000..3683563
--- /dev/null
+++ b/back/Controllers/Properties/launchSettings.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:47535",
+ "sslPort": 44340
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:5125",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:7189;http://localhost:5125",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/back/Controllers/appsettings.Development.json b/back/Controllers/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/back/Controllers/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/back/Controllers/appsettings.json b/back/Controllers/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/back/Controllers/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/back/Infrastructure/DatabaseContext.cs b/back/Infrastructure/DatabaseContext.cs
new file mode 100644
index 0000000..d787750
--- /dev/null
+++ b/back/Infrastructure/DatabaseContext.cs
@@ -0,0 +1,18 @@
+using Contracts.DTO;
+using Infrastructure.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace Infrastructure;
+
+public class DatabaseContext : DbContext
+{
+ public DatabaseContext(DbContextOptions options)
+ : base(options)
+ {
+ }
+
+ public DbSet Users { get; set; } = null!;
+ public DbSet SpendingGroups { get; set; } = null!;
+ public DbSet ChangeRecords { get; set; } = null!;
+ public DbSet SpendingPlans { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/back/Infrastructure/DbContextFactory.cs b/back/Infrastructure/DbContextFactory.cs
new file mode 100644
index 0000000..00e1443
--- /dev/null
+++ b/back/Infrastructure/DbContextFactory.cs
@@ -0,0 +1,20 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Infrastructure;
+
+public class DbContextFactory : IDbContextFactory
+{
+ private readonly IServiceProvider _serviceProvider;
+
+ public DbContextFactory(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ public DatabaseContext CreateDbContext()
+ {
+ var scope = _serviceProvider.CreateScope();
+ return scope.ServiceProvider.GetRequiredService();
+ }
+}
\ No newline at end of file
diff --git a/back/Infrastructure/Infrastructure.csproj b/back/Infrastructure/Infrastructure.csproj
new file mode 100644
index 0000000..0d0625a
--- /dev/null
+++ b/back/Infrastructure/Infrastructure.csproj
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/back/Infrastructure/Migrations/20241125164748_User.Designer.cs b/back/Infrastructure/Migrations/20241125164748_User.Designer.cs
new file mode 100644
index 0000000..9872477
--- /dev/null
+++ b/back/Infrastructure/Migrations/20241125164748_User.Designer.cs
@@ -0,0 +1,52 @@
+//
+using System;
+using Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Infrastructure.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20241125164748_User")]
+ partial class User
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Infrastructure.Models.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Balance")
+ .HasColumnType("numeric");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Password")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/back/Infrastructure/Migrations/20241125164748_User.cs b/back/Infrastructure/Migrations/20241125164748_User.cs
new file mode 100644
index 0000000..f35370d
--- /dev/null
+++ b/back/Infrastructure/Migrations/20241125164748_User.cs
@@ -0,0 +1,36 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Infrastructure.Migrations
+{
+ ///
+ public partial class User : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Users",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ Name = table.Column(type: "text", nullable: false),
+ Password = table.Column(type: "text", nullable: false),
+ Balance = table.Column(type: "numeric", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Users", x => x.Id);
+ });
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Users");
+ }
+ }
+}
diff --git a/back/Infrastructure/Migrations/20241126140310_SpendingGroup.Designer.cs b/back/Infrastructure/Migrations/20241126140310_SpendingGroup.Designer.cs
new file mode 100644
index 0000000..0ae4d47
--- /dev/null
+++ b/back/Infrastructure/Migrations/20241126140310_SpendingGroup.Designer.cs
@@ -0,0 +1,88 @@
+//
+using System;
+using Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Infrastructure.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20241126140310_SpendingGroup")]
+ partial class SpendingGroup
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("SpendingGroups");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Balance")
+ .HasColumnType("numeric");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Password")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
+ {
+ b.HasOne("Infrastructure.Models.User", "User")
+ .WithMany("SpendingGroups")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.User", b =>
+ {
+ b.Navigation("SpendingGroups");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/back/Infrastructure/Migrations/20241126140310_SpendingGroup.cs b/back/Infrastructure/Migrations/20241126140310_SpendingGroup.cs
new file mode 100644
index 0000000..1632633
--- /dev/null
+++ b/back/Infrastructure/Migrations/20241126140310_SpendingGroup.cs
@@ -0,0 +1,46 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Infrastructure.Migrations
+{
+ ///
+ public partial class SpendingGroup : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "SpendingGroups",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ Name = table.Column(type: "text", nullable: false),
+ UserId = table.Column(type: "uuid", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_SpendingGroups", x => x.Id);
+ table.ForeignKey(
+ name: "FK_SpendingGroups_Users_UserId",
+ column: x => x.UserId,
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_SpendingGroups_UserId",
+ table: "SpendingGroups",
+ column: "UserId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "SpendingGroups");
+ }
+ }
+}
diff --git a/back/Infrastructure/Migrations/20241126185002_AddChangeRecord.Designer.cs b/back/Infrastructure/Migrations/20241126185002_AddChangeRecord.Designer.cs
new file mode 100644
index 0000000..fc60907
--- /dev/null
+++ b/back/Infrastructure/Migrations/20241126185002_AddChangeRecord.Designer.cs
@@ -0,0 +1,134 @@
+//
+using System;
+using Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Infrastructure.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20241126185002_AddChangeRecord")]
+ partial class AddChangeRecord
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Infrastructure.Models.ChangeRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ChangedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SpendingGroupId")
+ .HasColumnType("uuid");
+
+ b.Property("Sum")
+ .HasColumnType("numeric");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SpendingGroupId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("ChangeRecords");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("SpendingGroups");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Balance")
+ .HasColumnType("numeric");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Password")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.ChangeRecord", b =>
+ {
+ b.HasOne("Infrastructure.Models.SpendingGroup", "SpendingGroup")
+ .WithMany()
+ .HasForeignKey("SpendingGroupId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Infrastructure.Models.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("SpendingGroup");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
+ {
+ b.HasOne("Infrastructure.Models.User", "User")
+ .WithMany("SpendingGroups")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.User", b =>
+ {
+ b.Navigation("SpendingGroups");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/back/Infrastructure/Migrations/20241126185002_AddChangeRecord.cs b/back/Infrastructure/Migrations/20241126185002_AddChangeRecord.cs
new file mode 100644
index 0000000..0f8a61d
--- /dev/null
+++ b/back/Infrastructure/Migrations/20241126185002_AddChangeRecord.cs
@@ -0,0 +1,59 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Infrastructure.Migrations
+{
+ ///
+ public partial class AddChangeRecord : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "ChangeRecords",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ Sum = table.Column(type: "numeric", nullable: false),
+ ChangedAt = table.Column(type: "timestamp with time zone", nullable: false),
+ UserId = table.Column(type: "uuid", nullable: false),
+ SpendingGroupId = table.Column(type: "uuid", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ChangeRecords", x => x.Id);
+ table.ForeignKey(
+ name: "FK_ChangeRecords_SpendingGroups_SpendingGroupId",
+ column: x => x.SpendingGroupId,
+ principalTable: "SpendingGroups",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_ChangeRecords_Users_UserId",
+ column: x => x.UserId,
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ChangeRecords_SpendingGroupId",
+ table: "ChangeRecords",
+ column: "SpendingGroupId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ChangeRecords_UserId",
+ table: "ChangeRecords",
+ column: "UserId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "ChangeRecords");
+ }
+ }
+}
diff --git a/back/Infrastructure/Migrations/20241126210947_fixChangeRecord.Designer.cs b/back/Infrastructure/Migrations/20241126210947_fixChangeRecord.Designer.cs
new file mode 100644
index 0000000..937117a
--- /dev/null
+++ b/back/Infrastructure/Migrations/20241126210947_fixChangeRecord.Designer.cs
@@ -0,0 +1,139 @@
+//
+using System;
+using Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Infrastructure.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20241126210947_fixChangeRecord")]
+ partial class fixChangeRecord
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Infrastructure.Models.ChangeRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ChangedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SpendingGroupId")
+ .HasColumnType("uuid");
+
+ b.Property("Sum")
+ .HasColumnType("numeric");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SpendingGroupId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("ChangeRecords");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("SpendingGroups");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Balance")
+ .HasColumnType("numeric");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Password")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.ChangeRecord", b =>
+ {
+ b.HasOne("Infrastructure.Models.SpendingGroup", "SpendingGroup")
+ .WithMany("ChangeRecords")
+ .HasForeignKey("SpendingGroupId");
+
+ b.HasOne("Infrastructure.Models.User", "User")
+ .WithMany("ChangeRecords")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("SpendingGroup");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
+ {
+ b.HasOne("Infrastructure.Models.User", "User")
+ .WithMany("SpendingGroups")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
+ {
+ b.Navigation("ChangeRecords");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.User", b =>
+ {
+ b.Navigation("ChangeRecords");
+
+ b.Navigation("SpendingGroups");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/back/Infrastructure/Migrations/20241126210947_fixChangeRecord.cs b/back/Infrastructure/Migrations/20241126210947_fixChangeRecord.cs
new file mode 100644
index 0000000..68c6484
--- /dev/null
+++ b/back/Infrastructure/Migrations/20241126210947_fixChangeRecord.cs
@@ -0,0 +1,60 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Infrastructure.Migrations
+{
+ ///
+ public partial class fixChangeRecord : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_ChangeRecords_SpendingGroups_SpendingGroupId",
+ table: "ChangeRecords");
+
+ migrationBuilder.AlterColumn(
+ name: "SpendingGroupId",
+ table: "ChangeRecords",
+ type: "uuid",
+ nullable: true,
+ oldClrType: typeof(Guid),
+ oldType: "uuid");
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_ChangeRecords_SpendingGroups_SpendingGroupId",
+ table: "ChangeRecords",
+ column: "SpendingGroupId",
+ principalTable: "SpendingGroups",
+ principalColumn: "Id");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_ChangeRecords_SpendingGroups_SpendingGroupId",
+ table: "ChangeRecords");
+
+ migrationBuilder.AlterColumn(
+ name: "SpendingGroupId",
+ table: "ChangeRecords",
+ type: "uuid",
+ nullable: false,
+ defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
+ oldClrType: typeof(Guid),
+ oldType: "uuid",
+ oldNullable: true);
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_ChangeRecords_SpendingGroups_SpendingGroupId",
+ table: "ChangeRecords",
+ column: "SpendingGroupId",
+ principalTable: "SpendingGroups",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ }
+ }
+}
diff --git a/back/Infrastructure/Migrations/20241126222847_AddSpendingPlan.Designer.cs b/back/Infrastructure/Migrations/20241126222847_AddSpendingPlan.Designer.cs
new file mode 100644
index 0000000..e43fe93
--- /dev/null
+++ b/back/Infrastructure/Migrations/20241126222847_AddSpendingPlan.Designer.cs
@@ -0,0 +1,177 @@
+//
+using System;
+using Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Infrastructure.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20241126222847_AddSpendingPlan")]
+ partial class AddSpendingPlan
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Infrastructure.Models.ChangeRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ChangedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SpendingGroupId")
+ .HasColumnType("uuid");
+
+ b.Property("Sum")
+ .HasColumnType("numeric");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SpendingGroupId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("ChangeRecords");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("SpendingGroups");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingPlan", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("EndAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SpendingGroupId")
+ .HasColumnType("uuid");
+
+ b.Property("StartAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Sum")
+ .HasColumnType("numeric");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SpendingGroupId");
+
+ b.ToTable("SpendingPlans");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Balance")
+ .HasColumnType("numeric");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Password")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.ChangeRecord", b =>
+ {
+ b.HasOne("Infrastructure.Models.SpendingGroup", "SpendingGroup")
+ .WithMany("ChangeRecords")
+ .HasForeignKey("SpendingGroupId");
+
+ b.HasOne("Infrastructure.Models.User", "User")
+ .WithMany("ChangeRecords")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("SpendingGroup");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
+ {
+ b.HasOne("Infrastructure.Models.User", "User")
+ .WithMany("SpendingGroups")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingPlan", b =>
+ {
+ b.HasOne("Infrastructure.Models.SpendingGroup", "SpendingGroup")
+ .WithMany("SpendingPlans")
+ .HasForeignKey("SpendingGroupId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("SpendingGroup");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
+ {
+ b.Navigation("ChangeRecords");
+
+ b.Navigation("SpendingPlans");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.User", b =>
+ {
+ b.Navigation("ChangeRecords");
+
+ b.Navigation("SpendingGroups");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/back/Infrastructure/Migrations/20241126222847_AddSpendingPlan.cs b/back/Infrastructure/Migrations/20241126222847_AddSpendingPlan.cs
new file mode 100644
index 0000000..fcc2f3e
--- /dev/null
+++ b/back/Infrastructure/Migrations/20241126222847_AddSpendingPlan.cs
@@ -0,0 +1,48 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Infrastructure.Migrations
+{
+ ///
+ public partial class AddSpendingPlan : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "SpendingPlans",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ StartAt = table.Column(type: "timestamp with time zone", nullable: false),
+ EndAt = table.Column(type: "timestamp with time zone", nullable: false),
+ Sum = table.Column(type: "numeric", nullable: false),
+ SpendingGroupId = table.Column(type: "uuid", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_SpendingPlans", x => x.Id);
+ table.ForeignKey(
+ name: "FK_SpendingPlans_SpendingGroups_SpendingGroupId",
+ column: x => x.SpendingGroupId,
+ principalTable: "SpendingGroups",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_SpendingPlans_SpendingGroupId",
+ table: "SpendingPlans",
+ column: "SpendingGroupId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "SpendingPlans");
+ }
+ }
+}
diff --git a/back/Infrastructure/Migrations/DatabaseContextModelSnapshot.cs b/back/Infrastructure/Migrations/DatabaseContextModelSnapshot.cs
new file mode 100644
index 0000000..93d0295
--- /dev/null
+++ b/back/Infrastructure/Migrations/DatabaseContextModelSnapshot.cs
@@ -0,0 +1,174 @@
+//
+using System;
+using Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Infrastructure.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ partial class DatabaseContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Infrastructure.Models.ChangeRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ChangedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SpendingGroupId")
+ .HasColumnType("uuid");
+
+ b.Property("Sum")
+ .HasColumnType("numeric");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SpendingGroupId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("ChangeRecords");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("SpendingGroups");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingPlan", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("EndAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SpendingGroupId")
+ .HasColumnType("uuid");
+
+ b.Property("StartAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Sum")
+ .HasColumnType("numeric");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SpendingGroupId");
+
+ b.ToTable("SpendingPlans");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Balance")
+ .HasColumnType("numeric");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Password")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.ChangeRecord", b =>
+ {
+ b.HasOne("Infrastructure.Models.SpendingGroup", "SpendingGroup")
+ .WithMany("ChangeRecords")
+ .HasForeignKey("SpendingGroupId");
+
+ b.HasOne("Infrastructure.Models.User", "User")
+ .WithMany("ChangeRecords")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("SpendingGroup");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
+ {
+ b.HasOne("Infrastructure.Models.User", "User")
+ .WithMany("SpendingGroups")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingPlan", b =>
+ {
+ b.HasOne("Infrastructure.Models.SpendingGroup", "SpendingGroup")
+ .WithMany("SpendingPlans")
+ .HasForeignKey("SpendingGroupId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("SpendingGroup");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
+ {
+ b.Navigation("ChangeRecords");
+
+ b.Navigation("SpendingPlans");
+ });
+
+ modelBuilder.Entity("Infrastructure.Models.User", b =>
+ {
+ b.Navigation("ChangeRecords");
+
+ b.Navigation("SpendingGroups");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/back/Infrastructure/Models/Changerecord.cs b/back/Infrastructure/Models/Changerecord.cs
new file mode 100644
index 0000000..60cb92d
--- /dev/null
+++ b/back/Infrastructure/Models/Changerecord.cs
@@ -0,0 +1,24 @@
+using Contracts.DTO;
+
+namespace Infrastructure.Models;
+
+public class ChangeRecord
+{
+ public Guid Id { get; set; }
+ public decimal Sum { get; set; }
+ public DateTime ChangedAt { get; set; }
+
+ public Guid UserId { get; set; }
+ public User User { get; set; } = null!;
+
+ public Guid? SpendingGroupId { get; set; }
+ public SpendingGroup? SpendingGroup { get; set; }
+
+ public void Update(ChangeRecordDto changeRecordDto)
+ {
+ Id = changeRecordDto.Id;
+ Sum = changeRecordDto.Sum;
+ ChangedAt = changeRecordDto.ChangedAt;
+ SpendingGroupId = changeRecordDto.SpendingGroupId;
+ }
+}
\ No newline at end of file
diff --git a/back/Infrastructure/Models/SpendingGroup.cs b/back/Infrastructure/Models/SpendingGroup.cs
new file mode 100644
index 0000000..23758eb
--- /dev/null
+++ b/back/Infrastructure/Models/SpendingGroup.cs
@@ -0,0 +1,12 @@
+namespace Infrastructure.Models;
+
+public class SpendingGroup
+{
+ public Guid Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+
+ public Guid UserId { get; set; }
+ public User User { get; set; } = null!;
+ public List? ChangeRecords { get; set; }
+ public List? SpendingPlans { get; set; }
+}
\ No newline at end of file
diff --git a/back/Infrastructure/Models/SpendingPlan.cs b/back/Infrastructure/Models/SpendingPlan.cs
new file mode 100644
index 0000000..8162016
--- /dev/null
+++ b/back/Infrastructure/Models/SpendingPlan.cs
@@ -0,0 +1,22 @@
+using Contracts.DTO;
+
+namespace Infrastructure.Models;
+
+public class SpendingPlan
+{
+ public Guid Id { get; set; }
+ public DateTime StartAt { get; set; }
+ public DateTime EndAt { get; set; }
+ public decimal Sum { get; set; }
+
+ public Guid SpendingGroupId { get; set; }
+ public SpendingGroup SpendingGroup { get; set; } = null!;
+
+ public void Update(SpendingPlanDto spendingPlan)
+ {
+ StartAt = spendingPlan.StartAt;
+ EndAt = spendingPlan.EndAt;
+ Sum = spendingPlan.Sum;
+ SpendingGroupId = spendingPlan.SpendingGroupId;
+ }
+}
\ No newline at end of file
diff --git a/back/Infrastructure/Models/User.cs b/back/Infrastructure/Models/User.cs
new file mode 100644
index 0000000..469e15e
--- /dev/null
+++ b/back/Infrastructure/Models/User.cs
@@ -0,0 +1,27 @@
+using System.Reflection.Metadata.Ecma335;
+using Contracts.DTO;
+
+namespace Infrastructure.Models;
+
+public class User
+{
+ public Guid Id { get; set; }
+ public string Name { get; set; } = null!;
+ public string Password { get; set; } = null!;
+ public decimal Balance { get; set; }
+ public List? SpendingGroups { get; set; }
+ public List? ChangeRecords { get; set; }
+
+ public void Update(UserDto userDto)
+ {
+ Id = userDto.Id;
+ if (!string.IsNullOrWhiteSpace(userDto.Name))
+ {
+ Name = userDto.Name;
+ }
+ if (!string.IsNullOrWhiteSpace(userDto.Password))
+ {
+ Password = userDto.Password;
+ }
+ }
+}
\ No newline at end of file
diff --git a/back/Infrastructure/Repositories/ChangeRecordRepo.cs b/back/Infrastructure/Repositories/ChangeRecordRepo.cs
new file mode 100644
index 0000000..6da1839
--- /dev/null
+++ b/back/Infrastructure/Repositories/ChangeRecordRepo.cs
@@ -0,0 +1,94 @@
+using Contracts.DTO;
+using Contracts.Repositories;
+using Contracts.SearchModels;
+using Infrastructure.Support.Mappers;
+using Microsoft.EntityFrameworkCore;
+
+namespace Infrastructure.Repositories;
+
+public class ChangeRecordRepo : IChangeRecordRepo
+{
+ public readonly IDbContextFactory _factory;
+
+ public ChangeRecordRepo(IDbContextFactory factory)
+ {
+ _factory = factory;
+ }
+
+ public async Task Create(ChangeRecordDto changeRecord)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var createdRecord = await context.ChangeRecords.AddAsync(changeRecord.ToModel());
+
+ await context.SaveChangesAsync();
+ return createdRecord.Entity.ToDto();
+ }
+
+ public async Task Delete(ChangeRecordSearch search)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var record = await context.ChangeRecords
+ .FirstOrDefaultAsync(x => x.Id == search.Id);
+ if (record == null)
+ {
+ return null;
+ }
+
+ context.ChangeRecords.Remove(record);
+ await context.SaveChangesAsync();
+ return record.ToDto();
+ }
+
+ public async Task Get(ChangeRecordSearch search)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var record = await context.ChangeRecords
+ .FirstOrDefaultAsync(x => x.Id == search.Id);
+ if (record == null)
+ {
+ return null;
+ }
+ return record.ToDto();
+ }
+
+ public async Task> GetList(ChangeRecordSearch? search = null)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var query = context.ChangeRecords.AsQueryable();
+
+ if (search != null)
+ {
+ if (search.SpendingGroupId.HasValue)
+ {
+ query = query.Where(x => x.SpendingGroupId == search.SpendingGroupId);
+ }
+ if (search.From.HasValue && search.To.HasValue)
+ {
+ query = query.Where(x => x.ChangedAt >= search.From && x.ChangedAt <= search.To);
+ }
+ }
+ return await query.Select(x => x.ToDto()).ToListAsync();
+ }
+
+ public async Task Update(ChangeRecordDto changeRecord)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var existingRecord = await context.ChangeRecords
+ .FirstOrDefaultAsync(x => x.Id == changeRecord.Id);
+
+ if (existingRecord == null)
+ {
+ return null;
+ }
+
+ existingRecord.Update(changeRecord);
+ context.ChangeRecords.Update(existingRecord);
+ await context.SaveChangesAsync();
+ return existingRecord.ToDto();
+ }
+}
diff --git a/back/Infrastructure/Repositories/SpendingGroupRepo.cs b/back/Infrastructure/Repositories/SpendingGroupRepo.cs
new file mode 100644
index 0000000..6c8382a
--- /dev/null
+++ b/back/Infrastructure/Repositories/SpendingGroupRepo.cs
@@ -0,0 +1,100 @@
+using Contracts.DTO;
+using Contracts.Repositories;
+using Contracts.SearchModels;
+using Infrastructure.Support.Mappers;
+using Microsoft.EntityFrameworkCore;
+
+namespace Infrastructure.Repositories;
+
+public class SpendingGroupRepo : ISpendingGroupRepo
+{
+ public readonly IDbContextFactory _factory;
+
+ public SpendingGroupRepo(IDbContextFactory factory)
+ {
+ _factory = factory;
+ }
+
+ public async Task Create(SpendingGroupDto spendingGroup)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var createdGroup = await context.SpendingGroups.AddAsync(spendingGroup.ToModel());
+ await context.SaveChangesAsync();
+ return createdGroup.Entity.ToDto();
+ }
+
+ public async Task Delete(SpendingGroupSearch search)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var group = await context.SpendingGroups
+ .FirstOrDefaultAsync(x => x.Id == search.Id
+ || x.Name == search.Name);
+ if (group == null)
+ {
+ return null;
+ }
+
+ context.SpendingGroups.Remove(group);
+ await context.SaveChangesAsync();
+ return group.ToDto();
+ }
+
+ public async Task Get(SpendingGroupSearch search)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var group = await context.SpendingGroups
+ .Include(x => x.ChangeRecords)
+ .Include(x => x.SpendingPlans)
+ .FirstOrDefaultAsync(x => x.Id == search.Id
+ || x.Name == search.Name);
+
+ return group?.ToDto();
+ }
+
+ public async Task> GetList(SpendingGroupSearch? search = null)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var query = context.SpendingGroups.AsQueryable();
+
+ if (search != null)
+ {
+ if (search.Id != null)
+ {
+ query = query.Where(x => x.Id == search.Id);
+ }
+
+ if (!string.IsNullOrWhiteSpace(search.Name))
+ {
+ query = query.Where(x => x.Name.Contains(search.Name, StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ return await query
+ .Include(x => x.ChangeRecords)
+ .Include(x => x.SpendingPlans)
+ .Select(x => x.ToDto())
+ .ToListAsync();
+ }
+
+ public async Task Update(SpendingGroupDto spendingGroup)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var existingGroup = await context.SpendingGroups
+ .FirstOrDefaultAsync(x => x.Id == spendingGroup.Id);
+
+ if (existingGroup == null)
+ {
+ return null;
+ }
+
+ existingGroup.Name = spendingGroup.Name;
+ context.SpendingGroups.Update(existingGroup);
+ await context.SaveChangesAsync();
+ return existingGroup.ToDto();
+ }
+}
diff --git a/back/Infrastructure/Repositories/SpendingPlanRepo.cs b/back/Infrastructure/Repositories/SpendingPlanRepo.cs
new file mode 100644
index 0000000..05c03f3
--- /dev/null
+++ b/back/Infrastructure/Repositories/SpendingPlanRepo.cs
@@ -0,0 +1,86 @@
+using Contracts.DTO;
+using Contracts.Repositories;
+using Contracts.SearchModels;
+using Infrastructure.Support.Mappers;
+using Microsoft.EntityFrameworkCore;
+
+namespace Infrastructure.Repositories;
+
+public class SpendingPlanRepo : ISpendingPlanRepo
+{
+ public readonly IDbContextFactory _factory;
+
+ public SpendingPlanRepo(IDbContextFactory factory)
+ {
+ _factory = factory;
+ }
+
+ public async Task Create(SpendingPlanDto dto)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var plan = await context.SpendingPlans.AddAsync(dto.ToModel());
+
+ await context.SaveChangesAsync();
+ return plan.Entity.ToDto();
+ }
+
+ public async Task Delete(SpendingPlanSearch search)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var plan = await context.SpendingPlans.FirstOrDefaultAsync(x => x.Id == search.Id);
+
+ if (plan == null)
+ {
+ return null;
+ }
+
+ context.SpendingPlans.Remove(plan);
+ await context.SaveChangesAsync();
+ return plan.ToDto();
+ }
+
+ public async Task Get(SpendingPlanSearch search)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var plan = await context.SpendingPlans.FirstOrDefaultAsync(x => x.Id == search.Id);
+
+ return plan?.ToDto();
+ }
+
+ public async Task> GetList(SpendingPlanSearch? search = null)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var query = context.SpendingPlans.AsQueryable();
+
+ if (search != null)
+ {
+ if (search.Id != null)
+ {
+ query = query.Where(x => x.Id == search.Id);
+ }
+ }
+
+ return await query.Select(x => x.ToDto()).ToListAsync();
+ }
+
+ public async Task Update(SpendingPlanDto dto)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var plan = await context.SpendingPlans.FirstOrDefaultAsync(x => x.Id == dto.Id);
+
+ if (plan == null)
+ {
+ return null;
+ }
+
+ plan.Update(dto);
+ context.SpendingPlans.Update(plan);
+ await context.SaveChangesAsync();
+ return plan.ToDto();
+ }
+}
diff --git a/back/Infrastructure/Repositories/UserRepo.cs b/back/Infrastructure/Repositories/UserRepo.cs
new file mode 100644
index 0000000..e3a450d
--- /dev/null
+++ b/back/Infrastructure/Repositories/UserRepo.cs
@@ -0,0 +1,91 @@
+using Contracts.DTO;
+using Contracts.Repositories;
+using Contracts.SearchModels;
+using Infrastructure.Support.Mappers;
+using Microsoft.EntityFrameworkCore;
+
+namespace Infrastructure.Repositories;
+
+public class UserRepo : IUserRepo
+{
+ public readonly IDbContextFactory _factory;
+
+ public UserRepo(IDbContextFactory factory)
+ {
+ _factory = factory;
+ }
+
+ public async Task ChangeBalance(UserSearch search, decimal amount)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var user = await context.Users
+ .FirstOrDefaultAsync(x => x.Id == search.Id
+ || x.Name == search.Name);
+
+ if (user == null)
+ {
+ return false;
+ }
+
+ user.Balance += amount;
+ context.Users.Update(user);
+ await context.SaveChangesAsync();
+ return true;
+ }
+
+ public async Task Create(UserDto user)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var createdUser = await context.Users.AddAsync(user.ToModel());
+
+ await context.SaveChangesAsync();
+ return createdUser.Entity.ToDto();
+ }
+
+ public async Task Delete(UserSearch search)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var user = await context.Users
+ .FirstOrDefaultAsync(x => x.Id == search.Id
+ || x.Name == search.Name);
+ if (user == null)
+ {
+ return null;
+ }
+
+ context.Users.Remove(user);
+ await context.SaveChangesAsync();
+ return user.ToDto();
+ }
+
+ public async Task Get(UserSearch search)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var user = await context.Users
+ .FirstOrDefaultAsync(x => x.Id == search.Id
+ || x.Name == search.Name);
+
+ return user?.ToDto();
+ }
+
+ public async Task Update(UserDto user)
+ {
+ using var context = _factory.CreateDbContext();
+
+ var existingUser = await context.Users.FirstOrDefaultAsync(x => x.Id == user.Id);
+
+ if (existingUser == null)
+ {
+ return null;
+ }
+
+ existingUser.Update(user);
+ context.Users.Update(existingUser);
+ await context.SaveChangesAsync();
+ return existingUser.ToDto();
+ }
+}
\ No newline at end of file
diff --git a/back/Infrastructure/Support/Mappers/ChangeRecordMapper.cs b/back/Infrastructure/Support/Mappers/ChangeRecordMapper.cs
new file mode 100644
index 0000000..cd5d76e
--- /dev/null
+++ b/back/Infrastructure/Support/Mappers/ChangeRecordMapper.cs
@@ -0,0 +1,27 @@
+using Contracts.DTO;
+using Infrastructure.Models;
+
+namespace Infrastructure.Support.Mappers;
+
+public static class ChangeRecordMapper
+{
+ public static ChangeRecordDto ToDto(this ChangeRecord changeRecord)
+ => new()
+ {
+ Id = changeRecord.Id,
+ Sum = changeRecord.Sum,
+ ChangedAt = changeRecord.ChangedAt,
+ SpendingGroupId = changeRecord.SpendingGroupId,
+ UserId = changeRecord.UserId
+ };
+
+ public static ChangeRecord ToModel(this ChangeRecordDto changeRecord)
+ => new()
+ {
+ Id = changeRecord.Id,
+ Sum = changeRecord.Sum,
+ ChangedAt = changeRecord.ChangedAt,
+ SpendingGroupId = changeRecord.SpendingGroupId,
+ UserId = changeRecord.UserId
+ };
+}
\ No newline at end of file
diff --git a/back/Infrastructure/Support/Mappers/SpendingGroupMapper.cs b/back/Infrastructure/Support/Mappers/SpendingGroupMapper.cs
new file mode 100644
index 0000000..9432073
--- /dev/null
+++ b/back/Infrastructure/Support/Mappers/SpendingGroupMapper.cs
@@ -0,0 +1,24 @@
+using Contracts.DTO;
+using Infrastructure.Models;
+
+namespace Infrastructure.Support.Mappers;
+
+public static class SpendingGroupMapper
+{
+ public static SpendingGroupDto ToDto(this SpendingGroup group)
+ => new()
+ {
+ Id = group.Id,
+ Name = group.Name,
+ UserId = group.UserId,
+ ChangeRecords = group.ChangeRecords?.Select(x => x.ToDto()).ToList() ?? [],
+ SpendingPlans = group.SpendingPlans?.Select(x => x.ToDto()).ToList() ?? []
+ };
+ public static SpendingGroup ToModel(this SpendingGroupDto group)
+ => new()
+ {
+ Id = group.Id,
+ Name = group.Name,
+ UserId = group.UserId
+ };
+}
\ No newline at end of file
diff --git a/back/Infrastructure/Support/Mappers/SpendingPlanMapper.cs b/back/Infrastructure/Support/Mappers/SpendingPlanMapper.cs
new file mode 100644
index 0000000..22fe884
--- /dev/null
+++ b/back/Infrastructure/Support/Mappers/SpendingPlanMapper.cs
@@ -0,0 +1,27 @@
+using Contracts.DTO;
+using Infrastructure.Models;
+
+namespace Infrastructure.Support.Mappers;
+
+public static class SpendingPlanMapper
+{
+ public static SpendingPlan ToModel(this SpendingPlanDto plan)
+ => new()
+ {
+ Id = plan.Id,
+ StartAt = plan.StartAt,
+ EndAt = plan.EndAt,
+ Sum = plan.Sum,
+ SpendingGroupId = plan.SpendingGroupId
+ };
+
+ public static SpendingPlanDto ToDto(this SpendingPlan plan)
+ => new()
+ {
+ Id = plan.Id,
+ StartAt = plan.StartAt,
+ EndAt = plan.EndAt,
+ Sum = plan.Sum,
+ SpendingGroupId = plan.SpendingGroupId
+ };
+}
\ No newline at end of file
diff --git a/back/Infrastructure/Support/Mappers/UserMapper.cs b/back/Infrastructure/Support/Mappers/UserMapper.cs
new file mode 100644
index 0000000..02e47fc
--- /dev/null
+++ b/back/Infrastructure/Support/Mappers/UserMapper.cs
@@ -0,0 +1,25 @@
+using Contracts.DTO;
+using Infrastructure.Models;
+
+namespace Infrastructure.Support.Mappers;
+
+public static class UserMapper
+{
+ public static UserDto ToDto(this User user)
+ => new()
+ {
+ Id = user.Id,
+ Name = user.Name,
+ Balance = user.Balance,
+ Password = user.Password
+ };
+
+ public static User ToModel(this UserDto user)
+ => new()
+ {
+ Id = user.Id,
+ Name = user.Name,
+ Balance = user.Balance,
+ Password = user.Password
+ };
+}
\ No newline at end of file
diff --git a/back/Services/Domain/AuthService.cs b/back/Services/Domain/AuthService.cs
new file mode 100644
index 0000000..4d59a79
--- /dev/null
+++ b/back/Services/Domain/AuthService.cs
@@ -0,0 +1,49 @@
+using Contracts.DTO;
+using Contracts.Mappers;
+using Contracts.Repositories;
+using Contracts.SearchModels;
+using Contracts.Services;
+using Contracts.ViewModels;
+using Services.Support.Exceptions;
+
+namespace Services.Domain;
+
+public class AuthService : IAuthService
+{
+ private readonly IUserRepo _userRepo;
+
+ public AuthService(IUserRepo userRepo)
+ {
+ _userRepo = userRepo;
+ }
+
+ public async Task Login(UserLoginDTO loginData)
+ {
+ if (loginData == null || string.IsNullOrWhiteSpace(loginData.Name)
+ || string.IsNullOrWhiteSpace(loginData.Password))
+ {
+ throw new ArgumentException("Неверные данные для входа");
+ }
+
+ var user = await _userRepo.Get(new UserSearch() { Name = loginData.Name });
+ if (user == null)
+ {
+ throw new UserNotFoundException($"Пользователь {loginData.Name} не найден");
+ }
+
+ return user.ToView();
+ }
+
+ public async Task Register(UserDto user)
+ {
+ var existingUser = await _userRepo.Get(new UserSearch() { Name = user.Name });
+ if (existingUser != null)
+ {
+ throw new AlreadyExistsException("Такой пользователь уже существует");
+ }
+
+ var createdUser = await _userRepo.Create(user);
+
+ return createdUser.ToView();
+ }
+}
diff --git a/back/Services/Domain/ChangeRecordService.cs b/back/Services/Domain/ChangeRecordService.cs
new file mode 100644
index 0000000..45ed858
--- /dev/null
+++ b/back/Services/Domain/ChangeRecordService.cs
@@ -0,0 +1,59 @@
+using Contracts.DTO;
+using Contracts.Mappers;
+using Contracts.Repositories;
+using Contracts.SearchModels;
+using Contracts.Services;
+using Contracts.ViewModels;
+
+namespace Services.Domain;
+
+public class ChangeRecordService : IChangeRecordService
+{
+ private readonly IChangeRecordRepo _changeRecordRepo;
+ private readonly IUserRepo _userRepo;
+
+ public ChangeRecordService(IChangeRecordRepo changeRecordRepo, IUserRepo userRepo)
+ {
+ _changeRecordRepo = changeRecordRepo;
+ _userRepo = userRepo;
+ }
+
+ public async Task Create(ChangeRecordDto model)
+ {
+ var record = await _changeRecordRepo.Create(model);
+
+ await _userRepo.ChangeBalance(new() { Id = model.UserId }, model.Sum);
+
+ return record.ToView();
+ }
+
+ public async Task Delete(ChangeRecordSearch search)
+ {
+ var record = await _changeRecordRepo.Delete(search);
+ if (record == null)
+ {
+ throw new EntryPointNotFoundException("При удалении не получилось найти запись измнения баланса");
+ }
+ // Возвращает баланс обратно
+ await _userRepo.ChangeBalance(new() { Id = record.UserId }, -record.Sum);
+ return record.ToView();
+ }
+
+ public async Task> GetList(ChangeRecordSearch? search = null)
+ {
+ var records = await _changeRecordRepo.GetList(search);
+
+ return records.Select(x => x.ToView()).ToList();
+ }
+
+ public async Task Update(ChangeRecordDto model)
+ {
+ var record = await _changeRecordRepo.Update(model);
+ if (record == null)
+ {
+ throw new EntryPointNotFoundException("При изменении не получилось найти запись измнения баланса");
+ }
+ await _userRepo.ChangeBalance(new() { Id = model.UserId }, model.Sum - record.Sum);
+ return record.ToView();
+ }
+}
diff --git a/back/Services/Domain/SpendingGroupService.cs b/back/Services/Domain/SpendingGroupService.cs
new file mode 100644
index 0000000..e4ff348
--- /dev/null
+++ b/back/Services/Domain/SpendingGroupService.cs
@@ -0,0 +1,63 @@
+using Contracts.DTO;
+using Contracts.Mappers;
+using Contracts.Repositories;
+using Contracts.SearchModels;
+using Contracts.ViewModels;
+using Services.Support.Exceptions;
+
+namespace Contracts.Services;
+
+public class SpendingGroupService : ISpendingGroupService
+{
+ private readonly ISpendingGroupRepo _spendingGroupRepo;
+
+ public SpendingGroupService(ISpendingGroupRepo spendingGroupRepo)
+ {
+ _spendingGroupRepo = spendingGroupRepo;
+ }
+
+ public async Task Create(SpendingGroupDto model)
+ {
+ var group = await _spendingGroupRepo.Create(model);
+ return group.ToView();
+ }
+
+ public async Task Delete(SpendingGroupSearch search)
+ {
+ var group = await _spendingGroupRepo.Delete(search);
+ if (group == null)
+ {
+ throw new EntityNotFoundException("При удалении не получилось найти группу");
+ }
+
+ return group.ToView();
+ }
+
+ public async Task GetDetails(SpendingGroupSearch search)
+ {
+ var group = await _spendingGroupRepo.Get(search);
+ if (group == null)
+ {
+ throw new EntityNotFoundException("Не удалось найти группу по таким параметрам");
+ }
+
+ return group.ToView();
+ }
+
+ public async Task> GetList(SpendingGroupSearch? search = null)
+ {
+ var groups = await _spendingGroupRepo.GetList(search);
+ return groups.Select(x => x.ToView()).ToList();
+ }
+
+ public async Task Update(SpendingGroupDto model)
+ {
+ var group = await _spendingGroupRepo.Update(model);
+ if (group == null)
+ {
+ throw new EntityNotFoundException("При обновлении не получилось найти группу");
+ }
+
+ return group.ToView();
+ }
+}
diff --git a/back/Services/Domain/SpendingPlanService.cs b/back/Services/Domain/SpendingPlanService.cs
new file mode 100644
index 0000000..ff655d0
--- /dev/null
+++ b/back/Services/Domain/SpendingPlanService.cs
@@ -0,0 +1,61 @@
+using Contracts.DTO;
+using Contracts.Mappers;
+using Contracts.Repositories;
+using Contracts.SearchModels;
+using Contracts.Services;
+using Contracts.ViewModels;
+using Services.Support.Exceptions;
+
+namespace Services.Domain;
+
+public class SpendingPlanService : ISpendingPlanService
+{
+ private readonly ISpendingPlanRepo _spendingPlanRepo;
+
+ public SpendingPlanService(ISpendingPlanRepo spendingPlanRepo)
+ {
+ _spendingPlanRepo = spendingPlanRepo;
+ }
+
+ public async Task Create(SpendingPlanDto spendingPlan)
+ {
+ var plan = await _spendingPlanRepo.Create(spendingPlan);
+ return plan.ToView();
+ }
+
+ public async Task Delete(SpendingPlanSearch search)
+ {
+ var plan = await _spendingPlanRepo.Delete(search);
+ if (plan == null)
+ {
+ throw new EntityNotFoundException("При удалении не получилось найти план");
+ }
+ return plan.ToView();
+ }
+
+ public async Task GetDetails(SpendingPlanSearch search)
+ {
+ var plan = await _spendingPlanRepo.Get(search);
+ if (plan == null)
+ {
+ throw new EntityNotFoundException("Не удалось найти план по таким параметрам");
+ }
+ return plan.ToView();
+ }
+
+ public async Task> GetList(SpendingPlanSearch? search = null)
+ {
+ var plans = await _spendingPlanRepo.GetList(search);
+ return plans.Select(x => x.ToView()).ToList();
+ }
+
+ public async Task Update(SpendingPlanDto spendingPlan)
+ {
+ var plan = await _spendingPlanRepo.Update(spendingPlan);
+ if (plan == null)
+ {
+ throw new EntityNotFoundException("При обновлении не получилось найти план");
+ }
+ return plan.ToView();
+ }
+}
diff --git a/back/Services/Domain/UserService.cs b/back/Services/Domain/UserService.cs
new file mode 100644
index 0000000..5f6f2c5
--- /dev/null
+++ b/back/Services/Domain/UserService.cs
@@ -0,0 +1,49 @@
+using Contracts.DTO;
+using Contracts.Mappers;
+using Contracts.Repositories;
+using Contracts.SearchModels;
+using Contracts.Services;
+using Contracts.ViewModels;
+using Services.Support.Exceptions;
+
+namespace Services.Domain;
+
+public class UserService : IUserService
+{
+ private readonly IUserRepo _userRepo;
+
+ public UserService(IUserRepo userRepo)
+ {
+ _userRepo = userRepo;
+ }
+
+ public async Task Delete(UserSearch search)
+ {
+ var user = await _userRepo.Delete(search);
+ if (user == null)
+ {
+ throw new UserNotFoundException($"Пользователь для удаления не найден");
+ }
+ return user.ToView();
+ }
+
+ public async Task GetDetails(UserSearch search)
+ {
+ var user = await _userRepo.Get(search);
+ if (user == null)
+ {
+ throw new UserNotFoundException($"Пользователь {search.Name} не найден");
+ }
+ return user.ToView();
+ }
+
+ public async Task UpdateUserData(UserDto user)
+ {
+ var updatedUser = await _userRepo.Update(user);
+ if (updatedUser == null)
+ {
+ throw new EntityNotFoundException("При обновлении не получилось найти пользователя с id = " + user.Id);
+ }
+ return updatedUser.ToView();
+ }
+}
diff --git a/back/Services/Services.csproj b/back/Services/Services.csproj
new file mode 100644
index 0000000..3c22415
--- /dev/null
+++ b/back/Services/Services.csproj
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/back/Services/Support/Exceptions/AlreadyExistsException.cs b/back/Services/Support/Exceptions/AlreadyExistsException.cs
new file mode 100644
index 0000000..8577218
--- /dev/null
+++ b/back/Services/Support/Exceptions/AlreadyExistsException.cs
@@ -0,0 +1,9 @@
+namespace Services.Support.Exceptions;
+
+public class AlreadyExistsException : Exception
+{
+ public AlreadyExistsException(string message) : base(message) { }
+
+ public AlreadyExistsException(string message, Exception innerException)
+ : base(message, innerException) { }
+}
\ No newline at end of file
diff --git a/back/Services/Support/Exceptions/EntityNotFoundException.cs b/back/Services/Support/Exceptions/EntityNotFoundException.cs
new file mode 100644
index 0000000..957b0b3
--- /dev/null
+++ b/back/Services/Support/Exceptions/EntityNotFoundException.cs
@@ -0,0 +1,10 @@
+namespace Services.Support.Exceptions;
+
+public class EntityNotFoundException : Exception
+{
+ public EntityNotFoundException(string message)
+ : base(message) { }
+ public EntityNotFoundException(string message, Exception innerException)
+ : base(message, innerException) { }
+
+}
\ No newline at end of file
diff --git a/back/Services/Support/Exceptions/UserNotFoundException.cs b/back/Services/Support/Exceptions/UserNotFoundException.cs
new file mode 100644
index 0000000..77b2297
--- /dev/null
+++ b/back/Services/Support/Exceptions/UserNotFoundException.cs
@@ -0,0 +1,12 @@
+using Contracts.SearchModels;
+
+namespace Services.Support.Exceptions;
+
+public class UserNotFoundException : EntityNotFoundException
+{
+ public UserNotFoundException(string message)
+ : base(message) { }
+ public UserNotFoundException(string message, Exception innerException)
+ : base(message, innerException) { }
+
+}
\ No newline at end of file
diff --git a/front/.gitignore b/front/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/front/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/front/index.html b/front/index.html
new file mode 100644
index 0000000..28e68e6
--- /dev/null
+++ b/front/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ ДомБюдж
+
+
+
+
+
+
diff --git a/front/package-lock.json b/front/package-lock.json
new file mode 100644
index 0000000..7fb419c
--- /dev/null
+++ b/front/package-lock.json
@@ -0,0 +1,1292 @@
+{
+ "name": "dombudg",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "dombudg",
+ "version": "0.0.0",
+ "dependencies": {
+ "vue": "^3.5.12"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.1.4",
+ "typescript": "~5.6.2",
+ "vite": "^5.4.10",
+ "vue-tsc": "^2.1.8"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
+ "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.26.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
+ "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz",
+ "integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz",
+ "integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz",
+ "integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz",
+ "integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz",
+ "integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz",
+ "integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz",
+ "integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz",
+ "integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz",
+ "integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz",
+ "integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz",
+ "integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz",
+ "integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz",
+ "integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz",
+ "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz",
+ "integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz",
+ "integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz",
+ "integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz",
+ "integrity": "sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.0.tgz",
+ "integrity": "sha512-7n7KdUEtx/7Yl7I/WVAMZ1bEb0eVvXF3ummWTeLcs/9gvo9pJhuLdouSXGjdZ/MKD1acf1I272+X0RMua4/R3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@volar/language-core": {
+ "version": "2.4.10",
+ "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.10.tgz",
+ "integrity": "sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/source-map": "2.4.10"
+ }
+ },
+ "node_modules/@volar/source-map": {
+ "version": "2.4.10",
+ "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.10.tgz",
+ "integrity": "sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@volar/typescript": {
+ "version": "2.4.10",
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.10.tgz",
+ "integrity": "sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/language-core": "2.4.10",
+ "path-browserify": "^1.0.1",
+ "vscode-uri": "^3.0.8"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
+ "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.25.3",
+ "@vue/shared": "3.5.13",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
+ "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.13",
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
+ "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.25.3",
+ "@vue/compiler-core": "3.5.13",
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/compiler-ssr": "3.5.13",
+ "@vue/shared": "3.5.13",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.11",
+ "postcss": "^8.4.48",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
+ "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/compiler-vue2": {
+ "version": "2.7.16",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
+ "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "de-indent": "^1.0.2",
+ "he": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/language-core": {
+ "version": "2.1.10",
+ "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.1.10.tgz",
+ "integrity": "sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/language-core": "~2.4.8",
+ "@vue/compiler-dom": "^3.5.0",
+ "@vue/compiler-vue2": "^2.7.16",
+ "@vue/shared": "^3.5.0",
+ "alien-signals": "^0.2.0",
+ "minimatch": "^9.0.3",
+ "muggle-string": "^0.4.1",
+ "path-browserify": "^1.0.1"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz",
+ "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
+ "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.13",
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
+ "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.13",
+ "@vue/runtime-core": "3.5.13",
+ "@vue/shared": "3.5.13",
+ "csstype": "^3.1.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
+ "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.13",
+ "@vue/shared": "3.5.13"
+ },
+ "peerDependencies": {
+ "vue": "3.5.13"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz",
+ "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
+ "license": "MIT"
+ },
+ "node_modules/alien-signals": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.2.2.tgz",
+ "integrity": "sha512-cZIRkbERILsBOXTQmMrxc9hgpxglstn69zm+F1ARf4aPAzdAFYd6sBq87ErO0Fj3DV94tglcyHG5kQz9nDC/8A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
+ },
+ "node_modules/de-indent": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
+ "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "license": "MIT"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.13",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.13.tgz",
+ "integrity": "sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/muggle-string": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
+ "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/path-browserify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
+ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz",
+ "integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.6"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.27.4",
+ "@rollup/rollup-android-arm64": "4.27.4",
+ "@rollup/rollup-darwin-arm64": "4.27.4",
+ "@rollup/rollup-darwin-x64": "4.27.4",
+ "@rollup/rollup-freebsd-arm64": "4.27.4",
+ "@rollup/rollup-freebsd-x64": "4.27.4",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.27.4",
+ "@rollup/rollup-linux-arm-musleabihf": "4.27.4",
+ "@rollup/rollup-linux-arm64-gnu": "4.27.4",
+ "@rollup/rollup-linux-arm64-musl": "4.27.4",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.27.4",
+ "@rollup/rollup-linux-riscv64-gnu": "4.27.4",
+ "@rollup/rollup-linux-s390x-gnu": "4.27.4",
+ "@rollup/rollup-linux-x64-gnu": "4.27.4",
+ "@rollup/rollup-linux-x64-musl": "4.27.4",
+ "@rollup/rollup-win32-arm64-msvc": "4.27.4",
+ "@rollup/rollup-win32-ia32-msvc": "4.27.4",
+ "@rollup/rollup-win32-x64-msvc": "4.27.4",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
+ "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vscode-uri": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
+ "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vue": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz",
+ "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/compiler-sfc": "3.5.13",
+ "@vue/runtime-dom": "3.5.13",
+ "@vue/server-renderer": "3.5.13",
+ "@vue/shared": "3.5.13"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-tsc": {
+ "version": "2.1.10",
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.1.10.tgz",
+ "integrity": "sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/typescript": "~2.4.8",
+ "@vue/language-core": "2.1.10",
+ "semver": "^7.5.4"
+ },
+ "bin": {
+ "vue-tsc": "bin/vue-tsc.js"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.0"
+ }
+ }
+ }
+}
diff --git a/front/package.json b/front/package.json
new file mode 100644
index 0000000..a3857a9
--- /dev/null
+++ b/front/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "dombudg",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vue-tsc -b && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "vue": "^3.5.12"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.1.4",
+ "typescript": "~5.6.2",
+ "vite": "^5.4.10",
+ "vue-tsc": "^2.1.8"
+ }
+}
diff --git a/front/src/App.vue b/front/src/App.vue
new file mode 100644
index 0000000..bbb0db4
--- /dev/null
+++ b/front/src/App.vue
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/front/src/main.ts b/front/src/main.ts
new file mode 100644
index 0000000..2425c0f
--- /dev/null
+++ b/front/src/main.ts
@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import './style.css'
+import App from './App.vue'
+
+createApp(App).mount('#app')
diff --git a/front/src/style.css b/front/src/style.css
new file mode 100644
index 0000000..e69de29
diff --git a/front/src/vite-env.d.ts b/front/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/front/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/front/tsconfig.app.json b/front/tsconfig.app.json
new file mode 100644
index 0000000..cb88a5a
--- /dev/null
+++ b/front/tsconfig.app.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "preserve",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
+}
diff --git a/front/tsconfig.json b/front/tsconfig.json
new file mode 100644
index 0000000..1ffef60
--- /dev/null
+++ b/front/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/front/tsconfig.node.json b/front/tsconfig.node.json
new file mode 100644
index 0000000..abcd7f0
--- /dev/null
+++ b/front/tsconfig.node.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2022",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/front/vite.config.ts b/front/vite.config.ts
new file mode 100644
index 0000000..bbcf80c
--- /dev/null
+++ b/front/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [vue()],
+})
diff --git a/front/vite.config.ts.timestamp-1732528329064-33d75fbae33f5.mjs b/front/vite.config.ts.timestamp-1732528329064-33d75fbae33f5.mjs
new file mode 100644
index 0000000..88c3d34
--- /dev/null
+++ b/front/vite.config.ts.timestamp-1732528329064-33d75fbae33f5.mjs
@@ -0,0 +1,10 @@
+// vite.config.ts
+import { defineConfig } from "file:///C:/Users/MM-NKA/source/repos/domBudg/front/node_modules/vite/dist/node/index.js";
+import vue from "file:///C:/Users/MM-NKA/source/repos/domBudg/front/node_modules/@vitejs/plugin-vue/dist/index.mjs";
+var vite_config_default = defineConfig({
+ plugins: [vue()]
+});
+export {
+ vite_config_default as default
+};
+//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxVc2Vyc1xcXFxNTS1OS0FcXFxcc291cmNlXFxcXHJlcG9zXFxcXGRvbUJ1ZGdcXFxcZnJvbnRcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkM6XFxcXFVzZXJzXFxcXE1NLU5LQVxcXFxzb3VyY2VcXFxccmVwb3NcXFxcZG9tQnVkZ1xcXFxmcm9udFxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vQzovVXNlcnMvTU0tTktBL3NvdXJjZS9yZXBvcy9kb21CdWRnL2Zyb250L3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZSdcbmltcG9ydCB2dWUgZnJvbSAnQHZpdGVqcy9wbHVnaW4tdnVlJ1xuXG4vLyBodHRwczovL3ZpdGUuZGV2L2NvbmZpZy9cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gIHBsdWdpbnM6IFt2dWUoKV0sXG59KVxuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUE4VCxTQUFTLG9CQUFvQjtBQUMzVixPQUFPLFNBQVM7QUFHaEIsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDMUIsU0FBUyxDQUFDLElBQUksQ0FBQztBQUNqQixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=