From 6d2e84cb6b66d0c349fd0a750aa6b0837aeb8f4b Mon Sep 17 00:00:00 2001 From: TheSilentSage <83112995+TheSilentSage@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:35:43 +0530 Subject: [PATCH] feat: users can save course-wise notes * fix: merged slot for lab while fetching timetable * feat: users can save course-wise notes --------- Co-authored-by: Dev Keshwani --- vitty-backend-api/api/serializers/notes.go | 19 +++++ vitty-backend-api/api/v2/initialize.go | 1 + vitty-backend-api/api/v2/noteHandler.go | 85 +++++++++++++++++++ vitty-backend-api/go.mod | 6 +- vitty-backend-api/go.sum | 12 +++ .../internal/models/initialize.go | 1 + vitty-backend-api/internal/models/notes.go | 41 +++++++++ vitty-backend-api/internal/utils/users.go | 10 +++ 8 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 vitty-backend-api/api/serializers/notes.go create mode 100644 vitty-backend-api/api/v2/noteHandler.go create mode 100644 vitty-backend-api/internal/models/notes.go diff --git a/vitty-backend-api/api/serializers/notes.go b/vitty-backend-api/api/serializers/notes.go new file mode 100644 index 0000000..549f69a --- /dev/null +++ b/vitty-backend-api/api/serializers/notes.go @@ -0,0 +1,19 @@ +package serializers + +import "github.com/GDGVIT/vitty-backend/vitty-backend-api/internal/models" + +func NotesSerializer(notes []models.Notes) []map[string]interface{} { + var result []map[string]interface{} + + for _, note := range notes { + out := map[string]interface{}{ + "note_id": note.NoteID, + "note_name": note.NoteName, + "note_content": note.NoteContent, + "course_id": note.CourseID, + "course_name": note.Courses.CourseName, + } + result = append(result, out) + } + return result +} diff --git a/vitty-backend-api/api/v2/initialize.go b/vitty-backend-api/api/v2/initialize.go index 256c0ab..2d14391 100644 --- a/vitty-backend-api/api/v2/initialize.go +++ b/vitty-backend-api/api/v2/initialize.go @@ -10,4 +10,5 @@ func V2Handler(api fiber.Router) { userHandler(group) timetableHandler(group) friendHandler(group) + noteHandler(group) } diff --git a/vitty-backend-api/api/v2/noteHandler.go b/vitty-backend-api/api/v2/noteHandler.go new file mode 100644 index 0000000..e1ce5f3 --- /dev/null +++ b/vitty-backend-api/api/v2/noteHandler.go @@ -0,0 +1,85 @@ +package v2 + +import ( + "strings" + + "github.com/GDGVIT/vitty-backend/vitty-backend-api/api/middleware" + "github.com/GDGVIT/vitty-backend/vitty-backend-api/api/serializers" + "github.com/GDGVIT/vitty-backend/vitty-backend-api/internal/models" + "github.com/GDGVIT/vitty-backend/vitty-backend-api/internal/utils" + "github.com/gofiber/fiber/v2" +) + +func noteHandler(api fiber.Router) { + group := api.Group("/notes") + group.Use(middleware.JWTAuthMiddleware) + group.Get("/:courseId", getNotes) + group.Post("/save", saveNote) + group.Delete("/:noteId?", deleteNote) +} + +func getNotes(c *fiber.Ctx) error { + var notes models.Notes + courseId := c.Params("courseId") + notes.CourseID = courseId + notes.UserName = c.Locals("user").(models.User).Username + err, userNotes := notes.GetNotesByCourseId() + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Notes fetch failed", + }) + } + + if len(userNotes) == 0 { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "data": "No notes to display", + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "data": serializers.NotesSerializer(userNotes), + }) +} + +func saveNote(c *fiber.Ctx) error { + var note models.Notes + + if err := c.BodyParser(¬e); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Invalid request body", + }) + } + + if !strings.Contains(note.NoteID, "note_") || len(note.NoteID) < 32 { + note.NoteID = utils.UUIDWithPrefix("note") + } + + note.SaveNote() + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "detail": "Note Saved Successfully", + }) +} + +func deleteNote(c *fiber.Ctx) error { + var note models.Notes + + noteId := c.Params("noteId") + if noteId == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "detail": "Note id is missing", + }) + } + + note.NoteID = noteId + note.UserName = c.Locals("user").(models.User).Username + + err := note.DeleteNote() + + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.ErrBadRequest) + } + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "detail": "Note deleted successfully", + }) +} diff --git a/vitty-backend-api/go.mod b/vitty-backend-api/go.mod index 0836a17..baa69a0 100644 --- a/vitty-backend-api/go.mod +++ b/vitty-backend-api/go.mod @@ -6,13 +6,17 @@ require ( firebase.google.com/go v3.13.0+incompatible github.com/gofiber/fiber/v2 v2.46.0 github.com/golang-jwt/jwt/v4 v4.5.0 + github.com/google/uuid v1.3.0 + github.com/joho/godotenv v1.5.1 github.com/labstack/echo/v4 v4.11.2 + github.com/labstack/gommon v0.4.0 github.com/urfave/cli/v2 v2.25.6 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 golang.org/x/oauth2 v0.11.0 google.golang.org/api v0.138.0 gorm.io/driver/postgres v1.5.2 gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55 + gorm.io/plugin/soft_delete v1.2.1 ) require ( @@ -30,7 +34,6 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/s2a-go v0.1.5 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -39,7 +42,6 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/klauspost/compress v1.16.3 // indirect - github.com/labstack/gommon v0.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect diff --git a/vitty-backend-api/go.sum b/vitty-backend-api/go.sum index f68de46..b72dc6e 100644 --- a/vitty-backend-api/go.sum +++ b/vitty-backend-api/go.sum @@ -96,8 +96,12 @@ github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 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.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/labstack/echo/v4 v4.11.2 h1:T+cTLQxWCDfqDEoydYm5kCobjmHwOwcv4OJAPHilmdE= @@ -113,6 +117,8 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.3 h1:j7a/xn1U6TKA/PHHxqZuzh64CdtRc7rU9M+AvkOl5bA= +github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= @@ -301,7 +307,13 @@ 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/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/driver/sqlite v1.1.3 h1:BYfdVuZB5He/u9dt4qDpZqiqDJ6KhPqs5QUqsr/Eeuc= +gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c= +gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55 h1:sC1Xj4TYrLqg1n3AN10w871An7wJM0gzgcm8jkIkECQ= gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/plugin/soft_delete v1.2.1 h1:qx9D/c4Xu6w5KT8LviX8DgLcB9hkKl6JC9f44Tj7cGU= +gorm.io/plugin/soft_delete v1.2.1/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/vitty-backend-api/internal/models/initialize.go b/vitty-backend-api/internal/models/initialize.go index 61f7665..79130e3 100644 --- a/vitty-backend-api/internal/models/initialize.go +++ b/vitty-backend-api/internal/models/initialize.go @@ -11,6 +11,7 @@ func InitializeModels() { "User": &User{}, "Timetable": &Timetable{}, "Friend Requests": &FriendRequest{}, + "Notes": &Notes{}, "Courses": &Courses{}, } diff --git a/vitty-backend-api/internal/models/notes.go b/vitty-backend-api/internal/models/notes.go new file mode 100644 index 0000000..cc4d1d3 --- /dev/null +++ b/vitty-backend-api/internal/models/notes.go @@ -0,0 +1,41 @@ +package models + +import ( + "github.com/GDGVIT/vitty-backend/vitty-backend-api/internal/database" + "gorm.io/gorm" +) + +type Notes struct { + NoteID string `json:"note_id,omitempty" gorm:"unique"` + NoteName string `json:"note_name" gorm:"primaryKey"` + UserName string `json:"user_name" gorm:"primaryKey"` + CourseID string `json:"course_id"` + NoteContent string `json:"note_content"` + User User `gorm:"foreignKey:UserName;references:Username;constraint:OnDelete:CASCADE"` + Courses Courses ` gorm:"foreignKey:CourseID;references:CourseId;constraint:OnDelete:CASCADE"` +} + +func (n *Notes) SaveNote() error { + result := database.DB.Save(n) + + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + + return result.Error +} + +func (n *Notes) DeleteNote() error { + result := database.DB.Where("user_name = ?", n.UserName).Delete(&n) + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + + return result.Error +} + +func (n *Notes) GetNotesByCourseId() (error, []Notes) { + var notes []Notes + err := database.DB.Where(n).Preload("Courses").Find(¬es).Error + return err, notes +} diff --git a/vitty-backend-api/internal/utils/users.go b/vitty-backend-api/internal/utils/users.go index fab413f..5cc6457 100644 --- a/vitty-backend-api/internal/utils/users.go +++ b/vitty-backend-api/internal/utils/users.go @@ -1,8 +1,11 @@ package utils import ( + "strings" + "github.com/GDGVIT/vitty-backend/vitty-backend-api/internal/database" "github.com/GDGVIT/vitty-backend/vitty-backend-api/internal/models" + "github.com/google/uuid" ) func CheckUserExists(username string) bool { @@ -58,3 +61,10 @@ func ValidateUsername(username string) (bool, string) { return true, "" } + +func UUIDWithPrefix(prefix string) string { + id := uuid.New().String() + id = prefix + "_" + id + id = strings.ReplaceAll(id, "-", "") + return id +}