0.1.0
Готовый прототип с базовым функционалом системы
This commit is contained in:
commit
8d96d61577
25
.dockerignore
Normal file
25
.dockerignore
Normal file
@ -0,0 +1,25 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
5
.env
Normal file
5
.env
Normal file
@ -0,0 +1,5 @@
|
||||
POSTGRES_DB="dombudg"
|
||||
POSTGRES_USER="postgres"
|
||||
POSTGRES_PASSWORD="postgres"
|
||||
|
||||
CONNECTION_STRING="Host=database:5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD};"
|
39
.vscode/launch.json
vendored
Normal file
39
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": ".NET Core Launch (web)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceFolder}/back/Controllers/bin/Debug/net8.0/Controllers.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/back/Controllers",
|
||||
"stopAtEntry": false,
|
||||
"serverReadyAction": {
|
||||
"action": "openExternally",
|
||||
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
|
||||
},
|
||||
"env": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/Views": "${workspaceFolder}/Views"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach"
|
||||
},
|
||||
{
|
||||
"name": "Docker .NET Launch",
|
||||
"type": "docker",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "docker-run: debug",
|
||||
"netCore": {
|
||||
"appProject": "${workspaceFolder}/back/Controllers/Controllers.csproj"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
101
.vscode/tasks.json
vendored
Normal file
101
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/back/Api.sln",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/back/Api.sln",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"watch",
|
||||
"run",
|
||||
"--project",
|
||||
"${workspaceFolder}/back/Api.sln"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"type": "docker-build",
|
||||
"label": "docker-build: debug",
|
||||
"dependsOn": [
|
||||
"build"
|
||||
],
|
||||
"dockerBuild": {
|
||||
"tag": "dombudg:dev",
|
||||
"target": "base",
|
||||
"dockerfile": "${workspaceFolder}/back/Controllers/Dockerfile",
|
||||
"context": "${workspaceFolder}",
|
||||
"pull": true
|
||||
},
|
||||
"netCore": {
|
||||
"appProject": "${workspaceFolder}/back/Controllers/Controllers.csproj"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "docker-build",
|
||||
"label": "docker-build: release",
|
||||
"dependsOn": [
|
||||
"build"
|
||||
],
|
||||
"dockerBuild": {
|
||||
"tag": "dombudg:latest",
|
||||
"dockerfile": "${workspaceFolder}/back/Controllers/Dockerfile",
|
||||
"context": "${workspaceFolder}",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"architecture": "amd64"
|
||||
},
|
||||
"pull": true
|
||||
},
|
||||
"netCore": {
|
||||
"appProject": "${workspaceFolder}/back/Controllers/Controllers.csproj"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "docker-run",
|
||||
"label": "docker-run: debug",
|
||||
"dependsOn": [
|
||||
"docker-build: debug"
|
||||
],
|
||||
"dockerRun": {},
|
||||
"netCore": {
|
||||
"appProject": "${workspaceFolder}/back/Controllers/Controllers.csproj",
|
||||
"enableDebugging": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "docker-run",
|
||||
"label": "docker-run: release",
|
||||
"dependsOn": [
|
||||
"docker-build: release"
|
||||
],
|
||||
"dockerRun": {},
|
||||
"netCore": {
|
||||
"appProject": "${workspaceFolder}/back/Controllers/Controllers.csproj"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
484
back/.gitignore
vendored
Normal file
484
back/.gitignore
vendored
Normal file
@ -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
|
40
back/Api.sln
Normal file
40
back/Api.sln
Normal file
@ -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
|
9
back/Contracts/Contracts.csproj
Normal file
9
back/Contracts/Contracts.csproj
Normal file
@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
11
back/Contracts/DTOs/ChangeRecordDto.cs
Normal file
11
back/Contracts/DTOs/ChangeRecordDto.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Contracts.DTO;
|
||||
|
||||
public class ChangeRecordDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public Guid? SpendingGroupId { get; set; }
|
||||
public string? SpendingGroupName { get; set; }
|
||||
public decimal Sum { get; set; }
|
||||
public DateTime ChangedAt { get; set; }
|
||||
}
|
10
back/Contracts/DTOs/SpendingGroupDto.cs
Normal file
10
back/Contracts/DTOs/SpendingGroupDto.cs
Normal file
@ -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<ChangeRecordDto> ChangeRecords { get; set; } = new();
|
||||
public List<SpendingPlanDto> SpendingPlans { get; set; } = new();
|
||||
}
|
10
back/Contracts/DTOs/SpendingPlanDto.cs
Normal file
10
back/Contracts/DTOs/SpendingPlanDto.cs
Normal file
@ -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; }
|
||||
}
|
9
back/Contracts/DTOs/UserDTO.cs
Normal file
9
back/Contracts/DTOs/UserDTO.cs
Normal file
@ -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; }
|
||||
}
|
7
back/Contracts/DTOs/UserLoginDTO.cs
Normal file
7
back/Contracts/DTOs/UserLoginDTO.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Contracts.DTO;
|
||||
|
||||
public class UserLoginDto
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Password { get; set; } = string.Empty;
|
||||
}
|
16
back/Contracts/Mappers/ChangeRecordMapper.cs
Normal file
16
back/Contracts/Mappers/ChangeRecordMapper.cs
Normal file
@ -0,0 +1,16 @@
|
||||
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.ToString("dd.MM.yyyy"),
|
||||
SpendingGroupName = dto.SpendingGroupName ?? string.Empty
|
||||
};
|
||||
}
|
16
back/Contracts/Mappers/SpendingGroupMapper.cs
Normal file
16
back/Contracts/Mappers/SpendingGroupMapper.cs
Normal file
@ -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()
|
||||
};
|
||||
}
|
16
back/Contracts/Mappers/SpendingPlanMapper.cs
Normal file
16
back/Contracts/Mappers/SpendingPlanMapper.cs
Normal file
@ -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.ToString("dd.MM.yyyy"),
|
||||
EndAt = dto.EndAt.ToString("dd.MM.yyyy"),
|
||||
Sum = dto.Sum
|
||||
};
|
||||
}
|
16
back/Contracts/Mappers/UserMapper.cs
Normal file
16
back/Contracts/Mappers/UserMapper.cs
Normal file
@ -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
|
||||
};
|
||||
|
||||
}
|
13
back/Contracts/Repositories/IChangeRecordRepo.cs
Normal file
13
back/Contracts/Repositories/IChangeRecordRepo.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Contracts.DTO;
|
||||
using Contracts.SearchModels;
|
||||
|
||||
namespace Contracts.Repositories;
|
||||
|
||||
public interface IChangeRecordRepo
|
||||
{
|
||||
Task<ChangeRecordDto> Create(ChangeRecordDto changeRecord);
|
||||
Task<ChangeRecordDto?> Get(ChangeRecordSearch search);
|
||||
Task<IEnumerable<ChangeRecordDto>> GetList(ChangeRecordSearch? search = null);
|
||||
Task<ChangeRecordDto?> Update(ChangeRecordDto changeRecord);
|
||||
Task<ChangeRecordDto?> Delete(ChangeRecordSearch search);
|
||||
}
|
13
back/Contracts/Repositories/ISpendingGroupRepo.cs
Normal file
13
back/Contracts/Repositories/ISpendingGroupRepo.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Contracts.DTO;
|
||||
using Contracts.SearchModels;
|
||||
|
||||
namespace Contracts.Repositories;
|
||||
|
||||
public interface ISpendingGroupRepo
|
||||
{
|
||||
Task<SpendingGroupDto?> Get(SpendingGroupSearch search);
|
||||
Task<IEnumerable<SpendingGroupDto>> GetList(SpendingGroupSearch? search = null);
|
||||
Task<SpendingGroupDto> Create(SpendingGroupDto spendingGroup);
|
||||
Task<SpendingGroupDto?> Delete(SpendingGroupSearch search);
|
||||
Task<SpendingGroupDto?> Update(SpendingGroupDto spendingGroup);
|
||||
}
|
13
back/Contracts/Repositories/ISpendingPlanRepo.cs
Normal file
13
back/Contracts/Repositories/ISpendingPlanRepo.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Contracts.DTO;
|
||||
using Contracts.SearchModels;
|
||||
|
||||
namespace Contracts.Repositories;
|
||||
|
||||
public interface ISpendingPlanRepo
|
||||
{
|
||||
Task<SpendingPlanDto> Create(SpendingPlanDto dto);
|
||||
Task<SpendingPlanDto?> Update(SpendingPlanDto dto);
|
||||
Task<SpendingPlanDto?> Delete(SpendingPlanSearch search);
|
||||
Task<SpendingPlanDto?> Get(SpendingPlanSearch search);
|
||||
Task<IEnumerable<SpendingPlanDto>> GetList(SpendingPlanSearch? search = null);
|
||||
}
|
13
back/Contracts/Repositories/IUserRepo.cs
Normal file
13
back/Contracts/Repositories/IUserRepo.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Contracts.DTO;
|
||||
using Contracts.SearchModels;
|
||||
|
||||
namespace Contracts.Repositories;
|
||||
|
||||
public interface IUserRepo
|
||||
{
|
||||
public Task<UserDto?> Get(UserSearch search);
|
||||
public Task<UserDto> Create(UserDto user);
|
||||
public Task<UserDto?> Update(UserDto user);
|
||||
public Task<bool> ChangeBalance(UserSearch search, decimal amount);
|
||||
public Task<UserDto?> Delete(UserSearch search);
|
||||
}
|
10
back/Contracts/SearchModels/ChangeRecordSearch.cs
Normal file
10
back/Contracts/SearchModels/ChangeRecordSearch.cs
Normal file
@ -0,0 +1,10 @@
|
||||
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; }
|
||||
public Guid? UserId { get; set; }
|
||||
}
|
8
back/Contracts/SearchModels/SpendingGroupSearch.cs
Normal file
8
back/Contracts/SearchModels/SpendingGroupSearch.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Contracts.SearchModels;
|
||||
|
||||
public class SpendingGroupSearch
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
}
|
6
back/Contracts/SearchModels/SpendingPlanSearch.cs
Normal file
6
back/Contracts/SearchModels/SpendingPlanSearch.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Contracts.SearchModels;
|
||||
|
||||
public class SpendingPlanSearch
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
}
|
7
back/Contracts/SearchModels/UserSearch.cs
Normal file
7
back/Contracts/SearchModels/UserSearch.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Contracts.SearchModels;
|
||||
|
||||
public class UserSearch
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
}
|
11
back/Contracts/Services/IAuthService.cs
Normal file
11
back/Contracts/Services/IAuthService.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using Contracts.DTO;
|
||||
using Contracts.SearchModels;
|
||||
using Contracts.ViewModels;
|
||||
|
||||
namespace Contracts.Services;
|
||||
|
||||
public interface IAuthService
|
||||
{
|
||||
public Task<UserViewModel> Login(UserLoginDto loginData);
|
||||
public Task<UserViewModel> Register(UserDto user);
|
||||
}
|
13
back/Contracts/Services/IChangeRecordService.cs
Normal file
13
back/Contracts/Services/IChangeRecordService.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Contracts.DTO;
|
||||
using Contracts.SearchModels;
|
||||
using Contracts.ViewModels;
|
||||
|
||||
namespace Contracts.Services;
|
||||
|
||||
public interface IChangeRecordService
|
||||
{
|
||||
Task<ChangeRecordViewModel> Create(ChangeRecordDto model);
|
||||
Task<ChangeRecordViewModel> Update(ChangeRecordDto model);
|
||||
Task<ChangeRecordViewModel> Delete(ChangeRecordSearch search);
|
||||
Task<IEnumerable<ChangeRecordViewModel>> GetList(ChangeRecordSearch? search = null);
|
||||
}
|
14
back/Contracts/Services/ISpendingGroupService.cs
Normal file
14
back/Contracts/Services/ISpendingGroupService.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using Contracts.DTO;
|
||||
using Contracts.SearchModels;
|
||||
using Contracts.ViewModels;
|
||||
|
||||
namespace Contracts.Services;
|
||||
|
||||
public interface ISpendingGroupService
|
||||
{
|
||||
Task<SpendingGroupViewModel> GetDetails(SpendingGroupSearch search);
|
||||
Task<IEnumerable<SpendingGroupViewModel>> GetList(SpendingGroupSearch? search = null);
|
||||
Task<SpendingGroupViewModel> Create(SpendingGroupDto model);
|
||||
Task<SpendingGroupViewModel> Update(SpendingGroupDto model);
|
||||
Task<SpendingGroupViewModel> Delete(SpendingGroupSearch search);
|
||||
}
|
14
back/Contracts/Services/ISpendingPlanService.cs
Normal file
14
back/Contracts/Services/ISpendingPlanService.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using Contracts.DTO;
|
||||
using Contracts.SearchModels;
|
||||
using Contracts.ViewModels;
|
||||
|
||||
namespace Contracts.Services;
|
||||
|
||||
public interface ISpendingPlanService
|
||||
{
|
||||
Task<SpendingPlanViewModel> GetDetails(SpendingPlanSearch search);
|
||||
Task<IEnumerable<SpendingPlanViewModel>> GetList(SpendingPlanSearch? search = null);
|
||||
Task<SpendingPlanViewModel> Create(SpendingPlanDto spendingPlan);
|
||||
Task<SpendingPlanViewModel> Delete(SpendingPlanSearch search);
|
||||
Task<SpendingPlanViewModel> Update(SpendingPlanDto spendingPlan);
|
||||
}
|
12
back/Contracts/Services/IUserService.cs
Normal file
12
back/Contracts/Services/IUserService.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Contracts.DTO;
|
||||
using Contracts.SearchModels;
|
||||
using Contracts.ViewModels;
|
||||
|
||||
namespace Contracts.Services;
|
||||
|
||||
public interface IUserService
|
||||
{
|
||||
public Task<UserViewModel> GetDetails(UserSearch search);
|
||||
public Task<UserViewModel> UpdateUserData(UserDto user);
|
||||
public Task<UserViewModel> Delete(UserSearch search);
|
||||
}
|
9
back/Contracts/ViewModels/ChangeRecordViewModel.cs
Normal file
9
back/Contracts/ViewModels/ChangeRecordViewModel.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Contracts.ViewModels;
|
||||
|
||||
public class ChangeRecordViewModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public decimal Sum { get; set; }
|
||||
public string ChangedAt { get; set; } = null!;
|
||||
public string SpendingGroupName { get; set; } = string.Empty;
|
||||
}
|
9
back/Contracts/ViewModels/SpendingGroupViewModel.cs
Normal file
9
back/Contracts/ViewModels/SpendingGroupViewModel.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Contracts.ViewModels;
|
||||
|
||||
public class SpendingGroupViewModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public List<ChangeRecordViewModel> ChangeRecords { get; set; } = new();
|
||||
public List<SpendingPlanViewModel> SpendingPlans { get; set; } = new();
|
||||
}
|
9
back/Contracts/ViewModels/SpendingPlanViewModel.cs
Normal file
9
back/Contracts/ViewModels/SpendingPlanViewModel.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Contracts.ViewModels;
|
||||
|
||||
public class SpendingPlanViewModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string StartAt { get; set; } = null!;
|
||||
public string EndAt { get; set; } = null!;
|
||||
public decimal Sum { get; set; }
|
||||
}
|
8
back/Contracts/ViewModels/UserViewModel.cs
Normal file
8
back/Contracts/ViewModels/UserViewModel.cs
Normal file
@ -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; }
|
||||
}
|
23
back/Controllers/Controllers.csproj
Normal file
23
back/Controllers/Controllers.csproj
Normal file
@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Contracts\Contracts.csproj" />
|
||||
<ProjectReference Include="..\Services\Services.csproj" />
|
||||
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
5
back/Controllers/Controllers.http
Normal file
5
back/Controllers/Controllers.http
Normal file
@ -0,0 +1,5 @@
|
||||
@Controllers_HostAddress = http://localhost:5125
|
||||
|
||||
Accept: application/json
|
||||
|
||||
###
|
63
back/Controllers/Controllers/AuthController.cs
Normal file
63
back/Controllers/Controllers/AuthController.cs
Normal file
@ -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<ActionResult<UserViewModel>> 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<ActionResult<UserViewModel>> 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);
|
||||
}
|
||||
}
|
||||
}
|
100
back/Controllers/Controllers/ChangeRecordController.cs
Normal file
100
back/Controllers/Controllers/ChangeRecordController.cs
Normal file
@ -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<ActionResult<ChangeRecordViewModel>> 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<ActionResult<IEnumerable<ChangeRecordViewModel>>> GetChangeRecords()
|
||||
{
|
||||
try
|
||||
{
|
||||
var records = await _changeRecordService.GetList();
|
||||
return Ok(records);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("filter")]
|
||||
public async Task<ActionResult<IEnumerable<ChangeRecordViewModel>>> 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<ActionResult<ChangeRecordViewModel>> 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<ActionResult> 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);
|
||||
}
|
||||
}
|
||||
}
|
126
back/Controllers/Controllers/SpendingGroupController.cs
Normal file
126
back/Controllers/Controllers/SpendingGroupController.cs
Normal file
@ -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<ActionResult<SpendingGroupViewModel>> 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<ActionResult<List<SpendingGroupViewModel>>> GetSpendingGroups()
|
||||
{
|
||||
try
|
||||
{
|
||||
var groups = await _spendingGroupService.GetList();
|
||||
return Ok(groups);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("filter")]
|
||||
public async Task<ActionResult<List<SpendingGroupViewModel>>> 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<ActionResult<SpendingGroupViewModel>> 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<ActionResult<SpendingGroupViewModel>> 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<ActionResult> 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);
|
||||
}
|
||||
}
|
||||
}
|
128
back/Controllers/Controllers/SpendingPlanController.cs
Normal file
128
back/Controllers/Controllers/SpendingPlanController.cs
Normal file
@ -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<ActionResult<SpendingPlanViewModel>> 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<ActionResult<List<SpendingPlanViewModel>>> GetSpendingPlans()
|
||||
{
|
||||
try
|
||||
{
|
||||
var plans = await _spendingPlanService.GetList();
|
||||
return Ok(plans);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("filter")]
|
||||
public async Task<ActionResult<List<SpendingPlanViewModel>>> 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<ActionResult<SpendingPlanViewModel>> 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<ActionResult<SpendingPlanViewModel>> 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<ActionResult> 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);
|
||||
}
|
||||
}
|
||||
}
|
73
back/Controllers/Controllers/UserController.cs
Normal file
73
back/Controllers/Controllers/UserController.cs
Normal file
@ -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<ActionResult<UserViewModel>> 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<ActionResult<UserViewModel>> 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<ActionResult<UserViewModel>> 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);
|
||||
}
|
||||
}
|
||||
}
|
24
back/Controllers/Dockerfile
Normal file
24
back/Controllers/Dockerfile
Normal file
@ -0,0 +1,24 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 5125
|
||||
|
||||
ENV ASPNETCORE_URLS=http://+:5125
|
||||
|
||||
USER app
|
||||
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
ARG configuration=Release
|
||||
WORKDIR /src
|
||||
COPY ["back/Controllers/Controllers.csproj", "back/Controllers/"]
|
||||
RUN dotnet restore "back/Controllers/Controllers.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/back/Controllers"
|
||||
RUN dotnet build "Controllers.csproj" -c $configuration -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG configuration=Release
|
||||
RUN dotnet publish "Controllers.csproj" -c $configuration -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "Controllers.dll"]
|
19
back/Controllers/Extensions/AddDomainServicesExt.cs
Normal file
19
back/Controllers/Extensions/AddDomainServicesExt.cs
Normal file
@ -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<IAuthService, AuthService>();
|
||||
services.AddTransient<IUserService, UserService>();
|
||||
|
||||
services.AddTransient<ISpendingGroupService, SpendingGroupService>();
|
||||
|
||||
services.AddTransient<IChangeRecordService, ChangeRecordService>();
|
||||
|
||||
services.AddTransient<ISpendingPlanService, SpendingPlanService>();
|
||||
}
|
||||
}
|
17
back/Controllers/Extensions/AddReposExt.cs
Normal file
17
back/Controllers/Extensions/AddReposExt.cs
Normal file
@ -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<IUserRepo, UserRepo>();
|
||||
services.AddTransient<ISpendingGroupRepo, SpendingGroupRepo>();
|
||||
services.AddTransient<IChangeRecordRepo, ChangeRecordRepo>();
|
||||
services.AddTransient<ISpendingPlanRepo, SpendingPlanRepo>();
|
||||
}
|
||||
}
|
31
back/Controllers/Extensions/DatabaseSetupExt.cs
Normal file
31
back/Controllers/Extensions/DatabaseSetupExt.cs
Normal file
@ -0,0 +1,31 @@
|
||||
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("Нет строки подключения");
|
||||
Console.WriteLine("Connection string: " + connectionString);
|
||||
services.AddDbContext<DatabaseContext>(options => options.UseNpgsql(connectionString));
|
||||
services.AddSingleton<IDbContextFactory<DatabaseContext>, DbContextFactory>();
|
||||
}
|
||||
|
||||
public static void MigrateDb(this IApplicationBuilder app)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = app.ApplicationServices.CreateScope();
|
||||
var context = scope.ServiceProvider.GetRequiredService<IDbContextFactory<DatabaseContext>>();
|
||||
using var db = context.CreateDbContext();
|
||||
db.Database.Migrate();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
33
back/Controllers/Program.cs
Normal file
33
back/Controllers/Program.cs
Normal file
@ -0,0 +1,33 @@
|
||||
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.UseCors(builder => builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
await app.RunAsync();
|
41
back/Controllers/Properties/launchSettings.json
Normal file
41
back/Controllers/Properties/launchSettings.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
back/Controllers/appsettings.Development.json
Normal file
8
back/Controllers/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
9
back/Controllers/appsettings.json
Normal file
9
back/Controllers/appsettings.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
18
back/Infrastructure/DatabaseContext.cs
Normal file
18
back/Infrastructure/DatabaseContext.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using Contracts.DTO;
|
||||
using Infrastructure.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Infrastructure;
|
||||
|
||||
public class DatabaseContext : DbContext
|
||||
{
|
||||
public DatabaseContext(DbContextOptions<DatabaseContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public DbSet<User> Users { get; set; } = null!;
|
||||
public DbSet<SpendingGroup> SpendingGroups { get; set; } = null!;
|
||||
public DbSet<ChangeRecord> ChangeRecords { get; set; } = null!;
|
||||
public DbSet<SpendingPlan> SpendingPlans { get; set; } = null!;
|
||||
}
|
20
back/Infrastructure/DbContextFactory.cs
Normal file
20
back/Infrastructure/DbContextFactory.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Infrastructure;
|
||||
|
||||
public class DbContextFactory : IDbContextFactory<DatabaseContext>
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public DbContextFactory(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public DatabaseContext CreateDbContext()
|
||||
{
|
||||
var scope = _serviceProvider.CreateScope();
|
||||
return scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
||||
}
|
||||
}
|
17
back/Infrastructure/Infrastructure.csproj
Normal file
17
back/Infrastructure/Infrastructure.csproj
Normal file
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Contracts\Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
52
back/Infrastructure/Migrations/20241125164748_User.Designer.cs
generated
Normal file
52
back/Infrastructure/Migrations/20241125164748_User.Designer.cs
generated
Normal file
@ -0,0 +1,52 @@
|
||||
// <auto-generated />
|
||||
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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Balance")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
36
back/Infrastructure/Migrations/20241125164748_User.cs
Normal file
36
back/Infrastructure/Migrations/20241125164748_User.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class User : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Password = table.Column<string>(type: "text", nullable: false),
|
||||
Balance = table.Column<decimal>(type: "numeric", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
}
|
||||
}
|
||||
}
|
88
back/Infrastructure/Migrations/20241126140310_SpendingGroup.Designer.cs
generated
Normal file
88
back/Infrastructure/Migrations/20241126140310_SpendingGroup.Designer.cs
generated
Normal file
@ -0,0 +1,88 @@
|
||||
// <auto-generated />
|
||||
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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("SpendingGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Infrastructure.Models.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Balance")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("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
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SpendingGroup : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SpendingGroups",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
UserId = table.Column<Guid>(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");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "SpendingGroups");
|
||||
}
|
||||
}
|
||||
}
|
134
back/Infrastructure/Migrations/20241126185002_AddChangeRecord.Designer.cs
generated
Normal file
134
back/Infrastructure/Migrations/20241126185002_AddChangeRecord.Designer.cs
generated
Normal file
@ -0,0 +1,134 @@
|
||||
// <auto-generated />
|
||||
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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("ChangedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("SpendingGroupId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Sum")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SpendingGroupId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("ChangeRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("SpendingGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Infrastructure.Models.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Balance")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("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
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddChangeRecord : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ChangeRecords",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Sum = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
ChangedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
SpendingGroupId = table.Column<Guid>(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");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ChangeRecords");
|
||||
}
|
||||
}
|
||||
}
|
139
back/Infrastructure/Migrations/20241126210947_fixChangeRecord.Designer.cs
generated
Normal file
139
back/Infrastructure/Migrations/20241126210947_fixChangeRecord.Designer.cs
generated
Normal file
@ -0,0 +1,139 @@
|
||||
// <auto-generated />
|
||||
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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("ChangedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("SpendingGroupId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Sum")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SpendingGroupId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("ChangeRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("SpendingGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Infrastructure.Models.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Balance")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("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
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class fixChangeRecord : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ChangeRecords_SpendingGroups_SpendingGroupId",
|
||||
table: "ChangeRecords");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
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");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ChangeRecords_SpendingGroups_SpendingGroupId",
|
||||
table: "ChangeRecords");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
177
back/Infrastructure/Migrations/20241126222847_AddSpendingPlan.Designer.cs
generated
Normal file
177
back/Infrastructure/Migrations/20241126222847_AddSpendingPlan.Designer.cs
generated
Normal file
@ -0,0 +1,177 @@
|
||||
// <auto-generated />
|
||||
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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("ChangedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("SpendingGroupId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Sum")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SpendingGroupId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("ChangeRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("SpendingGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Infrastructure.Models.SpendingPlan", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("EndAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("SpendingGroupId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("StartAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<decimal>("Sum")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SpendingGroupId");
|
||||
|
||||
b.ToTable("SpendingPlans");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Infrastructure.Models.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Balance")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("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
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddSpendingPlan : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SpendingPlans",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
StartAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
EndAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
Sum = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
SpendingGroupId = table.Column<Guid>(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");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "SpendingPlans");
|
||||
}
|
||||
}
|
||||
}
|
174
back/Infrastructure/Migrations/DatabaseContextModelSnapshot.cs
Normal file
174
back/Infrastructure/Migrations/DatabaseContextModelSnapshot.cs
Normal file
@ -0,0 +1,174 @@
|
||||
// <auto-generated />
|
||||
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<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("ChangedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("SpendingGroupId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Sum")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SpendingGroupId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("ChangeRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Infrastructure.Models.SpendingGroup", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("SpendingGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Infrastructure.Models.SpendingPlan", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("EndAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("SpendingGroupId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("StartAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<decimal>("Sum")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SpendingGroupId");
|
||||
|
||||
b.ToTable("SpendingPlans");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Infrastructure.Models.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Balance")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("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
|
||||
}
|
||||
}
|
||||
}
|
24
back/Infrastructure/Models/Changerecord.cs
Normal file
24
back/Infrastructure/Models/Changerecord.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
12
back/Infrastructure/Models/SpendingGroup.cs
Normal file
12
back/Infrastructure/Models/SpendingGroup.cs
Normal file
@ -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<ChangeRecord>? ChangeRecords { get; set; }
|
||||
public List<SpendingPlan>? SpendingPlans { get; set; }
|
||||
}
|
22
back/Infrastructure/Models/SpendingPlan.cs
Normal file
22
back/Infrastructure/Models/SpendingPlan.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
27
back/Infrastructure/Models/User.cs
Normal file
27
back/Infrastructure/Models/User.cs
Normal file
@ -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<SpendingGroup>? SpendingGroups { get; set; }
|
||||
public List<ChangeRecord>? 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;
|
||||
}
|
||||
}
|
||||
}
|
98
back/Infrastructure/Repositories/ChangeRecordRepo.cs
Normal file
98
back/Infrastructure/Repositories/ChangeRecordRepo.cs
Normal file
@ -0,0 +1,98 @@
|
||||
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<DatabaseContext> _factory;
|
||||
|
||||
public ChangeRecordRepo(IDbContextFactory<DatabaseContext> factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public async Task<ChangeRecordDto> 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<ChangeRecordDto?> 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<ChangeRecordDto?> 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<IEnumerable<ChangeRecordDto>> 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);
|
||||
}
|
||||
if (search.UserId.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.UserId == search.UserId);
|
||||
}
|
||||
}
|
||||
return await query.Include(x => x.SpendingGroup).Select(x => x.ToDto()).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<ChangeRecordDto?> 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();
|
||||
}
|
||||
}
|
103
back/Infrastructure/Repositories/SpendingGroupRepo.cs
Normal file
103
back/Infrastructure/Repositories/SpendingGroupRepo.cs
Normal file
@ -0,0 +1,103 @@
|
||||
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<DatabaseContext> _factory;
|
||||
|
||||
public SpendingGroupRepo(IDbContextFactory<DatabaseContext> factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public async Task<SpendingGroupDto> 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<SpendingGroupDto?> 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<SpendingGroupDto?> 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
|
||||
|| (!string.IsNullOrWhiteSpace(search.Name)
|
||||
&& x.Name == search.Name
|
||||
&& x.UserId == search.UserId));
|
||||
|
||||
return group?.ToDto();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SpendingGroupDto>> 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) && search.UserId.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.Name.Contains(search.Name, StringComparison.OrdinalIgnoreCase)
|
||||
&& x.UserId == search.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
return await query
|
||||
.Include(x => x.ChangeRecords)
|
||||
.Include(x => x.SpendingPlans)
|
||||
.Select(x => x.ToDto())
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<SpendingGroupDto?> 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();
|
||||
}
|
||||
}
|
86
back/Infrastructure/Repositories/SpendingPlanRepo.cs
Normal file
86
back/Infrastructure/Repositories/SpendingPlanRepo.cs
Normal file
@ -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<DatabaseContext> _factory;
|
||||
|
||||
public SpendingPlanRepo(IDbContextFactory<DatabaseContext> factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public async Task<SpendingPlanDto> 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<SpendingPlanDto?> 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<SpendingPlanDto?> 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<IEnumerable<SpendingPlanDto>> 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<SpendingPlanDto?> 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();
|
||||
}
|
||||
}
|
91
back/Infrastructure/Repositories/UserRepo.cs
Normal file
91
back/Infrastructure/Repositories/UserRepo.cs
Normal file
@ -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<DatabaseContext> _factory;
|
||||
|
||||
public UserRepo(IDbContextFactory<DatabaseContext> factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public async Task<bool> 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<UserDto> 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<UserDto?> 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<UserDto?> 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<UserDto?> 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();
|
||||
}
|
||||
}
|
28
back/Infrastructure/Support/Mappers/ChangeRecordMapper.cs
Normal file
28
back/Infrastructure/Support/Mappers/ChangeRecordMapper.cs
Normal file
@ -0,0 +1,28 @@
|
||||
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,
|
||||
SpendingGroupName = changeRecord.SpendingGroup?.Name
|
||||
};
|
||||
|
||||
public static ChangeRecord ToModel(this ChangeRecordDto changeRecord)
|
||||
=> new()
|
||||
{
|
||||
Id = changeRecord.Id,
|
||||
Sum = changeRecord.Sum,
|
||||
ChangedAt = changeRecord.ChangedAt,
|
||||
SpendingGroupId = changeRecord.SpendingGroupId,
|
||||
UserId = changeRecord.UserId
|
||||
};
|
||||
}
|
24
back/Infrastructure/Support/Mappers/SpendingGroupMapper.cs
Normal file
24
back/Infrastructure/Support/Mappers/SpendingGroupMapper.cs
Normal file
@ -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
|
||||
};
|
||||
}
|
27
back/Infrastructure/Support/Mappers/SpendingPlanMapper.cs
Normal file
27
back/Infrastructure/Support/Mappers/SpendingPlanMapper.cs
Normal file
@ -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
|
||||
};
|
||||
}
|
25
back/Infrastructure/Support/Mappers/UserMapper.cs
Normal file
25
back/Infrastructure/Support/Mappers/UserMapper.cs
Normal file
@ -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
|
||||
};
|
||||
}
|
49
back/Services/Domain/AuthService.cs
Normal file
49
back/Services/Domain/AuthService.cs
Normal file
@ -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<UserViewModel> 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<UserViewModel> 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();
|
||||
}
|
||||
}
|
59
back/Services/Domain/ChangeRecordService.cs
Normal file
59
back/Services/Domain/ChangeRecordService.cs
Normal file
@ -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<ChangeRecordViewModel> Create(ChangeRecordDto model)
|
||||
{
|
||||
var record = await _changeRecordRepo.Create(model);
|
||||
|
||||
await _userRepo.ChangeBalance(new() { Id = model.UserId }, model.Sum);
|
||||
|
||||
return record.ToView();
|
||||
}
|
||||
|
||||
public async Task<ChangeRecordViewModel> 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<IEnumerable<ChangeRecordViewModel>> GetList(ChangeRecordSearch? search = null)
|
||||
{
|
||||
var records = await _changeRecordRepo.GetList(search);
|
||||
|
||||
return records.Select(x => x.ToView()).ToList();
|
||||
}
|
||||
|
||||
public async Task<ChangeRecordViewModel> 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();
|
||||
}
|
||||
}
|
63
back/Services/Domain/SpendingGroupService.cs
Normal file
63
back/Services/Domain/SpendingGroupService.cs
Normal file
@ -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<SpendingGroupViewModel> Create(SpendingGroupDto model)
|
||||
{
|
||||
var group = await _spendingGroupRepo.Create(model);
|
||||
return group.ToView();
|
||||
}
|
||||
|
||||
public async Task<SpendingGroupViewModel> Delete(SpendingGroupSearch search)
|
||||
{
|
||||
var group = await _spendingGroupRepo.Delete(search);
|
||||
if (group == null)
|
||||
{
|
||||
throw new EntityNotFoundException("При удалении не получилось найти группу");
|
||||
}
|
||||
|
||||
return group.ToView();
|
||||
}
|
||||
|
||||
public async Task<SpendingGroupViewModel> GetDetails(SpendingGroupSearch search)
|
||||
{
|
||||
var group = await _spendingGroupRepo.Get(search);
|
||||
if (group == null)
|
||||
{
|
||||
throw new EntityNotFoundException("Не удалось найти группу по таким параметрам");
|
||||
}
|
||||
|
||||
return group.ToView();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SpendingGroupViewModel>> GetList(SpendingGroupSearch? search = null)
|
||||
{
|
||||
var groups = await _spendingGroupRepo.GetList(search);
|
||||
return groups.Select(x => x.ToView()).ToList();
|
||||
}
|
||||
|
||||
public async Task<SpendingGroupViewModel> Update(SpendingGroupDto model)
|
||||
{
|
||||
var group = await _spendingGroupRepo.Update(model);
|
||||
if (group == null)
|
||||
{
|
||||
throw new EntityNotFoundException("При обновлении не получилось найти группу");
|
||||
}
|
||||
|
||||
return group.ToView();
|
||||
}
|
||||
}
|
61
back/Services/Domain/SpendingPlanService.cs
Normal file
61
back/Services/Domain/SpendingPlanService.cs
Normal file
@ -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<SpendingPlanViewModel> Create(SpendingPlanDto spendingPlan)
|
||||
{
|
||||
var plan = await _spendingPlanRepo.Create(spendingPlan);
|
||||
return plan.ToView();
|
||||
}
|
||||
|
||||
public async Task<SpendingPlanViewModel> Delete(SpendingPlanSearch search)
|
||||
{
|
||||
var plan = await _spendingPlanRepo.Delete(search);
|
||||
if (plan == null)
|
||||
{
|
||||
throw new EntityNotFoundException("При удалении не получилось найти план");
|
||||
}
|
||||
return plan.ToView();
|
||||
}
|
||||
|
||||
public async Task<SpendingPlanViewModel> GetDetails(SpendingPlanSearch search)
|
||||
{
|
||||
var plan = await _spendingPlanRepo.Get(search);
|
||||
if (plan == null)
|
||||
{
|
||||
throw new EntityNotFoundException("Не удалось найти план по таким параметрам");
|
||||
}
|
||||
return plan.ToView();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SpendingPlanViewModel>> GetList(SpendingPlanSearch? search = null)
|
||||
{
|
||||
var plans = await _spendingPlanRepo.GetList(search);
|
||||
return plans.Select(x => x.ToView()).ToList();
|
||||
}
|
||||
|
||||
public async Task<SpendingPlanViewModel> Update(SpendingPlanDto spendingPlan)
|
||||
{
|
||||
var plan = await _spendingPlanRepo.Update(spendingPlan);
|
||||
if (plan == null)
|
||||
{
|
||||
throw new EntityNotFoundException("При обновлении не получилось найти план");
|
||||
}
|
||||
return plan.ToView();
|
||||
}
|
||||
}
|
49
back/Services/Domain/UserService.cs
Normal file
49
back/Services/Domain/UserService.cs
Normal file
@ -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<UserViewModel> Delete(UserSearch search)
|
||||
{
|
||||
var user = await _userRepo.Delete(search);
|
||||
if (user == null)
|
||||
{
|
||||
throw new UserNotFoundException($"Пользователь для удаления не найден");
|
||||
}
|
||||
return user.ToView();
|
||||
}
|
||||
|
||||
public async Task<UserViewModel> GetDetails(UserSearch search)
|
||||
{
|
||||
var user = await _userRepo.Get(search);
|
||||
if (user == null)
|
||||
{
|
||||
throw new UserNotFoundException($"Пользователь {search.Name} не найден");
|
||||
}
|
||||
return user.ToView();
|
||||
}
|
||||
|
||||
public async Task<UserViewModel> UpdateUserData(UserDto user)
|
||||
{
|
||||
var updatedUser = await _userRepo.Update(user);
|
||||
if (updatedUser == null)
|
||||
{
|
||||
throw new EntityNotFoundException("При обновлении не получилось найти пользователя с id = " + user.Id);
|
||||
}
|
||||
return updatedUser.ToView();
|
||||
}
|
||||
}
|
13
back/Services/Services.csproj
Normal file
13
back/Services/Services.csproj
Normal file
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Contracts\Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
@ -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) { }
|
||||
}
|
10
back/Services/Support/Exceptions/EntityNotFoundException.cs
Normal file
10
back/Services/Support/Exceptions/EntityNotFoundException.cs
Normal file
@ -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) { }
|
||||
|
||||
}
|
12
back/Services/Support/Exceptions/UserNotFoundException.cs
Normal file
12
back/Services/Support/Exceptions/UserNotFoundException.cs
Normal file
@ -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) { }
|
||||
|
||||
}
|
41
docker-compose.debug.yml
Normal file
41
docker-compose.debug.yml
Normal file
@ -0,0 +1,41 @@
|
||||
# Please refer https://aka.ms/HTTPSinContainer on how to setup an https developer certificate for your ASP.NET Core service.
|
||||
services:
|
||||
api:
|
||||
image: api
|
||||
build:
|
||||
context: .
|
||||
dockerfile: back/Controllers/Dockerfile
|
||||
args:
|
||||
- configuration=Debug
|
||||
ports:
|
||||
- 5125:5125
|
||||
environment:
|
||||
- ConnectionStrings__DefaultConnection=${CONNECTION_STRING}
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
volumes:
|
||||
- ~/.vsdbg:/remote_debugger:rw
|
||||
depends_on:
|
||||
- database
|
||||
dombudg:
|
||||
image: dombudg
|
||||
build:
|
||||
context: front
|
||||
dockerfile: ./Dockerfile
|
||||
environment:
|
||||
- VITE_API_URL=http://api:5125
|
||||
ports:
|
||||
- 80:80
|
||||
depends_on:
|
||||
- api
|
||||
database:
|
||||
image: postgres:14
|
||||
environment:
|
||||
- POSTGRES_DB=${POSTGRES_DB}
|
||||
- POSTGRES_USER=${POSTGRES_USER}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local
|
35
docker-compose.yml
Normal file
35
docker-compose.yml
Normal file
@ -0,0 +1,35 @@
|
||||
services:
|
||||
api:
|
||||
image: api
|
||||
build:
|
||||
context: .
|
||||
dockerfile: back/Controllers/Dockerfile
|
||||
ports:
|
||||
- 5125:5125
|
||||
environment:
|
||||
- ConnectionStrings__DefaultConnection=${CONNECTION_STRING}
|
||||
depends_on:
|
||||
- database
|
||||
dombudg:
|
||||
image: dombudg
|
||||
build:
|
||||
context: front
|
||||
dockerfile: ./Dockerfile
|
||||
environment:
|
||||
- VITE_API_URL=http://api:5125
|
||||
ports:
|
||||
- 80:80
|
||||
depends_on:
|
||||
- api
|
||||
database:
|
||||
image: postgres:14
|
||||
environment:
|
||||
- POSTGRES_DB=${POSTGRES_DB}
|
||||
- POSTGRES_USER=${POSTGRES_USER}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local
|
24
front/.dockerignore
Normal file
24
front/.dockerignore
Normal file
@ -0,0 +1,24 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
26
front/.gitignore
vendored
Normal file
26
front/.gitignore
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
# 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?
|
||||
|
||||
.env
|
18
front/Dockerfile
Normal file
18
front/Dockerfile
Normal file
@ -0,0 +1,18 @@
|
||||
FROM node as vite-app
|
||||
|
||||
ARG VITE_API_URL
|
||||
|
||||
WORKDIR /app/client
|
||||
COPY . .
|
||||
|
||||
RUN ["npm", "i"]
|
||||
RUN ["npm", "run", "build"]
|
||||
|
||||
FROM nginx:alpine
|
||||
|
||||
COPY nginx.conf /etc/nginx
|
||||
|
||||
RUN rm -rf /usr/share/nginx/html/*
|
||||
COPY --from=vite-app /app/client/dist /usr/share/nginx/html
|
||||
|
||||
ENTRYPOINT ["nginx", "-g", "daemon off;"]
|
38
front/components.d.ts
vendored
Normal file
38
front/components.d.ts
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AButton: typeof import('ant-design-vue/es')['Button']
|
||||
ADatePicker: typeof import('ant-design-vue/es')['DatePicker']
|
||||
AForm: typeof import('ant-design-vue/es')['Form']
|
||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||
AInput: typeof import('ant-design-vue/es')['Input']
|
||||
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
|
||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||
ALayout: typeof import('ant-design-vue/es')['Layout']
|
||||
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
|
||||
ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
|
||||
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||
ASpace: typeof import('ant-design-vue/es')['Space']
|
||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||
ATable: typeof import('ant-design-vue/es')['Table']
|
||||
ChangeRecordManager: typeof import('./src/components/support/ChangeRecordManager.vue')['default']
|
||||
Groups: typeof import('./src/components/pages/Groups.vue')['default']
|
||||
Header: typeof import('./src/components/main/Header.vue')['default']
|
||||
Home: typeof import('./src/components/pages/Home.vue')['default']
|
||||
Login: typeof import('./src/components/pages/Login.vue')['default']
|
||||
PlanManager: typeof import('./src/components/support/PlanManager.vue')['default']
|
||||
Plans: typeof import('./src/components/pages/Plans.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SignUp: typeof import('./src/components/pages/SignUp.vue')['default']
|
||||
SpendingGroupManager: typeof import('./src/components/support/SpendingGroupManager.vue')['default']
|
||||
}
|
||||
}
|
12
front/index.html
Normal file
12
front/index.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ДомБюдж</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
55
front/nginx.conf
Normal file
55
front/nginx.conf
Normal file
@ -0,0 +1,55 @@
|
||||
# Запускать в качестве менее привилегированного пользователя по соображениям безопасности..
|
||||
user nginx;
|
||||
|
||||
# Значение auto устанавливает число максимально доступных ядер CPU,
|
||||
# чтобы обеспечить лучшую производительность.
|
||||
worker_processes auto;
|
||||
|
||||
events { worker_connections 1024; }
|
||||
|
||||
http {
|
||||
server {
|
||||
# Hide nginx version information.
|
||||
server_tokens off;
|
||||
|
||||
listen 80;
|
||||
root /usr/share/nginx/html;
|
||||
include /etc/nginx/mime.types;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://api:5125/api/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Prefix /test;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_http_version 1.0;
|
||||
gzip_comp_level 5;
|
||||
gzip_types
|
||||
application/atom+xml
|
||||
application/javascript
|
||||
application/json
|
||||
application/rss+xml
|
||||
application/vnd.ms-fontobject
|
||||
application/x-font-ttf
|
||||
application/x-web-app-manifest+json
|
||||
application/xhtml+xml
|
||||
application/xml
|
||||
font/opentype
|
||||
image/svg+xml
|
||||
image/x-icon
|
||||
text/css
|
||||
text/plain
|
||||
text/x-component;
|
||||
gzip_proxied no-cache no-store private expired auth;
|
||||
gzip_min_length 256;
|
||||
gunzip on;
|
||||
}
|
||||
}
|
3000
front/package-lock.json
generated
Normal file
3000
front/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
front/package.json
Normal file
28
front/package.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "dombudg",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"gen-api": "swagger-typescript-api -r -o ./src/core/api/ --modular -p "
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^12.0.0",
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"dayjs": "^1.11.13",
|
||||
"pinia": "^2.2.8",
|
||||
"vue": "^3.5.12",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"swagger-typescript-api": "^13.0.23",
|
||||
"typescript": "~5.6.2",
|
||||
"unplugin-vue-components": "^0.27.5",
|
||||
"vite": "^5.4.10",
|
||||
"vue-tsc": "^2.1.8"
|
||||
}
|
||||
}
|
26
front/src/App.vue
Normal file
26
front/src/App.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import Header from './components/main/Header.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-layout class="layout">
|
||||
<Header />
|
||||
<a-layout-content>
|
||||
<RouterView />
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
main {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 5vh;
|
||||
}
|
||||
|
||||
.base-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 80dvw;
|
||||
}
|
||||
</style>
|
59
front/src/components/main/Header.vue
Normal file
59
front/src/components/main/Header.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { inject } from 'vue';
|
||||
import { useUserStore } from '../../store';
|
||||
import { AuthService } from '../../core/services/auth-service';
|
||||
import router from '../../router';
|
||||
|
||||
const store = useUserStore();
|
||||
const authService = inject(AuthService.name) as AuthService;
|
||||
|
||||
function logout() {
|
||||
authService.logout();
|
||||
router.push({ name: 'login' });
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-layout-header class="header">
|
||||
<div class="base-nav">
|
||||
<div>ДомБюдж</div>
|
||||
<nav>
|
||||
<RouterLink :to="{ name: 'home' }">Главная</RouterLink>
|
||||
<RouterLink :to="{ name: 'groups' }">Группы расходов</RouterLink>
|
||||
</nav>
|
||||
</div>
|
||||
<div v-if="!store.user.id">
|
||||
<RouterLink :to="{ name: 'login' }">Войти</RouterLink>
|
||||
</div>
|
||||
<div v-else>
|
||||
<label for="logout">Привет, {{ store.user.name }}! Ваш текущий баланс: {{ store.user.balance }}</label>
|
||||
<a-button
|
||||
name="logout"
|
||||
@click="logout()"
|
||||
danger
|
||||
style="margin-left: 30px"
|
||||
>
|
||||
Выйти
|
||||
</a-button>
|
||||
</div>
|
||||
</a-layout-header>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.header {
|
||||
background-color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.base-nav {
|
||||
display: inline-flex;
|
||||
justify-content: left;
|
||||
}
|
||||
.base-nav a {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
</style>
|
72
front/src/components/pages/Groups.vue
Normal file
72
front/src/components/pages/Groups.vue
Normal file
@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import { useAsyncState } from '@vueuse/core';
|
||||
import { inject } from 'vue';
|
||||
import { GroupService } from '../../core/services/group-service';
|
||||
import SpendingGroupManager from '../support/SpendingGroupManager.vue';
|
||||
import { DeleteOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const groupService = inject(GroupService.name) as GroupService;
|
||||
|
||||
const { state, isReady } = useAsyncState(() => groupService.getList(), []);
|
||||
const columns = [
|
||||
{
|
||||
title: "Название группы",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "Планы группы",
|
||||
dataIndex: "plans",
|
||||
key: "plans",
|
||||
},
|
||||
{
|
||||
title: 'Операция',
|
||||
dataIndex: 'operation',
|
||||
key: 'operation',
|
||||
}
|
||||
]
|
||||
|
||||
const refreshData = () => {
|
||||
groupService.getList().then(data => {
|
||||
state.value = data;
|
||||
isReady.value = true;
|
||||
});
|
||||
}
|
||||
|
||||
const onDelete = (key: string) => {
|
||||
groupService.deleteGroup(key)
|
||||
.then(() => {
|
||||
refreshData();
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="base-page">
|
||||
<h1>Группы расходов</h1>
|
||||
<SpendingGroupManager :refreshData="refreshData" />
|
||||
<a-table :dataSource="state" :columns="columns" v-if="isReady">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'plans'">
|
||||
<RouterLink :to="{ name: 'plans', params: { groupId: record.id } }" >Планы</RouterLink>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'operation'">
|
||||
<a-popconfirm
|
||||
v-if="state?.length"
|
||||
title="Точно удалить?"
|
||||
@confirm="onDelete(record.id)"
|
||||
>
|
||||
<a><DeleteOutlined /> Удалить</a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<div v-else>
|
||||
<a-spin size="large" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
76
front/src/components/pages/Home.vue
Normal file
76
front/src/components/pages/Home.vue
Normal file
@ -0,0 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
import { useAsyncState } from '@vueuse/core';
|
||||
import ChangeRecordMenu from '../support/ChangeRecordManager.vue';
|
||||
import { ChangeRecordService } from '../../core/services/change-record-service';
|
||||
import { inject } from 'vue';
|
||||
import { DeleteOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const changeRecordService = inject(ChangeRecordService.name) as ChangeRecordService;
|
||||
|
||||
const { state, isReady } = useAsyncState(() => changeRecordService.getList(), []);
|
||||
const columns = [
|
||||
{
|
||||
title: 'Дата',
|
||||
dataIndex: 'changedAt',
|
||||
key: 'changedAt',
|
||||
},
|
||||
{
|
||||
title: 'Сумма',
|
||||
dataIndex: 'sum',
|
||||
key: 'sum',
|
||||
},
|
||||
{
|
||||
title: 'Группа расходов',
|
||||
dataIndex: 'spendingGroupName',
|
||||
key: 'spendingGroupName',
|
||||
},
|
||||
{
|
||||
title: 'Операция',
|
||||
dataIndex: 'operation',
|
||||
key: 'operation',
|
||||
}
|
||||
]
|
||||
|
||||
const refreshData = () => {
|
||||
changeRecordService.getList().then(data => {
|
||||
state.value = data;
|
||||
isReady.value = true;
|
||||
});
|
||||
}
|
||||
|
||||
const onDelete = (key: string) => {
|
||||
changeRecordService.deleteRecord(key)
|
||||
.then(() => {
|
||||
refreshData();
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="base-page">
|
||||
<h1>История изменений баланса</h1>
|
||||
<ChangeRecordMenu :refreshData="refreshData" />
|
||||
<a-table :dataSource="state" :columns="columns" v-if="isReady" >
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-popconfirm
|
||||
v-if="state?.length"
|
||||
title="Точно удалить?"
|
||||
@confirm="onDelete(record.id)"
|
||||
>
|
||||
<a><DeleteOutlined /> Удалить</a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<div v-else>
|
||||
<a-spin size="large" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.layout {
|
||||
|
||||
}
|
||||
</style>
|
76
front/src/components/pages/Login.vue
Normal file
76
front/src/components/pages/Login.vue
Normal file
@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<a-form
|
||||
:model="formState"
|
||||
name="login"
|
||||
class="login-form"
|
||||
@finish="onFinish"
|
||||
@finishFailed="onFinishFailed"
|
||||
>
|
||||
<a-form-item
|
||||
label="Логин"
|
||||
name="name"
|
||||
:rules="[{ required: true, message: 'Пожалуйста, введите свой логин' }]"
|
||||
>
|
||||
<a-input v-model:value="formState.name">
|
||||
<template #prefix>
|
||||
<UserOutlined class="site-form-item-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="Пароль"
|
||||
name="password"
|
||||
:rules="[{ required: true, message: 'Пароль тоже нужен!' }]"
|
||||
>
|
||||
<a-input-password v-model:value="formState.password">
|
||||
<template #prefix>
|
||||
<LockOutlined class="site-form-item-icon" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button :disabled="disabled" type="primary" html-type="submit" class="login-form-button">
|
||||
Войти
|
||||
</a-button>
|
||||
Или
|
||||
<RouterLink :to="{ name: 'signup' }">создать аккаунт</RouterLink>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, computed, inject } from 'vue';
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue';
|
||||
import { UserLoginDto } from '../../core/api/data-contracts';
|
||||
import { RouterLink } from 'vue-router';
|
||||
import { AuthService } from '../../core/services/auth-service';
|
||||
import router from '../../router';
|
||||
|
||||
const formState = reactive<UserLoginDto>({
|
||||
name: '',
|
||||
password: '',
|
||||
});
|
||||
const authService = inject(AuthService.name) as AuthService;
|
||||
console.log(authService);
|
||||
// Логика формы
|
||||
const onFinish = async (values: any) => {
|
||||
console.log('Success:', values);
|
||||
await authService.login(formState);
|
||||
router.push({ name: 'home' });
|
||||
};
|
||||
const onFinishFailed = (errorInfo: any) => {
|
||||
console.log('Failed:', errorInfo);
|
||||
};
|
||||
const disabled = computed(() => {
|
||||
return !(formState.name && formState.password);
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.login-form {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.login-form-button {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
79
front/src/components/pages/Plans.vue
Normal file
79
front/src/components/pages/Plans.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<script setup lang="ts">
|
||||
import { useAsyncState } from '@vueuse/core';
|
||||
import { inject } from 'vue';
|
||||
import { PlanService } from '../../core/services/plans-service';
|
||||
import { useRoute } from 'vue-router';
|
||||
import PlanManager from '../support/PlanManager.vue';
|
||||
import { DeleteOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const planService = inject(PlanService.name) as PlanService;
|
||||
const groupId = useRoute().params.groupId as string;
|
||||
|
||||
|
||||
|
||||
const { state, isReady } = useAsyncState(() => planService.getList(groupId), []);
|
||||
const columns = [
|
||||
{
|
||||
title: "Планируемые расходы",
|
||||
dataIndex: "sum",
|
||||
key: "sum",
|
||||
},
|
||||
{
|
||||
title: "Начало плана",
|
||||
dataIndex: "startAt",
|
||||
key: "startAt",
|
||||
},
|
||||
{
|
||||
title: "Конец плана",
|
||||
dataIndex: "endAt",
|
||||
key: "endAt",
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Операция',
|
||||
dataIndex: 'operation',
|
||||
key: 'operation',
|
||||
}
|
||||
]
|
||||
|
||||
const refreshData = () => {
|
||||
planService.getList(groupId).then(data => {
|
||||
state.value = data;
|
||||
isReady.value = true;
|
||||
});
|
||||
};
|
||||
|
||||
const onDelete = (key: string) => {
|
||||
planService.deletePlan(key)
|
||||
.then(() => {
|
||||
refreshData();
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="base-page">
|
||||
<h1>Планы группы</h1>
|
||||
<PlanManager :groupId="groupId" :refreshData="refreshData"/>
|
||||
<a-table :dataSource="state" :columns="columns" v-if="isReady">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-popconfirm
|
||||
v-if="state?.length"
|
||||
title="Точно удалить?"
|
||||
@confirm="onDelete(record.id)"
|
||||
>
|
||||
<a><DeleteOutlined /> Удалить</a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<div v-else>
|
||||
<a-spin size="large" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
86
front/src/components/pages/SignUp.vue
Normal file
86
front/src/components/pages/SignUp.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<a-form
|
||||
:model="formState"
|
||||
name="signup"
|
||||
class="signup-form"
|
||||
@finish="onFinish"
|
||||
@finishFailed="onFinishFailed"
|
||||
>
|
||||
<a-form-item
|
||||
label="Логин"
|
||||
name="name"
|
||||
:rules="[{ required: true, message: 'Пожалуйста, введите свой логин' }]"
|
||||
>
|
||||
<a-input v-model:value="formState.name">
|
||||
<template #prefix>
|
||||
<UserOutlined class="site-form-item-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="Пароль"
|
||||
name="password"
|
||||
:rules="[{ required: true, message: 'Пароль тоже нужен!' }]"
|
||||
>
|
||||
<a-input-password v-model:value="formState.password">
|
||||
<template #prefix>
|
||||
<LockOutlined class="site-form-item-icon" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="Баланс"
|
||||
name="balance"
|
||||
:rules="[{ required: true, message: 'Пожалуйста, введите свой баланс' }]"
|
||||
>
|
||||
<a-input-number prefix="₽" v-model:value="formState.balance" min="0"/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button :disabled="disabled" type="primary" html-type="submit" class="signup-form-button">
|
||||
Создать
|
||||
</a-button>
|
||||
Или
|
||||
<RouterLink :to="{ name: 'login' }">войти в свой аккаунт</RouterLink>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, computed, inject } from 'vue';
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue';
|
||||
import { UserDto } from '../../core/api/data-contracts';
|
||||
import { RouterLink } from 'vue-router';
|
||||
import { AuthService } from '../../core/services/auth-service';
|
||||
import router from '../../router';
|
||||
|
||||
const formState = reactive<UserDto>({
|
||||
name: '',
|
||||
password: '',
|
||||
balance: 0
|
||||
});
|
||||
const authService = inject(AuthService.name) as AuthService;
|
||||
|
||||
// Логика формы
|
||||
const onFinish = async (values: any) => {
|
||||
console.log('Success:', values);
|
||||
await authService.register(formState);
|
||||
router.push({ name: 'home' });
|
||||
};
|
||||
const onFinishFailed = (errorInfo: any) => {
|
||||
console.log('Failed:', errorInfo);
|
||||
};
|
||||
const disabled = computed(() => {
|
||||
return !(formState.name && formState.password && formState.balance);
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.signup-form {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.signup-form-button {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user