From 774383ec3f48717ad31d921da809aeb5a01021c8 Mon Sep 17 00:00:00 2001 From: TheSilentSage Date: Tue, 11 Jun 2024 19:26:21 +0530 Subject: [PATCH 1/2] fix: merged slot for lab while fetching timetable --- .../internal/models/timetable.go | 63 ++++++++++++------- .../internal/utils/timetableDetection.go | 6 -- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/vitty-backend-api/internal/models/timetable.go b/vitty-backend-api/internal/models/timetable.go index 62d04f1..deeb9a9 100644 --- a/vitty-backend-api/internal/models/timetable.go +++ b/vitty-backend-api/internal/models/timetable.go @@ -29,10 +29,12 @@ func (t Timetable) GetDaySlots(day time.Weekday) map[string][]Slot { resp := make(map[string][]Slot) var data []Slot daySlots := DailySlots[day.String()] + labSlot := "" var err error // Theory slots for _, slot := range t.Slots { + if slot.Type == "Theory" && slices.Contains(daySlots["Theory"], slot.Slot) { index := slices.Index(daySlots["Theory"], slot.Slot) slot.StartTime, err = time.ParseInLocation(STD_REF_TIME, TheoryTimings[index].StartTime, time.Local) @@ -51,19 +53,27 @@ func (t Timetable) GetDaySlots(day time.Weekday) map[string][]Slot { data = append(data, slot) } else if slot.Type == "Lab" && slices.Contains(daySlots["Lab"], slot.Slot) { index := slices.Index(daySlots["Lab"], slot.Slot) - slot.StartTime, err = time.ParseInLocation(STD_REF_TIME, LabTimings[index].StartTime, time.Local) - if err != nil { - log.Println("Error parsing time: ", err) - return nil - } - slot.EndTime, err = time.ParseInLocation(STD_REF_TIME, LabTimings[index].EndTime, time.Local) + if labSlot == "" { + labSlot += slot.Slot + "+" + continue + } else { + slot.StartTime, err = time.ParseInLocation(STD_REF_TIME, LabTimings[index-1].StartTime, time.Local) + if err != nil { + log.Println("Error parsing time: ", err) + return nil + } - if err != nil { - log.Println("Error parsing time: ", err) - return nil + slot.EndTime, err = time.ParseInLocation(STD_REF_TIME, LabTimings[index].EndTime, time.Local) + if err != nil { + log.Println("Error parsing time: ", err) + return nil + } + slot.Slot = labSlot + slot.Slot + labSlot = "" + data = append(data, slot) } - data = append(data, slot) + } } resp[day.String()] = data @@ -72,9 +82,11 @@ func (t Timetable) GetDaySlots(day time.Weekday) map[string][]Slot { func (t Timetable) GetDaywiseTimetable() map[string][]Slot { resp := make(map[string][]Slot) + labSlot := "" for _, slot := range t.Slots { for day, value := range DailySlots { + if slices.Contains(value["Theory"], slot.Slot) { index := slices.Index(value["Theory"], slot.Slot) var err error @@ -91,18 +103,27 @@ func (t Timetable) GetDaywiseTimetable() map[string][]Slot { resp[day] = append(resp[day], slot) } else if slices.Contains(value["Lab"], slot.Slot) { index := slices.Index(value["Lab"], slot.Slot) - var err error - slot.StartTime, err = time.ParseInLocation(STD_REF_TIME, LabTimings[index].StartTime, time.Local) - if err != nil { - log.Println("Error parsing time: ", err) - return nil - } - slot.EndTime, err = time.ParseInLocation(STD_REF_TIME, LabTimings[index].EndTime, time.Local) - if err != nil { - log.Println("Error parsing time: ", err) - return nil + + if labSlot == "" { + labSlot += slot.Slot + "+" + continue + } else { + var err error + slot.StartTime, err = time.ParseInLocation(STD_REF_TIME, LabTimings[index-1].StartTime, time.Local) + if err != nil { + log.Println("Error parsing time: ", err) + return nil + } + slot.EndTime, err = time.ParseInLocation(STD_REF_TIME, LabTimings[index].EndTime, time.Local) + if err != nil { + log.Println("Error parsing time: ", err) + return nil + } + + slot.Slot = labSlot + slot.Slot + labSlot = "" + resp[day] = append(resp[day], slot) } - resp[day] = append(resp[day], slot) } } } diff --git a/vitty-backend-api/internal/utils/timetableDetection.go b/vitty-backend-api/internal/utils/timetableDetection.go index 19ad91d..17bd513 100644 --- a/vitty-backend-api/internal/utils/timetableDetection.go +++ b/vitty-backend-api/internal/utils/timetableDetection.go @@ -48,12 +48,6 @@ func DetectTimetable(text string) ([]TimetableSlotV1, error) { } if len(Slots) == 0 { - return Slots, nil - } - - var err error - - if err != nil { return Slots, errors.New("error in detecting timetable") } From 26137224df912716cf098b2dfc6647c0f08dcb48 Mon Sep 17 00:00:00 2001 From: TheSilentSage Date: Mon, 30 Dec 2024 22:24:43 +0530 Subject: [PATCH 2/2] feat: users can form circles and find leisure timings --- vitty-backend-api/api/serializers/circles.go | 52 ++ vitty-backend-api/api/v2/circleHandler.go | 534 ++++++++++++++++++ vitty-backend-api/api/v2/initialize.go | 1 + vitty-backend-api/go.mod | 3 +- vitty-backend-api/go.sum | 2 + .../internal/database/initialize.go | 7 +- .../internal/models/circleRequest.go | 88 +++ vitty-backend-api/internal/models/circles.go | 161 ++++++ .../internal/models/initialize.go | 3 + .../internal/models/user-circles.go | 137 +++++ vitty-backend-api/internal/utils/users.go | 10 + 11 files changed, 995 insertions(+), 3 deletions(-) create mode 100644 vitty-backend-api/api/serializers/circles.go create mode 100644 vitty-backend-api/api/v2/circleHandler.go create mode 100644 vitty-backend-api/internal/models/circleRequest.go create mode 100644 vitty-backend-api/internal/models/circles.go create mode 100644 vitty-backend-api/internal/models/user-circles.go diff --git a/vitty-backend-api/api/serializers/circles.go b/vitty-backend-api/api/serializers/circles.go new file mode 100644 index 0000000..6df33ba --- /dev/null +++ b/vitty-backend-api/api/serializers/circles.go @@ -0,0 +1,52 @@ +package serializers + +import "github.com/GDGVIT/vitty-backend/vitty-backend-api/internal/models" + +func CirclesListSerializer(ucj []models.UsersCirclesJoin) []map[string]interface{} { + var result []map[string]interface{} + + for _, userCircle := range ucj { + + out := map[string]interface{}{ + "circle_id": userCircle.CID, + "circle_role": userCircle.CircleRole, + "circle_name": userCircle.Circles.CircleName, + } + result = append(result, out) + } + + return result + +} + +func CircleRequestsSerializer(circleRequests []models.CircleRequest) []map[string]interface{} { + var result []map[string]interface{} + + for _, circleRequest := range circleRequests { + out := map[string]interface{}{ + "from_username": circleRequest.FromUsername, + "to_username": circleRequest.ToUsername, + "circle_id": circleRequest.CID, + "circle_name": circleRequest.Circles.CircleName, + } + result = append(result, out) + } + + return result +} + +func UsersListCircleSerializer(users []models.User) []map[string]interface{} { + var result []map[string]interface{} + + for _, user := range users { + out := map[string]interface{}{ + "username": user.Username, + "name": user.Name, + "picture": user.Picture, + "email": user.Email, + } + result = append(result, out) + } + + return result +} diff --git a/vitty-backend-api/api/v2/circleHandler.go b/vitty-backend-api/api/v2/circleHandler.go new file mode 100644 index 0000000..76336aa --- /dev/null +++ b/vitty-backend-api/api/v2/circleHandler.go @@ -0,0 +1,534 @@ +package v2 + +import ( + "errors" + "log" + + "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" + "gorm.io/gorm" +) + +func circleHandler(api fiber.Router) { + group := api.Group("/circles") + group.Use(middleware.JWTAuthMiddleware) + group.Get("/", getCircles) + group.Get("/:circleId", getUsersOfCircle) + group.Get("/leisure/:circleId", getLeisureTime) + group.Get("/requests/received", getReceivedCircleRequests) + group.Get("/requests/sent", getSentCircleRequests) + group.Post("/create/:circleName", createCircle) + group.Post("/sendRequest/:circleId/:username", sendCircleRequestToUser) + group.Post("/acceptRequest/:circleId", acceptCircleRequest) + group.Post("/declineRequest/:circleId", declineCircleRequest) + group.Patch("/", updateCircleName) + group.Delete("/:circleId", deleteCircle) + group.Delete("/remove/:circleId/:username", removeUserFromCircle) + group.Delete("/leave/:circleId", leaveCircle) + group.Delete("/unsendRequest/:circleId/:username", unsendCircleRequestToUser) +} + +func getCircles(c *fiber.Ctx) error { + var user_circle models.UsersCirclesJoin + + username := c.Locals("user").(models.User).Username + + user_circle.Uname = username + + err, circles := user_circle.GetCirclesofUser() + + if err != nil { + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "detail": "circles not fetched", + }) + } + + if len(circles) == 0 { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "data": "You are not part of any circle", + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "data": serializers.CirclesListSerializer(circles), + }) +} + +func getUsersOfCircle(c *fiber.Ctx) error { + var userCircle models.UsersCirclesJoin + + circleId := c.Params("circleId") + username := c.Locals("user").(models.User).Username + + userCircle.CID = circleId + userCircle.Uname = username + err, isPartOfCircle := userCircle.IsUserOfCircle() + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "invalid circle id", + }) + } + + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "user authorization could not be verified", + }) + } + + if isPartOfCircle { + userCircle.Uname = "" + err, users := userCircle.GetUsersofCircle() + + if err == nil { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "data": serializers.UsersListCircleSerializer(users), + }) + } + } + + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "users list fetch failed", + }) +} + +func getLeisureTime(c *fiber.Ctx) error { + var circle models.Circles + + circleId := c.Params("circleId") + + circle.CircleId = circleId + + err, circleSlotMap := circle.GetCircleSlots() + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "invalid circle id", + }) + } + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "detail": "leisure times couldn't be fetched", + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "data": circleSlotMap, + }) + +} + +func getReceivedCircleRequests(c *fiber.Ctx) error { + var circleRequest models.CircleRequest + + username := c.Locals("user").(models.User).Username + + err, circleRequests := circleRequest.GetReceivedRequests(username) + + if err != nil { + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "detail": "requests not able to be fetched", + }) + } + + if len(circleRequests) == 0 { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "data": "your inbox is empty", + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "data": serializers.CircleRequestsSerializer(circleRequests), + }) + +} + +func getSentCircleRequests(c *fiber.Ctx) error { + var circleRequest models.CircleRequest + + username := c.Locals("user").(models.User).Username + + err, circleRequests := circleRequest.GetSentRequests(username) + + if err != nil { + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "detail": "requests not able to be fetched", + }) + } + + if len(circleRequests) == 0 { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "data": "your outbox is empty", + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "data": serializers.CircleRequestsSerializer(circleRequests), + }) + +} + +func createCircle(c *fiber.Ctx) error { + var circle models.Circles + var user_circle models.UsersCirclesJoin + + circleName := c.Params("circleName") + username := c.Locals("user").(models.User).Username + + if circleName == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.ErrBadRequest) + } else { + circle.CircleId = utils.UUIDWithPrefix("circle") + circle.CircleName = circleName + circle.Uname = username + } + + err := circle.CreateCircle() + if err != nil { + if errors.Is(err, gorm.ErrDuplicatedKey) { + return c.Status(fiber.StatusConflict).JSON(fiber.Map{ + "detail": "circle name already exists", + }) + } + + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "detail": "circle creation failed", + }) + } + + request_user := c.Locals("user").(models.User) + + user_circle.CID = circle.CircleId + user_circle.CircleRole = "admin" + user_circle.Uname = request_user.Username + + err = user_circle.AddUserToCircle() + + if err != nil { + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "detail": "user error circle creation failed", + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "detail": "circle created successfully", + }) +} + +func sendCircleRequestToUser(c *fiber.Ctx) error { + var circleRequest models.CircleRequest + var usersCirclesJoin models.UsersCirclesJoin + + circleId := c.Params("circleId") + from_user := c.Locals("user").(models.User).Username + to_user := c.Params("username") + + usersCirclesJoin.CID = circleId + usersCirclesJoin.Uname = from_user + + err, isCircleAdmin := usersCirclesJoin.IsUserCircleAdmin() + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "detail": "circle does not exist", + }) + } + + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "circle search failed", + }) + } + + if !isCircleAdmin { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "detail": "you cannot send requests", + }) + } + + circleRequest.CID = circleId + circleRequest.FromUsername = from_user + circleRequest.ToUsername = to_user + + err = circleRequest.CreateRequest() + if err != nil { + if errors.Is(err, gorm.ErrDuplicatedKey) { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "detail": "request pending", + }) + } + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "detail": "request not sent", + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "detail": "request sent successfully", + }) +} + +func acceptCircleRequest(c *fiber.Ctx) error { + var circleRequest models.CircleRequest + + circleId := c.Params("circleId") + to_user := c.Locals("user").(models.User).Username + + circleRequest.CID = circleId + circleRequest.ToUsername = to_user + + err := circleRequest.AcceptRequest() + + if err != nil { + + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "detail": "you do not have any requests to accept", + }) + } + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "detail": "request not accepted", + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "detail": "request accepted successfully", + }) +} + +func updateCircleName(c *fiber.Ctx) error { + var circle models.Circles + + err := c.BodyParser(&circle) + if err != nil { + log.Println(err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.ErrBadRequest) + } + + err = circle.UpdateCircleName(circle.CircleName) + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Status(fiber.StatusBadRequest).JSON(fiber.ErrBadRequest) + } + + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "detail": "circle name not updated", + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "detail": "circle name updated", + }) +} + +func deleteCircle(c *fiber.Ctx) error { + var circle models.Circles + + circleId := c.Params("circleId") + circle.CircleId = circleId + + err := circle.DeleteCircle() + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Status(fiber.StatusBadRequest).JSON(fiber.ErrBadRequest) + } + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "circle was not deleted", + }) + } + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "detail": "circle deleted successfully", + }) +} + +func removeUserFromCircle(c *fiber.Ctx) error { + var ucj models.UsersCirclesJoin + + circleId := c.Params("circleId") + username := c.Params("username") + requestUser := c.Locals("user").(models.User) + + ucj.CID = circleId + ucj.Uname = requestUser.Username + + err, isCircleAdmin := ucj.IsUserCircleAdmin() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "detail": "circle does not exist", + }) + } + + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "circle search failed", + }) + } + + if !isCircleAdmin { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "detail": "you cannot remove user", + }) + } + + ucj.Uname = username + err = ucj.DeleteUserFromCircle() + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Status(fiber.StatusBadRequest).JSON(fiber.ErrBadRequest) + } + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "user not removed from the circle", + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "detail": "user removed from circle", + }) +} + +func leaveCircle(c *fiber.Ctx) error { + var ucj models.UsersCirclesJoin + + circleId := c.Params("circleId") + username := c.Locals("user").(models.User).Username + + ucj.CID = circleId + ucj.Uname = username + + err, isCircleAdmin := ucj.IsUserCircleAdmin() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "detail": "circle does not exist", + }) + } + + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "circle search failed", + }) + } + + err = ucj.DeleteUserFromCircle() + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Status(fiber.StatusBadRequest).JSON(fiber.ErrBadRequest) + } + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "you didn't leave the circle", + }) + } + + if isCircleAdmin { + ucj.Uname = "" + err, users := ucj.GetUsersofCircle() + if err != nil { + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "you didn't leave the circle;admin error", + }) + } + + if len(users) != 0 { + var circle models.Circles + circle.CircleId = circleId + err = circle.UpdateCircleUsername(users[0].Username) + if err != nil { + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "you didn't leave the circle;admin update error", + }) + } + ucj.Uname = users[0].Username + err = ucj.UpdateUserCircleRole("admin") + if err != nil { + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "you didn't leave the circle;admin update error", + }) + } + } + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "detail": "you left the circle ", + }) +} + +func unsendCircleRequestToUser(c *fiber.Ctx) error { + var circleRequest models.CircleRequest + + circleId := c.Params("circleId") + from_user := c.Locals("user").(models.User).Username + to_user := c.Params("username") + + circleRequest.CID = circleId + circleRequest.FromUsername = from_user + circleRequest.ToUsername = to_user + + err := circleRequest.DeleteRequest() + + if err != nil { + + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Status(fiber.StatusBadRequest).JSON(fiber.ErrBadRequest) + } + + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "detail": "request could not be unsent", + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "detail": "request unsent successfully", + }) +} + +func declineCircleRequest(c *fiber.Ctx) error { + var circleRequest models.CircleRequest + + circleId := c.Params("circleId") + to_user := c.Locals("user").(models.User).Username + + circleRequest.CID = circleId + circleRequest.ToUsername = to_user + + err := circleRequest.DeclineRequest() + if err != nil { + + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Status(fiber.StatusBadRequest).JSON(fiber.ErrBadRequest) + } + + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "detail": "request not declined", + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "detail": "request declined successfully", + }) +} diff --git a/vitty-backend-api/api/v2/initialize.go b/vitty-backend-api/api/v2/initialize.go index 256c0ab..065cb46 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) + circleHandler(group) } diff --git a/vitty-backend-api/go.mod b/vitty-backend-api/go.mod index 0836a17..385c87b 100644 --- a/vitty-backend-api/go.mod +++ b/vitty-backend-api/go.mod @@ -6,6 +6,8 @@ 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/urfave/cli/v2 v2.25.6 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 @@ -30,7 +32,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 diff --git a/vitty-backend-api/go.sum b/vitty-backend-api/go.sum index f68de46..14f92ed 100644 --- a/vitty-backend-api/go.sum +++ b/vitty-backend-api/go.sum @@ -98,6 +98,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 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= diff --git a/vitty-backend-api/internal/database/initialize.go b/vitty-backend-api/internal/database/initialize.go index 4a4d834..1ff54cd 100644 --- a/vitty-backend-api/internal/database/initialize.go +++ b/vitty-backend-api/internal/database/initialize.go @@ -15,10 +15,13 @@ func Connect(debug string, dbUrls string) { if debug == "true" { DB, err = gorm.Open(postgres.Open(dbUrls), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Info), + Logger: logger.Default.LogMode(logger.Info), + TranslateError: true, }) } else { - DB, err = gorm.Open(postgres.Open(dbUrls), &gorm.Config{}) + DB, err = gorm.Open(postgres.Open(dbUrls), &gorm.Config{ + TranslateError: true, + }) } if err != nil { diff --git a/vitty-backend-api/internal/models/circleRequest.go b/vitty-backend-api/internal/models/circleRequest.go new file mode 100644 index 0000000..6588e23 --- /dev/null +++ b/vitty-backend-api/internal/models/circleRequest.go @@ -0,0 +1,88 @@ +package models + +import ( + "github.com/GDGVIT/vitty-backend/vitty-backend-api/internal/database" + "gorm.io/gorm" +) + +type CircleRequest struct { + FromUsername string `gorm:"primaryKey"` + ToUsername string `gorm:"primaryKey"` + CID string `gorm:"primaryKey"` + From User `gorm:"constraint:OnDelete:CASCADE;foreignKey:FromUsername;references:Username"` + To User `gorm:"constraint:OnDelete:CASCADE;foreignKey:ToUsername;references:Username"` + Circles Circles `gorm:"constraint:OnDelete:CASCADE;foreignKey:CID;references:CircleId;constraint:OnDelete:CASCADE"` +} + +func (cr *CircleRequest) CreateRequest() error { + err := database.DB.Create(&cr).Error + return err +} + +func (cr *CircleRequest) AcceptRequest() error { + var userCircle UsersCirclesJoin + + userCircle.CID = cr.CID + userCircle.CircleRole = "member" + userCircle.Uname = cr.ToUsername + + err, circleRequest := cr.GetRequestByCircleId() + + if circleRequest.ToUsername == "" { + return gorm.ErrRecordNotFound + } else if err != nil { + return err + } + + err = userCircle.AddUserToCircle() + + if err != nil { + return err + } + + err = cr.DeleteRequest() + + if err != nil { + return err + } + + return nil + +} + +func (cr *CircleRequest) DeclineRequest() error { + result := database.DB.Where(cr).Delete(&CircleRequest{}) + + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + + return result.Error +} + +func (cr *CircleRequest) GetRequestByCircleId() (error, CircleRequest) { + var circleRequest CircleRequest + err := database.DB.Where(cr).Find(&circleRequest).Error + return err, circleRequest +} + +func (cr *CircleRequest) GetReceivedRequests(username string) (error, []CircleRequest) { + var circleRequests []CircleRequest + err := database.DB.Where("to_username = ?", username).Preload("Circles").Find(&circleRequests).Error + return err, circleRequests +} + +func (cr *CircleRequest) GetSentRequests(username string) (error, []CircleRequest) { + var circleRequests []CircleRequest + err := database.DB.Where("from_username = ?", username).Preload("Circles").Find(&circleRequests).Error + return err, circleRequests +} + +func (cr *CircleRequest) DeleteRequest() error { + result := database.DB.Where(&cr).Delete(&CircleRequest{}) + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + + return result.Error +} diff --git a/vitty-backend-api/internal/models/circles.go b/vitty-backend-api/internal/models/circles.go new file mode 100644 index 0000000..ff8763f --- /dev/null +++ b/vitty-backend-api/internal/models/circles.go @@ -0,0 +1,161 @@ +package models + +import ( + "encoding/json" + "log" + "strings" + "time" + + "github.com/GDGVIT/vitty-backend/vitty-backend-api/internal/database" + "gorm.io/gorm" +) + +type Circles struct { + CircleId string `json:"circle_id" gorm:"unique"` + CircleName string `json:"circle_name" gorm:"primaryKey"` + Uname string `json:"omitempty" gorm:"primaryKey"` + CircleSlots string + User User `gorm:"foreignKey:Uname;references:Username;"` +} + +func (c *Circles) CreateCircle() error { + err := database.DB.Create(c).Error + return err +} + +func (c *Circles) GetCircleByCircleId() error { + err := database.DB.Where(c).First(&c).Error + return err +} + +func (c *Circles) GetCircleSlots() (error, map[string]string) { + err := database.DB.Where(c).First(&c).Error + return err, jsonToCircleSlots(c.CircleSlots) +} + +func (c *Circles) UpdateCircleName(circleName string) error { + result := database.DB.Model(&Circles{}).Where("circle_id like ?", c.CircleId).Update("circle_name", circleName) + + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + + return result.Error +} + +func (c *Circles) UpdateCircleUsername(username string) error { + err := database.DB.Model(&Circles{}).Where(c).Update("uname", username).Error + return err +} + +func (c *Circles) updateCircleSlots(circleSlotMap map[string]string) error { + jsonString := circleSlotsToJson(circleSlotMap) + err := database.DB.Model(&Circles{}).Where(c).Update("circle_slots", jsonString).Error + return err +} + +func (c *Circles) DeleteCircle() error { + result := database.DB.Where(&c).Delete(&Circles{}) + + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + + return result.Error +} + +func (c *Circles) ComputeCircleSlots(user User, recompute bool) error { + + resultMap := make(map[string]string) + dayWiseSlots := user.GetTimeTable().GetDaywiseTimetable() + + var err error + var circleSlotMap map[string]string + + if !recompute { + err, circleSlotMap = c.GetCircleSlots() + + if err != nil { + return err + } + } + + for day := time.Monday; day <= time.Friday; day++ { + var result []string + var dupEightSlot bool + + referenceTable := make(map[string]struct{}) + + for _, slot := range dayWiseSlots[day.String()] { + referenceTime := slot.StartTime.Format("15:04") + referenceTable[referenceTime] = struct{}{} + } + + timings := append(TheoryTimings, LabTimings...) + + if c.CircleSlots == "" { + for _, timing := range timings { + timeOfTiming := strings.Split(timing.StartTime, "T")[1] + if _, found := referenceTable[timeOfTiming]; !found { + if strings.Contains(timeOfTiming, "08:00") { + if dupEightSlot { + continue + } + dupEightSlot = true + } + + result = append(result, timeOfTiming) + } + } + + } else { + + timings := strings.Split(circleSlotMap[day.String()], ",") + + for _, timing := range timings { + if _, found := referenceTable[timing]; !found { + if strings.Contains(timing, "08:00") { + if dupEightSlot { + continue + } + dupEightSlot = true + } + + result = append(result, timing) + } + } + + } + resultMap[day.String()] = strings.Join(result, ",") + + } + + c.updateCircleSlots(resultMap) + + return nil + +} + +// Helper Functions + +func circleSlotsToJson(circleSlotMap map[string]string) string { + jsonBytes, err := json.Marshal(circleSlotMap) + + if err != nil { + log.Println(err) + } + return string(jsonBytes) +} + +func jsonToCircleSlots(jsonString string) map[string]string { + resultMap := make(map[string]string) + if jsonString == "" { + return resultMap + } + + if err := json.Unmarshal([]byte(jsonString), &resultMap); err != nil { + log.Fatal(err) + } + + return resultMap +} diff --git a/vitty-backend-api/internal/models/initialize.go b/vitty-backend-api/internal/models/initialize.go index 4181fd3..b5d0d44 100644 --- a/vitty-backend-api/internal/models/initialize.go +++ b/vitty-backend-api/internal/models/initialize.go @@ -11,6 +11,9 @@ func InitializeModels() { "User": &User{}, "Timetable": &Timetable{}, "Friend Requests": &FriendRequest{}, + "Circles": &Circles{}, + "UserCirclesJoin": &UsersCirclesJoin{}, + "CircleRequests": &CircleRequest{}, } for name, model := range MODELS { diff --git a/vitty-backend-api/internal/models/user-circles.go b/vitty-backend-api/internal/models/user-circles.go new file mode 100644 index 0000000..b7b85ab --- /dev/null +++ b/vitty-backend-api/internal/models/user-circles.go @@ -0,0 +1,137 @@ +package models + +import ( + "log" + + "github.com/GDGVIT/vitty-backend/vitty-backend-api/internal/database" + "gorm.io/gorm" +) + +type UsersCirclesJoin struct { + CID string `gorm:"primaryKey"` + Uname string `gorm:"primaryKey"` + CircleRole string + Circles Circles `gorm:"foreignKey:CID;references:CircleId;constraint:OnDelete:CASCADE"` + User User `gorm:"foreignKey:Uname;references:Username"` +} + +func (ucj *UsersCirclesJoin) AddUserToCircle() error { + err := database.DB.Create(ucj).Error + if err != nil { + return err + } + + var user User + user.Username = ucj.Uname + + err = ucj.Circles.ComputeCircleSlots(user, false) + + return err +} + +func (ucj *UsersCirclesJoin) UpdateUserCircleRole(role string) error { + err := database.DB.Model(&UsersCirclesJoin{}).Where(&ucj).Update("circle_role", role).Error + return err +} + +func (ucj *UsersCirclesJoin) DeleteUserFromCircle() error { + + err := database.DB.Delete(ucj).Error + + if err != nil { + return err + } + + ucj.Uname = "" + err, users := ucj.GetUsersofCircle() + + if err != nil { + return err + } + + if len(users) == 0 { + var circle Circles + circle.CircleId = ucj.CID + + err := circle.DeleteCircle() + return err + } else { + for _, user := range users { + ucj.Circles.CircleSlots = "" + ucj.Circles.CircleId = ucj.CID + err = ucj.Circles.ComputeCircleSlots(user, true) + if err != nil { + return err + } + } + } + + return nil +} + +func (ucj *UsersCirclesJoin) GetCircleofUserByCID() (error, UsersCirclesJoin) { + var circleUser UsersCirclesJoin + + result := database.DB.Where(&ucj).Preload("Circles").Find(&circleUser) + log.Println(result.Error) + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound, circleUser + } + return result.Error, circleUser +} + +func (ucj *UsersCirclesJoin) GetCirclesofUser() (error, []UsersCirclesJoin) { + var circleUsers []UsersCirclesJoin + + err := database.DB.Where(&ucj).Preload("Circles").Find(&circleUsers).Error + if err != nil { + return err, nil + } + + return err, circleUsers +} + +func (ucj *UsersCirclesJoin) GetUsersofCircle() (error, []User) { + var users []User + var circleUsers []UsersCirclesJoin + + err := database.DB.Where(&ucj).Preload("User").Find(&circleUsers).Error + if err != nil { + return err, nil + } + + for _, circleUser := range circleUsers { + users = append(users, circleUser.User) + } + + return err, users +} + +func (ucj *UsersCirclesJoin) IsUserCircleAdmin() (error, bool) { + + err, userCircle := ucj.GetCircleofUserByCID() + + if err != nil { + return err, false + } + + if userCircle.CircleRole == "admin" { + return nil, true + } + + return nil, false +} + +func (ucj *UsersCirclesJoin) IsUserOfCircle() (error, bool) { + + err, userCircle := ucj.GetCircleofUserByCID() + if err != nil { + return err, false + } + + if userCircle.Uname == ucj.Uname { + return nil, true + } + + return nil, false +} 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 +}