diff --git a/Building b/Building new file mode 100644 index 00000000..e69de29b diff --git a/Makefile b/Makefile index 28b2eab3..30e9ecc5 100644 --- a/Makefile +++ b/Makefile @@ -47,9 +47,13 @@ fmt: ## Format all go files @go fmt ./... check: ## Run static analysis on all go files - staticcheck ./... + @printf "[\t\033[1;34mBuilding project\033[0m\t]\n" + @go build ./... 2>&1 || (printf "\033[1;31;1mError building\033[0m: "; exit 1) + @printf "[\t\033[1;34mRunning staticcheck\033[0m\t]\n" + staticcheck -f stylish ./... test: check ## Run all tests + @printf "[\t\033[1;34mRunning go tests\033[0m\t]\n" go test ./... check-sql: ## Lint all sql files diff --git a/internal/api/dbmodels/board.sql.go b/internal/api/dbmodels/board.sql.go index 1ce78a0d..217ce3d2 100644 --- a/internal/api/dbmodels/board.sql.go +++ b/internal/api/dbmodels/board.sql.go @@ -56,21 +56,34 @@ INSERT INTO position ( oid, semester, - tier + tier, + full_name, + title, + team ) VALUES -(?, ?, ?) +(?, ?, ?, ?, ?, ?) RETURNING oid, semester, tier, full_name, title, team ` type CreatePositionParams struct { - Oid string `json:"oid"` - Semester string `json:"semester"` - Tier int64 `json:"tier"` + Oid string `json:"oid"` + Semester string `json:"semester"` + Tier int64 `json:"tier"` + FullName string `json:"full_name"` + Title sql.NullString `json:"title"` + Team sql.NullString `json:"team"` } func (q *Queries) CreatePosition(ctx context.Context, arg CreatePositionParams) (Position, error) { - row := q.db.QueryRowContext(ctx, createPosition, arg.Oid, arg.Semester, arg.Tier) + row := q.db.QueryRowContext(ctx, createPosition, + arg.Oid, + arg.Semester, + arg.Tier, + arg.FullName, + arg.Title, + arg.Team, + ) var i Position err := row.Scan( &i.Oid, diff --git a/internal/api/docs/docs.go b/internal/api/docs/docs.go index 808210d4..e0d3c641 100644 --- a/internal/api/docs/docs.go +++ b/internal/api/docs/docs.go @@ -35,7 +35,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.CreateAnnouncementParams" + "$ref": "#/definitions/domain.Announcement" } } ], @@ -94,7 +94,7 @@ const docTemplate = `{ "200": { "description": "Announcement details", "schema": { - "$ref": "#/definitions/dbmodels.Announcement" + "$ref": "#/definitions/dto_request.Announcement" } }, "404": { @@ -143,7 +143,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.UpdateAnnouncementParams" + "$ref": "#/definitions/domain.Announcement" } } ], @@ -257,7 +257,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/dbmodels.Officer" + "$ref": "#/definitions/domain.Officer" } } }, @@ -291,7 +291,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.CreateOfficerParams" + "$ref": "#/definitions/domain.Officer" } } ], @@ -350,7 +350,7 @@ const docTemplate = `{ "200": { "description": "Officer details", "schema": { - "$ref": "#/definitions/dbmodels.Officer" + "$ref": "#/definitions/dto_request.Officer" } }, "404": { @@ -399,7 +399,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.UpdateOfficerParams" + "$ref": "#/definitions/domain.Officer" } } ], @@ -513,7 +513,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/dbmodels.Position" + "$ref": "#/definitions/dto_request.Position" } } }, @@ -547,7 +547,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.UpdatePositionParams" + "$ref": "#/definitions/domain.Position" } } ], @@ -609,7 +609,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.CreatePositionParams" + "$ref": "#/definitions/domain.Position" } } ], @@ -660,7 +660,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.DeletePositionParams" + "$ref": "#/definitions/domain.Position" } } ], @@ -730,7 +730,7 @@ const docTemplate = `{ "200": { "description": "Position details", "schema": { - "$ref": "#/definitions/dbmodels.Position" + "$ref": "#/definitions/domain.Position" } }, "404": { @@ -773,7 +773,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/dbmodels.Tier" + "$ref": "#/definitions/dto_request.Tier" } } }, @@ -807,7 +807,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.CreateTierParams" + "$ref": "#/definitions/domain.Tier" } } ], @@ -924,7 +924,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.UpdateTierParams" + "$ref": "#/definitions/domain.Tier" } } ], @@ -1055,7 +1055,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/dbmodels.Event" + "$ref": "#/definitions/domain.Event" } } }, @@ -1089,7 +1089,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.CreateEventParams" + "$ref": "#/definitions/domain.Event" } } ], @@ -1148,7 +1148,7 @@ const docTemplate = `{ "200": { "description": "Event details", "schema": { - "$ref": "#/definitions/dbmodels.Event" + "$ref": "#/definitions/dto_request.Event" } }, "404": { @@ -1197,7 +1197,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.UpdateEventParams" + "$ref": "#/definitions/domain.Event" } } ], @@ -1294,181 +1294,90 @@ const docTemplate = `{ } }, "definitions": { - "dbmodels.Announcement": { + "dbmodels.Tier": { "type": "object", "properties": { - "announce_at": { - "type": "integer" - }, - "discord_channel_id": { - "$ref": "#/definitions/sql.NullString" + "t_index": { + "$ref": "#/definitions/sql.NullInt64" }, - "discord_message_id": { + "team": { "$ref": "#/definitions/sql.NullString" }, - "uuid": { - "type": "string" - }, - "visibility": { - "type": "string" - } - } - }, - "dbmodels.CreateAnnouncementParams": { - "type": "object", - "properties": { - "announce_at": { + "tier": { "type": "integer" }, - "discord_channel_id": { - "$ref": "#/definitions/sql.NullString" - }, - "discord_message_id": { + "title": { "$ref": "#/definitions/sql.NullString" - }, - "uuid": { - "type": "string" - }, - "visibility": { - "type": "string" } } }, - "dbmodels.CreateEventParams": { + "domain.Announcement": { "type": "object", "properties": { - "end_at": { - "type": "integer" - }, - "host": { - "type": "string" - }, - "is_all_day": { - "type": "boolean" - }, - "location": { + "announceAt": { "type": "string" }, - "start_at": { - "type": "integer" - }, - "uuid": { + "discordChannelID": { "type": "string" - } - } - }, - "dbmodels.CreateOfficerParams": { - "type": "object", - "properties": { - "discord": { - "$ref": "#/definitions/sql.NullString" }, - "full_name": { + "discordMessageID": { "type": "string" }, - "github": { - "$ref": "#/definitions/sql.NullString" - }, - "picture": { - "$ref": "#/definitions/sql.NullString" - }, "uuid": { "type": "string" - } - } - }, - "dbmodels.CreatePositionParams": { - "type": "object", - "properties": { - "oid": { - "type": "string" }, - "semester": { + "visibility": { "type": "string" - }, - "tier": { - "type": "integer" } } }, - "dbmodels.CreateTierParams": { + "domain.Event": { "type": "object", "properties": { - "t_index": { - "$ref": "#/definitions/sql.NullInt64" - }, - "team": { - "$ref": "#/definitions/sql.NullString" - }, - "tier": { - "type": "integer" - }, - "title": { - "$ref": "#/definitions/sql.NullString" - } - } - }, - "dbmodels.DeletePositionParams": { - "type": "object", - "properties": { - "oid": { + "endAt": { "type": "string" }, - "semester": { - "type": "string" - }, - "tier": { - "type": "integer" - } - } - }, - "dbmodels.Event": { - "type": "object", - "properties": { - "end_at": { - "type": "integer" - }, "host": { "type": "string" }, - "is_all_day": { + "isAllDay": { "type": "boolean" }, "location": { "type": "string" }, - "start_at": { - "type": "integer" + "startAt": { + "type": "string" }, "uuid": { "type": "string" } } }, - "dbmodels.Officer": { + "domain.Officer": { "type": "object", "properties": { "discord": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, - "full_name": { + "fullName": { "type": "string" }, "github": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "picture": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "uuid": { "type": "string" } } }, - "dbmodels.Position": { + "domain.Position": { "type": "object", "properties": { - "full_name": { + "fullName": { "type": "string" }, "oid": { @@ -1478,97 +1387,97 @@ const docTemplate = `{ "type": "string" }, "team": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "tier": { "type": "integer" }, "title": { - "$ref": "#/definitions/sql.NullString" + "type": "string" } } }, - "dbmodels.Tier": { + "domain.Tier": { "type": "object", "properties": { - "t_index": { - "$ref": "#/definitions/sql.NullInt64" - }, "team": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "tier": { "type": "integer" }, + "tindex": { + "type": "integer" + }, "title": { - "$ref": "#/definitions/sql.NullString" + "type": "string" } } }, - "dbmodels.UpdateAnnouncementParams": { + "dto_request.Announcement": { "type": "object", "properties": { "announce_at": { - "$ref": "#/definitions/sql.NullInt64" + "type": "integer" }, "discord_channel_id": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "discord_message_id": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "uuid": { "type": "string" }, "visibility": { - "$ref": "#/definitions/sql.NullString" + "type": "string" } } }, - "dbmodels.UpdateEventParams": { + "dto_request.Event": { "type": "object", "properties": { "end_at": { - "$ref": "#/definitions/sql.NullInt64" + "type": "integer" }, "host": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "is_all_day": { - "$ref": "#/definitions/sql.NullBool" + "type": "boolean" }, "location": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "start_at": { - "$ref": "#/definitions/sql.NullInt64" + "type": "integer" }, "uuid": { "type": "string" } } }, - "dbmodels.UpdateOfficerParams": { + "dto_request.Officer": { "type": "object", "properties": { "discord": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "full_name": { "type": "string" }, "github": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "picture": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "uuid": { "type": "string" } } }, - "dbmodels.UpdatePositionParams": { + "dto_request.Position": { "type": "object", "properties": { "full_name": { @@ -1581,42 +1490,30 @@ const docTemplate = `{ "type": "string" }, "team": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "tier": { "type": "integer" }, "title": { - "$ref": "#/definitions/sql.NullString" + "type": "string" } } }, - "dbmodels.UpdateTierParams": { + "dto_request.Tier": { "type": "object", "properties": { "t_index": { - "$ref": "#/definitions/sql.NullInt64" + "type": "integer" }, "team": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "tier": { "type": "integer" }, "title": { - "$ref": "#/definitions/sql.NullString" - } - } - }, - "sql.NullBool": { - "type": "object", - "properties": { - "bool": { - "type": "boolean" - }, - "valid": { - "description": "Valid is true if Bool is not NULL", - "type": "boolean" + "type": "string" } } }, diff --git a/internal/api/docs/swagger.json b/internal/api/docs/swagger.json index 08f86682..049f92ed 100644 --- a/internal/api/docs/swagger.json +++ b/internal/api/docs/swagger.json @@ -24,7 +24,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.CreateAnnouncementParams" + "$ref": "#/definitions/domain.Announcement" } } ], @@ -83,7 +83,7 @@ "200": { "description": "Announcement details", "schema": { - "$ref": "#/definitions/dbmodels.Announcement" + "$ref": "#/definitions/dto_request.Announcement" } }, "404": { @@ -132,7 +132,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.UpdateAnnouncementParams" + "$ref": "#/definitions/domain.Announcement" } } ], @@ -246,7 +246,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/dbmodels.Officer" + "$ref": "#/definitions/domain.Officer" } } }, @@ -280,7 +280,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.CreateOfficerParams" + "$ref": "#/definitions/domain.Officer" } } ], @@ -339,7 +339,7 @@ "200": { "description": "Officer details", "schema": { - "$ref": "#/definitions/dbmodels.Officer" + "$ref": "#/definitions/dto_request.Officer" } }, "404": { @@ -388,7 +388,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.UpdateOfficerParams" + "$ref": "#/definitions/domain.Officer" } } ], @@ -502,7 +502,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/dbmodels.Position" + "$ref": "#/definitions/dto_request.Position" } } }, @@ -536,7 +536,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.UpdatePositionParams" + "$ref": "#/definitions/domain.Position" } } ], @@ -598,7 +598,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.CreatePositionParams" + "$ref": "#/definitions/domain.Position" } } ], @@ -649,7 +649,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.DeletePositionParams" + "$ref": "#/definitions/domain.Position" } } ], @@ -719,7 +719,7 @@ "200": { "description": "Position details", "schema": { - "$ref": "#/definitions/dbmodels.Position" + "$ref": "#/definitions/domain.Position" } }, "404": { @@ -762,7 +762,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/dbmodels.Tier" + "$ref": "#/definitions/dto_request.Tier" } } }, @@ -796,7 +796,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.CreateTierParams" + "$ref": "#/definitions/domain.Tier" } } ], @@ -913,7 +913,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.UpdateTierParams" + "$ref": "#/definitions/domain.Tier" } } ], @@ -1044,7 +1044,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/dbmodels.Event" + "$ref": "#/definitions/domain.Event" } } }, @@ -1078,7 +1078,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.CreateEventParams" + "$ref": "#/definitions/domain.Event" } } ], @@ -1137,7 +1137,7 @@ "200": { "description": "Event details", "schema": { - "$ref": "#/definitions/dbmodels.Event" + "$ref": "#/definitions/dto_request.Event" } }, "404": { @@ -1186,7 +1186,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dbmodels.UpdateEventParams" + "$ref": "#/definitions/domain.Event" } } ], @@ -1283,181 +1283,90 @@ } }, "definitions": { - "dbmodels.Announcement": { + "dbmodels.Tier": { "type": "object", "properties": { - "announce_at": { - "type": "integer" - }, - "discord_channel_id": { - "$ref": "#/definitions/sql.NullString" + "t_index": { + "$ref": "#/definitions/sql.NullInt64" }, - "discord_message_id": { + "team": { "$ref": "#/definitions/sql.NullString" }, - "uuid": { - "type": "string" - }, - "visibility": { - "type": "string" - } - } - }, - "dbmodels.CreateAnnouncementParams": { - "type": "object", - "properties": { - "announce_at": { + "tier": { "type": "integer" }, - "discord_channel_id": { - "$ref": "#/definitions/sql.NullString" - }, - "discord_message_id": { + "title": { "$ref": "#/definitions/sql.NullString" - }, - "uuid": { - "type": "string" - }, - "visibility": { - "type": "string" } } }, - "dbmodels.CreateEventParams": { + "domain.Announcement": { "type": "object", "properties": { - "end_at": { - "type": "integer" - }, - "host": { - "type": "string" - }, - "is_all_day": { - "type": "boolean" - }, - "location": { + "announceAt": { "type": "string" }, - "start_at": { - "type": "integer" - }, - "uuid": { + "discordChannelID": { "type": "string" - } - } - }, - "dbmodels.CreateOfficerParams": { - "type": "object", - "properties": { - "discord": { - "$ref": "#/definitions/sql.NullString" }, - "full_name": { + "discordMessageID": { "type": "string" }, - "github": { - "$ref": "#/definitions/sql.NullString" - }, - "picture": { - "$ref": "#/definitions/sql.NullString" - }, "uuid": { "type": "string" - } - } - }, - "dbmodels.CreatePositionParams": { - "type": "object", - "properties": { - "oid": { - "type": "string" }, - "semester": { + "visibility": { "type": "string" - }, - "tier": { - "type": "integer" } } }, - "dbmodels.CreateTierParams": { + "domain.Event": { "type": "object", "properties": { - "t_index": { - "$ref": "#/definitions/sql.NullInt64" - }, - "team": { - "$ref": "#/definitions/sql.NullString" - }, - "tier": { - "type": "integer" - }, - "title": { - "$ref": "#/definitions/sql.NullString" - } - } - }, - "dbmodels.DeletePositionParams": { - "type": "object", - "properties": { - "oid": { + "endAt": { "type": "string" }, - "semester": { - "type": "string" - }, - "tier": { - "type": "integer" - } - } - }, - "dbmodels.Event": { - "type": "object", - "properties": { - "end_at": { - "type": "integer" - }, "host": { "type": "string" }, - "is_all_day": { + "isAllDay": { "type": "boolean" }, "location": { "type": "string" }, - "start_at": { - "type": "integer" + "startAt": { + "type": "string" }, "uuid": { "type": "string" } } }, - "dbmodels.Officer": { + "domain.Officer": { "type": "object", "properties": { "discord": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, - "full_name": { + "fullName": { "type": "string" }, "github": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "picture": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "uuid": { "type": "string" } } }, - "dbmodels.Position": { + "domain.Position": { "type": "object", "properties": { - "full_name": { + "fullName": { "type": "string" }, "oid": { @@ -1467,97 +1376,97 @@ "type": "string" }, "team": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "tier": { "type": "integer" }, "title": { - "$ref": "#/definitions/sql.NullString" + "type": "string" } } }, - "dbmodels.Tier": { + "domain.Tier": { "type": "object", "properties": { - "t_index": { - "$ref": "#/definitions/sql.NullInt64" - }, "team": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "tier": { "type": "integer" }, + "tindex": { + "type": "integer" + }, "title": { - "$ref": "#/definitions/sql.NullString" + "type": "string" } } }, - "dbmodels.UpdateAnnouncementParams": { + "dto_request.Announcement": { "type": "object", "properties": { "announce_at": { - "$ref": "#/definitions/sql.NullInt64" + "type": "integer" }, "discord_channel_id": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "discord_message_id": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "uuid": { "type": "string" }, "visibility": { - "$ref": "#/definitions/sql.NullString" + "type": "string" } } }, - "dbmodels.UpdateEventParams": { + "dto_request.Event": { "type": "object", "properties": { "end_at": { - "$ref": "#/definitions/sql.NullInt64" + "type": "integer" }, "host": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "is_all_day": { - "$ref": "#/definitions/sql.NullBool" + "type": "boolean" }, "location": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "start_at": { - "$ref": "#/definitions/sql.NullInt64" + "type": "integer" }, "uuid": { "type": "string" } } }, - "dbmodels.UpdateOfficerParams": { + "dto_request.Officer": { "type": "object", "properties": { "discord": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "full_name": { "type": "string" }, "github": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "picture": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "uuid": { "type": "string" } } }, - "dbmodels.UpdatePositionParams": { + "dto_request.Position": { "type": "object", "properties": { "full_name": { @@ -1570,42 +1479,30 @@ "type": "string" }, "team": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "tier": { "type": "integer" }, "title": { - "$ref": "#/definitions/sql.NullString" + "type": "string" } } }, - "dbmodels.UpdateTierParams": { + "dto_request.Tier": { "type": "object", "properties": { "t_index": { - "$ref": "#/definitions/sql.NullInt64" + "type": "integer" }, "team": { - "$ref": "#/definitions/sql.NullString" + "type": "string" }, "tier": { "type": "integer" }, "title": { - "$ref": "#/definitions/sql.NullString" - } - } - }, - "sql.NullBool": { - "type": "object", - "properties": { - "bool": { - "type": "boolean" - }, - "valid": { - "description": "Valid is true if Bool is not NULL", - "type": "boolean" + "type": "string" } } }, diff --git a/internal/api/docs/swagger.yaml b/internal/api/docs/swagger.yaml index c7679583..a04f5797 100644 --- a/internal/api/docs/swagger.yaml +++ b/internal/api/docs/swagger.yaml @@ -1,68 +1,5 @@ definitions: - dbmodels.Announcement: - properties: - announce_at: - type: integer - discord_channel_id: - $ref: '#/definitions/sql.NullString' - discord_message_id: - $ref: '#/definitions/sql.NullString' - uuid: - type: string - visibility: - type: string - type: object - dbmodels.CreateAnnouncementParams: - properties: - announce_at: - type: integer - discord_channel_id: - $ref: '#/definitions/sql.NullString' - discord_message_id: - $ref: '#/definitions/sql.NullString' - uuid: - type: string - visibility: - type: string - type: object - dbmodels.CreateEventParams: - properties: - end_at: - type: integer - host: - type: string - is_all_day: - type: boolean - location: - type: string - start_at: - type: integer - uuid: - type: string - type: object - dbmodels.CreateOfficerParams: - properties: - discord: - $ref: '#/definitions/sql.NullString' - full_name: - type: string - github: - $ref: '#/definitions/sql.NullString' - picture: - $ref: '#/definitions/sql.NullString' - uuid: - type: string - type: object - dbmodels.CreatePositionParams: - properties: - oid: - type: string - semester: - type: string - tier: - type: integer - type: object - dbmodels.CreateTierParams: + dbmodels.Tier: properties: t_index: $ref: '#/definitions/sql.NullInt64' @@ -73,111 +10,115 @@ definitions: title: $ref: '#/definitions/sql.NullString' type: object - dbmodels.DeletePositionParams: + domain.Announcement: properties: - oid: + announceAt: type: string - semester: + discordChannelID: + type: string + discordMessageID: + type: string + uuid: + type: string + visibility: type: string - tier: - type: integer type: object - dbmodels.Event: + domain.Event: properties: - end_at: - type: integer + endAt: + type: string host: type: string - is_all_day: + isAllDay: type: boolean location: type: string - start_at: - type: integer + startAt: + type: string uuid: type: string type: object - dbmodels.Officer: + domain.Officer: properties: discord: - $ref: '#/definitions/sql.NullString' - full_name: + type: string + fullName: type: string github: - $ref: '#/definitions/sql.NullString' + type: string picture: - $ref: '#/definitions/sql.NullString' + type: string uuid: type: string type: object - dbmodels.Position: + domain.Position: properties: - full_name: + fullName: type: string oid: type: string semester: type: string team: - $ref: '#/definitions/sql.NullString' + type: string tier: type: integer title: - $ref: '#/definitions/sql.NullString' + type: string type: object - dbmodels.Tier: + domain.Tier: properties: - t_index: - $ref: '#/definitions/sql.NullInt64' team: - $ref: '#/definitions/sql.NullString' + type: string tier: type: integer + tindex: + type: integer title: - $ref: '#/definitions/sql.NullString' + type: string type: object - dbmodels.UpdateAnnouncementParams: + dto_request.Announcement: properties: announce_at: - $ref: '#/definitions/sql.NullInt64' + type: integer discord_channel_id: - $ref: '#/definitions/sql.NullString' + type: string discord_message_id: - $ref: '#/definitions/sql.NullString' + type: string uuid: type: string visibility: - $ref: '#/definitions/sql.NullString' + type: string type: object - dbmodels.UpdateEventParams: + dto_request.Event: properties: end_at: - $ref: '#/definitions/sql.NullInt64' + type: integer host: - $ref: '#/definitions/sql.NullString' + type: string is_all_day: - $ref: '#/definitions/sql.NullBool' + type: boolean location: - $ref: '#/definitions/sql.NullString' + type: string start_at: - $ref: '#/definitions/sql.NullInt64' + type: integer uuid: type: string type: object - dbmodels.UpdateOfficerParams: + dto_request.Officer: properties: discord: - $ref: '#/definitions/sql.NullString' + type: string full_name: type: string github: - $ref: '#/definitions/sql.NullString' + type: string picture: - $ref: '#/definitions/sql.NullString' + type: string uuid: type: string type: object - dbmodels.UpdatePositionParams: + dto_request.Position: properties: full_name: type: string @@ -186,30 +127,22 @@ definitions: semester: type: string team: - $ref: '#/definitions/sql.NullString' + type: string tier: type: integer title: - $ref: '#/definitions/sql.NullString' + type: string type: object - dbmodels.UpdateTierParams: + dto_request.Tier: properties: t_index: - $ref: '#/definitions/sql.NullInt64' + type: integer team: - $ref: '#/definitions/sql.NullString' + type: string tier: type: integer title: - $ref: '#/definitions/sql.NullString' - type: object - sql.NullBool: - properties: - bool: - type: boolean - valid: - description: Valid is true if Bool is not NULL - type: boolean + type: string type: object sql.NullInt64: properties: @@ -242,7 +175,7 @@ paths: name: body required: true schema: - $ref: '#/definitions/dbmodels.CreateAnnouncementParams' + $ref: '#/definitions/domain.Announcement' produces: - application/json responses: @@ -317,7 +250,7 @@ paths: "200": description: Announcement details schema: - $ref: '#/definitions/dbmodels.Announcement' + $ref: '#/definitions/dto_request.Announcement' "404": description: Not Found schema: @@ -348,7 +281,7 @@ paths: name: body required: true schema: - $ref: '#/definitions/dbmodels.UpdateAnnouncementParams' + $ref: '#/definitions/domain.Announcement' produces: - application/json responses: @@ -391,7 +324,7 @@ paths: description: List of officers schema: items: - $ref: '#/definitions/dbmodels.Officer' + $ref: '#/definitions/domain.Officer' type: array "500": description: Internal Server Error @@ -412,7 +345,7 @@ paths: name: body required: true schema: - $ref: '#/definitions/dbmodels.CreateOfficerParams' + $ref: '#/definitions/domain.Officer' produces: - application/json responses: @@ -487,7 +420,7 @@ paths: "200": description: Officer details schema: - $ref: '#/definitions/dbmodels.Officer' + $ref: '#/definitions/dto_request.Officer' "404": description: Not Found schema: @@ -518,7 +451,7 @@ paths: name: body required: true schema: - $ref: '#/definitions/dbmodels.UpdateOfficerParams' + $ref: '#/definitions/domain.Officer' produces: - application/json responses: @@ -561,7 +494,7 @@ paths: name: body required: true schema: - $ref: '#/definitions/dbmodels.DeletePositionParams' + $ref: '#/definitions/domain.Position' produces: - application/json responses: @@ -603,7 +536,7 @@ paths: description: List of positions schema: items: - $ref: '#/definitions/dbmodels.Position' + $ref: '#/definitions/dto_request.Position' type: array "500": description: Internal Server Error @@ -624,7 +557,7 @@ paths: name: body required: true schema: - $ref: '#/definitions/dbmodels.CreatePositionParams' + $ref: '#/definitions/domain.Position' produces: - application/json responses: @@ -658,7 +591,7 @@ paths: name: body required: true schema: - $ref: '#/definitions/dbmodels.UpdatePositionParams' + $ref: '#/definitions/domain.Position' produces: - application/json responses: @@ -706,7 +639,7 @@ paths: "200": description: Position details schema: - $ref: '#/definitions/dbmodels.Position' + $ref: '#/definitions/domain.Position' "404": description: Not Found schema: @@ -734,7 +667,7 @@ paths: description: List of tiers schema: items: - $ref: '#/definitions/dbmodels.Tier' + $ref: '#/definitions/dto_request.Tier' type: array "500": description: Internal Server Error @@ -755,7 +688,7 @@ paths: name: body required: true schema: - $ref: '#/definitions/dbmodels.CreateTierParams' + $ref: '#/definitions/domain.Tier' produces: - application/json responses: @@ -873,7 +806,7 @@ paths: name: body required: true schema: - $ref: '#/definitions/dbmodels.UpdateTierParams' + $ref: '#/definitions/domain.Tier' produces: - application/json responses: @@ -921,7 +854,7 @@ paths: description: List of events schema: items: - $ref: '#/definitions/dbmodels.Event' + $ref: '#/definitions/domain.Event' type: array "500": description: Internal Server Error @@ -942,7 +875,7 @@ paths: name: body required: true schema: - $ref: '#/definitions/dbmodels.CreateEventParams' + $ref: '#/definitions/domain.Event' produces: - application/json responses: @@ -1017,7 +950,7 @@ paths: "200": description: Event details schema: - $ref: '#/definitions/dbmodels.Event' + $ref: '#/definitions/dto_request.Event' "404": description: Not Found schema: @@ -1048,7 +981,7 @@ paths: name: body required: true schema: - $ref: '#/definitions/dbmodels.UpdateEventParams' + $ref: '#/definitions/domain.Event' produces: - application/json responses: diff --git a/internal/api/handlers/announcement.go b/internal/api/handlers/announcement.go index 2fc804eb..24e01794 100644 --- a/internal/api/handlers/announcement.go +++ b/internal/api/handlers/announcement.go @@ -3,10 +3,12 @@ package handlers import ( + "fmt" "net/http" - "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/services" + "github.com/acmcsufoss/api.acmcsuf.com/internal/dto/request" + "github.com/acmcsufoss/api.acmcsuf.com/internal/mapper" "github.com/gin-gonic/gin" ) @@ -26,7 +28,7 @@ func NewAnnouncementHandler(announcementService services.AnnouncementServicer) * // @Accept json // @Produce json // @Param id path string true "Announcement ID" -// @Success 200 {object} dbmodels.Announcement "Announcement details" +// @Success 200 {object} dto_request.Announcement "Announcement details" // @Failure 404 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /v1/announcements/{id} [get] @@ -35,7 +37,6 @@ func (h *AnnouncementHandler) GetAnnouncement(c *gin.Context) { id := c.Param("id") announcement, err := h.announcementService.Get(ctx, id) - if err != nil { if err.Error() == "sql: no rows in result set" { c.JSON(http.StatusNotFound, gin.H{ @@ -46,9 +47,10 @@ func (h *AnnouncementHandler) GetAnnouncement(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to retrieve announcement", }) + return } - c.JSON(http.StatusOK, announcement) + c.JSON(http.StatusOK, mapper.ToAnnouncementDTO(&announcement)) } func (h *AnnouncementHandler) GetAnnouncements(c *gin.Context) { @@ -73,15 +75,17 @@ func (h *AnnouncementHandler) GetAnnouncements(c *gin.Context) { // @Tags Announcements // @Accept json // @Produce json -// @Param body body dbmodels.CreateAnnouncementParams true "Announcement data" +// @Param body body domain.Announcement true "Announcement data" // @Success 200 {object} map[string]interface{} "Success message with UUID" // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /v1/announcements [post] func (h *AnnouncementHandler) CreateAnnouncement(c *gin.Context) { ctx := c.Request.Context() - var params dbmodels.CreateAnnouncementParams + var params dto_request.Announcement + // fmt.Println(params) + fmt.Println(c.Request.Body) if err := c.ShouldBindJSON(¶ms); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Invalid request body. " + err.Error(), @@ -90,7 +94,7 @@ func (h *AnnouncementHandler) CreateAnnouncement(c *gin.Context) { } // TODO: error out if required fields aren't provided - if err := h.announcementService.Create(ctx, params); err != nil { + if err := h.announcementService.Create(ctx, mapper.ToAnnouncementDomain(¶ms)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to create announcement", }) @@ -111,7 +115,7 @@ func (h *AnnouncementHandler) CreateAnnouncement(c *gin.Context) { // @Accept json // @Produce json // @Param id path string true "Announcement ID" -// @Param body body dbmodels.UpdateAnnouncementParams true "Updated announcement data" +// @Param body body domain.Announcement true "Updated announcement data" // @Success 200 {object} map[string]string "Success message" // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string @@ -119,16 +123,17 @@ func (h *AnnouncementHandler) CreateAnnouncement(c *gin.Context) { // @Router /v1/announcements/{id} [put] func (h *AnnouncementHandler) UpdateAnnouncement(c *gin.Context) { ctx := c.Request.Context() - var params dbmodels.UpdateAnnouncementParams + var params dto_request.UpdateAnnouncement id := c.Param("id") if err := c.ShouldBindJSON(¶ms); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Invalid request body. " + err.Error(), }) + return } - if err := h.announcementService.Update(ctx, id, params); err != nil { + if err := h.announcementService.Update(ctx, id, mapper.ToUpdateAnnouncementDomain(¶ms)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to update announcement", }) diff --git a/internal/api/handlers/board.go b/internal/api/handlers/board.go deleted file mode 100644 index 59287dec..00000000 --- a/internal/api/handlers/board.go +++ /dev/null @@ -1,541 +0,0 @@ -// Package handlers handles http requests and responses -// Business logic belongs in services, not here -package handlers - -import ( - "net/http" - "strconv" - - "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" - "github.com/acmcsufoss/api.acmcsuf.com/internal/api/services" - "github.com/gin-gonic/gin" -) - -type BoardHandler struct { - boardService services.BoardServicer -} - -func NewBoardHandler(boardService services.BoardServicer) *BoardHandler { - return &BoardHandler{boardService: boardService} -} - -// GetOfficer godoc -// -// @Summary Get an Officer by UUID -// @Description Retrieves a single officer from the database. -// @Tags Board -// @Accept json -// @Produce json -// @Param id path string true "Officer UUID" -// @Success 200 {object} dbmodels.Officer "Officer details" -// @Failure 404 {object} map[string]string -// @Failure 500 {object} map[string]string -// @Router /v1/board/officers/{id} [get] -func (h *BoardHandler) GetOfficer(c *gin.Context) { - ctx := c.Request.Context() - id := c.Param("id") - - officer, err := h.boardService.GetOfficer(ctx, id) - - if err != nil { - if err.Error() == "sql: no rows in result set" { - c.JSON(http.StatusNotFound, gin.H{ - "error": "Officer not found", - }) - return - } - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to retrieve officer", - }) - return - } - - c.JSON(http.StatusOK, officer) -} - -// GetOfficers godoc -// -// @Summary Get all officers -// @Description Get all officers from the database -// @Tags Board -// @Accept json -// @Produce json -// @Success 200 {array} dbmodels.Officer "List of officers" -// @Failure 500 {object} map[string]string -// @Router /v1/board/officers [get] -func (h *BoardHandler) GetOfficers(c *gin.Context) { - ctx := c.Request.Context() - - officers, err := h.boardService.ListOfficers(ctx) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to retrieve officers", - }) - return - } - - c.JSON(http.StatusOK, officers) -} - -// CreateOfficer godoc -// -// @Summary Creates a new officer -// @Description Creates a new officer in the database. -// @Tags Board -// @Accept json -// @Produce json -// @Param body body dbmodels.CreateOfficerParams true "Officer data" -// @Success 200 {object} map[string]interface{} "Success message with UUID" -// @Failure 400 {object} map[string]string -// @Failure 500 {object} map[string]string -// @Router /v1/board/officers [post] -func (h *BoardHandler) CreateOfficer(c *gin.Context) { - ctx := c.Request.Context() - var params dbmodels.CreateOfficerParams - - if err := c.ShouldBindJSON(¶ms); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Invalid request body. " + err.Error(), - }) - return - } - - if params.FullName == "" { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "FullName is a required field", - }) - return - } - - if err := h.boardService.CreateOfficer(ctx, params); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to create officer. " + err.Error(), - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "Officer created successfully", - "uuid": params.Uuid, - }) -} - -// UpdateOfficer godoc -// -// @Summary Updates an officer -// @Description Updates an officer in the database -// @Tags Board -// @Accept json -// @Produce json -// @Param id path string true "Officer UUID" -// @Param body body dbmodels.UpdateOfficerParams true "Updated officer data" -// @Success 200 {object} map[string]string "Success message" -// @Failure 400 {object} map[string]string -// @Failure 404 {object} map[string]string -// @Failure 500 {object} map[string]string -// @Router /v1/board/officers/{id} [put] -func (h *BoardHandler) UpdateOfficer(c *gin.Context) { - ctx := c.Request.Context() - var params dbmodels.UpdateOfficerParams - id := c.Param("id") - - if err := c.ShouldBindJSON(¶ms); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Invalid request body. " + err.Error(), - }) - return - } - - if err := h.boardService.UpdateOfficer(ctx, id, params); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to update officer. " + err.Error(), - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "Officer updated successfully", - "uuid": id, - }) -} - -// DeleteOfficer godoc -// -// @Summary Deletes an officer -// @Description Delete an officer from the database -// @Tags Board -// @Accept json -// @Produce json -// @Param id path string true "Officer UUID" -// @Success 200 {object} map[string]string "Success message" -// @Failure 404 {object} map[string]string -// @Failure 500 {object} map[string]string -// @Router /v1/board/officers/{id} [delete] -func (h *BoardHandler) DeleteOfficer(c *gin.Context) { - ctx := c.Request.Context() - id := c.Param("id") - - if err := h.boardService.DeleteOfficer(ctx, id); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to delete officer", - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "Officer deleted successfully", - }) -} - -// GetTiers godoc -// -// @Summary Get all tiers -// @Description Get all tiers from the database -// @Tags Board -// @Accept json -// @Produce json -// @Success 200 {array} dbmodels.Tier "List of tiers" -// @Failure 500 {object} map[string]string -// @Router /v1/board/tiers [get] -func (h *BoardHandler) GetTiers(c *gin.Context) { - ctx := c.Request.Context() - - tiers, err := h.boardService.ListTiers(ctx) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to retrieve tiers", - }) - return - } - - c.JSON(http.StatusOK, tiers) -} - -// GetTier godoc -// -// @Summary Get a Tier by tier number -// @Description Retrieves a single tier from the database. -// @Tags Board -// @Accept json -// @Produce json -// @Param id path int true "Tier number" -// @Success 200 {object} dbmodels.Tier "Tier details" -// @Failure 400 {object} map[string]string -// @Failure 404 {object} map[string]string -// @Failure 500 {object} map[string]string -// @Router /v1/board/tiers/{id} [get] -func (h *BoardHandler) GetTier(c *gin.Context) { - ctx := c.Request.Context() - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Invalid tier number", - }) - return - } - - tier, err := h.boardService.GetTier(ctx, int64(id)) - if err != nil { - if err.Error() == "sql: no rows in result set" { - c.JSON(http.StatusNotFound, gin.H{ - "error": "Tier not found", - }) - return - } - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to retrieve tier", - }) - return - } - - c.JSON(http.StatusOK, tier) -} - -// CreateTier godoc -// -// @Summary Creates a new tier -// @Description Creates a new tier in the database. -// @Tags Board -// @Accept json -// @Produce json -// @Param body body dbmodels.CreateTierParams true "Tier data" -// @Success 200 {object} map[string]interface{} "Success message with tier number" -// @Failure 400 {object} map[string]string -// @Failure 500 {object} map[string]string -// @Router /v1/board/tiers [post] -func (h *BoardHandler) CreateTier(c *gin.Context) { - ctx := c.Request.Context() - var params dbmodels.CreateTierParams - - if err := c.ShouldBindJSON(¶ms); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Invalid request body. " + err.Error(), - }) - return - } - - if err := h.boardService.CreateTier(ctx, params); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to create tier. " + err.Error(), - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "Tier created successfully", - "tier": params.Tier, - }) -} - -// UpdateTier godoc -// -// @Summary Updates a tier -// @Description Updates a tier in the database -// @Tags Board -// @Accept json -// @Produce json -// @Param id path int true "Tier number" -// @Param body body dbmodels.UpdateTierParams true "Updated tier data" -// @Success 200 {object} map[string]string "Success message" -// @Failure 400 {object} map[string]string -// @Failure 404 {object} map[string]string -// @Failure 500 {object} map[string]string -// @Router /v1/board/tiers/{id} [put] -func (h *BoardHandler) UpdateTier(c *gin.Context) { - ctx := c.Request.Context() - var params dbmodels.UpdateTierParams - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Invalid tier number", - }) - return - } - - if err := c.ShouldBindJSON(¶ms); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Invalid request body. " + err.Error(), - }) - return - } - - params.Tier = int64(id) - - if err := h.boardService.UpdateTier(ctx, params); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to update tier. " + err.Error(), - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "Tier updated successfully", - "tier": params.Tier, - }) -} - -// DeleteTier godoc -// -// @Summary Deletes a tier -// @Description Delete a tier from the database -// @Tags Board -// @Accept json -// @Produce json -// @Param id path int true "Tier number" -// @Success 200 {object} map[string]string "Success message" -// @Failure 400 {object} map[string]string -// @Failure 404 {object} map[string]string -// @Failure 500 {object} map[string]string -// @Router /v1/board/tiers/{id} [delete] -func (h *BoardHandler) DeleteTier(c *gin.Context) { - ctx := c.Request.Context() - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Invalid tier number", - }) - return - } - - if err := h.boardService.DeleteTier(ctx, int64(id)); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to delete tier", - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "Tier deleted successfully", - }) -} - -// GetPositions godoc -// -// @Summary Get all positions -// @Description Get all positions from the database -// @Tags Board -// @Accept json -// @Produce json -// @Success 200 {array} dbmodels.Position "List of positions" -// @Failure 500 {object} map[string]string -// @Router /v1/board/positions [get] -func (h *BoardHandler) GetPositions(c *gin.Context) { - ctx := c.Request.Context() - - positions, err := h.boardService.ListPositions(ctx) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to retrieve positions", - }) - return - } - - c.JSON(http.StatusOK, positions) -} - -// GetPosition godoc -// -// @Summary Get a Position by UUID -// @Description Retrieves a single position from the database by officer UUID. -// @Tags Board -// @Accept json -// @Produce json -// @Param id path string true "Officer full name" -// @Success 200 {object} dbmodels.Position "Position details" -// @Failure 404 {object} map[string]string -// @Failure 500 {object} map[string]string -// @Router /v1/board/positions/{id} [get] -func (h *BoardHandler) GetPosition(c *gin.Context) { - ctx := c.Request.Context() - id := c.Param("id") - - position, err := h.boardService.GetPosition(ctx, id) - - if err != nil { - if err.Error() == "sql: no rows in result set" { - c.JSON(http.StatusNotFound, gin.H{ - "error": "Position not found", - }) - return - } - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to retrieve position", - }) - return - } - - c.JSON(http.StatusOK, position) -} - -// CreatePosition godoc -// -// @Summary Creates a new position -// @Description Creates a new position in the database. -// @Tags Board -// @Accept json -// @Produce json -// @Param body body dbmodels.CreatePositionParams true "Position data" -// @Success 200 {object} map[string]interface{} "Success message" -// @Failure 400 {object} map[string]string -// @Failure 500 {object} map[string]string -// @Router /v1/board/positions [post] -func (h *BoardHandler) CreatePosition(c *gin.Context) { - ctx := c.Request.Context() - var params dbmodels.CreatePositionParams - - if err := c.ShouldBindJSON(¶ms); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Invalid request body. " + err.Error(), - }) - return - } - - if err := h.boardService.CreatePosition(ctx, params); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to create position. " + err.Error(), - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "Position created successfully", - "oid": params.Oid, - "semester": params.Semester, - "tier": params.Tier, - }) -} - -// UpdatePosition godoc -// -// @Summary Updates a position -// @Description Updates a position in the database -// @Tags Board -// @Accept json -// @Produce json -// @Param body body dbmodels.UpdatePositionParams true "Updated position data (must include oid, semester, tier)" -// @Success 200 {object} map[string]string "Success message" -// @Failure 400 {object} map[string]string -// @Failure 404 {object} map[string]string -// @Failure 500 {object} map[string]string -// @Router /v1/board/positions [put] -func (h *BoardHandler) UpdatePosition(c *gin.Context) { - ctx := c.Request.Context() - var params dbmodels.UpdatePositionParams - - if err := c.ShouldBindJSON(¶ms); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Invalid request body. " + err.Error(), - }) - return - } - - if err := h.boardService.UpdatePosition(ctx, params); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to update position. " + err.Error(), - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "Position updated successfully", - "oid": params.Oid, - "semester": params.Semester, - "tier": params.Tier, - }) -} - -// DeletePosition godoc -// -// @Summary Deletes a position -// @Description Delete a position from the database (requires oid, semester, and tier) -// @Tags Board -// @Accept json -// @Produce json -// @Param body body dbmodels.DeletePositionParams true "Position identifier" -// @Success 200 {object} map[string]string "Success message" -// @Failure 400 {object} map[string]string -// @Failure 404 {object} map[string]string -// @Failure 500 {object} map[string]string -// @Router /v1/board/positions [delete] -func (h *BoardHandler) DeletePosition(c *gin.Context) { - ctx := c.Request.Context() - var params dbmodels.DeletePositionParams - - if err := c.ShouldBindJSON(¶ms); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Invalid request body. " + err.Error(), - }) - return - } - - if err := h.boardService.DeletePosition(ctx, params); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to delete position", - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "Position deleted successfully", - }) -} diff --git a/internal/api/handlers/event.go b/internal/api/handlers/event.go index e3ad3792..53ecc2cb 100644 --- a/internal/api/handlers/event.go +++ b/internal/api/handlers/event.go @@ -6,8 +6,9 @@ import ( "fmt" "net/http" - "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/services" + dto_request "github.com/acmcsufoss/api.acmcsuf.com/internal/dto/request" + "github.com/acmcsufoss/api.acmcsuf.com/internal/mapper" "github.com/gin-gonic/gin" ) @@ -29,7 +30,7 @@ func NewEventHandler(eventService services.EventsServicer) *EventsHandler { // @Accept json // @Produce json // @Param id path string true "Event ID" -// @Success 200 {object} dbmodels.Event "Event details" +// @Success 200 {object} dto_request.Event "Event details" // @Failure 404 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /v1/events/{id} [get] @@ -38,7 +39,6 @@ func (h *EventsHandler) GetEvent(c *gin.Context) { id := c.Param("id") event, err := h.eventsService.Get(ctx, id) - if err != nil { if err.Error() == "sql: no rows in result set" { c.JSON(http.StatusNotFound, gin.H{ @@ -48,7 +48,7 @@ func (h *EventsHandler) GetEvent(c *gin.Context) { } c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to retrieve event", + "error": "Failed to retrieve event: " + err.Error(), }) return } @@ -63,14 +63,14 @@ func (h *EventsHandler) GetEvent(c *gin.Context) { // @Tags Events // @Accept json // @Produce json -// @Param body body dbmodels.CreateEventParams true "Event data" +// @Param body body domain.Event true "Event data" // @Success 200 {object} map[string]interface{} "Success message with UUID" // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /v1/events [post] func (h *EventsHandler) CreateEvent(c *gin.Context) { ctx := c.Request.Context() - var params dbmodels.CreateEventParams + var params dto_request.Event if err := c.ShouldBindJSON(¶ms); err != nil { c.JSON(http.StatusBadRequest, gin.H{ @@ -86,7 +86,7 @@ func (h *EventsHandler) CreateEvent(c *gin.Context) { return } - err := h.eventsService.Create(ctx, params) + err := h.eventsService.Create(ctx, mapper.ToEventDomain(¶ms)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to create event. " + err.Error(), @@ -95,7 +95,7 @@ func (h *EventsHandler) CreateEvent(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{ "message": "Event created successfully", - "uuid": params.Uuid, + "uuid": mapper.ToEventDomain(¶ms).Uuid, }) } @@ -107,7 +107,7 @@ func (h *EventsHandler) CreateEvent(c *gin.Context) { // @Accept json // @Produce json // @Param host query string false "Filter by host" -// @Success 200 {array} dbmodels.Event "List of events" +// @Success 200 {array} domain.Event "List of events" // @Failure 500 {object} map[string]string // @Router /v1/events [get] func (h *EventsHandler) GetEvents(c *gin.Context) { @@ -122,7 +122,7 @@ func (h *EventsHandler) GetEvents(c *gin.Context) { events, err := h.eventsService.List(ctx, filters...) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to retrieve events", + "error": "Failed to retrieve events:" + err.Error(), }) return } @@ -137,7 +137,7 @@ func (h *EventsHandler) GetEvents(c *gin.Context) { // @Accept json // @Produce json // @Param id path string true "Event ID" -// @Param body body dbmodels.UpdateEventParams true "Updated event data" +// @Param body body domain.Event true "Updated event data" // @Success 200 {object} map[string]string "Success message" // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string @@ -145,7 +145,7 @@ func (h *EventsHandler) GetEvents(c *gin.Context) { // @Router /v1/events/{id} [put] func (h *EventsHandler) UpdateEvent(c *gin.Context) { ctx := c.Request.Context() - var params dbmodels.UpdateEventParams + var params dto_request.UpdateEvent id := c.Param("id") fmt.Println("UPDATE:", id) @@ -156,16 +156,17 @@ func (h *EventsHandler) UpdateEvent(c *gin.Context) { return } - if err := h.eventsService.Update(ctx, id, params); err != nil { + if err := h.eventsService.Update(ctx, id, mapper.ToUpdateEventDomain(¶ms)); err != nil { error := fmt.Sprint("Failed to update event: ", err, " | ", ctx, " | ", id, " | ", params) c.JSON(http.StatusInternalServerError, gin.H{ "error": error, }) return } + c.JSON(http.StatusOK, gin.H{ "message": "Event updated successfully", - "uuid": params.Uuid, + "uuid": mapper.ToUpdateEventDomain(¶ms).Uuid, }) } diff --git a/internal/api/handlers/officer.go b/internal/api/handlers/officer.go new file mode 100644 index 00000000..97524263 --- /dev/null +++ b/internal/api/handlers/officer.go @@ -0,0 +1,187 @@ +// Package handlers handles http requests and responses +// Business logic belongs in services, not here +package handlers + +import ( + "net/http" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/services" + dto_request "github.com/acmcsufoss/api.acmcsuf.com/internal/dto/request" + "github.com/acmcsufoss/api.acmcsuf.com/internal/mapper" + "github.com/gin-gonic/gin" +) + +type OfficerHandler struct { + officerService services.OfficerServicer +} + +func NewOfficerHandler(officerService services.OfficerServicer) *OfficerHandler { + return &OfficerHandler{officerService: officerService} +} + +// GetOfficer godoc +// +// @Summary Get an Officer by UUID +// @Description Retrieves a single officer from the database. +// @Tags Board +// @Accept json +// @Produce json +// @Param id path string true "Officer UUID" +// @Success 200 {object} dto_request.Officer "Officer details" +// @Failure 404 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /v1/board/officers/{id} [get] +func (h *OfficerHandler) GetOfficer(c *gin.Context) { + ctx := c.Request.Context() + id := c.Param("id") + + officer, err := h.officerService.Get(ctx, id) + if err != nil { + if err.Error() == "sql: no rows in result set" { + c.JSON(http.StatusNotFound, gin.H{ + "error": "Officer not found", + }) + return + } + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to retrieve officer", + }) + return + } + + c.JSON(http.StatusOK, officer) +} + +// GetOfficers godoc +// +// @Summary Get all officers +// @Description Get all officers from the database +// @Tags Board +// @Accept json +// @Produce json +// @Success 200 {array} domain.Officer "List of officers" +// @Failure 500 {object} map[string]string +// @Router /v1/board/officers [get] +func (h *OfficerHandler) GetOfficers(c *gin.Context) { + ctx := c.Request.Context() + + officers, err := h.officerService.List(ctx) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to retrieve officers", + }) + return + } + + c.JSON(http.StatusOK, officers) +} + +// CreateOfficer godoc +// +// @Summary Creates a new officer +// @Description Creates a new officer in the database. +// @Tags Board +// @Accept json +// @Produce json +// @Param body body domain.Officer true "Officer data" +// @Success 200 {object} map[string]interface{} "Success message with UUID" +// @Failure 400 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /v1/board/officers [post] +func (h *OfficerHandler) CreateOfficer(c *gin.Context) { + ctx := c.Request.Context() + var params dto_request.Officer + + if err := c.ShouldBindJSON(¶ms); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid request body. " + err.Error(), + }) + return + } + + if params.FullName == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "FullName is a required field", + }) + return + } + + if err := h.officerService.Create(ctx, mapper.ToOfficerDomain(¶ms)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to create officer. " + err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Officer created successfully", + "uuid": mapper.ToOfficerDomain(¶ms).Uuid, + }) +} + +// UpdateOfficer godoc +// +// @Summary Updates an officer +// @Description Updates an officer in the database +// @Tags Board +// @Accept json +// @Produce json +// @Param id path string true "Officer UUID" +// @Param body body domain.Officer true "Updated officer data" +// @Success 200 {object} map[string]string "Success message" +// @Failure 400 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /v1/board/officers/{id} [put] +func (h *OfficerHandler) UpdateOfficer(c *gin.Context) { + ctx := c.Request.Context() + var params dto_request.UpdateOfficer + id := c.Param("id") + + if err := c.ShouldBindJSON(¶ms); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid request body. " + err.Error(), + }) + return + } + + if err := h.officerService.Update(ctx, id, mapper.ToUpdateOfficerDomain(¶ms)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to update officer. " + err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Officer updated successfully", + "uuid": id, + }) +} + +// DeleteOfficer godoc +// +// @Summary Deletes an officer +// @Description Delete an officer from the database +// @Tags Board +// @Accept json +// @Produce json +// @Param id path string true "Officer UUID" +// @Success 200 {object} map[string]string "Success message" +// @Failure 404 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /v1/board/officers/{id} [delete] +func (h *OfficerHandler) DeleteOfficer(c *gin.Context) { + ctx := c.Request.Context() + id := c.Param("id") + + if err := h.officerService.Delete(ctx, id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to delete officer", + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Officer deleted successfully", + }) +} diff --git a/internal/api/handlers/position.go b/internal/api/handlers/position.go new file mode 100644 index 00000000..78a3e27e --- /dev/null +++ b/internal/api/handlers/position.go @@ -0,0 +1,191 @@ +// Package handlers handles http requests and responses +// Business logic belongs in services, not here +package handlers + +import ( + "net/http" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/services" + dto_request "github.com/acmcsufoss/api.acmcsuf.com/internal/dto/request" + "github.com/acmcsufoss/api.acmcsuf.com/internal/mapper" + "github.com/gin-gonic/gin" +) + +type PositionHandler struct { + positionService services.PositionServicer +} + +func NewPositionHandler(positionService services.PositionServicer) *PositionHandler { + return &PositionHandler{positionService: positionService} +} + +// GetPositions godoc +// +// @Summary Get all positions +// @Description Get all positions from the database +// @Tags Board +// @Accept json +// @Produce json +// @Success 200 {array} dto_request.Position "List of positions" +// @Failure 500 {object} map[string]string +// @Router /v1/board/positions [get] +func (h *PositionHandler) GetPositions(c *gin.Context) { + ctx := c.Request.Context() + + positions, err := h.positionService.List(ctx) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to retrieve positions", + }) + return + } + + c.JSON(http.StatusOK, positions) +} + +// GetPosition godoc +// +// @Summary Get a Position by UUID +// @Description Retrieves a single position from the database by officer UUID. +// @Tags Board +// @Accept json +// @Produce json +// @Param id path string true "Officer full name" +// @Success 200 {object} domain.Position "Position details" +// @Failure 404 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /v1/board/positions/{id} [get] +func (h *PositionHandler) GetPosition(c *gin.Context) { + ctx := c.Request.Context() + id := c.Param("id") + + position, err := h.positionService.Get(ctx, id) + if err != nil { + if err.Error() == "sql: no rows in result set" { + c.JSON(http.StatusNotFound, gin.H{ + "error": "Position not found", + }) + return + } + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to retrieve position", + }) + return + } + + c.JSON(http.StatusOK, position) +} + +// CreatePosition godoc +// +// @Summary Creates a new position +// @Description Creates a new position in the database. +// @Tags Board +// @Accept json +// @Produce json +// @Param body body domain.Position true "Position data" +// @Success 200 {object} map[string]interface{} "Success message" +// @Failure 400 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /v1/board/positions [post] +func (h *PositionHandler) CreatePosition(c *gin.Context) { + ctx := c.Request.Context() + var params dto_request.Position + + if err := c.ShouldBindJSON(¶ms); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid request body. " + err.Error(), + }) + return + } + + if err := h.positionService.Create(ctx, mapper.ToPositionDomain(¶ms)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to create position. " + err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Position created successfully", + "oid": params.Oid, + "semester": params.Semester, + "tier": params.Tier, + }) +} + +// UpdatePosition godoc +// +// @Summary Updates a position +// @Description Updates a position in the database +// @Tags Board +// @Accept json +// @Produce json +// @Param body body domain.Position true "Updated position data (must include oid, semester, tier)" +// @Success 200 {object} map[string]string "Success message" +// @Failure 400 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /v1/board/positions [put] +func (h *PositionHandler) UpdatePosition(c *gin.Context) { + ctx := c.Request.Context() + var params dto_request.UpdatePosition + id := c.Param("id") + + if err := c.ShouldBindJSON(¶ms); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid request body. " + err.Error(), + }) + return + } + + if err := h.positionService.Update(ctx, id, mapper.ToUpdatePositionDomain(¶ms)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to update position. " + err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Position updated successfully", + "oid": params.Oid, + "semester": params.Semester, + "tier": params.Tier, + }) +} + +// DeletePosition godoc +// +// @Summary Deletes a position +// @Description Delete a position from the database (requires oid, semester, and tier) +// @Tags Board +// @Accept json +// @Produce json +// @Param body body domain.Position true "Position identifier" +// @Success 200 {object} map[string]string "Success message" +// @Failure 400 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /v1/board/positions [delete] +func (h *PositionHandler) DeletePosition(c *gin.Context) { + ctx := c.Request.Context() + var params dto_request.Position + + if err := c.ShouldBindJSON(¶ms); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid request body. " + err.Error(), + }) + return + } + + if err := h.positionService.DeletePosition(ctx, mapper.ToPositionDomain(¶ms)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to delete position", + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Position deleted successfully", + }) +} diff --git a/internal/api/handlers/tier.go b/internal/api/handlers/tier.go new file mode 100644 index 00000000..b4802b34 --- /dev/null +++ b/internal/api/handlers/tier.go @@ -0,0 +1,203 @@ +// Package handlers handles http requests and responses +// Business logic belongs in services, not here +package handlers + +import ( + "net/http" + "strconv" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/services" + dto_request "github.com/acmcsufoss/api.acmcsuf.com/internal/dto/request" + "github.com/acmcsufoss/api.acmcsuf.com/internal/mapper" + "github.com/gin-gonic/gin" +) + +type TierHandler struct { + tierService services.TierServicer +} + +func NewTierHandler(tierService services.TierServicer) *TierHandler { + return &TierHandler{tierService: tierService} +} + +// GetTiers godoc +// +// @Summary Get all tiers +// @Description Get all tiers from the database +// @Tags Board +// @Accept json +// @Produce json +// @Success 200 {array} dto_request.Tier "List of tiers" +// @Failure 500 {object} map[string]string +// @Router /v1/board/tiers [get] +func (h *TierHandler) GetTiers(c *gin.Context) { + ctx := c.Request.Context() + + tiers, err := h.tierService.List(ctx) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to retrieve tiers", + }) + return + } + + c.JSON(http.StatusOK, tiers) +} + +// GetTier godoc +// +// @Summary Get a Tier by tier number +// @Description Retrieves a single tier from the database. +// @Tags Board +// @Accept json +// @Produce json +// @Param id path int true "Tier number" +// @Success 200 {object} dbmodels.Tier "Tier details" +// @Failure 400 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /v1/board/tiers/{id} [get] +func (h *TierHandler) GetTier(c *gin.Context) { + ctx := c.Request.Context() + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid tier number", + }) + return + } + + tier, err := h.tierService.Get(ctx, int64(id)) + if err != nil { + if err.Error() == "sql: no rows in result set" { + c.JSON(http.StatusNotFound, gin.H{ + "error": "Tier not found", + }) + return + } + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to retrieve tier", + }) + return + } + + c.JSON(http.StatusOK, tier) +} + +// CreateTier godoc +// +// @Summary Creates a new tier +// @Description Creates a new tier in the database. +// @Tags Board +// @Accept json +// @Produce json +// @Param body body domain.Tier true "Tier data" +// @Success 200 {object} map[string]interface{} "Success message with tier number" +// @Failure 400 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /v1/board/tiers [post] +func (h *TierHandler) CreateTier(c *gin.Context) { + ctx := c.Request.Context() + var params dto_request.Tier + + if err := c.ShouldBindJSON(¶ms); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid request body. " + err.Error(), + }) + return + } + + if err := h.tierService.Create(ctx, mapper.ToTierDomain(¶ms)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to create tier. " + err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Tier created successfully", + "tier": params.Tier, + }) +} + +// UpdateTier godoc +// +// @Summary Updates a tier +// @Description Updates a tier in the database +// @Tags Board +// @Accept json +// @Produce json +// @Param id path int true "Tier number" +// @Param body body domain.Tier true "Updated tier data" +// @Success 200 {object} map[string]string "Success message" +// @Failure 400 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /v1/board/tiers/{id} [put] +func (h *TierHandler) UpdateTier(c *gin.Context) { + ctx := c.Request.Context() + var params dto_request.UpdateTier + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid tier number", + }) + return + } + + if err := c.ShouldBindJSON(¶ms); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid request body. " + err.Error(), + }) + return + } + + params.Tier = id + + if err := h.tierService.Update(ctx, int64(id), mapper.ToUpdateTierDomain(¶ms)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to update tier. " + err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Tier updated successfully", + "tier": params.Tier, + }) +} + +// DeleteTier godoc +// +// @Summary Deletes a tier +// @Description Delete a tier from the database +// @Tags Board +// @Accept json +// @Produce json +// @Param id path int true "Tier number" +// @Success 200 {object} map[string]string "Success message" +// @Failure 400 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /v1/board/tiers/{id} [delete] +func (h *TierHandler) DeleteTier(c *gin.Context) { + ctx := c.Request.Context() + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid tier number", + }) + return + } + + if err := h.tierService.Delete(ctx, int64(id)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to delete tier", + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Tier deleted successfully", + }) +} diff --git a/internal/api/routes/v1.go b/internal/api/routes/v1.go index 48f13c6b..8b32c97f 100644 --- a/internal/api/routes/v1.go +++ b/internal/api/routes/v1.go @@ -15,8 +15,8 @@ import ( ) func SetupV1(router *gin.Engine, eventService services.EventsServicer, - announcementService services.AnnouncementServicer, boardService services.BoardServicer) { - + announcementService services.AnnouncementServicer, officerService services.OfficerServicer, positionService services.PositionServicer, tierService services.TierServicer, +) { cfg := config.Load() if cfg.DiscordBotToken == "" && cfg.Env != "development" { log.Fatal("Error: DISCORD_BOT_TOKEN is not set") @@ -38,7 +38,9 @@ func SetupV1(router *gin.Engine, eventService services.EventsServicer, eh := handlers.NewEventHandler(eventService) ah := handlers.NewAnnouncementHandler(announcementService) - bh := handlers.NewBoardHandler(boardService) + oh := handlers.NewOfficerHandler(officerService) + ph := handlers.NewPositionHandler(positionService) + th := handlers.NewTierHandler(tierService) v1 := router.Group("/v1") // Public (read-only) routes @@ -51,14 +53,14 @@ func SetupV1(router *gin.Engine, eventService services.EventsServicer, board := v1.Group("/board") { - board.GET("/officers", bh.GetOfficers) - board.GET("/officers/:id", bh.GetOfficer) + board.GET("/officers", oh.GetOfficers) + board.GET("/officers/:id", oh.GetOfficer) - board.GET("/tiers", bh.GetTiers) - board.GET("/tiers/:id", bh.GetTier) + board.GET("/tiers", th.GetTiers) + board.GET("/tiers/:id", th.GetTier) - board.GET("/positions", bh.GetPositions) - board.GET("/positions/:id", bh.GetPosition) + board.GET("/positions", ph.GetPositions) + board.GET("/positions/:id", ph.GetPosition) } } @@ -77,19 +79,19 @@ func SetupV1(router *gin.Engine, eventService services.EventsServicer, board := protected.Group("/board") { // Officers - board.POST("/officers", bh.CreateOfficer) - board.PUT("/officers/:id", bh.UpdateOfficer) - board.DELETE("/officers/:id", bh.DeleteOfficer) + board.POST("/officers", oh.CreateOfficer) + board.PUT("/officers/:id", oh.UpdateOfficer) + board.DELETE("/officers/:id", oh.DeleteOfficer) // Tiers - board.POST("/tiers", bh.CreateTier) - board.PUT("/tiers/:id", bh.UpdateTier) - board.DELETE("/tiers/:id", bh.DeleteTier) + board.POST("/tiers", th.CreateTier) + board.PUT("/tiers/:id", th.UpdateTier) + board.DELETE("/tiers/:id", th.DeleteTier) // Positions - board.POST("/positions", bh.CreatePosition) - board.PUT("/positions", bh.UpdatePosition) - board.DELETE("/positions", bh.DeletePosition) + board.POST("/positions", ph.CreatePosition) + board.PUT("/positions", ph.UpdatePosition) + board.DELETE("/positions", ph.DeletePosition) } } } diff --git a/internal/api/server.go b/internal/api/server.go index 91f8ccfd..2b51785f 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -16,6 +16,7 @@ import ( mw "github.com/acmcsufoss/api.acmcsuf.com/internal/api/middleware" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/routes" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/services" + "github.com/acmcsufoss/api.acmcsuf.com/internal/repository" ) // Run initializes the database, services, and router, then starts the server. @@ -31,15 +32,33 @@ func Run(ctx context.Context) { // Now we init services & gin router, and then start the server queries := dbmodels.New(db) - eventsService := services.NewEventsService(queries) - announcementService := services.NewAnnouncementService(queries) - boardService := services.NewBoardService(queries, db) + // ---- Repositories ---- + announcementsRepo := repository.NewAnnouncementRepository(queries) + eventsRepo := repository.NewEventRepository(queries) + officerRepo := repository.NewOfficerRepository(queries) + positionRepo := repository.NewPositionRepository(queries) + tierRepo := repository.NewTierRepository(queries) + + // ---- Services ---- + announcementService := services.NewAnnouncementService(announcementsRepo) + eventsService := services.NewEventsService(eventsRepo) + officerService := services.NewOfficerService(officerRepo) + positionService := services.NewPositionService(positionRepo) + tierService := services.NewTierService(tierRepo) + router := gin.Default() router.Use(mw.Cors(), mw.Ratelimiter()) router.SetTrustedProxies(cfg.TrustedProxies) routes.SetupRoot(router) - routes.SetupV1(router, eventsService, announcementService, boardService) + routes.SetupV1( + router, + eventsService, + announcementService, + officerService, + positionService, + tierService, + ) go func() { serverAddr := fmt.Sprintf("localhost:%s", cfg.Port) @@ -79,5 +98,4 @@ func NewDB(ctx context.Context, url string) (*sql.DB, func(), error) { return db, func() { db.Close() }, nil - } diff --git a/internal/api/services/announcement.go b/internal/api/services/announcement.go index 818e06c2..ac89c12b 100644 --- a/internal/api/services/announcement.go +++ b/internal/api/services/announcement.go @@ -2,53 +2,60 @@ package services import ( "context" - - "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" + "fmt" "log" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/domain" + "github.com/acmcsufoss/api.acmcsuf.com/internal/repository" ) type AnnouncementServicer interface { - Service[dbmodels.Announcement, string, dbmodels.CreateAnnouncementParams, - dbmodels.UpdateAnnouncementParams] + Service[domain.Announcement, string, domain.UpdateAnnouncement] } type AnnouncementService struct { - q *dbmodels.Queries + announcementRepository repository.AnnouncementRepository } // compile time check var _ AnnouncementServicer = (*AnnouncementService)(nil) -func NewAnnouncementService(q *dbmodels.Queries) *AnnouncementService { - return &AnnouncementService{q: q} +func NewAnnouncementService(announcementRepository repository.AnnouncementRepository) *AnnouncementService { + return &AnnouncementService{announcementRepository: announcementRepository} } -func (s *AnnouncementService) Get(ctx context.Context, uuid string) (dbmodels.Announcement, error) { - announcement, err := s.q.GetAnnouncement(ctx, uuid) +func (s *AnnouncementService) Get(ctx context.Context, uuid string) (domain.Announcement, error) { + announcement, err := s.announcementRepository.GetByID(ctx, uuid) if err != nil { - return dbmodels.Announcement{}, err + return domain.Announcement{}, err } return announcement, nil } func (s *AnnouncementService) Create(ctx context.Context, - params dbmodels.CreateAnnouncementParams) error { - - if err := s.q.CreateAnnouncement(ctx, params); err != nil { + params domain.Announcement, +) error { + if params.Uuid == "" { + fmt.Println("No id") + return fmt.Errorf("no unique identifier for announcement") + } + if err := s.announcementRepository.Create(ctx, params); err != nil { + fmt.Println("Error with creation:", params, ":", err) return err } return nil } type AnnouncementFilter interface { - Apply(events []dbmodels.Announcement) []dbmodels.Announcement + Apply(events []domain.Announcement) []domain.Announcement } func (s *AnnouncementService) List(ctx context.Context, - filters ...any) ([]dbmodels.Announcement, error) { - - announcements, err := s.q.GetAnnouncements(ctx) + filters ...any, +) ([]domain.Announcement, error) { + announcements, err := s.announcementRepository.GetAll(ctx) if err != nil { + fmt.Println(err) return nil, err } @@ -62,9 +69,9 @@ func (s *AnnouncementService) List(ctx context.Context, } func (s *AnnouncementService) Update(ctx context.Context, uuid string, - params dbmodels.UpdateAnnouncementParams) error { - - err := s.q.UpdateAnnouncement(ctx, params) + params domain.UpdateAnnouncement, +) error { + err := s.announcementRepository.Update(ctx, params) if err != nil { log.Printf("Error updating announcement with UUID %s: %v", uuid, err) return err @@ -73,7 +80,7 @@ func (s *AnnouncementService) Update(ctx context.Context, uuid string, } func (s *AnnouncementService) Delete(ctx context.Context, uuid string) error { - err := s.q.DeleteAnnouncement(ctx, uuid) + err := s.announcementRepository.Delete(ctx, uuid) if err != nil { log.Printf("Error deleting announcement with UUID %s: %v", uuid, err) return err diff --git a/internal/api/services/board.go b/internal/api/services/board.go deleted file mode 100644 index 955379dd..00000000 --- a/internal/api/services/board.go +++ /dev/null @@ -1,170 +0,0 @@ -package services - -import ( - "context" - - "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" -) - -type BoardServicer interface { - // Officer methods - GetOfficer(ctx context.Context, id string) (dbmodels.Officer, error) - ListOfficers(ctx context.Context, filters ...any) ([]dbmodels.Officer, error) - CreateOfficer(ctx context.Context, params dbmodels.CreateOfficerParams) error - UpdateOfficer(ctx context.Context, id string, params dbmodels.UpdateOfficerParams) error - DeleteOfficer(ctx context.Context, id string) error - - // Tier methods - GetTier(ctx context.Context, tierName int64) (dbmodels.Tier, error) - ListTiers(ctx context.Context, filters ...any) ([]dbmodels.Tier, error) - CreateTier(ctx context.Context, params dbmodels.CreateTierParams) error - UpdateTier(ctx context.Context, params dbmodels.UpdateTierParams) error - DeleteTier(ctx context.Context, tierName int64) error - - // Position methods - GetPosition(ctx context.Context, oid string) (dbmodels.Position, error) - ListPositions(ctx context.Context, filters ...any) ([]dbmodels.Position, error) - CreatePosition(ctx context.Context, params dbmodels.CreatePositionParams) error - UpdatePosition(ctx context.Context, params dbmodels.UpdatePositionParams) error - DeletePosition(ctx context.Context, arg dbmodels.DeletePositionParams) error -} - -type BoardService struct { - q *dbmodels.Queries - db dbmodels.DBTX -} - -var _ BoardServicer = (*BoardService)(nil) - -func NewBoardService(q *dbmodels.Queries, db dbmodels.DBTX) *BoardService { - return &BoardService{ - q: q, - db: db, - } -} - -type OfficerFilter interface { - Apply(officers []dbmodels.Officer) []dbmodels.Officer -} - -type TierFilter interface { - Apply(tiers []dbmodels.Tier) []dbmodels.Tier -} - -type PositionFilter interface { - Apply(positions []dbmodels.Position) []dbmodels.Position -} - -// Officer Methods -func (s *BoardService) GetOfficer(ctx context.Context, uuid string) (dbmodels.Officer, error) { - row, err := s.q.GetOfficer(ctx, uuid) - if err != nil { - return dbmodels.Officer{}, err - } - - return dbmodels.Officer{ - Uuid: uuid, - FullName: row.FullName, - Picture: row.Picture, - Github: row.Github, - Discord: row.Discord, - }, nil -} - -func (s *BoardService) ListOfficers(ctx context.Context, filters ...any) ([]dbmodels.Officer, error) { - officers, err := s.q.GetOfficers(ctx) - if err != nil { - return nil, err - } - - result := officers - for _, filter := range filters { - if officerFilter, ok := filter.(OfficerFilter); ok { - result = officerFilter.Apply(result) - } - } - - return result, nil -} - -func (s *BoardService) CreateOfficer(ctx context.Context, params dbmodels.CreateOfficerParams) error { - _, err := s.q.CreateOfficer(ctx, params) - return err -} - -func (s *BoardService) UpdateOfficer(ctx context.Context, uuid string, params dbmodels.UpdateOfficerParams) error { - params.Uuid = uuid - return s.q.UpdateOfficer(ctx, params) -} - -func (s *BoardService) DeleteOfficer(ctx context.Context, uuid string) error { - return s.q.DeleteOfficer(ctx, uuid) -} - -// Tier Methods -func (s *BoardService) GetTier(ctx context.Context, tierName int64) (dbmodels.Tier, error) { - return s.q.GetTier(ctx, tierName) -} - -func (s *BoardService) ListTiers(ctx context.Context, filters ...any) ([]dbmodels.Tier, error) { - tiers, err := s.q.GetTiers(ctx) - if err != nil { - return nil, err - } - - result := tiers - for _, filter := range filters { - if tierFilter, ok := filter.(TierFilter); ok { - result = tierFilter.Apply(result) - } - } - - return result, nil -} - -func (s *BoardService) CreateTier(ctx context.Context, params dbmodels.CreateTierParams) error { - _, err := s.q.CreateTier(ctx, params) - return err -} - -func (s *BoardService) UpdateTier(ctx context.Context, params dbmodels.UpdateTierParams) error { - return s.q.UpdateTier(ctx, params) -} - -func (s *BoardService) DeleteTier(ctx context.Context, tierName int64) error { - return s.q.DeleteTier(ctx, tierName) -} - -// Position Methods -func (s *BoardService) GetPosition(ctx context.Context, oid string) (dbmodels.Position, error) { - return s.q.GetPosition(ctx, oid) -} - -func (s *BoardService) ListPositions(ctx context.Context, filters ...any) ([]dbmodels.Position, error) { - positions, err := s.q.GetPositions(ctx) - if err != nil { - return nil, err - } - - result := positions - for _, filter := range filters { - if positionFilter, ok := filter.(PositionFilter); ok { - result = positionFilter.Apply(result) - } - } - - return result, nil -} - -func (s *BoardService) CreatePosition(ctx context.Context, params dbmodels.CreatePositionParams) error { - _, err := s.q.CreatePosition(ctx, params) - return err -} - -func (s *BoardService) UpdatePosition(ctx context.Context, params dbmodels.UpdatePositionParams) error { - return s.q.UpdatePosition(ctx, params) -} - -func (s *BoardService) DeletePosition(ctx context.Context, arg dbmodels.DeletePositionParams) error { - return s.q.DeletePosition(ctx, arg) -} diff --git a/internal/api/services/event.go b/internal/api/services/event.go index bd76bf61..689a9df4 100644 --- a/internal/api/services/event.go +++ b/internal/api/services/event.go @@ -3,35 +3,40 @@ package services import ( "context" + "fmt" - "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" + "github.com/acmcsufoss/api.acmcsuf.com/internal/domain" + "github.com/acmcsufoss/api.acmcsuf.com/internal/repository" ) type EventsServicer interface { - Service[dbmodels.Event, string, dbmodels.CreateEventParams, dbmodels.UpdateEventParams] + Service[domain.Event, string, domain.UpdateEvent] } type EventsService struct { - q *dbmodels.Queries + eventRepo repository.EventRepository } // this checks that EventsService implements EventsServicers at compile time var _ EventsServicer = (*EventsService)(nil) -func NewEventsService(q *dbmodels.Queries) *EventsService { - return &EventsService{q: q} +func NewEventsService(eventRepo repository.EventRepository) *EventsService { + return &EventsService{eventRepo: eventRepo} } -func (s *EventsService) Get(ctx context.Context, uuid string) (dbmodels.Event, error) { - event, err := s.q.GetEvent(ctx, uuid) +func (s *EventsService) Get(ctx context.Context, uuid string) (domain.Event, error) { + event, err := s.eventRepo.GetByID(ctx, uuid) if err != nil { - return dbmodels.Event{}, err + return domain.Event{}, err } return event, nil } -func (s *EventsService) Create(ctx context.Context, params dbmodels.CreateEventParams) error { - if err := s.q.CreateEvent(ctx, params); err != nil { +func (s *EventsService) Create(ctx context.Context, params domain.Event) error { + if params.Uuid == "" { + return fmt.Errorf("no unique identifier for event") + } + if err := s.eventRepo.Create(ctx, params); err != nil { return err } return nil @@ -39,7 +44,7 @@ func (s *EventsService) Create(ctx context.Context, params dbmodels.CreateEventP // TODO: Move filters to their own file or module or something type EventFilter interface { - Apply(events []dbmodels.Event) []dbmodels.Event + Apply(events []domain.Event) []domain.Event } type HostFilter struct { @@ -49,12 +54,12 @@ type HostFilter struct { // Ensure HostFilter implements EventFilter var _ EventFilter = (*HostFilter)(nil) -func (f *HostFilter) Apply(events []dbmodels.Event) []dbmodels.Event { +func (f *HostFilter) Apply(events []domain.Event) []domain.Event { if f.Host == "" { return events } - filtered := make([]dbmodels.Event, 0) + filtered := make([]domain.Event, 0) for _, event := range events { if event.Host == f.Host { filtered = append(filtered, event) @@ -63,8 +68,8 @@ func (f *HostFilter) Apply(events []dbmodels.Event) []dbmodels.Event { return filtered } -func (s *EventsService) List(ctx context.Context, filters ...any) ([]dbmodels.Event, error) { - events, err := s.q.GetEvents(ctx) +func (s *EventsService) List(ctx context.Context, filters ...any) ([]domain.Event, error) { + events, err := s.eventRepo.GetAll(ctx) if err != nil { return nil, err } @@ -79,16 +84,16 @@ func (s *EventsService) List(ctx context.Context, filters ...any) ([]dbmodels.Ev return result, nil } -func (s *EventsService) Update(ctx context.Context, uuid string, params dbmodels.UpdateEventParams) error { +func (s *EventsService) Update(ctx context.Context, uuid string, params domain.UpdateEvent) error { params.Uuid = uuid - if err := s.q.UpdateEvent(ctx, params); err != nil { + if err := s.eventRepo.Update(ctx, params); err != nil { return err } return nil } func (s *EventsService) Delete(ctx context.Context, uuid string) error { - if err := s.q.DeleteEvent(ctx, uuid); err != nil { + if err := s.eventRepo.Delete(ctx, uuid); err != nil { return err } return nil diff --git a/internal/api/services/interfaces.go b/internal/api/services/interfaces.go index 10db3d9e..c3eb1d38 100644 --- a/internal/api/services/interfaces.go +++ b/internal/api/services/interfaces.go @@ -4,10 +4,10 @@ import ( "context" ) -type Service[T any, ID any, CreateParams any, UpdateParams any] interface { +type Service[T any, ID any, Update any] interface { Get(ctx context.Context, id ID) (T, error) List(ctx context.Context, filters ...any) ([]T, error) - Create(ctx context.Context, params CreateParams) error - Update(ctx context.Context, id ID, params UpdateParams) error + Create(ctx context.Context, params T) error + Update(ctx context.Context, id ID, params Update) error Delete(ctx context.Context, id ID) error } diff --git a/internal/api/services/officer.go b/internal/api/services/officer.go new file mode 100644 index 00000000..3d010981 --- /dev/null +++ b/internal/api/services/officer.go @@ -0,0 +1,84 @@ +package services + +import ( + "context" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/domain" + "github.com/acmcsufoss/api.acmcsuf.com/internal/repository" +) + +type OfficerServicer interface { + Service[domain.Officer, string, domain.UpdateOfficer] +} + +type OfficerService struct { + officerRepo repository.OfficerRepository +} + +var _ OfficerServicer = (*OfficerService)(nil) + +// There used to be a dbmodels.DBTX var here but I don't think it was used? +func NewOfficerService(officerRepo repository.OfficerRepository) *OfficerService { + return &OfficerService{officerRepo: officerRepo} +} + +type OfficerFilter interface { + Apply(officers []domain.Officer) []domain.Officer +} + +// Officer Methods +func (s *OfficerService) Get(ctx context.Context, uuid string) (domain.Officer, error) { + row, err := s.officerRepo.GetByID(ctx, uuid) + if err != nil { + return domain.Officer{}, err + } + + return domain.Officer{ + Uuid: uuid, + FullName: row.FullName, + Picture: row.Picture, + Github: row.Github, + Discord: row.Discord, + }, nil +} + +func (s *OfficerService) List(ctx context.Context, filters ...any) ([]domain.Officer, error) { + officers, err := s.officerRepo.GetAll(ctx) + if err != nil { + return nil, err + } + + result := officers + for _, filter := range filters { + if officerFilter, ok := filter.(OfficerFilter); ok { + result = officerFilter.Apply(result) + } + } + + return result, nil +} + +func (s *OfficerService) Create(ctx context.Context, params domain.Officer) error { + err := s.officerRepo.Create(ctx, params) + if err != nil { + return err + } + return nil +} + +func (s *OfficerService) Update(ctx context.Context, uuid string, params domain.UpdateOfficer) error { + params.Uuid = uuid + err := s.officerRepo.Update(ctx, params) + if err != nil { + return err + } + return nil +} + +func (s *OfficerService) Delete(ctx context.Context, uuid string) error { + err := s.officerRepo.Delete(ctx, uuid) + if err != nil { + return err + } + return nil +} diff --git a/internal/api/services/position.go b/internal/api/services/position.go new file mode 100644 index 00000000..c82fba9a --- /dev/null +++ b/internal/api/services/position.go @@ -0,0 +1,85 @@ +package services + +import ( + "context" + "fmt" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/domain" + "github.com/acmcsufoss/api.acmcsuf.com/internal/repository" +) + +type PositionServicer interface { + Service[domain.Position, string, domain.UpdatePosition] + + DeletePosition(ctx context.Context, arg domain.Position) error +} + +type PositionService struct { + positionRepo repository.PositionRepository +} + +var _ PositionServicer = (*PositionService)(nil) + +// There used to be a dbmodels.DBTX var here but I don't think it was used? +func NewPositionService(positionRepo repository.PositionRepository) *PositionService { + return &PositionService{positionRepo: positionRepo} +} + +type PositionFilter interface { + Apply(positions []domain.Position) []domain.Position +} + +// Position Methods +func (s *PositionService) Get(ctx context.Context, oid string) (domain.Position, error) { + position, err := s.positionRepo.GetByID(ctx, oid) + if err != nil { + return domain.Position{}, err + } + return position, nil +} + +func (s *PositionService) List(ctx context.Context, filters ...any) ([]domain.Position, error) { + positions, err := s.positionRepo.GetAll(ctx) + if err != nil { + return nil, err + } + + result := positions + for _, filter := range filters { + if positionFilter, ok := filter.(PositionFilter); ok { + result = positionFilter.Apply(result) + } + } + + return result, nil +} + +func (s *PositionService) Create(ctx context.Context, params domain.Position) error { + err := s.positionRepo.Create(ctx, params) + if err != nil { + return err + } + return nil +} + +func (s *PositionService) Update(ctx context.Context, uuid string, params domain.UpdatePosition) error { + err := s.positionRepo.Update(ctx, params) + if err != nil { + return err + } + + return nil +} + +func (s *PositionService) DeletePosition(ctx context.Context, arg domain.Position) error { + err := s.positionRepo.DeletePosition(ctx, arg) + if err != nil { + return err + } + return nil +} + +func (s *PositionService) Delete(ctx context.Context, arg string) error { + // This function is here only to satisfy Service interface, use DeletePosition to delete a position + return fmt.Errorf("error: the developer is using the wrong delete function") +} diff --git a/internal/api/services/tier.go b/internal/api/services/tier.go new file mode 100644 index 00000000..192848a1 --- /dev/null +++ b/internal/api/services/tier.go @@ -0,0 +1,73 @@ +package services + +import ( + "context" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/domain" + "github.com/acmcsufoss/api.acmcsuf.com/internal/repository" +) + +type TierServicer interface { + Service[domain.Tier, int64, domain.UpdateTier] +} + +type TierService struct { + tierRepo repository.TierRepository +} + +var _ TierServicer = (*TierService)(nil) + +// There used to be a dbmodels.DBTX var here but I don't think it was used? +func NewTierService(tierRepo repository.TierRepository) *TierService { + return &TierService{tierRepo: tierRepo} +} + +type TierFilter interface { + Apply(tiers []domain.Tier) []domain.Tier +} + +// Tier Methods +func (s *TierService) Get(ctx context.Context, tierName int64) (domain.Tier, error) { + return s.tierRepo.GetByID(ctx, tierName) +} + +func (s *TierService) List(ctx context.Context, filters ...any) ([]domain.Tier, error) { + tiers, err := s.tierRepo.GetAll(ctx) + if err != nil { + return nil, err + } + + result := tiers + for _, filter := range filters { + if tierFilter, ok := filter.(TierFilter); ok { + result = tierFilter.Apply(result) + } + } + + return result, nil +} + +func (s *TierService) Create(ctx context.Context, params domain.Tier) error { + err := s.tierRepo.Create(ctx, params) + if err != nil { + return err + } + return nil +} + +func (s *TierService) Update(ctx context.Context, tierName int64, params domain.UpdateTier) error { + params.Tier = int(tierName) + err := s.tierRepo.Update(ctx, params) + if err != nil { + return err + } + return nil +} + +func (s *TierService) Delete(ctx context.Context, tierName int64) error { + err := s.tierRepo.Delete(ctx, tierName) + if err != nil { + return err + } + return nil +} diff --git a/internal/cli/announcements/post.go b/internal/cli/announcements/post.go index fbb2330a..ef8d64f6 100644 --- a/internal/cli/announcements/post.go +++ b/internal/cli/announcements/post.go @@ -10,10 +10,10 @@ import ( "github.com/charmbracelet/huh" "github.com/spf13/cobra" - "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" "github.com/acmcsufoss/api.acmcsuf.com/internal/cli/config" "github.com/acmcsufoss/api.acmcsuf.com/internal/cli/forms" "github.com/acmcsufoss/api.acmcsuf.com/internal/cli/oauth" + dto_request "github.com/acmcsufoss/api.acmcsuf.com/internal/dto/request" "github.com/acmcsufoss/api.acmcsuf.com/utils" ) @@ -68,8 +68,8 @@ func postAnnouncement(cfg *config.Config) { } // TODO: Use DTO models instaad of dbmodels -func postForm() (*dbmodels.CreateAnnouncementParams, error) { - var payload dbmodels.CreateAnnouncementParams +func postForm() (*dto_request.Announcement, error) { + var payload dto_request.Announcement var err error var ( announceAtStr string @@ -110,8 +110,8 @@ func postForm() (*dbmodels.CreateAnnouncementParams, error) { if err != nil { return nil, err } - payload.DiscordChannelID = utils.StringtoNullString(channelIDStr) - payload.DiscordMessageID = utils.StringtoNullString(messageIDStr) + payload.DiscordChannelID = &channelIDStr + payload.DiscordMessageID = &messageIDStr return &payload, err } diff --git a/internal/cli/announcements/put.go b/internal/cli/announcements/put.go index e4ab8047..036cc089 100644 --- a/internal/cli/announcements/put.go +++ b/internal/cli/announcements/put.go @@ -10,9 +10,9 @@ import ( "github.com/charmbracelet/huh" "github.com/spf13/cobra" - "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" "github.com/acmcsufoss/api.acmcsuf.com/internal/cli/config" "github.com/acmcsufoss/api.acmcsuf.com/internal/cli/oauth" + dto_request "github.com/acmcsufoss/api.acmcsuf.com/internal/dto/request" "github.com/acmcsufoss/api.acmcsuf.com/utils" ) @@ -58,7 +58,7 @@ func putAnnouncements(id string, cfg *config.Config) { } // ----- Update found announceement ----- - var oldPayload dbmodels.CreateAnnouncementParams + var oldPayload dto_request.UpdateAnnouncement err = json.Unmarshal(body, &oldPayload) if err != nil { fmt.Println("Error: failed to unmarshal response body:", err) @@ -99,8 +99,8 @@ func putAnnouncements(id string, cfg *config.Config) { } // TODO: Use DTO models instaad of dbmodels -func putForm(uuid string) (*dbmodels.UpdateAnnouncementParams, error) { - var payload dbmodels.UpdateAnnouncementParams +func putForm(uuid string) (*dto_request.UpdateAnnouncement, error) { + var payload dto_request.UpdateAnnouncement var err error var ( visibilityStr string @@ -134,20 +134,20 @@ func putForm(uuid string) (*dbmodels.UpdateAnnouncementParams, error) { payload.Uuid = uuid // HACK: These conversions won't be necessary once we start using DTO models here if visibilityStr != "" { - payload.Visibility = utils.StringtoNullString(visibilityStr) + payload.Visibility = &visibilityStr } if announceAtStr != "" { timestamp, err := utils.ByteSlicetoUnix([]byte(announceAtStr)) if err != nil { return nil, err } - payload.AnnounceAt = utils.Int64toNullInt64(timestamp) + payload.AnnounceAt = ×tamp } if channelIDStr != "" { - payload.DiscordChannelID = utils.StringtoNullString(channelIDStr) + payload.DiscordChannelID = &channelIDStr } if messageIDStr != "" { - payload.DiscordMessageID = utils.StringtoNullString(messageIDStr) + payload.DiscordMessageID = &messageIDStr } return &payload, nil } diff --git a/internal/cli/events/post.go b/internal/cli/events/post.go index 31fd6d5f..fe7cadb7 100644 --- a/internal/cli/events/post.go +++ b/internal/cli/events/post.go @@ -13,7 +13,6 @@ import ( "github.com/charmbracelet/huh" "github.com/spf13/cobra" - // TODO: db params shouldn't be exposed here "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" "github.com/acmcsufoss/api.acmcsuf.com/internal/cli/config" "github.com/acmcsufoss/api.acmcsuf.com/internal/cli/oauth" @@ -44,18 +43,20 @@ var PostEvent = &cobra.Command{ if startAtString != "" { var err error - payload.StartAt, err = utils.ByteSlicetoUnix([]byte(startAtString)) + startAtUnix, err := utils.ByteSlicetoUnix([]byte(startAtString)) if err != nil { fmt.Println(err) return } + payload.StartAt = startAtUnix if duration != "" { var err error - payload.EndAt, err = utils.TimeAfterDuration(payload.StartAt, duration) + endAtUnix, err := utils.TimeAfterDuration(payload.StartAt, duration) if err != nil { fmt.Println(err) return } + payload.EndAt = endAtUnix } } diff --git a/internal/domain/announcements.go b/internal/domain/announcements.go new file mode 100644 index 00000000..25328c83 --- /dev/null +++ b/internal/domain/announcements.go @@ -0,0 +1,21 @@ +package domain + +import ( + "time" +) + +type Announcement struct { + Uuid string + Visibility string + AnnounceAt time.Time + DiscordChannelID *string + DiscordMessageID *string +} + +type UpdateAnnouncement struct { + Uuid string + Visibility *string + AnnounceAt *time.Time + DiscordChannelID *string + DiscordMessageID *string +} diff --git a/internal/domain/event.go b/internal/domain/event.go new file mode 100644 index 00000000..d8591374 --- /dev/null +++ b/internal/domain/event.go @@ -0,0 +1,23 @@ +package domain + +import ( + "time" +) + +type Event struct { + Uuid string + Location string + StartAt time.Time + EndAt time.Time + IsAllDay bool + Host string +} + +type UpdateEvent struct { + Uuid string + Location *string + StartAt *time.Time + EndAt *time.Time + IsAllDay *bool + Host *string +} diff --git a/internal/domain/officer.go b/internal/domain/officer.go new file mode 100644 index 00000000..6c8e7c88 --- /dev/null +++ b/internal/domain/officer.go @@ -0,0 +1,17 @@ +package domain + +type Officer struct { + Uuid string + FullName string + Picture *string + Github *string + Discord *string +} + +type UpdateOfficer struct { + Uuid string + FullName *string + Picture *string + Github *string + Discord *string +} diff --git a/internal/domain/position.go b/internal/domain/position.go new file mode 100644 index 00000000..6b07cb3a --- /dev/null +++ b/internal/domain/position.go @@ -0,0 +1,19 @@ +package domain + +type Position struct { + Oid string + Semester string + Tier int + FullName string + Title *string + Team *string +} + +type UpdatePosition struct { + Oid string + Semester string + Tier int + FullName string + Title *string + Team *string +} diff --git a/internal/domain/tier.go b/internal/domain/tier.go new file mode 100644 index 00000000..104053a8 --- /dev/null +++ b/internal/domain/tier.go @@ -0,0 +1,15 @@ +package domain + +type Tier struct { + Tier int + Title *string + Tindex *int + Team *string +} + +type UpdateTier struct { + Tier int + Title *string + Tindex *int + Team *string +} diff --git a/internal/dto/request/announcements.go b/internal/dto/request/announcements.go new file mode 100644 index 00000000..c6e30afe --- /dev/null +++ b/internal/dto/request/announcements.go @@ -0,0 +1,17 @@ +package dto_request + +type Announcement struct { + Uuid string `json:"uuid"` + Visibility string `json:"visibility"` + AnnounceAt int64 `json:"announce_at"` + DiscordChannelID *string `json:"discord_channel_id"` + DiscordMessageID *string `json:"discord_message_id"` +} + +type UpdateAnnouncement struct { + Uuid string `json:"uuid"` + Visibility *string `json:"visibility"` + AnnounceAt *int64 `json:"announce_at"` + DiscordChannelID *string `json:"discord_channel_id"` + DiscordMessageID *string `json:"discord_message_id"` +} diff --git a/internal/dto/request/event.go b/internal/dto/request/event.go new file mode 100644 index 00000000..102f3439 --- /dev/null +++ b/internal/dto/request/event.go @@ -0,0 +1,19 @@ +package dto_request + +type Event struct { + Uuid string `json:"uuid"` + Location string `json:"location"` + StartAt int64 `json:"start_at"` + EndAt int64 `json:"end_at"` + IsAllDay bool `json:"is_all_day"` + Host string `json:"host"` +} + +type UpdateEvent struct { + Uuid string `json:"uuid"` + Location *string `json:"location"` + StartAt *int64 `json:"start_at"` + EndAt *int64 `json:"end_at"` + IsAllDay *bool `json:"is_all_day"` + Host *string `json:"host"` +} diff --git a/internal/dto/request/officer.go b/internal/dto/request/officer.go new file mode 100644 index 00000000..be1ca31e --- /dev/null +++ b/internal/dto/request/officer.go @@ -0,0 +1,17 @@ +package dto_request + +type Officer struct { + Uuid string `json:"uuid"` + FullName string `json:"full_name"` + Picture *string `json:"picture"` + Github *string `json:"github"` + Discord *string `json:"discord"` +} + +type UpdateOfficer struct { + Uuid string `json:"uuid"` + FullName *string `json:"full_name"` + Picture *string `json:"picture"` + Github *string `json:"github"` + Discord *string `json:"discord"` +} diff --git a/internal/dto/request/position.go b/internal/dto/request/position.go new file mode 100644 index 00000000..9ab4a4dd --- /dev/null +++ b/internal/dto/request/position.go @@ -0,0 +1,19 @@ +package dto_request + +type Position struct { + Oid string `json:"oid"` + Semester string `json:"semester"` + Tier int `json:"tier"` + FullName string `json:"full_name"` + Title *string `json:"title"` + Team *string `json:"team"` +} + +type UpdatePosition struct { + Oid string `json:"oid"` + Semester string `json:"semester"` + Tier int `json:"tier"` + FullName string `json:"full_name"` + Title *string `json:"title"` + Team *string `json:"team"` +} diff --git a/internal/dto/request/tier.go b/internal/dto/request/tier.go new file mode 100644 index 00000000..1f6c62a6 --- /dev/null +++ b/internal/dto/request/tier.go @@ -0,0 +1,15 @@ +package dto_request + +type Tier struct { + Tier int `json:"tier"` + Title *string `json:"title"` + Tindex *int `json:"t_index"` + Team *string `json:"team"` +} + +type UpdateTier struct { + Tier int `json:"tier"` + Title *string `json:"title"` + Tindex *int `json:"t_index"` + Team *string `json:"team"` +} diff --git a/internal/dto/response/announcements.go b/internal/dto/response/announcements.go new file mode 100644 index 00000000..af348a6b --- /dev/null +++ b/internal/dto/response/announcements.go @@ -0,0 +1,9 @@ +package dto_response + +type Announcement struct { + Uuid string `json:"uuid"` + Visibility string `json:"visibility"` + AnnounceAt int64 `json:"announce_at"` + DiscordChannelID string `json:"discord_channel_id"` + DiscordMessageID string `json:"discord_message_id"` +} diff --git a/internal/dto/response/event.go b/internal/dto/response/event.go new file mode 100644 index 00000000..adbef018 --- /dev/null +++ b/internal/dto/response/event.go @@ -0,0 +1,10 @@ +package dto_response + +type Event struct { + Uuid string `json:"uuid"` + Location string `json:"location"` + StartAt int64 `json:"start_at"` + EndAt int64 `json:"end_at"` + IsAllDay bool `json:"is_all_day"` + Host string `json:"host"` +} diff --git a/internal/dto/response/officer.go b/internal/dto/response/officer.go new file mode 100644 index 00000000..f0fbd76e --- /dev/null +++ b/internal/dto/response/officer.go @@ -0,0 +1,9 @@ +package dto_response + +type Officer struct { + Uuid string `json:"uuid"` + FullName string `json:"full_name"` + Picture string `json:"picture"` + Github string `json:"github"` + Discord string `json:"discord"` +} diff --git a/internal/dto/response/position.go b/internal/dto/response/position.go new file mode 100644 index 00000000..5d061ff9 --- /dev/null +++ b/internal/dto/response/position.go @@ -0,0 +1,10 @@ +package dto_response + +type Position struct { + Oid string `json:"oid"` + Semester string `json:"semester"` + Tier int `json:"tier"` + FullName string `json:"tier"` + Title *string `json:"title"` + Team *string `json:"team"` +} diff --git a/internal/dto/response/tier.go b/internal/dto/response/tier.go new file mode 100644 index 00000000..db901827 --- /dev/null +++ b/internal/dto/response/tier.go @@ -0,0 +1,8 @@ +package dto_response + +type Tier struct { + Tier int `json:"tier"` + Title string `json:"title"` + Tindex int `json:"t_index"` + Team string `json:"team"` +} diff --git a/internal/mapper/domain_to_dto.go b/internal/mapper/domain_to_dto.go new file mode 100644 index 00000000..87ed2ee6 --- /dev/null +++ b/internal/mapper/domain_to_dto.go @@ -0,0 +1,60 @@ +package mapper + +import ( + "github.com/acmcsufoss/api.acmcsuf.com/internal/domain" + + dto_response "github.com/acmcsufoss/api.acmcsuf.com/internal/dto/response" +) + +// File deticated to mapping domain models to dto response models + +func ToAnnouncementDTO(a *domain.Announcement) dto_response.Announcement { + return dto_response.Announcement{ + Uuid: a.Uuid, + Visibility: a.Visibility, + AnnounceAt: a.AnnounceAt.Unix(), + DiscordChannelID: *a.DiscordChannelID, + DiscordMessageID: *a.DiscordMessageID, + } +} + +func ToEventEventDTO(e *domain.Event) dto_response.Event { + return dto_response.Event{ + Uuid: e.Uuid, + Location: e.Location, + StartAt: e.StartAt.Unix(), + EndAt: e.EndAt.Unix(), + IsAllDay: e.IsAllDay, + Host: e.Host, + } +} + +func ToOfficerDTO(o domain.Officer) dto_response.Officer { + return dto_response.Officer{ + Uuid: o.Uuid, + FullName: o.FullName, + Picture: *o.Picture, + Github: *o.Github, + Discord: *o.Discord, + } +} + +func ToPositionDTO(p *domain.Position) dto_response.Position { + return dto_response.Position{ + Oid: p.Oid, + Semester: p.Semester, + Tier: p.Tier, + FullName: p.FullName, + Title: p.Title, + Team: p.Team, + } +} + +func ToTierDTO(t *domain.Tier) dto_response.Tier { + return dto_response.Tier{ + Tier: t.Tier, + Title: *t.Title, + Tindex: *t.Tindex, + Team: *t.Team, + } +} diff --git a/internal/mapper/dto_to_domain.go b/internal/mapper/dto_to_domain.go new file mode 100644 index 00000000..e14a1890 --- /dev/null +++ b/internal/mapper/dto_to_domain.go @@ -0,0 +1,112 @@ +package mapper + +import ( + "time" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/domain" + dto_request "github.com/acmcsufoss/api.acmcsuf.com/internal/dto/request" +) + +func ToAnnouncementDomain(a *dto_request.Announcement) domain.Announcement { + return domain.Announcement{ + Uuid: a.Uuid, + Visibility: a.Visibility, + AnnounceAt: time.Unix(a.AnnounceAt, 0), + DiscordChannelID: a.DiscordChannelID, + DiscordMessageID: a.DiscordMessageID, + } +} + +func ToUpdateAnnouncementDomain(a *dto_request.UpdateAnnouncement) domain.UpdateAnnouncement { + var announceAt time.Time + if a.AnnounceAt != nil { + announceAt = time.Unix(*a.AnnounceAt, 0) + } + return domain.UpdateAnnouncement{ + Uuid: a.Uuid, + Visibility: a.Visibility, + AnnounceAt: &announceAt, + DiscordChannelID: a.DiscordChannelID, + DiscordMessageID: a.DiscordMessageID, + } +} + +func ToEventDomain(e *dto_request.Event) domain.Event { + return domain.Event{ + Location: e.Location, + StartAt: time.Unix(e.StartAt, 0), + EndAt: time.Unix(e.EndAt, 0), + IsAllDay: e.IsAllDay, + Host: e.Host, + } +} + +func ToUpdateEventDomain(e *dto_request.UpdateEvent) domain.UpdateEvent { + startAt := time.Unix(*e.StartAt, 0) + endAt := time.Unix(*e.EndAt, 0) + return domain.UpdateEvent{ + Location: e.Location, + StartAt: &startAt, + EndAt: &endAt, + IsAllDay: e.IsAllDay, + Host: e.Host, + } +} + +func ToOfficerDomain(o *dto_request.Officer) domain.Officer { + return domain.Officer{ + FullName: o.FullName, + Picture: o.Picture, + Github: o.Github, + Discord: o.Discord, + } +} + +func ToUpdateOfficerDomain(o *dto_request.UpdateOfficer) domain.UpdateOfficer { + return domain.UpdateOfficer{ + FullName: o.FullName, + Picture: o.Picture, + Github: o.Github, + Discord: o.Discord, + } +} + +func ToPositionDomain(p *dto_request.Position) domain.Position { + return domain.Position{ + Oid: p.Oid, + Semester: p.Semester, + Tier: p.Tier, + FullName: p.FullName, + Title: p.Title, + Team: p.Team, + } +} + +func ToUpdatePositionDomain(p *dto_request.UpdatePosition) domain.UpdatePosition { + return domain.UpdatePosition{ + Oid: p.Oid, + Semester: p.Semester, + Tier: p.Tier, + FullName: p.FullName, + Title: p.Title, + Team: p.Team, + } +} + +func ToTierDomain(t *dto_request.Tier) domain.Tier { + return domain.Tier{ + Tier: t.Tier, + Title: t.Title, + Tindex: t.Tindex, + Team: t.Team, + } +} + +func ToUpdateTierDomain(t *dto_request.UpdateTier) domain.UpdateTier { + return domain.UpdateTier{ + Tier: t.Tier, + Title: t.Title, + Tindex: t.Tindex, + Team: t.Team, + } +} diff --git a/internal/repository/announcements.go b/internal/repository/announcements.go new file mode 100644 index 00000000..4ba4d51f --- /dev/null +++ b/internal/repository/announcements.go @@ -0,0 +1,66 @@ +package repository + +import ( + "context" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" + "github.com/acmcsufoss/api.acmcsuf.com/internal/domain" +) + +type AnnouncementRepository interface { + Repository[domain.Announcement, string, domain.UpdateAnnouncement] +} + +type announcementRepository struct { + db *dbmodels.Queries +} + +func NewAnnouncementRepository(db *dbmodels.Queries) AnnouncementRepository { + return &announcementRepository{db: db} +} + +func (r *announcementRepository) GetByID(ctx context.Context, id string) (domain.Announcement, error) { + dbAnnouncement, err := r.db.GetAnnouncement(ctx, id) + if err != nil { + return domain.Announcement{}, err + } + + return convertDBAnnouncementToDomain(dbAnnouncement), nil +} + +func (r *announcementRepository) GetAll(ctx context.Context) ([]domain.Announcement, error) { + dbAnnouncement, err := r.db.GetAnnouncements(ctx) + if err != nil { + return nil, err + } + + var eventSlice []domain.Announcement + for _, elm := range dbAnnouncement { + eventSlice = append(eventSlice, convertDBAnnouncementToDomain(elm)) + } + return eventSlice, nil +} + +func (r *announcementRepository) Delete(ctx context.Context, id string) error { + err := r.db.DeleteAnnouncement(ctx, id) + if err != nil { + return err + } + return nil +} + +func (r *announcementRepository) Create(ctx context.Context, args domain.Announcement) error { + err := r.db.CreateAnnouncement(ctx, convertDomainToCreateDBAnnouncement(args)) + if err != nil { + return err + } + return nil +} + +func (r *announcementRepository) Update(ctx context.Context, args domain.UpdateAnnouncement) error { + err := r.db.UpdateAnnouncement(ctx, convertDomainToUpdateDBAnnouncement(args)) + if err != nil { + return err + } + return nil +} diff --git a/internal/repository/conv.go b/internal/repository/conv.go new file mode 100644 index 00000000..5c6d40c4 --- /dev/null +++ b/internal/repository/conv.go @@ -0,0 +1,275 @@ +package repository + +import ( + "database/sql" + "time" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" + "github.com/acmcsufoss/api.acmcsuf.com/internal/domain" +) + +// File for converting Database models into Doamin models + +// ---- Event Converter ---- +func convertDBEventToDomain(dbEvent dbmodels.Event) domain.Event { + return domain.Event{ + Uuid: dbEvent.Uuid, + Location: dbEvent.Location, + StartAt: time.Unix(dbEvent.StartAt, 0), + EndAt: time.Unix(dbEvent.EndAt, 0), + IsAllDay: dbEvent.IsAllDay, + Host: dbEvent.Host, + } +} + +func convertDomainToCreateDBEvent(dEvent domain.Event) dbmodels.CreateEventParams { + return dbmodels.CreateEventParams{ + Uuid: dEvent.Uuid, + Location: dEvent.Location, + StartAt: dEvent.StartAt.Unix(), + EndAt: dEvent.EndAt.Unix(), + IsAllDay: dEvent.IsAllDay, + Host: dEvent.Host, + } +} + +func convertDomainToUpdateDBEvent(dEvent domain.UpdateEvent) dbmodels.UpdateEventParams { + // -- sql null values -- + var loc string + if dEvent.Location != nil { + loc = *dEvent.Location + } + + var start int64 + if dEvent.StartAt != nil { + start = dEvent.StartAt.Unix() + } + + var end int64 + if dEvent.EndAt != nil { + end = dEvent.StartAt.Unix() + } + + var allDay bool + if dEvent.IsAllDay != nil { + allDay = *dEvent.IsAllDay + } + + var host string + if dEvent.Host != nil { + host = *dEvent.Host + } + + return dbmodels.UpdateEventParams{ + Uuid: dEvent.Uuid, + Location: sql.NullString{String: loc, Valid: validString(dEvent.Location)}, + StartAt: sql.NullInt64{Int64: start, Valid: validTime(dEvent.StartAt)}, + EndAt: sql.NullInt64{Int64: end, Valid: validTime(dEvent.EndAt)}, + IsAllDay: sql.NullBool{Bool: allDay, Valid: validBool(dEvent.IsAllDay)}, + Host: sql.NullString{String: host, Valid: validString(dEvent.Host)}, + } +} + +// ---- Officer Converter ---- +func convertDBOfficerToDomain(dbOfficer dbmodels.Officer) domain.Officer { + return domain.Officer{ + Uuid: dbOfficer.Uuid, + FullName: dbOfficer.FullName, + Picture: &dbOfficer.Picture.String, + Github: &dbOfficer.Github.String, + Discord: &dbOfficer.Discord.String, + } +} + +func convertDomainToCreateDBOfficer(dOfficer domain.Officer) dbmodels.CreateOfficerParams { + // -- sql null values -- + pic := dOfficer.Picture + github := dOfficer.Github + discord := dOfficer.Discord + + return dbmodels.CreateOfficerParams{ + Uuid: dOfficer.Uuid, + FullName: dOfficer.FullName, + Picture: sql.NullString{String: *pic, Valid: validString(pic)}, + Github: sql.NullString{String: *github, Valid: validString(pic)}, + Discord: sql.NullString{String: *discord, Valid: validString(pic)}, + } +} + +func convertDomainToUpdateDBOfficer(dOfficer domain.UpdateOfficer) dbmodels.UpdateOfficerParams { + // -- sql null values -- + pic := dOfficer.Picture + github := dOfficer.Github + discord := dOfficer.Discord + + return dbmodels.UpdateOfficerParams{ + Uuid: dOfficer.Uuid, + FullName: *dOfficer.FullName, + Picture: sql.NullString{String: *pic, Valid: validString(pic)}, + Github: sql.NullString{String: *github, Valid: validString(github)}, + Discord: sql.NullString{String: *discord, Valid: validString(discord)}, + } +} + +// ---- Announcement Converter ---- +func convertDBAnnouncementToDomain(dbAnnouncement dbmodels.Announcement) domain.Announcement { + return domain.Announcement{ + Uuid: dbAnnouncement.Uuid, + Visibility: dbAnnouncement.Visibility, + AnnounceAt: time.Unix(dbAnnouncement.AnnounceAt, 0), + DiscordChannelID: &dbAnnouncement.DiscordChannelID.String, + DiscordMessageID: &dbAnnouncement.DiscordMessageID.String, + } +} + +func convertDomainToCreateDBAnnouncement(dAnnouncement domain.Announcement) dbmodels.CreateAnnouncementParams { + // -- sql null values -- + chanID := dAnnouncement.DiscordChannelID + msgID := dAnnouncement.DiscordMessageID + + return dbmodels.CreateAnnouncementParams{ + Uuid: dAnnouncement.Uuid, + Visibility: dAnnouncement.Visibility, + AnnounceAt: dAnnouncement.AnnounceAt.Unix(), + DiscordChannelID: sql.NullString{String: *chanID, Valid: validString(chanID)}, + DiscordMessageID: sql.NullString{String: *msgID, Valid: validString(msgID)}, + } +} + +func convertDomainToUpdateDBAnnouncement(dAnnouncement domain.UpdateAnnouncement) dbmodels.UpdateAnnouncementParams { + // -- sql null values -- + + var vis string + if dAnnouncement.Visibility != nil { + vis = *dAnnouncement.Visibility + } + var announceAt int64 + announceAtPtr := dAnnouncement.AnnounceAt + if announceAtPtr != nil { + announceAt = announceAtPtr.Unix() + } + var chanID string + if dAnnouncement.DiscordChannelID != nil { + chanID = *dAnnouncement.DiscordChannelID + } + + var msgID string + if dAnnouncement.DiscordMessageID != nil { + msgID = *dAnnouncement.DiscordMessageID + } + + return dbmodels.UpdateAnnouncementParams{ + Uuid: dAnnouncement.Uuid, + Visibility: sql.NullString{String: vis, Valid: validString(dAnnouncement.Visibility)}, + AnnounceAt: sql.NullInt64{Int64: announceAt, Valid: validTime(announceAtPtr)}, + DiscordChannelID: sql.NullString{String: chanID, Valid: validString(dAnnouncement.DiscordChannelID)}, + DiscordMessageID: sql.NullString{String: msgID, Valid: validString(dAnnouncement.DiscordMessageID)}, + } +} + +// ---- Tier Converter ---- +func convertDBTierToDomain(dbTier dbmodels.Tier) domain.Tier { + // note: &int(exp) / &(int)(exp) is illegal, so it is split into v and then &v + v := int(dbTier.TIndex.Int64) + return domain.Tier{ + Tier: int(dbTier.Tier), + Title: &dbTier.Title.String, + Tindex: &v, + Team: &dbTier.Team.String, + } +} + +func convertDomainToCreateDBTier(dTier domain.Tier) dbmodels.CreateTierParams { + // -- sql null values -- + title := dTier.Title + tIdx := int64(*dTier.Tindex) + team := dTier.Team + + return dbmodels.CreateTierParams{ + Tier: int64(dTier.Tier), + Title: sql.NullString{String: *title, Valid: validString(title)}, + TIndex: sql.NullInt64{Int64: tIdx, Valid: validInt64(&tIdx)}, + Team: sql.NullString{String: *team, Valid: validString(team)}, + } +} + +func convertDomainToUpdateDBTier(dTier domain.UpdateTier) dbmodels.UpdateTierParams { + // -- sql null values -- + title := dTier.Title + tIdx := int64(*dTier.Tindex) + team := dTier.Team + + return dbmodels.UpdateTierParams{ + Tier: int64(dTier.Tier), + Title: sql.NullString{String: *title, Valid: validString(title)}, + TIndex: sql.NullInt64{Int64: tIdx, Valid: validInt64(&tIdx)}, + Team: sql.NullString{String: *team, Valid: validString(team)}, + } +} + +// ---- Position Converter ---- +func convertDBPositionToDomain(dbPosition dbmodels.Position) domain.Position { + return domain.Position{ + Oid: dbPosition.Oid, + Semester: dbPosition.Semester, + Tier: int(dbPosition.Tier), + FullName: dbPosition.FullName, + Title: &dbPosition.Team.String, + Team: &dbPosition.Team.String, + } +} + +func convertDomainToCreateDBPosition(dPosition domain.Position) dbmodels.CreatePositionParams { + // -- sql null types -- + title := dPosition.Title + team := dPosition.Team + + return dbmodels.CreatePositionParams{ + Oid: dPosition.Oid, + Semester: dPosition.Semester, + Tier: int64(dPosition.Tier), + FullName: dPosition.FullName, + Title: sql.NullString{String: *title, Valid: validString(title)}, + Team: sql.NullString{String: *team, Valid: validString(team)}, + } +} + +func convertDomainToUpdateDBPosition(dPosition domain.UpdatePosition) dbmodels.UpdatePositionParams { + // -- sql null types -- + title := dPosition.Title + team := dPosition.Team + + return dbmodels.UpdatePositionParams{ + Oid: dPosition.Oid, + Semester: dPosition.Semester, + Tier: int64(dPosition.Tier), + FullName: dPosition.FullName, + Title: sql.NullString{String: *title, Valid: validString(title)}, + Team: sql.NullString{String: *team, Valid: validString(team)}, + } +} + +func convertDomainToDeleteDBPosition(dPosition domain.Position) dbmodels.DeletePositionParams { + return dbmodels.DeletePositionParams{ + Oid: dPosition.Oid, + Semester: dPosition.Semester, + Tier: int64(dPosition.Tier), + } +} + +// ---- Functions to check validity ---- +func validInt64(i *int64) bool { + return i != nil +} + +func validString(s *string) bool { + return s != nil +} + +func validBool(b *bool) bool { + return b != nil +} + +func validTime(t *time.Time) bool { + return t != nil +} diff --git a/internal/repository/event.go b/internal/repository/event.go new file mode 100644 index 00000000..1204d347 --- /dev/null +++ b/internal/repository/event.go @@ -0,0 +1,66 @@ +package repository + +import ( + "context" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" + "github.com/acmcsufoss/api.acmcsuf.com/internal/domain" +) + +type EventRepository interface { + Repository[domain.Event, string, domain.UpdateEvent] +} + +type eventRepository struct { + db *dbmodels.Queries +} + +func NewEventRepository(db *dbmodels.Queries) EventRepository { + return &eventRepository{db: db} +} + +func (r *eventRepository) GetByID(ctx context.Context, id string) (domain.Event, error) { + dbEvent, err := r.db.GetEvent(ctx, id) + if err != nil { + return domain.Event{}, err + } + + return convertDBEventToDomain(dbEvent), nil +} + +func (r *eventRepository) GetAll(ctx context.Context) ([]domain.Event, error) { + dbEvent, err := r.db.GetEvents(ctx) + if err != nil { + return nil, err + } + + var eventSlice []domain.Event + for _, elm := range dbEvent { + eventSlice = append(eventSlice, convertDBEventToDomain(elm)) + } + return eventSlice, nil +} + +func (r *eventRepository) Delete(ctx context.Context, id string) error { + err := r.db.DeleteEvent(ctx, id) + if err != nil { + return err + } + return nil +} + +func (r *eventRepository) Create(ctx context.Context, args domain.Event) error { + err := r.db.CreateEvent(ctx, convertDomainToCreateDBEvent(args)) + if err != nil { + return err + } + return nil +} + +func (r *eventRepository) Update(ctx context.Context, args domain.UpdateEvent) error { + err := r.db.UpdateEvent(ctx, convertDomainToUpdateDBEvent(args)) + if err != nil { + return err + } + return nil +} diff --git a/internal/repository/interfaces.go b/internal/repository/interfaces.go new file mode 100644 index 00000000..5b3367e7 --- /dev/null +++ b/internal/repository/interfaces.go @@ -0,0 +1,15 @@ +package repository + +import ( + "context" +) + +type Repository[T any, ID any, Update any] interface { + GetAll(ctx context.Context) ([]T, error) + + GetByID(ctx context.Context, id ID) (T, error) + Delete(ctx context.Context, id ID) error + + Create(ctx context.Context, args T) error + Update(ctx context.Context, args Update) error +} diff --git a/internal/repository/officer.go b/internal/repository/officer.go new file mode 100644 index 00000000..728f239a --- /dev/null +++ b/internal/repository/officer.go @@ -0,0 +1,74 @@ +package repository + +import ( + "context" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" + "github.com/acmcsufoss/api.acmcsuf.com/internal/domain" +) + +type OfficerRepository interface { + Repository[domain.Officer, string, domain.UpdateOfficer] +} + +type officerRepository struct { + db *dbmodels.Queries +} + +func NewOfficerRepository(db *dbmodels.Queries) OfficerRepository { + return &officerRepository{db: db} +} + +func (r *officerRepository) GetAll(ctx context.Context) ([]domain.Officer, error) { + dbOfficers, err := r.db.GetOfficers(ctx) + if err != nil { + return nil, err + } + + var officers []domain.Officer + for _, dbOfficer := range dbOfficers { + officers = append(officers, convertDBOfficerToDomain(dbOfficer)) + } + return officers, nil +} + +func (r *officerRepository) GetByID(ctx context.Context, id string) (domain.Officer, error) { + row, err := r.db.GetOfficer(ctx, id) // Get officers and get officers return completly different things? + if err != nil { + return domain.Officer{}, err + } + + dbOfficer := dbmodels.Officer{ + Uuid: id, + FullName: row.FullName, + Picture: row.Picture, + Github: row.Github, + Discord: row.Discord, + } + + return convertDBOfficerToDomain(dbOfficer), nil +} + +func (r *officerRepository) Delete(ctx context.Context, id string) error { + err := r.db.DeleteOfficer(ctx, id) + if err != nil { + return err + } + return nil +} + +func (r *officerRepository) Create(ctx context.Context, args domain.Officer) error { + _, err := r.db.CreateOfficer(ctx, convertDomainToCreateDBOfficer(args)) + if err != nil { + return err + } + return nil +} + +func (r *officerRepository) Update(ctx context.Context, args domain.UpdateOfficer) error { + err := r.db.UpdateOfficer(ctx, convertDomainToUpdateDBOfficer(args)) + if err != nil { + return err + } + return nil +} diff --git a/internal/repository/position.go b/internal/repository/position.go new file mode 100644 index 00000000..7a0a6ce8 --- /dev/null +++ b/internal/repository/position.go @@ -0,0 +1,73 @@ +package repository + +import ( + "context" + "fmt" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" + "github.com/acmcsufoss/api.acmcsuf.com/internal/domain" +) + +type PositionRepository interface { + Repository[domain.Position, string, domain.UpdatePosition] + DeletePosition(ctx context.Context, args domain.Position) error +} + +type positionRepository struct { + db *dbmodels.Queries +} + +func NewPositionRepository(db *dbmodels.Queries) PositionRepository { + return &positionRepository{db: db} +} + +func (r *positionRepository) GetAll(ctx context.Context) ([]domain.Position, error) { + dbPositions, err := r.db.GetPositions(ctx) + if err != nil { + return nil, err + } + + var positions []domain.Position + for _, dbPosition := range dbPositions { + positions = append(positions, convertDBPositionToDomain(dbPosition)) + } + return positions, nil +} + +func (r *positionRepository) GetByID(ctx context.Context, id string) (domain.Position, error) { + dbPosition, err := r.db.GetPosition(ctx, id) + if err != nil { + return domain.Position{}, err + } + + return convertDBPositionToDomain(dbPosition), nil +} + +func (r *positionRepository) Create(ctx context.Context, args domain.Position) error { + _, err := r.db.CreatePosition(ctx, convertDomainToCreateDBPosition(args)) + if err != nil { + return err + } + return nil +} + +func (r *positionRepository) Update(ctx context.Context, args domain.UpdatePosition) error { + err := r.db.UpdatePosition(ctx, convertDomainToUpdateDBPosition(args)) + if err != nil { + return err + } + return nil +} + +func (r *positionRepository) Delete(ctx context.Context, args string) error { + // Here to satisfy the Repository interface only, use DeletePosition + return fmt.Errorf("error: developer is using the wrong delete function") +} + +func (r *positionRepository) DeletePosition(ctx context.Context, args domain.Position) error { + err := r.db.DeletePosition(ctx, convertDomainToDeleteDBPosition(args)) + if err != nil { + return err + } + return nil +} diff --git a/internal/repository/tier.go b/internal/repository/tier.go new file mode 100644 index 00000000..b0988d92 --- /dev/null +++ b/internal/repository/tier.go @@ -0,0 +1,66 @@ +package repository + +import ( + "context" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/dbmodels" + "github.com/acmcsufoss/api.acmcsuf.com/internal/domain" +) + +type TierRepository interface { + Repository[domain.Tier, int64, domain.UpdateTier] +} + +type tierRepository struct { + db *dbmodels.Queries +} + +func NewTierRepository(db *dbmodels.Queries) TierRepository { + return &tierRepository{db: db} +} + +func (r *tierRepository) GetAll(ctx context.Context) ([]domain.Tier, error) { + dbTiers, err := r.db.GetTiers(ctx) + if err != nil { + return nil, err + } + + var tiers []domain.Tier + for _, dbTier := range dbTiers { + tiers = append(tiers, convertDBTierToDomain(dbTier)) + } + return tiers, nil +} + +func (r *tierRepository) GetByID(ctx context.Context, id int64) (domain.Tier, error) { + dbTier, err := r.db.GetTier(ctx, id) + if err != nil { + return domain.Tier{}, err + } + + return convertDBTierToDomain(dbTier), nil +} + +func (r *tierRepository) Delete(ctx context.Context, id int64) error { + err := r.db.DeleteTier(ctx, id) + if err != nil { + return err + } + return nil +} + +func (r *tierRepository) Create(ctx context.Context, args domain.Tier) error { + _, err := r.db.CreateTier(ctx, convertDomainToCreateDBTier(args)) + if err != nil { + return err + } + return nil +} + +func (r *tierRepository) Update(ctx context.Context, args domain.UpdateTier) error { + err := r.db.UpdateTier(ctx, convertDomainToUpdateDBTier(args)) + if err != nil { + return err + } + return nil +}