From 6a9cde458af30aa2c7304f5ffee802daf895cb4f Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 12 Sep 2024 20:06:27 +0200 Subject: [PATCH] added questions, replies and likes functionality" --- go.mod | 36 +- go.sum | 47 +- modules/database/database.go | 2 - modules/structs/questions.go | 17 + modules/structs/users.go | 10 +- modules/utils/globals.go | 5 + routers/router/api/v1/app/app.go | 15 +- routers/router/api/v1/lessons/lessons.go | 132 ++++- routers/router/api/v1/lessons/questions.go | 452 ++++++++++++++++++ routers/router/api/v1/organization/roles.go | 4 +- .../router/api/v1/organization/settings.go | 24 +- routers/router/api/v1/organization/team.go | 7 +- routers/router/api/v1/user/auth.go | 3 +- routers/router/api/v1/user/user.go | 57 ++- routers/router/router.go | 6 + socketclients/socketclients.go | 10 +- 16 files changed, 727 insertions(+), 100 deletions(-) create mode 100644 routers/router/api/v1/lessons/questions.go diff --git a/go.mod b/go.mod index 4cce330..82f544b 100644 --- a/go.mod +++ b/go.mod @@ -4,38 +4,40 @@ go 1.21.0 require ( git.ex.umbach.dev/Alex/roese-utils v1.0.21 - git.ex.umbach.dev/LMS/libcore v1.0.6 + git.ex.umbach.dev/LMS/libcore v1.0.8 github.com/gofiber/fiber/v2 v2.52.5 github.com/gofiber/websocket/v2 v2.2.1 github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 - github.com/rs/zerolog v1.31.0 - golang.org/x/crypto v0.14.0 + github.com/rs/zerolog v1.33.0 + golang.org/x/crypto v0.27.0 gorm.io/driver/mysql v1.5.7 gorm.io/gorm v1.25.11 ) require ( - github.com/andybalholm/brotli v1.0.5 // indirect - github.com/fasthttp/websocket v1.5.3 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/fasthttp/websocket v1.5.10 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // 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.15.5 // indirect - github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/go-playground/validator/v10 v10.22.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/klauspost/compress v1.17.0 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/rivo/uniseg v0.2.0 // indirect - github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/fasthttp v1.55.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect ) diff --git a/go.sum b/go.sum index b915315..4a0dc32 100644 --- a/go.sum +++ b/go.sum @@ -4,20 +4,17 @@ git.ex.umbach.dev/Alex/roese-utils v1.0.21 h1:ae1AHQh8UHJVLbpk5Gf4S5IP0EEWGD1JFI git.ex.umbach.dev/Alex/roese-utils v1.0.21/go.mod h1:hFcnKQl6nuGFEMCxK/eVQBUD6ixBFlAaiy4E2aQqUL8= git.ex.umbach.dev/LMS/libcore v1.0.6 h1:Af+2jD4aC3+4qMgSmTn4nvRosAS5ETTX9JR3gznlGPI= git.ex.umbach.dev/LMS/libcore v1.0.6/go.mod h1:BvbMJWYQ83dblDAB4ooLfwuorjdy6G3txdOrjRfIKPA= -github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= -github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +git.ex.umbach.dev/LMS/libcore v1.0.7 h1:bKt02PY0FffHU79EnW5k6HvySLt4KgyKmb7duulweTM= +git.ex.umbach.dev/LMS/libcore v1.0.7/go.mod h1:BvbMJWYQ83dblDAB4ooLfwuorjdy6G3txdOrjRfIKPA= +git.ex.umbach.dev/LMS/libcore v1.0.8 h1:6OLKoWEiSy+rm/UdKFASGWzR7hQocBKPA5KR8zsdzLY= +git.ex.umbach.dev/LMS/libcore v1.0.8/go.mod h1:BvbMJWYQ83dblDAB4ooLfwuorjdy6G3txdOrjRfIKPA= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek= -github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs= github.com/fasthttp/websocket v1.5.10 h1:bc7NIGyrg1L6sd5pRzCIbXpro54SZLEluZCu0rOpcN4= github.com/fasthttp/websocket v1.5.10/go.mod h1:BwHeuXGWzCW1/BIKUKD3+qfCl+cTdsHu/f243NcAI/Q= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -26,11 +23,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o 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.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24= -github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 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/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= @@ -47,12 +41,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -61,63 +51,38 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= -github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= -github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= -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/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= -github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= 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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.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/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= diff --git a/modules/database/database.go b/modules/database/database.go index 2731365..7f7624e 100644 --- a/modules/database/database.go +++ b/modules/database/database.go @@ -44,6 +44,4 @@ func InitDatabase() { db.AutoMigrate(&models.LessonContent{}) db.AutoMigrate(&models.Question{}) db.AutoMigrate(&models.QuestionLike{}) - db.AutoMigrate(&models.QuestionReply{}) - db.AutoMigrate(&models.QuestionReplyLike{}) } diff --git a/modules/structs/questions.go b/modules/structs/questions.go index c025054..2062b76 100644 --- a/modules/structs/questions.go +++ b/modules/structs/questions.go @@ -1 +1,18 @@ package structs + +import "git.ex.umbach.dev/LMS/libcore/models" + +type QuestionsResponse struct { + Questions []models.Question + LikedQuestions []string +} + +// swagger:model CreateQuestionRequest +type CreateQuestionRequest struct { + Message string +} + +type LessonIdQuestionIdParam struct { + LessonId string + QuestionId string +} diff --git a/modules/structs/users.go b/modules/structs/users.go index d129e4d..83b146e 100644 --- a/modules/structs/users.go +++ b/modules/structs/users.go @@ -1,7 +1,11 @@ package structs +// swagger:model GetUserResponse type GetUserResponse struct { - AvatarUrl string + Id string + FirstName string + LastName string + ProfilePictureUrl string } // swagger:model UserLoginRequest @@ -53,3 +57,7 @@ type GetUserProfileResponse struct { Email string RoleId string } + +type UserIdParam struct { + UserId string +} diff --git a/modules/utils/globals.go b/modules/utils/globals.go index b73bdfd..97639a1 100644 --- a/modules/utils/globals.go +++ b/modules/utils/globals.go @@ -97,6 +97,11 @@ const ( SendCmdLessonContentUpdatedPosition = 15 SendCmdLessonContentFileUpdated = 16 SendCmdUserProfilePictureUpdated = 17 + SendCmdLessonQuestionCreated = 18 + SendCmdLessonQuestionLiked = 19 + SendCmdLessonQuestionCountUpLikes = 20 + SendCmdLessonQuestionDisliked = 21 + SendCmdLessonQuestionCountDownLikes = 22 ) // commands received from websocket clients diff --git a/routers/router/api/v1/app/app.go b/routers/router/api/v1/app/app.go index 5a1e5ab..83d085c 100644 --- a/routers/router/api/v1/app/app.go +++ b/routers/router/api/v1/app/app.go @@ -27,13 +27,20 @@ func GetApp(c *fiber.Ctx) error { var user models.User - database.DB.Model(&models.User{ - Id: c.Locals("userId").(string), - }).Select("profile_picture_url").First(&user) + if err := database.DB.Model(&models.User{}). + Where("id = ?", c.Locals("userId").(string)). + Select("profile_picture_url").First(&user).Error; err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } var organization models.Organization - database.DB.Model(&models.Organization{}).Select("company_name", "primary_color", "logo_url", "banner_url").Where("id = ?", c.Locals("organizationId").(string)).First(&organization) + if err := database.DB.Model(&models.Organization{}). + Select("company_name", "primary_color", "logo_url", "banner_url"). + Where("id = ?", c.Locals("organizationId").(string)). + First(&organization).Error; err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } return c.JSON(structs.GetAppResponse{ User: structs.AppUser{ diff --git a/routers/router/api/v1/lessons/lessons.go b/routers/router/api/v1/lessons/lessons.go index 0ff6fa7..558c655 100644 --- a/routers/router/api/v1/lessons/lessons.go +++ b/routers/router/api/v1/lessons/lessons.go @@ -35,7 +35,9 @@ func GetLessons(c *fiber.Ctx) error { var lessons []structs.LessonResponse - if err := database.DB.Model(&models.Lesson{}).Where("organization_id = ?", c.Locals("organizationId")).Find(&lessons).Error; err != nil { + if err := database.DB.Model(&models.Lesson{}). + Where("organization_id = ?", c.Locals("organizationId")). + Find(&lessons).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -116,9 +118,23 @@ func GetLessonContents(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusBadRequest) } + // check if lesson belongs to organization + + var lesson models.Lesson + + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + First(&lesson).Error; err != nil { + return c.SendStatus(fiber.StatusNotFound) + } + + // get lesson contents + var lessonContents []structs.GetLessonContentsResponse - if err := database.DB.Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId).Find(&lessonContents).Error; err != nil { + if err := database.DB.Model(&models.LessonContent{}). + Where("lesson_id = ?", params.LessonId). + Find(&lessonContents).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -160,7 +176,9 @@ func GetLessonSettings(c *fiber.Ctx) error { var lessonSettings structs.LessonSettings - if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).First(&lessonSettings).Error; err != nil { + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + First(&lessonSettings).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -204,7 +222,9 @@ func UpdateLessonPreviewTitle(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusBadRequest) } - if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).Update("title", body.Title).Error; err != nil { + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + Update("title", body.Title).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -278,7 +298,9 @@ func UpdateLessonPreviewThumbnail(c *fiber.Ctx) error { lesson := models.Lesson{} - if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).First(&lesson).Error; err != nil { + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + First(&lesson).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -296,7 +318,9 @@ func UpdateLessonPreviewThumbnail(c *fiber.Ctx) error { thumbnailUrl := databasePath + fileName - if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).Update("thumbnail_url", thumbnailUrl).Error; err != nil { + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + Update("thumbnail_url", thumbnailUrl).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -362,8 +386,7 @@ func UpdateLessonState(c *fiber.Ctx) error { } if err := database.DB.Model(&models.Lesson{}). - Where("id = ?", params.LessonId). - Where("organization_id = ?", c.Locals("organizationId").(string)). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). Update("state", body.State).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -428,11 +451,25 @@ func AddLessonContent(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusBadRequest) } + // check if lesson belongs to organization + + var lesson models.Lesson + + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + First(&lesson).Error; err != nil { + return c.SendStatus(fiber.StatusNotFound) + } + // get last position var lastContent models.LessonContent - if err := database.DB.Select("position").Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId).Order("position desc").First(&lastContent).Error; err != nil { + if err := database.DB.Select("position"). + Model(&models.LessonContent{}). + Where("lesson_id = ?", params.LessonId). + Order("position desc"). + First(&lastContent).Error; err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { return c.SendStatus(fiber.StatusNotFound) } @@ -532,11 +569,24 @@ func UploadLessonContentFile(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusBadRequest) } + // check if lesson belongs to organization + + var lesson models.Lesson + + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + First(&lesson).Error; err != nil { + return c.SendStatus(fiber.StatusNotFound) + } + // get current file content := models.LessonContent{} - if err := database.DB.Select("type", "data").Model(&models.LessonContent{}).Where("id = ?", params.ContentId).First(&content).Error; err != nil { + if err := database.DB.Select("type", "data"). + Model(&models.LessonContent{}). + Where("id = ? AND lesson_id = ?", params.ContentId, params.LessonId). + First(&content).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -552,7 +602,9 @@ func UploadLessonContentFile(c *fiber.Ctx) error { utils.CreateFolderStructureIfNotExists(publicPath) - if err := database.DB.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).Update("data", databasePath+fileName).Error; err != nil { + if err := database.DB.Model(&models.LessonContent{}). + Where("id = ? AND lesson_id = ?", params.ContentId, params.LessonId). + Update("data", databasePath+fileName).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -624,7 +676,21 @@ func UpdateLessonContent(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusBadRequest) } - if err := database.DB.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).Update("data", body.Data).Error; err != nil { + // check if lesson belongs to organization + + var lesson models.Lesson + + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + First(&lesson).Error; err != nil { + return c.SendStatus(fiber.StatusNotFound) + } + + // update content + + if err := database.DB.Model(&models.LessonContent{}). + Where("id = ? AND lesson_id = ?", params.ContentId, params.LessonId). + Update("data", body.Data).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -692,6 +758,16 @@ func UpdateLessonContentPosition(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusBadRequest) } + // check if lesson belongs to organization + + var lesson models.Lesson + + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + First(&lesson).Error; err != nil { + return c.SendStatus(fiber.StatusNotFound) + } + // update position newPosition := body.Position @@ -704,7 +780,9 @@ func UpdateLessonContentPosition(c *fiber.Ctx) error { // Fetch the current position of the content being moved var currentContent models.LessonContent - if err := tx.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).First(¤tContent).Error; err != nil { + if err := tx.Model(&models.LessonContent{}). + Where("id = ? AND lesson_id = ?", params.ContentId, params.LessonId). + First(¤tContent).Error; err != nil { tx.Rollback() return c.SendStatus(fiber.StatusNotFound) } @@ -720,7 +798,8 @@ func UpdateLessonContentPosition(c *fiber.Ctx) error { if oldPosition < newPosition { // Items between oldPosition and newPosition need to be shifted down - if err := tx.Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId). + if err := tx.Model(&models.LessonContent{}). + Where("lesson_id = ?", params.LessonId). Where("position > ? AND position <= ?", oldPosition, newPosition). Update("position", gorm.Expr("position - 1")).Error; err != nil { tx.Rollback() @@ -728,7 +807,8 @@ func UpdateLessonContentPosition(c *fiber.Ctx) error { } } else { // Items between newPosition and oldPosition need to be shifted up - if err := tx.Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId). + if err := tx.Model(&models.LessonContent{}). + Where("lesson_id = ?", params.LessonId). Where("position >= ? AND position < ?", newPosition, oldPosition). Update("position", gorm.Expr("position + 1")).Error; err != nil { tx.Rollback() @@ -737,7 +817,9 @@ func UpdateLessonContentPosition(c *fiber.Ctx) error { } // Update the position of the moved content - if err := tx.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).Update("position", newPosition).Error; err != nil { + if err := tx.Model(&models.LessonContent{}). + Where("id = ? AND lesson_id = ?", params.ContentId, params.LessonId). + Update("position", newPosition).Error; err != nil { tx.Rollback() return c.SendStatus(fiber.StatusInternalServerError) } @@ -807,9 +889,21 @@ func DeleteLessonContent(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusInternalServerError) } + // check if lesson belongs to organization + + var lesson models.Lesson + + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + First(&lesson).Error; err != nil { + return c.SendStatus(fiber.StatusNotFound) + } + // Fetch the current position of the content being deleted var content models.LessonContent - if err := tx.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).First(&content).Error; err != nil { + if err := tx.Model(&models.LessonContent{}). + Where("id = ? AND lesson_id = ?", params.ContentId, params.LessonId). + First(&content).Error; err != nil { tx.Rollback() return c.SendStatus(fiber.StatusNotFound) } @@ -821,8 +915,8 @@ func DeleteLessonContent(c *fiber.Ctx) error { } // Shift down the positions of all contents with a higher position - if err := tx.Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId). - Where("position > ?", content.Position). + if err := tx.Model(&models.LessonContent{}). + Where("lesson_id = ? AND position > ?", params.LessonId, content.Position). Update("position", gorm.Expr("position - 1")).Error; err != nil { tx.Rollback() return c.SendStatus(fiber.StatusInternalServerError) diff --git a/routers/router/api/v1/lessons/questions.go b/routers/router/api/v1/lessons/questions.go new file mode 100644 index 0000000..7ff0a79 --- /dev/null +++ b/routers/router/api/v1/lessons/questions.go @@ -0,0 +1,452 @@ +package lessons + +import ( + "errors" + "fmt" + "time" + + "git.ex.umbach.dev/LMS/libcore/models" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "gorm.io/gorm" + "lms.de/backend/modules/database" + "lms.de/backend/modules/structs" + "lms.de/backend/modules/utils" + "lms.de/backend/socketclients" +) + +func GetQuestions(c *fiber.Ctx) error { + // swagger:operation GET /v1/lessons/{lessonId}/questions questions getQuestions + // --- + // summary: Get questions + // produces: + // - application/json + // responses: + // '200': + // description: Questions + // schema: + // "$ref": "#/definitions/QuestionsResponse" + // '500': + // description: Failed to get questions + + var params structs.LessonIdParam + + if err := c.ParamsParser(¶ms); err != nil { + return c.SendStatus(fiber.StatusBadRequest) + } + + // check if lesson belongs to organization + + var lesson models.Lesson + + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + First(&lesson).Error; err != nil { + return c.JSON([]models.Question{}) + } + + // get questions + + var questions []models.Question + + if err := database.DB.Model(&models.Question{}). + Where("lesson_id = ?", params.LessonId). + Find(&questions).Error; err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return c.SendStatus(fiber.StatusNotFound) + } + } + + // get all questions liked by user + + var likedQuestions []string + + if err := database.DB.Model(&models.QuestionLike{}). + Where("creator_user_id = ?", c.Locals("userId").(string)). + Pluck("question_id", &likedQuestions).Error; err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return c.SendStatus(fiber.StatusInternalServerError) + } + } + + /* + var questionLikes []models.QuestionLike + + if err := database.DB.Model(&models.QuestionLike{}). + Where("creator_user_id = ?", c.Locals("userId").(string)). + Find(&questionLikes).Error; err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return c.SendStatus(fiber.StatusInternalServerError) + } + } + + var likedQuestions []string + + for _, questionLike := range questionLikes { + likedQuestions = append(likedQuestions, questionLike.QuestionId) + } */ + + return c.JSON(structs.QuestionsResponse{ + Questions: questions, + LikedQuestions: likedQuestions, + }) +} + +func CreateQuestion(c *fiber.Ctx) error { + // swagger:operation POST /v1/lessons/{lessonId}/questions question createQuestion + // --- + // summary: Create question + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/CreateQuestionRequest" + // responses: + // '200': + // description: Question created successfully + // schema: + // type: string + // '400': + // description: Invalid request body + // '500': + // description: Failed to create question + + var params structs.LessonIdParam + + if err := c.ParamsParser(¶ms); err != nil { + return c.SendStatus(fiber.StatusBadRequest) + } + + var body structs.CreateQuestionRequest + + if err := c.BodyParser(&body); err != nil { + return c.SendStatus(fiber.StatusBadRequest) + } + + // check if lesson belongs to organization + + var lesson models.Lesson + + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + First(&lesson).Error; err != nil { + return c.SendStatus(fiber.StatusNotFound) + } + + // create question + + question := models.Question{ + Id: uuid.New().String(), + LessonId: params.LessonId, + Message: body.Message, + Likes: 0, + CreatorUserId: c.Locals("userId").(string), + CreatedAt: time.Now(), + } + + if err := database.DB.Create(&question).Error; err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } + + socketclients.BroadcastMessageToTopic( + c.Locals("organizationId").(string), + utils.SubscribedTopicLessonsId(params.LessonId), + structs.SendSocketMessage{ + Cmd: utils.SendCmdLessonQuestionCreated, + Body: question, + }, + ) + + return c.JSON(fiber.Map{ + "status": "success", + }) +} + +func CreateQuestionReply(c *fiber.Ctx) error { + // swagger:operation POST /v1/lessons/{lessonId}/questions/{questionId}/reply question createQuestionReply + // --- + // summary: Create question reply + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/CreateQuestionRequest" + // responses: + // '200': + // description: Question reply created successfully + // schema: + // type: string + // '400': + // description: Invalid request body + // '500': + // description: Failed to create question reply + + var params structs.LessonIdQuestionIdParam + + if err := c.ParamsParser(¶ms); err != nil { + return c.SendStatus(fiber.StatusBadRequest) + } + + var body structs.CreateQuestionRequest + + if err := c.BodyParser(&body); err != nil { + return c.SendStatus(fiber.StatusBadRequest) + } + + // check if lesson belongs to organization + + var lesson models.Lesson + + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + First(&lesson).Error; err != nil { + return c.SendStatus(fiber.StatusNotFound) + } + + // check if question belongs to lesson + + var question models.Question + + if err := database.DB.Model(&models.Question{}). + Where("id = ? AND lesson_id = ?", params.QuestionId, params.LessonId). + First(&question).Error; err != nil { + return c.SendStatus(fiber.StatusNotFound) + } + + // create question reply + + question = models.Question{ + Id: uuid.New().String(), + LessonId: params.LessonId, + QuestionId: params.QuestionId, + ReplyId: params.QuestionId, + Message: body.Message, + Likes: 0, + CreatorUserId: c.Locals("userId").(string), + } + + if err := database.DB.Create(&question).Error; err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } + + socketclients.BroadcastMessageToTopic( + c.Locals("organizationId").(string), + utils.SubscribedTopicLessonsId(params.LessonId), + structs.SendSocketMessage{ + Cmd: utils.SendCmdLessonQuestionCreated, + Body: question, + }, + ) + + return c.JSON(fiber.Map{ + "status": "success", + }) +} + +func LikeQuestion(c *fiber.Ctx) error { + // swagger:operation POST /v1/lessons/{lessonId}/questions/{questionId}/likes question likeQuestion + // --- + // summary: Like question + // produces: + // - application/json + // responses: + // '200': + // description: Question liked successfully + // schema: + // type: string + // '500': + // description: Failed to like question + + var params structs.LessonIdQuestionIdParam + + if err := c.ParamsParser(¶ms); err != nil { + return c.SendStatus(fiber.StatusBadRequest) + } + + // check if lesson belongs to organization + + var lesson models.Lesson + + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + First(&lesson).Error; err != nil { + fmt.Println("lesson not found") + return c.SendStatus(fiber.StatusNotFound) + } + + // check if question belongs to lesson + + var question models.Question + + if err := database.DB.Model(&models.Question{}). + Where("id = ? AND lesson_id = ?", params.QuestionId, params.LessonId). + First(&question).Error; err != nil { + fmt.Println("question not found") + return c.SendStatus(fiber.StatusNotFound) + } + + // check if user already liked question + + var questionLike models.QuestionLike + + if err := database.DB.Model(&models.QuestionLike{}). + Where("question_id = ? AND creator_user_id = ?", params.QuestionId, c.Locals("userId").(string)). + First(&questionLike).Error; err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return c.SendStatus(fiber.StatusInternalServerError) + } + } + + if questionLike.Id != "" { + fmt.Println("question already liked") + return c.SendStatus(fiber.StatusConflict) + } + + // like question + + questionLike = models.QuestionLike{ + Id: uuid.New().String(), + QuestionId: params.QuestionId, + CreatorUserId: c.Locals("userId").(string), + CreatedAt: time.Now(), + } + + if err := database.DB.Create(&questionLike).Error; err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } + + // update question likes + + if err := database.DB.Model(&models.Question{}). + Where("id = ?", params.QuestionId). + Update("likes", gorm.Expr("likes + 1")).Error; err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } + + // send message to user clients who liked the question + socketclients.SendMessageToUserWithTopic( + c.Locals("userId").(string), + utils.SubscribedTopicLessonsId(params.LessonId), + structs.SendSocketMessage{ + Cmd: utils.SendCmdLessonQuestionLiked, + Body: question.Id, + }, + ) + + // inform all users about the like + socketclients.BroadcastMessageToTopic( + c.Locals("userId").(string), + utils.SubscribedTopicLessonsId(params.LessonId), + structs.SendSocketMessage{ + Cmd: utils.SendCmdLessonQuestionCountUpLikes, + Body: question.Id, + }, + ) + + return c.JSON(fiber.Map{ + "status": "success", + }) +} + +func DislikeQuestion(c *fiber.Ctx) error { + // swagger:operation DELETE /v1/lessons/{lessonId}/questions/{questionId}/likes question dislikeQuestion + // --- + // summary: Dislike question + // produces: + // - application/json + // responses: + // '200': + // description: Question disliked successfully + // schema: + // type: string + // '500': + // description: Failed to dislike question + + var params structs.LessonIdQuestionIdParam + + if err := c.ParamsParser(¶ms); err != nil { + return c.SendStatus(fiber.StatusBadRequest) + } + + // check if lesson belongs to organization + + var lesson models.Lesson + + if err := database.DB.Model(&models.Lesson{}). + Where("id = ? AND organization_id = ?", params.LessonId, c.Locals("organizationId").(string)). + First(&lesson).Error; err != nil { + return c.SendStatus(fiber.StatusNotFound) + } + + // check if question belongs to lesson + + var question models.Question + + if err := database.DB.Model(&models.Question{}). + Where("id = ? AND lesson_id = ?", params.QuestionId, params.LessonId). + First(&question).Error; err != nil { + return c.SendStatus(fiber.StatusNotFound) + } + + // check if user already liked question + + var questionLike models.QuestionLike + + if err := database.DB.Model(&models.QuestionLike{}). + Where("question_id = ? AND creator_user_id = ?", params.QuestionId, c.Locals("userId").(string)). + First(&questionLike).Error; err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return c.SendStatus(fiber.StatusInternalServerError) + } + } + + if questionLike.Id == "" { + return c.SendStatus(fiber.StatusConflict) + } + + // dislike question + + if err := database.DB.Delete(&questionLike).Error; err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } + + // update question likes + + if err := database.DB.Model(&models.Question{}). + Where("id = ?", params.QuestionId). + Update("likes", gorm.Expr("likes - 1")).Error; err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } + + // send message to user clients who disliked the question + socketclients.SendMessageToUserWithTopic( + c.Locals("userId").(string), + utils.SubscribedTopicLessonsId(params.LessonId), + structs.SendSocketMessage{ + Cmd: utils.SendCmdLessonQuestionDisliked, + Body: question.Id, + }, + ) + + // inform all users about the dislike + socketclients.BroadcastMessageToTopic( + c.Locals("userId").(string), + utils.SubscribedTopicLessonsId(params.LessonId), + structs.SendSocketMessage{ + Cmd: utils.SendCmdLessonQuestionCountDownLikes, + Body: question.Id, + }, + ) + + return c.JSON(fiber.Map{ + "status": "success", + }) +} diff --git a/routers/router/api/v1/organization/roles.go b/routers/router/api/v1/organization/roles.go index d1d41c4..fa243de 100644 --- a/routers/router/api/v1/organization/roles.go +++ b/routers/router/api/v1/organization/roles.go @@ -65,7 +65,9 @@ func GetRoles(c *fiber.Ctx) error { var users []structs.HelperRoleUser - if err := database.DB.Model(&models.User{}).Where("role_id = ?", key).Where("organization_id = ?", c.Locals("organizationId").(string)).Find(&users).Error; err != nil { + if err := database.DB.Model(&models.User{}). + Where("role_id = ? AND organization_id = ?", key, c.Locals("organizationId").(string)). + Find(&users).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } diff --git a/routers/router/api/v1/organization/settings.go b/routers/router/api/v1/organization/settings.go index b21546e..765deee 100644 --- a/routers/router/api/v1/organization/settings.go +++ b/routers/router/api/v1/organization/settings.go @@ -73,7 +73,9 @@ func UpdateOrganizationSettings(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusBadRequest) } - if err := database.DB.Model(&models.Organization{}).Where("id = ?", c.Locals("organizationId").(string)).Updates(organizationSettings).Error; err != nil { + if err := database.DB.Model(&models.Organization{}). + Where("id = ?", c.Locals("organizationId").(string)). + Updates(organizationSettings).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -150,9 +152,10 @@ func UpdateOrganizationFile(c *fiber.Ctx) error { selectField = "banner_url" } - if err := database.DB.Model(&models.Organization{ - Id: c.Locals("organizationId").(string), - }).Select(selectField).First(&organization).Error; err != nil { + if err := database.DB.Model(&models.Organization{}). + Where("id = ?", c.Locals("organizationId").(string)). + Select(selectField). + First(&organization).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -180,7 +183,9 @@ func UpdateOrganizationFile(c *fiber.Ctx) error { update.BannerUrl = databasePath + fileName } - if err := database.DB.Model(&models.Organization{}).Where("id = ?", c.Locals("organizationId").(string)).Updates(update).Error; err != nil { + if err := database.DB.Model(&models.Organization{}). + Where("id = ?", c.Locals("organizationId").(string)). + Updates(update).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -241,7 +246,10 @@ func UpdateSubdomain(c *fiber.Ctx) error { var organization models.Organization - if err := database.DB.Select("subdomain").Model(organization).Where("id = ?", c.Locals("organizationId").(string)).First(&organization).Error; err != nil { + if err := database.DB.Select("subdomain"). + Model(organization). + Where("id = ?", c.Locals("organizationId").(string)). + First(&organization).Error; err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { return c.SendStatus(fiber.StatusNotFound) } @@ -250,7 +258,9 @@ func UpdateSubdomain(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusBadRequest) } - if err := database.DB.Model(&organization).Where("id = ?", c.Locals("organizationId")).Update("subdomain", params.Subdomain).Error; err != nil { + if err := database.DB.Model(&organization). + Where("id = ?", c.Locals("organizationId")). + Update("subdomain", params.Subdomain).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } diff --git a/routers/router/api/v1/organization/team.go b/routers/router/api/v1/organization/team.go index cc0caf4..2a8665e 100644 --- a/routers/router/api/v1/organization/team.go +++ b/routers/router/api/v1/organization/team.go @@ -37,7 +37,9 @@ func GetTeamMembers(c *fiber.Ctx) error { var users []structs.TeamMember - if err := database.DB.Model(&models.User{}).Where("organization_id = ?", c.Locals("organizationId")).Find(&users).Error; err != nil { + if err := database.DB.Model(&models.User{}). + Where("organization_id = ?", c.Locals("organizationId")). + Find(&users).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -176,8 +178,7 @@ func UpdateTeamMemberRole(c *fiber.Ctx) error { } if err := database.DB.Model(&models.User{}). - Where("id = ?", params.MemberId). - Where("organization_id = ?", c.Locals("organizationId").(string)). + Where("id = ? AND organization_id = ?", params.MemberId, c.Locals("organizationId").(string)). Update("role_id", body.RoleId).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } diff --git a/routers/router/api/v1/user/auth.go b/routers/router/api/v1/user/auth.go index 5ed6fd0..1696335 100644 --- a/routers/router/api/v1/user/auth.go +++ b/routers/router/api/v1/user/auth.go @@ -62,7 +62,8 @@ func UserLogin(c *fiber.Ctx) error { organizationId := c.Locals("organizationId").(string) - if err := database.DB.Select("id", "disabled", "password").First(&user, "email = ? AND organization_id = ?", body.Email, organizationId).Error; err != nil { + if err := database.DB.Select("id", "disabled", "password"). + First(&user, "email = ? AND organization_id = ?", body.Email, organizationId).Error; err != nil { logger.AddSystemLog(rslogger.LogTypeError, "Failed to find user with email %s", body.Email) return c.SendStatus(fiber.StatusBadRequest) } diff --git a/routers/router/api/v1/user/user.go b/routers/router/api/v1/user/user.go index 0701b31..5b6aad8 100644 --- a/routers/router/api/v1/user/user.go +++ b/routers/router/api/v1/user/user.go @@ -32,7 +32,10 @@ func GetUserProfile(c *fiber.Ctx) error { var user structs.GetUserProfileResponse - database.DB.Model(&models.User{}).Where("id = ?", c.Locals("userId").(string)).First(&user) + database.DB.Model(&models.User{}). + Select("email", "first_name", "last_name", "profile_picture_url", "role_id"). + Where("id = ?", c.Locals("userId").(string)). + First(&user) return c.JSON(user) } @@ -78,7 +81,10 @@ func UpdateUserProfilePicture(c *fiber.Ctx) error { user := models.User{} - if err := database.DB.Model(&models.User{}).Select("profile_picture_url").Where("id = ?", c.Locals("userId").(string)).First(&user).Error; err != nil { + if err := database.DB.Model(&models.User{}). + Select("profile_picture_url"). + Where("id = ?", c.Locals("userId").(string)). + First(&user).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -96,7 +102,9 @@ func UpdateUserProfilePicture(c *fiber.Ctx) error { profilePictureUrl := databasePath + fileName - if err := database.DB.Model(&models.User{}).Where("id = ?", c.Locals("userId").(string)).Update("profile_picture_url", profilePictureUrl).Error; err != nil { + if err := database.DB.Model(&models.User{}). + Where("id = ?", c.Locals("userId").(string)). + Update("profile_picture_url", profilePictureUrl).Error; err != nil { return c.SendStatus(fiber.StatusInternalServerError) } @@ -120,3 +128,46 @@ func UpdateUserProfilePicture(c *fiber.Ctx) error { "Data": databasePath + fileName, }) } + +func GetUser(c *fiber.Ctx) error { + // swagger:operation GET /user/{userId} user getUser + // --- + // summary: Get user + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: userId + // in: path + // type: string + // required: true + // responses: + // '200': + // description: User fetched successfully + // schema: + // "$ref": "#/definitions/GetUserResponse" + // '400': + // description: Invalid request body + // '404': + // description: User not found + // '500': + // description: Failed to fetch user + + var params structs.UserIdParam + + if err := c.ParamsParser(¶ms); err != nil { + return c.SendStatus(fiber.StatusBadRequest) + } + + var user structs.GetUserResponse + + if err := database.DB.Model(&models.User{}). + Select("id", "first_name", "last_name", "profile_picture_url"). + Where("id = ? AND organization_id = ?", params.UserId, c.Locals("organizationId").(string)). + First(&user).Error; err != nil { + return c.SendStatus(fiber.StatusNotFound) + } + + return c.JSON(user) +} diff --git a/routers/router/router.go b/routers/router/router.go index 80e74d2..c566487 100644 --- a/routers/router/router.go +++ b/routers/router/router.go @@ -37,6 +37,7 @@ func SetupRoutes(app *fiber.App) { u.Post("/auth/login", handleOrganizationSubdomain, user.UserLogin) u.Get("/profile", handleOrganizationSubdomain, requestAccessValidation, user.GetUserProfile) u.Post("/profile/picture", handleOrganizationSubdomain, requestAccessValidation, user.UpdateUserProfilePicture) + u.Get("/:userId", handleOrganizationSubdomain, requestAccessValidation, user.GetUser) l := v1.Group("/lessons") l.Get("/", handleOrganizationSubdomain, requestAccessValidation, lessons.GetLessons) @@ -51,6 +52,11 @@ func SetupRoutes(app *fiber.App) { l.Patch("/:lessonId/contents/:contentId/position", handleOrganizationSubdomain, requestAccessValidation, lessons.UpdateLessonContentPosition) l.Delete("/:lessonId/contents/:contentId", handleOrganizationSubdomain, requestAccessValidation, lessons.DeleteLessonContent) l.Post("/:lessonId/contents/:contentId/file/:type", handleOrganizationSubdomain, requestAccessValidation, lessons.UploadLessonContentFile) + l.Get("/:lessonId/questions", handleOrganizationSubdomain, requestAccessValidation, lessons.GetQuestions) + l.Post("/:lessonId/questions", handleOrganizationSubdomain, requestAccessValidation, lessons.CreateQuestion) + l.Post("/:lessonId/questions/:questionId/replies", handleOrganizationSubdomain, requestAccessValidation, lessons.CreateQuestionReply) + l.Post("/:lessonId/questions/:questionId/likes", handleOrganizationSubdomain, requestAccessValidation, lessons.LikeQuestion) + l.Delete("/:lessonId/questions/:questionId/likes", handleOrganizationSubdomain, requestAccessValidation, lessons.DislikeQuestion) app.Static("/static", config.Cfg.FolderPaths.PublicStatic) } diff --git a/socketclients/socketclients.go b/socketclients/socketclients.go index e23f493..2ccb6d4 100644 --- a/socketclients/socketclients.go +++ b/socketclients/socketclients.go @@ -72,7 +72,7 @@ func BroadcastMessageToTopics(organizationId string, topics []string, sendSocket } func hasClientSubscribedToTopic(topic string, clientTopic string) bool { - return clientTopic == topic || strings.HasPrefix(clientTopic, topic) + return clientTopic == topic /* || strings.HasPrefix(clientTopic, topic) */ } // Used to determine if a user is connected regardless of the session used @@ -93,3 +93,11 @@ func SendMessageToUserWithTopicExceptBrowserTabSession(userId string, topic stri } } } + +func SendMessageToUserWithTopic(userId string, topic string, sendSocketMessage structs.SendSocketMessage) { + for _, client := range cache.GetSocketClients() { + if client.UserId == userId && client.SubscribedTopic == topic { + client.SendMessage(sendSocketMessage) + } + } +}