diff --git a/backend/auth/locals.go b/backend/auth/locals.go deleted file mode 100644 index 8c1955655..000000000 --- a/backend/auth/locals.go +++ /dev/null @@ -1,52 +0,0 @@ -package auth - -import ( - "fmt" - - "github.com/GenerateNU/sac/backend/utilities" - "github.com/gofiber/fiber/v2" - "github.com/google/uuid" -) - -type localsKey byte - -const ( - claimsKey localsKey = 0 - userIDKey localsKey = 1 -) - -func CustomClaimsFrom(c *fiber.Ctx) (*CustomClaims, error) { - rawClaims := c.Locals(claimsKey) - if rawClaims == nil { - return nil, utilities.Forbidden() - } - - claims, ok := rawClaims.(*CustomClaims) - if !ok { - return nil, fmt.Errorf("claims are not of type CustomClaims. got: %T", rawClaims) - } - - return claims, nil -} - -func SetClaims(c *fiber.Ctx, claims *CustomClaims) { - c.Locals(claimsKey, claims) -} - -func UserIDFrom(c *fiber.Ctx) (*uuid.UUID, error) { - userID := c.Locals(userIDKey) - if userID == nil { - return nil, utilities.Forbidden() - } - - id, ok := userID.(*uuid.UUID) - if !ok { - return nil, fmt.Errorf("userID is not of type uuid.UUID. got: %T", userID) - } - - return id, nil -} - -func SetUserID(c *fiber.Ctx, id *uuid.UUID) { - c.Locals(userIDKey, id) -} diff --git a/backend/constants/db.go b/backend/constants/db.go index 74f80d992..eb6d046ed 100644 --- a/backend/constants/db.go +++ b/backend/constants/db.go @@ -1,6 +1,9 @@ package constants +import "time" + const ( - MAX_IDLE_CONNECTIONS int = 10 - MAX_OPEN_CONNECTIONS int = 100 + MAX_IDLE_CONNECTIONS int = 10 + MAX_OPEN_CONNECTIONS int = 100 + DB_TIMEOUT time.Duration = 200 * time.Millisecond ) diff --git a/backend/database/super.go b/backend/database/super.go index 403f79ff6..6255b640c 100644 --- a/backend/database/super.go +++ b/backend/database/super.go @@ -32,14 +32,10 @@ func SuperUser(superUserSettings config.SuperUserSettings) (*models.User, error) func SuperClub() models.Club { return models.Club{ - Name: "SAC", - Preview: "SAC", - Description: "SAC", - NumMembers: 0, - IsRecruiting: true, - RecruitmentCycle: models.Always, - RecruitmentType: models.Application, - ApplicationLink: "https://generatenu.com/apply", - Logo: "https://aws.amazon.com/s3", + Name: "SAC", + Preview: "SAC", + Description: "SAC", + NumMembers: 0, + Logo: "https://aws.amazon.com/s3", } } diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 38a673141..598efc4e3 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -180,7 +180,7 @@ const docTemplate = `{ }, "/auth/refresh": { "post": { - "description": "Refreshes a user's access token", + "description": "Refreshes a user's access token and returns a new pair of tokens", "consumes": [ "application/json" ], @@ -190,7 +190,7 @@ const docTemplate = `{ "tags": [ "auth" ], - "summary": "Refreshes a user's access token", + "summary": "Refreshes a user's access token and returns a new pair of tokens", "operationId": "refresh-user", "responses": { "200": { @@ -1033,17 +1033,17 @@ const docTemplate = `{ } } }, - "/clubs/{clubID}/contacts/": { + "/clubs/{clubID}/events/": { "get": { - "description": "Retrieves all contacts associated with a club", + "description": "Retrieves all events associated with a club", "produces": [ "application/json" ], "tags": [ - "club-contact" + "club-event" ], - "summary": "Retrieve all contacts for a club", - "operationId": "get-contacts-by-club", + "summary": "Retrieve all events for a club", + "operationId": "get-events-by-club", "parameters": [ { "type": "string", @@ -1051,6 +1051,18 @@ const docTemplate = `{ "name": "clubID", "in": "path", "required": true + }, + { + "type": "integer", + "description": "Limit", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page", + "name": "page", + "in": "query" } ], "responses": { @@ -1059,7 +1071,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.Contact" + "$ref": "#/definitions/models.Event" } } }, @@ -1076,20 +1088,19 @@ const docTemplate = `{ "schema": {} } } - }, - "put": { - "description": "Creates a contact", - "consumes": [ - "application/json" - ], + } + }, + "/clubs/{clubID}/followers/": { + "get": { + "description": "Retrieves all followers associated with a club", "produces": [ "application/json" ], "tags": [ - "club-contact" + "club-follower" ], - "summary": "Creates a contact", - "operationId": "put-contact", + "summary": "Retrieve all followers for a club", + "operationId": "get-followers-by-club", "parameters": [ { "type": "string", @@ -1099,30 +1110,32 @@ const docTemplate = `{ "required": true }, { - "description": "Contact Body", - "name": "contactBody", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/contacts.PutContactRequestBody" - } + "type": "integer", + "description": "Limit", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page", + "name": "page", + "in": "query" } ], "responses": { - "201": { - "description": "Created", + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/models.Contact" + "type": "array", + "items": { + "$ref": "#/definitions/models.User" + } } }, "400": { "description": "Bad Request", "schema": {} }, - "401": { - "description": "Unauthorized", - "schema": {} - }, "404": { "description": "Not Found", "schema": {} @@ -1134,17 +1147,17 @@ const docTemplate = `{ } } }, - "/clubs/{clubID}/events/": { + "/clubs/{clubID}/leadership/": { "get": { - "description": "Retrieves all events associated with a club", + "description": "Retrieves all leadership associated with a club", "produces": [ "application/json" ], "tags": [ - "club-event" + "club-leader" ], - "summary": "Retrieve all events for a club", - "operationId": "get-events-by-club", + "summary": "Retrieve all leadership for a club", + "operationId": "get-leadership-by-club", "parameters": [ { "type": "string", @@ -1152,18 +1165,6 @@ const docTemplate = `{ "name": "clubID", "in": "path", "required": true - }, - { - "type": "integer", - "description": "Limit", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "description": "Page", - "name": "page", - "in": "query" } ], "responses": { @@ -1172,7 +1173,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.Event" + "$ref": "#/definitions/models.Leader" } } }, @@ -1189,19 +1190,20 @@ const docTemplate = `{ "schema": {} } } - } - }, - "/clubs/{clubID}/followers/": { - "get": { - "description": "Retrieves all followers associated with a club", + }, + "post": { + "description": "Creates a leader associated with a club", + "consumes": [ + "multipart/form-data" + ], "produces": [ "application/json" ], "tags": [ - "club-follower" + "club-leader" ], - "summary": "Retrieve all followers for a club", - "operationId": "get-followers-by-club", + "summary": "Create a leader for a club", + "operationId": "create-leader-by-club", "parameters": [ { "type": "string", @@ -1209,34 +1211,23 @@ const docTemplate = `{ "name": "clubID", "in": "path", "required": true - }, - { - "type": "integer", - "description": "Limit", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "description": "Page", - "name": "page", - "in": "query" } ], "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.User" - } + "$ref": "#/definitions/models.Leader" } }, "400": { "description": "Bad Request", "schema": {} }, + "401": { + "description": "Unauthorized", + "schema": {} + }, "404": { "description": "Not Found", "schema": {} @@ -1248,17 +1239,17 @@ const docTemplate = `{ } } }, - "/clubs/{clubID}/members/": { + "/clubs/{clubID}/leadership/{leaderID}": { "get": { - "description": "Retrieves all members associated with a club", + "description": "Retrieves a leader associated with a club", "produces": [ "application/json" ], "tags": [ - "club-member" + "club-leader" ], - "summary": "Retrieve all members for a club", - "operationId": "get-members-by-club", + "summary": "Retrieve a leader for a club", + "operationId": "get-leader-by-club", "parameters": [ { "type": "string", @@ -1268,36 +1259,24 @@ const docTemplate = `{ "required": true }, { - "type": "integer", - "description": "Limit", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "description": "Page", - "name": "page", - "in": "query" + "type": "string", + "description": "leader ID", + "name": "leaderID", + "in": "path", + "required": true } ], "responses": { "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.User" - } + "$ref": "#/definitions/models.Leader" } }, "400": { "description": "Bad Request", "schema": {} }, - "401": { - "description": "Unauthorized", - "schema": {} - }, "404": { "description": "Not Found", "schema": {} @@ -1308,8 +1287,8 @@ const docTemplate = `{ } } }, - "post": { - "description": "Creates a new member associated with a club", + "put": { + "description": "Updates a leader associated with a club", "consumes": [ "application/json" ], @@ -1317,10 +1296,10 @@ const docTemplate = `{ "application/json" ], "tags": [ - "club-member" + "club-leader" ], - "summary": "Create a new member for a club", - "operationId": "create-member-for-club", + "summary": "Update a leader for a club", + "operationId": "update-leader-by-club", "parameters": [ { "type": "string", @@ -1331,17 +1310,17 @@ const docTemplate = `{ }, { "type": "string", - "description": "User ID", - "name": "userID", + "description": "leader ID", + "name": "leaderID", "in": "path", "required": true } ], "responses": { - "201": { - "description": "Created", + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/models.User" + "$ref": "#/definitions/models.Leader" } }, "400": { @@ -1363,15 +1342,15 @@ const docTemplate = `{ } }, "delete": { - "description": "Deletes a member associated with a club", + "description": "Delete a leader associated with a club", "produces": [ "application/json" ], "tags": [ - "club-member" + "club-leader" ], - "summary": "Delete a member from a club", - "operationId": "delete-member-from-club", + "summary": "Delete a leader for a club", + "operationId": "delete-leader-by-club", "parameters": [ { "type": "string", @@ -1382,27 +1361,20 @@ const docTemplate = `{ }, { "type": "string", - "description": "User ID", - "name": "userID", + "description": "leader ID", + "name": "leaderID", "in": "path", "required": true } ], "responses": { "204": { - "description": "No Content", - "schema": { - "$ref": "#/definitions/models.User" - } + "description": "No Content" }, "400": { "description": "Bad Request", "schema": {} }, - "401": { - "description": "Unauthorized", - "schema": {} - }, "404": { "description": "Not Found", "schema": {} @@ -1412,11 +1384,9 @@ const docTemplate = `{ "schema": {} } } - } - }, - "/clubs/{clubID}/poc/": { - "post": { - "description": "Creates a point of contact associated with a club", + }, + "patch": { + "description": "Updates a leader photo associated with a club", "consumes": [ "multipart/form-data" ], @@ -1424,10 +1394,10 @@ const docTemplate = `{ "application/json" ], "tags": [ - "club-point-of-contact" + "club-leader" ], - "summary": "Create a point of contact for a club", - "operationId": "create-point-of-contact-by-club", + "summary": "Update a leader photo for a club", + "operationId": "update-leader-photo-by-club", "parameters": [ { "type": "string", @@ -1435,13 +1405,20 @@ const docTemplate = `{ "name": "clubID", "in": "path", "required": true + }, + { + "type": "string", + "description": "leader ID", + "name": "leaderID", + "in": "path", + "required": true } ], "responses": { - "201": { - "description": "Created", + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/models.PointOfContact" + "$ref": "#/definitions/models.Leader" } }, "400": { @@ -1463,20 +1440,17 @@ const docTemplate = `{ } } }, - "/clubs/{clubID}/poc/{pocID}": { - "put": { - "description": "Updates a point of contact associated with a club", - "consumes": [ - "application/json" - ], + "/clubs/{clubID}/members/": { + "get": { + "description": "Retrieves all members associated with a club", "produces": [ "application/json" ], "tags": [ - "club-point-of-contact" + "club-member" ], - "summary": "Update a point of contact for a club", - "operationId": "update-point-of-contact-by-club", + "summary": "Retrieve all members for a club", + "operationId": "get-members-by-club", "parameters": [ { "type": "string", @@ -1486,18 +1460,26 @@ const docTemplate = `{ "required": true }, { - "type": "string", - "description": "Point of Contact ID", - "name": "pocID", - "in": "path", - "required": true + "type": "integer", + "description": "Limit", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page", + "name": "page", + "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.PointOfContact" + "type": "array", + "items": { + "$ref": "#/definitions/models.User" + } } }, "400": { @@ -1518,16 +1500,19 @@ const docTemplate = `{ } } }, - "delete": { - "description": "Delete a point of contact associated with a club", + "post": { + "description": "Creates a new member associated with a club", + "consumes": [ + "application/json" + ], "produces": [ "application/json" ], "tags": [ - "club-point-of-contact" + "club-member" ], - "summary": "Delete a point of contact for a club", - "operationId": "delete-point-of-contact-by-club", + "summary": "Create a new member for a club", + "operationId": "create-member-for-club", "parameters": [ { "type": "string", @@ -1538,20 +1523,27 @@ const docTemplate = `{ }, { "type": "string", - "description": "Point of Contact ID", - "name": "pocID", + "description": "User ID", + "name": "userID", "in": "path", "required": true } ], "responses": { - "204": { - "description": "No Content" + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.User" + } }, "400": { "description": "Bad Request", "schema": {} }, + "401": { + "description": "Unauthorized", + "schema": {} + }, "404": { "description": "Not Found", "schema": {} @@ -1562,19 +1554,16 @@ const docTemplate = `{ } } }, - "patch": { - "description": "Updates a point of contact photo associated with a club", - "consumes": [ - "multipart/form-data" - ], + "delete": { + "description": "Deletes a member associated with a club", "produces": [ "application/json" ], "tags": [ - "club-point-of-contact" + "club-member" ], - "summary": "Update a point of contact photo for a club", - "operationId": "update-point-of-contact-photo-by-club", + "summary": "Delete a member from a club", + "operationId": "delete-member-from-club", "parameters": [ { "type": "string", @@ -1585,17 +1574,17 @@ const docTemplate = `{ }, { "type": "string", - "description": "Point of Contact ID", - "name": "pocID", + "description": "User ID", + "name": "userID", "in": "path", "required": true } ], "responses": { - "200": { - "description": "OK", + "204": { + "description": "No Content", "schema": { - "$ref": "#/definitions/models.PointOfContact" + "$ref": "#/definitions/models.User" } }, "400": { @@ -1617,17 +1606,17 @@ const docTemplate = `{ } } }, - "/clubs/{clubID}/pocs/": { + "/clubs/{clubID}/socials/": { "get": { - "description": "Retrieves all point of contacts associated with a club", + "description": "Retrieves all socials associated with a club", "produces": [ "application/json" ], "tags": [ - "club-point-of-contact" + "club-social" ], - "summary": "Retrieve all point of contacts for a club", - "operationId": "get-point-of-contacts-by-club", + "summary": "Retrieve all socials for a club", + "operationId": "get-socials-by-club", "parameters": [ { "type": "string", @@ -1643,7 +1632,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.PointOfContact" + "$ref": "#/definitions/models.Social" } } }, @@ -1660,19 +1649,20 @@ const docTemplate = `{ "schema": {} } } - } - }, - "/clubs/{clubID}/pocs/{pocID}": { - "get": { - "description": "Retrieves a point of contact associated with a club", + }, + "put": { + "description": "Creates a social", + "consumes": [ + "application/json" + ], "produces": [ "application/json" ], "tags": [ - "club-point-of-contact" + "club-social" ], - "summary": "Retrieve a point of contact for a club", - "operationId": "get-point-of-contact-by-club", + "summary": "Creates a social", + "operationId": "put-social", "parameters": [ { "type": "string", @@ -1682,24 +1672,30 @@ const docTemplate = `{ "required": true }, { - "type": "string", - "description": "Point of Contact ID", - "name": "pocID", - "in": "path", - "required": true + "description": "Social Body", + "name": "socialBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/socials.PutSocialRequestBody" + } } ], "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "schema": { - "$ref": "#/definitions/models.PointOfContact" + "$ref": "#/definitions/models.Social" } }, "400": { "description": "Bad Request", "schema": {} }, + "401": { + "description": "Unauthorized", + "schema": {} + }, "404": { "description": "Not Found", "schema": {} @@ -1868,162 +1864,6 @@ const docTemplate = `{ } } }, - "/contacts/": { - "get": { - "description": "Retrieves all contacts", - "produces": [ - "application/json" - ], - "tags": [ - "contact" - ], - "summary": "Retrieve all contacts", - "operationId": "get-contacts", - "parameters": [ - { - "type": "integer", - "description": "Limit", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "description": "Page", - "name": "page", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Contact" - } - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "404": { - "description": "Not Found", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "type": "string" - } - } - } - } - }, - "/contacts/{contactID}/": { - "get": { - "description": "Retrieves a contact by id", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "contact" - ], - "summary": "Retrieves a contact", - "operationId": "get-contact", - "parameters": [ - { - "type": "string", - "description": "Contact ID", - "name": "contactID", - "in": "path", - "required": true - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/models.Contact" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "404": { - "description": "Not Found", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "type": "string" - } - } - } - }, - "delete": { - "description": "Deletes a contact", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "contact" - ], - "summary": "Deletes a contact", - "operationId": "delete-contact", - "parameters": [ - { - "type": "string", - "description": "Contact ID", - "name": "contactID", - "in": "path", - "required": true - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/models.Contact" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "404": { - "description": "Not Found", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "type": "string" - } - } - } - } - }, "/events/": { "get": { "description": "Retrieves all events", @@ -2309,7 +2149,7 @@ const docTemplate = `{ } } }, - "/pocs/": { + "/leader/": { "get": { "description": "Retrieves all point of contacts", "produces": [ @@ -2340,7 +2180,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.PointOfContact" + "$ref": "#/definitions/models.Leader" } } }, @@ -2365,7 +2205,7 @@ const docTemplate = `{ } } }, - "/pocs/{pocID}/": { + "/leader/{leaderID}/": { "get": { "description": "Retrieves a point of contact by id", "produces": [ @@ -2379,17 +2219,318 @@ const docTemplate = `{ "parameters": [ { "type": "string", - "description": "Point of Contact ID", - "name": "pocID", + "description": "Point of Contact ID", + "name": "leaderID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Leader" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/search/clubs": { + "get": { + "description": "Searches through clubs", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "search" + ], + "summary": "Searches through clubs", + "operationId": "search-clubs", + "parameters": [ + { + "type": "integer", + "name": "max_members", + "in": "query" + }, + { + "type": "integer", + "name": "min_members", + "in": "query" + }, + { + "type": "string", + "name": "search", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "tags", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/types.SearchResult-models_Club" + } + } + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, + "/search/events": { + "get": { + "description": "Searches through events", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "search" + ], + "summary": "Searches through events", + "operationId": "search-event", + "parameters": [ + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "clubs", + "in": "query" + }, + { + "type": "string", + "name": "end_time", + "in": "query" + }, + { + "type": "array", + "items": { + "enum": [ + "hybrid", + "in_person", + "virtual" + ], + "type": "string" + }, + "collectionFormat": "csv", + "name": "event_type", + "in": "query" + }, + { + "type": "string", + "name": "search", + "in": "query" + }, + { + "type": "string", + "name": "start_time", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "tags", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/types.SearchResult-models_Event" + } + } + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, + "/socials/": { + "get": { + "description": "Retrieves all socials", + "produces": [ + "application/json" + ], + "tags": [ + "social" + ], + "summary": "Retrieve all socials", + "operationId": "get-socials", + "parameters": [ + { + "type": "integer", + "description": "Limit", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page", + "name": "page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Social" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/socials/{socialID}/": { + "get": { + "description": "Retrieves a social by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "social" + ], + "summary": "Retrieves a social", + "operationId": "get-social", + "parameters": [ + { + "type": "string", + "description": "Social ID", + "name": "socialID", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.Social" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "description": "Deletes a social", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "social" + ], + "summary": "Deletes a social", + "operationId": "delete-social", + "parameters": [ + { + "type": "string", + "description": "Social ID", + "name": "socialID", "in": "path", "required": true } ], "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "schema": { - "$ref": "#/definitions/models.PointOfContact" + "$ref": "#/definitions/models.Social" } }, "400": { @@ -3276,8 +3417,7 @@ const docTemplate = `{ }, "password": { "description": "MARK: must be validated manually", - "type": "string", - "maxLength": 255 + "type": "string" } } }, @@ -3290,13 +3430,11 @@ const docTemplate = `{ "properties": { "new_password": { "description": "MARK: must be validated manually", - "type": "string", - "maxLength": 255 + "type": "string" }, "old_password": { "description": "MARK: must be validated manually", - "type": "string", - "maxLength": 255 + "type": "string" } } }, @@ -4280,15 +4418,15 @@ const docTemplate = `{ ], "properties": { "new_password": { - "type": "string", - "minLength": 8 + "description": "MARK: must be validated manually", + "type": "string" }, "token": { "type": "string" }, "verify_new_password": { - "type": "string", - "minLength": 8 + "description": "MARK: must be validated manually", + "type": "string" } } }, @@ -4304,36 +4442,14 @@ const docTemplate = `{ } } }, - "contacts.PutContactRequestBody": { + "models.Application": { "type": "object", - "required": [ - "content", - "type" - ], "properties": { - "content": { - "type": "string", - "maxLength": 255 + "description": { + "type": "string" }, - "type": { - "maxLength": 255, - "enum": [ - "facebook", - "instagram", - "x", - "linkedin", - "youtube", - "github", - "slack", - "discord", - "email", - "customSite" - ], - "allOf": [ - { - "$ref": "#/definitions/models.ContactType" - } - ] + "title": { + "type": "string" } } }, @@ -4360,9 +4476,6 @@ const docTemplate = `{ "models.Club": { "type": "object", "properties": { - "application_link": { - "type": "string" - }, "created_at": { "type": "string", "example": "2023-09-20T16:34:50Z" @@ -4374,9 +4487,6 @@ const docTemplate = `{ "type": "string", "example": "123e4567-e89b-12d3-a456-426614174000" }, - "is_recruiting": { - "type": "boolean" - }, "logo": { "type": "string" }, @@ -4392,11 +4502,8 @@ const docTemplate = `{ "preview": { "type": "string" }, - "recruitment_cycle": { - "$ref": "#/definitions/models.RecruitmentCycle" - }, - "recruitment_type": { - "$ref": "#/definitions/models.RecruitmentType" + "recruitment": { + "$ref": "#/definitions/models.Recruitment" }, "updated_at": { "type": "string", @@ -4443,56 +4550,6 @@ const docTemplate = `{ "CSSH" ] }, - "models.Contact": { - "type": "object", - "properties": { - "content": { - "type": "string" - }, - "created_at": { - "type": "string", - "example": "2023-09-20T16:34:50Z" - }, - "id": { - "type": "string", - "example": "123e4567-e89b-12d3-a456-426614174000" - }, - "type": { - "$ref": "#/definitions/models.ContactType" - }, - "updated_at": { - "type": "string", - "example": "2023-09-20T16:34:50Z" - } - } - }, - "models.ContactType": { - "type": "string", - "enum": [ - "facebook", - "instagram", - "x", - "linkedin", - "youtube", - "github", - "slack", - "discord", - "email", - "customSite" - ], - "x-enum-varnames": [ - "Facebook", - "Instagram", - "X", - "LinkedIn", - "YouTube", - "GitHub", - "Slack", - "Discord", - "Email", - "CustomSite" - ] - }, "models.Event": { "type": "object", "properties": { @@ -4610,6 +4667,35 @@ const docTemplate = `{ "May" ] }, + "models.Leader": { + "type": "object", + "properties": { + "created_at": { + "type": "string", + "example": "2023-09-20T16:34:50Z" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "name": { + "type": "string" + }, + "photo_file": { + "$ref": "#/definitions/models.File" + }, + "position": { + "type": "string" + }, + "updated_at": { + "type": "string", + "example": "2023-09-20T16:34:50Z" + } + } + }, "models.Major": { "type": "string", "enum": [ @@ -4817,32 +4903,23 @@ const docTemplate = `{ "Theatre" ] }, - "models.PointOfContact": { + "models.Recruitment": { "type": "object", "properties": { - "created_at": { - "type": "string", - "example": "2023-09-20T16:34:50Z" - }, - "email": { - "type": "string" - }, - "id": { - "type": "string", - "example": "123e4567-e89b-12d3-a456-426614174000" - }, - "name": { - "type": "string" + "applications": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Application" + } }, - "photo_file": { - "$ref": "#/definitions/models.File" + "cycle": { + "$ref": "#/definitions/models.RecruitmentCycle" }, - "position": { - "type": "string" + "is_recruiting": { + "type": "boolean" }, - "updated_at": { - "type": "string", - "example": "2023-09-20T16:34:50Z" + "type": { + "$ref": "#/definitions/models.RecruitmentType" } } }, @@ -4869,9 +4946,59 @@ const docTemplate = `{ "application" ], "x-enum-varnames": [ - "Unrestricted", - "Tryout", - "Application" + "RecruitmentTypeUnrestricted", + "RecruitmentTypeTryout", + "RecruitmentTypeApplication" + ] + }, + "models.Social": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "created_at": { + "type": "string", + "example": "2023-09-20T16:34:50Z" + }, + "id": { + "type": "string", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "type": { + "$ref": "#/definitions/models.SocialType" + }, + "updated_at": { + "type": "string", + "example": "2023-09-20T16:34:50Z" + } + } + }, + "models.SocialType": { + "type": "string", + "enum": [ + "facebook", + "instagram", + "x", + "linkedin", + "youtube", + "github", + "slack", + "discord", + "email", + "customSite" + ], + "x-enum-varnames": [ + "Facebook", + "Instagram", + "X", + "LinkedIn", + "YouTube", + "GitHub", + "Slack", + "Discord", + "Email", + "CustomSite" ] }, "models.Tag": { @@ -4947,6 +5074,39 @@ const docTemplate = `{ } } }, + "socials.PutSocialRequestBody": { + "type": "object", + "required": [ + "content", + "type" + ], + "properties": { + "content": { + "type": "string", + "maxLength": 255 + }, + "type": { + "maxLength": 255, + "enum": [ + "facebook", + "instagram", + "x", + "linkedin", + "youtube", + "github", + "slack", + "discord", + "email", + "customSite" + ], + "allOf": [ + { + "$ref": "#/definitions/models.SocialType" + } + ] + } + } + }, "tags.CreateClubTagsRequestBody": { "type": "object", "required": [ @@ -4975,6 +5135,28 @@ const docTemplate = `{ } } }, + "types.SearchResult-models_Club": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Club" + } + } + } + }, + "types.SearchResult-models_Event": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Event" + } + } + } + }, "utilities.SuccessResponse": { "type": "object", "properties": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index bbfe208db..ff93b92eb 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -169,7 +169,7 @@ }, "/auth/refresh": { "post": { - "description": "Refreshes a user's access token", + "description": "Refreshes a user's access token and returns a new pair of tokens", "consumes": [ "application/json" ], @@ -179,7 +179,7 @@ "tags": [ "auth" ], - "summary": "Refreshes a user's access token", + "summary": "Refreshes a user's access token and returns a new pair of tokens", "operationId": "refresh-user", "responses": { "200": { @@ -1022,17 +1022,17 @@ } } }, - "/clubs/{clubID}/contacts/": { + "/clubs/{clubID}/events/": { "get": { - "description": "Retrieves all contacts associated with a club", + "description": "Retrieves all events associated with a club", "produces": [ "application/json" ], "tags": [ - "club-contact" + "club-event" ], - "summary": "Retrieve all contacts for a club", - "operationId": "get-contacts-by-club", + "summary": "Retrieve all events for a club", + "operationId": "get-events-by-club", "parameters": [ { "type": "string", @@ -1040,6 +1040,18 @@ "name": "clubID", "in": "path", "required": true + }, + { + "type": "integer", + "description": "Limit", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page", + "name": "page", + "in": "query" } ], "responses": { @@ -1048,7 +1060,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.Contact" + "$ref": "#/definitions/models.Event" } } }, @@ -1065,20 +1077,19 @@ "schema": {} } } - }, - "put": { - "description": "Creates a contact", - "consumes": [ - "application/json" - ], + } + }, + "/clubs/{clubID}/followers/": { + "get": { + "description": "Retrieves all followers associated with a club", "produces": [ "application/json" ], "tags": [ - "club-contact" + "club-follower" ], - "summary": "Creates a contact", - "operationId": "put-contact", + "summary": "Retrieve all followers for a club", + "operationId": "get-followers-by-club", "parameters": [ { "type": "string", @@ -1088,30 +1099,32 @@ "required": true }, { - "description": "Contact Body", - "name": "contactBody", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/contacts.PutContactRequestBody" - } + "type": "integer", + "description": "Limit", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page", + "name": "page", + "in": "query" } ], "responses": { - "201": { - "description": "Created", + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/models.Contact" + "type": "array", + "items": { + "$ref": "#/definitions/models.User" + } } }, "400": { "description": "Bad Request", "schema": {} }, - "401": { - "description": "Unauthorized", - "schema": {} - }, "404": { "description": "Not Found", "schema": {} @@ -1123,17 +1136,17 @@ } } }, - "/clubs/{clubID}/events/": { + "/clubs/{clubID}/leadership/": { "get": { - "description": "Retrieves all events associated with a club", + "description": "Retrieves all leadership associated with a club", "produces": [ "application/json" ], "tags": [ - "club-event" + "club-leader" ], - "summary": "Retrieve all events for a club", - "operationId": "get-events-by-club", + "summary": "Retrieve all leadership for a club", + "operationId": "get-leadership-by-club", "parameters": [ { "type": "string", @@ -1141,18 +1154,6 @@ "name": "clubID", "in": "path", "required": true - }, - { - "type": "integer", - "description": "Limit", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "description": "Page", - "name": "page", - "in": "query" } ], "responses": { @@ -1161,7 +1162,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.Event" + "$ref": "#/definitions/models.Leader" } } }, @@ -1178,19 +1179,20 @@ "schema": {} } } - } - }, - "/clubs/{clubID}/followers/": { - "get": { - "description": "Retrieves all followers associated with a club", + }, + "post": { + "description": "Creates a leader associated with a club", + "consumes": [ + "multipart/form-data" + ], "produces": [ "application/json" ], "tags": [ - "club-follower" + "club-leader" ], - "summary": "Retrieve all followers for a club", - "operationId": "get-followers-by-club", + "summary": "Create a leader for a club", + "operationId": "create-leader-by-club", "parameters": [ { "type": "string", @@ -1198,34 +1200,23 @@ "name": "clubID", "in": "path", "required": true - }, - { - "type": "integer", - "description": "Limit", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "description": "Page", - "name": "page", - "in": "query" } ], "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.User" - } + "$ref": "#/definitions/models.Leader" } }, "400": { "description": "Bad Request", "schema": {} }, + "401": { + "description": "Unauthorized", + "schema": {} + }, "404": { "description": "Not Found", "schema": {} @@ -1237,17 +1228,17 @@ } } }, - "/clubs/{clubID}/members/": { + "/clubs/{clubID}/leadership/{leaderID}": { "get": { - "description": "Retrieves all members associated with a club", + "description": "Retrieves a leader associated with a club", "produces": [ "application/json" ], "tags": [ - "club-member" + "club-leader" ], - "summary": "Retrieve all members for a club", - "operationId": "get-members-by-club", + "summary": "Retrieve a leader for a club", + "operationId": "get-leader-by-club", "parameters": [ { "type": "string", @@ -1257,36 +1248,24 @@ "required": true }, { - "type": "integer", - "description": "Limit", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "description": "Page", - "name": "page", - "in": "query" + "type": "string", + "description": "leader ID", + "name": "leaderID", + "in": "path", + "required": true } ], "responses": { "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.User" - } + "$ref": "#/definitions/models.Leader" } }, "400": { "description": "Bad Request", "schema": {} }, - "401": { - "description": "Unauthorized", - "schema": {} - }, "404": { "description": "Not Found", "schema": {} @@ -1297,8 +1276,8 @@ } } }, - "post": { - "description": "Creates a new member associated with a club", + "put": { + "description": "Updates a leader associated with a club", "consumes": [ "application/json" ], @@ -1306,10 +1285,10 @@ "application/json" ], "tags": [ - "club-member" + "club-leader" ], - "summary": "Create a new member for a club", - "operationId": "create-member-for-club", + "summary": "Update a leader for a club", + "operationId": "update-leader-by-club", "parameters": [ { "type": "string", @@ -1320,17 +1299,17 @@ }, { "type": "string", - "description": "User ID", - "name": "userID", + "description": "leader ID", + "name": "leaderID", "in": "path", "required": true } ], "responses": { - "201": { - "description": "Created", + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/models.User" + "$ref": "#/definitions/models.Leader" } }, "400": { @@ -1352,15 +1331,15 @@ } }, "delete": { - "description": "Deletes a member associated with a club", + "description": "Delete a leader associated with a club", "produces": [ "application/json" ], "tags": [ - "club-member" + "club-leader" ], - "summary": "Delete a member from a club", - "operationId": "delete-member-from-club", + "summary": "Delete a leader for a club", + "operationId": "delete-leader-by-club", "parameters": [ { "type": "string", @@ -1371,27 +1350,20 @@ }, { "type": "string", - "description": "User ID", - "name": "userID", + "description": "leader ID", + "name": "leaderID", "in": "path", "required": true } ], "responses": { "204": { - "description": "No Content", - "schema": { - "$ref": "#/definitions/models.User" - } + "description": "No Content" }, "400": { "description": "Bad Request", "schema": {} }, - "401": { - "description": "Unauthorized", - "schema": {} - }, "404": { "description": "Not Found", "schema": {} @@ -1401,11 +1373,9 @@ "schema": {} } } - } - }, - "/clubs/{clubID}/poc/": { - "post": { - "description": "Creates a point of contact associated with a club", + }, + "patch": { + "description": "Updates a leader photo associated with a club", "consumes": [ "multipart/form-data" ], @@ -1413,10 +1383,10 @@ "application/json" ], "tags": [ - "club-point-of-contact" + "club-leader" ], - "summary": "Create a point of contact for a club", - "operationId": "create-point-of-contact-by-club", + "summary": "Update a leader photo for a club", + "operationId": "update-leader-photo-by-club", "parameters": [ { "type": "string", @@ -1424,13 +1394,20 @@ "name": "clubID", "in": "path", "required": true + }, + { + "type": "string", + "description": "leader ID", + "name": "leaderID", + "in": "path", + "required": true } ], "responses": { - "201": { - "description": "Created", + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/models.PointOfContact" + "$ref": "#/definitions/models.Leader" } }, "400": { @@ -1452,20 +1429,17 @@ } } }, - "/clubs/{clubID}/poc/{pocID}": { - "put": { - "description": "Updates a point of contact associated with a club", - "consumes": [ - "application/json" - ], + "/clubs/{clubID}/members/": { + "get": { + "description": "Retrieves all members associated with a club", "produces": [ "application/json" ], "tags": [ - "club-point-of-contact" + "club-member" ], - "summary": "Update a point of contact for a club", - "operationId": "update-point-of-contact-by-club", + "summary": "Retrieve all members for a club", + "operationId": "get-members-by-club", "parameters": [ { "type": "string", @@ -1475,18 +1449,26 @@ "required": true }, { - "type": "string", - "description": "Point of Contact ID", - "name": "pocID", - "in": "path", - "required": true + "type": "integer", + "description": "Limit", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page", + "name": "page", + "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.PointOfContact" + "type": "array", + "items": { + "$ref": "#/definitions/models.User" + } } }, "400": { @@ -1507,16 +1489,19 @@ } } }, - "delete": { - "description": "Delete a point of contact associated with a club", + "post": { + "description": "Creates a new member associated with a club", + "consumes": [ + "application/json" + ], "produces": [ "application/json" ], "tags": [ - "club-point-of-contact" + "club-member" ], - "summary": "Delete a point of contact for a club", - "operationId": "delete-point-of-contact-by-club", + "summary": "Create a new member for a club", + "operationId": "create-member-for-club", "parameters": [ { "type": "string", @@ -1527,20 +1512,27 @@ }, { "type": "string", - "description": "Point of Contact ID", - "name": "pocID", + "description": "User ID", + "name": "userID", "in": "path", "required": true } ], "responses": { - "204": { - "description": "No Content" + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.User" + } }, "400": { "description": "Bad Request", "schema": {} }, + "401": { + "description": "Unauthorized", + "schema": {} + }, "404": { "description": "Not Found", "schema": {} @@ -1551,19 +1543,16 @@ } } }, - "patch": { - "description": "Updates a point of contact photo associated with a club", - "consumes": [ - "multipart/form-data" - ], + "delete": { + "description": "Deletes a member associated with a club", "produces": [ "application/json" ], "tags": [ - "club-point-of-contact" + "club-member" ], - "summary": "Update a point of contact photo for a club", - "operationId": "update-point-of-contact-photo-by-club", + "summary": "Delete a member from a club", + "operationId": "delete-member-from-club", "parameters": [ { "type": "string", @@ -1574,17 +1563,17 @@ }, { "type": "string", - "description": "Point of Contact ID", - "name": "pocID", + "description": "User ID", + "name": "userID", "in": "path", "required": true } ], "responses": { - "200": { - "description": "OK", + "204": { + "description": "No Content", "schema": { - "$ref": "#/definitions/models.PointOfContact" + "$ref": "#/definitions/models.User" } }, "400": { @@ -1606,17 +1595,17 @@ } } }, - "/clubs/{clubID}/pocs/": { + "/clubs/{clubID}/socials/": { "get": { - "description": "Retrieves all point of contacts associated with a club", + "description": "Retrieves all socials associated with a club", "produces": [ "application/json" ], "tags": [ - "club-point-of-contact" + "club-social" ], - "summary": "Retrieve all point of contacts for a club", - "operationId": "get-point-of-contacts-by-club", + "summary": "Retrieve all socials for a club", + "operationId": "get-socials-by-club", "parameters": [ { "type": "string", @@ -1632,7 +1621,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.PointOfContact" + "$ref": "#/definitions/models.Social" } } }, @@ -1649,19 +1638,20 @@ "schema": {} } } - } - }, - "/clubs/{clubID}/pocs/{pocID}": { - "get": { - "description": "Retrieves a point of contact associated with a club", + }, + "put": { + "description": "Creates a social", + "consumes": [ + "application/json" + ], "produces": [ "application/json" ], "tags": [ - "club-point-of-contact" + "club-social" ], - "summary": "Retrieve a point of contact for a club", - "operationId": "get-point-of-contact-by-club", + "summary": "Creates a social", + "operationId": "put-social", "parameters": [ { "type": "string", @@ -1671,24 +1661,30 @@ "required": true }, { - "type": "string", - "description": "Point of Contact ID", - "name": "pocID", - "in": "path", - "required": true + "description": "Social Body", + "name": "socialBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/socials.PutSocialRequestBody" + } } ], "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "schema": { - "$ref": "#/definitions/models.PointOfContact" + "$ref": "#/definitions/models.Social" } }, "400": { "description": "Bad Request", "schema": {} }, + "401": { + "description": "Unauthorized", + "schema": {} + }, "404": { "description": "Not Found", "schema": {} @@ -1857,162 +1853,6 @@ } } }, - "/contacts/": { - "get": { - "description": "Retrieves all contacts", - "produces": [ - "application/json" - ], - "tags": [ - "contact" - ], - "summary": "Retrieve all contacts", - "operationId": "get-contacts", - "parameters": [ - { - "type": "integer", - "description": "Limit", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "description": "Page", - "name": "page", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Contact" - } - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "404": { - "description": "Not Found", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "type": "string" - } - } - } - } - }, - "/contacts/{contactID}/": { - "get": { - "description": "Retrieves a contact by id", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "contact" - ], - "summary": "Retrieves a contact", - "operationId": "get-contact", - "parameters": [ - { - "type": "string", - "description": "Contact ID", - "name": "contactID", - "in": "path", - "required": true - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/models.Contact" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "404": { - "description": "Not Found", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "type": "string" - } - } - } - }, - "delete": { - "description": "Deletes a contact", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "contact" - ], - "summary": "Deletes a contact", - "operationId": "delete-contact", - "parameters": [ - { - "type": "string", - "description": "Contact ID", - "name": "contactID", - "in": "path", - "required": true - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/models.Contact" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "404": { - "description": "Not Found", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "type": "string" - } - } - } - } - }, "/events/": { "get": { "description": "Retrieves all events", @@ -2298,7 +2138,7 @@ } } }, - "/pocs/": { + "/leader/": { "get": { "description": "Retrieves all point of contacts", "produces": [ @@ -2329,7 +2169,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.PointOfContact" + "$ref": "#/definitions/models.Leader" } } }, @@ -2354,7 +2194,7 @@ } } }, - "/pocs/{pocID}/": { + "/leader/{leaderID}/": { "get": { "description": "Retrieves a point of contact by id", "produces": [ @@ -2368,17 +2208,318 @@ "parameters": [ { "type": "string", - "description": "Point of Contact ID", - "name": "pocID", + "description": "Point of Contact ID", + "name": "leaderID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Leader" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/search/clubs": { + "get": { + "description": "Searches through clubs", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "search" + ], + "summary": "Searches through clubs", + "operationId": "search-clubs", + "parameters": [ + { + "type": "integer", + "name": "max_members", + "in": "query" + }, + { + "type": "integer", + "name": "min_members", + "in": "query" + }, + { + "type": "string", + "name": "search", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "tags", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/types.SearchResult-models_Club" + } + } + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, + "/search/events": { + "get": { + "description": "Searches through events", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "search" + ], + "summary": "Searches through events", + "operationId": "search-event", + "parameters": [ + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "clubs", + "in": "query" + }, + { + "type": "string", + "name": "end_time", + "in": "query" + }, + { + "type": "array", + "items": { + "enum": [ + "hybrid", + "in_person", + "virtual" + ], + "type": "string" + }, + "collectionFormat": "csv", + "name": "event_type", + "in": "query" + }, + { + "type": "string", + "name": "search", + "in": "query" + }, + { + "type": "string", + "name": "start_time", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "tags", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/types.SearchResult-models_Event" + } + } + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, + "/socials/": { + "get": { + "description": "Retrieves all socials", + "produces": [ + "application/json" + ], + "tags": [ + "social" + ], + "summary": "Retrieve all socials", + "operationId": "get-socials", + "parameters": [ + { + "type": "integer", + "description": "Limit", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Page", + "name": "page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Social" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/socials/{socialID}/": { + "get": { + "description": "Retrieves a social by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "social" + ], + "summary": "Retrieves a social", + "operationId": "get-social", + "parameters": [ + { + "type": "string", + "description": "Social ID", + "name": "socialID", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.Social" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "description": "Deletes a social", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "social" + ], + "summary": "Deletes a social", + "operationId": "delete-social", + "parameters": [ + { + "type": "string", + "description": "Social ID", + "name": "socialID", "in": "path", "required": true } ], "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "schema": { - "$ref": "#/definitions/models.PointOfContact" + "$ref": "#/definitions/models.Social" } }, "400": { @@ -3265,8 +3406,7 @@ }, "password": { "description": "MARK: must be validated manually", - "type": "string", - "maxLength": 255 + "type": "string" } } }, @@ -3279,13 +3419,11 @@ "properties": { "new_password": { "description": "MARK: must be validated manually", - "type": "string", - "maxLength": 255 + "type": "string" }, "old_password": { "description": "MARK: must be validated manually", - "type": "string", - "maxLength": 255 + "type": "string" } } }, @@ -4269,15 +4407,15 @@ ], "properties": { "new_password": { - "type": "string", - "minLength": 8 + "description": "MARK: must be validated manually", + "type": "string" }, "token": { "type": "string" }, "verify_new_password": { - "type": "string", - "minLength": 8 + "description": "MARK: must be validated manually", + "type": "string" } } }, @@ -4293,36 +4431,14 @@ } } }, - "contacts.PutContactRequestBody": { + "models.Application": { "type": "object", - "required": [ - "content", - "type" - ], "properties": { - "content": { - "type": "string", - "maxLength": 255 + "description": { + "type": "string" }, - "type": { - "maxLength": 255, - "enum": [ - "facebook", - "instagram", - "x", - "linkedin", - "youtube", - "github", - "slack", - "discord", - "email", - "customSite" - ], - "allOf": [ - { - "$ref": "#/definitions/models.ContactType" - } - ] + "title": { + "type": "string" } } }, @@ -4349,9 +4465,6 @@ "models.Club": { "type": "object", "properties": { - "application_link": { - "type": "string" - }, "created_at": { "type": "string", "example": "2023-09-20T16:34:50Z" @@ -4363,9 +4476,6 @@ "type": "string", "example": "123e4567-e89b-12d3-a456-426614174000" }, - "is_recruiting": { - "type": "boolean" - }, "logo": { "type": "string" }, @@ -4381,11 +4491,8 @@ "preview": { "type": "string" }, - "recruitment_cycle": { - "$ref": "#/definitions/models.RecruitmentCycle" - }, - "recruitment_type": { - "$ref": "#/definitions/models.RecruitmentType" + "recruitment": { + "$ref": "#/definitions/models.Recruitment" }, "updated_at": { "type": "string", @@ -4432,56 +4539,6 @@ "CSSH" ] }, - "models.Contact": { - "type": "object", - "properties": { - "content": { - "type": "string" - }, - "created_at": { - "type": "string", - "example": "2023-09-20T16:34:50Z" - }, - "id": { - "type": "string", - "example": "123e4567-e89b-12d3-a456-426614174000" - }, - "type": { - "$ref": "#/definitions/models.ContactType" - }, - "updated_at": { - "type": "string", - "example": "2023-09-20T16:34:50Z" - } - } - }, - "models.ContactType": { - "type": "string", - "enum": [ - "facebook", - "instagram", - "x", - "linkedin", - "youtube", - "github", - "slack", - "discord", - "email", - "customSite" - ], - "x-enum-varnames": [ - "Facebook", - "Instagram", - "X", - "LinkedIn", - "YouTube", - "GitHub", - "Slack", - "Discord", - "Email", - "CustomSite" - ] - }, "models.Event": { "type": "object", "properties": { @@ -4599,6 +4656,35 @@ "May" ] }, + "models.Leader": { + "type": "object", + "properties": { + "created_at": { + "type": "string", + "example": "2023-09-20T16:34:50Z" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "name": { + "type": "string" + }, + "photo_file": { + "$ref": "#/definitions/models.File" + }, + "position": { + "type": "string" + }, + "updated_at": { + "type": "string", + "example": "2023-09-20T16:34:50Z" + } + } + }, "models.Major": { "type": "string", "enum": [ @@ -4806,32 +4892,23 @@ "Theatre" ] }, - "models.PointOfContact": { + "models.Recruitment": { "type": "object", "properties": { - "created_at": { - "type": "string", - "example": "2023-09-20T16:34:50Z" - }, - "email": { - "type": "string" - }, - "id": { - "type": "string", - "example": "123e4567-e89b-12d3-a456-426614174000" - }, - "name": { - "type": "string" + "applications": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Application" + } }, - "photo_file": { - "$ref": "#/definitions/models.File" + "cycle": { + "$ref": "#/definitions/models.RecruitmentCycle" }, - "position": { - "type": "string" + "is_recruiting": { + "type": "boolean" }, - "updated_at": { - "type": "string", - "example": "2023-09-20T16:34:50Z" + "type": { + "$ref": "#/definitions/models.RecruitmentType" } } }, @@ -4858,9 +4935,59 @@ "application" ], "x-enum-varnames": [ - "Unrestricted", - "Tryout", - "Application" + "RecruitmentTypeUnrestricted", + "RecruitmentTypeTryout", + "RecruitmentTypeApplication" + ] + }, + "models.Social": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "created_at": { + "type": "string", + "example": "2023-09-20T16:34:50Z" + }, + "id": { + "type": "string", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "type": { + "$ref": "#/definitions/models.SocialType" + }, + "updated_at": { + "type": "string", + "example": "2023-09-20T16:34:50Z" + } + } + }, + "models.SocialType": { + "type": "string", + "enum": [ + "facebook", + "instagram", + "x", + "linkedin", + "youtube", + "github", + "slack", + "discord", + "email", + "customSite" + ], + "x-enum-varnames": [ + "Facebook", + "Instagram", + "X", + "LinkedIn", + "YouTube", + "GitHub", + "Slack", + "Discord", + "Email", + "CustomSite" ] }, "models.Tag": { @@ -4936,6 +5063,39 @@ } } }, + "socials.PutSocialRequestBody": { + "type": "object", + "required": [ + "content", + "type" + ], + "properties": { + "content": { + "type": "string", + "maxLength": 255 + }, + "type": { + "maxLength": 255, + "enum": [ + "facebook", + "instagram", + "x", + "linkedin", + "youtube", + "github", + "slack", + "discord", + "email", + "customSite" + ], + "allOf": [ + { + "$ref": "#/definitions/models.SocialType" + } + ] + } + } + }, "tags.CreateClubTagsRequestBody": { "type": "object", "required": [ @@ -4964,6 +5124,28 @@ } } }, + "types.SearchResult-models_Club": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Club" + } + } + } + }, + "types.SearchResult-models_Event": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Event" + } + } + } + }, "utilities.SuccessResponse": { "type": "object", "properties": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 93f68a29c..34a776f79 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -5,7 +5,6 @@ definitions: type: string password: description: 'MARK: must be validated manually' - maxLength: 255 type: string required: - email @@ -15,11 +14,9 @@ definitions: properties: new_password: description: 'MARK: must be validated manually' - maxLength: 255 type: string old_password: description: 'MARK: must be validated manually' - maxLength: 255 type: string required: - new_password @@ -874,12 +871,12 @@ definitions: base.VerifyPasswordResetTokenRequestBody: properties: new_password: - minLength: 8 + description: 'MARK: must be validated manually' type: string token: type: string verify_new_password: - minLength: 8 + description: 'MARK: must be validated manually' type: string required: - new_password @@ -894,29 +891,12 @@ definitions: required: - name type: object - contacts.PutContactRequestBody: + models.Application: properties: - content: - maxLength: 255 + description: + type: string + title: type: string - type: - allOf: - - $ref: '#/definitions/models.ContactType' - enum: - - facebook - - instagram - - x - - linkedin - - youtube - - github - - slack - - discord - - email - - customSite - maxLength: 255 - required: - - content - - type type: object models.Category: properties: @@ -934,8 +914,6 @@ definitions: type: object models.Club: properties: - application_link: - type: string created_at: example: "2023-09-20T16:34:50Z" type: string @@ -944,8 +922,6 @@ definitions: id: example: 123e4567-e89b-12d3-a456-426614174000 type: string - is_recruiting: - type: boolean logo: type: string name: @@ -956,10 +932,8 @@ definitions: type: string preview: type: string - recruitment_cycle: - $ref: '#/definitions/models.RecruitmentCycle' - recruitment_type: - $ref: '#/definitions/models.RecruitmentType' + recruitment: + $ref: '#/definitions/models.Recruitment' updated_at: example: "2023-09-20T16:34:50Z" type: string @@ -998,46 +972,6 @@ definitions: - CPS - CS - CSSH - models.Contact: - properties: - content: - type: string - created_at: - example: "2023-09-20T16:34:50Z" - type: string - id: - example: 123e4567-e89b-12d3-a456-426614174000 - type: string - type: - $ref: '#/definitions/models.ContactType' - updated_at: - example: "2023-09-20T16:34:50Z" - type: string - type: object - models.ContactType: - enum: - - facebook - - instagram - - x - - linkedin - - youtube - - github - - slack - - discord - - email - - customSite - type: string - x-enum-varnames: - - Facebook - - Instagram - - X - - LinkedIn - - YouTube - - GitHub - - Slack - - Discord - - Email - - CustomSite models.Event: properties: created_at: @@ -1119,6 +1053,26 @@ definitions: x-enum-varnames: - December - May + models.Leader: + properties: + created_at: + example: "2023-09-20T16:34:50Z" + type: string + email: + type: string + id: + example: 123e4567-e89b-12d3-a456-426614174000 + type: string + name: + type: string + photo_file: + $ref: '#/definitions/models.File' + position: + type: string + updated_at: + example: "2023-09-20T16:34:50Z" + type: string + type: object models.Major: enum: - africanaStudies @@ -1323,25 +1277,18 @@ definitions: - Spanish - SpeechLanguagePathologyAndAudiology - Theatre - models.PointOfContact: + models.Recruitment: properties: - created_at: - example: "2023-09-20T16:34:50Z" - type: string - email: - type: string - id: - example: 123e4567-e89b-12d3-a456-426614174000 - type: string - name: - type: string - photo_file: - $ref: '#/definitions/models.File' - position: - type: string - updated_at: - example: "2023-09-20T16:34:50Z" - type: string + applications: + items: + $ref: '#/definitions/models.Application' + type: array + cycle: + $ref: '#/definitions/models.RecruitmentCycle' + is_recruiting: + type: boolean + type: + $ref: '#/definitions/models.RecruitmentType' type: object models.RecruitmentCycle: enum: @@ -1362,9 +1309,49 @@ definitions: - application type: string x-enum-varnames: - - Unrestricted - - Tryout - - Application + - RecruitmentTypeUnrestricted + - RecruitmentTypeTryout + - RecruitmentTypeApplication + models.Social: + properties: + content: + type: string + created_at: + example: "2023-09-20T16:34:50Z" + type: string + id: + example: 123e4567-e89b-12d3-a456-426614174000 + type: string + type: + $ref: '#/definitions/models.SocialType' + updated_at: + example: "2023-09-20T16:34:50Z" + type: string + type: object + models.SocialType: + enum: + - facebook + - instagram + - x + - linkedin + - youtube + - github + - slack + - discord + - email + - customSite + type: string + x-enum-varnames: + - Facebook + - Instagram + - X + - LinkedIn + - YouTube + - GitHub + - Slack + - Discord + - Email + - CustomSite models.Tag: properties: category_id: @@ -1415,6 +1402,30 @@ definitions: example: "2023-09-20T16:34:50Z" type: string type: object + socials.PutSocialRequestBody: + properties: + content: + maxLength: 255 + type: string + type: + allOf: + - $ref: '#/definitions/models.SocialType' + enum: + - facebook + - instagram + - x + - linkedin + - youtube + - github + - slack + - discord + - email + - customSite + maxLength: 255 + required: + - content + - type + type: object tags.CreateClubTagsRequestBody: properties: tags: @@ -1433,6 +1444,20 @@ definitions: required: - tags type: object + types.SearchResult-models_Club: + properties: + results: + items: + $ref: '#/definitions/models.Club' + type: array + type: object + types.SearchResult-models_Event: + properties: + results: + items: + $ref: '#/definitions/models.Event' + type: array + type: object utilities.SuccessResponse: properties: message: @@ -1555,7 +1580,7 @@ paths: post: consumes: - application/json - description: Refreshes a user's access token + description: Refreshes a user's access token and returns a new pair of tokens operationId: refresh-user produces: - application/json @@ -1573,7 +1598,7 @@ paths: "500": description: Internal Server Error schema: {} - summary: Refreshes a user's access token + summary: Refreshes a user's access token and returns a new pair of tokens tags: - auth /auth/register: @@ -2132,76 +2157,6 @@ paths: summary: Update a club tags: - club - /clubs/{clubID}/contacts/: - get: - description: Retrieves all contacts associated with a club - operationId: get-contacts-by-club - parameters: - - description: Club ID - in: path - name: clubID - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/models.Contact' - type: array - "400": - description: Bad Request - schema: {} - "404": - description: Not Found - schema: {} - "500": - description: Internal Server Error - schema: {} - summary: Retrieve all contacts for a club - tags: - - club-contact - put: - consumes: - - application/json - description: Creates a contact - operationId: put-contact - parameters: - - description: Club ID - in: path - name: clubID - required: true - type: string - - description: Contact Body - in: body - name: contactBody - required: true - schema: - $ref: '#/definitions/contacts.PutContactRequestBody' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/models.Contact' - "400": - description: Bad Request - schema: {} - "401": - description: Unauthorized - schema: {} - "404": - description: Not Found - schema: {} - "500": - description: Internal Server Error - schema: {} - summary: Creates a contact - tags: - - club-contact /clubs/{clubID}/events/: get: description: Retrieves all events associated with a club @@ -2280,33 +2235,60 @@ paths: summary: Retrieve all followers for a club tags: - club-follower - /clubs/{clubID}/members/: - delete: - description: Deletes a member associated with a club - operationId: delete-member-from-club + /clubs/{clubID}/leadership/: + get: + description: Retrieves all leadership associated with a club + operationId: get-leadership-by-club parameters: - description: Club ID in: path name: clubID required: true type: string - - description: User ID - in: path - name: userID - required: true - type: string produces: - application/json responses: - "204": - description: No Content + "200": + description: OK schema: - $ref: '#/definitions/models.User' + items: + $ref: '#/definitions/models.Leader' + type: array "400": description: Bad Request schema: {} - "401": - description: Unauthorized + "404": + description: Not Found + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: Retrieve all leadership for a club + tags: + - club-leader + post: + consumes: + - multipart/form-data + description: Creates a leader associated with a club + operationId: create-leader-by-club + parameters: + - description: Club ID + in: path + name: clubID + required: true + type: string + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.Leader' + "400": + description: Bad Request + schema: {} + "401": + description: Unauthorized schema: {} "404": description: Not Found @@ -2314,73 +2296,97 @@ paths: "500": description: Internal Server Error schema: {} - summary: Delete a member from a club + summary: Create a leader for a club tags: - - club-member + - club-leader + /clubs/{clubID}/leadership/{leaderID}: + delete: + description: Delete a leader associated with a club + operationId: delete-leader-by-club + parameters: + - description: Club ID + in: path + name: clubID + required: true + type: string + - description: leader ID + in: path + name: leaderID + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + "400": + description: Bad Request + schema: {} + "404": + description: Not Found + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: Delete a leader for a club + tags: + - club-leader get: - description: Retrieves all members associated with a club - operationId: get-members-by-club + description: Retrieves a leader associated with a club + operationId: get-leader-by-club parameters: - description: Club ID in: path name: clubID required: true type: string - - description: Limit - in: query - name: limit - type: integer - - description: Page - in: query - name: page - type: integer + - description: leader ID + in: path + name: leaderID + required: true + type: string produces: - application/json responses: "200": description: OK schema: - items: - $ref: '#/definitions/models.User' - type: array + $ref: '#/definitions/models.Leader' "400": description: Bad Request schema: {} - "401": - description: Unauthorized - schema: {} "404": description: Not Found schema: {} "500": description: Internal Server Error schema: {} - summary: Retrieve all members for a club + summary: Retrieve a leader for a club tags: - - club-member - post: + - club-leader + patch: consumes: - - application/json - description: Creates a new member associated with a club - operationId: create-member-for-club + - multipart/form-data + description: Updates a leader photo associated with a club + operationId: update-leader-photo-by-club parameters: - description: Club ID in: path name: clubID required: true type: string - - description: User ID + - description: leader ID in: path - name: userID + name: leaderID required: true type: string produces: - application/json responses: - "201": - description: Created + "200": + description: OK schema: - $ref: '#/definitions/models.User' + $ref: '#/definitions/models.Leader' "400": description: Bad Request schema: {} @@ -2393,28 +2399,32 @@ paths: "500": description: Internal Server Error schema: {} - summary: Create a new member for a club + summary: Update a leader photo for a club tags: - - club-member - /clubs/{clubID}/poc/: - post: + - club-leader + put: consumes: - - multipart/form-data - description: Creates a point of contact associated with a club - operationId: create-point-of-contact-by-club + - application/json + description: Updates a leader associated with a club + operationId: update-leader-by-club parameters: - description: Club ID in: path name: clubID required: true type: string + - description: leader ID + in: path + name: leaderID + required: true + type: string produces: - application/json responses: - "201": - description: Created + "200": + description: OK schema: - $ref: '#/definitions/models.PointOfContact' + $ref: '#/definitions/models.Leader' "400": description: Bad Request schema: {} @@ -2427,22 +2437,22 @@ paths: "500": description: Internal Server Error schema: {} - summary: Create a point of contact for a club + summary: Update a leader for a club tags: - - club-point-of-contact - /clubs/{clubID}/poc/{pocID}: + - club-leader + /clubs/{clubID}/members/: delete: - description: Delete a point of contact associated with a club - operationId: delete-point-of-contact-by-club + description: Deletes a member associated with a club + operationId: delete-member-from-club parameters: - description: Club ID in: path name: clubID required: true type: string - - description: Point of Contact ID + - description: User ID in: path - name: pocID + name: userID required: true type: string produces: @@ -2450,41 +2460,49 @@ paths: responses: "204": description: No Content + schema: + $ref: '#/definitions/models.User' "400": description: Bad Request schema: {} + "401": + description: Unauthorized + schema: {} "404": description: Not Found schema: {} "500": description: Internal Server Error schema: {} - summary: Delete a point of contact for a club + summary: Delete a member from a club tags: - - club-point-of-contact - patch: - consumes: - - multipart/form-data - description: Updates a point of contact photo associated with a club - operationId: update-point-of-contact-photo-by-club + - club-member + get: + description: Retrieves all members associated with a club + operationId: get-members-by-club parameters: - description: Club ID in: path name: clubID required: true type: string - - description: Point of Contact ID - in: path - name: pocID - required: true - type: string + - description: Limit + in: query + name: limit + type: integer + - description: Page + in: query + name: page + type: integer produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/models.PointOfContact' + items: + $ref: '#/definitions/models.User' + type: array "400": description: Bad Request schema: {} @@ -2497,32 +2515,32 @@ paths: "500": description: Internal Server Error schema: {} - summary: Update a point of contact photo for a club + summary: Retrieve all members for a club tags: - - club-point-of-contact - put: + - club-member + post: consumes: - application/json - description: Updates a point of contact associated with a club - operationId: update-point-of-contact-by-club + description: Creates a new member associated with a club + operationId: create-member-for-club parameters: - description: Club ID in: path name: clubID required: true type: string - - description: Point of Contact ID + - description: User ID in: path - name: pocID + name: userID required: true type: string produces: - application/json responses: - "200": - description: OK + "201": + description: Created schema: - $ref: '#/definitions/models.PointOfContact' + $ref: '#/definitions/models.User' "400": description: Bad Request schema: {} @@ -2535,13 +2553,13 @@ paths: "500": description: Internal Server Error schema: {} - summary: Update a point of contact for a club + summary: Create a new member for a club tags: - - club-point-of-contact - /clubs/{clubID}/pocs/: + - club-member + /clubs/{clubID}/socials/: get: - description: Retrieves all point of contacts associated with a club - operationId: get-point-of-contacts-by-club + description: Retrieves all socials associated with a club + operationId: get-socials-by-club parameters: - description: Club ID in: path @@ -2555,7 +2573,7 @@ paths: description: OK schema: items: - $ref: '#/definitions/models.PointOfContact' + $ref: '#/definitions/models.Social' type: array "400": description: Bad Request @@ -2566,43 +2584,48 @@ paths: "500": description: Internal Server Error schema: {} - summary: Retrieve all point of contacts for a club + summary: Retrieve all socials for a club tags: - - club-point-of-contact - /clubs/{clubID}/pocs/{pocID}: - get: - description: Retrieves a point of contact associated with a club - operationId: get-point-of-contact-by-club + - club-social + put: + consumes: + - application/json + description: Creates a social + operationId: put-social parameters: - description: Club ID in: path name: clubID required: true type: string - - description: Point of Contact ID - in: path - name: pocID + - description: Social Body + in: body + name: socialBody required: true - type: string + schema: + $ref: '#/definitions/socials.PutSocialRequestBody' produces: - application/json responses: - "200": - description: OK + "201": + description: Created schema: - $ref: '#/definitions/models.PointOfContact' + $ref: '#/definitions/models.Social' "400": description: Bad Request schema: {} + "401": + description: Unauthorized + schema: {} "404": description: Not Found schema: {} "500": description: Internal Server Error schema: {} - summary: Retrieve a point of contact for a club + summary: Creates a social tags: - - club-point-of-contact + - club-social /clubs/{clubID}/tags/: get: description: Retrieves all tags associated with a club @@ -2712,110 +2735,6 @@ paths: summary: Delete a tag for a club tags: - club-tag - /contacts/: - get: - description: Retrieves all contacts - operationId: get-contacts - parameters: - - description: Limit - in: query - name: limit - type: integer - - description: Page - in: query - name: page - type: integer - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/models.Contact' - type: array - "400": - description: Bad Request - schema: - type: string - "404": - description: Not Found - schema: - type: string - "500": - description: Internal Server Error - schema: - type: string - summary: Retrieve all contacts - tags: - - contact - /contacts/{contactID}/: - delete: - consumes: - - application/json - description: Deletes a contact - operationId: delete-contact - parameters: - - description: Contact ID - in: path - name: contactID - required: true - type: string - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/models.Contact' - "400": - description: Bad Request - schema: - type: string - "404": - description: Not Found - schema: - type: string - "500": - description: Internal Server Error - schema: - type: string - summary: Deletes a contact - tags: - - contact - get: - consumes: - - application/json - description: Retrieves a contact by id - operationId: get-contact - parameters: - - description: Contact ID - in: path - name: contactID - required: true - type: string - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/models.Contact' - "400": - description: Bad Request - schema: - type: string - "404": - description: Not Found - schema: - type: string - "500": - description: Internal Server Error - schema: - type: string - summary: Retrieves a contact - tags: - - contact /events/: get: description: Retrieves all events @@ -3013,7 +2932,7 @@ paths: summary: Retrieve a file tags: - file - /pocs/: + /leader/: get: description: Retrieves all point of contacts operationId: get-point-of-contacts @@ -3033,7 +2952,7 @@ paths: description: OK schema: items: - $ref: '#/definitions/models.PointOfContact' + $ref: '#/definitions/models.Leader' type: array "400": description: Bad Request @@ -3050,14 +2969,14 @@ paths: summary: Retrieve all point of contacts tags: - point of contact - /pocs/{pocID}/: + /leader/{leaderID}/: get: description: Retrieves a point of contact by id operationId: get-point-of-contact parameters: - description: Point of Contact ID in: path - name: pocID + name: leaderID required: true type: string produces: @@ -3066,7 +2985,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/models.PointOfContact' + $ref: '#/definitions/models.Leader' "400": description: Bad Request schema: @@ -3082,6 +3001,206 @@ paths: summary: Retrieves a point of contact tags: - point of contact + /search/clubs: + get: + consumes: + - application/json + description: Searches through clubs + operationId: search-clubs + parameters: + - in: query + name: max_members + type: integer + - in: query + name: min_members + type: integer + - in: query + name: search + type: string + - collectionFormat: csv + in: query + items: + type: string + name: tags + type: array + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/types.SearchResult-models_Club' + type: array + "404": + description: Not Found + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: Searches through clubs + tags: + - search + /search/events: + get: + consumes: + - application/json + description: Searches through events + operationId: search-event + parameters: + - collectionFormat: csv + in: query + items: + type: string + name: clubs + type: array + - in: query + name: end_time + type: string + - collectionFormat: csv + in: query + items: + enum: + - hybrid + - in_person + - virtual + type: string + name: event_type + type: array + - in: query + name: search + type: string + - in: query + name: start_time + type: string + - collectionFormat: csv + in: query + items: + type: string + name: tags + type: array + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/types.SearchResult-models_Event' + type: array + "404": + description: Not Found + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: Searches through events + tags: + - search + /socials/: + get: + description: Retrieves all socials + operationId: get-socials + parameters: + - description: Limit + in: query + name: limit + type: integer + - description: Page + in: query + name: page + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Social' + type: array + "400": + description: Bad Request + schema: + type: string + "404": + description: Not Found + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: Retrieve all socials + tags: + - social + /socials/{socialID}/: + delete: + consumes: + - application/json + description: Deletes a social + operationId: delete-social + parameters: + - description: Social ID + in: path + name: socialID + required: true + type: string + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.Social' + "400": + description: Bad Request + schema: + type: string + "404": + description: Not Found + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: Deletes a social + tags: + - social + get: + consumes: + - application/json + description: Retrieves a social by id + operationId: get-social + parameters: + - description: Social ID + in: path + name: socialID + required: true + type: string + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.Social' + "400": + description: Bad Request + schema: + type: string + "404": + description: Not Found + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: Retrieves a social + tags: + - social /tags: get: description: Retrieves all tags diff --git a/backend/entities/categories/base/controller.go b/backend/entities/categories/base/controller.go index 2ec179336..5732bb2b4 100644 --- a/backend/entities/categories/base/controller.go +++ b/backend/entities/categories/base/controller.go @@ -64,12 +64,12 @@ func (cat *CategoryController) CreateCategory(c *fiber.Ctx) error { // @Failure 500 {string} error // @Router /categories/ [get] func (cat *CategoryController) GetCategories(c *fiber.Ctx) error { - pagination, ok := fiberpaginate.FromContext(c) + pageInfo, ok := fiberpaginate.FromContext(c) if !ok { - return utilities.ErrExpectedPagination + return utilities.ErrExpectedPageInfo } - categories, err := cat.categoryService.GetCategories(*pagination) + categories, err := cat.categoryService.GetCategories(*pageInfo) if err != nil { return err } diff --git a/backend/entities/categories/tags/controller.go b/backend/entities/categories/tags/controller.go index 8fb211b12..efaba43a8 100644 --- a/backend/entities/categories/tags/controller.go +++ b/backend/entities/categories/tags/controller.go @@ -32,12 +32,12 @@ func NewCategoryTagController(categoryTagService CategoryTagServiceInterface) *C // @Failure 500 {object} error // @Router /categories/{categoryID}/tags/ [get] func (ct *CategoryTagController) GetTagsByCategory(c *fiber.Ctx) error { - pagination, ok := fiberpaginate.FromContext(c) + pageInfo, ok := fiberpaginate.FromContext(c) if !ok { - return utilities.ErrExpectedPagination + return utilities.ErrExpectedPageInfo } - tags, err := ct.categoryTagService.GetTagsByCategory(c.Params("categoryID"), *pagination) + tags, err := ct.categoryTagService.GetTagsByCategory(c.Params("categoryID"), *pageInfo) if err != nil { return err } diff --git a/backend/entities/clubs/base/controller.go b/backend/entities/clubs/base/controller.go index 0d665fa11..ae6e52bcc 100644 --- a/backend/entities/clubs/base/controller.go +++ b/backend/entities/clubs/base/controller.go @@ -3,6 +3,7 @@ package base import ( "net/http" + "github.com/GenerateNU/sac/backend/locals" "github.com/GenerateNU/sac/backend/utilities" "github.com/garrettladley/fiberpaginate" "github.com/gofiber/fiber/v2" @@ -30,12 +31,12 @@ func NewClubController(clubService ClubServiceInterface) *ClubController { // @Failure 500 {object} error // @Router /clubs/ [get] func (cl *ClubController) GetClubs(c *fiber.Ctx) error { - pagination, ok := fiberpaginate.FromContext(c) + pageInfo, ok := fiberpaginate.FromContext(c) if !ok { - return utilities.ErrExpectedPagination + return utilities.ErrExpectedPageInfo } - clubs, err := cl.clubService.GetClubs(*pagination) + clubs, err := cl.clubService.GetClubs(*pageInfo) if err != nil { return err } @@ -65,7 +66,12 @@ func (cl *ClubController) CreateClub(c *fiber.Ctx) error { return utilities.InvalidJSON() } - club, err := cl.clubService.CreateClub(clubBody) + userID, err := locals.UserIDFrom(c) + if err != nil { + return err + } + + club, err := cl.clubService.CreateClub(*userID, clubBody) if err != nil { return err } diff --git a/backend/entities/clubs/base/models.go b/backend/entities/clubs/base/models.go index e0a6b63f1..a547908c2 100644 --- a/backend/entities/clubs/base/models.go +++ b/backend/entities/clubs/base/models.go @@ -2,11 +2,9 @@ package base import ( "github.com/GenerateNU/sac/backend/entities/models" - "github.com/google/uuid" ) type CreateClubRequestBody struct { - UserID uuid.UUID `json:"user_id" validate:"required,uuid4"` Name string `json:"name" validate:"required,max=255"` Preview string `json:"preview" validate:"required,max=255"` Description string `json:"description" validate:"required,http_url,s3_url,max=255"` diff --git a/backend/entities/clubs/base/routes.go b/backend/entities/clubs/base/routes.go index 7633698b2..d90ffd0e5 100644 --- a/backend/entities/clubs/base/routes.go +++ b/backend/entities/clubs/base/routes.go @@ -2,11 +2,12 @@ package base import ( p "github.com/GenerateNU/sac/backend/auth" - "github.com/GenerateNU/sac/backend/entities/clubs/contacts" "github.com/GenerateNU/sac/backend/entities/clubs/events" "github.com/GenerateNU/sac/backend/entities/clubs/followers" + "github.com/GenerateNU/sac/backend/entities/clubs/leadership" "github.com/GenerateNU/sac/backend/entities/clubs/members" - "github.com/GenerateNU/sac/backend/entities/clubs/pocs" + "github.com/GenerateNU/sac/backend/entities/clubs/recruitment" + "github.com/GenerateNU/sac/backend/entities/clubs/socials" "github.com/GenerateNU/sac/backend/entities/clubs/tags" authMiddleware "github.com/GenerateNU/sac/backend/middleware/auth" @@ -20,9 +21,10 @@ func ClubRoutes(clubParams types.RouteParams) { tags.ClubTag(clubParams) followers.ClubFollower(clubParams) members.ClubMember(clubParams) - contacts.ClubContact(clubParams) + leadership.ClubLeader(clubParams) events.ClubEvent(clubParams) - pocs.ClubPointOfContact(clubParams) + socials.ClubSocial(clubParams) + recruitment.ClubRecruitment(clubParams) } func ClubRouter(clubParams types.RouteParams) fiber.Router { diff --git a/backend/entities/clubs/base/service.go b/backend/entities/clubs/base/service.go index 3bfebe917..495727065 100644 --- a/backend/entities/clubs/base/service.go +++ b/backend/entities/clubs/base/service.go @@ -1,19 +1,19 @@ package base import ( - "fmt" - "github.com/GenerateNU/sac/backend/entities/clubs" "github.com/GenerateNU/sac/backend/entities/models" + "github.com/GenerateNU/sac/backend/errs" "github.com/GenerateNU/sac/backend/types" "github.com/GenerateNU/sac/backend/utilities" "github.com/garrettladley/fiberpaginate" + "github.com/google/uuid" ) type ClubServiceInterface interface { GetClubs(pageInfo fiberpaginate.PageInfo) ([]models.Club, error) GetClub(id string) (*models.Club, error) - CreateClub(clubBody CreateClubRequestBody) (*models.Club, error) + CreateClub(userID uuid.UUID, clubBody CreateClubRequestBody) (*models.Club, error) UpdateClub(id string, clubBody UpdateClubRequestBody) (*models.Club, error) DeleteClub(id string) error } @@ -30,7 +30,7 @@ func (c *ClubService) GetClubs(pagination fiberpaginate.PageInfo) ([]models.Club return GetClubs(c.DB, pagination) } -func (c *ClubService) CreateClub(clubBody CreateClubRequestBody) (*models.Club, error) { +func (c *ClubService) CreateClub(userID uuid.UUID, clubBody CreateClubRequestBody) (*models.Club, error) { if err := utilities.Validate(c.Validate, clubBody); err != nil { return nil, err } @@ -40,7 +40,7 @@ func (c *ClubService) CreateClub(clubBody CreateClubRequestBody) (*models.Club, return nil, err } - return CreateClub(c.DB, clubBody.UserID, *club) + return CreateClub(c.DB, userID, *club) } func (c *ClubService) GetClub(id string) (*models.Club, error) { @@ -59,7 +59,7 @@ func (c *ClubService) UpdateClub(id string, clubBody UpdateClubRequestBody) (*mo } if utilities.AtLeastOne(clubBody, UpdateClubRequestBody{}) { - return nil, fmt.Errorf("at least one field must be present") + return nil, errs.ErrAtLeastOne } if err := utilities.Validate(c.Validate, clubBody); err != nil { diff --git a/backend/entities/clubs/base/transactions.go b/backend/entities/clubs/base/transactions.go index 8399b7c24..65c924e07 100644 --- a/backend/entities/clubs/base/transactions.go +++ b/backend/entities/clubs/base/transactions.go @@ -2,6 +2,7 @@ package base import ( "errors" + "log/slog" "github.com/GenerateNU/sac/backend/constants" "github.com/GenerateNU/sac/backend/utilities" @@ -47,6 +48,7 @@ func CreateClub(db *gorm.DB, userId uuid.UUID, club models.Club) (*models.Club, if err := tx.Create(&club).Error; err != nil { tx.Rollback() + slog.Info("err in create club", "err", err) return nil, err } @@ -58,6 +60,7 @@ func CreateClub(db *gorm.DB, userId uuid.UUID, club models.Club) (*models.Club, if err := tx.Create(&membership).Error; err != nil { tx.Rollback() + slog.Info("err in create membership", "err", err) return nil, err } @@ -71,8 +74,7 @@ func CreateClub(db *gorm.DB, userId uuid.UUID, club models.Club) (*models.Club, return nil, err } - err = search.Upsert[models.Club](db, constants.CLUBS_INDEX, club.ID.String(), &club) - if err != nil { + if err := search.Upsert[models.Club](db, constants.CLUBS_INDEX, club.ID.String(), &club); err != nil { return nil, err } diff --git a/backend/entities/clubs/contacts/controller.go b/backend/entities/clubs/contacts/controller.go deleted file mode 100644 index ac0bb51b9..000000000 --- a/backend/entities/clubs/contacts/controller.go +++ /dev/null @@ -1,69 +0,0 @@ -package contacts - -import ( - "net/http" - - "github.com/GenerateNU/sac/backend/utilities" - "github.com/gofiber/fiber/v2" -) - -type ClubContactController struct { - clubContactService ClubContactServiceInterface -} - -func NewClubContactController(clubContactService ClubContactServiceInterface) *ClubContactController { - return &ClubContactController{clubContactService: clubContactService} -} - -// GetClubContacts godoc -// -// @Summary Retrieve all contacts for a club -// @Description Retrieves all contacts associated with a club -// @ID get-contacts-by-club -// @Tags club-contact -// @Produce json -// @Param clubID path string true "Club ID" -// @Success 200 {object} []models.Contact -// @Failure 400 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /clubs/{clubID}/contacts/ [get] -func (cc *ClubContactController) GetClubContacts(c *fiber.Ctx) error { - contacts, err := cc.clubContactService.GetClubContacts(c.Params("clubID")) - if err != nil { - return err - } - - return c.Status(http.StatusOK).JSON(contacts) -} - -// PutContact godoc -// -// @Summary Creates a contact -// @Description Creates a contact -// @ID put-contact -// @Tags club-contact -// @Accept json -// @Produce json -// @Param clubID path string true "Club ID" -// @Param contactBody body PutContactRequestBody true "Contact Body" -// @Success 201 {object} models.Contact -// @Failure 400 {object} error -// @Failure 401 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /clubs/{clubID}/contacts/ [put] -func (cc *ClubContactController) PutContact(c *fiber.Ctx) error { - var contactBody PutContactRequestBody - - if err := c.BodyParser(&contactBody); err != nil { - return utilities.InvalidJSON() - } - - contact, err := cc.clubContactService.PutClubContact(c.Params("clubID"), contactBody) - if err != nil { - return err - } - - return c.Status(http.StatusOK).JSON(contact) -} diff --git a/backend/entities/clubs/contacts/models.go b/backend/entities/clubs/contacts/models.go deleted file mode 100644 index 26c5cf1dd..000000000 --- a/backend/entities/clubs/contacts/models.go +++ /dev/null @@ -1,8 +0,0 @@ -package contacts - -import "github.com/GenerateNU/sac/backend/entities/models" - -type PutContactRequestBody struct { - Type models.ContactType `json:"type" validate:"required,max=255,oneof=facebook instagram x linkedin youtube github slack discord email customSite"` - Content string `json:"content" validate:"required,contact_pointer,max=255"` -} diff --git a/backend/entities/clubs/contacts/routes.go b/backend/entities/clubs/contacts/routes.go deleted file mode 100644 index 252fcfe52..000000000 --- a/backend/entities/clubs/contacts/routes.go +++ /dev/null @@ -1,20 +0,0 @@ -package contacts - -import ( - authMiddleware "github.com/GenerateNU/sac/backend/middleware/auth" - "github.com/GenerateNU/sac/backend/types" -) - -func ClubContact(clubParams types.RouteParams) { - clubContactController := NewClubContactController(NewClubContactService(clubParams.ServiceParams)) - - clubContacts := clubParams.Router.Group("/contacts") - - // api/v1/clubs/:clubID/contacts/* - clubContacts.Get("/", clubContactController.GetClubContacts) - clubContacts.Put( - "/", - authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), - clubContactController.PutContact, - ) -} diff --git a/backend/entities/clubs/contacts/service.go b/backend/entities/clubs/contacts/service.go deleted file mode 100644 index 5c12bd409..000000000 --- a/backend/entities/clubs/contacts/service.go +++ /dev/null @@ -1,49 +0,0 @@ -package contacts - -import ( - "github.com/GenerateNU/sac/backend/entities/models" - "github.com/GenerateNU/sac/backend/types" - "github.com/GenerateNU/sac/backend/utilities" -) - -type ClubContactServiceInterface interface { - GetClubContacts(clubID string) ([]models.Contact, error) - PutClubContact(clubID string, contactBody PutContactRequestBody) (*models.Contact, error) -} - -type ClubContactService struct { - types.ServiceParams -} - -func NewClubContactService(params types.ServiceParams) ClubContactServiceInterface { - return &ClubContactService{params} -} - -func (c *ClubContactService) GetClubContacts(clubID string) ([]models.Contact, error) { - idAsUUID, err := utilities.ValidateID(clubID) - if err != nil { - return nil, err - } - - return GetClubContacts(c.DB, *idAsUUID) -} - -func (c *ClubContactService) PutClubContact(clubID string, contactBody PutContactRequestBody) (*models.Contact, error) { - idAsUUID, err := utilities.ValidateID(clubID) - if err != nil { - return nil, err - } - - if err := utilities.Validate(c.Validate, contactBody); err != nil { - return nil, err - } - - contact, err := utilities.MapJsonTags(contactBody, &models.Contact{}) - if err != nil { - return nil, err - } - - contact.ClubID = *idAsUUID - - return PutClubContact(c.DB, *contact) -} diff --git a/backend/entities/clubs/events/controller.go b/backend/entities/clubs/events/controller.go index 9f0235657..9726078b5 100644 --- a/backend/entities/clubs/events/controller.go +++ b/backend/entities/clubs/events/controller.go @@ -32,12 +32,12 @@ func NewClubEventController(clubEventService ClubEventServiceInterface) *ClubEve // @Failure 500 {object} error // @Router /clubs/{clubID}/events/ [get] func (cl *ClubEventController) GetClubEvents(c *fiber.Ctx) error { - pagination, ok := fiberpaginate.FromContext(c) + pageInfo, ok := fiberpaginate.FromContext(c) if !ok { - return utilities.ErrExpectedPagination + return utilities.ErrExpectedPageInfo } - if events, err := cl.clubEventService.GetClubEvents(c.Params("clubID"), *pagination); err != nil { + if events, err := cl.clubEventService.GetClubEvents(c.Params("clubID"), *pageInfo); err != nil { return err } else { return c.Status(http.StatusOK).JSON(events) diff --git a/backend/entities/clubs/followers/controller.go b/backend/entities/clubs/followers/controller.go index 45f8b9217..24e4c4b6e 100644 --- a/backend/entities/clubs/followers/controller.go +++ b/backend/entities/clubs/followers/controller.go @@ -32,15 +32,31 @@ func NewClubFollowerController(clubFollowerService ClubFollowerServiceInterface) // @Failure 500 {object} error // @Router /clubs/{clubID}/followers/ [get] func (cf *ClubFollowerController) GetClubFollowers(c *fiber.Ctx) error { - pagination, ok := fiberpaginate.FromContext(c) + pageInfo, ok := fiberpaginate.FromContext(c) if !ok { - return utilities.ErrExpectedPagination + return utilities.ErrExpectedPageInfo } - followers, err := cf.clubFollowerService.GetClubFollowers(c.Params("clubID"), *pagination) + followers, err := cf.clubFollowerService.GetClubFollowers(c.Params("clubID"), *pageInfo) if err != nil { return err } return c.Status(http.StatusOK).JSON(followers) } + +func (cf *ClubFollowerController) CreateClubFollowing(c *fiber.Ctx) error { + if err := cf.clubFollowerService.CreateClubFollower(c.Params("clubID"), c.Params("userID")); err != nil { + return err + } + + return c.SendStatus(http.StatusCreated) +} + +func (cf *ClubFollowerController) DeleteClubFollowing(c *fiber.Ctx) error { + if err := cf.clubFollowerService.DeleteClubFollower(c.Params("clubID"), c.Params("userID")); err != nil { + return err + } + + return c.SendStatus(http.StatusNoContent) +} diff --git a/backend/entities/clubs/followers/routes.go b/backend/entities/clubs/followers/routes.go index 81f9e3a61..a89ee1f66 100644 --- a/backend/entities/clubs/followers/routes.go +++ b/backend/entities/clubs/followers/routes.go @@ -1,6 +1,8 @@ package followers import ( + authMiddleware "github.com/GenerateNU/sac/backend/middleware/auth" + "github.com/GenerateNU/sac/backend/types" ) @@ -11,4 +13,14 @@ func ClubFollower(clubParams types.RouteParams) { // api/clubs/:clubID/followers/* clubFollowers.Get("/", clubParams.UtilityMiddleware.Paginator, clubFollowerController.GetClubFollowers) + clubFollowers.Post( + "/:userID", + authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubFollowerController.CreateClubFollowing, + ) + clubFollowers.Delete( + "/:userID", + authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubFollowerController.DeleteClubFollowing, + ) } diff --git a/backend/entities/clubs/followers/service.go b/backend/entities/clubs/followers/service.go index e1fced5c2..5568295a8 100644 --- a/backend/entities/clubs/followers/service.go +++ b/backend/entities/clubs/followers/service.go @@ -9,6 +9,8 @@ import ( type ClubFollowerServiceInterface interface { GetClubFollowers(clubID string, pageInfo fiberpaginate.PageInfo) ([]models.User, error) + CreateClubFollower(clubID string, userID string) error + DeleteClubFollower(clubID string, userID string) error } type ClubFollowerService struct { @@ -27,3 +29,31 @@ func (cf *ClubFollowerService) GetClubFollowers(clubID string, pageInfo fiberpag return GetClubFollowers(cf.DB, *idAsUUID, pageInfo) } + +func (cf *ClubFollowerService) CreateClubFollower(clubID string, userID string) error { + clubIDAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return err + } + + userIDAsUUID, err := utilities.ValidateID(userID) + if err != nil { + return err + } + + return CreateClubFollower(cf.DB, *clubIDAsUUID, *userIDAsUUID) +} + +func (cf *ClubFollowerService) DeleteClubFollower(clubID string, userID string) error { + clubIDAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return err + } + + userIDAsUUID, err := utilities.ValidateID(userID) + if err != nil { + return err + } + + return DeleteClubFollower(cf.DB, *clubIDAsUUID, *userIDAsUUID) +} diff --git a/backend/entities/clubs/followers/transactions.go b/backend/entities/clubs/followers/transactions.go index 8a98f8d2e..1595c19c5 100644 --- a/backend/entities/clubs/followers/transactions.go +++ b/backend/entities/clubs/followers/transactions.go @@ -3,6 +3,7 @@ package followers import ( "github.com/GenerateNU/sac/backend/entities/clubs" "github.com/GenerateNU/sac/backend/entities/models" + "github.com/GenerateNU/sac/backend/entities/users" "github.com/GenerateNU/sac/backend/utilities" "github.com/garrettladley/fiberpaginate" "github.com/google/uuid" @@ -22,3 +23,39 @@ func GetClubFollowers(db *gorm.DB, clubID uuid.UUID, pageInfo fiberpaginate.Page return users, nil } + +func CreateClubFollower(db *gorm.DB, clubID uuid.UUID, userID uuid.UUID) error { + user, err := users.GetUser(db, userID) + if err != nil { + return err + } + + club, err := clubs.GetClub(db, clubID) + if err != nil { + return err + } + + if err := db.Model(&user).Association("Follower").Append(club); err != nil { + return err + } + + return nil +} + +func DeleteClubFollower(db *gorm.DB, clubID uuid.UUID, userID uuid.UUID) error { + user, err := users.GetUser(db, userID) + if err != nil { + return err + } + + club, err := clubs.GetClub(db, clubID) + if err != nil { + return err + } + + if err := db.Model(&user).Association("Follower").Delete(club); err != nil { + return err + } + + return nil +} diff --git a/backend/entities/clubs/leadership/controller.go b/backend/entities/clubs/leadership/controller.go new file mode 100644 index 000000000..1f59be18e --- /dev/null +++ b/backend/entities/clubs/leadership/controller.go @@ -0,0 +1,180 @@ +package leadership + +import ( + "net/http" + + "github.com/GenerateNU/sac/backend/utilities" + "github.com/gofiber/fiber/v2" +) + +type ClubLeaderController struct { + clubLeaderService ClubLeaderServiceInterface +} + +func NewClubLeaderController(clubLeaderService ClubLeaderServiceInterface) *ClubLeaderController { + return &ClubLeaderController{clubLeaderService: clubLeaderService} +} + +// GetClubLeadership godoc +// +// @Summary Retrieve all leadership for a club +// @Description Retrieves all leadership associated with a club +// @ID get-leadership-by-club +// @Tags club-leader +// @Produce json +// @Param clubID path string true "Club ID" +// @Success 200 {object} []models.Leader +// @Failure 400 {object} error +// @Failure 404 {object} error +// @Failure 500 {object} error +// @Router /clubs/{clubID}/leadership/ [get] +func (l *ClubLeaderController) GetClubLeadership(c *fiber.Ctx) error { + leadership, err := l.clubLeaderService.GetClubLeadership(c.Params("clubID")) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(leadership) +} + +// GetClubLeader godoc +// +// @Summary Retrieve a leader for a club +// @Description Retrieves a leader associated with a club +// @ID get-leader-by-club +// @Tags club-leader +// @Produce json +// @Param clubID path string true "Club ID" +// @Param leaderID path string true "leader ID" +// @Success 200 {object} models.Leader +// @Failure 400 {object} error +// @Failure 404 {object} error +// @Failure 500 {object} error +// @Router /clubs/{clubID}/leadership/{leaderID} [get] +func (l *ClubLeaderController) GetClubLeader(c *fiber.Ctx) error { + leader, err := l.clubLeaderService.GetClubLeader(c.Params("clubID"), c.Params("leaderID")) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(leader) +} + +// UpdateClubLeaderPhoto godoc +// +// @Summary Update a leader photo for a club +// @Description Updates a leader photo associated with a club +// @ID update-leader-photo-by-club +// @Tags club-leader +// @Accept multipart/form-data +// @Produce json +// @Param clubID path string true "Club ID" +// @Param leaderID path string true "leader ID" +// @Success 200 {object} models.Leader +// @Failure 400 {object} error +// @Failure 401 {object} error +// @Failure 404 {object} error +// @Failure 500 {object} error +// @Router /clubs/{clubID}/leadership/{leaderID} [patch] +func (l *ClubLeaderController) UpdateClubLeaderPhoto(c *fiber.Ctx) error { + formFile, err := c.FormFile("file") + if err != nil { + return err + } + + leader, err := l.clubLeaderService.UpdateClubLeaderPhoto(c.Params("clubID"), c.Params("leaderID"), formFile) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(leader) +} + +// UpdateClubLeader godoc +// +// @Summary Update a leader for a club +// @Description Updates a leader associated with a club +// @ID update-leader-by-club +// @Tags club-leader +// @Accept json +// @Produce json +// @Param clubID path string true "Club ID" +// @Param leaderID path string true "leader ID" +// @Success 200 {object} models.Leader +// @Failure 400 {object} error +// @Failure 401 {object} error +// @Failure 404 {object} error +// @Failure 500 {object} error +// @Router /clubs/{clubID}/leadership/{leaderID} [put] +func (l *ClubLeaderController) UpdateClubLeader(c *fiber.Ctx) error { + var leaderBody UpdateLeaderBody + + if err := c.BodyParser(&leaderBody); err != nil { + return utilities.InvalidJSON() + } + + leader, err := l.clubLeaderService.UpdateClubLeader(c.Params("clubID"), c.Params("leaderID"), leaderBody) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(leader) +} + +// CreateClubLeader godoc +// +// @Summary Create a leader for a club +// @Description Creates a leader associated with a club +// @ID create-leader-by-club +// @Tags club-leader +// @Accept multipart/form-data +// @Produce json +// @Param clubID path string true "Club ID" +// @Success 201 {object} models.Leader +// @Failure 400 {object} error +// @Failure 401 {object} error +// @Failure 404 {object} error +// @Failure 500 {object} error +// @Router /clubs/{clubID}/leadership/ [post] +func (l *ClubLeaderController) CreateClubLeader(c *fiber.Ctx) error { + var leaderBody CreateLeaderBody + + if err := c.BodyParser(&leaderBody); err != nil { + return utilities.InvalidJSON() + } + + formFile, err := c.FormFile("file") + if err != nil { + return err + } + + leader, err := l.clubLeaderService.CreateClubLeader(c.Params("clubID"), leaderBody, formFile) + if err != nil { + return err + } + + return c.Status(http.StatusCreated).JSON(leader) +} + +// DeleteClubLeader godoc +// +// @Summary Delete a leader for a club +// @Description Delete a leader associated with a club +// @ID delete-leader-by-club +// @Tags club-leader +// @Produce json +// @Param clubID path string true "Club ID" +// @Param leaderID path string true "leader ID" +// @Success 204 {object} nil +// @Failure 400 {object} error +// @Failure 404 {object} error +// @Failure 500 {object} error +// @Router /clubs/{clubID}/leadership/{leaderID} [delete] +func (l *ClubLeaderController) DeleteClubLeader(c *fiber.Ctx) error { + err := l.clubLeaderService.DeleteClubLeader(c.Params("clubID"), c.Params("leaderID")) + if err != nil { + return err + } + + return c.SendStatus(http.StatusNoContent) +} diff --git a/backend/entities/clubs/pocs/models.go b/backend/entities/clubs/leadership/models.go similarity index 80% rename from backend/entities/clubs/pocs/models.go rename to backend/entities/clubs/leadership/models.go index 85fa0a9dd..fe71a997e 100644 --- a/backend/entities/clubs/pocs/models.go +++ b/backend/entities/clubs/leadership/models.go @@ -1,12 +1,12 @@ -package pocs +package leadership -type CreatePointOfContactBody struct { +type CreateLeaderBody struct { Name string `json:"name" validate:"required,max=255"` Email string `json:"email" validate:"required,email,max=255"` Position string `json:"position" validate:"required,max=255"` } -type UpdatePointOfContactBody struct { +type UpdateLeaderBody struct { Name string `json:"name" validate:"omitempty,max=255"` Email string `json:"email" validate:"omitempty,email,max=255"` Position string `json:"position" validate:"omitempty,max=255"` diff --git a/backend/entities/clubs/leadership/routes.go b/backend/entities/clubs/leadership/routes.go new file mode 100644 index 000000000..d56b868c2 --- /dev/null +++ b/backend/entities/clubs/leadership/routes.go @@ -0,0 +1,36 @@ +package leadership + +import ( + authMiddleware "github.com/GenerateNU/sac/backend/middleware/auth" + "github.com/GenerateNU/sac/backend/types" +) + +func ClubLeader(leaderParams types.RouteParams) { + clubLeaderController := NewClubLeaderController(NewClubLeaderService(leaderParams.ServiceParams)) + + clubLeaders := leaderParams.Router.Group("/leadership") + + // api/v1/clubs/:clubID/leadership/* + clubLeaders.Get("/", clubLeaderController.GetClubLeadership) + clubLeaders.Get("/:leaderID", clubLeaderController.GetClubLeader) + clubLeaders.Post( + "/", + authMiddleware.AttachExtractor(leaderParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubLeaderController.CreateClubLeader, + ) + clubLeaders.Patch( + "/:leaderID", + authMiddleware.AttachExtractor(leaderParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubLeaderController.UpdateClubLeader, + ) + clubLeaders.Patch( + "/:leaderID/photo", + authMiddleware.AttachExtractor(leaderParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubLeaderController.UpdateClubLeaderPhoto, + ) + clubLeaders.Delete( + "/:leaderID", + authMiddleware.AttachExtractor(leaderParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubLeaderController.DeleteClubLeader, + ) +} diff --git a/backend/entities/clubs/leadership/service.go b/backend/entities/clubs/leadership/service.go new file mode 100644 index 000000000..1c5c4c14f --- /dev/null +++ b/backend/entities/clubs/leadership/service.go @@ -0,0 +1,154 @@ +package leadership + +import ( + "mime/multipart" + + "github.com/GenerateNU/sac/backend/entities/files/base" + "github.com/GenerateNU/sac/backend/entities/models" + + "github.com/GenerateNU/sac/backend/integrations/file" + "github.com/GenerateNU/sac/backend/types" + "github.com/GenerateNU/sac/backend/utilities" +) + +type ClubLeaderServiceInterface interface { + GetClubLeadership(clubID string) ([]models.Leader, error) + GetClubLeader(clubID, leaderID string) (*models.Leader, error) + CreateClubLeader(clubID string, leaderBody CreateLeaderBody, fileHeader *multipart.FileHeader) (*models.Leader, error) + UpdateClubLeaderPhoto(clubID, leaderID string, fileHeader *multipart.FileHeader) (*models.Leader, error) + UpdateClubLeader(clubID, leaderID string, leaderBody UpdateLeaderBody) (*models.Leader, error) + DeleteClubLeader(clubID, leaderID string) error +} + +type ClubLeaderService struct { + types.ServiceParams +} + +func NewClubLeaderService(serviceParams types.ServiceParams) ClubLeaderServiceInterface { + return &ClubLeaderService{serviceParams} +} + +func (cl *ClubLeaderService) GetClubLeadership(clubID string) ([]models.Leader, error) { + clubIdAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, err + } + + return GetClubLeadership(cl.DB, *clubIdAsUUID) +} + +func (cl *ClubLeaderService) GetClubLeader(clubID, leaderID string) (*models.Leader, error) { + clubIdAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, err + } + + leaderIDAsUUID, err := utilities.ValidateID(leaderID) + if err != nil { + return nil, err + } + + return GetClubLeader(cl.DB, *clubIdAsUUID, *leaderIDAsUUID) +} + +// MOVE THIS TO TRANSACTIOPNS +func (cl *ClubLeaderService) CreateClubLeader(clubID string, leaderBody CreateLeaderBody, fileHeader *multipart.FileHeader) (*models.Leader, error) { + if err := utilities.Validate(cl.Validate, leaderBody); err != nil { + return nil, err + } + + clubIdAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, err + } + + leader, err := GetClubLeaderByClubIDAndEmail(cl.DB, *clubIdAsUUID, leaderBody.Email) + if err == nil { + return leader, nil + } + + return CreateClubLeader(cl.DB, + models.Leader{ + Name: leaderBody.Name, + Email: leaderBody.Email, + Position: leaderBody.Position, + ClubID: *clubIdAsUUID, + }, + func() (*models.FileInfo, error) { + return cl.Integrations.File.UploadFile("leadership", fileHeader, []file.FileType{file.IMAGE}) + }, + cl.Integrations.File.DeleteFile, + ) +} + +func (cl *ClubLeaderService) UpdateClubLeaderPhoto(clubID, leaderID string, fileHeader *multipart.FileHeader) (*models.Leader, error) { + clubIdAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, err + } + + leaderIDAsUUID, err := utilities.ValidateID(leaderID) + if err != nil { + return nil, err + } + + leader, err := GetClubLeader(cl.DB, *clubIdAsUUID, *leaderIDAsUUID) + if err != nil { + return nil, err + } + + if err := cl.Integrations.File.DeleteFile(leader.PhotoFile.FileURL); err != nil { + return nil, err + } + + fileInfo, err := cl.Integrations.File.UploadFile("point_of_contacts", fileHeader, []file.FileType{file.IMAGE}) + if err != nil { + return nil, err + } + + file, err := base.UpdateFile(cl.DB, leader.PhotoFile.ID, *fileInfo) + if err != nil { + return nil, err + } + + leader.PhotoFile = *file + + return leader, nil +} + +func (cl *ClubLeaderService) UpdateClubLeader(clubID, leaderID string, leaderBody UpdateLeaderBody) (*models.Leader, error) { + if err := utilities.Validate(cl.Validate, leaderBody); err != nil { + return nil, err + } + + clubIdAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, err + } + + leaderIDAsUUID, err := utilities.ValidateID(leaderID) + if err != nil { + return nil, err + } + + leader, err := utilities.MapJsonTags(leaderBody, &models.Leader{}) + if err != nil { + return nil, err + } + + return UpdateClubLeader(cl.DB, *clubIdAsUUID, *leaderIDAsUUID, *leader) +} + +func (cl *ClubLeaderService) DeleteClubLeader(clubID, leaderID string) error { + clubIdAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return err + } + + leaderIDAsUUID, err := utilities.ValidateID(leaderID) + if err != nil { + return err + } + + return DeleteClubLeader(cl.DB, *clubIdAsUUID, *leaderIDAsUUID, cl.Integrations.File.DeleteFile) +} diff --git a/backend/entities/clubs/leadership/transactions.go b/backend/entities/clubs/leadership/transactions.go new file mode 100644 index 000000000..967659779 --- /dev/null +++ b/backend/entities/clubs/leadership/transactions.go @@ -0,0 +1,138 @@ +package leadership + +import ( + "errors" + + "github.com/GenerateNU/sac/backend/entities/files/base" + "github.com/GenerateNU/sac/backend/entities/models" + + "github.com/GenerateNU/sac/backend/utilities" + "github.com/google/uuid" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +func GetClubLeadership(db *gorm.DB, clubID uuid.UUID) ([]models.Leader, error) { + var leadership []models.Leader + + result := db.Preload("PhotoFile").Where("club_id = ?", clubID).Find(&leadership) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, utilities.ErrNotFound + } + return nil, result.Error + } + + return leadership, nil +} + +func GetClubLeader(db *gorm.DB, clubID uuid.UUID, leaderID uuid.UUID) (*models.Leader, error) { + var leader models.Leader + + if err := db.Preload("PhotoFile").First(&leader, "id = ? AND club_id = ?", leaderID, clubID).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, utilities.ErrNotFound + } + return nil, err + } + + return &leader, nil +} + +func GetClubLeaderByClubIDAndEmail(db *gorm.DB, clubID uuid.UUID, email string) (*models.Leader, error) { + var leader models.Leader + + if err := db.First(&leader, "email = ? AND club_id = ?", email, clubID).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, utilities.ErrNotFound + } + return nil, err + } + + return &leader, nil +} + +func CreateClubLeader(db *gorm.DB, leader models.Leader, fileIntegrationUpload func() (*models.FileInfo, error), fileIntegrationDelete func(string) error) (*models.Leader, error) { + fileInfo, err := fileIntegrationUpload() + if err != nil { + return nil, err + } + + tx := db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + if err := db.Create(&leader).Error; err != nil { + return nil, err + } + + file, err := base.CreateFile(tx, leader.ID, "leadership", *fileInfo) + if err != nil { + tx.Rollback() + return nil, err + } + + if err := tx.Commit().Error; err != nil { + if err := fileIntegrationDelete(fileInfo.FileURL); err != nil { + return nil, err + } + + return nil, err + } + + leader.PhotoFile = *file + + return &leader, nil +} + +func UpdateClubLeader(db *gorm.DB, clubID uuid.UUID, leaderID uuid.UUID, updatedLeader models.Leader) (*models.Leader, error) { + leader, err := GetClubLeader(db, clubID, leaderID) + if err != nil { + return nil, err + } + if err := db.Model(&leader).Clauses(clause.Returning{}).Updates(&updatedLeader).Error; err != nil { + return nil, err + } + + return leader, nil +} + +func DeleteClubLeader(db *gorm.DB, clubID uuid.UUID, leaderID uuid.UUID, fileIntegrationDelete func(string) error) error { + leader, err := GetClubLeader(db, clubID, leaderID) + if err != nil { + return err + } + + if err := fileIntegrationDelete(leader.PhotoFile.FileURL); err != nil { + return err + } + + tx := db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := tx.Error; err != nil { + return err + } + + err = base.DeleteFile(tx, leader.PhotoFile.ID) + if err != nil { + tx.Rollback() + return err + } + + if result := db.Delete(&models.Leader{}, "id = ? AND club_id = ?", leaderID, clubID); result.RowsAffected == 0 { + tx.Rollback() + if result.Error == nil { + return utilities.ErrNotFound + } + return result.Error + } + + return tx.Commit().Error +} diff --git a/backend/entities/clubs/members/controller.go b/backend/entities/clubs/members/controller.go index b5c899bee..d9b9b4eef 100644 --- a/backend/entities/clubs/members/controller.go +++ b/backend/entities/clubs/members/controller.go @@ -16,29 +16,13 @@ func NewClubMemberController(clubMemberService ClubMemberServiceInterface) *Club return &ClubMemberController{clubMemberService: clubMemberService} } -// GetClubMembers godoc -// -// @Summary Retrieve all members for a club -// @Description Retrieves all members associated with a club -// @ID get-members-by-club -// @Tags club-member -// @Produce json -// @Param clubID path string true "Club ID" -// @Param limit query int false "Limit" -// @Param page query int false "Page" -// @Success 200 {object} []models.User -// @Failure 400 {object} error -// @Failure 401 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /clubs/{clubID}/members/ [get] func (cm *ClubMemberController) GetClubMembers(c *fiber.Ctx) error { - pagination, ok := fiberpaginate.FromContext(c) + pageInfo, ok := fiberpaginate.FromContext(c) if !ok { - return utilities.ErrExpectedPagination + return utilities.ErrExpectedPageInfo } - followers, err := cm.clubMemberService.GetClubMembers(c.Params("clubID"), *pagination) + followers, err := cm.clubMemberService.GetClubMembers(c.Params("clubID"), *pageInfo) if err != nil { return err } @@ -46,49 +30,16 @@ func (cm *ClubMemberController) GetClubMembers(c *fiber.Ctx) error { return c.Status(http.StatusOK).JSON(followers) } -// CreateClubMember godoc -// -// @Summary Create a new member for a club -// @Description Creates a new member associated with a club -// @ID create-member-for-club -// @Tags club-member -// @Accept json -// @Produce json -// @Param clubID path string true "Club ID" -// @Param userID path string true "User ID" -// @Success 201 {object} models.User -// @Failure 400 {object} error -// @Failure 401 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /clubs/{clubID}/members/ [post] func (cm *ClubMemberController) CreateClubMember(c *fiber.Ctx) error { - err := cm.clubMemberService.CreateClubMember(c.Params("clubID"), c.Params("userID")) - if err != nil { + if err := cm.clubMemberService.CreateClubMember(c.Params("clubID"), c.Params("userID")); err != nil { return err } return c.SendStatus(http.StatusCreated) } -// DeleteClubMember godoc -// -// @Summary Delete a member from a club -// @Description Deletes a member associated with a club -// @ID delete-member-from-club -// @Tags club-member -// @Produce json -// @Param clubID path string true "Club ID" -// @Param userID path string true "User ID" -// @Success 204 {object} models.User -// @Failure 400 {object} error -// @Failure 401 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /clubs/{clubID}/members/ [delete] func (cm *ClubMemberController) DeleteClubMember(c *fiber.Ctx) error { - err := cm.clubMemberService.DeleteClubMember(c.Params("clubID"), c.Params("userID")) - if err != nil { + if err := cm.clubMemberService.DeleteClubMember(c.Params("clubID"), c.Params("userID")); err != nil { return err } diff --git a/backend/entities/clubs/members/transactions.go b/backend/entities/clubs/members/transactions.go index 8a0fd1f62..ef2d78865 100644 --- a/backend/entities/clubs/members/transactions.go +++ b/backend/entities/clubs/members/transactions.go @@ -2,9 +2,9 @@ package members import ( "github.com/GenerateNU/sac/backend/entities/clubs" + "github.com/GenerateNU/sac/backend/entities/clubs/followers" "github.com/GenerateNU/sac/backend/entities/models" "github.com/GenerateNU/sac/backend/entities/users" - "github.com/GenerateNU/sac/backend/entities/users/followers" "github.com/GenerateNU/sac/backend/utilities" "github.com/garrettladley/fiberpaginate" @@ -58,7 +58,7 @@ func CreateClubMember(db *gorm.DB, clubID uuid.UUID, userID uuid.UUID) error { return err } - if err := followers.CreateFollowing(tx, userID, clubID); err != nil { + if err := followers.CreateClubFollower(tx, userID, clubID); err != nil { tx.Rollback() return err } @@ -94,7 +94,7 @@ func DeleteClubMember(db *gorm.DB, clubID uuid.UUID, userID uuid.UUID) error { return err } - if err := followers.DeleteFollowing(tx, userID, clubID); err != nil { + if err := followers.DeleteClubFollower(tx, userID, clubID); err != nil { tx.Rollback() return err } diff --git a/backend/entities/clubs/pocs/controller.go b/backend/entities/clubs/pocs/controller.go deleted file mode 100644 index 8d9de7e6f..000000000 --- a/backend/entities/clubs/pocs/controller.go +++ /dev/null @@ -1,180 +0,0 @@ -package pocs - -import ( - "net/http" - - "github.com/GenerateNU/sac/backend/utilities" - "github.com/gofiber/fiber/v2" -) - -type ClubPointOfContactController struct { - clubPointOfContactService ClubPointOfContactServiceInterface -} - -func NewClubPointOfContactController(clubPointOfContactService ClubPointOfContactServiceInterface) *ClubPointOfContactController { - return &ClubPointOfContactController{clubPointOfContactService: clubPointOfContactService} -} - -// GetClubPointOfContacts godoc -// -// @Summary Retrieve all point of contacts for a club -// @Description Retrieves all point of contacts associated with a club -// @ID get-point-of-contacts-by-club -// @Tags club-point-of-contact -// @Produce json -// @Param clubID path string true "Club ID" -// @Success 200 {object} []models.PointOfContact -// @Failure 400 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /clubs/{clubID}/pocs/ [get] -func (cpoc *ClubPointOfContactController) GetClubPointOfContacts(c *fiber.Ctx) error { - pointOfContact, err := cpoc.clubPointOfContactService.GetClubPointOfContacts(c.Params("clubID")) - if err != nil { - return err - } - - return c.Status(http.StatusOK).JSON(pointOfContact) -} - -// GetClubPointOfContact godoc -// -// @Summary Retrieve a point of contact for a club -// @Description Retrieves a point of contact associated with a club -// @ID get-point-of-contact-by-club -// @Tags club-point-of-contact -// @Produce json -// @Param clubID path string true "Club ID" -// @Param pocID path string true "Point of Contact ID" -// @Success 200 {object} models.PointOfContact -// @Failure 400 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /clubs/{clubID}/pocs/{pocID} [get] -func (cpoc *ClubPointOfContactController) GetClubPointOfContact(c *fiber.Ctx) error { - pointOfContact, err := cpoc.clubPointOfContactService.GetClubPointOfContact(c.Params("clubID"), c.Params("pocID")) - if err != nil { - return err - } - - return c.Status(http.StatusOK).JSON(pointOfContact) -} - -// UpdateClubPointOfContactPhoto godoc -// -// @Summary Update a point of contact photo for a club -// @Description Updates a point of contact photo associated with a club -// @ID update-point-of-contact-photo-by-club -// @Tags club-point-of-contact -// @Accept multipart/form-data -// @Produce json -// @Param clubID path string true "Club ID" -// @Param pocID path string true "Point of Contact ID" -// @Success 200 {object} models.PointOfContact -// @Failure 400 {object} error -// @Failure 401 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /clubs/{clubID}/poc/{pocID} [patch] -func (cpoc *ClubPointOfContactController) UpdateClubPointOfContactPhoto(c *fiber.Ctx) error { - formFile, err := c.FormFile("file") - if err != nil { - return err - } - - pointOfContact, err := cpoc.clubPointOfContactService.UpdateClubPointOfContactPhoto(c.Params("clubID"), c.Params("pocID"), formFile) - if err != nil { - return err - } - - return c.Status(http.StatusOK).JSON(pointOfContact) -} - -// UpdateClubPointOfContact godoc -// -// @Summary Update a point of contact for a club -// @Description Updates a point of contact associated with a club -// @ID update-point-of-contact-by-club -// @Tags club-point-of-contact -// @Accept json -// @Produce json -// @Param clubID path string true "Club ID" -// @Param pocID path string true "Point of Contact ID" -// @Success 200 {object} models.PointOfContact -// @Failure 400 {object} error -// @Failure 401 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /clubs/{clubID}/poc/{pocID} [put] -func (cpoc *ClubPointOfContactController) UpdateClubPointOfContact(c *fiber.Ctx) error { - var pointOfContactBody UpdatePointOfContactBody - - if err := c.BodyParser(&pointOfContactBody); err != nil { - return utilities.InvalidJSON() - } - - pointOfContact, err := cpoc.clubPointOfContactService.UpdateClubPointOfContact(c.Params("clubID"), c.Params("pocID"), pointOfContactBody) - if err != nil { - return err - } - - return c.Status(http.StatusOK).JSON(pointOfContact) -} - -// CreateClubPointOfContact godoc -// -// @Summary Create a point of contact for a club -// @Description Creates a point of contact associated with a club -// @ID create-point-of-contact-by-club -// @Tags club-point-of-contact -// @Accept multipart/form-data -// @Produce json -// @Param clubID path string true "Club ID" -// @Success 201 {object} models.PointOfContact -// @Failure 400 {object} error -// @Failure 401 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /clubs/{clubID}/poc/ [post] -func (cpoc *ClubPointOfContactController) CreateClubPointOfContact(c *fiber.Ctx) error { - var pointOfContactBody CreatePointOfContactBody - - if err := c.BodyParser(&pointOfContactBody); err != nil { - return utilities.InvalidJSON() - } - - formFile, err := c.FormFile("file") - if err != nil { - return err - } - - pointOfContact, err := cpoc.clubPointOfContactService.CreateClubPointOfContact(c.Params("clubID"), pointOfContactBody, formFile) - if err != nil { - return err - } - - return c.Status(http.StatusCreated).JSON(pointOfContact) -} - -// DeleteClubPointOfContact godoc -// -// @Summary Delete a point of contact for a club -// @Description Delete a point of contact associated with a club -// @ID delete-point-of-contact-by-club -// @Tags club-point-of-contact -// @Produce json -// @Param clubID path string true "Club ID" -// @Param pocID path string true "Point of Contact ID" -// @Success 204 {object} nil -// @Failure 400 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /clubs/{clubID}/poc/{pocID} [delete] -func (cpoc *ClubPointOfContactController) DeleteClubPointOfContact(c *fiber.Ctx) error { - err := cpoc.clubPointOfContactService.DeleteClubPointOfContact(c.Params("clubID"), c.Params("pocID")) - if err != nil { - return err - } - - return c.SendStatus(http.StatusNoContent) -} diff --git a/backend/entities/clubs/pocs/routes.go b/backend/entities/clubs/pocs/routes.go deleted file mode 100644 index bd57f188c..000000000 --- a/backend/entities/clubs/pocs/routes.go +++ /dev/null @@ -1,36 +0,0 @@ -package pocs - -import ( - authMiddleware "github.com/GenerateNU/sac/backend/middleware/auth" - "github.com/GenerateNU/sac/backend/types" -) - -func ClubPointOfContact(clubParams types.RouteParams) { - clubPointOfContactController := NewClubPointOfContactController(NewClubPointOfContactService(clubParams.ServiceParams)) - - clubPointOfContacts := clubParams.Router.Group("/pocs") - - // api/v1/clubs/:clubID/pocs/* - clubPointOfContacts.Get("/", clubPointOfContactController.GetClubPointOfContacts) - clubPointOfContacts.Get("/:pocID", clubPointOfContactController.GetClubPointOfContact) - clubPointOfContacts.Post( - "/", - authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), - clubPointOfContactController.CreateClubPointOfContact, - ) - clubPointOfContacts.Patch( - "/:pocID", - authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), - clubPointOfContactController.UpdateClubPointOfContact, - ) - clubPointOfContacts.Patch( - "/:pocID/photo", - authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), - clubPointOfContactController.UpdateClubPointOfContactPhoto, - ) - clubPointOfContacts.Delete( - "/:pocID", - authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), - clubPointOfContactController.DeleteClubPointOfContact, - ) -} diff --git a/backend/entities/clubs/pocs/service.go b/backend/entities/clubs/pocs/service.go deleted file mode 100644 index 82472507e..000000000 --- a/backend/entities/clubs/pocs/service.go +++ /dev/null @@ -1,206 +0,0 @@ -package pocs - -import ( - "mime/multipart" - - "github.com/GenerateNU/sac/backend/entities/files/base" - "github.com/GenerateNU/sac/backend/entities/models" - - "github.com/GenerateNU/sac/backend/integrations/file" - "github.com/GenerateNU/sac/backend/types" - "github.com/GenerateNU/sac/backend/utilities" -) - -type ClubPointOfContactServiceInterface interface { - GetClubPointOfContacts(clubID string) ([]models.PointOfContact, error) - GetClubPointOfContact(clubID, pocID string) (*models.PointOfContact, error) - CreateClubPointOfContact(clubID string, pointOfContactBody CreatePointOfContactBody, fileHeader *multipart.FileHeader) (*models.PointOfContact, error) - UpdateClubPointOfContactPhoto(clubID, pocID string, fileHeader *multipart.FileHeader) (*models.PointOfContact, error) - UpdateClubPointOfContact(clubID, pocID string, pointOfContactBody UpdatePointOfContactBody) (*models.PointOfContact, error) - DeleteClubPointOfContact(clubID, pocID string) error -} - -type ClubPointOfContactService struct { - types.ServiceParams -} - -func NewClubPointOfContactService(serviceParams types.ServiceParams) ClubPointOfContactServiceInterface { - return &ClubPointOfContactService{serviceParams} -} - -func (cpoc *ClubPointOfContactService) GetClubPointOfContacts(clubID string) ([]models.PointOfContact, error) { - clubIdAsUUID, err := utilities.ValidateID(clubID) - if err != nil { - return nil, err - } - - return GetClubPointOfContacts(cpoc.DB, *clubIdAsUUID) -} - -func (cpoc *ClubPointOfContactService) GetClubPointOfContact(clubID, pocID string) (*models.PointOfContact, error) { - clubIdAsUUID, err := utilities.ValidateID(clubID) - if err != nil { - return nil, err - } - - pocIdAsUUID, err := utilities.ValidateID(pocID) - if err != nil { - return nil, err - } - - return GetClubPointOfContact(cpoc.DB, *clubIdAsUUID, *pocIdAsUUID) -} - -func (cpoc *ClubPointOfContactService) CreateClubPointOfContact(clubID string, pointOfContactBody CreatePointOfContactBody, fileHeader *multipart.FileHeader) (*models.PointOfContact, error) { - if err := utilities.Validate(cpoc.Validate, pointOfContactBody); err != nil { - return nil, err - } - - clubIdAsUUID, err := utilities.ValidateID(clubID) - if err != nil { - return nil, err - } - - _, err = GetClubPointOfContactByClubIDAndEmail(cpoc.DB, *clubIdAsUUID, pointOfContactBody.Email) - if err == nil { - return nil, err - } - - fileInfo, err := cpoc.Integrations.File.UploadFile("point_of_contacts", fileHeader, []file.FileType{file.IMAGE}) - if err != nil { - return nil, err - } - - tx := cpoc.DB.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - - poc, err := CreateClubPointOfContact(tx, *clubIdAsUUID, pointOfContactBody) - if err != nil { - tx.Rollback() - return nil, err - } - - file, err := base.CreateFile(tx, poc.ID, "point_of_contacts", *fileInfo) - if err != nil { - tx.Rollback() - return nil, err - } - - if err := tx.Commit().Error; err != nil { - if err := cpoc.Integrations.File.DeleteFile(fileInfo.FileURL); err != nil { - return nil, err - } - - return nil, err - } - - poc.PhotoFile = *file - - return poc, nil -} - -func (cpoc *ClubPointOfContactService) UpdateClubPointOfContactPhoto(clubID, pocID string, fileHeader *multipart.FileHeader) (*models.PointOfContact, error) { - clubIdAsUUID, err := utilities.ValidateID(clubID) - if err != nil { - return nil, err - } - - pocIdAsUUID, err := utilities.ValidateID(pocID) - if err != nil { - return nil, err - } - - pointOfContact, err := GetClubPointOfContact(cpoc.DB, *clubIdAsUUID, *pocIdAsUUID) - if err != nil { - return nil, err - } - - if err := cpoc.Integrations.File.DeleteFile(pointOfContact.PhotoFile.FileURL); err != nil { - return nil, err - } - - fileInfo, err := cpoc.Integrations.File.UploadFile("point_of_contacts", fileHeader, []file.FileType{file.IMAGE}) - if err != nil { - return nil, err - } - - file, err := base.UpdateFile(cpoc.DB, pointOfContact.PhotoFile.ID, *fileInfo) - if err != nil { - return nil, err - } - - pointOfContact.PhotoFile = *file - - return pointOfContact, nil -} - -func (cpoc *ClubPointOfContactService) UpdateClubPointOfContact(clubID, pocID string, pointOfContactBody UpdatePointOfContactBody) (*models.PointOfContact, error) { - if err := utilities.Validate(cpoc.Validate, pointOfContactBody); err != nil { - return nil, err - } - - clubIdAsUUID, err := utilities.ValidateID(clubID) - if err != nil { - return nil, err - } - - pocIdAsUUID, err := utilities.ValidateID(pocID) - if err != nil { - return nil, err - } - - return UpdateClubPointOfContact(cpoc.DB, *clubIdAsUUID, *pocIdAsUUID, pointOfContactBody) -} - -func (cpoc *ClubPointOfContactService) DeleteClubPointOfContact(clubID, pocID string) error { - clubIdAsUUID, err := utilities.ValidateID(clubID) - if err != nil { - return err - } - - pocIdAsUUID, err := utilities.ValidateID(pocID) - if err != nil { - return err - } - - pointOfContact, err := GetClubPointOfContact(cpoc.DB, *clubIdAsUUID, *pocIdAsUUID) - if err != nil { - return err - } - - if err := cpoc.Integrations.File.DeleteFile(pointOfContact.PhotoFile.FileURL); err != nil { - return err - } - - tx := cpoc.DB.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - if err := tx.Error; err != nil { - return err - } - - err = base.DeleteFile(tx, pointOfContact.PhotoFile.ID) - if err != nil { - tx.Rollback() - return err - } - - err = DeleteClubPointOfContact(tx, *clubIdAsUUID, *pocIdAsUUID) - if err != nil { - tx.Rollback() - return err - } - - if err := tx.Commit().Error; err != nil { - return err - } - - return nil -} diff --git a/backend/entities/clubs/pocs/transactions.go b/backend/entities/clubs/pocs/transactions.go deleted file mode 100644 index 5233fdfaa..000000000 --- a/backend/entities/clubs/pocs/transactions.go +++ /dev/null @@ -1,92 +0,0 @@ -package pocs - -import ( - "errors" - - "github.com/GenerateNU/sac/backend/entities/models" - - "github.com/GenerateNU/sac/backend/utilities" - "github.com/google/uuid" - "gorm.io/gorm" - "gorm.io/gorm/clause" -) - -func GetClubPointOfContacts(db *gorm.DB, clubID uuid.UUID) ([]models.PointOfContact, error) { - var pointOfContacts []models.PointOfContact - - result := db.Preload("PhotoFile").Where("club_id = ?", clubID).Find(&pointOfContacts) - if result.Error != nil { - if errors.Is(result.Error, gorm.ErrRecordNotFound) { - return nil, utilities.ErrNotFound - } - return nil, result.Error - } - - return pointOfContacts, nil -} - -func GetClubPointOfContact(db *gorm.DB, clubID uuid.UUID, pocID uuid.UUID) (*models.PointOfContact, error) { - var pointOfContact models.PointOfContact - - if err := db.Preload("PhotoFile").First(&pointOfContact, "id = ? AND club_id = ?", pocID, clubID).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, utilities.ErrNotFound - } - return nil, err - } - - return &pointOfContact, nil -} - -func GetClubPointOfContactByClubIDAndEmail(db *gorm.DB, clubID uuid.UUID, email string) (*models.PointOfContact, error) { - var pointOfContact models.PointOfContact - - if err := db.First(&pointOfContact, "email = ? AND club_id = ?", email, clubID).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, utilities.ErrNotFound - } - return nil, err - } - - return &pointOfContact, nil -} - -func CreateClubPointOfContact(db *gorm.DB, clubID uuid.UUID, pointOfContactBody CreatePointOfContactBody) (*models.PointOfContact, error) { - pointOfContact := models.PointOfContact{ - Name: pointOfContactBody.Name, - Email: pointOfContactBody.Email, - Position: pointOfContactBody.Position, - ClubID: clubID, - } - - if err := db.Create(&pointOfContact).Error; err != nil { - return nil, err - } - return &pointOfContact, nil -} - -func UpdateClubPointOfContact(db *gorm.DB, clubID uuid.UUID, pocID uuid.UUID, pointOfContactBody UpdatePointOfContactBody) (*models.PointOfContact, error) { - pointOfContact, err := GetClubPointOfContact(db, clubID, pocID) - if err != nil { - return nil, err - } - if err := db.Model(&pointOfContact).Clauses(clause.Returning{}).Updates(models.PointOfContact{ - Name: pointOfContactBody.Name, - Email: pointOfContactBody.Email, - Position: pointOfContactBody.Position, - }).Error; err != nil { - return nil, err - } - - return pointOfContact, nil -} - -func DeleteClubPointOfContact(db *gorm.DB, clubID uuid.UUID, pocID uuid.UUID) error { - if result := db.Delete(&models.PointOfContact{}, "id = ? AND club_id = ?", pocID, clubID); result.RowsAffected == 0 { - if result.Error == nil { - return utilities.ErrNotFound - } - return result.Error - } - return nil -} diff --git a/backend/entities/clubs/recruitment/controller.go b/backend/entities/clubs/recruitment/controller.go new file mode 100644 index 000000000..cf321d1f2 --- /dev/null +++ b/backend/entities/clubs/recruitment/controller.go @@ -0,0 +1,121 @@ +package recruitment + +import ( + "net/http" + + "github.com/GenerateNU/sac/backend/utilities" + "github.com/garrettladley/fiberpaginate" + "github.com/gofiber/fiber/v2" +) + +type ClubRecruitmentController struct { + clubRecruitmentService ClubRecruitmentServicer +} + +func NewClubRecruitmentController(clubRecruitmentService ClubRecruitmentServicer) *ClubRecruitmentController { + return &ClubRecruitmentController{clubRecruitmentService: clubRecruitmentService} +} + +func (cr *ClubRecruitmentController) CreateClubRecruitment(c *fiber.Ctx) error { + var body CreateClubRecruitmentRequestBody + if err := c.BodyParser(&body); err != nil { + return utilities.InvalidJSON() + } + + recruitment, err := cr.clubRecruitmentService.CreateClubRecruitment(c.UserContext(), c.Params("clubID"), body) + if err != nil { + return err + } + + return c.Status(http.StatusCreated).JSON(recruitment) +} + +func (cr *ClubRecruitmentController) GetClubRecruitment(c *fiber.Ctx) error { + recruitment, err := cr.clubRecruitmentService.GetClubRecruitment(c.UserContext(), c.Params("clubID")) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(recruitment) +} + +func (cr *ClubRecruitmentController) UpdateClubRecruitment(c *fiber.Ctx) error { + var body UpdateClubRecruitmentRequestBody + if err := c.BodyParser(&body); err != nil { + return utilities.InvalidJSON() + } + + recruitment, err := cr.clubRecruitmentService.UpdateClubRecruitment(c.UserContext(), c.Params("clubID"), body) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(recruitment) +} + +func (cr *ClubRecruitmentController) DeleteClubRecruitment(c *fiber.Ctx) error { + if err := cr.clubRecruitmentService.DeleteClubRecruitment(c.UserContext(), c.Params("clubID")); err != nil { + return err + } + + return c.SendStatus(http.StatusNoContent) +} + +func (cr *ClubRecruitmentController) CreateClubRecruitmentApplication(c *fiber.Ctx) error { + var body CreateApplicationRequestBody + if err := c.BodyParser(&body); err != nil { + return utilities.InvalidJSON() + } + + application, err := cr.clubRecruitmentService.CreateClubRecruitmentApplication(c.UserContext(), c.Params("clubID"), body) + if err != nil { + return err + } + + return c.Status(http.StatusCreated).JSON(application) +} + +func (cr *ClubRecruitmentController) GetClubRecruitmentApplications(c *fiber.Ctx) error { + pageInfo, ok := fiberpaginate.FromContext(c) + if !ok { + return utilities.ErrExpectedPageInfo + } + + applications, err := cr.clubRecruitmentService.GetClubRecruitmentApplications(c.UserContext(), c.Params("clubID"), *pageInfo) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(applications) +} + +func (cr *ClubRecruitmentController) GetClubRecruitmentApplication(c *fiber.Ctx) error { + application, err := cr.clubRecruitmentService.GetClubRecruitmentApplication(c.UserContext(), c.Params("clubID"), c.Params("applicationID")) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(application) +} + +func (cr *ClubRecruitmentController) UpdateClubRecruitmentApplication(c *fiber.Ctx) error { + var body UpdateApplicationRequestBody + if err := c.BodyParser(&body); err != nil { + return utilities.InvalidJSON() + } + + application, err := cr.clubRecruitmentService.UpdateClubRecruitmentApplication(c.UserContext(), c.Params("clubID"), c.Params("applicationID"), body) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(application) +} + +func (cr *ClubRecruitmentController) DeleteClubRecruitmentApplication(c *fiber.Ctx) error { + if err := cr.clubRecruitmentService.DeleteClubRecruitmentApplication(c.UserContext(), c.Params("clubID"), c.Params("applicationID")); err != nil { + return err + } + + return c.SendStatus(http.StatusNoContent) +} diff --git a/backend/entities/clubs/recruitment/models.go b/backend/entities/clubs/recruitment/models.go new file mode 100644 index 000000000..11c6e95e4 --- /dev/null +++ b/backend/entities/clubs/recruitment/models.go @@ -0,0 +1,24 @@ +package recruitment + +import "github.com/GenerateNU/sac/backend/entities/models" + +type CreateClubRecruitmentRequestBody struct { + Cycle models.RecruitmentCycle `json:"cycle" validate:"required,oneof=fall spring fallSpring always"` + Type models.RecruitmentType `json:"type" validate:"required,oneof=unrestricted tryout application"` +} + +type CreateApplicationRequestBody struct { + Title string `json:"title" validate:"required,max=255"` + Link string `json:"link" validate:"required,http_url,max=255"` +} + +type UpdateClubRecruitmentRequestBody struct { + Cycle models.RecruitmentCycle `json:"cycle" validate:"omitempty,oneof=fall spring fallSpring always"` + Type models.RecruitmentType `json:"type" validate:"omitempty,oneof=unrestricted tryout application"` + IsRecruiting *bool `json:"is_recruiting" validate:"omitempty"` +} + +type UpdateApplicationRequestBody struct { + Title string `json:"title" validate:"omitempty,max=255"` + Link string `json:"link" validate:"omitempty,http_url,max=255"` +} diff --git a/backend/entities/clubs/recruitment/routes.go b/backend/entities/clubs/recruitment/routes.go new file mode 100644 index 000000000..47ca3db20 --- /dev/null +++ b/backend/entities/clubs/recruitment/routes.go @@ -0,0 +1,69 @@ +package recruitment + +import ( + authMiddleware "github.com/GenerateNU/sac/backend/middleware/auth" + "github.com/GenerateNU/sac/backend/types" +) + +func ClubRecruitment(clubParams types.RouteParams) { + clubRecruitmentController := NewClubRecruitmentController(NewClubRecruitmentService(clubParams.ServiceParams)) + + // api/v1/clubs/:clubID/recruitment/* + clubRecruitment := clubParams.Router.Group("/recruitment") + + clubRecruitment.Get( + "/", + authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubRecruitmentController.GetClubRecruitment, + ) + clubRecruitment.Post( + "/", + authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubRecruitmentController.CreateClubRecruitment, + ) + clubRecruitment.Patch( + "/", + authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubRecruitmentController.UpdateClubRecruitment, + ) + + clubRecruitment.Delete( + "/", + authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubRecruitmentController.DeleteClubRecruitment, + ) + + // api/v1/clubs/:clubID/recruitment/applications/* + clubRecruitmentApplications := clubRecruitment.Group("/applications") + + clubRecruitmentApplications.Get( + "/", + clubParams.UtilityMiddleware.Paginator, + authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubRecruitmentController.GetClubRecruitmentApplications, + ) + clubRecruitmentApplications.Post( + "/", + authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubRecruitmentController.CreateClubRecruitmentApplication, + ) + + // api/v1/clubs/:clubID/recruitment/applications/:applicationID/* + clubRecruitmentApplicationsID := clubRecruitmentApplications.Group("/:applicationID") + + clubRecruitmentApplicationsID.Get( + "/", + authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubRecruitmentController.GetClubRecruitmentApplication, + ) + clubRecruitmentApplicationsID.Patch( + "/", + authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubRecruitmentController.UpdateClubRecruitmentApplication, + ) + clubRecruitmentApplicationsID.Delete( + "/", + authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubRecruitmentController.DeleteClubRecruitmentApplication, + ) +} diff --git a/backend/entities/clubs/recruitment/service.go b/backend/entities/clubs/recruitment/service.go new file mode 100644 index 000000000..a2696bb09 --- /dev/null +++ b/backend/entities/clubs/recruitment/service.go @@ -0,0 +1,201 @@ +package recruitment + +import ( + "context" + "time" + + "github.com/GenerateNU/sac/backend/constants" + "github.com/GenerateNU/sac/backend/entities/models" + "github.com/GenerateNU/sac/backend/errs" + "github.com/GenerateNU/sac/backend/types" + "github.com/GenerateNU/sac/backend/utilities" + "github.com/garrettladley/fiberpaginate" +) + +type ClubRecruitmentServicer interface { + CreateClubRecruitment(ctx context.Context, clubID string, body CreateClubRecruitmentRequestBody) (*models.Recruitment, error) + GetClubRecruitment(ctx context.Context, clubID string) (*models.Recruitment, error) + UpdateClubRecruitment(ctx context.Context, clubID string, body UpdateClubRecruitmentRequestBody) (*models.Recruitment, error) + DeleteClubRecruitment(ctx context.Context, clubID string) error + + CreateClubRecruitmentApplication(ctx context.Context, clubID string, body CreateApplicationRequestBody) (*models.Application, error) + GetClubRecruitmentApplications(ctx context.Context, clubID string, pageInfo fiberpaginate.PageInfo) ([]models.Application, error) + + GetClubRecruitmentApplication(ctx context.Context, clubID string, applicationID string) (*models.Application, error) + UpdateClubRecruitmentApplication(ctx context.Context, clubID string, applicationID string, body UpdateApplicationRequestBody) (*models.Application, error) + DeleteClubRecruitmentApplication(ctx context.Context, clubID string, applicationID string) error +} + +type ClubRecruitmentService struct { + types.ServiceParams +} + +func NewClubRecruitmentService(params types.ServiceParams) ClubRecruitmentServicer { + return &ClubRecruitmentService{params} +} + +func (s *ClubRecruitmentService) CreateClubRecruitment(ctx context.Context, clubID string, body CreateClubRecruitmentRequestBody) (*models.Recruitment, error) { + idAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, err + } + + if err := utilities.Validate(s.Validate, body); err != nil { + return nil, err + } + + recruitment, err := utilities.MapJsonTags(body, &models.Recruitment{}) + if err != nil { + return nil, err + } + + createClubRecruitmentCtx, createClubRecruitmentCancel := context.WithTimeoutCause(ctx, constants.DB_TIMEOUT+1*time.Second, errs.ErrDatabaseTimeout) + defer createClubRecruitmentCancel() + return CreateClubRecruitment(createClubRecruitmentCtx, s.DB, idAsUUID, recruitment) +} + +func (s *ClubRecruitmentService) GetClubRecruitment(ctx context.Context, clubID string) (*models.Recruitment, error) { + idAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, err + } + + getClubRecruitmentCtx, getClubRecruitmentCancel := context.WithTimeoutCause(ctx, constants.DB_TIMEOUT, errs.ErrDatabaseTimeout) + defer getClubRecruitmentCancel() + + return GetClubRecruitment(getClubRecruitmentCtx, s.DB, idAsUUID) +} + +func (s *ClubRecruitmentService) UpdateClubRecruitment(ctx context.Context, clubID string, body UpdateClubRecruitmentRequestBody) (*models.Recruitment, error) { + clubIDAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, err + } + + if utilities.AtLeastOne(body, UpdateClubRecruitmentRequestBody{}) { + return nil, errs.ErrAtLeastOne + } + + if err := utilities.Validate(s.Validate, body); err != nil { + return nil, err + } + + recruitment, err := utilities.MapJsonTags(body, &models.Recruitment{}) + if err != nil { + return nil, err + } + + updateClubRecruitmentCtx, updateClubRecruitmentCancel := context.WithTimeoutCause(ctx, constants.DB_TIMEOUT, errs.ErrDatabaseTimeout) + defer updateClubRecruitmentCancel() + + return UpdateClubRecruitment(updateClubRecruitmentCtx, s.DB, clubIDAsUUID, recruitment) +} + +func (s *ClubRecruitmentService) DeleteClubRecruitment(ctx context.Context, clubID string) error { + idAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return err + } + + deleteClubRecruitmentCtx, deleteClubRecruitmentCancel := context.WithTimeoutCause(ctx, constants.DB_TIMEOUT, errs.ErrDatabaseTimeout) + defer deleteClubRecruitmentCancel() + + return DeleteClubRecruitment(deleteClubRecruitmentCtx, s.DB, idAsUUID) +} + +func (s *ClubRecruitmentService) CreateClubRecruitmentApplication(ctx context.Context, clubID string, body CreateApplicationRequestBody) (*models.Application, error) { + clubIDAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, err + } + + if err := utilities.Validate(s.Validate, body); err != nil { + return nil, err + } + + application, err := utilities.MapJsonTags(body, &models.Application{}) + if err != nil { + return nil, err + } + + createApplicationCtx, createApplicationCancel := context.WithTimeoutCause(ctx, constants.DB_TIMEOUT, errs.ErrDatabaseTimeout) + defer createApplicationCancel() + + return CreateApplication(createApplicationCtx, s.DB, clubIDAsUUID, application) +} + +func (s *ClubRecruitmentService) GetClubRecruitmentApplications(ctx context.Context, clubID string, pageInfo fiberpaginate.PageInfo) ([]models.Application, error) { + clubIDAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, err + } + + getClubRecruitmentApplicationsCtx, getClubRecruitmentApplicationsCancel := context.WithTimeoutCause(ctx, constants.DB_TIMEOUT, errs.ErrDatabaseTimeout) + defer getClubRecruitmentApplicationsCancel() + + return GetClubRecruitmentApplications(getClubRecruitmentApplicationsCtx, s.DB, clubIDAsUUID, pageInfo) +} + +func (s *ClubRecruitmentService) GetClubRecruitmentApplication(ctx context.Context, clubID string, applicationID string) (*models.Application, error) { + clubIDAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, err + } + + applicationIDAsUUID, err := utilities.ValidateID(applicationID) + if err != nil { + return nil, err + } + + getClubRecruitmentApplicationCtx, getClubRecruitmentApplicationCancel := context.WithTimeoutCause(ctx, constants.DB_TIMEOUT, errs.ErrDatabaseTimeout) + defer getClubRecruitmentApplicationCancel() + + return GetClubRecruitmentApplication(getClubRecruitmentApplicationCtx, s.DB, clubIDAsUUID, applicationIDAsUUID) +} + +func (s *ClubRecruitmentService) UpdateClubRecruitmentApplication(ctx context.Context, clubID string, applicationID string, body UpdateApplicationRequestBody) (*models.Application, error) { + clubIDAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, err + } + + applicationIDAsUUID, err := utilities.ValidateID(applicationID) + if err != nil { + return nil, err + } + + if utilities.AtLeastOne(body, UpdateApplicationRequestBody{}) { + return nil, errs.ErrAtLeastOne + } + + if err := utilities.Validate(s.Validate, body); err != nil { + return nil, err + } + + application, err := utilities.MapJsonTags(body, &models.Application{}) + if err != nil { + return nil, err + } + + updateClubRecruitmentApplicationCtx, updateClubRecruitmentApplicationCancel := context.WithTimeoutCause(ctx, constants.DB_TIMEOUT, errs.ErrDatabaseTimeout) + defer updateClubRecruitmentApplicationCancel() + + return UpdateClubRecruitmentApplication(updateClubRecruitmentApplicationCtx, s.DB, clubIDAsUUID, applicationIDAsUUID, application) +} + +func (s *ClubRecruitmentService) DeleteClubRecruitmentApplication(ctx context.Context, clubID string, applicationID string) error { + clubIDAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return err + } + + applicationIDAsUUID, err := utilities.ValidateID(applicationID) + if err != nil { + return err + } + + deleteClubRecruitmentApplicationCtx, deleteClubRecruitmentApplicationCancel := context.WithTimeoutCause(ctx, constants.DB_TIMEOUT, errs.ErrDatabaseTimeout) + defer deleteClubRecruitmentApplicationCancel() + + return DeleteClubRecruitmentApplication(deleteClubRecruitmentApplicationCtx, s.DB, clubIDAsUUID, applicationIDAsUUID) +} diff --git a/backend/entities/clubs/recruitment/transactions.go b/backend/entities/clubs/recruitment/transactions.go new file mode 100644 index 000000000..05a90a59e --- /dev/null +++ b/backend/entities/clubs/recruitment/transactions.go @@ -0,0 +1,144 @@ +package recruitment + +import ( + "context" + "errors" + + "github.com/GenerateNU/sac/backend/entities/clubs" + "github.com/GenerateNU/sac/backend/entities/models" + "github.com/GenerateNU/sac/backend/transactions" + "github.com/GenerateNU/sac/backend/utilities" + "github.com/garrettladley/fiberpaginate" + "github.com/google/uuid" + "gorm.io/gorm" +) + +func CreateClubRecruitment(ctx context.Context, db *gorm.DB, clubID *uuid.UUID, recruitment *models.Recruitment) (*models.Recruitment, error) { + _, err := clubs.GetClub(db, *clubID) + if err != nil { + return nil, err + } + + recruitment.ClubID = *clubID + + if err := db.WithContext(ctx).Save(recruitment).Error; err != nil { + return nil, err + } + + return recruitment, nil +} + +func GetClubRecruitment(ctx context.Context, db *gorm.DB, clubID *uuid.UUID) (*models.Recruitment, error) { + club, err := clubs.GetClub(db, *clubID) + if err != nil { + return nil, err + } + + var recruitment models.Recruitment + if err := db.WithContext(ctx).Model(&club).Association("Recruitment").Find(&recruitment); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, utilities.ErrNotFound + } + + return nil, err + } + + return &recruitment, nil +} + +func UpdateClubRecruitment(ctx context.Context, db *gorm.DB, clubID *uuid.UUID, recruitment *models.Recruitment) (*models.Recruitment, error) { + existingRecruitment, err := GetClubRecruitment(ctx, db, clubID) + if err != nil { + return nil, err + } + + if err := db.WithContext(ctx).Model(existingRecruitment).Updates(recruitment).Error; err != nil { + return nil, err + } + + return existingRecruitment, nil +} + +func DeleteClubRecruitment(ctx context.Context, db *gorm.DB, clubID *uuid.UUID) error { + club, err := clubs.GetClub(db, *clubID, transactions.PreloadRecruitment()) + if err != nil { + return err + } + + if err := db.WithContext(ctx).Model(&club).Association("Recruitment").Delete(); err != nil { + return err + } + + return nil +} + +func CreateApplication(ctx context.Context, db *gorm.DB, clubID *uuid.UUID, application *models.Application) (*models.Application, error) { + recruitment, err := GetClubRecruitment(ctx, db, clubID) + if err != nil { + return nil, err + } + + if err := db.WithContext(ctx).Model(&recruitment).Association("Application").Append(application); err != nil { + return nil, err + } + + return application, nil +} + +func GetClubRecruitmentApplications(ctx context.Context, db *gorm.DB, clubID *uuid.UUID, pageInfo fiberpaginate.PageInfo) ([]models.Application, error) { + recruitment, err := GetClubRecruitment(ctx, db, clubID) + if err != nil { + return nil, err + } + + var applications []models.Application + if err := db.WithContext(ctx).Scopes(utilities.IntoScope(pageInfo, db)).Model(&recruitment).Association("Application").Find(&applications); err != nil { + return nil, err + } + + return applications, nil +} + +func GetClubRecruitmentApplication(ctx context.Context, db *gorm.DB, clubID *uuid.UUID, applicationID *uuid.UUID) (*models.Application, error) { + recruitment, err := GetClubRecruitment(ctx, db, clubID) + if err != nil { + return nil, err + } + + var application models.Application + if err := db.WithContext(ctx).Model(&recruitment).Association("Application").Find(&application, applicationID); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, utilities.ErrNotFound + } + + return nil, err + } + + return &application, nil +} + +func UpdateClubRecruitmentApplication(ctx context.Context, db *gorm.DB, clubID *uuid.UUID, applicationID *uuid.UUID, application *models.Application) (*models.Application, error) { + existingApplication, err := GetClubRecruitmentApplication(ctx, db, clubID, applicationID) + if err != nil { + return nil, err + } + + if err := db.WithContext(ctx).Model(existingApplication).Updates(application).Error; err != nil { + return nil, err + } + + return existingApplication, nil +} + +func DeleteClubRecruitmentApplication(ctx context.Context, db *gorm.DB, clubID *uuid.UUID, applicationID *uuid.UUID) error { + application, err := GetClubRecruitmentApplication(ctx, db, clubID, applicationID) + if err != nil { + return err + } + + if err := db.WithContext(ctx).Delete(application).Error; err != nil { + return err + } + + return nil +} diff --git a/backend/entities/clubs/socials/controller.go b/backend/entities/clubs/socials/controller.go new file mode 100644 index 000000000..c573e5baf --- /dev/null +++ b/backend/entities/clubs/socials/controller.go @@ -0,0 +1,69 @@ +package socials + +import ( + "net/http" + + "github.com/GenerateNU/sac/backend/utilities" + "github.com/gofiber/fiber/v2" +) + +type ClubSocialController struct { + clubSocialService ClubSocialServiceInterface +} + +func NewClubSocialController(clubSocialService ClubSocialServiceInterface) *ClubSocialController { + return &ClubSocialController{clubSocialService: clubSocialService} +} + +// GetClubSocials godoc +// +// @Summary Retrieve all socials for a club +// @Description Retrieves all socials associated with a club +// @ID get-socials-by-club +// @Tags club-social +// @Produce json +// @Param clubID path string true "Club ID" +// @Success 200 {object} []models.Social +// @Failure 400 {object} error +// @Failure 404 {object} error +// @Failure 500 {object} error +// @Router /clubs/{clubID}/socials/ [get] +func (cc *ClubSocialController) GetClubSocials(c *fiber.Ctx) error { + socials, err := cc.clubSocialService.GetClubSocials(c.Params("clubID")) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(socials) +} + +// PutSocial godoc +// +// @Summary Creates a social +// @Description Creates a social +// @ID put-social +// @Tags club-social +// @Accept json +// @Produce json +// @Param clubID path string true "Club ID" +// @Param socialBody body PutSocialRequestBody true "Social Body" +// @Success 201 {object} models.Social +// @Failure 400 {object} error +// @Failure 401 {object} error +// @Failure 404 {object} error +// @Failure 500 {object} error +// @Router /clubs/{clubID}/socials/ [put] +func (cc *ClubSocialController) PutSocial(c *fiber.Ctx) error { + var socialBody PutSocialRequestBody + + if err := c.BodyParser(&socialBody); err != nil { + return utilities.InvalidJSON() + } + + social, err := cc.clubSocialService.PutClubSocial(c.Params("clubID"), socialBody) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(social) +} diff --git a/backend/entities/clubs/socials/models.go b/backend/entities/clubs/socials/models.go new file mode 100644 index 000000000..74f789c18 --- /dev/null +++ b/backend/entities/clubs/socials/models.go @@ -0,0 +1,8 @@ +package socials + +import "github.com/GenerateNU/sac/backend/entities/models" + +type PutSocialRequestBody struct { + Type models.SocialType `json:"type" validate:"required,max=255,oneof=facebook instagram x linkedin youtube github slack discord email customSite"` + Content string `json:"content" validate:"required,social_pointer,max=255"` +} diff --git a/backend/entities/clubs/socials/routes.go b/backend/entities/clubs/socials/routes.go new file mode 100644 index 000000000..91e4274d8 --- /dev/null +++ b/backend/entities/clubs/socials/routes.go @@ -0,0 +1,20 @@ +package socials + +import ( + authMiddleware "github.com/GenerateNU/sac/backend/middleware/auth" + "github.com/GenerateNU/sac/backend/types" +) + +func ClubSocial(clubParams types.RouteParams) { + clubSocialController := NewClubSocialController(NewClubSocialService(clubParams.ServiceParams)) + + clubSocials := clubParams.Router.Group("/socials") + + // api/v1/clubs/:clubID/socials/* + clubSocials.Get("/", clubSocialController.GetClubSocials) + clubSocials.Put( + "/", + authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubSocialController.PutSocial, + ) +} diff --git a/backend/entities/clubs/socials/service.go b/backend/entities/clubs/socials/service.go new file mode 100644 index 000000000..4f5403563 --- /dev/null +++ b/backend/entities/clubs/socials/service.go @@ -0,0 +1,49 @@ +package socials + +import ( + "github.com/GenerateNU/sac/backend/entities/models" + "github.com/GenerateNU/sac/backend/types" + "github.com/GenerateNU/sac/backend/utilities" +) + +type ClubSocialServiceInterface interface { + GetClubSocials(clubID string) ([]models.Social, error) + PutClubSocial(clubID string, contactBody PutSocialRequestBody) (*models.Social, error) +} + +type ClubSocialService struct { + types.ServiceParams +} + +func NewClubSocialService(params types.ServiceParams) ClubSocialServiceInterface { + return &ClubSocialService{params} +} + +func (c *ClubSocialService) GetClubSocials(clubID string) ([]models.Social, error) { + idAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, err + } + + return GetClubSocials(c.DB, *idAsUUID) +} + +func (c *ClubSocialService) PutClubSocial(clubID string, contactBody PutSocialRequestBody) (*models.Social, error) { + idAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, err + } + + if err := utilities.Validate(c.Validate, contactBody); err != nil { + return nil, err + } + + contact, err := utilities.MapJsonTags(contactBody, &models.Social{}) + if err != nil { + return nil, err + } + + contact.ClubID = *idAsUUID + + return PutClubSocial(c.DB, *contact) +} diff --git a/backend/entities/clubs/contacts/transactions.go b/backend/entities/clubs/socials/transactions.go similarity index 60% rename from backend/entities/clubs/contacts/transactions.go rename to backend/entities/clubs/socials/transactions.go index 5c8ce2bd1..efd369e38 100644 --- a/backend/entities/clubs/contacts/transactions.go +++ b/backend/entities/clubs/socials/transactions.go @@ -1,8 +1,7 @@ -package contacts +package socials import ( "errors" - "fmt" "github.com/GenerateNU/sac/backend/entities/models" @@ -12,33 +11,29 @@ import ( "gorm.io/gorm/clause" ) -func PutClubContact(db *gorm.DB, contact models.Contact) (*models.Contact, error) { +func PutClubSocial(db *gorm.DB, social models.Social) (*models.Social, error) { err := db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "club_id"}, {Name: "type"}}, DoUpdates: clause.AssignmentColumns([]string{"content"}), - }).Create(&contact).Error + }).Create(&social).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) || errors.Is(err, gorm.ErrForeignKeyViolated) { return nil, utilities.ErrNotFound } return nil, err } - return &contact, nil + return &social, nil } -func GetClubContacts(db *gorm.DB, clubID uuid.UUID) ([]models.Contact, error) { +func GetClubSocials(db *gorm.DB, clubID uuid.UUID) ([]models.Social, error) { var club models.Club - if err := db.Preload("Contact").First(&club, clubID).Error; err != nil { + if err := db.Preload("Social").First(&club, clubID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, utilities.ErrNotFound } return nil, err } - if club.Contact == nil { - return nil, fmt.Errorf("club with ID %s has no contacts", clubID) - } - - return club.Contact, nil + return club.Social, nil } diff --git a/backend/entities/clubs/transactions.go b/backend/entities/clubs/transactions.go index d501886d4..c00086607 100644 --- a/backend/entities/clubs/transactions.go +++ b/backend/entities/clubs/transactions.go @@ -36,7 +36,7 @@ func GetAdminIDs(db *gorm.DB, clubID uuid.UUID) ([]uuid.UUID, error) { return nil, err } - adminUUIDs := make([]uuid.UUID, 0) + adminUUIDs := make([]uuid.UUID, len(adminIDs)) for _, adminID := range adminIDs { adminUUIDs = append(adminUUIDs, adminID.ClubID) } diff --git a/backend/entities/contacts/base/controller.go b/backend/entities/contacts/base/controller.go deleted file mode 100644 index 46d8b3670..000000000 --- a/backend/entities/contacts/base/controller.go +++ /dev/null @@ -1,91 +0,0 @@ -package base - -import ( - "net/http" - - "github.com/GenerateNU/sac/backend/utilities" - "github.com/garrettladley/fiberpaginate" - "github.com/gofiber/fiber/v2" -) - -type ContactController struct { - contactService ContactServiceInterface -} - -func NewContactController(contactService ContactServiceInterface) *ContactController { - return &ContactController{contactService: contactService} -} - -// GetContact godoc -// -// @Summary Retrieves a contact -// @Description Retrieves a contact by id -// @ID get-contact -// @Tags contact -// @Accept json -// @Produce json -// @Param contactID path string true "Contact ID" -// @Success 201 {object} models.Contact -// @Failure 400 {string} error -// @Failure 404 {string} error -// @Failure 500 {string} error -// @Router /contacts/{contactID}/ [get] -func (co *ContactController) GetContact(c *fiber.Ctx) error { - contact, err := co.contactService.GetContact(c.Params("contactID")) - if err != nil { - return err - } - - return c.Status(http.StatusOK).JSON(contact) -} - -// GetContacts godoc -// -// @Summary Retrieve all contacts -// @Description Retrieves all contacts -// @ID get-contacts -// @Tags contact -// @Produce json -// @Param limit query int false "Limit" -// @Param page query int false "Page" -// @Success 200 {object} []models.Contact -// @Failure 400 {string} error -// @Failure 404 {string} error -// @Failure 500 {string} error -// @Router /contacts/ [get] -func (co *ContactController) GetContacts(c *fiber.Ctx) error { - pagination, ok := fiberpaginate.FromContext(c) - if !ok { - return utilities.ErrExpectedPagination - } - - contacts, err := co.contactService.GetContacts(*pagination) - if err != nil { - return err - } - - return c.Status(http.StatusOK).JSON(contacts) -} - -// DeleteContact godoc -// -// @Summary Deletes a contact -// @Description Deletes a contact -// @ID delete-contact -// @Tags contact -// @Accept json -// @Produce json -// @Param contactID path string true "Contact ID" -// @Success 201 {object} models.Contact -// @Failure 400 {string} error -// @Failure 404 {string} error -// @Failure 500 {string} error -// @Router /contacts/{contactID}/ [delete] -func (co *ContactController) DeleteContact(c *fiber.Ctx) error { - err := co.contactService.DeleteContact(c.Params("contactID")) - if err != nil { - return err - } - - return c.SendStatus(http.StatusNoContent) -} diff --git a/backend/entities/contacts/base/routes.go b/backend/entities/contacts/base/routes.go deleted file mode 100644 index 453705783..000000000 --- a/backend/entities/contacts/base/routes.go +++ /dev/null @@ -1,17 +0,0 @@ -package base - -import ( - "github.com/GenerateNU/sac/backend/auth" - "github.com/GenerateNU/sac/backend/types" -) - -func Contact(contactParams types.RouteParams) { - contactController := NewContactController(NewContactService(contactParams.ServiceParams)) - - // api/v1/contacts/* - contacts := contactParams.Router.Group("/contacts") - - contacts.Get("/", contactParams.UtilityMiddleware.Paginator, contactController.GetContacts) - contacts.Get("/:contactID", contactController.GetContact) - contacts.Delete("/:contactID", contactParams.AuthMiddleware.Authorize(auth.DeleteAll), contactController.DeleteContact) -} diff --git a/backend/entities/contacts/base/service.go b/backend/entities/contacts/base/service.go deleted file mode 100644 index 6b5967b2b..000000000 --- a/backend/entities/contacts/base/service.go +++ /dev/null @@ -1,44 +0,0 @@ -package base - -import ( - "github.com/GenerateNU/sac/backend/entities/models" - "github.com/GenerateNU/sac/backend/types" - "github.com/GenerateNU/sac/backend/utilities" - "github.com/garrettladley/fiberpaginate" -) - -type ContactServiceInterface interface { - GetContacts(pageInfo fiberpaginate.PageInfo) ([]models.Contact, error) - GetContact(contactID string) (*models.Contact, error) - DeleteContact(contactID string) error -} - -type ContactService struct { - types.ServiceParams -} - -func NewContactService(serviceParams types.ServiceParams) ContactServiceInterface { - return &ContactService{serviceParams} -} - -func (c *ContactService) GetContacts(pageInfo fiberpaginate.PageInfo) ([]models.Contact, error) { - return GetContacts(c.DB, pageInfo) -} - -func (c *ContactService) GetContact(contactID string) (*models.Contact, error) { - idAsUUID, err := utilities.ValidateID(contactID) - if err != nil { - return nil, err - } - - return GetContact(c.DB, *idAsUUID) -} - -func (c *ContactService) DeleteContact(contactID string) error { - idAsUUID, err := utilities.ValidateID(contactID) - if err != nil { - return err - } - - return DeleteContact(c.DB, *idAsUUID) -} diff --git a/backend/entities/contacts/base/transactions.go b/backend/entities/contacts/base/transactions.go deleted file mode 100644 index fa21fceab..000000000 --- a/backend/entities/contacts/base/transactions.go +++ /dev/null @@ -1,42 +0,0 @@ -package base - -import ( - "errors" - - "github.com/GenerateNU/sac/backend/entities/models" - "github.com/GenerateNU/sac/backend/utilities" - "github.com/garrettladley/fiberpaginate" - "github.com/google/uuid" - "gorm.io/gorm" -) - -func GetContacts(db *gorm.DB, pageInfo fiberpaginate.PageInfo) ([]models.Contact, error) { - var contacts []models.Contact - if err := db.Scopes(utilities.IntoScope(pageInfo, db)).Find(&contacts).Error; err != nil { - return nil, err - } - - return contacts, nil -} - -func GetContact(db *gorm.DB, id uuid.UUID) (*models.Contact, error) { - var contact models.Contact - if err := db.First(&contact, id).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, err - } - return nil, err - } - - return &contact, nil -} - -func DeleteContact(db *gorm.DB, id uuid.UUID) error { - if result := db.Delete(&models.Contact{}, id); result.RowsAffected == 0 { - if result.Error == nil { - return utilities.ErrNotFound - } - return result.Error - } - return nil -} diff --git a/backend/entities/events/base/controller.go b/backend/entities/events/base/controller.go index e53934759..f144be5b8 100644 --- a/backend/entities/events/base/controller.go +++ b/backend/entities/events/base/controller.go @@ -32,17 +32,32 @@ func NewEventController(eventService EventServiceInterface) *EventController { // @Failure 500 {object} error // @Router /events/ [get] func (e *EventController) GetAllEvents(c *fiber.Ctx) error { - pagination, ok := fiberpaginate.FromContext(c) + pageInfo, ok := fiberpaginate.FromContext(c) if !ok { - return utilities.ErrExpectedPagination + return utilities.ErrExpectedPageInfo } - events, err := e.eventService.GetEvents(*pagination) - if err != nil { - return err + usePagination := c.QueryBool("pagination", true) + if !usePagination { + pageInfo = nil } - return c.Status(http.StatusOK).JSON(events) + start := c.Query("start") + end := c.Query("end") + + if c.QueryBool("preview", false) { + events, err := e.eventService.GetEventsPreview(*pageInfo, start, end) + if err != nil { + return err + } + return c.Status(http.StatusOK).JSON(events) + } else { + events, err := e.eventService.GetEvents(*pageInfo, start, end) + if err != nil { + return err + } + return c.Status(http.StatusOK).JSON(events) + } } // GetEvent godoc diff --git a/backend/entities/events/base/models.go b/backend/entities/events/base/models.go new file mode 100644 index 000000000..ad8f9a120 --- /dev/null +++ b/backend/entities/events/base/models.go @@ -0,0 +1,21 @@ +package base + +import ( + "time" + + "github.com/GenerateNU/sac/backend/entities/models" + "github.com/google/uuid" +) + +type EventPreview struct { + ID uuid.UUID `json:"id"` + + Title string `json:"title"` + + EventType models.EventType `json:"event_type"` + Location string `json:"location"` + Link string `json:"link"` + + StartTime time.Time `json:"start_time"` + EndTime time.Time `json:"end_time"` +} diff --git a/backend/entities/events/base/service.go b/backend/entities/events/base/service.go index 0f7c9fa1e..9f971867e 100644 --- a/backend/entities/events/base/service.go +++ b/backend/entities/events/base/service.go @@ -1,6 +1,8 @@ package base import ( + "errors" + "github.com/GenerateNU/sac/backend/entities/events" "github.com/GenerateNU/sac/backend/entities/models" @@ -11,7 +13,8 @@ import ( type EventServiceInterface interface { // getters - GetEvents(pageInfo fiberpaginate.PageInfo) ([]models.Event, error) + GetEvents(pageInfo fiberpaginate.PageInfo, start string, end string) ([]models.Event, error) + GetEventsPreview(pageInfo fiberpaginate.PageInfo, start string, end string) ([]EventPreview, error) GetEvent(eventID string) (*models.Event, error) // event cud CreateEvent(body events.CreateEventRequestBody) (*models.Event, error) @@ -27,8 +30,48 @@ func NewEventService(serviceParams types.ServiceParams) EventServiceInterface { return &EventService{serviceParams} } -func (e *EventService) GetEvents(pageInfo fiberpaginate.PageInfo) ([]models.Event, error) { - return GetEvents(e.DB, pageInfo) +func (e *EventService) GetEvents(pageInfo fiberpaginate.PageInfo, start string, end string) ([]models.Event, error) { + if start != "" && end != "" { + return GetEvents(e.DB, pageInfo) + } + + startTime, err := utilities.ParseTime(start, utilities.YYYY_dash_MM_dash_DD) + if err != nil { + return nil, utilities.BadRequest(err) + } + + endTime, err := utilities.ParseTime(end, utilities.YYYY_dash_MM_dash_DD) + if err != nil { + return nil, utilities.BadRequest(err) + } + + if startTime.After(endTime) { + return nil, utilities.BadRequest(errors.New("start time must be before end time")) + } + + return GetEventsByTime(e.DB, pageInfo, startTime, endTime) +} + +func (e *EventService) GetEventsPreview(pageInfo fiberpaginate.PageInfo, start string, end string) ([]EventPreview, error) { + if start != "" && end != "" { + return GetEventsPreview(e.DB, pageInfo) + } + + startTime, err := utilities.ParseTime(start, utilities.YYYY_dash_MM_dash_DD) + if err != nil { + return nil, utilities.BadRequest(err) + } + + endTime, err := utilities.ParseTime(end, utilities.YYYY_dash_MM_dash_DD) + if err != nil { + return nil, utilities.BadRequest(err) + } + + if startTime.After(endTime) { + return nil, utilities.BadRequest(errors.New("start time must be before end time")) + } + + return GetEventsPreviewByTime(e.DB, pageInfo, startTime, endTime) } func (e *EventService) GetEvent(eventID string) (*models.Event, error) { diff --git a/backend/entities/events/base/transactions.go b/backend/entities/events/base/transactions.go index 10b47a15e..961bc069a 100644 --- a/backend/entities/events/base/transactions.go +++ b/backend/entities/events/base/transactions.go @@ -3,6 +3,7 @@ package base import ( "errors" "log/slog" + "time" "github.com/GenerateNU/sac/backend/constants" "github.com/GenerateNU/sac/backend/entities/events" @@ -18,12 +19,19 @@ import ( ) func GetEvents(db *gorm.DB, pageInfo fiberpaginate.PageInfo) ([]models.Event, error) { - var events []models.Event - if err := db.Scopes(utilities.IntoScope(pageInfo, db)).Find(&events).Error; err != nil { - return nil, err - } + return getEvents[models.Event](db, &pageInfo, nil, nil) +} - return events, nil +func GetEventsPreview(db *gorm.DB, pageInfo fiberpaginate.PageInfo) ([]EventPreview, error) { + return getEvents[EventPreview](db, &pageInfo, nil, nil) +} + +func GetEventsByTime(db *gorm.DB, pageInfo fiberpaginate.PageInfo, startTime time.Time, endTime time.Time) ([]models.Event, error) { + return getEvents[models.Event](db, &pageInfo, &startTime, &endTime) +} + +func GetEventsPreviewByTime(db *gorm.DB, pageInfo fiberpaginate.PageInfo, startTime time.Time, endTime time.Time) ([]EventPreview, error) { + return getEvents[EventPreview](db, &pageInfo, &startTime, &endTime) } func CreateEvent(db *gorm.DB, event models.Event) (*models.Event, error) { @@ -101,3 +109,22 @@ func DeleteEvent(db *gorm.DB, id uuid.UUID) error { return nil } + +func getEvents[T any](db *gorm.DB, pageInfo *fiberpaginate.PageInfo, startTime *time.Time, endTime *time.Time) ([]T, error) { + var events []T + query := db.Model(&events) + + if startTime != nil && endTime != nil { + query = query.Where("start_time >= ? AND end_time <= ?", *startTime, *endTime) + } + + if pageInfo != nil { + query = query.Scopes(utilities.IntoScope(*pageInfo, db)) + } + + if err := query.Find(&events).Error; err != nil { + return nil, err + } + + return events, nil +} diff --git a/backend/entities/files/base/controller.go b/backend/entities/files/base/controller.go index de0efaed3..48c8a58b2 100644 --- a/backend/entities/files/base/controller.go +++ b/backend/entities/files/base/controller.go @@ -32,12 +32,12 @@ func NewFileController(fileService FileServiceInterface) *FileController { // @Failure 500 {object} error // @Router /files/ [get] func (f *FileController) GetFiles(c *fiber.Ctx) error { - pagination, ok := fiberpaginate.FromContext(c) + pageInfo, ok := fiberpaginate.FromContext(c) if !ok { - return utilities.ErrExpectedPagination + return utilities.ErrExpectedPageInfo } - files, err := f.fileService.GetFiles(*pagination) + files, err := f.fileService.GetFiles(*pageInfo) if err != nil { return err } diff --git a/backend/entities/leadership/base/controller.go b/backend/entities/leadership/base/controller.go new file mode 100644 index 000000000..ee8efc6b7 --- /dev/null +++ b/backend/entities/leadership/base/controller.go @@ -0,0 +1,67 @@ +package base + +import ( + "net/http" + + "github.com/GenerateNU/sac/backend/utilities" + "github.com/garrettladley/fiberpaginate" + "github.com/gofiber/fiber/v2" +) + +type LeaderController struct { + leaderService LeaderServiceInterface +} + +func NewLeaderController(leaderService LeaderServiceInterface) *LeaderController { + return &LeaderController{leaderService: leaderService} +} + +// GetLeaders godoc +// +// @Summary Retrieve all point of contacts +// @Description Retrieves all point of contacts +// @ID get-point-of-contacts +// @Tags point of contact +// @Produce json +// @Param limit query int false "Limit" +// @Param page query int false "Page" +// @Success 200 {object} []models.Leader +// @Failure 400 {string} error +// @Failure 404 {string} error +// @Failure 500 {string} error +// @Router /leader/ [get] +func (l *LeaderController) GetLeadership(c *fiber.Ctx) error { + pageInfo, ok := fiberpaginate.FromContext(c) + if !ok { + return utilities.ErrExpectedPageInfo + } + + Leaders, err := l.leaderService.GetLeaders(*pageInfo) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(Leaders) +} + +// GetLeader godoc +// +// @Summary Retrieves a point of contact +// @Description Retrieves a point of contact by id +// @ID get-point-of-contact +// @Tags point of contact +// @Produce json +// @Param leaderID path string true "Point of Contact ID" +// @Success 200 {object} models.Leader +// @Failure 400 {string} error +// @Failure 404 {string} error +// @Failure 500 {string} error +// @Router /leader/{leaderID}/ [get] +func (l *LeaderController) GetLeader(c *fiber.Ctx) error { + Leader, err := l.leaderService.GetLeader(c.Params("leaderID")) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(Leader) +} diff --git a/backend/entities/leadership/base/routes.go b/backend/entities/leadership/base/routes.go new file mode 100644 index 000000000..265322da3 --- /dev/null +++ b/backend/entities/leadership/base/routes.go @@ -0,0 +1,16 @@ +package base + +import ( + "github.com/GenerateNU/sac/backend/types" +) + +func Leader(leaderParams types.RouteParams) { + LeaderController := NewLeaderController(NewLeaderService(leaderParams.ServiceParams)) + + // api/v1/leader/* + Leader := leaderParams.Router.Group("/leadership") + + Leader.Get("/", leaderParams.UtilityMiddleware.Paginator, LeaderController.GetLeadership) + Leader.Get("/:leaderID", LeaderController.GetLeader) + // Leader.Get("/:leaderID/file", LeaderController.GetPointOfContacFileInfo)) +} diff --git a/backend/entities/leadership/base/service.go b/backend/entities/leadership/base/service.go new file mode 100644 index 000000000..5a9ac7092 --- /dev/null +++ b/backend/entities/leadership/base/service.go @@ -0,0 +1,34 @@ +package base + +import ( + "github.com/GenerateNU/sac/backend/entities/models" + "github.com/GenerateNU/sac/backend/types" + "github.com/GenerateNU/sac/backend/utilities" + "github.com/garrettladley/fiberpaginate" +) + +type LeaderServiceInterface interface { + GetLeaders(pageInfo fiberpaginate.PageInfo) ([]models.Leader, error) + GetLeader(leaderID string) (*models.Leader, error) +} + +type LeaderService struct { + types.ServiceParams +} + +func NewLeaderService(serviceParams types.ServiceParams) LeaderServiceInterface { + return &LeaderService{serviceParams} +} + +func (ls *LeaderService) GetLeaders(pageInfo fiberpaginate.PageInfo) ([]models.Leader, error) { + return GetLeadership(ls.DB, pageInfo) +} + +func (ls *LeaderService) GetLeader(leaderID string) (*models.Leader, error) { + idAsUUID, err := utilities.ValidateID(leaderID) + if err != nil { + return nil, err + } + + return GetLeader(ls.DB, *idAsUUID) +} diff --git a/backend/entities/pocs/base/transactions.go b/backend/entities/leadership/base/transactions.go similarity index 55% rename from backend/entities/pocs/base/transactions.go rename to backend/entities/leadership/base/transactions.go index a31d5c0c9..86d1ce431 100644 --- a/backend/entities/pocs/base/transactions.go +++ b/backend/entities/leadership/base/transactions.go @@ -10,9 +10,9 @@ import ( "gorm.io/gorm" ) -func GetPointOfContacts(db *gorm.DB, pageInfo fiberpaginate.PageInfo) ([]models.PointOfContact, error) { - var pointOfContacts []models.PointOfContact - result := db.Preload("PhotoFile").Scopes(utilities.IntoScope(pageInfo, db)).Find(&pointOfContacts) +func GetLeadership(db *gorm.DB, pageInfo fiberpaginate.PageInfo) ([]models.Leader, error) { + var leadership []models.Leader + result := db.Preload("PhotoFile").Scopes(utilities.IntoScope(pageInfo, db)).Find(&leadership) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, utilities.ErrNotFound @@ -20,18 +20,18 @@ func GetPointOfContacts(db *gorm.DB, pageInfo fiberpaginate.PageInfo) ([]models. return nil, result.Error } - return pointOfContacts, nil + return leadership, nil } -func GetPointOfContact(db *gorm.DB, id uuid.UUID) (*models.PointOfContact, error) { - var pointOfContact models.PointOfContact +func GetLeader(db *gorm.DB, id uuid.UUID) (*models.Leader, error) { + var leader models.Leader - if err := db.Preload("PhotoFile").First(&pointOfContact, id).Error; err != nil { + if err := db.Preload("PhotoFile").First(&leader, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, utilities.ErrNotFound } return nil, err } - return &pointOfContact, nil + return &leader, nil } diff --git a/backend/entities/models/application.go b/backend/entities/models/application.go new file mode 100644 index 000000000..0cf6bff91 --- /dev/null +++ b/backend/entities/models/application.go @@ -0,0 +1,12 @@ +package models + +import "github.com/google/uuid" + +type Application struct { + Model + + Title string `json:"title"` + Link string `json:"link"` + + RecruitmentID *uuid.UUID `json:"-"` +} diff --git a/backend/entities/models/club.go b/backend/entities/models/club.go index 3eb8445cb..5aa2015c4 100644 --- a/backend/entities/models/club.go +++ b/backend/entities/models/club.go @@ -10,31 +10,30 @@ type Club struct { SoftDeletedAt gorm.DeletedAt `json:"-"` - Name string `json:"name"` - Preview string `json:"preview"` - Description string `json:"description"` - NumMembers int `json:"num_members"` - IsRecruiting bool `json:"is_recruiting"` - RecruitmentCycle RecruitmentCycle `json:"recruitment_cycle"` - RecruitmentType RecruitmentType `json:"recruitment_type"` - WeeklyTimeCommitment int `json:"weekly_time_commitment"` - OneWordToDescribeUs string `json:"one_word_to_describe_us"` - ApplicationLink string `json:"application_link"` - Logo string `json:"logo"` - - Parent *uuid.UUID `gorm:"foreignKey:Parent;" json:"-"` - Tag []Tag `gorm:"many2many:club_tags;" json:"-"` - Member []User `gorm:"many2many:user_club_members;" json:"-"` - Follower []User `gorm:"many2many:user_club_followers;" json:"-"` - IntendedApplicant []User `gorm:"many2many:user_club_intended_applicants;" json:"-"` - Comment []Comment `json:"-"` - PointOfContact []PointOfContact `json:"-"` - Contact []Contact `json:"-"` - - // Event - HostEvent []Event `gorm:"foreignKey:Host;" json:"-"` - Event []Event `gorm:"many2many:club_events;" json:"-"` - Notifcation []Notification `gorm:"polymorphic:Reference;" json:"-"` + Name string `json:"name"` + Preview string `json:"preview"` + Description string `json:"description"` + NumMembers int `json:"num_members"` + + WeeklyTimeCommitment int `json:"weekly_time_commitment"` + OneWordToDescribeUs string `json:"one_word_to_describe_us"` + + Recruitment Recruitment `json:"-"` + + Logo string `json:"logo"` + + Parent *uuid.UUID `gorm:"foreignKey:Parent;" json:"-"` + Tag []Tag `gorm:"many2many:club_tags;" json:"-"` + Member []User `gorm:"many2many:user_club_members;" json:"-"` + Follower []User `gorm:"many2many:user_club_followers;" json:"-"` + IntendedApplicant []User `gorm:"many2many:user_club_intended_applicants;" json:"-"` + Comment []Comment `json:"-"` + Leadership []Leader `json:"-"` + Social []Social `json:"-"` + + HostEvent []Event `gorm:"foreignKey:Host;" json:"-"` + Event []Event `gorm:"many2many:club_events;" json:"-"` + Notification []Notification `gorm:"polymorphic:Reference;" json:"-"` } type ClubSearchDocument struct { @@ -59,3 +58,7 @@ func (c *Club) ToSearchDocument() interface{} { Tags: tagIds, } } + +func (c Club) Preload(db *gorm.DB) *gorm.DB { + return db.Preload("Tag") +} diff --git a/backend/entities/models/contact.go b/backend/entities/models/contact.go deleted file mode 100644 index 82b3de1d3..000000000 --- a/backend/entities/models/contact.go +++ /dev/null @@ -1,50 +0,0 @@ -package models - -import "github.com/google/uuid" - -type ContactType string - -const ( - Facebook ContactType = "facebook" - Instagram ContactType = "instagram" - X ContactType = "x" - LinkedIn ContactType = "linkedin" - YouTube ContactType = "youtube" - GitHub ContactType = "github" - Slack ContactType = "slack" - Discord ContactType = "discord" - Email ContactType = "email" - CustomSite ContactType = "customSite" -) - -func GetContentPrefix(contactType ContactType) string { - switch contactType { - case Facebook: - return "https://facebook.com/" - case Instagram: - return "https://instagram.com/" - case X: - return "https://x.com/" - case LinkedIn: - return "https://linkedin.com/" - case YouTube: - return "https://youtube.com/" - case GitHub: - return "https://github.com/" - case Slack: - return "https://join.slack.com/" - case Discord: - return "https://discord.gg/" - default: - return "" - } -} - -type Contact struct { - Model - - Type ContactType `json:"type"` - Content string `json:"content"` - - ClubID uuid.UUID `json:"-"` -} diff --git a/backend/entities/models/event.go b/backend/entities/models/event.go index 3856203fc..edf5395a6 100644 --- a/backend/entities/models/event.go +++ b/backend/entities/models/event.go @@ -4,6 +4,7 @@ import ( "time" "github.com/google/uuid" + "gorm.io/gorm" ) type EventType string @@ -82,3 +83,7 @@ func (c *Event) ToSearchDocument() interface{} { Clubs: clubIds, } } + +func (c Event) Preload(db *gorm.DB) *gorm.DB { + return db.Preload("Tag").Preload("Club") +} diff --git a/backend/entities/models/poc.go b/backend/entities/models/leader.go similarity index 75% rename from backend/entities/models/poc.go rename to backend/entities/models/leader.go index 9a94b45dd..17508c99d 100644 --- a/backend/entities/models/poc.go +++ b/backend/entities/models/leader.go @@ -4,7 +4,7 @@ import ( "github.com/google/uuid" ) -type PointOfContact struct { +type Leader struct { Model Name string `json:"name"` @@ -15,3 +15,7 @@ type PointOfContact struct { PhotoFile File `gorm:"polymorphic:Owner;" json:"photo_file"` } + +func (l Leader) TableName() string { + return "leadership" +} diff --git a/backend/entities/models/recruitment.go b/backend/entities/models/recruitment.go new file mode 100644 index 000000000..841fc7c36 --- /dev/null +++ b/backend/entities/models/recruitment.go @@ -0,0 +1,18 @@ +package models + +import "github.com/google/uuid" + +type Recruitment struct { + Model + + Cycle RecruitmentCycle `json:"cycle"` + Type RecruitmentType `gorm:"column:_type" json:"type"` + + ClubID uuid.UUID `json:"-"` + + Application []Application `json:"-"` +} + +func (r Recruitment) TableName() string { + return "recruitment" +} diff --git a/backend/entities/models/recruitment_type.go b/backend/entities/models/recruitment_type.go index 6ebcc6542..f4b6097e0 100644 --- a/backend/entities/models/recruitment_type.go +++ b/backend/entities/models/recruitment_type.go @@ -3,7 +3,7 @@ package models type RecruitmentType string const ( - Unrestricted RecruitmentType = "unrestricted" - Tryout RecruitmentType = "tryout" - Application RecruitmentType = "application" + RecruitmentTypeUnrestricted RecruitmentType = "unrestricted" + RecruitmentTypeTryout RecruitmentType = "tryout" + RecruitmentTypeApplication RecruitmentType = "application" ) diff --git a/backend/entities/models/social.go b/backend/entities/models/social.go new file mode 100644 index 000000000..0e140a0b5 --- /dev/null +++ b/backend/entities/models/social.go @@ -0,0 +1,50 @@ +package models + +import "github.com/google/uuid" + +type SocialType string + +const ( + Facebook SocialType = "facebook" + Instagram SocialType = "instagram" + X SocialType = "x" + LinkedIn SocialType = "linkedin" + YouTube SocialType = "youtube" + GitHub SocialType = "github" + Slack SocialType = "slack" + Discord SocialType = "discord" + Email SocialType = "email" + CustomSite SocialType = "customSite" +) + +func GetSocialPrefix(contactType SocialType) string { + switch contactType { + case Facebook: + return "https://facebook.com/" + case Instagram: + return "https://instagram.com/" + case X: + return "https://x.com/" + case LinkedIn: + return "https://linkedin.com/" + case YouTube: + return "https://youtube.com/" + case GitHub: + return "https://github.com/" + case Slack: + return "https://join.slack.com/" + case Discord: + return "https://discord.gg/" + default: + return "" + } +} + +type Social struct { + Model + + Type SocialType `json:"type"` + Content string `json:"content"` + + ClubID uuid.UUID `json:"-"` +} diff --git a/backend/entities/oauth/base/controller.go b/backend/entities/oauth/base/controller.go index 2259f2f48..4c0343e41 100644 --- a/backend/entities/oauth/base/controller.go +++ b/backend/entities/oauth/base/controller.go @@ -26,7 +26,7 @@ func (oc *OAuthController) Authorize(c *fiber.Ctx) error { } // Extract the user making the call: - userID, err := locals.UserID(c) + userID, err := locals.UserIDFrom(c) if err != nil { return err } @@ -54,7 +54,7 @@ func (oc *OAuthController) Token(c *fiber.Ctx) error { } // Extract the user making the call: - userID, err := locals.UserID(c) + userID, err := locals.UserIDFrom(c) if err != nil { return err } @@ -76,7 +76,7 @@ func (oc *OAuthController) Revoke(c *fiber.Ctx) error { } // Extract the user making the call: - userID, err := locals.UserID(c) + userID, err := locals.UserIDFrom(c) if err != nil { return err } diff --git a/backend/entities/pocs/base/controller.go b/backend/entities/pocs/base/controller.go deleted file mode 100644 index e0d610a34..000000000 --- a/backend/entities/pocs/base/controller.go +++ /dev/null @@ -1,67 +0,0 @@ -package base - -import ( - "net/http" - - "github.com/GenerateNU/sac/backend/utilities" - "github.com/garrettladley/fiberpaginate" - "github.com/gofiber/fiber/v2" -) - -type PointOfContactController struct { - pointOfContactService PointOfContactServiceInterface -} - -func NewPointOfContactController(pointOfContactService PointOfContactServiceInterface) *PointOfContactController { - return &PointOfContactController{pointOfContactService: pointOfContactService} -} - -// GetPointOfContacts godoc -// -// @Summary Retrieve all point of contacts -// @Description Retrieves all point of contacts -// @ID get-point-of-contacts -// @Tags point of contact -// @Produce json -// @Param limit query int false "Limit" -// @Param page query int false "Page" -// @Success 200 {object} []models.PointOfContact -// @Failure 400 {string} error -// @Failure 404 {string} error -// @Failure 500 {string} error -// @Router /pocs/ [get] -func (poc *PointOfContactController) GetPointOfContacts(c *fiber.Ctx) error { - pagination, ok := fiberpaginate.FromContext(c) - if !ok { - return utilities.ErrExpectedPagination - } - - pointOfContacts, err := poc.pointOfContactService.GetPointOfContacts(*pagination) - if err != nil { - return err - } - - return c.Status(http.StatusOK).JSON(pointOfContacts) -} - -// GetPointOfContact godoc -// -// @Summary Retrieves a point of contact -// @Description Retrieves a point of contact by id -// @ID get-point-of-contact -// @Tags point of contact -// @Produce json -// @Param pocID path string true "Point of Contact ID" -// @Success 200 {object} models.PointOfContact -// @Failure 400 {string} error -// @Failure 404 {string} error -// @Failure 500 {string} error -// @Router /pocs/{pocID}/ [get] -func (poc *PointOfContactController) GetPointOfContact(c *fiber.Ctx) error { - pointOfContact, err := poc.pointOfContactService.GetPointOfContact(c.Params("pocID")) - if err != nil { - return err - } - - return c.Status(http.StatusOK).JSON(pointOfContact) -} diff --git a/backend/entities/pocs/base/routes.go b/backend/entities/pocs/base/routes.go deleted file mode 100644 index 907459002..000000000 --- a/backend/entities/pocs/base/routes.go +++ /dev/null @@ -1,16 +0,0 @@ -package base - -import ( - "github.com/GenerateNU/sac/backend/types" -) - -func PointOfContact(pointOfContactParams types.RouteParams) { - pointOfContactController := NewPointOfContactController(NewPointOfContactService(pointOfContactParams.ServiceParams)) - - // api/v1/pocs/* - pointofContact := pointOfContactParams.Router.Group("/pocs") - - pointofContact.Get("/", pointOfContactParams.UtilityMiddleware.Paginator, pointOfContactController.GetPointOfContacts) - pointofContact.Get("/:pocID", pointOfContactController.GetPointOfContact) - // pointOfContact.Get("/:pocID/file", pointOfContactController.GetPointOfContacFileInfo)) -} diff --git a/backend/entities/pocs/base/service.go b/backend/entities/pocs/base/service.go deleted file mode 100644 index 3c2b0efc9..000000000 --- a/backend/entities/pocs/base/service.go +++ /dev/null @@ -1,34 +0,0 @@ -package base - -import ( - "github.com/GenerateNU/sac/backend/entities/models" - "github.com/GenerateNU/sac/backend/types" - "github.com/GenerateNU/sac/backend/utilities" - "github.com/garrettladley/fiberpaginate" -) - -type PointOfContactServiceInterface interface { - GetPointOfContacts(pageInfo fiberpaginate.PageInfo) ([]models.PointOfContact, error) - GetPointOfContact(pocID string) (*models.PointOfContact, error) -} - -type PointOfContactService struct { - types.ServiceParams -} - -func NewPointOfContactService(serviceParams types.ServiceParams) PointOfContactServiceInterface { - return &PointOfContactService{serviceParams} -} - -func (poc *PointOfContactService) GetPointOfContacts(pageInfo fiberpaginate.PageInfo) ([]models.PointOfContact, error) { - return GetPointOfContacts(poc.DB, pageInfo) -} - -func (poc *PointOfContactService) GetPointOfContact(pocID string) (*models.PointOfContact, error) { - idAsUUID, err := utilities.ValidateID(pocID) - if err != nil { - return nil, err - } - - return GetPointOfContact(poc.DB, *idAsUUID) -} diff --git a/backend/entities/socials/base/controller.go b/backend/entities/socials/base/controller.go new file mode 100644 index 000000000..7fa004dc2 --- /dev/null +++ b/backend/entities/socials/base/controller.go @@ -0,0 +1,91 @@ +package base + +import ( + "net/http" + + "github.com/GenerateNU/sac/backend/utilities" + "github.com/garrettladley/fiberpaginate" + "github.com/gofiber/fiber/v2" +) + +type SocialController struct { + socialService SocialServiceInterface +} + +func NewSocialController(socialService SocialServiceInterface) *SocialController { + return &SocialController{socialService: socialService} +} + +// GetSocial godoc +// +// @Summary Retrieves a social +// @Description Retrieves a social by id +// @ID get-social +// @Tags social +// @Accept json +// @Produce json +// @Param socialID path string true "Social ID" +// @Success 201 {object} models.Social +// @Failure 400 {string} error +// @Failure 404 {string} error +// @Failure 500 {string} error +// @Router /socials/{socialID}/ [get] +func (co *SocialController) GetSocial(c *fiber.Ctx) error { + social, err := co.socialService.GetSocial(c.Params("socialID")) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(social) +} + +// GetSocials godoc +// +// @Summary Retrieve all socials +// @Description Retrieves all socials +// @ID get-socials +// @Tags social +// @Produce json +// @Param limit query int false "Limit" +// @Param page query int false "Page" +// @Success 200 {object} []models.Social +// @Failure 400 {string} error +// @Failure 404 {string} error +// @Failure 500 {string} error +// @Router /socials/ [get] +func (co *SocialController) GetSocials(c *fiber.Ctx) error { + pageInfo, ok := fiberpaginate.FromContext(c) + if !ok { + return utilities.ErrExpectedPageInfo + } + + socials, err := co.socialService.GetSocials(*pageInfo) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(socials) +} + +// DeleteSocial godoc +// +// @Summary Deletes a social +// @Description Deletes a social +// @ID delete-social +// @Tags social +// @Accept json +// @Produce json +// @Param socialID path string true "Social ID" +// @Success 201 {object} models.Social +// @Failure 400 {string} error +// @Failure 404 {string} error +// @Failure 500 {string} error +// @Router /socials/{socialID}/ [delete] +func (co *SocialController) DeleteSocial(c *fiber.Ctx) error { + err := co.socialService.DeleteSocial(c.Params("socialID")) + if err != nil { + return err + } + + return c.SendStatus(http.StatusNoContent) +} diff --git a/backend/entities/socials/base/routes.go b/backend/entities/socials/base/routes.go new file mode 100644 index 000000000..2b0187240 --- /dev/null +++ b/backend/entities/socials/base/routes.go @@ -0,0 +1,17 @@ +package base + +import ( + "github.com/GenerateNU/sac/backend/auth" + "github.com/GenerateNU/sac/backend/types" +) + +func Social(socialParams types.RouteParams) { + socialController := NewSocialController(NewSocialService(socialParams.ServiceParams)) + + // api/v1/socials/* + socials := socialParams.Router.Group("/socials") + + socials.Get("/", socialParams.UtilityMiddleware.Paginator, socialController.GetSocials) + socials.Get("/:socialID", socialController.GetSocial) + socials.Delete("/:socialID", socialParams.AuthMiddleware.Authorize(auth.DeleteAll), socialController.DeleteSocial) +} diff --git a/backend/entities/socials/base/service.go b/backend/entities/socials/base/service.go new file mode 100644 index 000000000..e7f7924f1 --- /dev/null +++ b/backend/entities/socials/base/service.go @@ -0,0 +1,44 @@ +package base + +import ( + "github.com/GenerateNU/sac/backend/entities/models" + "github.com/GenerateNU/sac/backend/types" + "github.com/GenerateNU/sac/backend/utilities" + "github.com/garrettladley/fiberpaginate" +) + +type SocialServiceInterface interface { + GetSocials(pageInfo fiberpaginate.PageInfo) ([]models.Social, error) + GetSocial(socialID string) (*models.Social, error) + DeleteSocial(socialID string) error +} + +type SocialService struct { + types.ServiceParams +} + +func NewSocialService(serviceParams types.ServiceParams) SocialServiceInterface { + return &SocialService{serviceParams} +} + +func (c *SocialService) GetSocials(pageInfo fiberpaginate.PageInfo) ([]models.Social, error) { + return GetSocials(c.DB, pageInfo) +} + +func (c *SocialService) GetSocial(socialID string) (*models.Social, error) { + idAsUUID, err := utilities.ValidateID(socialID) + if err != nil { + return nil, err + } + + return GetSocial(c.DB, *idAsUUID) +} + +func (c *SocialService) DeleteSocial(socialID string) error { + idAsUUID, err := utilities.ValidateID(socialID) + if err != nil { + return err + } + + return DeleteSocial(c.DB, *idAsUUID) +} diff --git a/backend/entities/socials/base/transactions.go b/backend/entities/socials/base/transactions.go new file mode 100644 index 000000000..f60927713 --- /dev/null +++ b/backend/entities/socials/base/transactions.go @@ -0,0 +1,42 @@ +package base + +import ( + "errors" + + "github.com/GenerateNU/sac/backend/entities/models" + "github.com/GenerateNU/sac/backend/utilities" + "github.com/garrettladley/fiberpaginate" + "github.com/google/uuid" + "gorm.io/gorm" +) + +func GetSocials(db *gorm.DB, pageInfo fiberpaginate.PageInfo) ([]models.Social, error) { + var socials []models.Social + if err := db.Scopes(utilities.IntoScope(pageInfo, db)).Find(&socials).Error; err != nil { + return nil, err + } + + return socials, nil +} + +func GetSocial(db *gorm.DB, id uuid.UUID) (*models.Social, error) { + var social models.Social + if err := db.First(&social, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err + } + return nil, err + } + + return &social, nil +} + +func DeleteSocial(db *gorm.DB, id uuid.UUID) error { + if result := db.Delete(&models.Social{}, id); result.RowsAffected == 0 { + if result.Error == nil { + return utilities.ErrNotFound + } + return result.Error + } + return nil +} diff --git a/backend/entities/tags/base/service.go b/backend/entities/tags/base/service.go index 6c11a40d6..f236f50da 100644 --- a/backend/entities/tags/base/service.go +++ b/backend/entities/tags/base/service.go @@ -1,10 +1,9 @@ package base import ( - "errors" - "github.com/GenerateNU/sac/backend/entities/models" "github.com/GenerateNU/sac/backend/entities/tags" + "github.com/GenerateNU/sac/backend/errs" "github.com/GenerateNU/sac/backend/types" "github.com/GenerateNU/sac/backend/utilities" @@ -59,7 +58,7 @@ func (t *TagService) UpdateTag(tagID string, tagBody UpdateTagRequestBody) (*mod } if utilities.AtLeastOne(tagBody, UpdateTagRequestBody{}) { - return nil, errors.New("at least one field must be present") + return nil, errs.ErrAtLeastOne } if err := utilities.Validate(t.Validate, tagBody); err != nil { diff --git a/backend/entities/users/base/controller.go b/backend/entities/users/base/controller.go index 91c596acc..202387350 100644 --- a/backend/entities/users/base/controller.go +++ b/backend/entities/users/base/controller.go @@ -34,12 +34,12 @@ func NewUserController(userService UserServiceInterface) *UserController { // @Failure 500 {object} error // @Router /users/ [get] func (u *UserController) GetUsers(c *fiber.Ctx) error { - pagination, ok := fiberpaginate.FromContext(c) + pageInfo, ok := fiberpaginate.FromContext(c) if !ok { - return utilities.ErrExpectedPagination + return utilities.ErrExpectedPageInfo } - users, err := u.userService.GetUsers(*pagination) + users, err := u.userService.GetUsers(*pageInfo) if err != nil { return err } @@ -63,7 +63,7 @@ func (u *UserController) GetUsers(c *fiber.Ctx) error { // @Failure 500 {object} error // @Router /auth/me [get] func (u *UserController) GetMe(c *fiber.Ctx) error { - userID, err := locals.UserID(c) + userID, err := locals.UserIDFrom(c) if err != nil { return err } diff --git a/backend/entities/users/base/service.go b/backend/entities/users/base/service.go index 15dfa8bd5..272fe3612 100644 --- a/backend/entities/users/base/service.go +++ b/backend/entities/users/base/service.go @@ -1,11 +1,10 @@ package base import ( - "errors" - "github.com/GenerateNU/sac/backend/auth" authEntities "github.com/GenerateNU/sac/backend/entities/auth" "github.com/GenerateNU/sac/backend/entities/models" + "github.com/GenerateNU/sac/backend/errs" "github.com/garrettladley/fiberpaginate" "github.com/google/uuid" @@ -55,7 +54,7 @@ func (u *UserService) UpdateUser(id string, userBody UpdateUserRequestBody) (*mo } if utilities.AtLeastOne(userBody, UpdateUserRequestBody{}) { - return nil, errors.New("no fields to update") + return nil, errs.ErrAtLeastOne } if err := utilities.Validate(u.Validate, userBody); err != nil { diff --git a/backend/entities/users/followers/controller.go b/backend/entities/users/followers/controller.go index b41741937..7c6e50704 100644 --- a/backend/entities/users/followers/controller.go +++ b/backend/entities/users/followers/controller.go @@ -3,7 +3,6 @@ package followers import ( "net/http" - "github.com/GenerateNU/sac/backend/utilities" "github.com/gofiber/fiber/v2" ) @@ -15,70 +14,11 @@ func NewUserFollowerController(userFollowerService UserFollowerServiceInterface) return &UserFollowerController{userFollowerService: userFollowerService} } -// CreateFollowing godoc -// -// @Summary Follow a club -// @Description Follow a club -// @ID create-following -// @Tags user-follower -// @Accept json -// @Produce json -// @Param userID path string true "User ID" -// @Param clubID path string true "Club ID" -// @Success 201 {object} utilities.SuccessResponse -// @Failure 401 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /users/{userID}/follower/{clubID}/ [post] -func (uf *UserFollowerController) CreateFollowing(c *fiber.Ctx) error { - err := uf.userFollowerService.CreateFollowing(c.Params("userID"), c.Params("clubID")) +func (ufc *UserFollowerController) GetFollowing(c *fiber.Ctx) error { + followers, err := ufc.userFollowerService.GetFollowing(c.Params("userID")) if err != nil { return err } - return utilities.FiberMessage(c, http.StatusCreated, "Successfully followed club") -} -// DeleteFollowing godoc -// -// @Summary Unfollow a club -// @Description Unfollow a club -// @ID delete-following -// @Tags user-follower -// @Accept json -// @Produce json -// @Param userID path string true "User ID" -// @Param clubID path string true "Club ID" -// @Success 204 {object} utilities.SuccessResponse -// @Failure 401 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /users/{userID}/follower/{clubID}/ [delete] -func (uf *UserFollowerController) DeleteFollowing(c *fiber.Ctx) error { - err := uf.userFollowerService.DeleteFollowing(c.Params("userID"), c.Params("clubID")) - if err != nil { - return err - } - return c.SendStatus(http.StatusNoContent) -} - -// GetAllFollowing godoc -// -// @Summary Retrieve all clubs a user is following -// @Description Retrieves all clubs a user is following -// @ID get-following -// @Tags user-follower -// @Produce json -// @Param userID path string true "User ID" -// @Success 200 {object} []models.Club -// @Failure 400 {object} error -// @Failure 401 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /users/{userID}/follower/ [get] -func (uf *UserFollowerController) GetFollowing(c *fiber.Ctx) error { - clubs, err := uf.userFollowerService.GetFollowing(c.Params("userID")) - if err != nil { - return err - } - return c.Status(http.StatusOK).JSON(clubs) + return c.Status(http.StatusOK).JSON(followers) } diff --git a/backend/entities/users/followers/routes.go b/backend/entities/users/followers/routes.go index 9928f0932..2112ee2fc 100644 --- a/backend/entities/users/followers/routes.go +++ b/backend/entities/users/followers/routes.go @@ -11,6 +11,4 @@ func UserFollower(userParams types.RouteParams) { userFollower := userParams.Router.Group("/follower") userFollower.Get("/", userFollowerController.GetFollowing) - userFollower.Post("/:clubID", userParams.AuthMiddleware.UserAuthorizeById, userFollowerController.CreateFollowing) - userFollower.Delete("/:clubID", userParams.AuthMiddleware.UserAuthorizeById, userFollowerController.DeleteFollowing) } diff --git a/backend/entities/users/followers/service.go b/backend/entities/users/followers/service.go index 2d2af7519..4e0da6ffc 100644 --- a/backend/entities/users/followers/service.go +++ b/backend/entities/users/followers/service.go @@ -7,9 +7,7 @@ import ( ) type UserFollowerServiceInterface interface { - CreateFollowing(userId string, clubId string) error - DeleteFollowing(userId string, clubId string) error - GetFollowing(userId string) ([]models.Club, error) + GetFollowing(userID string) ([]models.Club, error) } type UserFollowerService struct { @@ -20,35 +18,11 @@ func NewUserFollowerService(serviceParams types.ServiceParams) UserFollowerServi return &UserFollowerService{serviceParams} } -func (u *UserFollowerService) CreateFollowing(userId string, clubId string) error { - userIdAsUUID, err := utilities.ValidateID(userId) - if err != nil { - return err - } - clubIdAsUUID, err := utilities.ValidateID(clubId) - if err != nil { - return err - } - return CreateFollowing(u.DB, *userIdAsUUID, *clubIdAsUUID) -} - -func (u *UserFollowerService) DeleteFollowing(userId string, clubId string) error { - userIdAsUUID, err := utilities.ValidateID(userId) - if err != nil { - return err - } - clubIdAsUUID, err := utilities.ValidateID(clubId) - if err != nil { - return err - } - return DeleteFollowing(u.DB, *userIdAsUUID, *clubIdAsUUID) -} - -func (u *UserFollowerService) GetFollowing(userId string) ([]models.Club, error) { - userIdAsUUID, err := utilities.ValidateID(userId) +func (u *UserFollowerService) GetFollowing(userID string) ([]models.Club, error) { + userIDAsUUID, err := utilities.ValidateID(userID) if err != nil { return nil, err } - return GetClubFollowing(u.DB, *userIdAsUUID) + return GetClubFollowing(u.DB, *userIDAsUUID) } diff --git a/backend/entities/users/followers/transactions.go b/backend/entities/users/followers/transactions.go index 169f2e9d7..fa4ef7aa8 100644 --- a/backend/entities/users/followers/transactions.go +++ b/backend/entities/users/followers/transactions.go @@ -1,7 +1,6 @@ package followers import ( - "github.com/GenerateNU/sac/backend/entities/clubs" "github.com/GenerateNU/sac/backend/entities/models" "github.com/GenerateNU/sac/backend/entities/users" @@ -9,42 +8,6 @@ import ( "gorm.io/gorm" ) -func CreateFollowing(db *gorm.DB, userID uuid.UUID, clubID uuid.UUID) error { - user, err := users.GetUser(db, userID) - if err != nil { - return err - } - - club, err := clubs.GetClub(db, clubID) - if err != nil { - return err - } - - if err := db.Model(&user).Association("Follower").Append(club); err != nil { - return err - } - - return nil -} - -func DeleteFollowing(db *gorm.DB, userID uuid.UUID, clubID uuid.UUID) error { - user, err := users.GetUser(db, userID) - if err != nil { - return err - } - - club, err := clubs.GetClub(db, clubID) - if err != nil { - return err - } - - if err := db.Model(&user).Association("Follower").Delete(club); err != nil { - return err - } - - return nil -} - func GetClubFollowing(db *gorm.DB, userID uuid.UUID) ([]models.Club, error) { var clubs []models.Club diff --git a/backend/entities/users/members/controller.go b/backend/entities/users/members/controller.go index 5fb0ae78c..89fc60dbe 100644 --- a/backend/entities/users/members/controller.go +++ b/backend/entities/users/members/controller.go @@ -14,20 +14,6 @@ func NewUserMemberController(clubMemberService UserMemberServiceInterface) *User return &UserMemberController{clubMemberService: clubMemberService} } -// GetMembership godoc -// -// @Summary Retrieve all clubs a user is a member of -// @Description Retrieves all clubs a user is a member of -// @ID get-membership -// @Tags user-member -// @Produce json -// @Param userID path string true "User ID" -// @Success 200 {object} []models.Club -// @Failure 400 {object} error -// @Failure 401 {object} error -// @Failure 404 {object} error -// @Failure 500 {object} error -// @Router /users/{userID}/member/ [get] func (um *UserMemberController) GetMembership(c *fiber.Ctx) error { followers, err := um.clubMemberService.GetMembership(c.Params("userID")) if err != nil { diff --git a/backend/errs/db.go b/backend/errs/db.go new file mode 100644 index 000000000..8bfe733ec --- /dev/null +++ b/backend/errs/db.go @@ -0,0 +1,5 @@ +package errs + +import "errors" + +var ErrDatabaseTimeout = errors.New("database timeout") diff --git a/backend/errs/validate.go b/backend/errs/validate.go new file mode 100644 index 000000000..661a1f1dc --- /dev/null +++ b/backend/errs/validate.go @@ -0,0 +1,5 @@ +package errs + +import "errors" + +var ErrAtLeastOne = errors.New("at least one field must be provided") diff --git a/backend/locals/claims.go b/backend/locals/claims.go index 67155ef56..86f55273f 100644 --- a/backend/locals/claims.go +++ b/backend/locals/claims.go @@ -8,7 +8,7 @@ import ( "github.com/gofiber/fiber/v2" ) -func CustomClaims(c *fiber.Ctx) (*auth.CustomClaims, error) { +func CustomClaimsFrom(c *fiber.Ctx) (*auth.CustomClaims, error) { rawClaims := c.Locals(claimsKey) if rawClaims == nil { return nil, utilities.Forbidden() diff --git a/backend/locals/user_id.go b/backend/locals/user_id.go index f2962438b..8500a77be 100644 --- a/backend/locals/user_id.go +++ b/backend/locals/user_id.go @@ -8,7 +8,7 @@ import ( "github.com/google/uuid" ) -func UserID(c *fiber.Ctx) (*uuid.UUID, error) { +func UserIDFrom(c *fiber.Ctx) (*uuid.UUID, error) { userID := c.Locals(userIDKey) if userID == nil { return nil, utilities.Forbidden() diff --git a/backend/main.go b/backend/main.go index 8adbe970e..b8b8f2cbe 100644 --- a/backend/main.go +++ b/backend/main.go @@ -35,8 +35,7 @@ func main() { constants.SEARCH_URI = config.Search.URI - err = checkServerRunning(config.Application.Host, config.Application.Port) - if err == nil { + if checkServerRunning(config.Application.Host, config.Application.Port) == nil { utilities.Exit("A server is already running on %s:%d.\n", config.Application.Host, config.Application.Port) } diff --git a/backend/middleware/auth/auth.go b/backend/middleware/auth/auth.go index 99be76f45..df01b1e1c 100644 --- a/backend/middleware/auth/auth.go +++ b/backend/middleware/auth/auth.go @@ -21,9 +21,8 @@ import ( ) func (m *AuthMiddlewareService) IsSuper(c *fiber.Ctx) bool { - claims, err := locals.CustomClaims(c) + claims, err := locals.CustomClaimsFrom(c) if err != nil { - _ = err return false } if claims == nil { @@ -99,7 +98,7 @@ func (m *AuthMiddlewareService) Authorize(requiredPermissions ...auth.Permission return utilities.Unauthorized() } - claims, err := locals.CustomClaims(c) + claims, err := locals.CustomClaimsFrom(c) if err != nil { return err } diff --git a/backend/middleware/auth/club.go b/backend/middleware/auth/club.go index 8531c78a6..56ae9473d 100644 --- a/backend/middleware/auth/club.go +++ b/backend/middleware/auth/club.go @@ -25,7 +25,7 @@ func (m *AuthMiddlewareService) ClubAuthorizeById(c *fiber.Ctx, extractor Extrac return err } - userID, err := locals.UserID(c) + userID, err := locals.UserIDFrom(c) if err != nil { return err } diff --git a/backend/middleware/auth/event.go b/backend/middleware/auth/event.go index 5d0c13c39..c0fc03e9f 100644 --- a/backend/middleware/auth/event.go +++ b/backend/middleware/auth/event.go @@ -25,7 +25,7 @@ func (m *AuthMiddlewareService) EventAuthorizeById(c *fiber.Ctx, extractor Extra return err } - userID, err := locals.UserID(c) + userID, err := locals.UserIDFrom(c) if err != nil { return err } diff --git a/backend/middleware/auth/user.go b/backend/middleware/auth/user.go index 647570643..9c06cc2d8 100644 --- a/backend/middleware/auth/user.go +++ b/backend/middleware/auth/user.go @@ -21,7 +21,7 @@ func (m *AuthMiddlewareService) UserAuthorizeById(c *fiber.Ctx) error { return err } - userID, err := locals.UserID(c) + userID, err := locals.UserIDFrom(c) if err != nil { return err } diff --git a/backend/migrations/000001_init.down.sql b/backend/migrations/000001_init.down.sql index cf79cfbe6..7b7223876 100644 --- a/backend/migrations/000001_init.down.sql +++ b/backend/migrations/000001_init.down.sql @@ -1,14 +1,131 @@ BEGIN; -DROP TABLE IF EXISTS clubs CASCADE; - -DROP TABLE IF EXISTS events CASCADE; - -DROP TABLE IF EXISTS users CASCADE; - -DROP TABLE IF EXISTS categories CASCADE; - -DROP TABLE IF EXISTS tags CASCADE; +ALTER TABLE + "users" +ALTER COLUMN + id DROP DEFAULT; + +ALTER TABLE + recruitment +ALTER COLUMN + id DROP DEFAULT; + +ALTER TABLE + applications +ALTER COLUMN + id DROP DEFAULT; + +ALTER TABLE + clubs +ALTER COLUMN + id DROP DEFAULT; + +ALTER TABLE + series +ALTER COLUMN + id DROP DEFAULT; + +ALTER TABLE + EVENTS +ALTER COLUMN + id DROP DEFAULT; + +ALTER TABLE + categories +ALTER COLUMN + id DROP DEFAULT; + +ALTER TABLE + tags +ALTER COLUMN + id DROP DEFAULT; + +ALTER TABLE + club_events +ALTER COLUMN + club_id DROP DEFAULT, +ALTER COLUMN + event_id DROP DEFAULT; + +ALTER TABLE + club_tags +ALTER COLUMN + tag_id DROP DEFAULT, +ALTER COLUMN + club_id DROP DEFAULT; + +ALTER TABLE + contacts +ALTER COLUMN + id DROP DEFAULT; + +ALTER TABLE + event_tags +ALTER COLUMN + tag_id DROP DEFAULT, +ALTER COLUMN + event_id DROP DEFAULT; + +ALTER TABLE + files +ALTER COLUMN + id DROP DEFAULT; + +-- ALTER TABLE notifications ALTER COLUMN id DROP DEFAULT; -- Uncomment if notifications table is created +ALTER TABLE + leadership +ALTER COLUMN + id DROP DEFAULT; + +ALTER TABLE + user_club_followers +ALTER COLUMN + user_id DROP DEFAULT, +ALTER COLUMN + club_id DROP DEFAULT; + +ALTER TABLE + user_club_intended_applicants +ALTER COLUMN + user_id DROP DEFAULT, +ALTER COLUMN + club_id DROP DEFAULT; + +ALTER TABLE + user_club_members +ALTER COLUMN + user_id DROP DEFAULT; + +ALTER TABLE + user_event_rsvps +ALTER COLUMN + user_id DROP DEFAULT, +ALTER COLUMN + event_id DROP DEFAULT; + +ALTER TABLE + user_event_waitlists +ALTER COLUMN + user_id DROP DEFAULT, +ALTER COLUMN + event_id DROP DEFAULT; + +ALTER TABLE + user_tags +ALTER COLUMN + user_id DROP DEFAULT, +ALTER COLUMN + tag_id DROP DEFAULT; + +ALTER TABLE + verifications +ALTER COLUMN + user_id DROP DEFAULT; + +ALTER TABLE + user_oauth_tokens +ALTER COLUMN + user_id DROP DEFAULT; DROP TABLE IF EXISTS club_events CASCADE; @@ -16,15 +133,12 @@ DROP TABLE IF EXISTS club_tags CASCADE; DROP TABLE IF EXISTS contacts CASCADE; -DROP TABLE IF EXISTS series CASCADE; - DROP TABLE IF EXISTS event_tags CASCADE; DROP TABLE IF EXISTS files CASCADE; -DROP TABLE IF EXISTS notifications CASCADE; - -DROP TABLE IF EXISTS point_of_contacts CASCADE; +-- DROP TABLE IF EXISTS notifications CASCADE; -- Uncomment if notifications table is created +DROP TABLE IF EXISTS leadership CASCADE; DROP TABLE IF EXISTS user_club_followers CASCADE; @@ -40,10 +154,32 @@ DROP TABLE IF EXISTS user_tags CASCADE; DROP TABLE IF EXISTS verifications CASCADE; -DROP TYPE IF EXISTS OAuthResourceType CASCADE; - DROP TABLE IF EXISTS user_oauth_tokens CASCADE; +DROP TABLE IF EXISTS EVENTS CASCADE; + +DROP TABLE IF EXISTS series CASCADE; + +DROP TABLE IF EXISTS categories CASCADE; + +DROP TABLE IF EXISTS tags CASCADE; + +DROP TABLE IF EXISTS clubs CASCADE; + +DROP TABLE IF EXISTS applications CASCADE; + +DROP TABLE IF EXISTS recruitment CASCADE; + +DROP TABLE IF EXISTS "users" CASCADE; + +DROP TYPE IF EXISTS recruitment_cycle CASCADE; + +DROP TYPE IF EXISTS recruitment_type CASCADE; + +DROP TYPE IF EXISTS event_type CASCADE; + +DROP TYPE IF EXISTS OAuthResourceType CASCADE; + DROP EXTENSION IF EXISTS "uuid-ossp"; -COMMIT; +COMMIT; \ No newline at end of file diff --git a/backend/migrations/000001_init.up.sql b/backend/migrations/000001_init.up.sql index 8163d0fc5..f62171440 100644 --- a/backend/migrations/000001_init.up.sql +++ b/backend/migrations/000001_init.up.sql @@ -2,11 +2,11 @@ BEGIN; CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -CREATE TABLE IF NOT EXISTS users( +CREATE TABLE IF NOT EXISTS "users"( id uuid NOT NULL DEFAULT uuid_generate_v4(), - created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, - role varchar(255) NOT NULL DEFAULT 'student'::character varying, + created_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + role varchar(255) NOT NULL DEFAULT 'student' :: character varying, first_name varchar(255) NOT NULL, last_name varchar(255) NOT NULL, email varchar(255) NOT NULL, @@ -21,82 +21,113 @@ CREATE TABLE IF NOT EXISTS users( PRIMARY KEY(id) ); -CREATE UNIQUE INDEX IF NOT EXISTS uni_users_email ON users USING btree ("email"); +CREATE UNIQUE INDEX IF NOT EXISTS uni_users_email ON "users" USING btree ("email"); CREATE TABLE IF NOT EXISTS clubs( id uuid NOT NULL DEFAULT uuid_generate_v4(), - created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, - soft_deleted_at timestamp with time zone, + created_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + soft_deleted_at timestamp WITH time zone, name varchar(255) NOT NULL, preview varchar(255) NOT NULL, description text NOT NULL, num_members bigint NOT NULL, - is_recruiting boolean NOT NULL DEFAULT false, - recruitment_cycle varchar(255) NOT NULL DEFAULT 'always'::character varying, - recruitment_type varchar(255) NOT NULL DEFAULT 'unrestricted'::character varying, weekly_time_commitment bigint, - one_word_to_describe_us varchar(255) DEFAULT NULL::character varying, - application_link varchar(255) DEFAULT NULL::character varying, - logo varchar(255) DEFAULT NULL::character varying, - parent text, + one_word_to_describe_us varchar(255) DEFAULT NULL :: character varying, + recruitment_id uuid, + logo varchar(255) DEFAULT NULL :: character varying, + parent uuid, PRIMARY KEY(id) ); CREATE UNIQUE INDEX IF NOT EXISTS uni_clubs_name ON clubs USING btree ("name"); + CREATE INDEX IF NOT EXISTS idx_clubs_num_members ON clubs USING btree ("num_members"); + CREATE INDEX IF NOT EXISTS idx_clubs_one_word_to_describe_us ON clubs USING btree ("one_word_to_describe_us"); +CREATE TYPE recruitment_cycle AS ENUM ('fall', 'spring', 'fallSpring', 'always'); + +CREATE TYPE recruitment_type AS ENUM ('unrestricted', 'tryout', 'application'); + +CREATE TABLE IF NOT EXISTS recruitment ( + id uuid NOT NULL DEFAULT uuid_generate_v4(), + created_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + cycle recruitment_cycle, + _type recruitment_type, + club_id uuid NOT NULL, + PRIMARY KEY(id), + CONSTRAINT fk_clubs_recruitment FOREIGN KEY(club_id) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE +); + +ALTER TABLE + clubs +ADD + CONSTRAINT fk_clubs_recruitment FOREIGN KEY(recruitment_id) REFERENCES recruitment(id) ON UPDATE CASCADE ON DELETE CASCADE; + +CREATE TABLE IF NOT EXISTS applications ( + id uuid NOT NULL DEFAULT uuid_generate_v4(), + created_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + recruitment_id uuid NOT NULL, + title varchar(255) NOT NULL, + link varchar(255) NOT NULL, + PRIMARY KEY(id), + CONSTRAINT fk_recruitment_application FOREIGN KEY(recruitment_id) REFERENCES recruitment(id) ON UPDATE CASCADE ON DELETE CASCADE +); + CREATE TABLE IF NOT EXISTS series( id uuid NOT NULL DEFAULT uuid_generate_v4(), - created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + created_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(id) ); -CREATE TABLE IF NOT EXISTS events( +CREATE TYPE event_type AS ENUM ('hybrid', 'in_person', 'virtual'); + +CREATE TABLE IF NOT EXISTS "events"( id uuid NOT NULL DEFAULT uuid_generate_v4(), - created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + created_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, name varchar(255) NOT NULL, preview varchar(255) NOT NULL, description text NOT NULL, - event_type varchar(255) NOT NULL, + event_type event_type NOT NULL, location varchar(255), link varchar(255), is_public boolean NOT NULL, is_draft boolean NOT NULL, is_archived boolean NOT NULL, - start_time timestamp with time zone NOT NULL, - end_time timestamp with time zone NOT NULL, + start_time timestamp WITH time zone NOT NULL, + end_time timestamp WITH time zone NOT NULL, host uuid NOT NULL, series_id uuid, PRIMARY KEY(id), - CONSTRAINT fk_clubs_host_event FOREIGN key(host) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT fk_series_event FOREIGN key(series_id) REFERENCES series(id) ON UPDATE CASCADE ON DELETE CASCADE + CONSTRAINT fk_clubs_host_event FOREIGN KEY(host) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT fk_series_event FOREIGN KEY(series_id) REFERENCES series(id) ON UPDATE CASCADE ON DELETE CASCADE ); -CREATE INDEX IF NOT EXISTS idx_events_series_id ON events USING btree ("series_id"); +CREATE INDEX IF NOT EXISTS idx_events_series_id ON "events" USING btree ("series_id"); CREATE TABLE IF NOT EXISTS categories( id uuid NOT NULL DEFAULT uuid_generate_v4(), - created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + created_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, name varchar(255) NOT NULL, PRIMARY KEY(id) ); CREATE UNIQUE INDEX IF NOT EXISTS uni_categories_name ON categories USING btree ("name"); - CREATE TABLE IF NOT EXISTS tags( id uuid NOT NULL DEFAULT uuid_generate_v4(), - created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + created_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, name varchar(255) NOT NULL, category_id uuid NOT NULL, PRIMARY KEY(id), - CONSTRAINT fk_categories_tag FOREIGN key(category_id) REFERENCES categories(id) ON UPDATE CASCADE ON DELETE CASCADE + CONSTRAINT fk_categories_tag FOREIGN KEY(category_id) REFERENCES categories(id) ON UPDATE CASCADE ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_tags_category_id ON tags USING btree ("category_id"); @@ -104,44 +135,44 @@ CREATE INDEX IF NOT EXISTS idx_tags_category_id ON tags USING btree ("category_i CREATE TABLE IF NOT EXISTS club_events( club_id uuid NOT NULL DEFAULT uuid_generate_v4(), event_id uuid NOT NULL DEFAULT uuid_generate_v4(), - PRIMARY KEY(club_id,event_id), - CONSTRAINT fk_club_events_event FOREIGN key(event_id) REFERENCES events(id) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT fk_club_events_club FOREIGN key(club_id) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE + PRIMARY KEY(club_id, event_id), + CONSTRAINT fk_club_events_event FOREIGN KEY(event_id) REFERENCES "events"(id) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT fk_club_events_club FOREIGN KEY(club_id) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS club_tags( tag_id uuid NOT NULL DEFAULT uuid_generate_v4(), club_id uuid NOT NULL DEFAULT uuid_generate_v4(), - PRIMARY KEY(tag_id,club_id), - CONSTRAINT fk_club_tags_tag FOREIGN key(tag_id) REFERENCES tags(id) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT fk_club_tags_club FOREIGN key(club_id) REFERENCES clubs(id ) ON UPDATE CASCADE ON DELETE CASCADE + PRIMARY KEY(tag_id, club_id), + CONSTRAINT fk_club_tags_tag FOREIGN KEY(tag_id) REFERENCES tags(id) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT fk_club_tags_club FOREIGN KEY(club_id) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS contacts( id uuid NOT NULL DEFAULT uuid_generate_v4(), - created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + created_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, "type" varchar(255) NOT NULL, content varchar(255) NOT NULL, club_id uuid NOT NULL, PRIMARY KEY(id), - CONSTRAINT fk_clubs_contact FOREIGN key(club_id) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE + CONSTRAINT fk_clubs_contact FOREIGN KEY(club_id) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE ); -CREATE UNIQUE INDEX IF NOT EXISTS idx_contact_type ON contacts USING btree ("type","club_id"); +CREATE UNIQUE INDEX IF NOT EXISTS idx_contact_type ON contacts USING btree ("type", "club_id"); CREATE TABLE IF NOT EXISTS event_tags( tag_id uuid NOT NULL DEFAULT uuid_generate_v4(), event_id uuid NOT NULL DEFAULT uuid_generate_v4(), - PRIMARY KEY(tag_id,event_id), - CONSTRAINT fk_event_tags_tag FOREIGN key(tag_id) REFERENCES tags(id) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT fk_event_tags_event FOREIGN key(event_id) REFERENCES events(id) ON UPDATE CASCADE ON DELETE CASCADE + PRIMARY KEY(tag_id, event_id), + CONSTRAINT fk_event_tags_tag FOREIGN KEY(tag_id) REFERENCES tags(id) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT fk_event_tags_event FOREIGN KEY(event_id) REFERENCES "events"(id) ON UPDATE CASCADE ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS files( id uuid NOT NULL DEFAULT uuid_generate_v4(), - created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + created_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, owner_id uuid NOT NULL, owner_type varchar(255) NOT NULL, file_name varchar(255) NOT NULL, @@ -151,7 +182,9 @@ CREATE TABLE IF NOT EXISTS files( object_key varchar(255) NOT NULL, PRIMARY KEY(id) ); + CREATE INDEX IF NOT EXISTS idx_files_owner_type ON files USING btree ("owner_type"); + CREATE INDEX IF NOT EXISTS idx_files_owner_id ON files USING btree ("owner_id"); -- CREATE TABLE IF NOT EXISTS notifications( @@ -167,81 +200,81 @@ CREATE INDEX IF NOT EXISTS idx_files_owner_id ON files USING btree ("owner_id"); -- reference_type varchar(255) NOT NULL, -- PRIMARY KEY(id) -- ); - -CREATE TABLE IF NOT EXISTS point_of_contacts( +CREATE TABLE IF NOT EXISTS leadership( id uuid NOT NULL DEFAULT uuid_generate_v4(), - created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + created_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp WITH time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, name varchar(255) NOT NULL, email varchar(255) NOT NULL, position varchar(255) NOT NULL, club_id uuid NOT NULL, PRIMARY KEY(id), - CONSTRAINT fk_clubs_point_of_contact FOREIGN key(club_id) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE + CONSTRAINT fk_clubs_point_of_contact FOREIGN KEY(club_id) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE ); -CREATE UNIQUE INDEX IF NOT EXISTS compositeindex ON point_of_contacts USING btree ("email","club_id"); -CREATE INDEX IF NOT EXISTS idx_point_of_contacts_club_id ON point_of_contacts USING btree ("club_id"); -CREATE INDEX IF NOT EXISTS idx_point_of_contacts_email ON point_of_contacts USING btree ("email"); +CREATE UNIQUE INDEX IF NOT EXISTS compositeindex ON leadership USING btree ("email", "club_id"); + +CREATE INDEX IF NOT EXISTS idx_leadership_club_id ON leadership USING btree ("club_id"); +CREATE INDEX IF NOT EXISTS idx_leadership_email ON leadership USING btree ("email"); CREATE TABLE IF NOT EXISTS user_club_followers( user_id uuid NOT NULL DEFAULT uuid_generate_v4(), club_id uuid NOT NULL DEFAULT uuid_generate_v4(), - PRIMARY KEY(user_id,club_id), - CONSTRAINT fk_user_club_followers_user FOREIGN key(user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT fk_user_club_followers_club FOREIGN key(club_id) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE + PRIMARY KEY(user_id, club_id), + CONSTRAINT fk_user_club_followers_user FOREIGN KEY(user_id) REFERENCES "users"(id) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT fk_user_club_followers_club FOREIGN KEY(club_id) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS user_club_intended_applicants( user_id uuid NOT NULL DEFAULT uuid_generate_v4(), club_id uuid NOT NULL DEFAULT uuid_generate_v4(), - PRIMARY KEY(user_id,club_id), - CONSTRAINT fk_user_club_intended_applicants_user FOREIGN key(user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT fk_user_club_intended_applicants_club FOREIGN key(club_id) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE + PRIMARY KEY(user_id, club_id), + CONSTRAINT fk_user_club_intended_applicants_user FOREIGN KEY(user_id) REFERENCES "users"(id) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT fk_user_club_intended_applicants_club FOREIGN KEY(club_id) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS user_club_members( user_id uuid NOT NULL, club_id uuid NOT NULL, - membership_type varchar(255) NOT NULL DEFAULT 'member'::character varying, - PRIMARY KEY(user_id,club_id), - CONSTRAINT fk_user_club_members_user FOREIGN key(user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT fk_user_club_members_club FOREIGN key(club_id) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE + membership_type varchar(255) NOT NULL DEFAULT 'member' :: character varying, + PRIMARY KEY(user_id, club_id), + CONSTRAINT fk_user_club_members_user FOREIGN KEY(user_id) REFERENCES "users"(id) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT fk_user_club_members_club FOREIGN KEY(club_id) REFERENCES clubs(id) ON UPDATE CASCADE ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS user_event_rsvps( user_id uuid NOT NULL DEFAULT uuid_generate_v4(), event_id uuid NOT NULL DEFAULT uuid_generate_v4(), - PRIMARY KEY(user_id,event_id), - CONSTRAINT fk_user_event_rsvps_user FOREIGN key(user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT fk_user_event_rsvps_event FOREIGN key(event_id) REFERENCES events(id) ON UPDATE CASCADE ON DELETE CASCADE + PRIMARY KEY(user_id, event_id), + CONSTRAINT fk_user_event_rsvps_user FOREIGN KEY(user_id) REFERENCES "users"(id) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT fk_user_event_rsvps_event FOREIGN KEY(event_id) REFERENCES "events"(id) ON UPDATE CASCADE ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS user_event_waitlists( user_id uuid NOT NULL DEFAULT uuid_generate_v4(), event_id uuid NOT NULL DEFAULT uuid_generate_v4(), - PRIMARY KEY(user_id,event_id), - CONSTRAINT fk_user_event_waitlists_user FOREIGN key(user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT fk_user_event_waitlists_event FOREIGN key(event_id) REFERENCES events(id) ON UPDATE CASCADE ON DELETE CASCADE + PRIMARY KEY(user_id, event_id), + CONSTRAINT fk_user_event_waitlists_user FOREIGN KEY(user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT fk_user_event_waitlists_event FOREIGN KEY(event_id) REFERENCES "events"(id) ON UPDATE CASCADE ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS user_tags( user_id uuid NOT NULL DEFAULT uuid_generate_v4(), tag_id uuid NOT NULL DEFAULT uuid_generate_v4(), - PRIMARY KEY(user_id,tag_id), - CONSTRAINT fk_user_tags_user FOREIGN key(user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT fk_user_tags_tag FOREIGN key(tag_id) REFERENCES tags(id) ON UPDATE CASCADE ON DELETE CASCADE + PRIMARY KEY(user_id, tag_id), + CONSTRAINT fk_user_tags_user FOREIGN KEY(user_id) REFERENCES "users"(id) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT fk_user_tags_tag FOREIGN KEY(tag_id) REFERENCES tags(id) ON UPDATE CASCADE ON DELETE CASCADE ); -CREATE UNIQUE INDEX IF NOT EXISTS uni_users_email ON users USING btree ("email"); +CREATE UNIQUE INDEX IF NOT EXISTS uni_users_email ON "users" USING btree ("email"); CREATE TABLE IF NOT EXISTS verifications( user_id uuid NOT NULL, token varchar(255), - expires_at timestamp with time zone NOT NULL, + expires_at timestamp WITH time zone NOT NULL, "type" varchar(255) NOT NULL, - PRIMARY KEY(user_id,expires_at) + PRIMARY KEY(user_id, expires_at) ); CREATE UNIQUE INDEX IF NOT EXISTS uni_verifications_token ON verifications USING btree ("token"); @@ -254,8 +287,8 @@ CREATE TABLE IF NOT EXISTS user_oauth_tokens( access_token varchar, csrf_token varchar(255), resource_type OAuthResourceType, - expires_at timestamp with time zone NOT NULL, - PRIMARY KEY(user_id,resource_type) + expires_at timestamp WITH time zone NOT NULL, + PRIMARY KEY(user_id, resource_type) ); -COMMIT; +COMMIT; \ No newline at end of file diff --git a/backend/search/base/controller.go b/backend/search/base/controller.go index 4f7e4b8d7..bd8a6b474 100644 --- a/backend/search/base/controller.go +++ b/backend/search/base/controller.go @@ -20,12 +20,12 @@ func NewSearchController(searchService SearchServiceInterface) *SearchController // // @Summary Searches through clubs // @Description Searches through clubs -// @ID search-club +// @ID search-clubs // @Tags search // @Accept json // @Produce json -// @Param searchQuery query models.SearchQueryParams true "Search Body" -// @Success 200 {object} []models.ClubSearchResult +// @Param searchQuery query search_types.ClubSearchRequest true "Search Body" +// @Success 200 {object} []search_types.SearchResult[models.Club] // @Failure 404 {object} error // @Failure 500 {object} error // @Router /search/clubs [get] @@ -48,12 +48,12 @@ func (s *SearchController) SearchClubs(c *fiber.Ctx) error { // // @Summary Searches through events // @Description Searches through events -// @ID search-club +// @ID search-event // @Tags search // @Accept json // @Produce json -// @Param searchQuery query models.SearchQueryParams true "Search Body" -// @Success 200 {object} []models.EventsSearchResult +// @Param searchQuery query search_types.EventSearchRequest true "Search Body" +// @Success 200 {object} []search_types.SearchResult[models.Event] // @Failure 404 {object} error // @Failure 500 {object} error // @Router /search/events [get] diff --git a/backend/search/base/transactions.go b/backend/search/base/transactions.go index 9ea68146a..8ddfdce38 100644 --- a/backend/search/base/transactions.go +++ b/backend/search/base/transactions.go @@ -66,7 +66,11 @@ func Search[T types.Searchable](db *gorm.DB, query types.SearchRequest) (*types. func Upsert[T types.Searchable](db *gorm.DB, index string, uuid string, model types.ToSearchDocument) error { var elem T - if err := db.Model(&model).Preload("Tag").Where("Clubs").Where("id = ?", uuid).Find(&elem).Error; err != nil { + query := db.Model(&elem) + + query = elem.Preload(query) + + if err := query.Where("id = ?", uuid).Find(&elem).Error; err != nil { return err } diff --git a/backend/search/types/utilities.go b/backend/search/types/utilities.go index 4edfee64f..364bf464d 100644 --- a/backend/search/types/utilities.go +++ b/backend/search/types/utilities.go @@ -1,6 +1,9 @@ package types -import "github.com/GenerateNU/sac/backend/entities/models" +import ( + "github.com/GenerateNU/sac/backend/entities/models" + "gorm.io/gorm" +) type Json map[string]interface{} @@ -21,4 +24,5 @@ type BulkRequestCreate struct { type Searchable interface { models.Club | models.Event + Preload(db *gorm.DB) *gorm.DB } diff --git a/backend/server/server.go b/backend/server/server.go index 55e272e95..bf466e1f1 100644 --- a/backend/server/server.go +++ b/backend/server/server.go @@ -13,11 +13,11 @@ import ( auth "github.com/GenerateNU/sac/backend/entities/auth/base" categories "github.com/GenerateNU/sac/backend/entities/categories/base" clubs "github.com/GenerateNU/sac/backend/entities/clubs/base" - contacts "github.com/GenerateNU/sac/backend/entities/contacts/base" events "github.com/GenerateNU/sac/backend/entities/events/base" files "github.com/GenerateNU/sac/backend/entities/files/base" + leader "github.com/GenerateNU/sac/backend/entities/leadership/base" oauth "github.com/GenerateNU/sac/backend/entities/oauth/base" - pocs "github.com/GenerateNU/sac/backend/entities/pocs/base" + socials "github.com/GenerateNU/sac/backend/entities/socials/base" tags "github.com/GenerateNU/sac/backend/entities/tags/base" users "github.com/GenerateNU/sac/backend/entities/users/base" "github.com/GenerateNU/sac/backend/integrations" @@ -86,8 +86,8 @@ func allRoutes(app *fiber.App, routeParams types.RouteParams) { auth.Auth(routeParams) users.UserRoutes(routeParams) clubs.ClubRoutes(routeParams) - contacts.Contact(routeParams) - pocs.PointOfContact(routeParams) + socials.Social(routeParams) + leader.Leader(routeParams) tags.Tag(routeParams) categories.CategoryRoutes(routeParams) events.EventRoutes(routeParams) diff --git a/backend/transactions/preloaders.go b/backend/transactions/preloaders.go index b4c873c73..7f06a182d 100644 --- a/backend/transactions/preloaders.go +++ b/backend/transactions/preloaders.go @@ -27,3 +27,9 @@ func PreloadEvent() OptionalQuery { return db.Preload("Event") } } + +func PreloadRecruitment() OptionalQuery { + return func(db *gorm.DB) *gorm.DB { + return db.Preload("Recruitment") + } +} diff --git a/backend/utilities/api_error.go b/backend/utilities/api_error.go index f4ecc154e..7d6e297e6 100644 --- a/backend/utilities/api_error.go +++ b/backend/utilities/api_error.go @@ -10,9 +10,9 @@ import ( ) var ( - ErrNotFound = errors.New("not found") - ErrDuplicate = errors.New("duplicate") - ErrExpectedPagination = errors.New("expected pagination. make sure to use the fiberpaginate middleware") + ErrNotFound = errors.New("not found") + ErrDuplicate = errors.New("duplicate") + ErrExpectedPageInfo = errors.New("expected page info. make sure to use the fiberpaginate middleware") ) func IsNotFound(err error) bool { @@ -23,8 +23,8 @@ func IsDuplicate(err error) bool { return errors.Is(err, ErrDuplicate) } -func IsExpectedPagination(err error) bool { - return errors.Is(err, ErrExpectedPagination) +func IsExpectedPageInfo(err error) bool { + return errors.Is(err, ErrExpectedPageInfo) } type APIError struct { @@ -86,7 +86,7 @@ func ErrorHandler(c *fiber.Ctx, err error) error { apiErr = NewAPIError(http.StatusNotFound, err) case ErrDuplicate: apiErr = NewAPIError(http.StatusConflict, err) - case ErrExpectedPagination: + case ErrExpectedPageInfo: apiErr = NewAPIError(http.StatusInternalServerError, err) default: if castedErr, ok := err.(APIError); ok { diff --git a/backend/utilities/ctx.go b/backend/utilities/ctx.go new file mode 100644 index 000000000..005f5ef9e --- /dev/null +++ b/backend/utilities/ctx.go @@ -0,0 +1,44 @@ +package utilities + +import ( + "context" +) + +func ExecuteWithTimeout(ctx context.Context, fn func() error) error { + errChan := make(chan error) + + go func() { + err := fn() + errChan <- err + }() + + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errChan: + return err + } +} + +func ExecuteWithTimeoutResult[Result any](ctx context.Context, fn func() (*Result, error)) (*Result, error) { + resultChan := make(chan *Result) + errChan := make(chan error) + + go func() { + result, err := fn() + if err != nil { + errChan <- err + } else { + resultChan <- result + } + }() + + select { + case <-ctx.Done(): + return nil, ctx.Err() + case err := <-errChan: + return nil, err + case result := <-resultChan: + return result, nil + } +} diff --git a/backend/utilities/time.go b/backend/utilities/time.go new file mode 100644 index 000000000..5676509cc --- /dev/null +++ b/backend/utilities/time.go @@ -0,0 +1,13 @@ +package utilities + +import "time" + +type TimeFormat string + +const ( + YYYY_dash_MM_dash_DD TimeFormat = "2006-01-02" +) + +func ParseTime(s string, format TimeFormat) (time.Time, error) { + return time.Parse(string(format), s) +} diff --git a/backend/utilities/validator.go b/backend/utilities/validator.go index 22edbf8a9..a8d1ac444 100644 --- a/backend/utilities/validator.go +++ b/backend/utilities/validator.go @@ -24,8 +24,8 @@ func RegisterCustomValidators() (*validator.Validate, error) { return nil, err } - if err := validate.RegisterValidation("contact_pointer", func(fl validator.FieldLevel) bool { - return validateContactPointer(validate, fl) + if err := validate.RegisterValidation("social_pointer", func(fl validator.FieldLevel) bool { + return validateSocialPointer(validate, fl) }); err != nil { return nil, err } @@ -58,16 +58,16 @@ func validateS3URL(fl validator.FieldLevel) bool { return strings.HasPrefix(fl.Field().String(), "https://s3.amazonaws.com/") } -func validateContactPointer(validate *validator.Validate, fl validator.FieldLevel) bool { - contact, ok := fl.Parent().Interface().(struct { - Type models.ContactType +func validateSocialPointer(validate *validator.Validate, fl validator.FieldLevel) bool { + social, ok := fl.Parent().Interface().(struct { + Type models.SocialType Content string }) if !ok { return false } - validationRules := map[models.ContactType]string{ + validationRules := map[models.SocialType]string{ models.Facebook: "http_url", models.Instagram: "http_url", models.X: "http_url", @@ -80,12 +80,12 @@ func validateContactPointer(validate *validator.Validate, fl validator.FieldLeve models.CustomSite: "http_url", } - rule, ok := validationRules[contact.Type] + rule, ok := validationRules[social.Type] if !ok { return false // invalid contact type } - return validate.Var(contact.Content, rule) == nil && strings.HasPrefix(contact.Content, models.GetContentPrefix(contact.Type)) + return validate.Var(social.Content, rule) == nil && strings.HasPrefix(social.Content, models.GetSocialPrefix(social.Type)) } func validateNotEqualIfNotEmpty(fl validator.FieldLevel) bool { diff --git a/cli/go.sum b/cli/go.sum index 3803ea3ae..b1cae943d 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -1,38 +1,92 @@ github.com/GenerateNU/sac/backend v0.0.0-20240427140745-74eb4ec0f597 h1:bhattf8C4aGIdJ+0L168MztGMKcznF0ZiEDhvLlHMEU= +github.com/GenerateNU/sac/backend v0.0.0-20240427140745-74eb4ec0f597/go.mod h1:VEuNTR6WQ0OQ5fjMjoRcIymCMjRLdRUM611roK9yQYQ= github.com/awnumar/memcall v0.2.0 h1:sRaogqExTOOkkNwO9pzJsL8jrOV29UuUW7teRMfbqtI= +github.com/awnumar/memcall v0.2.0/go.mod h1:S911igBPR9CThzd/hYQQmTc9SWNu3ZHIlCGaWsWsoJo= github.com/awnumar/memguard v0.22.5 h1:PH7sbUVERS5DdXh3+mLo8FDcl1eIeVjJVYMnyuYpvuI= +github.com/awnumar/memguard v0.22.5/go.mod h1:+APmZGThMBWjnMlKiSM1X7MVpbIVewen2MTkqWkA/zE= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/garrettladley/mattress v0.4.0 h1:ZB3iqyc5q6bqIryNfsh2FMcbMdnV1XEryvqivouceQE= +github.com/garrettladley/mattress v0.4.0/go.mod h1:OWKIRc9wC3gtD3Ng/nUuNEiR1TJvRYLmn/KZYw9nl5Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= +github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8= +golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +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= diff --git a/cli/helpers/config.go b/cli/helpers/config.go index bdcb76d36..89b68f754 100644 --- a/cli/helpers/config.go +++ b/cli/helpers/config.go @@ -15,7 +15,7 @@ var ( WEB_DIR = filepath.Join(FRONTEND_DIR, "/web") BACKEND_DIR = filepath.Join(ROOT_DIR, "/backend") CLI_DIR = filepath.Join(ROOT_DIR, "/cli") - CONFIG = filepath.Join(ROOT_DIR, "/config") + CONFIG_FILE = filepath.Join(ROOT_DIR, "/config/.env.template") MIGRATIONS = filepath.Join(BACKEND_DIR, "/migrations") MOCK_FILE = filepath.Join(BACKEND_DIR, "/mock/data.sql") ) diff --git a/cli/helpers/database.go b/cli/helpers/database.go index d6be0e299..ad2951c00 100644 --- a/cli/helpers/database.go +++ b/cli/helpers/database.go @@ -22,7 +22,7 @@ func InitDB() error { } func DownDB() error { - config, err := config.GetConfiguration(CONFIG) + config, err := config.GetConfiguration(CONFIG_FILE) if err != nil { return err } @@ -33,8 +33,7 @@ func DownDB() error { cmd.Dir = ROOT_DIR cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - err = cmd.Run() - if err != nil { + if cmd.Run() != nil { return fmt.Errorf("error running down migrations: %w", err) } @@ -42,7 +41,7 @@ func DownDB() error { } func CleanTestDBs() error { - config, err := config.GetConfiguration(CONFIG) + config, err := config.GetConfiguration(CONFIG_FILE) if err != nil { return err } @@ -102,7 +101,7 @@ func CleanTestDBs() error { } func InsertDB() error { - config, err := config.GetConfiguration(CONFIG) + config, err := config.GetConfiguration(CONFIG_FILE) if err != nil { return err }