Compare commits

..

No commits in common. "e1e9324362a681fdebb11ff6f6d9ceb2bd690ddb" and "5fecd5b6fce4833f610832dc0318e5cae104f35f" have entirely different histories.

45 changed files with 203 additions and 1045 deletions

Binary file not shown.

45
go.mod
View File

@ -3,8 +3,6 @@ module madsky.ru/go-finance
go 1.24.0 go 1.24.0
require ( require (
github.com/doganarif/govisual v0.1.8
github.com/gin-gonic/gin v1.10.0
github.com/goccy/go-json v0.10.5 github.com/goccy/go-json v0.10.5
github.com/ilyakaznacheev/cleanenv v1.5.0 github.com/ilyakaznacheev/cleanenv v1.5.0
github.com/jackc/pgx/v5 v5.7.2 github.com/jackc/pgx/v5 v5.7.2
@ -14,52 +12,17 @@ require (
require ( require (
github.com/BurntSushi/toml v1.2.1 // indirect github.com/BurntSushi/toml v1.2.1 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/joho/godotenv v1.5.1 // indirect github.com/joho/godotenv v1.5.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/kr/text v0.2.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.opentelemetry.io/otel v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.32.0 // indirect
go.opentelemetry.io/otel/sdk v1.32.0 // indirect
go.opentelemetry.io/otel/trace v1.32.0 // indirect
go.opentelemetry.io/proto/otlp v1.1.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.31.0 // indirect golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.32.0 // indirect golang.org/x/net v0.32.0 // indirect
golang.org/x/sync v0.13.0 // indirect golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.32.0 // indirect golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.24.0 // indirect golang.org/x/text v0.21.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect

114
go.sum
View File

@ -1,58 +1,21 @@
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/doganarif/govisual v0.1.8 h1:qAimY3yJNPl84U1ZNEzua4EP5+DxBnLQpdtZmsmd8ig=
github.com/doganarif/govisual v0.1.8/go.mod h1:UC4PGlP6cZRjoTlIUFPOOKSEwbLgPF9kDIoIO1Y4LJs=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4= github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4=
github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk= github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
@ -65,61 +28,21 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
@ -128,27 +51,16 @@ go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiy
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E=
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
@ -158,14 +70,8 @@ google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojt
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ=
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@ -1,6 +0,0 @@
package auth
type LoginDto struct {
Login string `json:"login"`
Password string `json:"password"`
}

View File

@ -28,7 +28,7 @@ func NewRepository(client database.Client) Repository {
} }
func (r *repository) Find(ctx context.Context) ([]*status.Status, error) { func (r *repository) Find(ctx context.Context) ([]*status.Status, error) {
query := "select id, name, description, position from statuses order by position asc" query := "select id, name, description, position from statuses order by id asc"
rows, err := r.client.Query(ctx, query) rows, err := r.client.Query(ctx, query)
if err != nil { if err != nil {

View File

@ -1,50 +0,0 @@
package user
import (
"context"
"madsky.ru/go-finance/internal/database"
"madsky.ru/go-finance/internal/user"
)
type Repository interface {
Find(ctx context.Context) ([]*user.User, error)
FindOne(ctx context.Context, id uint64) (*user.User, error)
Create(ctx context.Context, dto *user.CreateUserDTO) (*user.User, error)
Update(ctx context.Context, id uint64, issue *user.CreateUserDTO) (*user.User, error)
Remove(ctx context.Context, id uint64) (uint64, error)
}
type repository struct {
client database.Client
}
func NewRepository(client database.Client) Repository {
return &repository{
client: client,
}
}
func (r *repository) Find(ctx context.Context) ([]*user.User, error) {
//TODO implement me
panic("implement me")
}
func (r *repository) FindOne(ctx context.Context, id uint64) (*user.User, error) {
//TODO implement me
panic("implement me")
}
func (r *repository) Create(ctx context.Context, dto *user.CreateUserDTO) (*user.User, error) {
//TODO implement me
panic("implement me")
}
func (r *repository) Update(ctx context.Context, id uint64, issue *user.CreateUserDTO) (*user.User, error) {
//TODO implement me
panic("implement me")
}
func (r repository) Remove(ctx context.Context, id uint64) (uint64, error) {
//TODO implement me
panic("implement me")
}

View File

@ -1,35 +0,0 @@
package handlers
import (
"context"
"fmt"
"github.com/goccy/go-json"
"madsky.ru/go-finance/internal/auth"
repository "madsky.ru/go-finance/internal/repository/user"
"madsky.ru/go-finance/internal/server/response"
"net/http"
)
func RegisterAuthRoutes(mux *http.ServeMux, ctx context.Context, repository repository.Repository) {
mux.HandleFunc("POST /api/login", Login(ctx, repository))
}
func Login(ctx context.Context, repository repository.Repository) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
p := 0
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
var loginDto auth.LoginDto
if err := dec.Decode(&loginDto); err != nil {
response.Error(w, err, http.StatusBadRequest)
return
}
fmt.Println(loginDto)
response.WriteJSON(w, nil, http.StatusCreated, &p)
}
}

View File

@ -10,14 +10,6 @@ import (
"net/http" "net/http"
) )
func RegisterIssueRoutes(mux *http.ServeMux, ctx context.Context, repository issue.Repository) {
mux.HandleFunc("GET /api/issues/", FindIssues(ctx, repository))
mux.HandleFunc("GET /api/issues/{id}", FindIssuesByID(ctx, repository))
mux.HandleFunc("POST /api/issues", CreateIssues(ctx, repository))
mux.HandleFunc("POST /api/issues/positions", UpdatePositions(ctx, repository))
mux.HandleFunc("DELETE /api/issues/{id}", DeleteIssues(ctx, repository))
}
func FindIssues(ctx context.Context, repository issue.Repository) http.HandlerFunc { func FindIssues(ctx context.Context, repository issue.Repository) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
i, err := repository.Find(ctx) i, err := repository.Find(ctx)

View File

@ -10,14 +10,6 @@ import (
"net/http" "net/http"
) )
func RegisterProjectRoutes(mux *http.ServeMux, ctx context.Context, repository project.Repository) {
mux.HandleFunc("GET /api/projects", FindProjects(ctx, repository))
mux.HandleFunc("GET /api/projects/{id}", FindProjectByID(ctx, repository))
mux.HandleFunc("POST /api/projects", CreateProject(ctx, repository))
mux.HandleFunc("PUT /api/projects/{id}", UpdateProject(ctx, repository))
mux.HandleFunc("DELETE /api/projects/{id}", DeleteProject(ctx, repository))
}
func FindProjects(ctx context.Context, repository project.Repository) http.HandlerFunc { func FindProjects(ctx context.Context, repository project.Repository) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
p, err := repository.Find(ctx) p, err := repository.Find(ctx)

View File

@ -10,13 +10,6 @@ import (
"net/http" "net/http"
) )
func RegisterStatusRoutes(mux *http.ServeMux, ctx context.Context, repository status.Repository) {
mux.HandleFunc("GET /api/statuses/", FindStatuses(ctx, repository))
mux.HandleFunc("GET /api/statuses/{id}", FindStatusById(ctx, repository))
mux.HandleFunc("POST /api/statuses", CreateStatus(ctx, repository))
mux.HandleFunc("DELETE /api/statuses/{id}", DeleteStatus(ctx, repository))
}
func FindStatuses(ctx context.Context, repository status.Repository) http.HandlerFunc { func FindStatuses(ctx context.Context, repository status.Repository) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
s, err := repository.Find(ctx) s, err := repository.Find(ctx)

View File

@ -2,20 +2,14 @@ package server
import ( import (
"context" "context"
"io/fs"
"madsky.ru/go-finance/web"
"os"
"strings"
//"github.com/doganarif/govisual"
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
"log/slog" "log/slog"
"madsky.ru/go-finance/internal/repository/issue" "madsky.ru/go-finance/internal/repository/issue"
"madsky.ru/go-finance/internal/repository/project" "madsky.ru/go-finance/internal/repository/project"
"madsky.ru/go-finance/internal/repository/status" "madsky.ru/go-finance/internal/repository/status"
"madsky.ru/go-finance/internal/repository/user"
"madsky.ru/go-finance/internal/server/handlers" "madsky.ru/go-finance/internal/server/handlers"
"madsky.ru/go-finance/internal/server/middleware"
"madsky.ru/go-finance/web"
"net/http" "net/http"
"time" "time"
) )
@ -23,74 +17,54 @@ import (
type Server struct { type Server struct {
http *http.Server http *http.Server
logger *slog.Logger logger *slog.Logger
gin *gin.Engine }
func RegisterProjectRoutes(mux *http.ServeMux, ctx context.Context, repository project.Repository) {
mux.HandleFunc("GET /api/projects", handlers.FindProjects(ctx, repository))
mux.HandleFunc("GET /api/projects/{id}", handlers.FindProjectByID(ctx, repository))
mux.HandleFunc("POST /api/projects", handlers.CreateProject(ctx, repository))
mux.HandleFunc("PUT /api/projects/{id}", handlers.UpdateProject(ctx, repository))
mux.HandleFunc("DELETE /api/projects/{id}", handlers.DeleteProject(ctx, repository))
}
func RegisterStatusRoutes(mux *http.ServeMux, ctx context.Context, repository status.Repository) {
mux.HandleFunc("GET /api/statuses/", handlers.FindStatuses(ctx, repository))
mux.HandleFunc("GET /api/statuses/{id}", handlers.FindStatusById(ctx, repository))
mux.HandleFunc("POST /api/statuses", handlers.CreateStatus(ctx, repository))
mux.HandleFunc("DELETE /api/statuses/{id}", handlers.DeleteStatus(ctx, repository))
}
func RegisterIssueRoutes(mux *http.ServeMux, ctx context.Context, repository issue.Repository) {
mux.HandleFunc("GET /api/issues/", handlers.FindIssues(ctx, repository))
mux.HandleFunc("GET /api/issues/{id}", handlers.FindIssuesByID(ctx, repository))
mux.HandleFunc("POST /api/issues", handlers.CreateIssues(ctx, repository))
mux.HandleFunc("POST /api/issues/positions", handlers.UpdatePositions(ctx, repository))
mux.HandleFunc("DELETE /api/issues/{id}", handlers.DeleteIssues(ctx, repository))
} }
func NewServer(ctx context.Context, client *pgxpool.Pool, logger *slog.Logger) *Server { func NewServer(ctx context.Context, client *pgxpool.Pool, logger *slog.Logger) *Server {
const addr = "0.0.0.0:3000" const addr = "localhost:3000"
//r := gin.Default()
//
//r.GET("/ping", func(c *gin.Context) {
// c.JSON(http.StatusOK, gin.H{
// "message": "pong",
// })
//})
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle("/", http.FileServer(http.FS(web.Dist)))
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler := middleware.LoggingMiddleware(mux, logger)
dist, err := fs.Sub(web.DistDir, "dist")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
f, err := dist.Open(strings.TrimPrefix(r.URL.Path, "/"))
if err == nil {
defer func(f fs.File) {
err := f.Close()
if err != nil {
logger.Error("error close file", err)
}
}(f)
}
if os.IsNotExist(err) {
r.URL.Path = "/"
}
http.FileServer(http.FS(dist)).ServeHTTP(w, r)
}))
//http://localhost:8080/__viz
//handler := govisual.Wrap(
// mux,
// govisual.WithRequestBodyLogging(true),
// govisual.WithResponseBodyLogging(true),
//)
//handler := middleware.LoggingMiddleware(mux, logger)
projectsRepository := project.NewRepository(client) projectsRepository := project.NewRepository(client)
handlers.RegisterProjectRoutes(mux, ctx, projectsRepository) RegisterProjectRoutes(mux, ctx, projectsRepository)
statusRepository := status.NewRepository(client) statusRepository := status.NewRepository(client)
handlers.RegisterStatusRoutes(mux, ctx, statusRepository) RegisterStatusRoutes(mux, ctx, statusRepository)
issueRepository := issue.NewRepository(client) issueRepository := issue.NewRepository(client)
handlers.RegisterIssueRoutes(mux, ctx, issueRepository) RegisterIssueRoutes(mux, ctx, issueRepository)
userRepository := user.NewRepository(client) logger.Info("start server", slog.String("addr", addr))
handlers.RegisterAuthRoutes(mux, ctx, userRepository)
//mux.Handle("/api/", http.StripPrefix("/api", apiHandler))
//logger.Info("start server", slog.String("addr", addr))
return &Server{ return &Server{
logger: logger, logger: logger,
//gin: r,
http: &http.Server{ http: &http.Server{
Addr: addr, Addr: addr,
Handler: mux, Handler: handler,
ReadTimeout: 10 * time.Second, ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20, MaxHeaderBytes: 1 << 20,
@ -99,7 +73,6 @@ func NewServer(ctx context.Context, client *pgxpool.Pool, logger *slog.Logger) *
} }
func (s *Server) Start() error { func (s *Server) Start() error {
//s.gin.Run('0.0.0.0:3000')
return s.http.ListenAndServe() return s.http.ListenAndServe()
} }

View File

@ -1,3 +0,0 @@
package user
type CreateUserDTO struct{}

View File

@ -1,6 +0,0 @@
package user
type User struct {
ID uint32 `json:"id"`
Login string `json:"login"`
}

View File

@ -11,7 +11,7 @@
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-draggable-next": "^2.2.1", "vue-draggable-next": "^2.2.1",
"vue-router": "^4.5.0", "vue-router": "^4.5.0",
"vuetify": "^3.8.2", "vuetify": "^3.7.16",
}, },
"devDependencies": { "devDependencies": {
"@mdi/font": "^7.4.47", "@mdi/font": "^7.4.47",
@ -803,7 +803,7 @@
"vue-tsc": ["vue-tsc@2.2.8", "", { "dependencies": { "@volar/typescript": "~2.4.11", "@vue/language-core": "2.2.8" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-jBYKBNFADTN+L+MdesNX/TB3XuDSyaWynKMDgR+yCSln0GQ9Tfb7JS2lr46s2LiFUT1WsmfWsSvIElyxzOPqcQ=="], "vue-tsc": ["vue-tsc@2.2.8", "", { "dependencies": { "@volar/typescript": "~2.4.11", "@vue/language-core": "2.2.8" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-jBYKBNFADTN+L+MdesNX/TB3XuDSyaWynKMDgR+yCSln0GQ9Tfb7JS2lr46s2LiFUT1WsmfWsSvIElyxzOPqcQ=="],
"vuetify": ["vuetify@3.8.2", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=2.1.0", "vue": "^3.5.0", "webpack-plugin-vuetify": ">=3.1.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-UJNFP4egmKJTQ3V3MKOq+7vIUKO7/Fko5G6yUsOW2Rm0VNBvAjgO6VY6EnK3DTqEKN6ugVXDEPw37NQSTGLZvw=="], "vuetify": ["vuetify@3.7.16", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=1.0.0", "vue": "^3.3.0", "webpack-plugin-vuetify": ">=2.0.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-Few/cBtgJYgdkzi0LWmVy67G5uc2+q7oWcadbcTUPAtEtGYNh2AM28h01Fk+ScJgfxkA077//ZDff1rh3jYG/w=="],
"which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], "which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],

View File

@ -2,7 +2,10 @@ package web
import ( import (
"embed" "embed"
"io/fs"
) )
//go:embed all:dist //go:embed all:dist
var DistDir embed.FS var distDir embed.FS
var Dist, _ = fs.Sub(distDir, "dist")

View File

@ -4,7 +4,6 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="mobile-web-app-capable" content="yes">
<title>Vite App</title> <title>Vite App</title>
</head> </head>
<body> <body>

View File

@ -20,7 +20,7 @@
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-draggable-next": "^2.2.1", "vue-draggable-next": "^2.2.1",
"vue-router": "^4.5.0", "vue-router": "^4.5.0",
"vuetify": "^3.8.2" "vuetify": "^3.7.16"
}, },
"devDependencies": { "devDependencies": {
"@mdi/font": "^7.4.47", "@mdi/font": "^7.4.47",

View File

@ -1,38 +0,0 @@
<script setup lang="ts">
import type { DynamicMenuElement } from '@/types.ts'
const componentProps = defineProps<{
menu: DynamicMenuElement[]
itemId?: number
isHovering?: boolean | null
}>()
</script>
<template>
<v-menu v-bind="componentProps" width="200">
<template v-slot:activator="{ props }">
<v-btn icon rounded :variant="componentProps.isHovering ? 'tonal' : 'text'" v-bind="props">
<v-icon icon="mdi-dots-horizontal"></v-icon>
</v-btn>
</template>
<v-list density="compact" class="mt-1">
<template v-for="el in componentProps.menu" :key="el.id">
<v-divider class="my-1" v-if="el.type === 'divider'"></v-divider>
<v-list-item
:disabled="el.disabled"
v-else
:color="el.color"
@click="el.click && el.click(componentProps.itemId)"
>
<v-list-item-title>
<v-icon class="mr-2" :color="el.color" :icon="el.icon"></v-icon>
<span>{{ el.title }}</span>
</v-list-item-title>
</v-list-item>
</template>
</v-list>
</v-menu>
</template>
<style scoped lang="scss"></style>

View File

@ -1,12 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{ text: string }>()
const hasDescription = computed(() => !!props.text)
</script>
<template>
<div>{{ hasDescription }} - {{ props.text }}</div>
</template>
<style scoped lang="scss"></style>

View File

@ -1,30 +0,0 @@
<script setup lang="ts">
const value = defineModel('value')
const props = defineProps<{
label?: string
required?: boolean
description?: string
width?: '0' | '25' | '50' | '100'
}>()
</script>
<template>
<div>
<div v-if="props.label" class="d-flex align-end ga-2">
<div>
<span>{{ props.label }}</span>
<span v-if="props.required" class="text-error">*</span>
</div>
<span v-if="props.description" class="text-caption">{{ props.description }}</span>
</div>
<v-text-field
v-model:model-value="value"
:class="`w-${props.width ?? 100}`"
hide-details
:placeholder="props.label"
></v-text-field>
</div>
</template>
<style scoped lang="scss"></style>

View File

@ -1,37 +0,0 @@
<script setup lang="ts">
const value = defineModel('value')
const props = withDefaults(
defineProps<{
label?: string
required?: boolean
description?: string
width?: '0' | '25' | '50' | '100'
rows?: number
}>(),
{
rows: 5,
},
)
</script>
<template>
<div>
<div v-if="props.label" class="d-flex align-end ga-2">
<div>
<span>{{ props.label }}</span>
<span v-if="props.required" class="text-error">*</span>
</div>
<span v-if="props.description" class="text-caption">{{ props.description }}</span>
</div>
<v-textarea
v-model:model-value="value"
:class="`w-${props.width ?? 100}`"
hide-details
:rows="props.rows"
:placeholder="props.label"
></v-textarea>
</div>
</template>
<style scoped lang="scss"></style>

View File

@ -25,13 +25,6 @@ const disabled = computed(() => !issue.value.name)
const addIssue = async () => { const addIssue = async () => {
const result = await issuesStore.create(issue.value) const result = await issuesStore.create(issue.value)
showForm.value = !result showForm.value = !result
issue.value = {
name: undefined,
description: undefined,
status_id: props.status.id,
project_id: 1,
position: 0,
}
} }
function onClickOutside() { function onClickOutside() {
@ -42,25 +35,15 @@ function onClickOutside() {
<template> <template>
<v-list-item rounded :border="showForm"> <v-list-item rounded :border="showForm">
<v-list-item-title v-if="showForm" v-click-outside="onClickOutside"> <v-list-item-title v-if="showForm" v-click-outside="onClickOutside">
<v-text-field <v-text-field hide-details placeholder="title" variant="plain" v-model:model-value="issue.name"></v-text-field>
hide-details
placeholder="title"
variant="plain"
@keydown.enter="addIssue"
v-model:model-value="issue.name"
/>
<v-row align="end"> <v-row align="end">
<v-col cols="auto"> <v-col cols="auto">
<v-select hide-details v-model:model-value="icon" width="70" :items="icons" variant="plain"></v-select> <v-select hide-details v-model:model-value="icon" width="70" :items="icons" variant="plain"></v-select>
</v-col> </v-col>
<v-spacer /> <v-spacer></v-spacer>
<v-col cols="auto"> <v-col cols="auto">
<v-btn variant="text" icon rounded class="mr-1" :disabled @click="addIssue"> <v-btn variant="tonal" class="mr-1" :disabled @click="addIssue">ok</v-btn>
<v-icon icon="mdi-check"></v-icon> <v-btn variant="tonal" @click="showForm = false">cancel</v-btn>
</v-btn>
<v-btn variant="text" icon rounded @click="showForm = false">
<v-icon icon="mdi-cancel"></v-icon>
</v-btn>
</v-col> </v-col>
</v-row> </v-row>
</v-list-item-title> </v-list-item-title>

View File

@ -1,13 +0,0 @@
<script setup lang="ts"></script>
<template>
<div class="d-flex flex-column ga-2">
<v-textarea rows="3"></v-textarea>
<div>
<v-btn>add</v-btn>
<v-btn variant="text">cancel</v-btn>
</div>
</div>
</template>
<style scoped lang="scss"></style>

View File

@ -1,41 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
import IssueItemAddComment from '@/components/IssueItemAddComment.vue'
import IssueItemCommentsList from '@/components/IssueItemCommentsList.vue'
import IssueItemHistory from '@/components/IssueItemHistory.vue'
const tab = ref('one')
</script>
<template>
<v-sheet>
<v-tabs density="compact" class="border rounded" v-model="tab">
<v-tab color="blue" slim value="one">Comments</v-tab>
<v-tab color="blue" slim value="two">History</v-tab>
</v-tabs>
<div>
<v-tabs-window v-model="tab">
<v-tabs-window-item value="one">
<div class="my-2 d-flex flex-column ga-2">
<issue-item-add-comment />
<issue-item-comments-list />
</div>
</v-tabs-window-item>
<v-tabs-window-item value="two">
<div class="my-2">
<issue-item-history />
</div>
</v-tabs-window-item>
</v-tabs-window>
</div>
</v-sheet>
</template>
<style scoped lang="scss">
.v-tabs {
.v-tabs-slider-wrapper {
transition: none;
}
}
</style>

View File

@ -1,9 +0,0 @@
<script setup lang="ts"></script>
<template>
<div>
<div v-for="el in 3" :key="el">comment {{ el }}</div>
</div>
</template>
<style scoped lang="scss"></style>

View File

@ -1,62 +0,0 @@
<script setup lang="ts">
// const isActive = defineModel<boolean>('isActive', { required: false })
import type { Issue } from '@/stores/issues.ts'
import { computed } from 'vue'
import StatusMenu from '@/components/StatusMenu.vue'
import EditableText from '@/components/EditableText.vue'
import IssueItemAddComment from '@/components/IssueItemAddComment.vue'
import IssueItemAddons from '@/components/IssueItemAddons.vue'
const props = defineProps<{ selectedIssue: Issue }>()
const emit = defineEmits<{
(e: 'onCancel'): void
}>()
const created = computed(() => {
return props.selectedIssue.created ? new Date(props.selectedIssue.created).toLocaleString('ru-RU') : undefined
})
const issueId = computed(() => {
return props.selectedIssue.project.key + '-' + props.selectedIssue.id
})
</script>
<template>
<v-card :width="800">
<template #append>
<div class="d-flex ga-2">
<v-btn class="border rounded" @click="emit('onCancel')" icon="mdi-dots-horizontal"></v-btn>
<v-btn class="border rounded" @click="emit('onCancel')" icon="mdi-close"></v-btn>
</div>
</template>
<template #title>
<div class="text-body-1">{{ issueId }}</div>
</template>
<v-card-text>
<v-row>
<v-col class="d-flex flex-column ga-4">
<div class="text-h5">{{ props.selectedIssue.name }}</div>
<div>
<div>description</div>
<editable-text :text="props.selectedIssue.description" />
</div>
<issue-item-addons />
</v-col>
<v-col class="d-flex flex-column ga-4 align-start">
<status-menu class="" :status="props.selectedIssue.status"></status-menu>
<v-sheet border class="pa-4 w-100">
<div>
assignee:x
<v-avatar size="x-small" text="M" color="success"></v-avatar>
</div>
</v-sheet>
<div class="text-caption">created: {{ created }}</div>
</v-col>
</v-row>
</v-card-text>
</v-card>
</template>
<style scoped lang="scss"></style>

View File

@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<div>history</div>
</template>
<style scoped lang="scss"></style>

View File

@ -1,77 +1,56 @@
<script setup lang="ts"> <script setup lang="ts">
import { type Issue, useIssuesStore } from '@/stores/issues.ts' import { type Issue, useIssuesStore } from '@/stores/issues.ts'
import { useStatusesStore } from '@/stores/statuses.ts'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const props = defineProps<{ issue: Issue }>() const props = defineProps<{ issue: Issue }>()
const router = useRouter()
const issuesStore = useIssuesStore() const issuesStore = useIssuesStore()
const statusesStore = useStatusesStore()
const projectKey = computed(() => props.issue.project.key) const menu = [
{
id: 1,
title: 'delete',
icon: 'mdi-trash-can',
},
]
const onDelete = async (id: number) => await issuesStore.remove(id) async function onDelete() {
await issuesStore.remove(props.issue.id)
function onStatusChange(statusId: number) {
console.log('on change status', statusId)
}
function onClick(id: number) {
console.log('on click', id)
router.push({ name: 'issues', query: { selectedIssue: `${projectKey.value}-${id}` } })
} }
</script> </script>
<template> <template>
<v-list-item rounded border class="mb-1 pl-2" @click="onClick(props.issue.id)"> <v-list-item rounded border class="mb-1">
<template v-slot:prepend> <template v-slot:prepend>
<v-icon class="handle cursor-move" icon="mdi-drag" /> <v-icon class="handle move" icon="mdi-drag" />
</template> </template>
<v-list-item-subtitle> <v-list-item-subtitle>
<div class="text-caption">{{ props.issue.description }}</div> <div>{{ props.issue.description }}</div>
</v-list-item-subtitle> </v-list-item-subtitle>
<v-list-item-title> <v-list-item-title>
<div class="d-flex ga-2 align-center"> <div class="d-flex ga-2 align-center">
<!-- <v-icon size="small" icon="mdi-bug" color="primary"></v-icon>--> <!-- <v-icon size="small" icon="mdi-bug" color="primary"></v-icon>-->
<!-- <v-chip :text="issue.id" variant="tonal" size="small" label />--> <v-chip :text="issue.id" variant="tonal" size="small" label />
<div class="text-body-2 text-wrap">{{ props.issue.name }}</div> <div class="text-body-1">{{ props.issue.name }}</div>
</div> </div>
</v-list-item-title> </v-list-item-title>
<template #append> <template #append>
<v-list-item-action class="ga-2"> <v-list-item-action class="ga-2">
<v-avatar color="blue" size="28" /> <v-avatar color="primary" size="28" />
<v-menu> <v-menu>
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-btn icon rounded variant="text" v-bind="props"> <v-btn size="small" icon="mdi-menu" variant="plain" v-bind="props" />
<v-icon size="small">mdi-menu</v-icon>
</v-btn>
</template> </template>
<v-list density="compact" class="mt-1"> <v-list density="compact" class="mt-1">
<v-list-item link> <v-list-item v-for="item in menu" :key="item.id" :value="item.title" @click="onDelete">
<v-list-item-title> <v-list-item-title>
<div class="d-flex ga-2 align-center"> <div class="d-flex ga-2 align-center">
<div>Change status</div> <v-icon size="small" :icon="item.icon"></v-icon>
<v-icon icon="mdi-menu-right"></v-icon> {{ item.title }}
</div> </div>
</v-list-item-title> </v-list-item-title>
<v-menu v-if="statusesStore.statuses" :open-on-focus="false" activator="parent" open-on-hover submenu>
<v-list>
<v-list-item
v-for="status in statusesStore.statuses"
:key="status.id"
link
@click="onStatusChange(status.id)"
:title="status.name"
>
</v-list-item>
</v-list>
</v-menu>
</v-list-item> </v-list-item>
<v-divider class="my-1"></v-divider>
<v-list-item link @click="onDelete(props.issue.id)" title="Delete"></v-list-item>
</v-list> </v-list>
</v-menu> </v-menu>
</v-list-item-action> </v-list-item-action>
@ -79,4 +58,8 @@ function onClick(id: number) {
</v-list-item> </v-list-item>
</template> </template>
<style scoped lang="scss"></style> <style scoped lang="scss">
.move {
cursor: move;
}
</style>

View File

@ -12,7 +12,7 @@ const issuesStore = useIssuesStore()
const selectedId = ref<number>() const selectedId = ref<number>()
const height = 600 const height = 400
async function onChange(val: { async function onChange(val: {
added: { element: Issue; newIndex: number } added: { element: Issue; newIndex: number }
@ -37,20 +37,20 @@ function onChoose() {
</script> </script>
<template> <template>
<v-sheet border width="18rem"> <v-sheet rounded>
<v-toolbar rounded color="transparent" density="compact"> <v-toolbar density="compact">
<v-toolbar-title> <v-toolbar-title>
<div class="d-flex align-center ga-2"> <div class="d-flex align-center ga-2">
<div class="text-uppercase text-body-2">{{ props.status.name }}</div> <div class="text-uppercase text-body-2">{{ props.status.name }}</div>
<v-chip variant="tonal" :text="props.issues.length" class="mr-2" /> <v-chip size="small" variant="tonal" :text="props.issues.length" class="mr-2" />
</div> </div>
</v-toolbar-title> </v-toolbar-title>
<template #append> <template #append>
<v-menu> <v-menu>
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-btn icon rounded density="compact" variant="text" v-bind="props"> <v-chip label variant="text" v-bind="props">
<v-icon icon="mdi-dots-horizontal"></v-icon> <v-icon icon="mdi-dots-horizontal"></v-icon>
</v-btn> </v-chip>
</template> </template>
<v-list density="compact" class="mt-1"> <v-list density="compact" class="mt-1">
@ -89,4 +89,12 @@ function onChoose() {
.sortable-drag { .sortable-drag {
background-color: rgb(var(--v-theme-background)); background-color: rgb(var(--v-theme-background));
} }
.animation {
animation: wave 0.5s linear;
}
@keyframes wave {
50% {
background-color: rgba(var(--v-theme-primary), 0.2);
}
}
</style> </style>

View File

@ -1,66 +0,0 @@
<script setup lang="ts">
import FormInput from '@/components/FormInput.vue'
import { ref } from 'vue'
import FormTextarea from '@/components/FormTextarea.vue'
import { useProjectsStore } from '@/stores/projects.ts'
const dialog = defineModel('dialog')
const projectsStore = useProjectsStore()
const name = ref('')
const description = ref('')
const key = ref('')
async function onSubmit() {
console.log(name.value)
await projectsStore.create({
name: name.value,
description: description.value,
key: key.value,
})
}
</script>
<template>
<v-sheet border class="pa-4">
<div class="d-flex align-center ga-4 mb-4">
<v-btn variant="text" @click="dialog = false" icon="mdi-close"></v-btn>
</div>
<div class="mb-4">
<div class="text-h6">Project type</div>
<v-list lines="two">
<v-list-item border rounded @click="console.log('Kanban')">
<div class="d-flex ga-2">
<div class="d-flex align-center">
<v-icon size="64" color="primary">mdi-paper-roll-outline</v-icon>
</div>
<div>
<div class="text-h6">Kanban</div>
<div class="text-body-2">
Kanban is all about helping teams visualize their work, limit work currently in progress, and maximize
efficiency.
</div>
</div>
</div>
</v-list-item>
</v-list>
</div>
<div class="mb-4">
<div class="text-h6">Project details</div>
<div class="text-caption">
Required fields are marked with an asterisk <span class="text-error text-body-2">*</span>
</div>
</div>
<v-form class="d-flex flex-column ga-4" @submit.prevent="onSubmit">
<form-input v-model:value="name" label="Name" :required="true" />
<form-input v-model:value="key" label="Key" :required="true" description="prefix for your project" width="50" />
<form-textarea v-model:value="description" label="Description" :rows="2" />
<v-btn type="submit" class="align-self-start" density="comfortable" color="primary">create</v-btn>
</v-form>
</v-sheet>
</template>
<style scoped lang="scss"></style>

View File

@ -1,40 +0,0 @@
<script setup lang="ts">
import { type Status, useStatusesStore } from '@/stores/statuses.ts'
import { ref, watchEffect } from 'vue'
const statusesStore = useStatusesStore()
const props = defineProps<{ status?: Status }>()
const selected = ref<Status>()
watchEffect(() => props.status && setSelected(props.status))
function setSelected(status: Status) {
selected.value = status
}
function onSelect(status: Status) {
console.log(status)
selected.value = status
}
</script>
<template>
<v-menu width="200">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" color="blue" append-icon="mdi-menu-down">{{ selected?.name }}</v-btn>
</template>
<v-list density="compact" class="mt-1">
<template v-for="el in statusesStore.statuses" :key="el.id">
<v-list-item @click="onSelect(el)">
<v-list-item-title>
<span>{{ el.name }}</span>
</v-list-item-title>
</v-list-item>
</template>
</v-list>
</v-menu>
</template>
<style scoped lang="scss"></style>

View File

@ -1,39 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRoute, useRouter } from 'vue-router' import { ref } from 'vue'
const route = useRoute() const component = ref<string>('issues')
const menu = [
{ id: 1, title: 'Projects' }, const menu = ['stat', 'issues', 'projects', 'tests']
// { id: 2, title: 'issues' },
// { id: 3, title: 'stat' }, function setComponent(value: string) {
// { id: 4, title: 'test' }, component.value = value
] }
</script> </script>
<template> <template>
<v-layout> <v-layout>
<v-app-bar density="comfortable" elevation="1"> <v-app-bar density="compact" elevation="1">
<template v-slot:prepend> <v-app-bar-title>Application - component: {{ component }}</v-app-bar-title>
<v-menu> <v-btn v-for="(el, index) in menu" :key="index" @click="setComponent(el)">{{ el }}</v-btn>
<template v-slot:activator="{ props }">
<v-app-bar-nav-icon variant="text" v-bind="props"></v-app-bar-nav-icon>
</template>
<v-list class="mt-1" width="200">
<v-list-item v-for="(item, index) in menu" :key="index" :to="`/${item.title}`">
<v-list-item-title class="d-flex align-center ga-4">
<v-avatar border size="24" rounded color="primary"> </v-avatar>
<div>{{ item.title }}</div>
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</template>
<v-app-bar-title>Application</v-app-bar-title>
</v-app-bar> </v-app-bar>
<v-main> <v-main>
<v-container :fluid="true"> <v-container :fluid="true">
<slot></slot> <slot name="default" :component="component"></slot>
</v-container> </v-container>
</v-main> </v-main>
</v-layout> </v-layout>

View File

@ -30,12 +30,12 @@ const vuetify = createVuetify({
}, },
VToolbar: { density: 'compact' }, VToolbar: { density: 'compact' },
VRow: { dense: true }, VRow: { dense: true },
VSheet: { rounded: true }, VSheet: { border: true, rounded: true },
VList: { density: 'compact' }, VList: { density: 'compact' },
// VCard: { variant: 'outlined' }, VCard: { variant: 'outlined' },
VChip: { density: 'compact', variant: 'outlined', label: true }, VChip: { density: 'compact', variant: 'outlined', label: true },
VNumberInput: { hideDetails: true, variant: 'outlined', density: 'compact' }, VNumberInput: { hideDetails: true, variant: 'outlined', density: 'compact' },
VBtn: { variant: 'outlined', density: 'compact' }, VBtn: { variant: 'tonal', density: 'compact' },
VTextField: { variant: 'outlined', density: 'compact', hideDetails: false }, VTextField: { variant: 'outlined', density: 'compact', hideDetails: false },
VSelect: { variant: 'outlined', density: 'compact', hideDetails: false }, VSelect: { variant: 'outlined', density: 'compact', hideDetails: false },
VTextarea: { hideDetails: true, variant: 'outlined', density: 'compact' }, VTextarea: { hideDetails: true, variant: 'outlined', density: 'compact' },

View File

@ -1,5 +1,6 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue' import HomeView from '../views/HomeView.vue'
import TestRootView from '@/views/TestRootView.vue'
declare module 'vue-router' { declare module 'vue-router' {
interface RouteMeta { interface RouteMeta {
@ -11,25 +12,17 @@ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
routes: [ routes: [
{ {
path: '', path: '/',
redirect: '/projects',
},
{
path: '/projects',
component: HomeView, component: HomeView,
children: [ children: [
{ path: '', component: () => import('@/views/ProjectRootView.vue') },
{ {
path: ':key/issues', path: '/',
name: 'issues', name: 'items',
component: () => import('@/views/IssuesRootView.vue'), component: TestRootView,
props: (route) => ({ selectedIssue: route.query.selectedIssue }),
}, },
], ],
}, },
], ],
}) })
router.beforeResolve((to, from) => {})
export default router export default router

View File

@ -0,0 +1,12 @@
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
export const useComponentsStore = defineStore('components', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

View File

@ -25,7 +25,6 @@ export interface PositionsDto {
export const useIssuesStore = defineStore('issues', () => { export const useIssuesStore = defineStore('issues', () => {
const issues = ref<Issue[]>([]) const issues = ref<Issue[]>([])
const selectedIssue = ref<Issue>()
const issuesObj = ref<Map<number, Issue[]>>(new Map()) const issuesObj = ref<Map<number, Issue[]>>(new Map())
const { GET, POST, DELETE } = useFetch('/api/issues') const { GET, POST, DELETE } = useFetch('/api/issues')
@ -70,12 +69,5 @@ export const useIssuesStore = defineStore('issues', () => {
} }
} }
async function findById(id: number) { return { issues, issuesObj, findAll, create, genIssuesObj, updatePositions, remove }
const response = await GET<Issue>({ param: id })
if (response?.data) {
selectedIssue.value = response.data
}
}
return { issues, issuesObj, selectedIssue, findAll, findById, create, genIssuesObj, updatePositions, remove }
}) })

View File

@ -9,7 +9,7 @@ export interface Project {
key: string key: string
} }
export type CreateProjectDto = Partial<Omit<Project, 'id'>> export type CreateProjectDto = Partial<Pick<Project, 'name' | 'description'>>
export type UpdateProjectDto = Partial<Pick<Project, 'id' | 'name' | 'description'>> export type UpdateProjectDto = Partial<Pick<Project, 'id' | 'name' | 'description'>>
export const useProjectsStore = defineStore('projects', () => { export const useProjectsStore = defineStore('projects', () => {
@ -27,11 +27,8 @@ export const useProjectsStore = defineStore('projects', () => {
if (response?.data) project.value = response.data if (response?.data) project.value = response.data
} }
async function create({ name, description, key }: CreateProjectDto) { async function create({ name, description }: CreateProjectDto) {
if (!name || !key) { const response = await POST<Project>({ body: { name, description } })
throw new Error('Project name and key is required')
}
const response = await POST<Project>({ body: { name, description, key } })
if (response?.data) projects.value.push(response.data) if (response?.data) projects.value.push(response.data)
} }

View File

@ -1,12 +0,0 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useServerStore = defineStore('server', () => {
const isOnline = ref(false)
function checkOnline() {
console.log('check online')
}
return { isOnline, checkOnline }
})

View File

@ -1,28 +0,0 @@
export interface DynamicMenuElement {
id: number
title?: string
icon?: string
color?: string
type?: 'divider'
disabled?: boolean
click?: (val?: number) => void
}
export interface IssueMenu {
id: number
name: string
click?: (id: number) => void
children?: { id: number; name: string; click?: (id: number) => void }[]
}
export interface TableHeader {
readonly key?: (string & {}) | 'data-table-group' | 'data-table-select' | 'data-table-expand' | undefined
readonly title?: string | undefined
readonly fixed?: boolean | undefined
readonly align?: 'start' | 'end' | 'center' | undefined
readonly width?: string | number | undefined
readonly minWidth?: string | number | undefined
readonly maxWidth?: string | number | undefined
readonly nowrap?: boolean | undefined
readonly sortable?: boolean | undefined
}

View File

@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<div>Foo Bar</div>
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,20 @@
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function click() {
count.value = count.value + 1
}
return {
count,
click,
}
},
template: `
<div class="d-flex ga-2 pa-2">
<div class="hello">COUNT: {{count}}</div>
<v-btn @click="click">click</v-btn>
</div>
`,
}

View File

@ -1,6 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, watchEffect } from 'vue' import { type Component, computed, onMounted, watchEffect } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import TestRootView from '@/views/TestRootView.vue'
import StatRootView from '@/views/StatRootView.vue'
import HelloWorld from '@/views/HelloWorld.js'
import DefaultLayout from '@/layouts/DefaultLayout.vue'
import IssuesRootView from '@/views/IssuesRootView.vue'
import ProjectRootView from '@/views/ProjectRootView.vue'
import { useIssuesStore } from '@/stores/issues.ts' import { useIssuesStore } from '@/stores/issues.ts'
import { useStatusesStore } from '@/stores/statuses.ts' import { useStatusesStore } from '@/stores/statuses.ts'
import { useProjectsStore } from '@/stores/projects.ts' import { useProjectsStore } from '@/stores/projects.ts'
@ -16,17 +22,34 @@ const layout = computed(() => {
return layout ? `${layout}Layout` : 'DefaultLayout' return layout ? `${layout}Layout` : 'DefaultLayout'
}) })
const components: { [key: string]: Component } = {
stat: StatRootView,
projects: ProjectRootView,
issues: IssuesRootView,
tests: TestRootView,
}
watchEffect(async () => await projectsStore.findAll()) watchEffect(async () => await projectsStore.findAll())
watchEffect(async () => await statusesStore.findAll()) watchEffect(async () => await statusesStore.findAll())
watchEffect(async () => await issuesStore.findAll()) watchEffect(async () => await issuesStore.findAll())
onMounted(async () => {
// await projectsStore.findById(122)
})
</script> </script>
<template> <template>
<component :is="layout"> <default-layout>
<router-view v-slot="{ Component, route }"> <template v-slot:default="{ component }">
<div :key="route.name"> <component :is="components[component]" />
<component :is="Component" /> <hello-world />
</div> </template>
</router-view> </default-layout>
</component> <!-- <component :is="layout">-->
<!-- <router-view v-slot="{ Component, route }">-->
<!-- <div :key="route.name">-->
<!-- <component :is="Component" />-->
<!-- </div>-->
<!-- </router-view>-->
<!-- </component>-->
</template> </template>

View File

@ -1,125 +1,43 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watchEffect } from 'vue' import { computed, watchEffect } from 'vue'
import { useIssuesStore } from '@/stores/issues.ts' import { useIssuesStore } from '@/stores/issues.ts'
import IssuesByStatusView from '@/components/IssuesByStatusList.vue' import IssuesByStatusView from '@/components/IssuesByStatusList.vue'
import { useStatusesStore } from '@/stores/statuses.ts' import { useStatusesStore } from '@/stores/statuses.ts'
import { useProjectsStore } from '@/stores/projects.ts'
import type { DynamicMenuElement } from '@/types.ts'
import DynamicMenu from '@/components/DynamicMenu.vue'
import { useRouter } from 'vue-router'
import IssueItemDetails from '@/components/IssueItemDetails.vue'
const issuesStore = useIssuesStore() const issuesStore = useIssuesStore()
const statusesStore = useStatusesStore() const statusesStore = useStatusesStore()
const projectsStore = useProjectsStore()
const router = useRouter()
const statuses = computed(() => statusesStore.statuses) const statuses = computed(() => statusesStore.statuses)
const props = defineProps<{ selectedIssue?: string }>() const cols = computed(() => (statuses.value.length > 0 ? 12 / statuses.value.length : 12))
function getIssues(statusId: number) { function getIssues(statusId: number) {
return issuesStore.issuesObj.get(statusId) ?? [] return issuesStore.issuesObj.get(statusId) ?? []
} }
const dialog = ref(false) function showLog() {
console.log('issue obj', issuesStore.issuesObj)
const items = [
{
id: 1,
title: 'bla',
},
]
const projectMenu: DynamicMenuElement[] = [
{
id: 1,
title: 'Add people',
icon: 'mdi-account',
disabled: true,
click: projectSettings,
},
{
id: 2,
title: 'Settings',
icon: 'mdi-cog',
click: projectSettings,
},
{ id: 3, type: 'divider' },
{
id: 4,
title: 'Delete',
icon: 'mdi-trash-can',
color: 'error',
click: deleteProject,
},
]
async function findById(val?: string) {
if (!val) return
const id = +val.split('-')[1]
await issuesStore.findById(id)
if (issuesStore.selectedIssue) dialog.value = true
} }
function deleteProject() { watchEffect(() => {
console.log('blat') issuesStore.genIssuesObj(statuses.value)
} })
function projectSettings() {
console.log('blat2')
}
function onCancel() {
router.push({ name: 'issues' })
dialog.value = false
}
watchEffect(async () => await findById(props.selectedIssue))
watchEffect(() => issuesStore.genIssuesObj(statuses.value))
</script> </script>
<template> <template>
<div> <div>
<v-row> <v-row>
<v-col cols="auto"> <v-col cols="3" align="center">
<router-link to="/projects" class="text-black text-decoration-underline text-body-1 cursor-pointer"> <v-text-field hide-details label="search" prepend-inner-icon="mdi-magnify" />
Projects </v-col>
</router-link> <v-col>
<v-btn icon="mdi-circle" size="default" density="comfortable" @click="showLog"></v-btn>
</v-col> </v-col>
</v-row> </v-row>
<v-row> <v-row v-if="statuses.length">
<v-col cols="auto"> <v-col :cols="cols" v-for="status in statuses" :key="status.id">
<v-hover v-slot="{ isHovering, props }">
<div v-bind="props" class="d-flex ga-2 align-center mb-4">
<v-avatar size="28" color="blue" border rounded></v-avatar>
<div class="text-h5 text-capitalize">{{ projectsStore.projects[0]?.name }}</div>
<dynamic-menu v-bind="props" :isHovering="isHovering" :menu="projectMenu"></dynamic-menu>
</div>
</v-hover>
</v-col>
</v-row>
<div class="d-flex ga-2 mb-4">
<v-text-field max-width="15rem" hide-details label="search" prepend-inner-icon="mdi-magnify" />
<v-select max-width="10rem" hide-details :items></v-select>
<v-avatar icon="mdi-account-outline" color="grey-lighten-4"></v-avatar>
</div>
<div v-if="statuses.length" class="d-flex ga-2 my-2 overflow-x-auto">
<div v-for="status in statuses" :key="status.id">
<issues-by-status-view :status="status" :issues="getIssues(status.id)" /> <issues-by-status-view :status="status" :issues="getIssues(status.id)" />
</div> </v-col>
<v-btn icon rounded size="small" density="default"> </v-row>
<v-icon size="default">mdi-plus</v-icon>
</v-btn>
</div>
<v-dialog v-model="dialog" width="auto" @after-leave="onCancel">
<issue-item-details
v-if="issuesStore.selectedIssue"
@on-cancel="onCancel"
:selected-issue="issuesStore.selectedIssue"
/>
</v-dialog>
</div> </div>
</template> </template>

View File

@ -1,92 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue' import { onMounted } from 'vue'
import { useProjectsStore } from '@/stores/projects.ts' import { useProjectsStore } from '@/stores/projects.ts'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import DynamicMenu from '@/components/DynamicMenu.vue'
import type { DynamicMenuElement, TableHeader } from '@/types.ts'
import ProjectCreateFlow from '@/components/ProjectCreateFlow.vue'
const projectsStore = useProjectsStore() const projectsStore = useProjectsStore()
const { projects } = storeToRefs(projectsStore) const { projects } = storeToRefs(projectsStore)
const headers: TableHeader[] = [
{ title: 'Name', key: 'name', width: 400 },
{ title: 'Key', key: 'key' },
{ title: 'Actions', key: 'actions', width: 100, align: 'end', sortable: false },
]
const menu: DynamicMenuElement[] = [
{
id: 1,
title: 'settings',
icon: 'mdi-cog',
click: projectSettings,
},
{
id: 2,
type: 'divider',
},
{
id: 3,
title: 'delete',
icon: 'mdi-trash-can',
color: 'error',
click: deleteProject,
},
]
const dialog = ref(false)
function projectSettings(id?: number) {
console.log('d', id)
}
async function deleteProject(id?: number) {
console.log('p', id)
if (id) await projectsStore.remove(id)
}
onMounted(async () => {}) onMounted(async () => {})
</script> </script>
<template> <template>
<div> <div>
<div class="d-flex align-center justify-space-between mb-4"> <div v-for="project in projects" :key="project.id">
<div class="text-h5 font-weight-bold">Projects</div> {{ project.name }} - {{ project.description }} - {{ project.key }}
<v-dialog transition="dialog-bottom-transition" max-width="500" v-model="dialog">
<template v-slot:activator="{ props: activatorProps }">
<v-btn v-bind="activatorProps" density="comfortable">
<span class="text-body-2">Create project</span>
</v-btn>
</template>
<project-create-flow v-model:dialog="dialog"></project-create-flow>
</v-dialog>
</div> </div>
<div class="d-flex ga-4 w-100 w-md-25">
<v-text-field :disabled="true" prepend-inner-icon="mdi-magnify" placeholder="search projects"></v-text-field>
</div>
<v-data-table :headers hover hide-default-footer class="border rounded" density="compact" :items="projects">
<template #[`item.name`]="{ item }">
<div class="d-flex ga-2">
<v-avatar color="blue" border rounded size="25"></v-avatar>
<v-hover v-slot="{ isHovering, props }">
<router-link
v-bind="props"
class="text-primary text-body-1 text-blue"
:class="{ 'text-decoration-none': !isHovering }"
:to="`/projects/${item.key}/issues`"
>
{{ item.name }}
</router-link>
</v-hover>
</div>
</template>
<template #[`item.actions`]="{ item }">
<span>{{ item.id }}</span>
<dynamic-menu :menu :item-id="item.id"></dynamic-menu>
</template>
</v-data-table>
</div> </div>
</template> </template>

View File

@ -6,8 +6,6 @@ import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
server: { server: {
host: '0.0.0.0',
port: 4000,
proxy: { proxy: {
'/api': 'http://localhost:3000', '/api': 'http://localhost:3000',
}, },