diff --git a/go.mod b/go.mod index 2cf49c1..b0837c3 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module donat-widget go 1.23.0 require ( + github.com/georgysavva/scany/v2 v2.1.3 github.com/go-playground/validator/v10 v10.22.0 github.com/jackc/pgx/v5 v5.6.0 - github.com/jcobhams/echovalidate/v2 v2.0.3 github.com/labstack/echo/v4 v4.12.0 github.com/swaggo/echo-swagger v1.4.1 github.com/swaggo/swag v1.16.3 @@ -29,6 +29,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/linxGnu/goseaweedfs v0.1.6 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/go.sum b/go.sum index 8f0fa28..0e85cec 100644 --- a/go.sum +++ b/go.sum @@ -1,50 +1,113 @@ +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB05Ql//KWfeTFs= +github.com/cockroachdb/cockroach-go/v2 v2.2.0/go.mod h1:u3MiKYGupPPjkn3ozknpMUpxPaNLTFWAya419/zv6eI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/georgysavva/scany/v2 v2.1.3 h1:Zd4zm/ej79Den7tBSU2kaTDPAH64suq4qlQdhiBeGds= +github.com/georgysavva/scany/v2 v2.1.3/go.mod h1:fqp9yHZzM/PFVa3/rYEC57VmDx+KDch0LoqrJzkvtos= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +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.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jcobhams/echovalidate/v2 v2.0.3/go.mod h1:I9VVW+j88qwoJC0ugb9B2Olvc2w+RN66oDyrVaT+Gns= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +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.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= +github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/linxGnu/goseaweedfs v0.1.6 h1:qcCz0aBJlXVCVX+NiEnU+uGHMBvJR33U3PquzlEpcs8= +github.com/linxGnu/goseaweedfs v0.1.6/go.mod h1:cv43dFeG4S6HnLa7U9aYw+wD/uZEi66UmwKdforYuWo= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk= github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc= +github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= +github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= 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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +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-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/infrastructure/pg/connection.go b/infrastructure/pg/connection.go index 57a25aa..397efac 100644 --- a/infrastructure/pg/connection.go +++ b/infrastructure/pg/connection.go @@ -2,7 +2,8 @@ package pg import ( "context" - "fmt" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgxpool" "sync" ) @@ -33,40 +34,39 @@ func NewPgPool( } func (pg *Postgres) CreateTable(ctx context.Context) error { - result, err := pg.db.Exec(ctx, createTableQuery) + _, err := pg.db.Exec(ctx, createTableQuery) if err != nil { return err } - fmt.Println(result) + + _, err = pg.db.Exec(ctx, onUpdateTableQuery) + if err != nil { + return err + } + return nil } func (pg *Postgres) DropTable(ctx context.Context) error { - result, err := pg.db.Exec(ctx, dropTableQuery) + _, err := pg.db.Exec(ctx, dropTableQuery) if err != nil { return err } - fmt.Println(result) return nil } -func (pg *Postgres) Exec(ctx context.Context, query string, args ...interface{}) (interface{}, error) { +func (pg *Postgres) Exec(ctx context.Context, query string, args ...interface{}) (pgconn.CommandTag, error) { result, err := pg.db.Exec(ctx, query, args...) if err != nil { - return nil, err + return result, err } return result, nil } -func (pg *Postgres) Query(ctx context.Context, query string, args ...interface{}) (interface{}, error) { +func (pg *Postgres) Query(ctx context.Context, query string, args ...interface{}) (pgx.Rows, error) { result, err := pg.db.Query(ctx, query, args...) if err != nil { return nil, err } return result, nil } - -func (pg *Postgres) QueryRow(ctx context.Context, query string, args ...interface{}) interface{} { - result := pg.db.QueryRow(ctx, query, args...) - return result -} diff --git a/infrastructure/pg/models.go b/infrastructure/pg/models.go index b9af5e3..0c2dc9d 100644 --- a/infrastructure/pg/models.go +++ b/infrastructure/pg/models.go @@ -3,21 +3,29 @@ package pg var createTableQuery = ` CREATE TABLE IF NOT EXISTS widgets ( id SERIAL PRIMARY KEY, - name TEXT NOT NULL, - owner_id INTEGER NOT NULL, - text TEXT DEFAULT 'Текст доаната', - video TEXT DEFAULT '', - image TEXT DEFAULT '', - audio TEXT DEFAULT '', + streamer_id INTEGER NOT NULL, + template_id INTEGER NOT NULL, + background_url TEXT DEFAULT '', + image_url TEXT DEFAULT '', + audio_url TEXT DEFAULT '', duration INTEGER DEFAULT 30, - display BOOLEAN DEFAULT TRUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS donats ( + id SERIAL PRIMARY KEY, + widget_id INTEGER NOT NULL, + text TEXT DEFAULT '', + amount TEXT DEFAULT '', + donat_user TEXT DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP ); ` var onUpdateTableQuery = ` -CREATE OR REPLACE FUNCTION update_updated_at() + CREATE OR REPLACE FUNCTION update_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); @@ -26,11 +34,17 @@ CREATE OR REPLACE FUNCTION update_updated_at() $$ LANGUAGE 'plpgsql'; CREATE TRIGGER update_updated_at_trigger - BEFORE UPDATE ON test_users + BEFORE UPDATE ON widgets + FOR EACH ROW + EXECUTE PROCEDURE update_updated_at(); + + CREATE TRIGGER update_updated_at_trigger + BEFORE UPDATE ON donats FOR EACH ROW EXECUTE PROCEDURE update_updated_at(); ` var dropTableQuery = ` DROP TABLE IF EXISTS widgets; + DROP TABLE IF EXISTS donats; ` diff --git a/infrastructure/weed/weed.go b/infrastructure/weed/weed.go new file mode 100644 index 0000000..61de523 --- /dev/null +++ b/infrastructure/weed/weed.go @@ -0,0 +1,98 @@ +package weed + +import ( + "bytes" + "donat-widget/internal/model" + "github.com/linxGnu/goseaweedfs" + "io" + "net/http" + "time" +) + +type Weed struct { + operation *goseaweedfs.Seaweed +} + +func NewWeed( + filer string, + masterUrl string, + +) (*Weed, error) { + filers := []string{filer} + sw, err := goseaweedfs.NewSeaweed( + masterUrl, + filers, + 8096, + &http.Client{Timeout: 5 * time.Minute}, + ) + if err != nil { + return &Weed{}, err + } + return &Weed{sw}, err + +} + +func (weed *Weed) Upload( + file model.UploadFile, + filename string, + size int64, + collection string, +) (model.FileData, error) { + var fileData model.FileData + fileData, err := weed.operation.Upload( + *file, + filename, + size, + collection, + "", + ) + if err != nil { + return fileData, err + } + + return fileData, nil +} + +func (weed *Weed) Download( + FileID model.FileID, +) (model.DownloadFile, error) { + var fileBuffer bytes.Buffer + + _, err := weed.operation.Download( + string(FileID), + nil, + func(r io.Reader) error { + _, err := io.Copy(&fileBuffer, r) + return err + }, + ) + file := fileBuffer.Bytes() + if err != nil { + return file, err + } + + return file, nil +} + +func (weed *Weed) Update( + file model.UploadFile, + fileID model.FileID, + filename string, + size int64, + collection string, +) error { + err := weed.operation.Replace( + string(fileID), + *file, + filename, + size, + collection, + "", + true, + ) + if err != nil { + return err + } + + return nil +} diff --git a/internal/api/http/handlers/widget/content/audio.go b/internal/api/http/handlers/widget/content/audio.go deleted file mode 100644 index 30f612d..0000000 --- a/internal/api/http/handlers/widget/content/audio.go +++ /dev/null @@ -1 +0,0 @@ -package content diff --git a/internal/api/http/handlers/widget/content/image.go b/internal/api/http/handlers/widget/content/image.go deleted file mode 100644 index 30f612d..0000000 --- a/internal/api/http/handlers/widget/content/image.go +++ /dev/null @@ -1 +0,0 @@ -package content diff --git a/internal/api/http/handlers/widget/content/text.go b/internal/api/http/handlers/widget/content/text.go deleted file mode 100644 index 30f612d..0000000 --- a/internal/api/http/handlers/widget/content/text.go +++ /dev/null @@ -1 +0,0 @@ -package content diff --git a/internal/api/http/handlers/widget/content/video.go b/internal/api/http/handlers/widget/content/video.go deleted file mode 100644 index 30f612d..0000000 --- a/internal/api/http/handlers/widget/content/video.go +++ /dev/null @@ -1 +0,0 @@ -package content diff --git a/internal/api/http/handlers/widget/create.go b/internal/api/http/handlers/widget/create.go deleted file mode 100644 index e25a15c..0000000 --- a/internal/api/http/handlers/widget/create.go +++ /dev/null @@ -1,52 +0,0 @@ -package widget - -import ( - "context" - "github.com/labstack/echo/v4" - "log/slog" - "strconv" -) - -type requestModel struct { - UserId int `json:"userId" validate:"required"` -} - -type responseModel struct { - WidgetId int `json:"widgetId"` -} - -type Creator interface { - Create(ctx context.Context, userId int) (int, error) -} - -// Create @Description Create widget -// -// @Tags Widget -// @Accept json -// @Produce json -// @Param RegisterData body requestModel true "Create widget" -// @Success 200 {object} responseModel -// @Router /api/widget/create [post] -func Create(service Creator, log *slog.Logger) echo.HandlerFunc { - return func(request echo.Context) error { - ctx := context.Background() - var widgetData requestModel - if err := request.Bind(&widgetData); err != nil { - slog.Error(err.Error()) - return echo.NewHTTPError(400, err.Error()) - } - - err := request.Validate(&widgetData) - if err != nil { - slog.Error(err.Error()) - return echo.NewHTTPError(400, err.Error()) - } - - id, err := service.Create(ctx, widgetData.UserId) - if err != nil { - slog.Error(err.Error()) - return request.JSON(422, err) - } - return request.String(200, strconv.FormatUint(uint64(id), 10)) - } -} diff --git a/internal/api/http/handlers/widget/delete.go b/internal/api/http/handlers/widget/delete.go deleted file mode 100644 index 3c5635c..0000000 --- a/internal/api/http/handlers/widget/delete.go +++ /dev/null @@ -1 +0,0 @@ -package widget diff --git a/internal/api/http/handlers/widget/donat/donat.go b/internal/api/http/handlers/widget/donat/donat.go new file mode 100644 index 0000000..81a3014 --- /dev/null +++ b/internal/api/http/handlers/widget/donat/donat.go @@ -0,0 +1,99 @@ +package donat + +import ( + "context" + "donat-widget/internal/model" + "github.com/labstack/echo/v4" + "log/slog" + "strconv" +) + +type donatSetter interface { + SetDonat( + ctx context.Context, + widgetID model.WidgetID, + text string, + amount model.DonatAmount, + donatUser string, + ) error +} +type SetDonatRequest struct { + WidgetID model.WidgetID `json:"widgetID" validate:"required"` + Text string `json:"text" validate:"required"` + Amount model.DonatAmount `json:"amount" validate:"required"` + DonatUser string `json:"donatUser" validate:"required"` +} + +// SetDonat +// +// @Description Set donat +// @Tags Donat +// @Accept json +// @Produce json +// @Param RegisterData body SetDonatRequest true "Set donat" +// @Router /api/widget/donat/set [post] +func SetDonat(donatService donatSetter) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + var donatData SetDonatRequest + if err := request.Bind(&donatData); err != nil { + slog.Error(err.Error()) + return echo.NewHTTPError(400, err.Error()) + } + + err := request.Validate(&donatData) + if err != nil { + slog.Error(err.Error()) + return echo.NewHTTPError(400, err.Error()) + } + + err = donatService.SetDonat( + ctx, + donatData.WidgetID, + donatData.Text, + donatData.Amount, + donatData.DonatUser, + ) + if err != nil { + return request.JSON(500, "Set donat error") + } + slog.Info("donat set success") + return request.String(200, "Set donat success") + } +} + +type donatDeleter interface { + DeleteDonat( + ctx context.Context, + DonatID model.DonatID, + ) error +} + +// DeleteDonat +// +// @Description Delete donat +// @Tags Donat +// @Accept json +// @Produce json +// @Router /api/widget/donat/delete/{donatID} [post] +func DeleteDonat(donatService donatDeleter) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + donatID, err := strconv.Atoi(request.Param("donatID")) + if err != nil { + return echo.NewHTTPError(400, "Path parameter 'donatID' is invalid") + } + + err = donatService.DeleteDonat( + ctx, + model.DonatID(donatID), + ) + if err != nil { + return echo.NewHTTPError(500, "Delete donat error") + } + slog.Info("Delete donat success") + return request.String(200, "Delete donat success") + } +} diff --git a/internal/api/http/handlers/widget/media/media.go b/internal/api/http/handlers/widget/media/media.go new file mode 100644 index 0000000..10fea56 --- /dev/null +++ b/internal/api/http/handlers/widget/media/media.go @@ -0,0 +1,210 @@ +package media + +import ( + "context" + "donat-widget/internal/model" + "github.com/labstack/echo/v4" + "log/slog" + "strconv" +) + +type FileSetter interface { + SetMediaFile( + ctx context.Context, + mediaType model.MediaType, + widgetID model.WidgetID, + file model.UploadFile, + filename string, + size int64, + collection string, + ) error +} + +// SetMediaFile +// +// @Description Upload media +// @Tags Media +// @Accept mpfd +// @Produce json +// @Param file formData file true "File to upload" +// @Param widgetId path int true "Widget ID" +// @Router /api/widget/media/{mediaType}/upload [post] +func SetMediaFile(service FileSetter) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + widgetID, err := strconv.Atoi(request.FormValue("widgetID")) + if err != nil { + return echo.NewHTTPError(404, "Path parameter 'widgetID' is invalid") + } + + mediaType := request.Param("mediaType") + + file, err := request.FormFile("file") + if err != nil { + return echo.NewHTTPError(404, "Form parameter 'file' is invalid") + } + + src, err := file.Open() + if err != nil { + return echo.NewHTTPError(500, "File is invalid") + } + + err = service.SetMediaFile( + ctx, + model.MediaType(mediaType), + model.WidgetID(widgetID), + &src, + file.Filename, + file.Size, + "", + ) + if err != nil { + return echo.NewHTTPError(500, "File upload is failed") + } + slog.Info("set " + mediaType + " file successfully") + return request.String(200, "File successfully uploaded") + } +} + +type FileGetter interface { + GetMediaFile( + ctx context.Context, + widgetID model.WidgetID, + mediaType model.MediaType, + ) (model.DownloadFile, error) +} + +// GetMediaFile +// +// @Description Get media +// @Produce application/octet-stream +// @Success 200 {array} byte +// @Param widgetId path int true "Widget ID" +// @Param mediaType path string true "'background' or 'image' or 'audio'" +// @Router /api/widget/media/{mediaType}/get/{widgetID} [post] +func GetMediaFile(service FileGetter) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + mediaType := request.Param("mediaType") + if mediaType != "background" && mediaType != "image" && mediaType != "audio" { + return echo.NewHTTPError(400, "Path parameter 'mediaType' is invalid") + } + widgetID, err := strconv.Atoi(request.Param("widgetID")) + if err != nil { + return echo.NewHTTPError(400, "Path parameter 'widgetID' is invalid") + } + + file, err := service.GetMediaFile( + ctx, + model.WidgetID(widgetID), + model.MediaType(mediaType), + ) + if err != nil { + return echo.NewHTTPError(500, "Get File is failed") + } + slog.Info("get " + mediaType + " file successfully") + return request.Blob(200, "application/octet-stream", file) + } +} + +type FileUpdater interface { + UpdateMediaFile( + ctx context.Context, + widgetID model.WidgetID, + mediaType model.MediaType, + file model.UploadFile, + filename string, + size int64, + collection string, + ) error +} + +func UpdateMediaFile(service FileUpdater) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + widgetID, err := strconv.Atoi(request.FormValue("widgetID")) + if err != nil { + return echo.NewHTTPError(404, "Path parameter 'widgetID' is invalid") + } + + mediaType := request.Param("mediaType") + + file, err := request.FormFile("file") + if err != nil { + return echo.NewHTTPError(404, "Form parameter 'file' is invalid") + } + + src, err := file.Open() + if err != nil { + return echo.NewHTTPError(500, "File is invalid") + } + + err = service.UpdateMediaFile( + ctx, + model.WidgetID(widgetID), + model.MediaType(mediaType), + &src, + file.Filename, + file.Size, + "", + ) + + if err != nil { + return echo.NewHTTPError(500, "File update is failed") + } + slog.Info("update media file successfully") + return request.String(200, "Media successfully uploaded") + } +} + +type UrlSetter interface { + SetMediaUrl( + ctx context.Context, + mediaType model.MediaType, + widgetID model.WidgetID, + mediaURL model.MediaUrl, + ) error +} +type SetRequest struct { + WidgetID model.WidgetID `json:"widgetID" validate:"required"` + MediaUrl model.MediaUrl `json:"mediaUrl" validate:"required"` +} + +// SetMediaUrl +// +// @Description Set media URL +// @Param mediaType path string true "'background' or 'image' or 'audio'" +// @Router /api/widget/media/{mediaType}/set [post] +func SetMediaUrl(service UrlSetter) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + var backgroundData SetRequest + if err := request.Bind(&backgroundData); err != nil { + return echo.NewHTTPError(400, err.Error()) + } + + mediaType := request.Param("mediaType") + + err := request.Validate(&backgroundData) + if err != nil { + + return echo.NewHTTPError(400, err.Error()) + } + + err = service.SetMediaUrl( + ctx, + model.MediaType(mediaType), + backgroundData.WidgetID, + backgroundData.MediaUrl, + ) + if err != nil { + return echo.NewHTTPError(500, "Set MediaUrl is failed") + } + slog.Info("Set media url successfully") + return request.String(200, "Media URL successfully set") + } +} diff --git a/internal/api/http/handlers/widget/view.go b/internal/api/http/handlers/widget/view.go deleted file mode 100644 index 3c5635c..0000000 --- a/internal/api/http/handlers/widget/view.go +++ /dev/null @@ -1 +0,0 @@ -package widget diff --git a/internal/api/http/handlers/widget/widget.go b/internal/api/http/handlers/widget/widget.go new file mode 100644 index 0000000..1c67a8c --- /dev/null +++ b/internal/api/http/handlers/widget/widget.go @@ -0,0 +1,212 @@ +package widget + +import ( + "context" + "donat-widget/internal/model" + "github.com/labstack/echo/v4" + "log/slog" + "strconv" +) + +type widgetCreator interface { + CreateWidget( + ctx context.Context, + streamerID model.StreamerID, + templateID model.TemplateID, + ) (model.WidgetID, error) +} +type CreateWidgetRequest struct { + StreamerID model.StreamerID `json:"streamerID" validate:"required"` + TemplateID model.TemplateID `json:"templateID" validate:"required"` +} +type CreateWidgetResponse struct { + WidgetID model.WidgetID `json:"widgetID"` +} + +// CreateWidget +// +// @Description Create widget +// @Tags Widget +// @Accept json +// @Produce json +// @Param RegisterData body CreateWidgetRequest true "Create widget" +// @Success 200 {object} CreateWidgetResponse +// @Router /api/widget/create [post] +func CreateWidget(widgetService widgetCreator) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + var widgetData CreateWidgetRequest + if err := request.Bind(&widgetData); err != nil { + return echo.NewHTTPError(400, err.Error()) + } + + err := request.Validate(&widgetData) + if err != nil { + return echo.NewHTTPError(400, err.Error()) + } + + widgetID, err := widgetService.CreateWidget( + ctx, + widgetData.StreamerID, + widgetData.TemplateID, + ) + if err != nil { + return request.JSON(422, "Create widget error") + } + + response := CreateWidgetResponse{ + WidgetID: widgetID, + } + slog.Info("Widget created") + return request.JSON(200, response) + } +} + +type widgetHTMLGetter interface { + GetWidgetHTML( + ctx context.Context, + widgetID model.WidgetID, + ) (model.WidgetHTML, error) +} + +// GetWidgetHTML @Description Get widget +// +// @Tags Widget +// @Accept json +// @Produce json +// @Success 200 +// @Router /api/widget/html/{widgetID} [get] +func GetWidgetHTML(widgetService widgetHTMLGetter) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + widgetID, err := strconv.Atoi(request.Param("widgetID")) + if err != nil { + return echo.NewHTTPError(400, "Path parameter 'widgetID' is invalid") + } + + widgetHTML, err := widgetService.GetWidgetHTML( + ctx, + model.WidgetID(widgetID), + ) + if err != nil { + return request.JSON(500, "Get widget HTML error") + } + slog.Info("Get widget HTML successfully") + return request.HTML(200, string(widgetHTML)) + } +} + +type widgetInfoGetter interface { + GetWidgetInfo( + ctx context.Context, + widgetID model.WidgetID, + ) (*model.DonatAndWidget, error) +} + +type GetInfoResponse struct { + AudioUrl model.MediaUrl `json:"audioUrl"` + ImageUrl model.MediaUrl `json:"imageUrl"` + Text string `json:"text"` + Amount model.DonatAmount `json:"amount"` + DonatUser string `json:"donatUser"` + Display model.Display `json:"display"` + Duration model.Duration `json:"duration"` + DonatID model.DonatID `json:"donatID"` +} + +// GetWidgetInfo +// +// @Description Widget Info +// @Tags Widget +// @Accept json +// @Produce json +// @Param widgetID path int true "Widget ID" +// @Router /api/widget/info/{widgetID} [get] +func GetWidgetInfo(widgetService widgetInfoGetter) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + widgetID, err := strconv.Atoi(request.Param("widgetID")) + if err != nil { + return echo.NewHTTPError(400, "Path parameter 'widgetID' is invalid") + } + + donatAndWidget, err := widgetService.GetWidgetInfo(ctx, model.WidgetID(widgetID)) + if err != nil { + return request.JSON(422, "Get widget info error") + } + + if !donatAndWidget.Display { + response := GetInfoResponse{ + Display: donatAndWidget.Display, + } + slog.Info("Get widget info successfully") + return request.JSON(200, response) + } + + response := GetInfoResponse{ + AudioUrl: donatAndWidget.Widget.AudioUrl, + ImageUrl: donatAndWidget.Widget.ImageUrl, + Text: donatAndWidget.Donat.Text, + Display: donatAndWidget.Display, + Duration: donatAndWidget.Widget.Duration, + DonatUser: donatAndWidget.Donat.DonatUser, + Amount: donatAndWidget.Donat.Amount, + DonatID: donatAndWidget.Donat.ID, + } + slog.Info("Get widget info successfully") + return request.JSON(200, response) + } +} + +type widgetDurationUpdater interface { + UpdateWidgetDuration( + ctx context.Context, + widgetID model.WidgetID, + duration model.Duration, + ) error +} + +type UpdateDurationRequest struct { + WidgetID model.WidgetID `json:"widgetID" validate:"required"` + Duration model.Duration `json:"duration" validate:"required"` +} + +// UpdateDuration +// +// @Description UpdateDuration +// @Tags Widget +// @Accept json +// @Produce json +// @Param UpdateData body UpdateDurationRequest true "UpdateDuration" +// @Success 200 {string} +// @Router /api/widget/duration/update [post] +func UpdateDuration(widgetService widgetDurationUpdater) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + var widgetData UpdateDurationRequest + if err := request.Bind(&widgetData); err != nil { + return echo.NewHTTPError(400, err.Error()) + } + + err := request.Validate(&widgetData) + if err != nil { + return echo.NewHTTPError(400, err.Error()) + } + + err = widgetService.UpdateWidgetDuration( + ctx, + widgetData.WidgetID, + widgetData.Duration, + ) + if err != nil { + return request.JSON(422, "Update duration error") + } + + slog.Info("Duration updated") + return request.JSON(200, "Update duration successfully") + } +} diff --git a/internal/app/http/app.go b/internal/app/http/app.go index f82b08b..8188c2e 100644 --- a/internal/app/http/app.go +++ b/internal/app/http/app.go @@ -2,11 +2,6 @@ package http import ( "context" - _ "donat-widget/api/http" - "donat-widget/infrastructure/pg" - widgetHandler "donat-widget/internal/api/http/handlers/widget" - "donat-widget/internal/config" - "donat-widget/pkg/validator" "github.com/labstack/echo/v4" "github.com/swaggo/echo-swagger" "log/slog" @@ -14,7 +9,22 @@ import ( ) import ( + "donat-widget/infrastructure/pg" + "donat-widget/infrastructure/weed" + "donat-widget/internal/config" + "donat-widget/internal/model" + "donat-widget/pkg/validator" +) + +import ( + widgetHandler "donat-widget/internal/api/http/handlers/widget" widgetService "donat-widget/internal/service/widget" + + mediaHandler "donat-widget/internal/api/http/handlers/widget/media" + mediaService "donat-widget/internal/service/widget/media" + + donatHandler "donat-widget/internal/api/http/handlers/widget/donat" + donatService "donat-widget/internal/service/widget/donat" ) type App struct { @@ -23,12 +33,12 @@ type App struct { func NewApp() *echo.Echo { app := &App{} - cfg := app.InitConfig() - pool := app.initDB(cfg) - log := app.InitLogger() - + _ = app.InitLogger() + app.Config = app.InitConfig() + db := app.initDB() + storage := app.InitStorage() server := InitHTTPServer() - InitHandlers(server, pool, log) + InitHandlers(server, db, storage) return server } @@ -48,26 +58,89 @@ func (a *App) InitConfig() *config.Config { return cfg } -func (a *App) initDB(cfg *config.Config) *pg.Postgres { - pool := pg.NewPgPool( - context.Background(), - cfg.Database.Username, - cfg.Database.Password, - cfg.Database.Host, - cfg.Database.Port, - cfg.Database.DBName, - ) - return pool -} - -func InitHandlers(server *echo.Echo, pool *pg.Postgres, log *slog.Logger) { - PREFIX := "/api/widget" - server.GET("/api/docs/*", echoSwagger.WrapHandler) - widgetSvc := widgetService.New(pool) - server.GET(PREFIX+"/create", widgetHandler.Create(widgetSvc, log)) -} - func (a *App) InitLogger() *slog.Logger { logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) + slog.SetDefault(logger) return logger } + +func (a *App) InitStorage() *weed.Weed { + storage, err := weed.NewWeed( + a.Config.Storage.Filer, + a.Config.Storage.Master, + ) + if err != nil { + panic(err) + } + + return storage +} + +func (a *App) initDB() *pg.Postgres { + + db := pg.NewPgPool( + context.Background(), + a.Config.Db.Username, + a.Config.Db.Password, + a.Config.Db.Host, + a.Config.Db.Port, + a.Config.Db.DBName, + ) + return db +} + +func InitHandlers( + server *echo.Echo, + db *pg.Postgres, + storage model.Storage, +) { + PREFIX := "/api/widget" + server.GET(PREFIX+"/docs/*", echoSwagger.WrapHandler) + server.GET(PREFIX+"/table/create", CreateTale(db)) + server.GET(PREFIX+"/table/drop", DropTale(db)) + + widgetSvc := widgetService.New(db) + server.GET(PREFIX+"/create", widgetHandler.CreateWidget(widgetSvc)) + server.GET(PREFIX+"/html/:widgetID", widgetHandler.GetWidgetHTML(widgetSvc)) + server.GET(PREFIX+"/info/:widgetID", widgetHandler.GetWidgetInfo(widgetSvc)) + server.GET(PREFIX+"/duration/update", widgetHandler.UpdateDuration(widgetSvc)) + + mediaSvc := mediaService.New(db, storage) + server.POST(PREFIX+"/media/:mediaType/upload", mediaHandler.SetMediaFile(mediaSvc)) + server.GET(PREFIX+"/media/:mediaType/get/:widgetID", mediaHandler.GetMediaFile(mediaSvc)) + server.POST(PREFIX+"/media/:mediaType/set", mediaHandler.SetMediaUrl(mediaSvc)) + + donatSvc := donatService.New(db) + server.POST(PREFIX+"/donat/set", donatHandler.SetDonat(donatSvc)) + server.DELETE(PREFIX+"/donat/delete/:donatID", donatHandler.DeleteDonat(donatSvc)) +} + +func CreateTale(db *pg.Postgres) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + err := db.CreateTable(ctx) + if err != nil { + slog.Error("db.CreateTable: ", err) + return err + } + + slog.Info("Create table ok") + return request.String(200, "Create table ok") + } +} + +func DropTale(db *pg.Postgres) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + err := db.DropTable(ctx) + if err != nil { + slog.Error("db.DropTable: ", err) + return err + } + + slog.Info("Dropped table ok") + return request.String(200, "Dropped table ok") + } +} diff --git a/internal/config/config.go b/internal/config/config.go index 73b4f1f..70531b5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,10 +6,11 @@ import ( ) type Config struct { - Database DatabaseConfig `yaml:"db"` + Db Database `yaml:"db"` + Storage Storage `yaml:"storage"` } -type DatabaseConfig struct { +type Database struct { Username string `yaml:"username"` Password string `yaml:"password"` Host string `yaml:"host"` @@ -17,6 +18,11 @@ type DatabaseConfig struct { DBName string `yaml:"dbname"` } +type Storage struct { + Filer string `yaml:"filer"` + Master string `yaml:"master"` +} + func Init() *Config { data, err := os.ReadFile("internal/config/config.yaml") if err != nil { diff --git a/internal/config/config.yaml b/internal/config/config.yaml index 645d3a5..c1a5d15 100644 --- a/internal/config/config.yaml +++ b/internal/config/config.yaml @@ -1,9 +1,13 @@ db: - port: "31004" - host: "92.63.193.151" - password: "FSefebvrebre" - username: "admin" - dbname: "user_db" + port: "5432" + host: "80.87.195.79" + password: "test" + username: "test" + dbname: "test" server: - port: "8002" \ No newline at end of file + port: "8002" + +storage: + filer: "http://92.63.193.151:8111" + master: "http://92.63.193.151:9333" \ No newline at end of file diff --git a/internal/model/interfaces.go b/internal/model/interfaces.go new file mode 100644 index 0000000..bd8e5b3 --- /dev/null +++ b/internal/model/interfaces.go @@ -0,0 +1,110 @@ +package model + +import ( + "context" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type Storage interface { + Upload( + file UploadFile, + filename string, + size int64, + collection string, + ) (FileData, error) + Download( + FileID FileID, + ) (DownloadFile, error) + Update( + file UploadFile, + fileID FileID, + filename string, + size int64, + collection string, + ) error +} + +type WidgetRepo interface { + CreateWidget( + ctx context.Context, + streamerID StreamerID, + templateId TemplateID, + ) (WidgetID, error) + + DeleteWidget( + ctx context.Context, + widgetID WidgetID, + ) error + + GetWidget( + ctx context.Context, + widgetID WidgetID, + ) (*Widget, error) + + UpdateWidgetDuration( + ctx context.Context, + widgetID WidgetID, + duration Duration, + ) error +} + +type DonatRepo interface { + SetDonat( + ctx context.Context, + widgetID WidgetID, + text string, + amount DonatAmount, + donatUser string, + ) error + GetDonat( + ctx context.Context, + widgetID WidgetID, + ) ([]*Donat, error) + DeleteDonat( + ctx context.Context, + donatID DonatID, + ) error +} + +type MediaRepo interface { + SetMediaFile( + file UploadFile, + filename string, + size int64, + collection string, + ) (FileID, error) + GetMediaFile( + fileID FileID, + ) (DownloadFile, error) + GetMediaUrl( + ctx context.Context, + widgetID WidgetID, + mediaType MediaType, + ) (MediaUrl, error) + SetMediaUrl( + ctx context.Context, + widgetID WidgetID, + mediaUrl MediaUrl, + mediaType MediaType, + ) error + UpdateMediaFile( + ctx context.Context, + widgetID WidgetID, + file UploadFile, + fileID FileID, + filename string, + size int64, + collection string, + mediaType MediaType, + ) error +} + +type Error interface { + Error() string +} + +type Db interface { + Exec(ctx context.Context, query string, args ...interface{}) (pgconn.CommandTag, error) + Query(ctx context.Context, query string, args ...interface{}) (pgx.Rows, error) +} diff --git a/internal/model/models.go b/internal/model/models.go new file mode 100644 index 0000000..20f0837 --- /dev/null +++ b/internal/model/models.go @@ -0,0 +1,33 @@ +package model + +import ( + "time" +) + +type Widget struct { + ID WidgetID + StreamerID StreamerID + TemplateID TemplateID + BackgroundUrl MediaUrl + ImageUrl MediaUrl + AudioUrl MediaUrl + Duration Duration + CreatedAt time.Time + UpdatedAt time.Time +} + +type Donat struct { + ID DonatID + WidgetID WidgetID + Text string + DonatUser string + Amount DonatAmount + CreatedAt time.Time + UpdatedAt time.Time +} + +type DonatAndWidget struct { + Widget *Widget + Donat *Donat + Display Display +} diff --git a/internal/model/types.go b/internal/model/types.go new file mode 100644 index 0000000..631c79c --- /dev/null +++ b/internal/model/types.go @@ -0,0 +1,25 @@ +package model + +import ( + "github.com/linxGnu/goseaweedfs" + "mime/multipart" +) + +type StreamerID int +type WidgetID int +type TemplateID int +type Duration int +type MediaUrl string +type MediaType string +type Display bool + +type Widgets []*Widget +type WidgetHTML string + +type FileData *goseaweedfs.FilePart +type UploadFile *multipart.File +type DownloadFile []byte +type FileID string + +type DonatAmount string +type DonatID int diff --git a/internal/model/widget-templates.go b/internal/model/widget-templates.go new file mode 100644 index 0000000..2ba6e76 --- /dev/null +++ b/internal/model/widget-templates.go @@ -0,0 +1,124 @@ +package model + +import "fmt" + +func GetTemplate1( + widgetID WidgetID, + backgroundUrl MediaUrl, +) WidgetHTML { + + style := fmt.Sprintf(`body { + margin: 0; + padding: 0; + height: 100vh; + display: flex; + justify-content: center; + align-items: flex-start; + background-image: url('%s'); + background-repeat: no-repeat; + background-size: cover; +} +#content { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 100px +} + +#content img { + width: 50vw; + height: 50vh; + object-fit: cover; +} + +#content audio { + display: none; +} + +#content p { + font-size: 60px; +} +`, backgroundUrl) + + script := fmt.Sprintf(`let widgetID = '%v' +let baseUrl = 'http://localhost:8002/api/widget' + +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function getWidgetInfo() { + let response = await fetch(baseUrl + '/info/' + widgetID); + let widget = await response.json(); + return widget +} + +function addImage(imageUrl) { + img = document.createElement('img'); + img.src = imageUrl + '?t=' + new Date().getTime(); + contentDiv.appendChild(img); +} + +function addText(text) { + p = document.createElement('p'); + p.innerHTML = text; + contentDiv.appendChild(p); +} + +function addAudio(audioUrl) { + audio = document.createElement('audio'); + audio.src = audioUrl; + audio.autoplay = true; + audio.controls = false; + contentDiv.appendChild(audio); +} + +async function endDonat(donatID) { + if (audio) { + audio.pause(); + audio.remove(); + } + while (contentDiv.firstChild) { + contentDiv.removeChild(contentDiv.firstChild); + } + let response = await fetch(baseUrl + '/donat/delete/' + String(donatID), {method: 'DELETE'}); +} + +let audio; +const contentDiv = document.getElementById('content'); + +async function widgetView() { + while (true) { + let widget = await getWidgetInfo() + console.log(widget); + if (!widget.display) { + await delay(5 * 1000); + continue + } + + addImage(widget.imageUrl) + addText(widget.text + widget.amount) + addAudio(widget.audioUrl) + + await delay(widget.duration * 1000); + await endDonat(widget.donatID) + } +} +widgetView()`, widgetID) + + template1 := fmt.Sprintf(` + + +
+